Vivado-HLSを使って簡単なRISC-V CPUを作ってみている。
まずは性能を度外視して動くものを作る。 次に、性能を向上させるための様々な試行を実施する。 まずは、標準的なテストパタンをPassさせるために命令を追加していこう。
一般的なCPUと同じで、まずは命令のデコード、デコードに応じて命令の動作の記述、そしてプログラムカウンタの更新と、これらの処理をすべてC言語で記述し、Vivado-HLSで高位合成させてみる。
まず、テストベンチを作成した。これはrv32ui-p-simpleテストパタンを逆アセンブリし、32bit単位でhexを並べてメモリに格納しておく。 これで、テストパタン完成だ。
riscv64-unknown-elf-objcopy -O binary rv32ui-p-simple rv32ui-p-simple.bin hexdump -v -e ' 1/4 "%08x " "\n"' rv32ui-p-simple.bin > rv32ui-p-simple.hex
rv32ui-p-simple.hex
04c0006f 34202f73 00800f93 03ff0a63 00900f93 03ff0663 00b00f93 03ff0263 ...
これを高位合成CPUのテスト環境で動作させるために、test_cpu_hls.cpp
(cpu_hlsのテストパタン環境)でファイルとしてロードする。このあたり、C言語の関数がそのまま使えるので便利だ。
test_cpu_hls.cpp
int main () { uint32_t memory[2048]; FILE *fp; if ((fp = fopen ("test.hex", "r")) == NULL) { perror ("test.hex"); exit (1); } int idx = 0; while (fscanf(fp, "%08x", &memory[idx]) != EOF) { fprintf (stdout, "memory[%d] <= %08x\n", idx, memory[idx]); idx ++; } ...
そして、これをデコードして命令を実行してく。 cpu_hls内に、以下の命令のデコーダとデータパスを実装した。
rv32_cpu.hpp
typedef enum { CSRRW , CSRRS , CSRRC , CSRRWI, CSRRSI, CSRRCI, LUI, AUIPC, ADDI, SLTI, SLTIU, XORI, ORI, ANDI, SLLI, SRLI, SRAI, ADD, SUB, SLL ,SLT, SLTU, XOR, SRL, SRA, OR, AND, LW, SW, JAL, JALR, BEQ, BNE, BLT, BGE, BLTU, BGEU, NOP, WFI } inst_rv32_t;
これらに対して、以下のように命令のデータパスを追加していく。
... case ORI : { reg_data = u_rv32_cpu.read_reg(rs1) | u_rv32_cpu.ExtractIField (inst); u_rv32_cpu.write_reg(rd, reg_data); break; } case ANDI : { reg_data = u_rv32_cpu.read_reg(rs1) & u_rv32_cpu.ExtractIField (inst); u_rv32_cpu.write_reg(rd, reg_data); break; } case ADD : { reg_data = u_rv32_cpu.read_reg(rs1) + u_rv32_cpu.read_reg(rs2); u_rv32_cpu.write_reg(rd, reg_data); break; } case SUB : { reg_data = u_rv32_cpu.read_reg(rs1) - u_rv32_cpu.read_reg(rs2); u_rv32_cpu.write_reg(rd, reg_data); break; } ...
かなり冗長な記述をしているが、これで回路規模がどのようになるのかは検証していない。 もう少し最適化の余地がありそうな気がする。
そして、さらに迷ったのが、高位合成デザイン内でfprintf()
などのデバッガを仕込んでいる場合、これを合成時に除去するためのdefineは、__SYNTHESIS__
というものを使うらしい。
FPGAの部屋を調べると書いてあった。ありがたい。
cpu_hls.cpp
#ifndef __SYNTHESIS__ FILE *cpu_log; if ((cpu_log = fopen("cpu.log", "w")) == NULL) { perror ("cpu.log"); } #endif // __SYNTHESIS__ #ifndef __SYNTHESIS__ fprintf(cpu_log, "[%08x] : %08x DASM(%08x)\n", pc, inst, inst); #endif // __SYNTHESIS__
fprintf()でログを出力し、その内容を解析した。
spike-dasm < cpu.log
結果は以下のようになった。fenceはまだ実装していないのでそこで停止してしまったが、概ね動作しているような気がする。ちゃんとレジスタ書き込み値も出力した方が良いな。
[00000000] : 04c0006f j pc + 0x4c [0000004c] : f1402573 csrr a0, mhartid [00000050] : 00051063 bnez a0, pc + 0 [00000054] : 00000297 auipc t0, 0x0 [00000058] : 01028293 addi t0, t0, 16 [0000005c] : 30529073 csrw mtvec, t0 [00000060] : 18005073 csrwi satp, 0 [00000064] : 00000297 auipc t0, 0x0 [00000068] : 02028293 addi t0, t0, 32 [0000006c] : 30529073 csrw mtvec, t0 [00000070] : 800002b7 lui t0, 0x80000 [00000074] : fff28293 addi t0, t0, -1 [00000078] : 3b029073 csrw pmpaddr0, t0 [0000007c] : 01f00293 li t0, 31 [00000080] : 3a029073 csrw pmpcfg0, t0 [00000084] : 00000297 auipc t0, 0x0 [00000088] : 01828293 addi t0, t0, 24 [0000008c] : 30529073 csrw mtvec, t0 [00000090] : 30205073 csrwi medeleg, 0 [00000094] : 30305073 csrwi mideleg, 0 [00000098] : 30405073 csrwi mie, 0 [0000009c] : 00000193 li gp, 0 [000000a0] : 00000297 auipc t0, 0x0 [000000a4] : f6428293 addi t0, t0, -156 [000000a8] : 30529073 csrw mtvec, t0 [000000ac] : 00100513 li a0, 1 [000000b0] : 01f51513 slli a0, a0, 31 [000000b4] : 00054863 bltz a0, pc + 16 [000000b8] : 0ff0000f fence