第12章
A拡張の実装
本章では、メモリの不可分操作を実現するA拡張を実装します。A拡張にはLoad-Reserved、Store-Conditionalを実現するZalrsc拡張(表12.2)、ロードした値を加工し、その結果をメモリにストアする操作を単一の命令で実装するZaamo拡張(表12.1)が含まれています。A拡張の命令を利用すると、同じメモリ空間で複数のソフトウェアを並列、並行して実行するとき、ソフトウェア間で同期をとりながら実行できます。
12.1 アトミック操作
12.1.1 アトミック操作とは何か?
アトミック操作(Atomic operation、不可分操作)とは、他のシステムからその操作を観測するとき、1つの操作として観測される操作のことです。つまり、他のシステムは、アトミック操作を行う前、アトミック操作を行った後の状態しか観測できません。

図12.1: 図12.2のプログラムを2つに分割して2つのCPUで実行する (Xは11になる)

図12.2: 1つのCPUでメモリ上の値を2回インクリメントする (Xは12になる)
アトミック操作は実行、観測される順序が重要なアプリケーションで利用します。例えば、アドレスXの値をロードして1を足した値を書き戻すプログラムを、2つのコアで同時に実行するとします(図12.1)。このとき命令の実行順序によっては、最終的な値が1つのコアで2回プログラムを実行した場合と異なってしまいます(図12.2)。この状態を避けるためにはロード、加算、ストアをアトミックに行う必要があります。このアトミック操作の実現方法として、A拡張はAMOADD命令、LR命令とSC命令を提供します。
12.1.2 Zaamo拡張
Zaamo拡張は、値をロードして、演算した値をストアする操作を1つの命令で行う命令を定義しています。AMOADD命令はロード、加算、ストアを行う単一の命令です。Zaamo拡張は他にも簡単な操作を行う命令も提供しています。
表12.1: Zaamo拡張の命令
命令 | 動作 (読み込んだ値をレジスタにライトバックする) |
---|---|
AMOSWAP.W/D | メモリから32/64ビット読み込み、 rs2の値を書き込む |
AMOADD.W/D | メモリから32/64ビット(符号付き)読み込み rs2(符号付き)の値を足して書き込む |
AMOAND.W/D | メモリから32/64ビット読み込み rs2の値をAND演算して書き込む |
AMOOR.W/D | メモリから32/64ビット読み込み rs2の値をOR演算して書き込む |
AMOXOR.W/D | メモリから32/64ビット読み込み rs2の値をXOR演算して書き込む |
AMOMIN.W/D | メモリから32/64ビット(符号付き)読み込み rs2(符号付き)の値と比べて小さい値を書き込む |
AMOMAX.W/D | メモリから32/64ビット(符号付き)読み込み rs2(符号付き)の値と比べて大きい値をを書き込む |
AMOMINU.W/D | メモリから32/64ビット(符号無し)読み込み rs2(符号無し)の値と比べて小さい値を書き込む |
AMOMAXU.W/D | メモリから32/64ビット(符号無し)読み込み rs2(符号無し)の値と比べて大きい値を書き込む |
12.1.3 Zalrsc拡張
Zalrsc拡張は、LR命令とSC命令を定義しています。LR、SC命令は、それぞれLoad-Reserved、Store-Conditional操作を実現する命令です。それぞれ次のように動作します。
- LR命令
- 指定されたアドレスのデータを読み込み、指定されたアドレスを予約セット(Reservation set)に登録します。 ロードしたデータをレジスタにライトバックします。
- SC命令
-
指定されたアドレスが予約セットに存在する場合、指定されたアドレスにデータを書き込みます(ストア成功)。
予約セットにアドレスが存在しない場合は書き込みません(ストア失敗)。
ストアに成功したら
0
、失敗したら0
以外の値をレジスタにライトバックします。 命令の実行後に必ず予約セットを空にします。
LR、SC命令を使うことで、アトミックなロード、加算、ストアを次のように記述できます(リスト12.1)。
1: atomic_add: 2: LR.W x2, (x3) ← アドレスx3の値をx2にロード 3: ADDI x2, x2, 1 ← x2に1を足す 4: SC.W x4, x2, (x3) ← ストアを試行し、結果をx4に格納 5: BNEZ x4, atomic_add ← SC命令が失敗していたらやり直す
例えば同時に2つのコアがリスト12.1を実行するとき、同期をとれていない書き込みはSC命令で失敗します。失敗したらLR命令からやり直すことで、1つのコアで2回実行した場合と同一の結果(1
を2回加算)になります。
予約セットのサイズは実装によって異なります。
表12.2: Zalrsc拡張の命令
命令 | 動作 |
---|---|
LR.W/D | メモリから32/64ビット読み込み、予約セットにアドレスを登録する 読み込んだ値をレジスタにライトバックする |
SC.W/D | 予約セットにrs1の値が登録されている場合、メモリにrs2の値を書き込み 0をレジスタにライトバックする。予約セットにアドレスが登録されていない場合 メモリに書き込まず、0以外の値をレジスタにライトバックする。 命令の実行後に予約セットを空にする |
12.1.4 命令の順序
A拡張の命令のビット列は、それぞれ1ビットのaq、rlビットを含んでいます。このビットは、他のコアやハードウェアスレッドからメモリ操作を観測したときにメモリ操作がどのような順序で観測されるかを制御するものです。
A拡張の命令をAとするとき、それぞれのビットの状態に応じて、Aによるメモリ操作は次のように観測されます。
- aq=0、rl=0
- Aの前後でメモリ操作の順序は保証されません。
- aq=1、rl=0
- Aの後ろにあるメモリを操作する命令は、Aのメモリ操作の後に観測されることが保証されます。
- aq=0、rl=1
- Aのメモリ操作は、Aの前にあるメモリを操作する命令が観測できるようになった後に観測されることが保証されます。
- aq=1、rl=1
- Aのメモリ操作は、Aの前にあるメモリを操作する命令よりも後、Aの後ろにあるメモリを操作する命令よりも前に観測されることが保証されます。
今のところ、CPUはメモリ操作を1命令ずつ直列に実行するため、常にaqが1
、rlが1
であるように動作します。そのため、本章ではaq、rlビットを考慮しないで実装を行います*1。
[*1] メモリ操作の並び替えによる高速化は応用編で検討します。
12.2 命令のデコード
A拡張の命令はすべてR形式で、opcodeはOP-AMO(7'b0101111
)です。それぞれの命令はfunct5(リスト12.3)とfunct3(Wは2
、Dは3
)で区別できます。
eeiパッケージにOP-AMOの定数を定義します(リスト12.2)。
1: const OP_AMO : logic<7> = 7'b0101111;
A拡張の命令を区別するための列挙型AMOOp
を定義します(リスト12.3)。それぞれ命令のfunct5と対応しています。
1: enum AMOOp: logic<5> { 2: LR = 5'b00010, 3: SC = 5'b00011, 4: SWAP = 5'b00001, 5: ADD = 5'b00000, 6: XOR = 5'b00100, 7: AND = 5'b01100, 8: OR = 5'b01000, 9: MIN = 5'b10000, 10: MAX = 5'b10100, 11: MINU = 5'b11000, 12: MAXU = 5'b11100, 13: }
12.2.1 is_amoフラグを実装する
InstCtrl
構造体に、A拡張の命令であることを示すis_amo
フラグを追加します(リスト12.4)。
1: struct InstCtrl { 2: itype : InstType , // 命令の形式 3: rwb_en : logic , // レジスタに書き込むかどうか 4: is_lui : logic , // LUI命令である 5: is_aluop : logic , // ALUを利用する命令である 6: is_muldiv: logic , // M拡張の命令である 7: is_op32 : logic , // OP-32またはOP-IMM-32である 8: is_jump : logic , // ジャンプ命令である 9: is_load : logic , // ロード命令である 10: is_csr : logic , // CSR命令である 11: is_amo : logic , // AMO instruction 12: funct3 : logic <3>, // 命令のfunct3フィールド 13: funct7 : logic <7>, // 命令のfunct7フィールド 14: }
命令がメモリにアクセスするかを判定するinst_is_memop関数を、is_amo
フラグを利用するように変更します(リスト12.5)。
1: function inst_is_memop ( 2: ctrl: input InstCtrl, 3: ) -> logic { 4: return ctrl.itype == InstType::S || ctrl.is_load || ctrl.is_amo; 5: }
inst_decoderモジュールのInstCtrl
を生成している部分を変更します。opcodeがOP-AMO
のとき、is_amo
をT
に設定します(リスト12.6)。その他のopcodeのis_amo
はF
に設定してください。
1: OP_SYSTEM: { 2: InstType::I, T, F, F, F, F, F, F, T, F 3: }, 4: OP_AMO: { 5: InstType::R, T, F, F, F, F, F, F, F, T 6: }, 7: default: { 8: InstType::X, F, F, F, F, F, F, F, F, F 9: },
また、A拡張の命令が有効な命令として判断されるようにします(リスト12.7)。
1: OP_MISC_MEM: T, // FENCE 2: OP_AMO : f3 == 3'b010 || f3 == 3'b011, // AMO 3: default : F,
12.2.2 アドレスを変更する
A拡張でアクセスするメモリのアドレスはrs1で指定されたレジスタの値です。これは基本整数命令セットのロードストア命令のアドレス指定方法(rs1と即値を足し合わせる)とは異なるため、memunitモジュールのaddr
ポートに割り当てる値をis_amo
フラグによって切り替えます(リスト12.8)。
1: var memu_rdata: UIntX; 2: var memu_stall: logic; 3: let memu_addr : Addr = if mems_ctrl.is_amo ? memq_rdata.rs1_data : memq_rdata.alu_result; 4: 5: inst memu: memunit ( 6: clk , 7: rst , 8: valid : mems_valid && !mems_expt.valid, 9: is_new: mems_is_new , 10: ctrl : mems_ctrl , 11: addr : memu_addr , 12: rs2 : memq_rdata.rs2_data , 13: rdata : memu_rdata , 14: stall : memu_stall , 15: membus: d_membus , 16: );
A拡張の命令のメモリアドレスが、操作するデータの幅に整列されていないとき、Store/AMO address misaligned例外が発生します。この例外はストア命令の場合の例外と同じです。
EXステージの例外判定でアドレスを使っている部分を変更します(リスト12.9)。causeとtvalの割り当てがストア命令の場合と同じになっていることを確認してください。
1: let memaddr : Addr = if exs_ctrl.is_amo ? exs_rs1_data : exs_alu_result; 2: let loadstore_address_misaligned : logic = inst_is_memop(exs_ctrl) && case exs_ctrl.funct3[1:0] { 3: 2'b00 : 0, // B 4: 2'b01 : memaddr[0] != 1'b0, // H 5: 2'b10 : memaddr[1:0] != 2'b0, // W 6: 2'b11 : memaddr[2:0] != 3'b0, // D 7: default: 0, 8: };
12.2.3 ライトバックする条件を変更する
A拡張の命令を実行するとき、ロードした値をレジスタにライトバックするように変更します(リスト12.10)。
1: let wbs_wb_data: UIntX = if wbs_ctrl.is_lui ? 2: wbs_imm 3: : if wbs_ctrl.is_jump ? 4: wbs_pc + 4 5: : if wbs_ctrl.is_load || wbs_ctrl.is_amo ? 6: wbq_rdata.mem_rdata 7: : if wbs_ctrl.is_csr ?
12.3 amounitモジュールの作成
A拡張は他のコア、ハードウェアスレッドと同期してメモリ操作を行うためのものであるため、A拡張の操作はcoreモジュールの外、メモリよりも前で行います。本書では、coreモジュールとmmio_controllerモジュールの間に、A拡張の命令を処理するamounitモジュールを実装します(図12.3)。

図12.3: amounitモジュールと他のモジュールの接続
12.3.1 インターフェースを作成する
amounitモジュールにA拡張の操作を指示するために、is_amo
フラグ、aq
ビット、rl
ビット、AMOOp
型をmembus_ifインターフェースに追加で定義したインターフェースを作成します。
src/core_data_if.veryl
を作成し、次のように記述します(リスト12.11)。
1: import eei::*; 2: 3: interface core_data_if { 4: var valid : logic ; 5: var ready : logic ; 6: var addr : logic<XLEN> ; 7: var wen : logic ; 8: var wdata : logic<MEMBUS_DATA_WIDTH> ; 9: var wmask : logic<MEMBUS_DATA_WIDTH / 8>; 10: var rvalid: logic ; 11: var rdata : logic<MEMBUS_DATA_WIDTH> ; 12: 13: var is_amo: logic ; 14: var aq : logic ; 15: var rl : logic ; 16: var amoop : AMOOp ; 17: var funct3: logic<3>; 18: 19: modport master { 20: valid : output, 21: ready : input , 22: addr : output, 23: wen : output, 24: wdata : output, 25: wmask : output, 26: rvalid: input , 27: rdata : input , 28: is_amo: output, 29: aq : output, 30: rl : output, 31: amoop : output, 32: funct3: output, 33: } 34: 35: modport slave { 36: ..converse(master) 37: } 38: 39: modport all_input { 40: ..input 41: } 42: }
12.3.2 amounitモジュールの作成
メモリ操作をcoreモジュールからそのままmmio_controllerモジュールに受け渡しするだけのモジュールを作成します。src/amounit.veryl
を作成し、次のように記述します(リスト12.12)。
1: import eei::*; 2: 3: module amounit ( 4: clk : input clock , 5: rst : input reset , 6: slave : modport core_data_if::slave, 7: master: modport Membus::master , 8: ) { 9: 10: enum State { 11: Init, 12: WaitReady, 13: WaitValid, 14: } 15: 16: var state : State; 17: inst slave_saved: core_data_if; 18: 19: // masterをリセットする 20: function reset_master () { 21: master.valid = 0; 22: master.addr = 0; 23: master.wen = 0; 24: master.wdata = 0; 25: master.wmask = 0; 26: } 27: 28: // masterに要求を割り当てる 29: function assign_master ( 30: addr : input Addr , 31: wen : input logic , 32: wdata: input UIntX , 33: wmask: input logic<$size(UIntX) / 8>, 34: ) { 35: master.valid = 1; 36: master.addr = addr; 37: master.wen = wen; 38: master.wdata = wdata; 39: master.wmask = wmask; 40: } 41: 42: // 新しく要求を受け入れる 43: function accept_request_comb () { 44: if slave.ready && slave.valid { 45: assign_master(slave.addr, slave.wen, slave.wdata, slave.wmask); 46: } 47: } 48: 49: // slaveに結果を割り当てる 50: always_comb { 51: slave.ready = 0; 52: slave.rvalid = 0; 53: slave.rdata = 0; 54: 55: case state { 56: State::Init: { 57: slave.ready = 1; 58: } 59: State::WaitValid: { 60: slave.ready = master.rvalid; 61: slave.rvalid = master.rvalid; 62: slave.rdata = master.rdata; 63: } 64: default: {} 65: } 66: } 67: 68: // masterに要求を割り当てる 69: always_comb { 70: reset_master(); 71: case state { 72: State::Init : accept_request_comb(); 73: State::WaitReady: { 74: assign_master(slave_saved.addr, slave_saved.wen, slave_saved.wdata, slave_saved.wmask); 75: } 76: State::WaitValid: accept_request_comb(); 77: default : {} 78: } 79: } 80: 81: // 新しく要求を受け入れる 82: function accept_request_ff () { 83: slave_saved.valid = slave.ready && slave.valid; 84: if slave.ready && slave.valid { 85: slave_saved.addr = slave.addr; 86: slave_saved.wen = slave.wen; 87: slave_saved.wdata = slave.wdata; 88: slave_saved.wmask = slave.wmask; 89: slave_saved.is_amo = slave.is_amo; 90: slave_saved.amoop = slave.amoop; 91: slave_saved.aq = slave.aq; 92: slave_saved.rl = slave.rl; 93: slave_saved.funct3 = slave.funct3; 94: state = if master.ready ? State::WaitValid : State::WaitReady; 95: } else { 96: state = State::Init; 97: } 98: } 99: 100: function on_clock () { 101: case state { 102: State::Init : accept_request_ff(); 103: State::WaitReady: if master.ready { 104: state = State::WaitValid; 105: } 106: State::WaitValid: if master.rvalid { 107: accept_request_ff(); 108: } 109: default: {} 110: } 111: } 112: 113: function on_reset () { 114: state = State::Init; 115: slave_saved.addr = 0; 116: slave_saved.wen = 0; 117: slave_saved.wdata = 0; 118: slave_saved.wmask = 0; 119: slave_saved.is_amo = 0; 120: slave_saved.amoop = 0 as AMOOp; 121: slave_saved.aq = 0; 122: slave_saved.rl = 0; 123: slave_saved.funct3 = 0; 124: } 125: 126: always_ff { 127: if_reset { 128: on_reset(); 129: } else { 130: on_clock(); 131: } 132: } 133: }
amounitモジュールはState::Init
、(State::WaitReady
、)State::WaitValid
の順に状態を移動し、通常のロードストア命令を処理します。
coreモジュールのロードストア用のインターフェースをmembus_ifからcore_data_ifに変更します(リスト12.13、リスト12.14、リスト12.15)。
1: i_membus: modport membus_if::<ILEN, XLEN>::master, 2: d_membus: modport core_data_if::master , 3: led : output UIntX ,
1: inst d_membus_core: core_data_if;
1: inst c: core ( 2: clk , 3: rst , 4: i_membus , 5: d_membus: d_membus_core, 6: led , 7: );
memunitモジュールのインターフェースも変更し、is_amo
、aq
、rl
、amoop
に値を割り当てます(リスト12.16、リスト12.17、リスト12.19、リスト12.18、リスト12.20)。
1: stall : output logic , // メモリアクセス命令が完了していない 2: membus: modport core_data_if::master, // メモリとのinterface 3: ) {
1: var req_wen : logic ; 2: var req_addr : Addr ; 3: var req_wdata : logic<MEMBUS_DATA_WIDTH> ; 4: var req_wmask : logic<MEMBUS_DATA_WIDTH / 8>; 5: var req_is_amo: logic ; 6: var req_amoop : AMOOp ; 7: var req_aq : logic ; 8: var req_rl : logic ; 9: var req_funct3: logic<3> ;
1: always_ff { 2: if_reset { 3: state = State::Init; 4: req_wen = 0; 5: req_addr = 0; 6: req_wdata = 0; 7: req_wmask = 0; 8: req_is_amo = 0; 9: req_amoop = 0 as AMOOp; 10: req_aq = 0; 11: req_rl = 0; 12: req_funct3 = 0; 13: } else {
1: always_comb { 2: // メモリアクセス 3: membus.valid = state == State::WaitReady; 4: membus.addr = req_addr; 5: membus.wen = req_wen; 6: membus.wdata = req_wdata; 7: membus.wmask = req_wmask; 8: membus.is_amo = req_is_amo; 9: membus.amoop = req_amoop; 10: membus.aq = req_aq; 11: membus.rl = req_rl; 12: membus.funct3 = req_funct3;
1: case state { 2: State::Init: if is_new & inst_is_memop(ctrl) { 3: ... 4: req_is_amo = ctrl.is_amo; 5: req_amoop = ctrl.funct7[6:2] as AMOOp; 6: req_aq = ctrl.funct7[1]; 7: req_rl = ctrl.funct7[0]; 8: req_funct3 = ctrl.funct3; 9: } 10: State::WaitReady: if membus.ready {
amounitモジュールをtopモジュールでインスタンス化し、coreモジュールとmmio_controllerモジュールのインターフェースを接続します(リスト12.21)。
1: inst amou: amounit ( 2: clk , 3: rst , 4: slave : d_membus_core, 5: master: d_membus , 6: );
12.4 Zalrsc拡張の実装
Zalrsc拡張の命令を実装します。予約セットのサイズは実装が自由に決めることができるため、本書では1つのアドレスのみ保持できるようにします。
12.4.1 LR.W、LR.D命令を実装する
32ビット幅、64ビット幅のLR命令を実装します。LR.W命令はmemunitモジュールで64ビットに符号拡張されるため、amounitモジュールでLR.W命令とLR.D命令を区別する必要はありません。
amounitモジュールに予約セットを作成します(リスト12.22、リスト12.23)。is_addr_reserved
で、予約セットに有効なアドレスが格納されているかを管理します。
1: // lr/sc 2: var is_addr_reserved: logic; 3: var reserved_addr : Addr ;
1: is_addr_reserved = 0; 2: reserved_addr = 0;
LR命令を実行するとき、予約セットにアドレスを登録してロード結果を返すようにします(リスト12.24、リスト12.25、リスト12.26)。既に予約セットが使われている場合はアドレスを上書きします。
1: function accept_request_comb () { 2: if slave.ready && slave.valid { 3: if slave.is_amo { 4: case slave.amoop { 5: AMOOp::LR: assign_master(slave.addr, 0, 0, 0); 6: default : {} 7: } 8: } else { 9: assign_master(slave.addr, slave.wen, slave.wdata, slave.wmask); 10: } 11: } 12: }
1: always_comb { 2: reset_master(); 3: case state { 4: State::Init : accept_request_comb(); 5: State::WaitReady: if slave_saved.is_amo { 6: case slave_saved.amoop { 7: AMOOp::LR: assign_master(slave_saved.addr, 0, 0, 0); 8: default : {} 9: } 10: } else { 11: assign_master(slave_saved.addr, slave_saved.wen, slave_saved.wdata, slave_saved.wmask); 12: }
1: function accept_request_ff () { 2: slave_saved.valid = slave.ready && slave.valid; 3: if slave.ready && slave.valid { 4: slave_saved.addr = slave.addr; 5: ... 6: slave_saved.funct3 = slave.funct3; 7: if slave.is_amo { 8: case slave.amoop { 9: AMOOp::LR: { 10: // reserve address 11: is_addr_reserved = 1; 12: reserved_addr = slave.addr; 13: state = if master.ready ? State::WaitValid : State::WaitReady; 14: } 15: default: {} 16: } 17: } else { 18: state = if master.ready ? State::WaitValid : State::WaitReady; 19: }
12.4.2 SC.W、SC.D命令を実装する
32ビット幅、64ビット幅のSC命令を実装します。SC.W命令はmemunitモジュールで書き込みマスクを設定しているため、amounitモジュールでSC.W命令とSC.D命令を区別する必要はありません。
SC命令が成功、失敗したときに結果を返すための状態をState
型に追加します(リスト12.27)。
1: enum State { 2: Init, 3: WaitReady, 4: WaitValid, 5: SCSuccess, 6: SCFail, 7: }
それぞれの状態で結果を返し、新しく要求を受け入れるようにします(リスト12.28)。State::SCSuccess
はSC命令に成功してストアが終わったときに結果を返します。成功したら0
、失敗したら1
を返します。
1: State::SCSuccess: { 2: slave.ready = master.rvalid; 3: slave.rvalid = master.rvalid; 4: slave.rdata = 0; 5: } 6: State::SCFail: { 7: slave.ready = 1; 8: slave.rvalid = 1; 9: slave.rdata = 1; 10: }
SC命令を受け入れるときに予約セットを確認し、アドレスが予約セットのアドレスと異なる場合は状態をState::SCFail
に移動します(リスト12.29)。成功、失敗に関係なく、予約セットを空にします。
1: AMOOp::SC: { 2: // reset reserved 3: let prev : logic = is_addr_reserved; 4: is_addr_reserved = 0; 5: // check 6: if prev && slave.addr == reserved_addr { 7: state = if master.ready ? State::SCSuccess : State::WaitReady; 8: } else { 9: state = State::SCFail; 10: } 11: }
SC命令でメモリのready
が1
になるのを待っているとき、ready
が1
になったら状態をState::SCSuccess
に移動します(リスト12.30)。また、命令の実行が終了したときに新しく要求を受け入れるようにします。
1: function on_clock () { 2: case state { 3: State::Init : accept_request_ff(); 4: State::WaitReady: if master.ready { 5: if slave_saved.is_amo && slave_saved.amoop == AMOOp::SC { 6: state = State::SCSuccess; 7: } else { 8: state = State::WaitValid; 9: } 10: } 11: State::WaitValid: if master.rvalid { 12: accept_request_ff(); 13: } 14: State::SCSuccess: if master.rvalid { 15: accept_request_ff(); 16: } 17: State::SCFail: accept_request_ff(); 18: default : {} 19: } 20: }
SC命令によるメモリへの書き込みを実装します(リスト12.31、リスト12.32)。
1: case slave.amoop { 2: AMOOp::LR: assign_master(slave.addr, 0, 0, 0); 3: AMOOp::SC: if is_addr_reserved && slave.addr == reserved_addr { 4: @<b> assign_master(slave.addr, 1, slave.wdata, slave.wmask);| 5: @<b> }| 6: default: {} 7: }
1: always_comb { 2: reset_master(); 3: case state { 4: State::Init : accept_request_comb(); 5: State::WaitReady: if slave_saved.is_amo { 6: case slave_saved.amoop { 7: AMOOp::LR: assign_master(slave_saved.addr, 0, 0, 0); 8: AMOOp::SC: assign_master(slave_saved.addr, 1, slave_saved.wdata, slave_saved.wmask); 9: default : {} 10: } 11: } else { 12: assign_master(slave_saved.addr, slave_saved.wen, slave_saved.wdata, slave_saved.wmask); 13: } 14: State::WaitValid : accept_request_comb(); 15: State::SCFail, State::SCSuccess: accept_request_comb(); 16: default : {} 17: } 18: }
12.5 Zaamo拡張の実装
Zaamo拡張の命令はロード、演算、ストアを行います。本章では、Zaamo拡張の命令をState::Init
(、State::AMOLoadReady
)、State::AMOLoadValid
(、State::AMOStoreReady
)、State::AMOStoreValid
という状態遷移で処理するように実装します。
State
型に新しい状態を定義してください(リスト12.33)。
1: enum State { 2: Init, 3: WaitReady, 4: WaitValid, 5: SCSuccess, 6: SCFail, 7: AMOLoadReady, 8: AMOLoadValid, 9: AMOStoreReady, 10: AMOStoreValid, 11: }
簡単にZalrsc拡張と区別するために、Zaamo拡張による要求かどうかを判定する関数(is_Zaamo
)をcore_data_ifインターフェースに作成します(リスト12.34、リスト12.35)。modportにimport宣言を追加してください。
1: function is_Zaamo () -> logic { 2: return is_amo && (amoop != AMOOp::LR && amoop != AMOOp::SC); 3: }
1: amoop : output, 2: funct3 : output, 3: is_Zaamo: import, 4: }
ロードした値とwdata
、フラグを利用して、ストアする値を生成する関数を作成します(リスト12.36)。32ビット演算のとき、下位32ビットと上位32ビットのどちらを使うかをアドレスによって判別しています。
1: // AMO ALU 2: function calc_amo::<W: u32> ( 3: amoop: input AMOOp , 4: wdata: input logic<W>, 5: rdata: input logic<W>, 6: ) -> logic<W> { 7: let lts: logic = $signed(wdata) <: $signed(rdata); 8: let ltu: logic = wdata <: rdata; 9: 10: return case amoop { 11: AMOOp::SWAP: wdata, 12: AMOOp::ADD : rdata + wdata, 13: AMOOp::XOR : rdata ^ wdata, 14: AMOOp::AND : rdata & wdata, 15: AMOOp::OR : rdata | wdata, 16: AMOOp::MIN : if lts ? wdata : rdata, 17: AMOOp::MAX : if !lts ? wdata : rdata, 18: AMOOp::MINU: if ltu ? wdata : rdata, 19: AMOOp::MAXU: if !ltu ? wdata : rdata, 20: default : 0, 21: }; 22: } 23: 24: // Zaamo拡張の命令のwdataを生成する 25: function gen_amo_wdata ( 26: req : modport core_data_if::all_input, 27: rdata: input UIntX , 28: ) -> UIntX { 29: case req.funct3 { 30: 3'b010: { // word 31: let low : logic = req.addr[2] == 0; 32: let rdata32: UInt32 = if low ? rdata[31:0] : rdata[63:32]; 33: let wdata32: UInt32 = if low ? req.wdata[31:0] : req.wdata[63:32]; 34: let result : UInt32 = calc_amo::<32>(req.amoop, wdata32, rdata32); 35: return if low ? {rdata[63:32], result} : {result, rdata[31:0]}; 36: } 37: 3'b011 : return calc_amo::<64>(req.amoop, req.wdata, rdata); // double 38: default: return 0; 39: } 40: }
ロードした値が命令の結果になるため、値を保持するためのレジスタを作成します(リスト12.37、リスト12.38)。
1: // amo 2: var zaamo_fetched_data: UIntX;
1: reserved_addr = 0; 2: zaamo_fetched_data = 0; 3: }
メモリアクセスが終了したら、ロードした値を返します(リスト12.39)。
1: State::AMOStoreValid: { 2: slave.ready = master.rvalid; 3: slave.rvalid = master.rvalid; 4: slave.rdata = zaamo_fetched_data; 5: }
状態に基づいて、メモリへのロード、ストア要求を割り当てます(リスト12.40、リスト12.41)。
1: default: if slave.is_Zaamo() { 2: assign_master(slave.addr, 0, 0, 0); 3: }
1: State::AMOLoadReady : assign_master (slave_saved.addr, 0, 0, 0); 2: State::AMOLoadValid, State::AMOStoreReady: { 3: let rdata : UIntX = if state == State::AMOLoadValid ? master.rdata : zaamo_fetched_data; 4: let wdata : UIntX = gen_amo_wdata(slave_saved, rdata); 5: assign_master(slave_saved.addr, 1, wdata, slave_saved.wmask); 6: } 7: State::AMOStoreValid: accept_request_comb();
master
、slave
の状態によってstate
を遷移します(リスト12.42)。
1: default: if slave.is_Zaamo() { 2: state = if master.ready ? State::AMOLoadValid : State::AMOLoadReady; 3: }
1: State::AMOLoadReady: if master.ready { 2: state = State::AMOLoadValid; 3: } 4: State::AMOLoadValid: if master.rvalid { 5: zaamo_fetched_data = master.rdata; 6: state = if slave.ready ? State::AMOStoreValid : State::AMOStoreReady; 7: } 8: State::AMOStoreReady: if master.ready { 9: state = State::AMOStoreValid; 10: } 11: State::AMOStoreValid: if master.rvalid { 12: accept_request_ff(); 13: }
riscv-testsのrv64ua-p-
から始まるテストを実行し、成功することを確認してください。