Verylで作るCPU
Star

第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つのフォーマットが定義されています。

RVC命令のフォーマット

図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.LWSPlw rd, offset(x2)CI
C.LDSPld rd, offset(x2)CI
C.SWSPsw rs2, offset(x2)CSS
C.SDSPsd rs2, offset(x2)CSS
C.LWlw rd, offset(rs)CL
C.LDld rd, offset(rs)CL
C.SWsw rs2, offset(rs1)CS
C.SDsd rs2, offset(rs1)CS
C.Jjal x0, offsetCJ
C.JRjalr x0, 0(rs1)CR
C.JALRjalr x1, 0(rs1)CR
C.BEQZbeq rs1, x0, offsetCB
C.BNEZbne rs1, x0, offsetCB
C.LIaddi rd, x0, immCI
C.LUIlui rd, immCI
C.ADDIaddi rd, rd, immCI
C.ADDIWaddiw rd, rd, immCI
C.ADDI16SPaddi x2, x2, immCI
C.ADDI4SPNaddi rd, x2, immCIW
C.SLLIslli rd, rd, shamtCI
C.SRLIsrli rd, rd, shamtCB
C.SRAIsrai rd, rd, shamtCB
C.ANDIandi rd, rd, immCB
C.MVadd rd, x0, rs2CR
C.ADDadd rd, rd, rs2CR
C.ANDand rd, rd, rs2CA
C.ORor rd, rd, rs2CA
C.XORxor rd, rd, rs2CA
C.SUBsub rd, rd, rs2CA
C.EBREAKebreakCR

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)。

リスト13.1: リスト13.1: IALIGNの定義 (eei.veryl)
1:     const IALIGN: u32 = 16;

mepcレジスタの書き込みマスクを変更して、トラップ時のジャンプ先アドレスに16ビットに整列されたアドレスを指定できるようにします(リスト13.2)。

リスト13.2: リスト13.2: MEPCの書き込みマスクを変更する (eei.veryl)
1:     const MEPC_WMASK  : UIntX = 'hffff_ffff_ffff_fffe;

命令アドレスのミスアライン例外の判定を変更します。IALIGNが16の場合は例外が発生しないようにします(リスト13.3)。ジャンプ、分岐命令は2バイト単位のアドレスしか指定できないため、C拡張が実装されている場合には例外が発生しません。

リスト13.3: リスト13.3: IALIGNが16のときに例外が発生しないようにする (core.veryl)
1:         let instruction_address_misaligned: logic = IALIGN == 32 && memq_wdata.br_taken && memq_wdata.jump_addr[1:0] != 2'b00;

13.3 実装方針

本章では次の順序でC拡張を実装します。

  1. 命令フェッチ処理(IFステージ)をcoreモジュールから分離する
  2. 16ビットに整列されたアドレスに配置された32ビット幅の命令を処理できるようにする
  3. RVC命令を32ビット幅の命令に変換するモジュールを作成する
  4. RVC命令を32ビット幅の命令に変換してcoreモジュールに供給する

最終的な命令フェッチ処理の構成は図図13.2のようになります。

命令フェッチ処理の構成

図13.2: 命令フェッチ処理の構成

13.4 命令フェッチモジュールの実装

13.4.1 インターフェースを作成する

まず、命令フェッチを行うモジュールとcoreモジュールのインターフェースを定義します。

src/core_inst_if.verylを作成し、次のように記述します(リスト13.4)。

リスト13.4: リスト13.4: core_inst_if.veryl
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: }

rvalidrreadyraddrrdataは、coreモジュールのFIFO(if_fifo)のwvalidwreadywdata.addrwdata.bitsと同じ役割を果たします。is_hazardnext_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)。

リスト13.5: リスト13.5: i_membusの型を変更する (core.veryl)
1:     i_membus: modport core_inst_if::master,

IFステージ部分のコードを次のように変更します(リスト13.6)。

リスト13.6: リスト13.6: IFステージの変更 (core.veryl)
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_fiforvalidrreadyrdataだった部分をi_membusに変更しています。

リスト13.7: リスト13.7: IDステージとi_membusを接続する (core.veryl)
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;
リスト13.8: リスト13.8: EXステージに進められるときにrreadyを1にする (core.veryl)
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)。

リスト13.9: リスト13.9: ポートの定義 (inst_fetcher.veryl)
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)。

リスト13.10: リスト13.10: fetchとissueを繋ぐFIFOの作成 (inst_fetcher.veryl)
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:     );
リスト13.11: リスト13.11: issueとcoreモジュールを繋ぐFIFOの作成 (inst_fetcher.veryl)
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ステージとほとんど同じです。

リスト13.12: リスト13.12: PCと状態管理用の変数の定義 (inst_fetcher.veryl)
1:     var fetch_pc          : Addr ;
2:     var fetch_requested   : logic;
3:     var fetch_pc_requested: Addr ;
リスト13.13: リスト13.13: メモリへの要求の割り当て (inst_fetcher.veryl)
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:     }
リスト13.14: リスト13.14: PC、状態の更新 (inst_fetcher.veryl)
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)。

リスト13.15: リスト13.15: ロードした64ビットの値をFIFOに格納する (inst_fetcher.veryl)
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)。

リスト13.16: リスト13.16: オフセットの更新 (inst_fetcher.veryl)
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:     }
リスト13.17: リスト13.17: issue_fifoに32ビットずつ命令を格納する (inst_fetcher.veryl)
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)。

リスト13.18: リスト13.18: issue_fifoとインターフェースを接続する (inst_fetcher.veryl)
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)。

リスト13.19: リスト13.19: インターフェースの定義 (top.veryl)
1:     inst i_membus_core: core_inst_if;

inst_fetcherモジュールをインスタンス化し、coreモジュールと接続します(リスト13.20リスト13.21)。

リスト13.20: リスト13.20: inst_fetcherモジュールのインスタンス化 (top.veryl)
1:     inst fetcher: inst_fetcher (
2:         clk                   ,
3:         rst                   ,
4:         core_if: i_membus_core,
5:         mem_if : i_membus     ,
6:     );
リスト13.21: リスト13.21: インターフェースを変更する (top.veryl)
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)。

リスト13.22: リスト13.22: 使用しない変数を削除する (top.veryl)
1:     var memarb_last_i: logic;
2:     var memarb_last_iaddr: Addr;
リスト13.23: リスト13.23: 使用しない変数を削除する (top.veryl)
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:     }
リスト13.24: リスト13.24: ビットの選択処理を削除する (top.veryl)
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_offset024の場合、既存の処理との変更点はありません。

fetch_fifo_rdataのデータの下位16ビットとアドレスを保持する変数を作成します(リスト13.25)。

リスト13.25: リスト13.25: データを一時保存するための変数の定義 (inst_fetcher.veryl)
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_offset6のとき、変数にデータを保存します(リスト13.26)。

リスト13.26: リスト13.26: offsetが6のとき、変数に命令の下位16ビットとアドレスを保存する (inst_fetcher.veryl)
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_offset26の場合のissue_fifoへの書き込みを実装します(リスト13.27)。6の場合、保存していた16ビットと新しく読み出した16ビットを結合した値、保存していたアドレスを書き込みます。

リスト13.27: リスト13.27: issue_fifoにoffsetが2、6の命令を格納する (inst_fetcher.veryl)
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_saved1)とき、fetch_fifoから供給されるデータには、32ビット幅の命令の上位16ビットを除いた残りの48ビットが含まれているのでfetch_fifo_rready1に設定しないことに注意してください。

13.6 RVC命令の変換

13.6.1 RVC命令フラグの実装

RVC命令を32ビット幅の命令に変換するモジュールを作る前に、RVC命令かどうかを示すフラグを作成します。

まず、core_inst_ifインターフェースとInstCtrl構造体にis_rvcフラグを追加します(リスト13.28リスト13.29リスト13.30)。

リスト13.28: リスト13.28: is_rvcフラグの定義 (core_inst_if.veryl)
1:     var rdata    : Inst ;
2:     var is_rvc   : logic;
3:     var is_hazard: logic;
リスト13.29: リスト13.29: modportにis_rvcを追加する (core_inst_if.veryl)
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:     }
リスト13.30: リスト13.30: InstCtrl型にis_rvcフラグを追加する (corectrl.veryl)
1:         is_amo   : logic      , // AMO instruction
2:         is_rvc   : logic      , // RVC instruction
3:         funct3   : logic   <3>, // 命令のfunct3フィールド

inst_fetcherモジュールで、is_rvc0に設定してcoreモジュールに供給します(リスト13.31リスト13.32リスト13.33)。

リスト13.31: リスト13.31: issue_fifo_type型にis_rvcフラグを追加する (inst_fetcher.veryl)
1:     struct issue_fifo_type {
2:         addr  : Addr ,
3:         bits  : Inst ,
4:         is_rvc: logic,
5:     }
リスト13.32: リスト13.32: is_rvcフラグを0に設定する (inst_fetcher.veryl)
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:     }
リスト13.33: リスト13.33: is_rvcフラグを接続する (inst_fetcher.veryl)
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に設定します。

リスト13.34: リスト13.34: is_rvcフラグをポートに追加する (inst_decoder.veryl)
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: ) {
リスト13.35: リスト13.35: InstCtrlにis_rvcフラグを設定する (inst_decoder.veryl)
1:                 default: {
2:                     InstType::X, F, F, F, F, F, F, F, F, F
3:                 },
4:             }, is_rvc, f3, f7
5:         };
リスト13.36: リスト13.36: IALIGNが32ではないとき、不正な命令にする (inst_decoder.veryl)
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)。

リスト13.37: リスト13.37: is_rvcフラグをinst_decoderに渡す (core.veryl)
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)。

リスト13.38: リスト13.38: 次の命令のアドレスを変える (core.veryl)
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にしています。

リスト13.39: リスト13.39: 命令のビット列を生成する関数を定義する (inst_gen_pkg.veryl)
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)。

リスト13.40: リスト13.40: ポートの定義 (rvc_converter.veryl)
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_rvc1にして、inst32に同じ意味の32ビット幅の命令を出力する組み合わせ回路です。

inst16からソースレジスタ番号を生成します(リスト13.41)。rs1drs2dの番号の範囲はx8からx15です。

リスト13.41: リスト13.41: レジスタ番号の生成 (rvc_converter.veryl)
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)。

リスト13.42: リスト13.42: 即値の生成 (rvc_converter.veryl)
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_rvc1を割り当てます。inst32には、初期値として右にinst16を詰めてゼロで拡張した値を割り当てます。

32ビット幅の命令への変換はopcode、funct、レジスタ番号などで分岐して地道に実装します。32ビット幅の命令に変換できないときinst32の値を更新しません。

inst16が不正なRVC命令のとき、inst_decoderモジュールでデコードできない命令をcoreモジュールに供給してIllegal instruction例外を発生させ、tvalに16ビット幅の不正な命令が設定されます。

リスト13.43: リスト13.43: RVC命令を32ビット幅の命令に変換する (rvc_converter.veryl)
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)。

リスト13.44: リスト13.44: rvc_converterモジュールのインスタンス化 (inst_fetcher.veryl)
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_offset4ではなく2増やすようにします(リスト13.45リスト13.46)。

リスト13.45: リスト13.45: RVC命令のときのオフセットの更新 (inst_fetcher.veryl)
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: }
リスト13.46: リスト13.46: RVC命令のときのissue_fifoへの書き込み (inst_fetcher.veryl)
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-から始まるテストを実行し、成功することを確認してください。