Verylで作るCPU Star

第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]W32ビット単位で加算を行う。結果は符号拡張する
SUBW32ビット単位で減算を行う。結果は符号拡張する
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ビット右算術シフトする
LUI32ビットの即値を生成する。結果は符号拡張する
AUIPC32ビットの即値を符号拡張したものに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命令をデコードする

ADDW、ADDIW、SUBW命令のフォーマット<a href="bib.html#bib-isa-manual.1.37">[6]</a>

図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_op321なら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ビットの演算結果は符号拡張します。

SLL[I]W、SRL[I]W、SRA[I]W命令のフォーマット <a href="bib.html#bib-isa-manual.1.37">[6]</a>

図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命令が追加されます。

LWU命令のフォーマット<a href="bib.html#bib-isa-manual.1.37">[6]</a>

図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命令が定義されています。

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 命令フェッチ処理を修正する

XLENMEM_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を実装できました。