第6章
RV64Iの実装
これまでに、RISC-Vの32ビットの基本整数命令セットであるRV32IのCPUを実装しました。RISC-Vには64ビットの基本整数命令セットとしてRV64Iが定義されています。本章では、RV32IのCPUをRV64Iにアップグレードします。
では、具体的にRV32IとRV64Iは何が違うのでしょうか?まず、RV64IではXLENが32ビットから64ビットに変更され、レジスタの幅や各種演算命令の演算の幅が64ビットになります。それに伴い、32ビット幅での整数演算を行う命令、64ビット幅でロードストアを行う命令が追加されます(表6.1)。また、演算の幅が64ビットに広がるだけではなく、一部の命令の動作が少し変わります(表6.2)。
表6.1: RV64Iで追加される命令
命令 | 動作 |
ADD[I]W | 32ビット単位で加算を行う。結果は符号拡張する |
SUBW | 32ビット単位で減算を行う。結果は符号拡張する |
SLL[I]W | レジスタの値を0 ~ 31ビット左論理シフトする。結果は符号拡張する |
SRL[I]W | レジスタの値を0 ~ 31ビット右論理シフトする。結果は符号拡張する |
SRA[I]W | レジスタの値を0 ~ 31ビット右算術シフトする。結果は符号拡張する |
LWU | メモリから32ビット読み込む。結果はゼロで拡張する |
LD | メモリから64ビット読み込む |
SD | メモリに64ビット書き込む |
表6.2: RV64Iで変更される命令
命令 | 変更後の動作 |
SLL[I] | 0 ~ 63ビット左論理シフトする |
SRL[I] | 0 ~ 63ビット右論理シフトする |
SRA[I] | 0 ~ 63ビット右算術シフトする |
LUI | 32ビットの即値を生成する。結果は符号拡張する |
AUIPC | 32ビットの即値を符号拡張したものにpcを足し合わせる |
LW | メモリから32ビット読み込む。結果は符号拡張する |
実装のテストにはriscv-testsを利用します。RV64I向けのテストはrv64ui-p-
から始まるテストです。命令を実装するたびにテストを実行することで、命令が正しく実行できていることを確認します。
6.1 XLENの変更
レジスタの幅が32ビットから64ビットに変わるということは、XLENが32から64に変わるということです。eeiパッケージに定義しているXLEN
を64に変更します(リスト6.1)。RV64Iになっても命令の幅(ILEN)は32ビットのままです。
リスト6.1: リスト6.1: XLENを変更する (eei.veryl)
const XLEN: u32 = 64;
6.1.1 SLL[I]、SRL[I]、SRA[I]命令を変更する
RV32Iでは、シフト命令はrs1の値を0 ~ 31ビットシフトする命令として定義されています。これがRV64Iでは、rs1の値を0 ~ 63ビットシフトする命令に変更されます。
これに対応するために、ALUのシフト演算する量を5ビットから6ビットに変更します(リスト6.2)。I形式の命令(SLLI、SRLI、SRAI)のときは即値の下位6ビット、R形式の命令(SLL、SRL、SRA)のときはレジスタの下位6ビットを利用します。
リスト6.2: リスト6.2: シフト命令でシフトする量を変更する (alu.veryl)
let sll: UIntX = op1 << op2[5:0];
let srl: UIntX = op1 >> op2[5:0];
let sra: SIntX = $signed(op1) >>> op2[5:0];
6.1.2 LUI、AUIPC命令を変更する
RV32Iでは、LUI命令は32ビットの即値をそのままレジスタに格納する命令として定義されています。これがRV64Iでは、32ビットの即値を64ビットに符号拡張した値を格納する命令に変更されます。AUIPC命令も同様で、即値にPCを足す前に、即値を64ビットに符号拡張します。
この対応ですが、XLENを64に変更した時点ですでに完了しています(リスト6.3)。そのため、コードの変更の必要はありません。
リスト6.3: リスト6.3: U形式の即値はXLENビットに拡張されている (inst_decoder.veryl)
let imm_u: UIntX = {bits[31] repeat XLEN - $bits(imm_u_g) - 12, imm_u_g, 12'b0};
6.1.3 CSRを変更する
MXLEN(=XLEN)が64ビットに変更されると、CSRの幅も64ビットに変更されます。そのため、mtvec、mepc、mcauseレジスタの幅を64ビットに変更する必要があります。
しかし、mtvec、mepc、mcauseレジスタはXLENビットのレジスタ(UIntX
)として定義しているため、変更の必要はありません。また、mtvec、mepc、mcauseレジスタはMXLENを基準に定義されており、RV32IからRV64Iに変わってもフィールドに変化はないため、対応は必要ありません。
唯一、書き込みマスクの幅を広げる必要があります(リスト6.4)。
リスト6.4: リスト6.4: CSRの書き込みマスクの幅を広げる (csrunit.veryl)
const MTVEC_WMASK : UIntX = 'hffff_ffff_ffff_fffc;
const MEPC_WMASK : UIntX = 'hffff_ffff_ffff_fffc;
const MCAUSE_WMASK: UIntX = 'hffff_ffff_ffff_ffff;
6.1.4 LW命令を変更する
LW命令は32ビットの値をロードする命令です。RV64Iでは、LW命令の結果が64ビットに符号拡張されるようになります。これに対応するため、memunitモジュールのrdata
の割り当てのLW部分を変更します(リスト6.5)。
リスト6.5: リスト6.5: LW命令のメモリの読み込み結果を符号拡張する (memunit.veryl)
2'b10 : {D[31] repeat W - 32, D[31:0]},
また、XLENが64に変更されたことで、幅をMEM_DATA_WIDTH
(=32)として定義しているreq_wdata
の代入文のビット幅が左右で合わなくなってしまっています。ビット幅を合わせるために、rs2の下位MEM_DATA_WIDTH
ビットだけを切り取ります(リスト6.6)。
リスト6.6: リスト6.6: 左辺と右辺でビット幅を合わせる (memunit.veryl)
case state {
State::Init: if is_new & inst_is_memop(ctrl) {
state = State::WaitReady;
req_wen = inst_is_store(ctrl);
req_addr = addr;
req_wdata = rs2[MEM_DATA_WIDTH - 1:0] << {addr[1:0], 3'b0};
6.1.5 riscv-testsでテストする
RV32I向けのテストの実行
まず、RV32I向けのテストが正しく動くことを確認します(リスト6.7)。
リスト6.7: リスト6.7: RV32I向けのテストを実行する
$ make build
$ make sim VERILATOR_FLAGS="-DTEST_MODE"
$ python3 test/test.py -r obj_dir/sim test/share rv32ui-p-
...
PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-srl.bin.hex
Test Result : 40 / 40
RV32I向けのテストにすべて成功しました。しかし、rv32ui-p-ma_data
は失敗するはずです(リスト5.14)。これは、riscv-testsのRV32I向けのテストは、XLENが64のときはテストを実行せずに成功とするためです(リスト6.8)。
リスト6.8: リスト6.8: rv32ui-p-addはXLENが64のときにテストせずに成功する (rv32ui-p-add.dump)
00000050 <reset_vector>:
...
13c: 00100513 li a0,1 ← a0 = 1
140: 01f51513 slli a0,a0,0x1f ← a0を31ビット左シフト
144: 00054c63 bltz a0,15c <reset_vector+0x10c> ← a0が0より小さかったらジャンプ
148: 0ff0000f fence
14c: 00100193 li gp,1 ← gp=1 (テスト成功) にする
150: 05d00893 li a7,93
154: 00000513 li a0,0
158: 00000073 ecall ← trap_vectorにジャンプして終了
riscv-testsは、a0に1を代入した後、a0を31ビット左シフトします。XLENが32のとき、a0の最上位ビット(符号ビット)が1になり、a0は0より小さくなります。XLENが64のとき、a0の符号は変わらないため、a0は0より大きくなります。これを利用して、XLENが32ではないときはtrap_vector
にジャンプして、テスト成功として終了しています。
RV64I向けのテストの実行
それでは、RV64I向けのテストを実行します(リスト6.9)。RV64I向けのテストは名前がrv64ui-p-
から始まります、
リスト6.9: リスト6.9: RV64I向けのテストを実行する
$ make build
$ make sim VERILATOR_FLAGS="-DTEST_MODE"
$ python3 test/test.py -r obj_dir/sim test/share rv64ui-p-
...
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-add.bin.hex
...
Test Result : 14 / 52
ADD命令のテストを含む、ほとんどのテストに失敗してしまいました。これはriscv-testsのテストが、まだ未実装の命令を含むためです(リスト6.10)。
リスト6.10: リスト6.10: ADD命令のテストは未実装の命令(ADDIW命令)を含む (rv64ui-p-add.dump)
0000000000000208 <test_7>:
208: 00700193 li gp,7
20c: 800005b7 lui a1,0x80000
210: ffff8637 lui a2,0xffff8
214: 00c58733 add a4,a1,a2
218: ffff03b7 lui t2,0xffff0
21c: fff3839b addiw t2,t2,-1 # fffffffffffeffff <_end+0xfffffffffffedfff>
220: 00f39393 slli t2,t2,0xf
224: 46771063 bne a4,t2,684 <fail>
ということで、失敗していることを気にせずに実装を進めます。
6.2 ADD[I]W、SUBW命令の実装
RV64Iでは、ADD命令は64ビット単位で演算する命令になり、32ビットの加算をするADDW命令とADDIW命令が追加されます。同様に、SUB命令は64ビッド単位の演算になり、32ビットの減算をするSUBW命令が追加されます。32ビットの演算結果は符号拡張します。
6.2.1 ADD[I]W、SUBW命令をデコードする
図6.1: ADDW、ADDIW、SUBW命令のフォーマット[6]
ADDW命令とSUBW命令はR形式で、opcodeはOP-32
(7'b0111011
)です。ADDIW命令はI形式で、opcodeはOP-IMM-32
(7'b0011011
)です。
まず、eeiパッケージにopcodeの定数を定義します(リスト6.11)。
リスト6.11: リスト6.11: opcodeを定義する (eei.veryl)
const OP_OP_32 : logic<7> = 7'b0111011;
const OP_OP_IMM_32: logic<7> = 7'b0011011;
次に、InstCtrl
構造体に、32ビット単位で演算を行う命令であることを示すis_op32
フラグを追加します(リスト6.12)。
リスト6.12: リスト6.12: is_op32を追加する (corectrl.veryl)
struct InstCtrl {
itype : InstType , // 命令の形式
rwb_en : logic , // レジスタに書き込むかどうか
is_lui : logic , // LUI命令である
is_aluop: logic , // ALUを利用する命令である
is_op32 : logic , // OP-32またはOP-IMM-32である
is_jump : logic , // ジャンプ命令である
is_load : logic , // ロード命令である
is_csr : logic , // CSR命令である
funct3 : logic <3>, // 命令のfunct3フィールド
funct7 : logic <7>, // 命令のfunct7フィールド
}
inst_decoderモジュールのInstCtrl
と即値を生成している部分を変更します(リスト6.13、リスト6.14)。これでデコードは完了です。
リスト6.13: リスト6.13: OP-32、OP-IMM-32のInstCtrlの生成 (inst_decoder.veryl)
is_op32を追加
ctrl = {case op { ↓
OP_LUI : {InstType::U, T, T, F, F, F, F, F},
OP_AUIPC : {InstType::U, T, F, F, F, F, F, F},
OP_JAL : {InstType::J, T, F, F, F, T, F, F},
OP_JALR : {InstType::I, T, F, F, F, T, F, F},
OP_BRANCH : {InstType::B, F, F, F, F, F, F, F},
OP_LOAD : {InstType::I, T, F, F, F, F, T, F},
OP_STORE : {InstType::S, F, F, F, F, F, F, F},
OP_OP : {InstType::R, T, F, T, F, F, F, F},
OP_OP_IMM : {InstType::I, T, F, T, F, F, F, F},
OP_OP_32 : {InstType::R, T, F, T, T, F, F, F},
OP_OP_IMM_32: {InstType::I, T, F, T, T, F, F, F},
OP_SYSTEM : {InstType::I, T, F, F, F, F, F, T},
default : {InstType::X, F, F, F, F, F, F, F},
}, f3, f7};
リスト6.14: リスト6.14: OP-IMM-32の即値の生成 (inst_decoder.veryl)
imm = case op {
OP_LUI, OP_AUIPC : imm_u,
OP_JAL : imm_j,
OP_JALR, OP_LOAD : imm_i,
OP_OP_IMM, OP_OP_IMM_32: imm_i,
OP_BRANCH : imm_b,
OP_STORE : imm_s,
default : 'x,
};
6.2.2 ALUにADDW、SUBWを実装する
制御フラグを生成できたので、それに応じて32ビットのADDとSUBを計算します。
まず、32ビットの足し算と引き算の結果を生成します(リスト6.15)。
リスト6.15: リスト6.15: 32ビットの足し算と引き算をする (alu.veryl)
let add32: UInt32 = op1[31:0] + op2[31:0];
let sub32: UInt32 = op1[31:0] - op2[31:0];
次に、フラグによって演算結果を選択する関数sel_wを作成します(リスト6.16)。この関数は、is_op32
が1
ならvalue32
を64ビットに符号拡張した値、0
ならvalue64
を返します。
リスト6.16: リスト6.16: 演算結果を選択する関数を作成する (alu.veryl)
function sel_w (
is_op32: input logic ,
value32: input UInt32,
value64: input UInt64,
) -> UInt64 {
if is_op32 {
return {value32[msb] repeat 32, value32};
} else {
return value64;
}
}
sel_w関数を使用し、aluモジュールの演算処理を変更します。case文の足し算と引き算の部分を次のように変更します(リスト6.17)。
リスト6.17: リスト6.17: 32ビットの演算結果を選択する (alu.veryl)
3'b000: result = if ctrl.itype == InstType::I | ctrl.funct7 == 0 {
sel_w(ctrl.is_op32, add32, add)
} else {
sel_w(ctrl.is_op32, sub32, sub)
};
6.2.3 ADD[I]W、SUBW命令をテストする
RV64I向けのテストを実行して、結果ファイルを確認します(リスト6.18、リスト6.19)。
リスト6.18: リスト6.18: RV64I向けのテストを実行する
$ make build
$ make sim VERILATOR_FLAGS="-DTEST_MODE"
$ python3 test/test.py -r obj_dir/sim test/share rv64ui-p-
リスト6.19: リスト6.19: テストの実行結果 (results/result.txt)
Test Result : 42 / 52
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-ld.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-lwu.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-ma_data.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-sd.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-slliw.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-sllw.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-sraiw.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-sraw.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-srliw.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-srlw.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-add.bin.hex
...
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-addiw.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-addw.bin.hex
...
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-subw.bin.hex
...
ADDIW、ADDW、SUBWだけでなく、未実装の命令以外のテストにも成功しました。
6.3 SLL[I]W、SRL[I]W、SRA[I]W命令の実装
RV64Iでは、SLL[I]、SRL[I]、SRA[I]命令はrs1を0 ~ 63ビットシフトする命令になり、rs1の下位32ビットを0 ~ 31ビットシフトするSLL[I]W、SRL[I]W、SRA[I]W命令が追加されます。32ビットの演算結果は符号拡張します。
図6.2: SLL[I]W、SRL[I]W、SRA[I]W命令のフォーマット [6]
SLL[I]W、SRL[I]W、SRA[I]W命令のフォーマットは、RV32IのSLL[I]、SRL[I]、SRA[I]命令のopcodeを変えたものと同じです。SLLW、SRLW、SRAW命令はR形式で、opcodeはOP-32
です。SLLIW、SRLIW、SRAIW命令はI形式で、opcodeはOP-IMM-32
です。どちらのopcodeの命令も、ADD[I]W命令とSUBW命令の実装時にデコードが完了しています。
aluモジュールで、32ビットのシフト演算の結果を生成します(リスト6.20)。
リスト6.20: リスト6.20: 32ビットのシフト演算をする (alu.veryl)
let sll32: UInt32 = op1[31:0] << op2[4:0];
let srl32: UInt32 = op1[31:0] >> op2[4:0];
let sra32: SInt32 = $signed(op1[31:0]) >>> op2[4:0];
生成したシフト演算の結果をsel_w関数で選択します。case文のシフト演算の部分を次のように変更します(リスト6.21)。
リスト6.21: リスト6.21: 32ビットの演算結果を選択する (alu.veryl)
3'b001: result = sel_w(ctrl.is_op32, sll32, sll);
...
3'b101: result = if ctrl.funct7 == 0 {
sel_w(ctrl.is_op32, srl32, srl)
} else {
sel_w(ctrl.is_op32, sra32, sra)
};
6.3.1 SLL[I]W、SRL[I]W、SRA[I]W命令をテストする
RV64I向けのテストを実行し、結果ファイルを確認します(リスト6.22、リスト6.23)。
リスト6.22: リスト6.22: RV64I向けのテストを実行する
$ make build
$ make sim VERILATOR_FLAGS="-DTEST_MODE"
$ python3 test/test.py -r obj_dir/sim test/share rv64ui-p-
リスト6.23: リスト6.23: テストの実行結果 (results/result.txt)
Test Result : 48 / 52
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-ld.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-lwu.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-ma_data.bin.hex
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-sd.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-add.bin.hex
...
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-sll.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-slli.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-slliw.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-sllw.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-sra.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-srai.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-sraiw.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-sraw.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-srl.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-srli.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-srliw.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-srlw.bin.hex
...
SLLW、SLLIW、SRLW、SRLIW、SRAW、SRAIW命令のテストに成功していることを確認できます。
6.4 LWU命令の実装
LB、LH命令は、ロードした値を符号拡張した値をレジスタに格納します。これに対して、LBU、LHU命令は、ロードした値をゼロで拡張した値をレジスタに格納します。
同様に、LW命令は、ロードした値を符号拡張した値をレジスタに格納します。これに対して、RV64Iでは、ロードした32ビットの値をゼロで拡張した値をレジスタに格納するLWU命令が追加されます。
図6.3: LWU命令のフォーマット[6]
LWU命令はI形式で、opcodeはLOAD
です。ロードストア命令はfunct3によって区別できて、LWU命令のfunct3は3'b110
です。デコード処理に変更は必要なく、メモリにアクセスする処理を変更する必要があります。
memunitモジュールの、ロードする部分を変更します。32ビットをrdata
に割り当てるとき、sext
によって符号かゼロで拡張するかを選択します(リスト6.24)。
リスト6.24: リスト6.24: LWU命令の実装 (memunit.veryl)
2'b10 : {sext & D[31] repeat W - 32, D[31:0]},
6.4.1 LWU命令をテストする
LWU命令のテストを実行します(リスト6.25)。
リスト6.25: リスト6.25: LWU命令をテストする
$ make build
$ make sim VERILATOR_FLAGS="-DTEST_MODE"
$ python3 test/test.py -r obj_dir/sim test/share rv64ui-p-lwu
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-lwu.bin.hex
Test Result : 1 / 1
6.5 LD、SD命令の実装
RV64Iには、64ビット単位でロードストアを行うLD命令とSD命令が定義されています。
図6.4: LD、SD命令のフォーマット
LD命令はI形式で、opcodeはLOAD
です。SD命令はS形式で、opcodeはSTORE
です。どちらの命令もfunct3は3'b011
です。デコード処理に変更は必要ありません。
6.5.1 メモリの幅を広げる
現在のメモリの1つのデータの幅(MEM_DATA_WIDTH
)は32ビットですが、このままだと64ビットでロードやストアを行うときに、最低2回のメモリアクセスが必要です。これを1回のメモリアクセスで済ませるために、データの幅を32ビットから64ビットに広げます(リスト6.26)。
リスト6.26: リスト6.26: MEM_DATA_WIDTHを64ビットに変更する (eei.veryl)
const MEM_DATA_WIDTH: u32 = 64;
6.5.2 命令フェッチ処理を修正する
XLEN
、MEM_DATA_WIDTH
が変わっても、命令の長さ(ILEN
)は32ビットのままです。そのため、topモジュールのi_membus.rdata
の幅は32ビットなのに対し、membus.rdata
は64ビットになり、ビット幅が一致しません。
ビット幅を合わせて正しく命令をフェッチするために、64ビットの読み出しデータの上位32ビット、下位32ビットをアドレスの下位ビットで選択します。アドレスが8の倍数のときは下位32ビット、それ以外のときは上位32ビットを選択します。
まず、命令フェッチの要求アドレスをレジスタに格納します(リスト6.27、リスト6.28)。
リスト6.27: リスト6.27: アドレスを格納するためのレジスタの定義 (top.veryl)
var memarb_last_i : logic;
var memarb_last_iaddr: Addr ;
リスト6.28: リスト6.28: レジスタに命令フェッチの要求アドレスを格納する (top.veryl)
// メモリアクセスを調停する
always_ff {
if_reset {
memarb_last_i = 0;
memarb_last_iaddr = 0;
} else {
if membus.ready {
memarb_last_i = !d_membus.valid;
memarb_last_iaddr = i_membus.addr;
}
}
}
このレジスタの値を利用し、i_membus.rdata
に割り当てる値を選択します(リスト6.29)。
リスト6.29: リスト6.29: アドレスによってデータを選択する (top.veryl)
i_membus.rdata = if memarb_last_iaddr[2] == 0 {
membus.rdata[31:0]
} else {
membus.rdata[63:32]
};
6.5.3 SD命令を実装する
SD命令の実装のためには、書き込むデータ(wdata
)と書き込みマスク(wmask
)を変更する必要があります。
書き込むデータはアドレスの下位2ビットではなく下位3ビット分シフトします(リスト6.30)。
リスト6.30: リスト6.30: 書き込むデータの変更 (memunit.veryl)
req_wdata = rs2 << {addr[2:0], 3'b0};
書き込みマスクは4ビットから8ビットに拡張されるため、アドレスの下位2ビットではなく下位3ビットで選択します(リスト6.31)。
リスト6.31: リスト6.31: 書き込みマスクの変更 (memunit.veryl)
req_wmask = case ctrl.funct3[1:0] {
2'b00 : 8'b1 << addr[2:0],
2'b01 : case addr[2:0] {
6 : 8'b11000000,
4 : 8'b00110000,
2 : 8'b00001100,
0 : 8'b00000011,
default: 'x,
},
2'b10 : case addr[2:0] {
0 : 8'b00001111,
4 : 8'b11110000,
default: 'x,
},
2'b11 : 8'b11111111,
default: 'x,
};
6.5.4 LD命令を実装する
メモリのデータ幅が64ビットに広がるため、rdata
に割り当てる値を、アドレスの下位2ビットではなく下位3ビットで選択します(リスト6.32)。
リスト6.32: リスト6.32: rdataの変更 (memunit.veryl)
rdata = case ctrl.funct3[1:0] {
2'b00 : case addr[2:0] {
0 : {sext & D[7] repeat W - 8, D[7:0]},
1 : {sext & D[15] repeat W - 8, D[15:8]},
2 : {sext & D[23] repeat W - 8, D[23:16]},
3 : {sext & D[31] repeat W - 8, D[31:24]},
4 : {sext & D[39] repeat W - 8, D[39:32]},
5 : {sext & D[47] repeat W - 8, D[47:40]},
6 : {sext & D[55] repeat W - 8, D[55:48]},
7 : {sext & D[63] repeat W - 8, D[63:56]},
default: 'x,
},
2'b01 : case addr[2:0] {
0 : {sext & D[15] repeat W - 16, D[15:0]},
2 : {sext & D[31] repeat W - 16, D[31:16]},
4 : {sext & D[47] repeat W - 16, D[47:32]},
6 : {sext & D[63] repeat W - 16, D[63:48]},
default: 'x,
},
2'b10 : case addr[2:0] {
0 : {sext & D[31] repeat W - 32, D[31:0]},
4 : {sext & D[63] repeat W - 32, D[63:32]},
default: 'x,
},
2'b11 : D,
default: 'x,
};
6.5.5 LD、SD命令をテストする
LD、SD命令のテストを実行する前に、メモリのデータ単位が4バイトから8バイトになったため、テストのHEXファイルを4バイト単位の改行から8バイト単位の改行に変更します(リスト6.33)。
リスト6.33: リスト6.33: HEXファイルを8バイト単位に変更する
$ cd test
$ find share/ -type f -name "*.bin" -exec sh -c "python3 bin2hex.py 8 {} > {}.hex" \;
riscv-testsを実行します(リスト6.34)。
リスト6.34: リスト6.34: RV32I、RV64Iをテストする
$ make build
$ make sim VERILATOR_FLAGS="-DTEST_MODE"
$ python3 test/test.py -r obj_dir/sim test/share rv32ui-p-
...
Test Result : 40 / 40
$ python3 test/test.py -r obj_dir/sim test/share rv64ui-p-
...
FAIL : ~/core/test/share/riscv-tests/isa/rv64ui-p-ma_data.bin.hex
...
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-ld.bin.hex
PASS : ~/core/test/share/riscv-tests/isa/rv64ui-p-sd.bin.hex
...
Test Result : 51 / 52
RV64IのCPUを実装できました。