第13章
C拡張の実装
13.1 概要
これまでに実装した命令はすべて32ビット幅のものでした。RISC-Vには32ビット幅以外の命令が定義されており、命令の下位ビットで何ビット幅の命令か判断できます(表13.1)。
表13.1: RISC-Vの命令長とエンコーディング
命令幅 | 命令の下位5ビット |
---|---|
16-bit (aa≠11) | xxxaa |
32-bit (bbb≠111) | bbb11 |
C拡張は16ビット幅の命令を定義する拡張です。よく使われる命令の幅を16ビットに圧縮できるようにすることでコードサイズを削減できます。これ以降、C拡張によって導入される16ビット幅の命令のことをRVC命令と呼びます。
全てのRVC命令には同じ操作をする32ビット幅の命令が存在します*1。
[*1] Zc*拡張の一部の命令は複数の命令になります
RVC命令は図13.1の9つのフォーマットが定義されています。

図13.1: RVC命令のフォーマット
rs1'
、rs2'
、rd'
は3ビットのフィールドで、よく使われる8番(x8)から15番(x15)のレジスタを指定します。即値の並び方やそれぞれの命令の具体的なフォーマットについては、仕様書か「13.6.2 32ビット幅の命令に変換する」のコードを参照してください。
RV64IのCPUに実装されるC拡張には表13.2のRVC命令が定義されています。
表13.2: C拡張の命令
命令 | 同じ意味の32ビット幅の命令 | 形式 |
---|---|---|
C.LWSP | lw rd, offset(x2) | CI |
C.LDSP | ld rd, offset(x2) | CI |
C.SWSP | sw rs2, offset(x2) | CSS |
C.SDSP | sd rs2, offset(x2) | CSS |
C.LW | lw rd, offset(rs) | CL |
C.LD | ld rd, offset(rs) | CL |
C.SW | sw rs2, offset(rs1) | CS |
C.SD | sd rs2, offset(rs1) | CS |
C.J | jal x0, offset | CJ |
C.JR | jalr x0, 0(rs1) | CR |
C.JALR | jalr x1, 0(rs1) | CR |
C.BEQZ | beq rs1, x0, offset | CB |
C.BNEZ | bne rs1, x0, offset | CB |
C.LI | addi rd, x0, imm | CI |
C.LUI | lui rd, imm | CI |
C.ADDI | addi rd, rd, imm | CI |
C.ADDIW | addiw rd, rd, imm | CI |
C.ADDI16SP | addi x2, x2, imm | CI |
C.ADDI4SPN | addi rd, x2, imm | CIW |
C.SLLI | slli rd, rd, shamt | CI |
C.SRLI | srli rd, rd, shamt | CB |
C.SRAI | srai rd, rd, shamt | CB |
C.ANDI | andi rd, rd, imm | CB |
C.MV | add rd, x0, rs2 | CR |
C.ADD | add rd, rd, rs2 | CR |
C.AND | and rd, rd, rs2 | CA |
C.OR | or rd, rd, rs2 | CA |
C.XOR | xor rd, rd, rs2 | CA |
C.SUB | sub rd, rd, rs2 | CA |
C.EBREAK | ebreak | CR |
C.ADDIW命令はRV32IのC拡張に定義されているC.JAL命令とエンコーディングが同じです。本書で実装するモジュールはRV32IのC拡張にも対応したものになっています。RV32IのC拡張については、仕様書か「13.6.2 32ビット幅の命令に変換する」のコードを参照してください。
C拡張は浮動小数点命令をサポートするF、D拡張が実装されている場合に他の命令を定義しますが、基本編ではF、D拡張を実装しないため実装、解説しません。
13.2 IALIGNの変更
「10.5 命令アドレスのミスアライン例外」で解説したように、命令はIALIGNビットに整列したアドレスに配置されます。C拡張はIALIGNによる制限を16ビットに緩め、全ての命令が16ビットに整列されたアドレスに配置されるように変更します。これにより、RVC命令と32ビット幅の命令の組み合わせがあったとしても効果的にコードサイズを削減できます。
eeiパッケージに定数IALIGN
を定義します(リスト13.1)。
1: const IALIGN: u32 = 16;
mepcレジスタの書き込みマスクを変更して、トラップ時のジャンプ先アドレスに16ビットに整列されたアドレスを指定できるようにします(リスト13.2)。
1: const MEPC_WMASK : UIntX = 'hffff_ffff_ffff_fffe;
命令アドレスのミスアライン例外の判定を変更します。IALIGNが16
の場合は例外が発生しないようにします(リスト13.3)。ジャンプ、分岐命令は2バイト単位のアドレスしか指定できないため、C拡張が実装されている場合には例外が発生しません。
1: let instruction_address_misaligned: logic = IALIGN == 32 && memq_wdata.br_taken && memq_wdata.jump_addr[1:0] != 2'b00;
13.3 実装方針
本章では次の順序でC拡張を実装します。
- 命令フェッチ処理(IFステージ)をcoreモジュールから分離する
- 16ビットに整列されたアドレスに配置された32ビット幅の命令を処理できるようにする
- RVC命令を32ビット幅の命令に変換するモジュールを作成する
- RVC命令を32ビット幅の命令に変換してcoreモジュールに供給する
最終的な命令フェッチ処理の構成は図図13.2のようになります。

図13.2: 命令フェッチ処理の構成
13.4 命令フェッチモジュールの実装
13.4.1 インターフェースを作成する
まず、命令フェッチを行うモジュールとcoreモジュールのインターフェースを定義します。
src/core_inst_if.veryl
を作成し、次のように記述します(リスト13.4)。
1: import eei::*; 2: 3: interface core_inst_if { 4: var rvalid : logic; 5: var rready : logic; 6: var raddr : Addr ; 7: var rdata : Inst ; 8: var is_hazard: logic; 9: var next_pc : Addr ; 10: 11: modport master { 12: rvalid : input , 13: rready : output, 14: raddr : input , 15: rdata : input , 16: is_hazard: output, // control hazard 17: next_pc : output, // actual next pc 18: } 19: 20: modport slave { 21: ..converse(master) 22: } 23: }
rvalid
、rready
、raddr
、rdata
は、coreモジュールのFIFO(if_fifo
)のwvalid
、wready
、wdata.addr
、wdata.bits
と同じ役割を果たします。is_hazard
、next_pc
は制御ハザードの情報を伝えるための変数です。
13.4.2 coreモジュールのIFステージを削除する
coreモジュールのIFステージを削除し、core_inst_ifインターフェースで代替します*2。
[*2] ここで削除するコードは次の「13.4.3 inst_fetcherモジュールを作成する」で実装するコードと似通っているため、削除せずにコメントアウトしておくと少し楽に実装できます。
coreモジュールのi_membus
の型をcore_inst_if
に変更します(リスト13.5)。
1: i_membus: modport core_inst_if::master,
IFステージ部分のコードを次のように変更します(リスト13.6)。
1: ///////////////////////////////// IF Stage ///////////////////////////////// 2: 3: var control_hazard : logic; 4: var control_hazard_pc_next: Addr ; 5: 6: always_comb { 7: i_membus.is_hazard = control_hazard; 8: i_membus.next_pc = control_hazard_pc_next; 9: }
coreモジュールの新しいIFステージ部分は、制御ハザードの情報をインターフェースに割り当てるだけの簡単なものになっています。if_fifo_type
型、if_fifo_
から始まる変数は使わなくなったので削除してください。
IDステージとcore_inst_ifインターフェースを接続します(リスト13.7、リスト13.8)。もともとif_fifo
のrvalid
、rready
、rdata
だった部分をi_membus
に変更しています。
1: let ids_valid : logic = i_membus.rvalid; 2: let ids_pc : Addr = i_membus.raddr; 3: let ids_inst_bits : Inst = i_membus.rdata;
1: always_comb { 2: // ID -> EX 3: i_membus.rready = exq_wready; 4: exq_wvalid = i_membus.rvalid; 5: exq_wdata.addr = i_membus.raddr; 6: exq_wdata.bits = i_membus.rdata; 7: exq_wdata.ctrl = ids_ctrl; 8: exq_wdata.imm = ids_imm;
13.4.3 inst_fetcherモジュールを作成する
IFステージの代わりに命令フェッチをするinst_fetcherモジュールを作成します。inst_fetcherモジュールでは命令フェッチ処理をfetch、issueの2段階で行います。
- fetch
-
メモリから64ビットの値を読み込み、issueとの間のFIFOに格納する。
アドレスを
8
進めて、次の64ビットを読み込む。 - issue
- fetchとの間のFIFOから64ビットを読み込み、 32ビットずつcoreモジュールとの間のFIFOに格納する。
fetchとissueは並列に独立して動かします。
inst_fetcherモジュールのポートを定義します。src/inst_fetcher.veryl
を作成し、次のように記述します(リスト13.9)。
1: module inst_fetcher ( 2: clk : input clock , 3: rst : input reset , 4: core_if: modport core_inst_if::slave, 5: mem_if : modport Membus::master , 6: ) {
core_if
はcoreモジュールとのインターフェース、mem_if
はメモリとのインターフェースです。
fetchとissue、issueとcore_ifの間のFIFOを作成します(リスト13.10、リスト13.11)。
1: struct fetch_fifo_type { 2: addr: Addr , 3: bits: logic<MEMBUS_DATA_WIDTH>, 4: } 5: 6: var fetch_fifo_flush : logic ; 7: var fetch_fifo_wvalid: logic ; 8: var fetch_fifo_wready: logic ; 9: var fetch_fifo_wdata : fetch_fifo_type; 10: var fetch_fifo_rdata : fetch_fifo_type; 11: var fetch_fifo_rready: logic ; 12: var fetch_fifo_rvalid: logic ; 13: 14: inst fetch_fifo: fifo #( 15: DATA_TYPE: fetch_fifo_type, 16: WIDTH : 3 , 17: ) ( 18: clk , 19: rst , 20: flush : fetch_fifo_flush , 21: wready : _ , 22: wready_two: fetch_fifo_wready, 23: wvalid : fetch_fifo_wvalid, 24: wdata : fetch_fifo_wdata , 25: rready : fetch_fifo_rready, 26: rvalid : fetch_fifo_rvalid, 27: rdata : fetch_fifo_rdata , 28: );
1: struct issue_fifo_type { 2: addr: Addr, 3: bits: Inst, 4: } 5: 6: var issue_fifo_flush : logic ; 7: var issue_fifo_wvalid: logic ; 8: var issue_fifo_wready: logic ; 9: var issue_fifo_wdata : issue_fifo_type; 10: var issue_fifo_rdata : issue_fifo_type; 11: var issue_fifo_rready: logic ; 12: var issue_fifo_rvalid: logic ; 13: 14: inst issue_fifo: fifo #( 15: DATA_TYPE: issue_fifo_type, 16: WIDTH : 3 , 17: ) ( 18: clk , 19: rst , 20: flush : issue_fifo_flush , 21: wready: issue_fifo_wready, 22: wvalid: issue_fifo_wvalid, 23: wdata : issue_fifo_wdata , 24: rready: issue_fifo_rready, 25: rvalid: issue_fifo_rvalid, 26: rdata : issue_fifo_rdata , 27: );
メモリへのアクセス処理(fetch)を実装します。FIFOに空きがあるとき、64ビットの値を読み込んでPCを8
進めます(リスト13.12、リスト13.13、リスト13.14)。この処理はcoreモジュールの元のIFステージとほとんど同じです。
1: var fetch_pc : Addr ; 2: var fetch_requested : logic; 3: var fetch_pc_requested: Addr ;
1: always_comb { 2: mem_if.valid = 0; 3: mem_if.addr = 0; 4: mem_if.wen = 0; 5: mem_if.wdata = 0; 6: mem_if.wmask = 0; 7: if !core_if.is_hazard { 8: mem_if.valid = fetch_fifo_wready; 9: if fetch_requested { 10: mem_if.valid = mem_if.valid && mem_if.rvalid; 11: } 12: mem_if.addr = fetch_pc; 13: } 14: }
1: always_ff { 2: if_reset { 3: fetch_pc = INITIAL_PC; 4: fetch_requested = 0; 5: fetch_pc_requested = 0; 6: } else { 7: if core_if.is_hazard { 8: fetch_pc = {core_if.next_pc[XLEN - 1:3], 3'b0}; 9: fetch_requested = 0; 10: fetch_pc_requested = 0; 11: } else { 12: if fetch_requested { 13: if mem_if.rvalid { 14: fetch_requested = mem_if.ready && mem_if.valid; 15: if mem_if.ready && mem_if.valid { 16: fetch_pc_requested = fetch_pc; 17: fetch_pc += 8; 18: } 19: } 20: } else { 21: if mem_if.ready && mem_if.valid { 22: fetch_requested = 1; 23: fetch_pc_requested = fetch_pc; 24: fetch_pc += 8; 25: } 26: } 27: } 28: } 29: }
メモリから読み込んだ値をissueとの間のFIFOに格納します(リスト13.15)。
1: // memory -> fetch_fifo 2: always_comb { 3: fetch_fifo_flush = core_if.is_hazard; 4: fetch_fifo_wvalid = fetch_requested && mem_if.rvalid; 5: fetch_fifo_wdata.addr = fetch_pc_requested; 6: fetch_fifo_wdata.bits = mem_if.rdata; 7: }
coreモジュールに命令を供給する処理(issue)を実装します。FIFOにデータが入っているとき、32ビットずつcoreモジュールとの間のFIFOに格納します。2つの32ビットの命令をFIFOに格納出来たら、fetchとの間のFIFOを読み進めます(リスト13.16、リスト13.17)。
1: var issue_pc_offset: logic<3>; 2: 3: always_ff { 4: if_reset { 5: issue_pc_offset = 0; 6: } else { 7: if core_if.is_hazard { 8: issue_pc_offset = core_if.next_pc[2:0]; 9: } else { 10: if issue_fifo_wready && issue_fifo_wvalid { 11: issue_pc_offset += 4; 12: } 13: } 14: } 15: }
1: // fetch_fifo <-> issue_fifo 2: always_comb { 3: let raddr : Addr = fetch_fifo_rdata.addr; 4: let rdata : logic<MEMBUS_DATA_WIDTH> = fetch_fifo_rdata.bits; 5: let offset: logic<3> = issue_pc_offset; 6: 7: fetch_fifo_rready = 0; 8: issue_fifo_wvalid = 0; 9: issue_fifo_wdata = 0; 10: 11: if !core_if.is_hazard && fetch_fifo_rvalid { 12: if issue_fifo_wready { 13: fetch_fifo_rready = offset == 4; 14: issue_fifo_wvalid = 1; 15: issue_fifo_wdata.addr = {raddr[msb:3], offset}; 16: issue_fifo_wdata.bits = case offset { 17: 0 : rdata[31:0], 18: 4 : rdata[63:32], 19: default: 0, 20: }; 21: } 22: } 23: }
core_if
とFIFOを接続します(リスト13.18)。
1: // issue_fifo <-> core 2: always_comb { 3: issue_fifo_flush = core_if.is_hazard; 4: issue_fifo_rready = core_if.rready; 5: core_if.rvalid = issue_fifo_rvalid; 6: core_if.raddr = issue_fifo_rdata.addr; 7: core_if.rdata = issue_fifo_rdata.bits; 8: }
13.4.4 inst_fetcherモジュールとcoreモジュールを接続する
topモジュールで、core_inst_ifをインスタンス化します。(リスト13.19)。
1: inst i_membus_core: core_inst_if;
inst_fetcherモジュールをインスタンス化し、coreモジュールと接続します(リスト13.20、リスト13.21)。
1: inst fetcher: inst_fetcher ( 2: clk , 3: rst , 4: core_if: i_membus_core, 5: mem_if : i_membus , 6: );
1: inst c: core ( 2: clk , 3: rst , 4: i_membus: i_membus_core, 5: d_membus: d_membus_core, 6: led , 7: );
inst_fetcherモジュールが64ビットのデータを32ビットの命令の列に変換してくれるようになったので、d_membus
との調停のところで32ビットずつ選択する必要がなくなりました。そのため、rdata
をそのまま割り当てて、memarb_last_iaddr
変数とビットの選択処理を削除します(リスト13.22、リスト13.23、リスト13.24)。
1: var memarb_last_i: logic; 2:var memarb_last_iaddr: Addr;
1: always_ff { 2: if_reset { 3: memarb_last_i = 0; 4:memarb_last_i = 0;5: } else { 6: if mmio_membus.ready { 7: memarb_last_i = !d_membus.valid; 8:memarb_last_iaddr = i_membus.addr;9: } 10: } 11: }
1: always_comb { 2: i_membus.ready = mmio_membus.ready && !d_membus.valid; 3: i_membus.rvalid = mmio_membus.rvalid && memarb_last_i; 4: i_membus.rdata = mmio_membus.rdata;
13.5 16ビット境界に配置された32ビット幅の命令のサポート
inst_fetcherモジュールで、アドレスが2バイトの倍数の32ビット幅の命令をcoreモジュールに供給できるようにします。
アドレスの下位3ビット(issue_pc_offset
)が6
の場合、issueとcoreの間に供給する命令のビット列はfetch_fifo_rdata
の上位16ビットとfetch_fifo
に格納されている次のデータの下位16ビットを結合したものになります。このとき、fetch_fifo_rdata
のデータの下位16ビットとアドレスを保存して、次のデータを読み出します。fetch_fifo
から次のデータを読み出せたら、保存していたデータと結合し、アドレスとともにissue_fifo
に書き込みます。issue_pc_offset
が0
、2
、4
の場合、既存の処理との変更点はありません。
fetch_fifo_rdata
のデータの下位16ビットとアドレスを保持する変数を作成します(リスト13.25)。
1: var issue_is_rdata_saved: logic ; 2: var issue_saved_addr : Addr ; 3: var issue_saved_bits : logic<16>; // rdata[63:48]
issue_pc_offset
が6
のとき、変数にデータを保存します(リスト13.26)。
1: always_ff { 2: if_reset { 3: issue_pc_offset = 0; 4: issue_is_rdata_saved = 0; 5: issue_saved_addr = 0; 6: issue_saved_bits = 0; 7: } else { 8: if core_if.is_hazard { 9: issue_pc_offset = core_if.next_pc[2:0]; 10: issue_is_rdata_saved = 0; 11: } else { 12: // offsetが6な32ビット命令の場合、 13: // アドレスと上位16ビットを保存してFIFOを読み進める 14: if issue_pc_offset == 6 && !issue_is_rdata_saved { 15: if fetch_fifo_rvalid { 16: issue_is_rdata_saved = 1; 17: issue_saved_addr = fetch_fifo_rdata.addr; 18: issue_saved_bits = fetch_fifo_rdata.bits[63:48]; 19: } 20: } else { 21: if issue_fifo_wready && issue_fifo_wvalid { 22: issue_pc_offset += 4; 23: issue_is_rdata_saved = 0; 24: } 25: } 26: } 27: } 28: }
issue_pc_offset
が2
、6
の場合のissue_fifo
への書き込みを実装します(リスト13.27)。6
の場合、保存していた16ビットと新しく読み出した16ビットを結合した値、保存していたアドレスを書き込みます。
1: if !core_if.is_hazard && fetch_fifo_rvalid { 2: if issue_fifo_wready { 3: if offset == 6 { 4: // offsetが6な32ビット命令の場合、 5: // 命令は{rdata_next[15:0], rdata[63:48}になる 6: if issue_is_rdata_saved { 7: issue_fifo_wvalid = 1; 8: issue_fifo_wdata.addr = {issue_saved_addr[msb:3], offset}; 9: issue_fifo_wdata.bits = {rdata[15:0], issue_saved_bits}; 10: } else { 11: // Read next 8 bytes 12: fetch_fifo_rready = 1; 13: } 14: } else { 15: fetch_fifo_rready = offset == 4; 16: issue_fifo_wvalid = 1; 17: issue_fifo_wdata.addr = {raddr[msb:3], offset}; 18: issue_fifo_wdata.bits = case offset { 19: 0 : rdata[31:0], 20: 2 : rdata[47:16], 21: 4 : rdata[63:32], 22: default: 0, 23: }; 24: } 25: } 26: }
32ビット幅の命令の下位16ビットが既に保存されている(issue_is_rdata_saved
が1
)とき、fetch_fifo
から供給されるデータには、32ビット幅の命令の上位16ビットを除いた残りの48ビットが含まれているのでfetch_fifo_rready
を1
に設定しないことに注意してください。
13.6 RVC命令の変換
13.6.1 RVC命令フラグの実装
RVC命令を32ビット幅の命令に変換するモジュールを作る前に、RVC命令かどうかを示すフラグを作成します。
まず、core_inst_if
インターフェースとInstCtrl
構造体にis_rvc
フラグを追加します(リスト13.28、リスト13.29、リスト13.30)。
1: var rdata : Inst ; 2: var is_rvc : logic; 3: var is_hazard: logic;
1: modport master { 2: rvalid : input , 3: rready : output, 4: raddr : input , 5: rdata : input , 6: is_rvc : input , 7: is_hazard: output, // control hazard 8: next_pc : output, // actual next pc 9: }
1: is_amo : logic , // AMO instruction 2: is_rvc : logic , // RVC instruction 3: funct3 : logic <3>, // 命令のfunct3フィールド
inst_fetcherモジュールで、is_rvc
を0
に設定してcoreモジュールに供給します(リスト13.31、リスト13.32、リスト13.33)。
1: struct issue_fifo_type { 2: addr : Addr , 3: bits : Inst , 4: is_rvc: logic, 5: }
1: if offset == 6 { 2: // offsetが6な32ビット命令の場合、 3: // 命令は{rdata_next[15:0], rdata[63:48}になる 4: if issue_is_rdata_saved { 5: issue_fifo_wvalid = 1; 6: issue_fifo_wdata.addr = {issue_saved_addr[msb:3], offset}; 7: issue_fifo_wdata.bits = {rdata[15:0], issue_saved_bits}; 8: issue_fifo_wdata.is_rvc = 0; 9: } else { 10: // Read next 8 bytes 11: fetch_fifo_rready = 1; 12: } 13: } else { 14: fetch_fifo_rready = offset == 4; 15: issue_fifo_wvalid = 1; 16: issue_fifo_wdata.addr = {raddr[msb:3], offset}; 17: issue_fifo_wdata.bits = case offset { 18: 0 : rdata[31:0], 19: 2 : rdata[47:16], 20: 4 : rdata[63:32], 21: default: 0, 22: }; 23: issue_fifo_wdata.is_rvc = 0; 24: }
1: always_comb { 2: issue_fifo_flush = core_if.is_hazard; 3: issue_fifo_rready = core_if.rready; 4: core_if.rvalid = issue_fifo_rvalid; 5: core_if.raddr = issue_fifo_rdata.addr; 6: core_if.rdata = issue_fifo_rdata.bits; 7: core_if.is_rvc = issue_fifo_rdata.is_rvc; 8: }
inst_decoderモジュールで、InstCtrl
構造体のis_rvc
フラグを設定します(リスト13.34、リスト13.35、リスト13.36)。また、C拡張が無効なのにRVC命令が供給されたらvalid
フラグを0
に設定します。
1: module inst_decoder ( 2: bits : input Inst , 3: is_rvc: input logic , 4: valid : output logic , 5: ctrl : output InstCtrl, 6: imm : output UIntX , 7: ) {
1: default: { 2: InstType::X, F, F, F, F, F, F, F, F, F 3: }, 4: }, is_rvc, f3, f7 5: };
1: OP_AMO : f3 == 3'b010 || f3 == 3'b011, // AMO 2: default : F, 3: } && (IALIGN == 16 || !is_rvc); // IALIGN == 32のとき、C拡張は無効
coreモジュールで、inst_decoderモジュールにis_rvc
フラグを渡します(リスト13.37)。
1: inst decoder: inst_decoder ( 2: bits : ids_inst_bits , 3: is_rvc: i_membus.is_rvc, 4: valid : ids_inst_valid , 5: ctrl : ids_ctrl , 6: imm : ids_imm , 7: );
ジャンプ命令でライトバックする値は次の命令のアドレスであるため、RVC命令の場合はPCに2
を足した値を設定します(リスト13.38)。
1: let wbs_wb_data: UIntX = if wbs_ctrl.is_lui ? 2: wbs_imm 3: : if wbs_ctrl.is_jump ? 4: wbs_pc + (if wbs_ctrl.is_rvc ? 2 : 4) 5: : if wbs_ctrl.is_load || wbs_ctrl.is_amo ?
13.6.2 32ビット幅の命令に変換する
RVC命令のopcode、functなどのフィールドを読んで、32ビット幅の命令を生成するrvc_converterモジュールを実装します。
その前に、命令のフィールドを引数に32ビット幅の命令を生成する関数を実装します。src/inst_gen_pkg.veryl
を作成し、次のように記述します(リスト13.39)。関数の名前は基本的に命令名と同じにしていますが、Verylのキーワードと被るものはinst_
をprefixにしています。
1: import eei::*; 2: 3: package inst_gen_pkg { 4: function add (rd: input logic<5>, rs1: input logic<5>, rs2: input logic<5>) -> Inst { 5: return {7'b0000000, rs2, rs1, 3'b000, rd, OP_OP}; 6: } 7: 8: function addw (rd: input logic<5>, rs1: input logic<5>, rs2: input logic<5>) -> Inst { 9: return {7'b0000000, rs2, rs1, 3'b000, rd, OP_OP_32}; 10: } 11: 12: function addi (rd : input logic<5> , rs1: input logic<5> , imm: input logic<12>) -> Inst { 13: return {imm, rs1, 3'b000, rd, OP_OP_IMM}; 14: } 15: 16: function addiw (rd: input logic<5> ,rs1: input logic<5>, imm: input logic<12>) -> Inst { 17: return {imm, rs1, 3'b000, rd, OP_OP_IMM_32}; 18: } 19: 20: function sub (rd: input logic<5>,rs1: input logic<5>, rs2: input logic<5>) -> Inst { 21: return {7'b0100000, rs2, rs1, 3'b000, rd, OP_OP}; 22: } 23: 24: function subw (rd: input logic<5>, rs1: input logic<5>, rs2: input logic<5>) -> Inst { 25: return {7'b0100000, rs2, rs1, 3'b000, rd, OP_OP_32}; 26: } 27: 28: function inst_xor (rd: input logic<5>, rs1: input logic<5>, rs2: input logic<5>) -> Inst { 29: return {7'b0000000, rs2, rs1, 3'b100, rd, OP_OP}; 30: } 31: 32: function inst_or (rd: input logic<5>, rs1: input logic<5>, rs2: input logic<5>) -> Inst { 33: return {7'b0000000, rs2, rs1, 3'b110, rd, OP_OP}; 34: } 35: 36: function inst_and (rd: input logic<5>, rs1: input logic<5>, rs2: input logic<5>) -> Inst { 37: return {7'b0000000, rs2, rs1, 3'b111, rd, OP_OP}; 38: } 39: 40: function andi (rd: input logic<5> , rs1: input logic<5>, imm: input logic<12>) -> Inst { 41: return {imm, rs1, 3'b111, rd, OP_OP_IMM}; 42: } 43: 44: function slli (rd: input logic<5>, rs1: input logic<5>, shamt: input logic<6>) -> Inst { 45: return {6'b000000, shamt, rs1, 3'b001, rd, OP_OP_IMM}; 46: } 47: 48: function srli (rd: input logic<5>, rs1: input logic<5>, shamt: input logic<6>) -> Inst { 49: return {6'b000000, shamt, rs1, 3'b101, rd, OP_OP_IMM}; 50: } 51: 52: function srai (rd: input logic<5>, rs1: input logic<5>, shamt: input logic<6>) -> Inst { 53: return {6'b010000, shamt, rs1, 3'b101, rd, OP_OP_IMM}; 54: } 55: 56: function lui (rd: input logic<5>, imm: input logic<20>) -> Inst { 57: return {imm, rd, OP_LUI}; 58: } 59: 60: function load (rd: input logic<5> ,rs1: input logic<5>, imm: input logic<12>, funct3: input logic<3>) -> Inst { 61: return {imm, rs1, funct3, rd, OP_LOAD}; 62: } 63: 64: function store (rs1: input logic<5>, rs2: input logic<5>, imm: input logic<12>, funct3: input logic<3>) -> Inst { 65: return {imm[11:5], rs2, rs1, funct3, imm[4:0], OP_STORE}; 66: } 67: 68: function jal (rd : input logic<5>, imm: input logic<20>) -> Inst { 69: return {imm[19], imm[9:0], imm[10], imm[18:11], rd, OP_JAL}; 70: } 71: 72: function jalr (rd: input logic<5>, rs1: input logic<5>, imm: input logic<12>) -> Inst { 73: return {imm, rs1, 3'b000, rd, OP_JALR}; 74: } 75: 76: function beq (rs1: input logic<5>, rs2: input logic<5>, imm: input logic<12>) -> Inst { 77: return {imm[11], imm[9:4], rs2, rs1, 3'b000, imm[3:0], imm[10], OP_BRANCH}; 78: } 79: 80: function bne (rs1: input logic<5>, rs2: input logic<5>, imm: input logic<12>) -> Inst { 81: return {imm[11], imm[9:4], rs2, rs1, 3'b001, imm[3:0], imm[10], OP_BRANCH}; 82: } 83: 84: function ebreak () -> Inst { 85: return 32'h00100073; 86: } 87: }
rvc_conveterモジュールのポートを定義します。src/rvc_converter.veryl
を作成し、次のように記述します(リスト13.40)。
1: import eei::*; 2: import inst_gen_pkg::*; 3: 4: module rvc_converter ( 5: inst16: input logic<16>, 6: is_rvc: output logic , 7: inst32: output Inst , 8: ) {
rvc_converterモジュールは、inst16
で16ビットの値を受け取り、それがRVC命令ならis_rvc
を1
にして、inst32
に同じ意味の32ビット幅の命令を出力する組み合わせ回路です。
inst16
からソースレジスタ番号を生成します(リスト13.41)。rs1d
、rs2d
の番号の範囲はx8
からx15
です。
1: let rs1 : logic<5> = inst16[11:7]; 2: let rs2 : logic<5> = inst16[6:2]; 3: let rs1d: logic<5> = {2'b01, inst16[9:7]}; 4: let rs2d: logic<5> = {2'b01, inst16[4:2]};
inst16
から即値を生成します(リスト13.42)。
1: let imm_i : logic<12> = {inst16[12] repeat 7, inst16[6:2]}; 2: let imm_shamt: logic<6> = {inst16[12], inst16[6:2]}; 3: let imm_j : logic<20> = {inst16[12] repeat 10, inst16[8], inst16[10:9], inst16[6], inst16[7], inst16[2], inst16[11], inst16[5:3]}; 4: let imm_br : logic<12> = {inst16[12] repeat 5, inst16[6:5], inst16[2], inst16[11:10], inst16[4:3]}; 5: let c0_mem_w : logic<12> = {5'b0, inst16[5], inst16[12:10], inst16[6], 2'b0}; // C.LW, C.SW 6: let c0_mem_d : logic<12> = {4'b0, inst16[6:5], inst16[12:10], 3'b0}; // C.LD, C.SD
inst16
から32ビット幅の命令を生成します(リスト13.43)。opcode(inst16[1:0]
)が2'b11
以外なら16ビット幅の命令なので、is_rvc
に1
を割り当てます。inst32
には、初期値として右にinst16
を詰めてゼロで拡張した値を割り当てます。
32ビット幅の命令への変換はopcode、funct、レジスタ番号などで分岐して地道に実装します。32ビット幅の命令に変換できないときinst32
の値を更新しません。
inst16
が不正なRVC命令のとき、inst_decoderモジュールでデコードできない命令をcoreモジュールに供給してIllegal instruction例外を発生させ、tvalに16ビット幅の不正な命令が設定されます。
1: always_comb { 2: is_rvc = inst16[1:0] != 2'b11; 3: inst32 = {16'b0, inst16}; 4: 5: let funct3: logic<3> = inst16[15:13]; 6: case inst16[1:0] { // opcode 7: 2'b00: case funct3 { // C0 8: 3'b000: if inst16 != 0 { // C.ADDI4SPN 9: let nzuimm: logic<10> = {inst16[10:7], inst16[12:11], inst16[5], inst16[6], 2'b0}; 10: inst32 = addi(rs2d, 2, {2'b0, nzuimm}); 11: } 12: 3'b010: inst32 = load(rs2d, rs1d, c0_mem_w, 3'b010); // C.LW 13: 3'b011: if XLEN >= 64 { // C.LD 14: inst32 = load(rs2d, rs1d, c0_mem_d, 3'b011); 15: } 16: 3'b110: inst32 = store(rs1d, rs2d, c0_mem_w, 3'b010); // C.SW 17: 3'b111: if XLEN >= 64 { // C.SD 18: inst32 = store(rs1d, rs2d, c0_mem_d, 3'b011); 19: } 20: default: {} 21: } 22: 2'b01: case funct3 { // C1 23: 3'b000: inst32 = addi(rs1, rs1, imm_i); // C.ADDI 24: 3'b001: inst32 = if XLEN == 32 ? jal(1, imm_j) : addiw(rs1, rs1, imm_i); // C.JAL / C.ADDIW 25: 3'b010: inst32 = addi(rs1, 0, imm_i); // C.LI 26: 3'b011: if rs1 == 2 { // C.ADDI16SP 27: let imm : logic<10> = {inst16[12], inst16[4:3], inst16[5], inst16[2], inst16[6], 4'b0}; 28: inst32 = addi(2, 2, {imm[msb] repeat 2, imm}); 29: } else { // C.LUI 30: inst32 = lui(rs1, {imm_i[msb] repeat 8, imm_i}); 31: } 32: 3'b100: case inst16[11:10] { // funct2 or funct6[1:0] 33: 2'b00: if !(XLEN == 32 && imm_shamt[msb] == 1) { 34: inst32 = srli(rs1d, rs1d, imm_shamt); // C.SRLI 35: } 36: 2'b01: if !(XLEN == 32 && imm_shamt[msb] == 1) { 37: inst32 = srai(rs1d, rs1d, imm_shamt); // C.SRAI 38: } 39: 2'b10: inst32 = andi(rs1d, rs1d, imm_i); // C.ADNI 40: 2'b11: if inst16[12] == 0 { 41: case inst16[6:5] { 42: 2'b00 : inst32 = sub(rs1d, rs1d, rs2d); // C.SUB 43: 2'b01 : inst32 = inst_xor(rs1d, rs1d, rs2d); // C.XOR 44: 2'b10 : inst32 = inst_or(rs1d, rs1d, rs2d); // C.OR 45: 2'b11 : inst32 = inst_and(rs1d, rs1d, rs2d); // C.AND 46: default: {} 47: } 48: } else { 49: if XLEN >= 64 { 50: if inst16[6:5] == 2'b00 { 51: inst32 = subw(rs1d, rs1d, rs2d); // C.SUBW 52: } else if inst16[6:5] == 2'b01 { 53: inst32 = addw(rs1d, rs1d, rs2d); // C.ADDW 54: } 55: } 56: } 57: default: {} 58: } 59: 3'b101 : inst32 = jal(0, imm_j); // C.J 60: 3'b110 : inst32 = beq(rs1d, 0, imm_br); // C.BEQZ 61: 3'b111 : inst32 = bne(rs1d, 0, imm_br); // C.BNEZ 62: default: {} 63: } 64: 2'b10: case funct3 { // C2 65: 3'b000: if !(XLEN == 32 && imm_shamt[msb] == 1) { 66: inst32 = slli(rs1, rs1, imm_shamt); // C.SLLI 67: } 68: 3'b010: if rs1 != 0 { // C.LWSP 69: let offset: logic<8> = {inst16[3:2], inst16[12], inst16[6:4], 2'b0}; 70: inst32 = load(rs1, 2, {4'b0, offset}, 3'b010); 71: } 72: 3'b011: if XLEN >= 64 && rs1 != 0 { // C.LDSP 73: let offset: logic<9> = {inst16[4:2], inst16[12], inst16[6:5], 3'b0}; 74: inst32 = load(rs1, 2, {3'b0, offset}, 3'b011); 75: } 76: 3'b100: if inst16[12] == 0 { 77: inst32 = if rs2 == 0 ? jalr(0, rs1, 0) : addi(rs1, rs2, 0); // C.JR / C.MV 78: } else { 79: if rs2 == 0 { 80: inst32 = if rs1 == 0 ? ebreak() : jalr(1, rs1, 0); // C.EBREAK : C.JALR 81: } else { 82: inst32 = add(rs1, rs1, rs2); // C.ADD 83: } 84: } 85: 3'b110: { // C.SWSP 86: let offset: logic<8> = {inst16[8:7], inst16[12:9], 2'b0}; 87: inst32 = store(2, rs2, {4'b0, offset}, 3'b010); 88: } 89: 3'b111: if XLEN >= 64 { // C.SDSP 90: let offset: logic<9> = {inst16[9:7], inst16[12:10], 3'b0}; 91: inst32 = store(2, rs2, {3'b0, offset}, 3'b011); 92: } 93: default: {} 94: } 95: default: {} 96: } 97: }
13.6.3 RVC命令を発行する
inst_fetcherモジュールでrvc_converterモジュールをインスタンス化し、RVC命令をcoreモジュールに供給します。
まず、rvc_converterモジュールをインスタンス化します(リスト13.44)。
1: // instruction converter 2: var rvcc_inst16: logic<16>; 3: var rvcc_is_rvc: logic ; 4: var rvcc_inst32: Inst ; 5: 6: inst rvcc: rvc_converter ( 7: inst16: case issue_pc_offset { 8: 0 : fetch_fifo_rdata.bits[15:0], 9: 2 : fetch_fifo_rdata.bits[31:16], 10: 4 : fetch_fifo_rdata.bits[47:32], 11: 6 : fetch_fifo_rdata.bits[63:48], 12: default: 0, 13: }, 14: is_rvc: rvcc_is_rvc, 15: inst32: rvcc_inst32, 16: );
RVC命令のとき、変換された32ビット幅の命令をissue_fifo
に書き込み、issue_pc_offset
を4
ではなく2
増やすようにします(リスト13.45、リスト13.46)。
1: // offsetが6な32ビット命令の場合、 2: // アドレスと上位16ビットを保存してFIFOを読み進める 3: if issue_pc_offset == 6 && !rvcc_is_rvc && !issue_is_rdata_saved { 4: if fetch_fifo_rvalid { 5: issue_is_rdata_saved = 1; 6: issue_saved_addr = fetch_fifo_rdata.addr; 7: issue_saved_bits = fetch_fifo_rdata.bits[63:48]; 8: } 9: } else { 10: if issue_fifo_wready && issue_fifo_wvalid { 11: issue_pc_offset += if issue_is_rdata_saved || !rvcc_is_rvc ? 4 : 2; 12: issue_is_rdata_saved = 0; 13: } 14: }
1: if !core_if.is_hazard && fetch_fifo_rvalid { 2: if issue_fifo_wready { 3: if offset == 6 { 4: // offsetが6な32ビット命令の場合、 5: // 命令は{rdata_next[15:0], rdata[63:48}になる 6: if issue_is_rdata_saved { 7: issue_fifo_wvalid = 1; 8: issue_fifo_wdata.addr = {issue_saved_addr[msb:3], offset}; 9: issue_fifo_wdata.bits = {rdata[15:0], issue_saved_bits}; 10: issue_fifo_wdata.is_rvc = 0; 11: } else { 12: fetch_fifo_rready = 1; 13: if rvcc_is_rvc { 14: issue_fifo_wvalid = 1; 15: issue_fifo_wdata.addr = {raddr[msb:3], offset}; 16: issue_fifo_wdata.is_rvc = 1; 17: issue_fifo_wdata.bits = rvcc_inst32; 18: } else { 19: // Read next 8 bytes 20: } 21: } 22: } else { 23: fetch_fifo_rready = !rvcc_is_rvc && offset == 4; 24: issue_fifo_wvalid = 1; 25: issue_fifo_wdata.addr = {raddr[msb:3], offset}; 26: if rvcc_is_rvc { 27: issue_fifo_wdata.bits = rvcc_inst32; 28: } else { 29: issue_fifo_wdata.bits = case offset { 30: 0 : rdata[31:0], 31: 2 : rdata[47:16], 32: 4 : rdata[63:32], 33: default: 0, 34: }; 35: } 36: issue_fifo_wdata.is_rvc = rvcc_is_rvc; 37: } 38: } 39: }
riscv-testsのrv64uc-p-
から始まるテストを実行し、成功することを確認してください。