Cyclone V SoCでのAMPアプリケーションの起動

画像

私の以前の記事を読んでいるなら、おそらくこのトピックに興味があり、もっと知りたいと思うでしょう。 この記事では、2つの異なるBaremetalアプリケーションを異なるSoC Cyclone Vコアで起動するという非常にプライベートでシンプルなタスクを検討します。本質的に、このようなシステムはAMP-非同期マルチプロセッシングと呼ばれます。 ロシア語では、このようなシステムの作成に関する別のより正確で詳細なガイドが見つからないことを忘れてしまったので、読んでください!



はじめに



読者はすでにアルテラHWマネージャとSoCALの標準ライブラリに精通していることが理解されています。 しかし、それでも、それらについていくつか話してみましょう。 SoC抽象化層(SoCAL)には、HPSレジスタを直接制御するためのビット、バイト、ワードの便利な設定/読み取りのための低レベル関数が含まれています。 ハードウェアマネージャー(HWマネージャー)は、ベアメタルアプリケーション、ドライバー、BPSなどを記述するためのより複雑な機能のセットです。 このアドレス/ ip / altera / hps / altera_hps / doc /または.hファイルのドキュメントを必ずお読みください。



プログラムをダウンロードする



最初に、プログラムがどのようにダウンロードされるかを覚えておく必要があります。私の最初の記事では、これについてはあまり言われませんでした。



HPSブートプロセスにはいくつかの段階があります。それらを理解してみましょう...



スイッチをオンにした直後に、BootRomと呼ばれるCortex-A9フラッシュメモリに直接配置されたコードが実行されます。 変更したり、その内容を見ることもできません。 これは一次初期化に使用され、次のステップでブートプロセスをSSBL(Second Stage Boot Loader、略称Preloader)に転送します。 プロセスを理解するために知っておく必要があるのは、まずBootRomコードがプリローダーのダウンロードソースを選択し、外部の物理的なBSELピンに焦点を当てていることです...



したがって、BootRomコードが実行された後、クロック、SDRAMなどの設定に必要なプリローダーがロードを開始します。 プログラムの実行開始後...


プリローダーをロードした後に何が起こるかを詳しく見てみましょう。 実際には、この後プログラムは実行を開始しますが、メイン関数main()からすぐには実行されません。 その前に、_main()関数が実行され、その主なタスクは、スキャッタファイルで指定されたメモリ内のアドレスにアプリケーションをマッピングすることです。 これは、アプリケーションのエントリポイントが、作成するmain()関数コードの先頭ではなく、コードを記述するときに非表示の特別な_main()ユーティリティ関数であり、コンパイル中にmain()の前に表示されることを意味します。 おそらく誰もがすでにこれを知っているのかもしれませんが、その時はそれが私にとって啓示だったので、エントリポイントはmain()の先頭にあると思いました。



コア



説明されているすべてのプロセスは、常に最初のcpu0コアで実行され、2番目のコアは常にリセット状態です。 2番目のコアを開始するには、RSTMGRグループのMPUMODRSTレジスタの対応するビットをリセットする必要があります。 さて、SYSMGRグループのCPU1STARTADDRレジスタにPCの開始アドレスを設定します。 ただし、PCの電源を入れた後、cpu1は0x0です。 Preloaderを0x0で実行した後は、何も役に立たないため、cpu1を実行する前にBootROMコードを0x0に配置する必要があります。 CPU1STARTADDRレジスタが読み取られ、その後PCが目的の値に設定されるのはBootROMコードからのみであることがわかりました。 判明したように、このコードを配置することは、一見したほど簡単ではありません。 これを行うには、HWマネージャーのalt_addr_space_remap関数、alt_address_space.hファイルが必要です。



alt_addr_space_remap(ALT_ADDR_SPACE_MPU_ZERO_AT_BOOTROM, ALT_ADDR_SPACE_NONMPU_ZERO_AT_SDRAM, ALT_ADDR_SPACE_H2F_ACCESSIBLE, ALT_ADDR_SPACE_LWH2F_ACCESSIBLE);
      
      





急いで喜ばないでください。BootROMを0x0にするのに十分ではありません。 L2キャッシュアドレスフィルターを構成する必要があります。 alt_addr_space_remap関数の説明には、BootROMを0x0に配置する必要がある場合、次のようにこのフィルターを構成し、関数の後にコードを配置する必要があるかどうかが記載されています。



 uint32_t addr_filt_start; uint32_t addr_filt_end; alt_l2_addr_filter_cfg_get(&addr_filt_start, &addr_filt_end); if (addr_filt_start != L2_CACHE_ADDR_FILTERING_START_RESET) { alt_l2_addr_filter_cfg_set(L2_CACHE_ADDR_FILTERING_START_RESET, addr_filt_end); }
      
      





その後のみ開始アドレスを設定し、カーネルを開始できます。



 alt_write_word(ALT_SYSMGR_ROMCODE_CPU1STARTADDR_ADDR, ALT_SYSMGR_ROMCODE_CPU1STARTADDR_VALUE_SET(0x100000)); //set PC of cpu1 to 0x00100000 alt_write_word(ALT_RSTMGR_MPUMODRST_ADDR, alt_read_byte(ALT_RSTMGR_MPUMODRST_ADDR) & ALT_RSTMGR_MPUMODRST_CPU1_CLR_MSK);
      
      





それでは、次は何ですか? そして、プロジェクトの構造を少し理解する必要があります。







最も最適なのは、AMPプロジェクトのこの構造です。 Vectorsブロックは、さまざまなプロセッサの割り込みベクトルと分岐を定義します。 割り込みベクトルは、各プロセッサに共通です。 残念ながら、このブロックはアセンブラーでしか作成できませんが、幸いなことにゼロから作成するのではなく、HW lib alt_interrupt_armcc.sライブラリファイルを編集するだけです。 必要な割り込みベクター、割り込みスタック、FPU VFP \ NEONのサポートを通知します。 必要なスプリッターを追加しましょう。



編集前のalt_interrupt_armcc.s
  PRESERVE8 AREA VECTORS, CODE, READONLY ENTRY EXPORT alt_interrupt_vector IMPORT __main EXPORT alt_int_handler_irq [WEAK] alt_interrupt_vector Vectors LDR PC, alt_reset_addr LDR PC, alt_undef_addr LDR PC, alt_svc_addr LDR PC, alt_prefetch_addr LDR PC, alt_abort_addr LDR PC, alt_reserved_addr LDR PC, alt_irq_addr LDR PC, alt_fiq_addr alt_reset_addr DCD alt_int_handler_reset alt_undef_addr DCD alt_int_handler_undef alt_svc_addr DCD alt_int_handler_svc alt_prefetch_addr DCD alt_int_handler_prefetch alt_abort_addr DCD alt_int_handler_abort alt_reserved_addr DCD alt_int_handler_reserve alt_irq_addr DCD alt_int_handler_irq alt_fiq_addr DCD alt_int_handler_fiq alt_int_handler_reset B alt_premain alt_int_handler_undef B alt_int_handler_undef alt_int_handler_svc B alt_int_handler_svc alt_int_handler_prefetch B alt_int_handler_prefetch alt_int_handler_abort B alt_int_handler_abort alt_int_handler_reserve B alt_int_handler_reserve alt_int_handler_irq B alt_int_handler_irq alt_int_handler_fiq B alt_int_handler_fiq ;===== AREA ALT_INTERRUPT_ARMCC, CODE, READONLY alt_premain FUNCTION ; Enable VFP / NEON. MRC p15, 0, r0, c1, c0, 2 ; Read CP Access register ORR r0, r0, #0x00f00000 ; Enable full access to NEON/VFP (Coprocessors 10 and 11) MCR p15, 0, r0, c1, c0, 2 ; Write CP Access register ISB MOV r0, #0x40000000 ; Switch on the VFP and NEON hardware VMSR fpexc, r0 ; Set EN bit in FPEXC B __main ENDFUNC ;===== AREA ALT_INTERRUPT_ARMCC, CODE, READONLY EXPORT alt_int_fixup_irq_stack ; void alt_int_fixup_irq_stack(uint32_t stack_irq); ; This is the same implementation of GNU but for ARMCC. alt_int_fixup_irq_stack FUNCTION ; r4: stack_sys MRS r3, CPSR MSR CPSR_c, #(0x12 :OR: 0x80 :OR: 0x40) MOV sp, r0 MSR CPSR_c, r3 BX lr ENDFUNC END
      
      





編集後のalt_interrupt_armcc.s
  PRESERVE8 PRESERVE8 AREA VECTORS, CODE, READONLY ENTRY EXPORT alt_interrupt_vector IMPORT __main EXPORT alt_int_handler_irq [WEAK] IMPORT secondaryCPUsInit alt_interrupt_vector Vectors LDR PC, alt_reset_addr LDR PC, alt_undef_addr LDR PC, alt_svc_addr LDR PC, alt_prefetch_addr LDR PC, alt_abort_addr LDR PC, alt_reserved_addr LDR PC, alt_irq_addr LDR PC, alt_fiq_addr alt_reset_addr DCD alt_int_handler_reset alt_undef_addr DCD alt_int_handler_undef alt_svc_addr DCD alt_int_handler_svc alt_prefetch_addr DCD alt_int_handler_prefetch alt_abort_addr DCD alt_int_handler_abort alt_reserved_addr DCD alt_int_handler_reserve alt_irq_addr DCD alt_int_handler_irq alt_fiq_addr DCD alt_int_handler_fiq alt_int_handler_reset B alt_premain alt_int_handler_undef B alt_int_handler_undef alt_int_handler_svc B alt_int_handler_svc alt_int_handler_prefetch B alt_int_handler_prefetch alt_int_handler_abort B alt_int_handler_abort alt_int_handler_reserve B alt_int_handler_reserve alt_int_handler_irq B alt_int_handler_irq alt_int_handler_fiq B alt_int_handler_fiq ;===== AREA ALT_INTERRUPT_ARMCC, CODE, READONLY alt_premain FUNCTION IF {TARGET_FEATURE_NEON} || {TARGET_FPU_VFP} ; Enable VFP / NEON. MRC p15, 0, r0, c1, c0, 2 ; Read CP Access register ORR r0, r0, #0x00f00000 ; Enable full access to NEON/VFP (Coprocessors 10 and 11) MCR p15, 0, r0, c1, c0, 2 ; Write CP Access register ISB MOV r0, #0x40000000 ; Switch on the VFP and NEON hardware VMSR fpexc, r0 ; Set EN bit in FPEXC ENDIF MRC p15, 0, r0, c0, c0, 5 ; Read CPU ID register ANDS r0, r0, #0x03 ; Mask off, leaving the CPU ID field BEQ primaryCPUInit ; jump to cpu0 code init BNE secondaryCPUsInit ; jump to cpu1 code init primaryCPUInit ;jump to main() B __main ENDFUNC ;===== AREA ALT_INTERRUPT_ARMCC, CODE, READONLY EXPORT alt_int_fixup_irq_stack ; void alt_int_fixup_irq_stack(uint32_t stack_irq); ; This is the same implementation of GNU but for ARMCC. alt_int_fixup_irq_stack FUNCTION ; r4: stack_sys MRS r3, CPSR MSR CPSR_c, #(0x12 :OR: 0x80 :OR: 0x40) MOV sp, r0 MSR CPSR_c, r3 BX lr ENDFUNC END
      
      





もちろん、別のファイルにsecondaryCPUsInit関数を追加する必要があります



start_cpu1.s
  PRESERVE8 AREA CPU1, CODE, READONLY ENTRY IMPORT eth IMPORT ||Image$$ARM_LIB_STACKHEAP$$ZI$$Base|| IMPORT ||Image$$ARM_LIB_STACKHEAP$$ZI$$Length|| IMPORT ||Image$$ARM_LIB_STACKHEAP$$ZI$$Limit|| cpu1_stackheap_base DCD ||Image$$ARM_LIB_STACKHEAP$$ZI$$Base|| cpu1_stackheap_lenth DCD ||Image$$ARM_LIB_STACKHEAP$$ZI$$Length|| cpu1_stackheap_limit DCD ||Image$$ARM_LIB_STACKHEAP$$ZI$$Limit|| Mode_USR EQU 0x10 Mode_FIQ EQU 0x11 Mode_IRQ EQU 0x12 Mode_SVC EQU 0x13 Mode_ABT EQU 0x17 Mode_UNDEF EQU 0x1B Mode_SYS EQU 0x1F Len_FIQ_Stack EQU 0x1000 Len_IRQ_Stack EQU 0x1000 I_Bit EQU 0x80 ; when I bit is set, IRQ is disabled F_Bit EQU 0x40 ; when F bit is set, FIQ is disabled EXPORT secondaryCPUsInit secondaryCPUsInit FUNCTION ; stack_base could be defined above, or located in a scatter file LDR R0, cpu1_stackheap_limit MRC p15, 0, r1, c0, c0, 5 ; Read CPU ID register ANDS r1, r1, #0x03 ; Mask off, leaving the CPU ID field SUB r0, r0, r1, LSL #14 ; Stack -0x4000 for cpu1 ; Enter each mode in turn and set up the stack pointer MSR CPSR_c, #Mode_FIQ:OR:I_Bit:OR:F_Bit ; Interrupts disabled MOV sp, R0 SUB R0, R0, #Len_FIQ_Stack MSR CPSR_c, #Mode_IRQ:OR:I_Bit:OR:F_Bit ; Interrupts disabled MOV sp, R0 SUB R0, R0, #Len_IRQ_Stack MSR CPSR_c, #Mode_SVC:OR:I_Bit:OR:F_Bit ; Interrupts disabled MOV sp, R0 ; Leave processor in SVC mode ; Enables the SCU MRC p15, 4, r0, c15, c0, 0 ; Read periph base address LDR r1, [r0, #0x0] ; Read the SCU Control Register ORR r1, r1, #0x1 ; Set bit 0 (The Enable bit) STR r1, [r0, #0x0] ; Write back modifed value ; ; Join SMP ; --------- MRC p15, 0, r0, c0, c0, 5 ; Read CPU ID register ANDS r0, r0, #0x03 ; Mask off, leaving the CPU ID field MOV r1, #0xF ; Move 0xF (represents all four ways) into r1 ;secureSCUInvalidate AND r0, r0, #0x03 ; Mask off unused bits of CPU ID MOV r0, r0, LSL #2 ; Convert into bit offset (four bits per core) AND r1, r1, #0x0F ; Mask off unused bits of ways MOV r1, r1, LSL r0 ; Shift ways into the correct CPU field MRC p15, 4, r2, c15, c0, 0 ; Read periph base address STR r1, [r2, #0x0C] ; Write to SCU Invalidate All in Secure State ;joinSMP ; SMP status is controlled by bit 6 of the CP15 Aux Ctrl Reg MRC p15, 0, r0, c1, c0, 1 ; Read ACTLR MOV r1, r0 ORR r0, r0, #0x040 ; Set bit 6 CMP r0, r1 MCRNE p15, 0, r0, c1, c0, 1 ; Write ACTLR ;enableMaintenanceBroadcast MRC p15, 0, r0, c1, c0, 1 ; Read Aux Ctrl register MOV r1, r0 ORR r0, r0, #0x01 ; Set the FW bit (bit 0) CMP r0, r1 MCRNE p15, 0, r0, c1, c0, 1 ; Write Aux Ctrl register B main_cpu1 ENDFUNC END
      
      





このコードを追加したばかりで、DS-5フォルダーのサンプルからオリジナルを取得したことは認めます。 スタックのセットアップのみをB main_cpu1



し、 B main_cpu1



の最後に関数に移動しました。 さて、SCUが必要であるように見えますが、私はそれを残し、残りには触れませんでした。何が起こっているかをよりよく理解するために、スキャッタファイルを解析する必要があります。



スキャッタファイル
LD_SDRAM 0x00100000 0x80000000 ;SDRAM_load region for MPU from 1 Mb to 3 Gb. DE1-SoC has 2 Gb of DDR memory

{

VECTORS +0

{

* (VECTORS, +FIRST)

}



APP_CODE +0

{

* (+RO, +RW, +ZI)

}



;Application heap and stack cpu0

ARM_LIB_STACKHEAP +0 EMPTY 8000

{ }



CPU1_CODE 0x00200000 FIXED 0x00100000

{

start_cpu1.o(CPU1, +FIRST)

main_sc.o(+RO, +RW, +ZI)

}



}






VECTORSは、SDRAMの先頭の0x00100000(alt_interrupt_armcc.sに記述されています)にあります。0x0に設定することはできません。CycloneV ハードプロセッサシステムテクニカルリファレンスマニュアルをご覧ください。 APP_CODE領域には、2番目のコアのmain()関数を除くすべてのコード(最初のコアのmain()およびその他の外部関数)が含まれます。



ARM_LIB_STACKHEAPは、スタックとヒープ用の予約語であり、サイズが8000バイトで、余裕を持って使用されます。 この行により、_main()関数でスタックを自動的に構成できます。 2番目のコアでは、start_cpu1.sファイルでこれを自分で行います。 STACKHEAPの下限から4000バイトまで後退するため、スタックのオーバーラップは発生しません。 最適なスタックサイズを選択する方法はまだありません。



CPU1_CODE領域は0x00200000から始まり、サイズは1 MBです。 別のmain_sc.cファイルに記述されたmain_cpu1()関数の前に、2番目のカーネルstart_cpu1.sを開始するためのファイルのアセンブラーコードがあります。 スキャッタファイルでは、目的のアドレスにファイルコードを個別に配置する場合は、.o拡張子を指定する必要があります。



したがって、実際には1つのプロジェクトに2つの異なるプログラムがあります。 デバッガーの設定で、ターゲットをDebug Cortex-A9x2 SMPに変更してから、2つのコア間でプロセスを切り替えることができます。



ボーナス



2つのコアで2つの異なるプログラムを起動する問題を解決する必要がある場合、両方のコアでMMUとキャッシュを有効にする方法を知っておくと役立ちます。 これがないと、プログラムはLEDを点滅させるよりも難しくなり、非常に遅くなります。







最初のコアのMMUとキャッシュ
 #include "alt_cache.h" #include "alt_mmu.h" /* MMU Page table - 16KB aligned at 16KB boundary */ #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) static uint32_t __attribute__ ((aligned (0x4000))) alt_pt_storage[4096]; static void *alt_pt_alloc(const size_t size, void *context) static void mmu_init(void) { uint32_t *ttb1 = NULL; // Populate the page table with sections (1 MiB regions). ALT_MMU_MEM_REGION_t regions[] = { // Memory area: 4 mb { .va = (void *)0x00000000, .pa = (void *)0x00000000, .size = 0x00400000, .access = ALT_MMU_AP_PRIV_ACCESS, .attributes = ALT_MMU_ATTR_WBA, .shareable = ALT_MMU_TTB_S_SHAREABLE, .execute = ALT_MMU_TTB_XN_DISABLE, .security = ALT_MMU_TTB_NS_SECURE }, // Device area: Everything else { .va = (void *)0x00400000, .pa = (void *)0x00400000, .size = 0xffc00000, .access = ALT_MMU_AP_PRIV_ACCESS, .attributes = ALT_MMU_ATTR_DEVICE_NS, .shareable = ALT_MMU_TTB_S_NON_SHAREABLE, .execute = ALT_MMU_TTB_XN_ENABLE, .security = ALT_MMU_TTB_NS_SECURE } }; alt_mmu_init(); alt_mmu_va_space_storage_required(regions, ARRAY_SIZE(regions)); alt_mmu_va_space_create(&ttb1, regions, ARRAY_SIZE(regions), alt_pt_alloc, alt_pt_storage); alt_mmu_va_space_enable(ttb1); } int main() { mmu_init(); alt_cache_system_enable(); }
      
      





これが、最初のコアのコードの外観です。 MMUとデータおよび命令キャッシュはコアごとに異なるため、2番目のコアのコードでは、L2は最初のコアによって既に初期化されているため、同様のMMU初期化関数を記述し、対応するキャッシュのみを有効にする必要があります。



2番目のコアのMMUとキャッシュ
 int main_cpu1() { mmu_init2(); alt_cache_l1_enable_all(); }
      
      





この構成は確実に機能します。



割り込みについて少し説明する価値があります。 ここではすべてが簡単で、最初にGICをオンにします(最初のコアで1回だけこれを行うだけで十分です)。次に、各コアでCPUに対して純粋に割り込みを個別に初期化し、有効にする必要があります。 これには関数が使用されます。



 alt_int_global_init(); alt_int_global_enable(); alt_int_cpu_init(); alt_int_cpu_enable();
      
      





割り込みが発生すると、カウンタは目的のベクトルに移動する必要があります。これは一度だけ宣言できます。 このため、2番目のコアの初期化もVECTORS領域から開始され、その条件をstart_cpu1ファイルに渡します。 それ以外の場合は、同じ名前の同じベクトルを再宣言する必要がありますが、これは1つのプロジェクトで行うことはできません。



一般的に、私は極端な「倒錯」さえしようとしました。 2つの完全に異なるプロジェクトを作成してコンパイルしましたが、重複しないようにコードを異なる場所に配置しました。 .axfを.binに変換しました。 最初のカーネルのコードでは、2番目のコアのコードのmain()の場所にカウンターアドレスを正確に構成しました。 次に、Hexを介して、エディターは2つのファイルを1つに縫い付け、アドレスにコードを正しく配置しました。 すべてうまくいきましたが、どういうわけかくだらない。 はい、そしてそのような奇跡をデバッグすることはまったく便利ではありません。 私はそれが悪い考えだと思ったが、確認するのはただ面白かった。 読んでくれた皆さんに感謝します!



文学



  1. 一般的に、必要なARMコンパイラarmlinkユーザーガイドのバージョンのドキュメントで、スキャター構文に関するすべての詳細情報を探してください。
  2. ARMコンパイラarmasmユーザーガイドのアセンブラについて。



All Articles