@Vengineerの戯言 : Twitter SystemVerilogの世界へようこそ、すべては、SystemC v0.9公開から始まった
はじめに
今回は、CFU をどのように 記述すればいいかを見ていきます。基本的には、Python コードで書き、そのPythonコードを自動的に Verilog HDL コードに変換している感じのようです。
Cfuクラス
CFU Playground では、プロジェクトディレクトリにある cfu.py ファイルから cfu.v ファイルを生成します。
例えば、proj/proj_template ディレクトリの cfu.py ファイルは、次のようになっています。
def make_cfu(): return Cfu({ # Add instructions here... 0: TemplateInstruction(), })
この cfu.py の make_fuc メソッドの部分で Cfu クラスに、このファイルで定義した TemplateInstruction メソッドを機能番号 0 に 登録しています。 Cfu クラスは、CFU-Playground/python/nmigen_cfu/cfu.py で次のように定義しています。
init メソッドで、引数で渡した instructions を self.instructions に代入します。最大8個の instructions を登録できます。 その後、Verilog HDL コードになったときの入力および出力信号を定義しています。
def __init__(self, instructions): self.instructions = instructions for i in range(8): if i not in self.instructions: self.instructions[i] = _FallbackInstruction() self.cmd_valid = Signal(name='cmd_valid') self.cmd_ready = Signal(name='cmd_ready') self.cmd_function_id = Signal( 10, name='cmd_payload_function_id') self.cmd_in0 = Signal(32, name='cmd_payload_inputs_0') self.cmd_in1 = Signal(32, name='cmd_payload_inputs_1') self.rsp_valid = Signal(name='rsp_valid') self.rsp_ready = Signal(name='rsp_ready') self.rsp_ok = Signal(name='rsp_payload_response_ok') self.rsp_out = Signal(32, name='rsp_payload_outputs_0') self.reset = Signal(name='reset') self.ports = [ self.cmd_valid, self.cmd_ready, self.cmd_function_id, self.cmd_in0, self.cmd_in1, self.rsp_valid, self.rsp_ready, self.rsp_ok, self.rsp_out, self.reset ]
elab メソッドでは、Verilog HDLの入出力のタイミングを生成しています。登録した各instructionを並列に実行して、入力信号 (cmd_function_id) の下位3ビットを機能番号として、この機能番号にて切り替えをやっている感じです。
def elab(self, m): # break out the functionid funct3 = Signal(3) funct7 = Signal(7) m.d.comb += [ funct3.eq(self.cmd_function_id[:3]), funct7.eq(self.cmd_function_id[-7:]), ] stored_function_id = Signal(3) current_function_id = Signal(3) current_function_done = Signal() stored_output = Signal(32) # Response is always OK. m.d.comb += self.rsp_ok.eq(1) instruction_outputs = Array(Signal(32) for _ in range(8)) instruction_dones = Array(Signal() for _ in range(8)) instruction_starts = Array(Signal() for _ in range(8)) for (i, instruction) in self.instructions.items(): m.d.comb += instruction_outputs[i].eq(instruction.output) m.d.comb += instruction_dones[i].eq(instruction.done) m.d.comb += instruction.start.eq(instruction_starts[i]) def check_instruction_done(): with m.If(current_function_done): m.d.comb += self.rsp_valid.eq(1) m.d.comb += self.rsp_out.eq( instruction_outputs[current_function_id]) with m.If(self.rsp_ready): m.next = "WAIT_CMD" with m.Else(): m.d.sync += stored_output.eq( instruction_outputs[current_function_id]) m.next = "WAIT_TRANSFER" with m.Else(): m.next = "WAIT_INSTRUCTION" with m.FSM(): with m.State("WAIT_CMD"): # We're waiting for a command from the CPU. m.d.comb += current_function_id.eq(funct3) m.d.comb += current_function_done.eq( instruction_dones[current_function_id]) m.d.comb += self.cmd_ready.eq(1) with m.If(self.cmd_valid): m.d.sync += stored_function_id.eq( self.cmd_function_id[:3]) m.d.comb += instruction_starts[current_function_id].eq(1) check_instruction_done() with m.State("WAIT_INSTRUCTION"): # An instruction is executing on the CFU. We're waiting until it # completes. m.d.comb += current_function_id.eq(stored_function_id) m.d.comb += current_function_done.eq( instruction_dones[current_function_id]) check_instruction_done() with m.State("WAIT_TRANSFER"): # Instruction has completed, but the CPU isn't ready to receive # the result. m.d.comb += self.rsp_valid.eq(1) m.d.comb += self.rsp_out.eq(stored_output) with m.If(self.rsp_ready): m.next = "WAIT_CMD" for (i, instruction) in self.instructions.items(): m.d.comb += [ instruction.in0.eq(self.cmd_in0), instruction.in1.eq(self.cmd_in1), instruction.funct7.eq(funct7), ] m.submodules[f"fn{i}"] = instruction
機能 (Instruction) の定義
各 Instruction の機能は、プロジェクトディレクトリにある cfu.py のInstructionBase クラスを継承したクラスの elab メソッドの部分になります。 proj/proj_template ディレクトリの cfu.py では、次のようにTemplateInstruction クラスの elab メソッドになります。
start が true の時は、in0 + in1 => output になり、done 信号を 1 にする。start が false の時は、done 信号を 0 にする、という感じです。
class TemplateInstruction(InstructionBase): """Template instruction """ def elab(self, m): with m.If(self.start): m.d.sync += self.output.eq(self.in0 + self.in1) m.d.sync += self.done.eq(1) with m.Else(): m.d.sync += self.done.eq(0)
Cfu クラスには、8個のInstructionを設定できると説明しましたが、設定しなかった機能番号には、次のような FallbackInstruction クラス が設定されます。 in0 => output になり、done 信号が常に 1 になっています。
class FallbackInstruction(InstructionBase): """Executed by CFU when no instruction explicitly defined for a given function id. This does nothing useful, but it does ensure that the CFU does not hang on an unknown functionid. """ def elab(self, m): m.d.comb += self.output.eq(self.in0) m.d.comb += self.done.eq(1)
Pythonコードから生成されるVerilog HDLコード
cfu.v は、次のように、Cfu モジュールとして定義されています。登録した Instruction は、fn0からfn7 モジュールとして定義されています。Cfu モジュールの中で、fn0からfn7 の切り替えを機能番号 (cmd_payload_function_id) にて切り替えを行っています。
/* Generated by Yosys 0.9 (git sha1 1979e0b) */ (* \nmigen.hierarchy = "Cfu" *) (* top = 1 *) (* generator = "nMigen" *) module Cfu(cmd_ready, cmd_payload_function_id, cmd_payload_inputs_0, cmd_payload_inputs_1, rsp_valid, rsp_ready, rsp_payload_response_ok, rsp_payload_outputs_0, reset, clk, rst, cmd_valid);
TemplateInstructionクラスに対応するVerilog HDLコード(cfu.v)は、次のようになっています。
(* \nmigen.hierarchy = "Cfu.fn0" *) (* generator = "nMigen" *) module fn0(done, start, in0, in1, rst, clk, \output ); (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/proj/proj_template/cfu.py:27" *) wire [32:0] \$1 ; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/proj/proj_template/cfu.py:27" *) wire [32:0] \$2 ; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/third_party/python/nmigen/nmigen/hdl/ir.py:524" *) input clk; (* init = 1'h0 *) (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:51" *) output done; reg done = 1'h0; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:51" *) reg \done$next ; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:46" *) input [31:0] in0; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:52" *) reg [31:0] in0s; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:47" *) input [31:0] in1; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:53" *) reg [31:0] in1s; (* init = 32'd0 *) (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:49" *) output [31:0] \output ; reg [31:0] \output = 32'd0; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:49" *) reg [31:0] \output$next ; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/third_party/python/nmigen/nmigen/hdl/ir.py:524" *) input rst; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:50" *) input start; assign \$2 = in0 + (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/proj/proj_template/cfu.py:27" *) in1; always @(posedge clk) done <= \done$next ; always @(posedge clk) \output <= \output$next ; always @* begin \output$next = \output ; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/proj/proj_template/cfu.py:26" *) casez (start) /* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/proj/proj_template/cfu.py:26" */ 1'h1: \output$next = \$1 [31:0]; endcase (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/third_party/python/nmigen/nmigen/hdl/xfrm.py:519" *) casez (rst) \output$next = 32'd0; endcase end always @* begin \done$next = done; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/proj/proj_template/cfu.py:26" *) casez (start) /* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/proj/proj_template/cfu.py:26" */ 1'h1: \done$next = 1'h1; /* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/proj/proj_template/cfu.py:29" */ default: \done$next = 1'h0; endcase (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/third_party/python/nmigen/nmigen/hdl/xfrm.py:519" *) casez (rst) 1'h1: \done$next = 1'h0; endcase end always @* begin in0s = 32'd0; in0s = in0; end always @* begin in1s = 32'd0; in1s = in1; end assign \$1 = \$2 ; endmodule
一方、FallInstruction クラスは、fn1 (fn2からfn7も同じ感じ)のように生成されています。
(* \nmigen.hierarchy = "Cfu.fn1" *) (* generator = "nMigen" *) module fn1(done, in0, in1, \output ); (* init = 1'h0 *) reg \$verilog_initial_trigger = 1'h0; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:51" *) output done; reg done; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:46" *) input [31:0] in0; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:52" *) reg [31:0] in0s; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:47" *) input [31:0] in1; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:53" *) reg [31:0] in1s; (* src = "/mnt/c/Users/haray/home/verilator/CFU-Playground/python/nmigen_cfu/cfu.py:49" *) output [31:0] \output ; reg [31:0] \output ; always @* begin \output = 32'd0; \output = in0; end always @* begin done = 1'h0; done = 1'h1; \$verilog_initial_trigger = \$verilog_initial_trigger ; end always @* begin in0s = 32'd0; in0s = in0; end always @* begin in1s = 32'd0; in1s = in1; end endmodule
おわりに
プロジェクトディレクトリにて、cfu.py という Python コード内で InstructionBase クラスを継承して、命令を定義すれば、あとは自動的に Verilog HDLコード (cfu.v) に変換され、全体に組み込まれるという仕組みです。
追加する部分だけを作ればいいというのはいいですね。