FADD.S命令
浮動小数点数の加算 FADD.S を、以下のFPUコアを利用して実装する。
dawsonjon / fpu
丸めモード
FADD.S命令の丸めモード(rm)のところに何を入れれば良い?
code:text
00a5f553 fadd.s fa0,fa1,fa0
fadd.s fa0, fa1, fa0 をコンパイルした結果が以下のバイト列。
00a5f553
0000_0000_1010_0101_1111_0101_0101_0011
0000000_01010_01011_111_01010_1010011
fadd.S(rs2 = f10, rs1 = f11, rm = 111, rd = f10)
rm = 111 って何?
丸めモードを指定しないことも可能らしい
111 は指定なし?
ひとまず rm のところには 111 を入れておくことにする
FADD.S命令
https://gyazo.com/e53f583d682d7833e2427d4db30f5325
instructions.sv へ FADD.S と FSUB.S を追加。
code:instructions.sv
// fadd.s
// rd = rs1 + rs2
);
fadd_s = {
7'b0000000, // funct7
rs2,
rs1,
3'b111, // roundMode (既存の出力コードに合わせて 111 を入れておく)
rd,
7'b1010011 // opCode
};
endfunction
// fsub.s
// rd = rs1 - rs2
);
fsub_s = {
7'b0000100, // funct7
rs2,
rs1,
3'b111, // roundMode (既存の出力コードに合わせて 111 を入れておく)
rd,
7'b1010011 // opCode
};
endfunction
adderを組み込んでみる
adder.v の使い方はこんな感じ。
1. input_a と input_b をセットし、input_a_stb と input_b_stb をアサートする。同じタイミングで output_z_ack をデアサートしておく
2. 計算が終わると output_z_stb がアサートされるので、それまで待機
3. output_z から計算結果を取得
4. 計算結果見終わったよを伝えるため、output_z_ack をアサートする。同じタイミングで input_a_stb と input_b_stb をデアサートしておく
FPUを使う場合のステージの遷移
FPUなしの場合
IF => EX => MEM => WB
FPUありの場合
IF
=> EX
=> FPEX1(in_strb をアサート)
=> FPEX2(result_strb のアサートを待機)
=> FPEX3(resultをレジスタに書き込んで、result_ack をアサート。同じタイミングで in1_strb と in2_strb をデアサート)
=> MEM(result_ack をデアサート)
=> WB
実装
こんな感じで動いた〜
code:diff
diff --git a/Makefile b/Makefile
index d91a75b..04aef1f 100644
--- a/Makefile
+++ b/Makefile
@@ -33,7 +33,7 @@ unit-test:
iverilog -g 2012 -s alu_test rtl/instructions.sv rtl/cpu.sv rtl/alu_test.sv && ./a.out
test:
- iverilog -g 2012 -s cpu_test -I rtl rtl/cpu_test.sv rtl/bram_controller.sv rtl/cpu.sv && ./a.out
+ iverilog -g 2012 -s cpu_test -I rtl rtl/cpu_test.sv rtl/bram_controller.sv rtl/fpu/adder/adder.v rtl/fpu_controller.sv rtl/cpu.sv && ./a.out
firmware/firmware.hex:
diff --git a/rtl/cpu.sv b/rtl/cpu.sv
index 131f644..bf27ad6 100644
--- a/rtl/cpu.sv
+++ b/rtl/cpu.sv
@@ -54,15 +54,19 @@ module cpu(
typedef enum {
IF_STAGE,
EX_STAGE,
+ FP1_STAGE, // FPU へ入力を渡す
+ FP2_STAGE, // FPU からの出力を待機
+ FP3_STAGE, // FPU の出力をレジスタへ保存
MEM_STAGE,
WB_STAGE,
ERR_STAGE
} stage_t;
stage_t stage_reg, stage_next;
- logic 31:0 pc_reg, pc_next; // PC - logic 31:0 instr_reg, instr_next; // フェッチした命令 - logic 31:0 mem_rdata_reg, mem_rdata_next; // lw でメモリから読み込んだデータ + logic 31:0 pc_reg, pc_next; // PC + logic 31:0 instr_reg, instr_next; // フェッチした命令 + logic 31:0 mem_rdata_reg, mem_rdata_next; // lw でメモリから読み込んだデータ + logic 31:0 fpu_result_reg, fpu_result_next; // FPU の演算結果 //-------------------------------------
// 出力信号
@@ -93,6 +97,7 @@ module cpu(
logic read_reg_type1;
logic read_reg_type2;
logic write_reg_type;
+ logic fpu;
decoder decoder_inst(
.instr(instr_reg),
@@ -107,7 +112,8 @@ module cpu(
.jumpReg(jump_reg),
.readRegType1(read_reg_type1),
.readRegType2(read_reg_type2),
- .writeRegType(write_reg_type)
+ .writeRegType(write_reg_type),
+ .fpu(fpu)
);
//-------------------------------------
@@ -142,6 +148,38 @@ module cpu(
.zero(alu_zero)
);
+ //-------------------------------------
+ // FPU
+ //-------------------------------------
+
+ logic 31:0 fpu_in1, fpu_in2, fpu_result; + logic fpu_in1_stb, fpu_in2_stb, fpu_result_ack;
+ logic fpu_in1_ack, fpu_in2_ack, fpu_result_stb;
+
+ assign fpu_op = 4'b0000; // fadd
+ assign fpu_in1 = rf_read_data1;
+ assign fpu_in2 = rf_read_data2;
+
+ assign fpu_in1_stb = (stage_reg == FP1_STAGE || stage_reg == FP2_STAGE) ? 1'b1 : 1'b0;
+ assign fpu_in2_stb = (stage_reg == FP1_STAGE || stage_reg == FP2_STAGE) ? 1'b1 : 1'b0;
+ assign fpu_result_ack = (stage_reg == FP3_STAGE) ? 1'b1 : 1'b0;
+
+ fpu_controller fpu_inst(
+ .clk(clk),
+ .reset_n(reset_n),
+ .op(fpu_op),
+ .in1(fpu_in1),
+ .in2(fpu_in2),
+ .in1_stb(fpu_in1_stb),
+ .in2_stb(fpu_in2_stb),
+ .in1_ack(fpu_in1_ack),
+ .in2_ack(fpu_in2_ack),
+ .out(fpu_result),
+ .out_stb(fpu_result_stb),
+ .out_ack(fpu_result_ack)
+ );
+
//-------------------------------------
// 条件分岐判定ユニット
//-------------------------------------
@@ -168,8 +206,9 @@ module cpu(
assign rf_addr2 = instr_reg24:20; // rs2 assign rf_addr3 = instr_reg11:7; // rd assign rf_we3 = (stage_reg == WB_STAGE) && dc_reg_write;
- assign rf_write_data3 = (dc_mem_to_reg) ? mem_rdata_reg : // lw の場合
- (jump) ? pc_reg + 4 // jal, jalr の場合
+ assign rf_write_data3 = (dc_mem_to_reg) ? mem_rdata_reg : // lw の場合
+ (jump) ? pc_reg + 4 : // jal, jalr の場合
+ (fpu === 1'b1) ? fpu_result_reg // FPU の演算結果
: alu_result;
regfile regfile_inst(
@@ -194,11 +233,13 @@ module cpu(
pc_reg <= 0;
instr_reg <= 32'h00000000;
mem_rdata_reg <= 32'h00000000;
+ fpu_result_reg <= 32'h00000000;
end else begin
stage_reg <= stage_next;
pc_reg <= pc_next;
instr_reg <= instr_next;
mem_rdata_reg <= mem_rdata_next;
+ fpu_result_reg <= fpu_result_next;
end
end
@@ -208,6 +249,7 @@ module cpu(
instr_next = instr_reg;
mem_rdata_next = mem_rdata_reg;
pc_next = pc_reg;
+ fpu_result_next = fpu_result_reg;
case (stage_reg)
// 命令フェッチ
@@ -222,7 +264,10 @@ module cpu(
end
// 実行
EX_STAGE: begin
- if (dc_mem_write) begin
+ if (fpu) begin
+ // 浮動小数点演算の場合
+ stage_next = FP1_STAGE;
+ end else if (dc_mem_write) begin
// sw の場合
stage_next = MEM_STAGE;
end else if (dc_mem_to_reg) begin
@@ -233,6 +278,24 @@ module cpu(
stage_next = WB_STAGE;
end
end
+ // 浮動小数点演算1(FPUへ入力を渡す)
+ FP1_STAGE: begin
+ stage_next = FP2_STAGE;
+ end
+ // 浮動小数点演算2(FPUからの出力を待機)
+ FP2_STAGE: begin
+ if (fpu_result_stb) begin
+ stage_next = FP3_STAGE;
+ end else begin
+ stage_next = FP2_STAGE;
+ end
+ end
+ // 浮動小数点演算3(FPUの出力をレジスタへ保存)
+ FP3_STAGE: begin
+ // FPU の演算結果をレジスタに保存
+ fpu_result_next = fpu_result;
+ stage_next = MEM_STAGE;
+ end
// メモリアクセス
MEM_STAGE: begin
if (mem_ready) begin
@@ -273,7 +336,8 @@ module decoder(
output logic jumpReg,
output logic readRegType1,
output logic readRegType2,
- output logic writeRegType
+ output logic writeRegType,
+ output logic fpu
);
@@ -299,6 +363,7 @@ module decoder(
(opCode === 7'b0000111) ? 1'b1 : // flw
(opCode === 7'b0010011) ? 1'b1 : // addi, ori
(opCode === 7'b0110011) ? 1'b1 : // R type (add)
+ (opCode === 7'b1010011) ? 1'b1 : // RV32F R type
(opCode === 7'b0110111) ? 1'b1 : // lui
(opCode === 7'b0010111) ? 1'b1 : // auipc
(opCode === 7'b1101111) ? 1'b1 : // jal
@@ -349,14 +414,19 @@ module decoder(
(opCode == 7'b0010011) ? 2'b11 // addi, ori => funct3
: 2'b10; // funct
- assign readRegType1 = 1'b0; // 整数レジスタを参照
+ // assign readRegType1 = 1'b0; // 整数レジスタを参照
+ assign readRegType1 = (opCode === 7'b1010011) ? 1'b1 // 浮動小数点レジスタを参照(RV32F の R type)
+ : 1'b0; // 整数レジスタを参照
- assign readRegType2 = (opCode === 7'b0100111) ? 1'b1 // 浮動小数点レジスタを参照 (fsw)
+ assign readRegType2 = (opCode === 7'b0100111) ? 1'b1 : // 浮動小数点レジスタを参照 (fsw)
+ (opCode === 7'b1010011) ? 1'b1 // 浮動小数点レジスタを参照(RV32F の R type)
: 1'b0; // 整数レジスタを参照
- assign writeRegType = (opCode === 7'b0000111) ? 1'b1 // 浮動小数点レジスタへ書き込み (flw)
- : 1'b0; // 整数レジスタへ書き込み
+ assign writeRegType = (opCode === 7'b0000111) ? 1'b1 : // 浮動小数点レジスタへ書き込み (flw)
+ (opCode === 7'b1010011) ? 1'b1 // 浮動小数点レジスタを参照(RV32F の R type)
+ : 1'b0; // 整数レジスタへ書き込み
+ assign fpu = (opCode === 7'b1010011) ? 1'b1 : 1'b0; // FPU を使う命令(RV32F R-type)
// always @(*) begin
// $display("opCode %b", opCode);
@@ -610,5 +680,7 @@ module regfile(
// $display("$30 %b", registers30); // $display("$31 %b", registers31); // $display("f5 %d", fpRegisters5); + // $display("f6 %d", fpRegisters6); + // $display("f7 %d", fpRegisters7); end
endmodule
diff --git a/rtl/fpu_controller.sv b/rtl/fpu_controller.sv
new file mode 100644
index 0000000..7980f77
--- /dev/null
+++ b/rtl/fpu_controller.sv
@@ -0,0 +1,65 @@
+//------------------------------------------------------------------------------
+// FPU Controller
+//
+// code | operation
+// -----|----------
+// 0000 | fadd
+// 0001 | fsub
+// 0010 | fmul
+// 0011 | fdiv
+// 0100 | fcvt.s.w
+// 0101 | fcvt.w.s
+// 0110 | feq
+// 0111 | flt
+// 1000 | fle
+//------------------------------------------------------------------------------
+
+module fpu_controller(
+ input logic clk,
+ input logic reset_n,
+ input in1_stb, in2_stb,
+ output in1_ack, in2_ack,
+ output out_stb,
+ input out_ack
+);
+ // 入力セレクタ
+ assign adder_in1_stb = (op === 4'b0000) ? in1_stb : 1'b0;
+ assign adder_in2_stb = (op === 4'b0000) ? in2_stb : 1'b0;
+ assign adder_out_ack = (op === 4'b0000) ? out_ack : 1'b0;
+
+ // 出力セレクタ
+ assign in1_ack = (op === 4'b0000) ? adder_in1_ack : 1'bx;
+ assign in2_ack = (op === 4'b0000) ? adder_in2_ack : 1'bx;
+ assign out_stb = (op === 4'b0000) ? adder_out_stb : 1'bx;
+ assign out = (op === 4'b0000) ? adder_out : 32'bx;
+
+ //------------------------------------------------------------------------------
+ // Floating-point Adder
+ //------------------------------------------------------------------------------
+
+ logic adder_in1_stb; // in1 が有効になったらアサートする
+ logic adder_in2_stb; // in2 が有効になったらアサートする
+ logic adder_in1_ack; // 受け手側で in1 の読み込みが終わったらアサートされる
+ logic adder_in2_ack; // 受け手側で in2 の読み込みが終わったらアサートされる
+ logic adder_out_stb; // 計算結果が out に返ってきたらアサートされる
+ logic adder_out_ack; // out の読み込みが終わったらアサートしてあげる
+
+ adder adder_inst (
+ .clk(clk),
+ .rst(~reset_n),
+ .input_a(in1),
+ .input_a_stb(adder_in1_stb),
+ .input_a_ack(adder_in1_ack),
+ .input_b(in2),
+ .input_b_stb(adder_in2_stb),
+ .input_b_ack(adder_in2_ack),
+ .output_z(adder_out),
+ .output_z_stb(adder_out_stb),
+ .output_z_ack(adder_out_ack)
+ );
+
+endmodule
diff --git a/rtl/test_program/fp.sv b/rtl/test_program/fp.sv
index 8f14c9d..fddcd92 100644
--- a/rtl/test_program/fp.sv
+++ b/rtl/test_program/fp.sv
@@ -2,11 +2,21 @@
* 浮動小数点数のテストプログラム
*/
+/* instruction0からinstruction63までの命令がメモリに書き込まれる */ +
+// FLW と FSW のテスト
instructions0 = flw(5, 0, 32'h100); // f5 = Mx0+0x100 instructions1 = fsw(0, 5, 32'h104); // M0x104 = f5 instructions2 = flw(6, 0, 32'h104); // f6 = M0x104 instructions3 = fsw(0, 6, 32'h108); // M0x108 = f6 -instructions4 = jal(0, 0); // 無限ループ +
+// FADD.S のテスト
+instructions4 = flw(5, 0, 32'h100); // f5 = M0x100 +instructions5 = flw(6, 0, 32'h100); // f6 = M0x100 +instructions6 = fadd_s(7, 5, 6); // f7 = f5 + f6 +instructions7 = fsw(0, 7, 32'h10c); // M0x10c = f7 +
+instructions8 = jal(0, 0); // 無限ループ // テスト用の浮動小数点数データ
instructions64 = 32'h3f800000; // M0x100 = 1.0 @@ -43,7 +53,7 @@ reset_n = 0;
reset_n = 1;
-#1000;
+#2000;
// 実行が終わった頃合いを見て、メモリの内容を確認
mem_monitor_on = 1;
@@ -69,3 +79,15 @@ assert(
) $display("PASSED"); else $display("FAILED: %d", mem_rdata);
mem_monitor_valid_reg = 0;
+
+mem_monitor_on = 1;
+mem_monitor_valid_reg = 1;
+mem_monitor_addr_reg = 32'h10c;
+mem_monitor_wstrb_reg = 4'b0000;
+#10;
+wait(mem_ready);
+assert(
+ mem_rdata === 32'h40000000
+) $display("PASSED"); else $display("FAILED: %d", mem_rdata);
+mem_monitor_valid_reg = 0;
+#10;
メモ
0.0 => 0x00000000
1.0 => 0x3f800000
2.0 => 0x40000000
3.0 => 0x40400000
123.456 => 0x42f6e979
-123.456 => 0xc2f6e979