ASFRV32IM
ASFRV32IMは250行のVerilogによるRISC-V Unpriviledged ISA 20191213のRV32IM準拠のCPUです。
RISC-V規格に準拠しているか検証するriscv-testを自動テストできるように移植し、OSなしのベアメタルでも実行できるベンチマークソフトのCoreMarkとDhrystoneを移植しました。Coremark/MHzは3.24、DMIPSは1.0495です。ただしASFRV32IMは乗算除算等も1サイクルで動作するシングルサイクルのCPUで、メモリアクセス関係などに非現実な箇所があり、そのまま現実の回路として実装できるものではないので、性能については割り引いて考えてください。
前半でASFRV32IMの動かし方、riscv-testでのテストの実行方法、CoremarkやDhrystoneの移植の仕方について説明し、後半でASFRV32IMの設計と実装の解説をしています。
ASFRV32IMの動かし方
Icarus Verlogを使います。Linuxなら大抵のディストリビューションでパッケージが用意されていると思います。Windows versionもあります。
論理合成
gitでASFRV32IMをダウンロードしてください。
$ git clone https://github.com/asfdrwe/ASFRV32IM.git
CPUの内部状態をダンプするテストベンチ(RV32IM_test.v)と、UARTを使って出力可能なテストベンチ(RV32IM_uart_test.v)と、ベンチマークソフトを動かせるように1千万ticks(500万サイクル)実行するテストベンチ(RV32IM_coremark_test.v)の3つを利用するものを論理合成します。
$ iverilog -o RV32IM RV32IM.v RV32IM_test.v
$ iverilog -o RV32IM_uart RV32IM.v RV32IM_uart_test.v
$ iverilog -o RV32IM_coremark RV32IM.v RV32IM_coremark_test.v
実行
ASFRV32Iと同様に1行8bit16進数で記述されたtest.hexを読み込んで実行します。詳細はASFRV32Iの解説の対応箇所を参考にしてください。
riscv-test
riscv-testはRISC-Vの各命令ごとに正常に動作しているか検証できるツールです。ASFRV32IMのUARTを利用してテスト合格時にPを出力(PASS)、テスト失敗時にFを出力(FAIL)するように修正しています。シェル上でforを活用して、RV32IMすべての命令のテストを実行し、出力にFがないか確認することでRV32IMを正しく実装できているかテストできます。
riscv-testのビルド
$ git clone https://github.com/riscv/riscv-tests
$ cd riscv-tests
$ git submodule update --init --recursive
$ autoconf
riscv-testの修正箇所
riscv-tests_auto.patchを当ててください。
ASFRV32IMは0番地から実行するので0番地から実行するようリンカスクリプトを修正するのと、
diff -uNr riscv-tests/env/p/link.ld riscv-tests.new/env/p/link.ld
--- riscv-tests/env/p/link.ld 2020-12-10 22:58:54.613821678 +0900
+++ riscv-tests.new/env/p/link.ld 2020-12-10 22:28:26.750911012 +0900
@@ -3,7 +3,7 @@
SECTIONS
{
- . = 0x80000000;
+ . = 0x00000000;
.text.init : { *(.text.init) }
. = ALIGN(0x1000);
.tohost : { *(.tohost) }
ASFRV32IMはCSR(Zicsr拡張)を実装していないのでCSR関係のコードの削除と、
diff -uNr riscv-tests/env/p/riscv_test.h riscv-tests.new/env/p/riscv_test.h
--- riscv-tests/env/p/riscv_test.h 2020-12-10 22:58:54.613821678 +0900
+++ riscv-tests.new/env/p/riscv_test.h 2020-12-10 22:51:22.239843787 +0900
@@ -163,71 +163,14 @@
.weak mtvec_handler; \
.globl _start; \
_start: \
- /* reset vector */ \
- j reset_vector; \
...
...
#define RVTEST_CODE_END \
- unimp
+ __loop: \
+ j __loop;
ASFRV32IMのUARTを利用してテスト合格時にPを出力、テスト失敗時にFを出力するように修正しています(それとASFRV32IMではecallは0番地へのジャンプとして実装していますが利用しないように修正しています)。
@@ -236,9 +179,15 @@
#define RVTEST_PASS \
fence; \
li TESTNUM, 1; \
- li a7, 93; \
- li a0, 0; \
- ecall
+ li a1, 0xfff0; \
+ li a2, 'P'; \
+ sb a2, (a1); \
+ nop; \
+ nop; \
+ nop; \
+ li a2, '\n'; \
+ sb a2, (a1); \
+ j __loop;
#define TESTNUM gp
#define RVTEST_FAIL \
@@ -248,7 +197,15 @@
or TESTNUM, TESTNUM, 1; \
li a7, 93; \
addi a0, TESTNUM, 0; \
- ecall
+ li a1, 0xfff0; \
+ li a2, 'F'; \
+ sb a2, (a1); \
+ nop; \
+ nop; \
+ nop; \
+ li a2, '\n'; \
+ sb a2, (a1); \
+ j __loop;
riscv-testのビルド
$ patch -p1 < (ASFRV32IM dir)/riscv-tests_auto.patch
$ ./configure
$ make
テストプログラムはriscv-test/isaにビルドされます。
RV32Iがrv32ui-p-でRV32Mがrv32um-p-なので、freedom-bin2hexを利用してまとめて*.hexに変換します。
$ mkdir ../autotest
$ cp (freedom-bin2hex path)/freedom-bin2hex.py ../autotest
$ cd isa
$ cp -a rv32ui-p-* rv32um-p-* ../../autotest/
$ cd ../../autotest
$ for i in *; do riscv64-unknown-elf-objcopy $i -O binary $i.bin ; python freedom-bin2hex.py -w 8 $i.bin $i.hex ; done
riscv-testの実行
forを使ってrv32ui-p-*とrv32um-p-*をまとめて実行できます。
$ cd ..
$ for i in autotest/*.hex ; do \cp -f "$i" test.hex ; echo "$i" ; ./RV32IM_uart ; done
正しければこんな感じですべてのRV32IM命令でPが出力され正しくRV32IMを実装できていることが確認できます。
autotest/rv32ui-p-add.hex
WARNING: RV32IM.v:11: $readmemh(test.hex): Not enough words in the file for the requested range [0:65535].
P
autotest/rv32ui-p-addi.hex
WARNING: RV32IM.v:11: $readmemh(test.hex): Not enough words in the file for the requested range [0:65535].
P
autotest/rv32ui-p-and.hex
WARNING: RV32IM.v:11: $readmemh(test.hex): Not enough words in the file for the requested range [0:65535].
P
...
Coremarkの移植
Coremarkは主に組み込み用CPUの性能測定に使われるベンチマークソフトです。ベアメタルで実行できる上、CPUが何サイクル実行しているかの情報の取得とUART等の文字出力ができるCPUなら簡単に移植できます。
coremark.patchの修正内容
自作CPUに移植するために修正しないといけない箇所は3箇所です。
barebones/core_portme.cのCORETIMETYPE barebones_clock()の#error以下を修正して何サイクル実行しているかの情報を取得できるようにします。RISC-Vには何サイクル実行しているかを保持するサイクルカウンタのCSRがあるのですが、ASFRV32IMはCSR未対応なので0xfff4番地にメモリマップドIOでサイクルカウンタを埋め込んであり、0xfff4番地の内容を読み込むことで何サイクル実行しているかの情報を取得しています。
diff -uNr coremark.orig/barebones/core_portme.c coremark/barebones/core_portme.c
--- coremark.orig/barebones/core_portme.c 2020-12-04 21:46:26.522006499 +0900
+++ coremark/barebones/core_portme.c 2020-12-04 21:54:59.959006742 +0900
@@ -44,8 +44,10 @@
CORETIMETYPE
barebones_clock()
{
-#error \
- "You must implement a method to measure time in barebones_clock()! This function should return current time.\n"
+ int *i = (int*)0xfff4; // COUNTER ADDRESS
+ unsigned int r;
+ asm volatile ("lw %0, (%1)" : "=r"(r) : "r"(i) );
+ return r;
}
/* Define : TIMER_RES_DIVIDER
Divider to trade off timer resolution and total time that can be
barebones/core_portme.cのvoid portable_init()で#error以下を修正してUARTの初期化コードを実装します。ASFRV32IMでは特に初期化は不要なので単にUART経由でINIT文字列を出力しています。
@@ -129,8 +132,6 @@
void
portable_init(core_portable *p, int *argc, char *argv[])
{
-#error \
- "Call board initialization routines in portable init (if needed), in particular initialize UART!\n"
if (sizeof(ee_ptr_int) != sizeof(ee_u8 *))
{
ee_printf(
@@ -142,6 +143,7 @@
ee_printf("ERROR! Please define ee_u32 to a 32b unsigned type!\n");
}
p->portable_id = 1;
+ ee_printf("INIT\n");
}
/* Function : portable_fini
Target specific final code
barebones/ee_printf.cの void uart_send_char(char c)で#error以下を修正してcをUART経由で出力できるようにします。ASFRV32IMでは、0xfff0番地と0xfff1番地にメモリマップドIOで、0xfff1番地の値が1ならUART出力可能0なら出力不可で、0xfff1番地の値が1のとき0xfff0番地への1バイト書き込みするとASCIIコードで対応する文字が表示されます。これを利用してcの内容を表示する関数uart_send_charを実装しています。
diff -uNr coremark.orig/barebones/ee_printf.c coremark/barebones/ee_printf.c
--- coremark.orig/barebones/ee_printf.c 2020-12-04 21:46:26.524006499 +0900
+++ coremark/barebones/ee_printf.c 2020-12-04 22:01:06.479000086 +0900
@@ -662,7 +662,11 @@
void
uart_send_char(char c)
{
-#error "You must implement the method uart_send_char to use this file!\n";
+ volatile char *c1 = (char*)0xfff0; // UART DATA ADDRESS
+ volatile char *c2 = (char*)0xfff1; // UART FLAG ADDRESS
+
+ while (*c2 != 1) {} // loop unless UART FLAG = 1
+ *c1 = c;
/* Output of a char to a UART usually follows the following model:
Wait until UART is ready
Write char to UART
あとbarebones/core_portme.cで#define CLOCKS_PER_SEC 1000として1秒で1000サイクル(1kHz)動作するようにしたり、ASFRV32IMのリンカスクリプトを利用するなど細かい修正を行っています。
Coremarkのビルド
coremarkを取得しパッチを当てて実行してください。
ビルド
$ git clone https://github.com/eembc/coremark.git
$ cd coremark
$ patch -p1 < ../coremark.patch
$ make PORT_DIR=barebones ITERATIONS=10
$ riscv64-unknown-elf-objcopy -O binary coremark.bin tmp.bin
$ python ../freedom-bin2hex.py -w 8 tmp.bin coremark.hex
$ cp coremark.hex ../test.hex
実行
$ ./RV32IM_coremark
結果
WARNING: RV32IM.v:11: $readmemh(test.hex): Not enough words in the file for the requested range [0:65535].
INIT
2K performance run parameters for coremark.
CoreMark Size : 666
Total ticks : 3083500
Total time (secs): 3083
Iterations/Sec : 0
Iterations : 10
Compiler version : GCC10.1.0
Compiler flags : -O2 -fno-builtin -march=rv32g -mabi=ilp32 -nostdlib -nostartfiles -T barebones/link.ld -DPERFORMANCE_RUN=1
Memory location : STACK
seedcrc : 0xe9f5
[0]crclist : 0xe714
[0]crcmatrix : 0x1fd7
[0]crcstate : 0x8e3a
[0]crcfinal : 0xfcaf
Correct operation validated. See README.md for run and reporting rules.
FINISH
1kHzで10回実行するのに3083秒かかっているので、1kHzの1000倍の速度の1MHzなら10回×1000=10000回を3083秒で実行できることになるので、10000(回/MHz)÷3083秒=3.24(Coremark/MHz)になります。
Dhrystoneの移植
dhrystoneは以前よく使われていたベンチマークです。riscv-testのbenchmarks以下に含まれています。
ベアメタルでも比較的容易に移植できますが、普通にint main()から始まりprintfを使うコードなので修正が必要です。
uartを利用するCoremarkのベアメタル用ee_printfと何サイクル実行しているかの情報を取得するbarebones_clock()をCoremarkのコードからdhrystone/ee_printf.cとして移植し、int main()をvoid main()にしてスタートアップルーチンを追加して(dhrystone/start.S)void main()を呼ぶように修正するなど、Coremarkより大きな修正を行っています。
詳細はdhrystone.patchを見てください。
ビルド
$ cp -a (riscv-tests dir)/benchmark/dhrystone dhrystone
$ cd dhrystone
$ patch -p1 < ../dhrystone.patch
$ make
$ riscv64-unknown-elf-objcopy -O binary dhrystone dhrystone.bin
$ python ../freedom-bin2hex.py -w 8 dhrystone.bin dhrystone.hex
$ cp dhrystone.hex ../test.hex
実行
$ ./RV32IM_coremark
結果
WARNING: RV32IM.v:11: $readmemh(test.hex): Not enough words in the file for the requested range [0:65535].
Dhrystone Benchmark, Version C, Version 2.2
Using barebones_clock(), HZ=1000000
Trying 500 runs through Dhrystone:
Final values of the variables used in the benchmark:
Int_Glob: 5
should be: 5
Bool_Glob: 1
should be: 1
Ch_1_Glob: A
should be: A
Ch_2_Glob: B
should be: B
Arr_1_Glob[8]: 7
should be: 7
Arr_2_Glob[8][7]: 510
should be: Number_Of_Runs + 10
Ptr_Glob->
Ptr_Comp: 65056
should be: (implementation-dependent)
Discr: 0
should be: 0
Enum_Comp: 2
should be: 2
Int_Comp: 17
should be: 17
Str_Comp: DHRYSTONE PROGRAM, SOME STRING
should be: DHRYSTONE PROGRAM, SOME STRING
Next_Ptr_Glob->
Ptr_Comp: 65056
should be: (implementation-dependent), same as above
Discr: 0
should be: 0
Enum_Comp: 1
should be: 1
Int_Comp: 18
should be: 18
Str_Comp: DHRYSTONE PROGRAM, SOME STRING
should be: DHRYSTONE PROGRAM, SOME STRING
Int_1_Loc: 5
should be: 5
Int_2_Loc: 13
should be: 13
Int_3_Loc: 7
should be: 7
Enum_Loc: 1
should be: 1
Str_1_Loc: DHRYSTONE PROGRAM, 1'ST STRING
should be: DHRYSTONE PROGRAM, 1'ST STRING
Str_2_Loc: DHRYSTONE PROGRAM, 2'ND STRING
should be: DHRYSTONE PROGRAM, 2'ND STRING
Microseconds for one run through Dhrystone: 542
Dhrystones per Second: 1844
Dhrystoneは1MHzで動かしています。基準となるVAX11/780が1757なのでDMIPSは
1844÷1757=1.0495です。
ASFRV32IMの設計と実装
ASFRV32Iをベースとしています。ASFRV32Iの設計と実装の詳細は別記事で解説しています。
ASFRV32IMの設計
RV32Mは乗算MUL/MULH/MULHSU/MULHU、除算DIV/DIVU、剰余REM/REMUの8命令からなります。
ASFRV32IMでは、ASFRV32IのRV32Mの演算を処理する信号をALUに送れるようデコーダーを修正してALUへの演算内容を指示するaluconを4bitから5bitに拡張し、ALUを拡張して乗算・除算・剰余を行えるように拡張しています。
ASFRV32IMの実装
ALUはVerilogの乗算・除算・剰余をそのまま使って演算処理を実装しています。そのため実機ではRV32M命令の演算回路は長いパスになると思われ、非常に低いクロックでしか動作しないとは思いますが、RV32IMを簡潔に実装するためにこのような実装にしています。
ASFRV32Iは200行を切るコンパクトなコードにするために見にくくなっている部分がありますが、ASFRV32IMはあまり見にくくならないようにコメント等を残した形なので250行まで増えています。本質的に必要な行数は30行程度だと思います。
ASFRV32IとASFRV32IMをdiff -uNr ASFRV32I/RV32I.v ASFRV32IM/RV32IM.vの出力から比較します。
ASFRV32IからASFRV32IMへの主な変更箇所は、デコーダーの信号線を増やしている箇所と
- wire [3:0] alucon;
+ wire [4:0] alucon;
+ assign alucon = ((op == RFORMAT) || (op == MULDIV)) ? {opcode[30], opcode[25], opcode[14:12]} :
+ ((op == IFORMAT_ALU) && (opcode[14:12] == 3'b101)) ? {opcode[30], opcode[25], opcode[14:12]} : // SRLI or SRAI
+ (op == IFORMAT_ALU) ? {2'b00, opcode[14:12]} : 5'b0;
ALUのfunction [31:0] ALU_EXEC内にRV32M命令用の演算を追加している箇所と
+ 5'b01000: // MUL (MULTIPLE)
+ ALU_EXEC = data1 * data2;
+ 5'b01001: begin // MULH (MULTIPLE)
+ tmpalu = $signed(data1) * $signed(data2);
+ ALU_EXEC = $signed(tmpalu) >>> 32;
+ end
+ 5'b01010: begin // MULHSU (MULTIPLE)
+ tmpalu = $signed(data1) * $signed({1'b0, data2});
+ ALU_EXEC = tmpalu >> 32;
+ end
+ 5'b01011: begin // MULHU (MULTIPLE)
+ tmpalu = data1 * data2;
+ ALU_EXEC = tmpalu >> 32;
+ end
+ 5'b01100: // DIV (DIVIDE)
+ ALU_EXEC = (data2 == 32'b0) ? 32'hffff_ffff :
+ ((data1 == 32'h8000_0000) && (data2 == 32'hffff_ffff)) ? 32'h8000_0000 : $signed($signed(data1) / $signed(data2));
+ 5'b01101: // DIVU (DIVIDE)
+ ALU_EXEC = (data2 == 32'b0) ? 32'hffff_ffff : (data1 / data2);
+ 5'b01110: // REM (DIVIDE REMINDER)
+ ALU_EXEC = (data2 == 32'b0) ? data1 :
+ ((data1 == 32'h8000_0000) && (data2 == 32'hffff_ffff)) ? 32'h0 : $signed($signed(data1) % $signed(data2));
+ 5'b01111: // REMU (DIVIDE REMINDER)
+ ALU_EXEC = (data2 == 32'b0) ? data1 : (data1 % data2);
メモリマップドIOで画面出力用のUARTと何サイクル実行したかという情報を保持するサイクルカウンタを扱うための箇所と
+ // UART OUTPUT and CYCLE COUNTER
+ reg [8:0] uart = 9'b0; // uart[8] for output sign, uart[7:0] for data
+ assign uart_out = uart;
+ localparam [31:0] UART_MMIO_ADDR = 32'h0000_fff0; // ADDRESS 0xfff0 for UART DATA
+ localparam [31:0] UART_MMIO_FLAG = 32'h0000_fff1; // ADDRESS 0xfff1 for UART FLAG
+ reg [31:0] counter = 32'b0;
+ localparam [31:0] COUNTER_MMIO_ADDR = 32'h0000_fff4; // ADDRESS 0xfff4 for COUNTER
+
+ // MEMORY READ
assign mem_data = (mem_rw == 1'b1) ? 32'b0 : // when MEMORY WRITE, the output from MEMORY is 32'b0
+ ((mem_val == 3'b010) && (mem_addr == COUNTER_MMIO_ADDR)) ? counter : // MEMORY MAPPED IO for CLOCK CYCLE COUNTER
+ ((mem_val[1:0] == 2'b00) && (mem_addr == UART_MMIO_FLAG)) ? 8'b1 : // MEMORY MAPPED IO for UART FLAG(always enabled(8'b1))
(mem_val == 3'b000) ? (mem[mem_addr][7] == 1'b1 ? {24'hffffff, mem[mem_addr]} : {24'h000000, mem[mem_addr]}) : // LB
(mem_val == 3'b001) ? (mem[mem_addr + 1][7] == 1'b1 ? {16'hffff, mem[mem_addr + 1], mem[mem_addr]} : {16'h0000, mem[mem_addr + 1], mem[mem_addr]}) : // LH
(mem_val == 3'b010) ? {mem[mem_addr + 3], mem[mem_addr + 2], mem[mem_addr + 1], mem[mem_addr]} : // LW
(mem_val == 3'b100) ? {24'h000000, mem[mem_addr]} : // LBU
(mem_val == 3'b101) ? {16'h0000, mem[mem_addr + 1], mem[mem_addr]} : // LHU
32'b0;
+ // MEMORY WRITE
+ always @(posedge clock) begin
+ if (mem_rw == 1'b1) begin
+ case (mem_val)
+ 3'b000: // SB
+ mem[mem_addr] <= #1 r_data2[7:0];
+ 3'b001: // SH
+ {mem[mem_addr + 1], mem[mem_addr]} <= #1 r_data2[15:0];
+ 3'b010: // SW
+ {mem[mem_addr + 3], mem[mem_addr + 2], mem[mem_addr + 1], mem[mem_addr]} <= #1 r_data2;
+ default: begin end // ILLEGAL
+ endcase
+ end
+ // MEMORY MAPPED IO to UART
+ if ((mem_rw == 1'b1) && (mem_addr == UART_MMIO_ADDR)) begin
+ uart <= #1 {1'b1, r_data2[7:0]};
+ end else begin
+ uart <= #1 9'b0;
+ end
+ end
RV32Mの実装とは関係ないのですがデコーダーの即値周りをASFRV32Iよりシンプルにしている箇所です(あまり変わらない気もしますが)。
- assign i_sext = ((op == IFORMAT_ALU) && ((opcode[14:12] == 3'b001) || (opcode[14:12] == 3'b101))) ? {27'b0, opcode[24:20]} : // SLLI or SRLI or SRAI
- (opcode[31] == 1'b1) ? {20'hfffff, opcode[31:20]} : {20'h00000, opcode[31:20]};
- assign s_sext = (opcode[31] == 1'b1) ? {20'hfffff, opcode[31:25],opcode[11:7]} : {20'h00000, opcode[31:25],opcode[11:7]};
- assign sb_sext = (opcode[31] == 1'b1) ? {19'h7ffff, opcode[31], opcode[7], opcode[30:25], opcode[11:8], 1'b0} : {19'h00000, opcode[31], opcode[7], opcode[30:25], opcode[11:8], 1'b0};
- assign u_sext = {opcode[31:12], 12'b0};
- assign uj_sext = (opcode[31] == 1'b1) ? {11'h7ff, opcode[31], opcode[19:12], opcode[20], opcode[30:21], 1'b0} : {11'h000, opcode[31], opcode[19:12], opcode[20], opcode[30:21], 1'b0};
- assign imm = ((op == IFORMAT_ALU) || (op == IFORMAT_LOAD) || (op == IFORMAT_JALR)) ? i_sext :
- (op == SFORMAT) ? s_sext :
- (op == SBFORMAT) ? sb_sext :
- ((op == UFORMAT_LUI) || (op == UFORMAT_AUIPC)) ? u_sext :
- (op == UJFORMAT) ? uj_sext : 32'b0;
- assign alucon = (op == RFORMAT) ? {opcode[30], opcode[14:12]} :
- (op == IFORMAT_ALU) ? ((opcode[14:12] == 3'b101) ? {opcode[30], opcode[14:12]} : // SRLI or SRAI
- {1'b0, opcode[14:12]}) : 4'b0;
+ assign imm[31:20] = ((op == UFORMAT_LUI) || (op == UFORMAT_AUIPC)) ? opcode[31:20] :
+ (opcode[31] == 1'b1) ? 12'hfff : 12'b0;
+ assign imm[19:12] = ((op == UFORMAT_LUI) || (op == UFORMAT_AUIPC) || (op == UJFORMAT)) ? opcode[19:12] :
+ (opcode[31] == 1'b1) ? 8'hff : 8'b0;
+ assign imm[11] = (op == SBFORMAT) ? opcode[7] :
+ ((op == UFORMAT_LUI) || (op == UFORMAT_AUIPC)) ? 1'b0 :
+ (op == UJFORMAT) ? opcode[20] : opcode[31];
+ assign imm[10:5] = ((op == UFORMAT_LUI) || (op == UFORMAT_AUIPC)) ? 6'b0 : opcode[30:25];
+ assign imm[4:1] = ((op == IFORMAT_ALU) || (op == IFORMAT_LOAD) || (op == IFORMAT_JALR) || (op == UJFORMAT)) ? opcode[24:21] :
+ ((op == SFORMAT) || (op == SBFORMAT)) ? opcode[11:8] : 4'b0;
+ assign imm[0] = ((op == IFORMAT_ALU) || (op == IFORMAT_LOAD) || (op == IFORMAT_JALR)) ? opcode[20] :
+ (op == SFORMAT) ? opcode[7] : 1'b0;
+
+ assign alucon = ((op == RFORMAT) || (op == MULDIV)) ? {opcode[30], opcode[25], opcode[14:12]} :
+ ((op == IFORMAT_ALU) && (opcode[14:12] == 3'b101)) ? {opcode[30], opcode[25], opcode[14:12]} : // SRLI or SRAI
+ (op == IFORMAT_ALU) ? {2'b00, opcode[14:12]} : 5'b0;
まとめ
自作のCPUでCoremark等の通常のプログラムを実行するために必要な事項を解説しました。Cのプログラムをコンパイルして実行できるとCPUを作れたという実感が湧いてくると思います。