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が合成されます。