8.Cコンパイラ、アセンブラ
とりあえず、開発スナップショット版は、GCC(CYGWIN)を使います。
LCC4.2もコンパイルしてみました。LCCはアセンブラソースを吐きます。これをGCCのアセンブラ?(as)でアセンブルしてもOKです。
例の剰余演算でも乗算コードを吐かないため、こちらの方がソースとしては理解しやすいかもしれません。
8.1スタートアップコード
PLASAM PROJECTの資産を流用します。GCCが吐くフォーマットは、ELFというバイナリフォーマットです。これを、YACCのメモリに載せるためにPLASAM作者から、convert.cが提供されています。(YACCでは、改造版convertc_tak.cを使用、以下a.exe、改造の許可はPLASAM作者であるSteve Rhoadsさんから頂いています。)中身は、筆者も理解していませんが、このプログラムは、スタートアップのオブジェクトコードを書き換えています。
以下は、PLASAM作者から提供されているboot.asmの冒頭部分です。
アセンブラにコメントが書いている通りですが、$gpの設定、$4,$5の設定を行っているようです。
$4,$5は、RAM領域を指し、これで、ゼロクリアした後、C言語のメインに行きます。
################################################################## # TITLE: Boot Up Code # AUTHOR: Steve Rhoads (rhoadss@yahoo.com) # DATE CREATED: 1/12/02 # FILENAME: boot.asm # PROJECT: Plasma CPU core # COPYRIGHT: Software placed into the public domain by the author. # Software 'as is' without warranty. Author liable for nothing. # DESCRIPTION: # Initializes the stack pointer and jumps to main2(). ################################################################## .text .align 2 .globl entry .ent entry entry: .set noreorder #These eight instructions must be the first instructions. #convert.exe will correctly initialize $gp lui $gp,0 ori $gp,$gp,0 #convert.exe will set $4=.sbss_start $5=.bss_end lui $4,0 ori $4,$4,0 lui $5,0 ori $5,$5,0 lui $sp,0 ori $sp,$sp,0xfff0 #initialize stack pointer $BSS_CLEAR: sw $0,0($4) slt $3,$4,$5 bnez $3,$BSS_CLEAR addiu $4,$4,4 jal main2 nop |
逆アセンブルリストを見てみましょう。
0: 3c1c0000 lui $gp,0x0 4: 379c0000 ori $gp,$gp,0x0 8: 34040000 li $a0,0x0 c: 34050000 li $a1,0x0 10: 341d0fec li $sp,0xfec 14: ac800000 sw $zero,0($a0) 18: 0085182a slt $v1,$a0,$a1 1c: 1460fffd bnez $v1,0x14 20: 24840004 addiu $a0,$a0,4 24: 0c000232 jal 0x8c8 |
この段階では、未だ、$gpも,$a0,$a1も0になっています。この後、a.exe(convertc_tak.c)が適切な値に設定してくれています。
これを、シミュレータで確かてみましょう。POWER ON後、0番地から実行を始めます。下の図の場合、3c1c0000が0番地からフェッチした命令になります。(PCは、このとき既に次の番地++4になっている) $gpの上位16ビットをゼロクリアしています。次に下位16ビットに8c6c番地を設定しています。結果、$gpを32ビット00008c6c番地に設定しています。どこから、この番地がでできたかは不明ですが、実際のアクセスでは、RAMが4KBなので、ちゃんとIMMEDIATE OFFSETが加算され、4KBのアクセスになります。同じように$a0,$a1もc80、d18に設定されているのが分かります。$spも0f18で、4KB以下になっています。(そうなるように小さいプログラムしか組めませんが。)
GCCが吐くMAPファイルを見ると$gp、$a0,$a1に対応したアドレスが見れます。
Allocating common symbols Common symbol size file result_buffer 0x8 test_ram4k_demo.o char_ptr 0x4 test_ram4k_demo.o buffer 0x78 test_ram4k_demo.o buf 0x2 test_ram4k_demo.o read_ptr 0x4 test_ram4k_demo.o sym 0x1 test_ram4k_demo.o Memory Configuration Name Origin Length Attributes *default* 0x00000000 0xffffffff Linker script and memory map Address of section .text set to 0x0 LOAD boot.o LOAD test_ram4k_demo.o .text 0x00000000 0xa98 0x00000000 _ftext=. *(.text) .text 0x00000000 0x80 boot.o 0x00000054 putchar 0x00000000 entry 0x00000060 puts 0x0000004c isr_enable .text 0x00000080 0xa18 test_ram4k_demo.o 0x00000868 strcpy 0x000000d8 print 0x000000c8 read_uart 0x000008c8 main2 0x000002d0 getsym 0x00000a70 parse_error 0x000006a0 strrev 0x00000798 calculator 0x00000704 itoa 0x00000114 print_long 0x00000898 calculator_test 0x000003ec expression 0x00000a20 set_interrupt_address 0x00000368 evaluate_number 0x00000a44 init_parser 0x000004dc term 0x00000080 print_uart 0x00000120 interrupt 0x000005f0 factor 0x000000b8 putc_uart 0x000002b8 print_longlong *(.stub) *(.gnu.warning) *(.gnu.linkonce.t*) *(.mips16.fn.*) *(.mips16.call.*) .init *(.init) .fini *(.fini) 0x00000a98 _ecode=. .reginfo 0x00000a98 0x18 *(.reginfo) .reginfo 0x00000a98 0x18 boot.o .reginfo 0x00000ab0 0x18 test_ram4k_demo.o .ctors *(.ctors) .dtors *(.dtors) .eh_frame *(.eh_frame) .gcc_except_table *(.gcc_except_table) .sdeinit *(.sdeinit) .sdefini *(.sdefini) .rodata 0x00000ab0 0x1cc *(.rodata) .rodata 0x00000ab0 0x1cc test_ram4k_demo.o *(.rdata) *(.gnu.linkonce.r*) .rodata1 *(.rodata1) 0x00000c7c _etext=. 0x00000c7c PROVIDE (etext, .) .data 0x00000c7c 0x0 0x00000c7c _fdata=. *(.data) *(.gnu.linkonce.d*) .data1 *(.data1) 0x00008c6c _gp=(.+0x7ff0) .lit8 *(.lit8) .lit4 *(.lit4) .sdata 0x00000c7c 0x4 *(.sdata) .sdata 0x00000c7c 0x4 test_ram4k_demo.o 0x00000c7c int_flag *(.gnu.linkonce.s*) 0x00000c80 _edata=. 0x00000c80 PROVIDE (edata, .) 0x00000c80 .=ALIGN(0x8) 0x00000c80 __bss_start=. 0x00000c80 _fbss=__bss_start .sbss 0x00000c80 0x15 *(.sbss) *(.scommon) .scommon 0x00000c80 0x15 test_ram4k_demo.o 0x00000c80 result_buffer 0x00000c88 char_ptr 0x00000c8c buf 0x00000c90 read_ptr 0x00000c94 sym .bss 0x00000ca0 0x78 *(.dynbss) *(.bss) *(COMMON) COMMON 0x00000ca0 0x78 test_ram4k_demo.o 0x00000ca0 buffer 0x00000d18 _end=. 0x00000d18 PROVIDE (end, .) .stab *(.stab) .stabstr *(.stabstr) .comment *(.comment) .debug *(.debug) .line *(.line) .debug_srcinfo *(.debug_srcinfo) .debug_sfnames *(.debug_sfnames) .debug_aranges *(.debug_aranges) .debug_pubnames *(.debug_pubnames) .debug_info *(.debug_info) .debug_abbrev *(.debug_abbrev) .debug_line *(.debug_line) .debug_frame *(.debug_frame) .debug_str *(.debug_str) .debug_loc *(.debug_loc) .debug_macinfo *(.debug_macinfo) .debug_weaknames *(.debug_weaknames) .debug_funcnames *(.debug_funcnames) .debug_typenames *(.debug_typenames) .debug_varnames *(.debug_varnames) .mdebug *(.mdebug) .rel.text *(.rel.text) *(.rel.gnu.linkonce.t*) .rela.text *(.rela.text) *(.rela.gnu.linkonce.t*) .rel.data *(.rel.data) *(.rel.gnu.linkonce.d*) .rela.data *(.rela.data) *(.rela.gnu.linkonce.d*) .rel.sdata *(.rel.sdata) *(.rel.gnu.linkonce.s*) .rela.sdata *(.rela.sdata) *(.rela.gnu.linkonce.s*) .rel.rodata *(.rel.rodata) *(.rel.gnu.linkonce.r*) .rela.rodata *(.rela.rodata) *(.rela.gnu.linkonce.r*) .gptab.sdata *(.gptab.data) *(.gptab.sdata) .gptab.sbss *(.gptab.bss) *(.gptab.sbss) OUTPUT(test.exe elf32-bigmips) |
なお、スタックは、使える限りの上限に設定した方が、スタック溢れの確率を減らせるので、この例(4KB)では、以下のように記述しています。$ff0から、UARTや、RTLシミュレーションのデバッグポートを配置したので、$fecとしています。最初の設定では、a.exeにより上書きされてしまいますが、main2に行く前のディレイドブランチにより、$SPは、$fecに設定されます。
################################################################## # TITLE: Boot Up Code # AUTHOR: Steve Rhoads (rhoadss@yahoo.com) # DATE CREATED: 1/12/02 # FILENAME: boot.asm # PROJECT: Plasma CPU core # COPYRIGHT: Software placed into the public domain by the author. # Software 'as is' without warranty. Author liable for nothing. # DESCRIPTION: # Initializes the stack pointer and jumps to main2(). ################################################################## .text .align 2 .globl entry .ent entry entry: .set noreorder #These nine instructions must be the first instructions #convert.exe will correctly initialize $gp lui $gp,0 ori $gp,$gp,0 #convert.exe will set $4=.sbss_start $5=.bss_end ori $4,$0,0 ori $5,$0,0 ori $sp,$0,0x00fec #initialize stack pointer $BSS_CLEAR: sw $0,0($4) slt $3,$4,$5 bnez $3,$BSS_CLEAR addiu $4,$4,4 jal main2 ori $sp,$0,0x00fec #initialize stack pointer |
下は、d18番地に0を書いた後、8c8番地(main2)に飛ぶ前に$spをfecに設定している部分のシミュレーション波形です。
以上から、コンパイルから、RAM容量に応じたRTLシミュレーションにいたる手順は以下の通りです。
これで、RAM初期化ファイルができました。codexxは、QurtasのWizardで2ポートRAMを生成する際に指定しました。Wizardで生成したRAMがRTLシミュレーション時に自動的にcodexxを読みます。(このとき、内部的には、HEXファイルをVerilog形式に直しています。)
Xilinxで注意することがあります。RTLシミュレーション時は、生成ツールの出力を使うのです問題ないのですが、「ゲートシミュレーションおよび、実際のFPGAでは、CoregenでRegenerateを行う必要がある」ということです。
また、このWizardで生成したRAMを論理合成すれば、初期化付RAMが合成されます。