构建五级流水线——lab1_bonus

本文最后更新于 2026年6月15日 18:45:15

乘除法

一、lab1背景介绍

在lab1中,我们完成了基本的加减法运算,在bonus部分,我们将会实现乘除法。为什么乘除法不能像加减法那样在alu中用*/来简单表示呢?这里我们需要对cpu频率和周期数的计算有更深入的了解:

在cpu中,每当时钟信号来临时,寄存器的值会被输入到各个模块中进行运算,等待其中的组合逻辑运算完成并将数据存入寄存器之后才能进入下一个时钟周期。这意味着,时钟周期受限于关键路径,即时钟周期必须大于等于两个寄存器之间延迟最长的组合逻辑。对于加法和减法,使用超前进位加法器可以在\(O(lgn)\)时间内完成计算,但乘除法复杂度高很多,强行放在在一个时钟周期内完成会导致时钟频率大大下降,严重拖慢cpu的运行速率。

为了确保cpu的速度,我们需要将乘除法分散在多个周期内完成,也就是本次lab的主题。

二、lab1目标

在lab1——bonus中,我们需要完成以下指令:

  1. mulmulw
    1
    mul x3, x1, x2  # 将x1*x2的低64位结果写入x3

这个指令会截断最终结果,只保留后64位,相当于对\(2^{64}\)进行取模运算。而 $X_{unsigned} = X_{signed} + 2^{64} $ ,因此mul指令对于有/无符号数得到的计算结果完全相同,不需要加以区分。

  1. div, divu, divwdivuwrem, remuremwremuw
  • div:求商
  • rem:求余数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    li x1, 13
    li x2, -5
    # 有符号操作
    div x3, x1, x2 # x3 = 13 / -5 = -2 (向零舍入)
    rem x4, x1, x2 # x4 = 13 % -5 = 3 (余数符号同被除数 x1)
    # 无符号操作
    # 此时 x2 的 -5 会被当作一个极大的无符号正数 (0xFFFFFFFB)
    divu x5, x1, x2 # x5 = 13 / 0xFFFFFFFB = 0
    remu x6, x1, x2 # x6 = 13 % 0xFFFFFFFB = 13

注:带w后缀的是32位版本的指令,只处理操作数的低32位,最后再把32位结果符号扩展成64位写回寄存器,divuwremuw也一样进行符号扩展。

注:先截断操作数是必要操作,尤其在除法中,如果高32位非空,不进行截断往往会带来错误的结果

  1. 边界条件 除数为0时,根据规定:
  • div/divu/divw/divuw:返回全1(对应有符号数的-1和无符号数的最大值)
  • rem/remu:返回被除数,以满足被除数 = 除数 × 商 + 余数remw/remuw:先将被除数截断至32位,再进行符号扩展

最小负数/(-1)=最小负数: > 补码中,有符号数的负数比整数多一位,原先结果为正,但超出表示范围,只能溢出,最终结果等于最小负数;余数仍然是0

三、乘法

本lab中,我采用移位乘法,思路非常接近快速幂: > 对于一个x位乘法A*B,进行x轮操作,每轮操作将A左移一位,再将B右移一位,每次根据B的最低位判断是否要加上A,最终汇报结果

这里的乘法器是一个状态机,需要存储当前的状态,后续状态也与当前状态有关。

乘法器共有三种状态:IDLEBUSYDONE,分别表示空闲,正在进行运算和运算完成。 - IDLE表示乘法器可以接收start信号开始计算,将操作数加载进来 - BUSY是乘法器正在被占用 - DONE用于指示当前拍已经完成计算,可以让下一条指令进来了,下一拍再次变回IDLE完成循环。

具体实现如下:

1. 加入新的指令

首先,我们需要先为两条乘法指令在mypkg中定义枚举类型:

1
2
3
4
5
6
mypkg.sv
typedef enum logic [1:0] {
MUL_NONE = 2'd0,
MUL_MUL = 2'd1,
MUL_MULW = 2'd2
} mul_t;
### 2. 接着完成乘法器主体部分 在utils中新建multiplier。作为一个时序逻辑,它需要接受clk信号和reset信号。同时,decoder将会提供各类操作信号(start,use_w,rs1_num,rs2_num),其中start是新出现的符号,表示乘法器需要开始运算了。最终,乘法器要将内部状态暴露出去,输出busydone,以及计算结果result
1
2
3
4
5
6
7
8
9
10
11
12
multiplier.sv
module multiplier
import common::*;
(
input logic clk,
input logic reset,
input u1 start,use_w,
input u64 rs1_num,rs2_num,

output u1 busy,done,
output u64 result
);

接着定义内部状态stateacc表示accumulator,用于存放中间运算结果,multiplicand存放被乘数rs1,multiplier_num存放乘数rs2。count用于计数,round则是循环上限。busydone信号通过内部状态导出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
multiplier.sv
typedef enum logic [1:0] {
MUL_IDLE = 2'd0,
MUL_BUSY = 2'd1,
MUL_DONE = 2'd2
} state_t;

state_t state;
u7 count, rounds;
u64 acc, multiplicand, multiplier_num;
u64 addend, acc_next;

assign busy = (state == MUL_BUSY);
assign done = (state == MUL_DONE);
然后是主体循环部分,假设每个时钟上升沿到来进行一轮运算,利用count进行计数。addend是每轮乘法的加数,根据当前乘数尾数决定是否增加被乘数,acc_next是下一步计算结果,利用历史结果acc加上addend得到。

之后根据内部状态决定如何运行:start会让IDLE转为BUSY,并加载数据;BUSY一共round-1轮,每次将acc更新为acc_next,改变乘数、被乘数;当进行round-1轮之后,更新状态为DONE,同时直接将acc_next的数据拿出来交给结果;一轮之后将DONE还原为IDLE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
multiplier.sv
assign addend = multiplier_num[0] ? multiplicand : 64'b0;
assign acc_next = acc + addend;

always_ff @(posedge clk) begin
if (reset) begin
state <= MUL_IDLE;
count <= 7'b0;
rounds <= 7'b0;
acc <= 64'b0;
multiplicand <= 64'b0;
multiplier_num <= 64'b0;
result <= 64'b0;
end else begin
case (state)
MUL_IDLE: begin
if (start) begin
state <= MUL_BUSY;
count <= 7'b0;
rounds <= use_w ? 7'd32 : 7'd64;
acc <= 64'b0;
multiplicand <= use_w ? {32'b0, rs1_num[31:0]} : rs1_num;
multiplier_num <= use_w ? {32'b0, rs2_num[31:0]} : rs2_num;
end
end
MUL_BUSY: begin
acc <= acc_next;
multiplicand <= multiplicand << 1;
multiplier_num <= multiplier_num >> 1;
count <= count + 7'd1;

if (count == (rounds - 7'd1)) begin
state <= MUL_DONE;
result <= use_w ? {{32{acc_next[31]}}, acc_next[31:0]} : acc_next;
end
end
MUL_DONE: begin
state <= MUL_IDLE;
end
default: begin
state <= MUL_IDLE;
end
endcase
end
end

3. 在decoder中输出对应信号

乘法信号is_mul需要额外根据funct7funct3判断,以与普通的寄存器操作区分开

1
2
decoder.sv
assign is_mul = ((is_alu_r || is_alu_rw) && (funct7 == 7'b0000001) && (funct3 == 3'b000));

并在下面判断mul_op

1
2
3
4
5
6
7
8
9
10
11
12
13
decoder.sv
else if (is_alu_r) begin
if (funct7 == 7'b0000001) begin
case(funct3)
3'b000: mul_op = MUL_MUL;
default: op = ALU_WRONG;
endcase
else if (is_alu_rw) begin
if (funct7 == 7'b0000001) begin
case(funct3)
3'b000: mul_op = MUL_MULW;
default: op = ALU_WRONG;
endcase

4. 将is_mulmul_opid_ex传递给ex

代码整体比较常规,这里掠过 需要注意bubble时要将is_mul清零,防止意外启动乘法器

5. 将multiplier包装进ex模块

ex模块需要额外接收clkreset信号,decoder解析出来的mul_opis_mul信号转交给multiplier,并将乘法器状态mul_done向外输出。

1
2
3
4
5
6
ex.sv
input logic clk,
input u1 reset,
input mul_t mul_op,
input u1 is_mul,
output u1 mul_done,
在内部,需要将普通alu信号与乘法器结果区分开,于是将原先的alu_result重命名为alu_result_normal;定义mul_busy导出乘法器状态,mul_use_w用于指导乘法器是否进行32位计算;定义mul_start用于指导multiplier何时开始计算

1
2
ex.sv
mul_start = is_mul && ~mul_busy;

这里只需要判断是乘法指令,且当前乘法器不在运行即可,DONE状态根本不理会start信号 最终根据当前指令是否是乘法指令来选择ex输出

1
2
ex.sv
assign alu_result = is_mul ? mul_result : alu_result_normal;

6. 完成乘法器的流水线控制

乘法器在计算时,ex阶段不能流入新的指令,因此pc,if_id,id_ex全都要stall;而ex_mem需要塞气泡,代表这是无效指令。待乘法器由BUSY转为DONE阶段时,stall取消,让新的指令流进来;bubble取消,让乘法器的结果流出去。 而当一条乘法指令进来时,乘法器从IDLE转为BUSY,这一拍ex_mem会根据mul_result将答案传出去,导致错误。为了防止这一行为,我们应该在识别出当前指令是乘法指令时就进行拦截,且这一拦截优先级高于DONE信号的判断

1
2
ctrl.sv
assign mul_stall = ex_is_mul && ~ex_mul_done;

接着把这个信号接入:

1
2
3
4
ctrl.sv
assign pc_enable = global_enable && ~load_use_stall && ~ibus_stall && ~mul_stall;
assign if_id_enable = global_enable && ~load_use_stall && ~mul_stall;
assign id_ex_enable = global_enable && ~mul_stall;
最后是ex_mem专属的flush
1
2
ctrl.sv
assign ex_mem_flush = global_enable && mul_stall;

7. 在core中完成接线和实例化

这次接线比较清晰,略去代码部分

四、除法

整体思路可以复用,具体实施过程中采用移位除法,复用乘法的stall逻辑。

1. 在mypkg中定义新的除法枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
mypkg.sv
typedef enum logic [3:0] {
DIV_NONE = 4'd0,
DIV_DIV = 4'd1,
DIV_DIVU = 4'd2,
DIV_REM = 4'd3,
DIV_REMU = 4'd4,
DIV_DIVW = 4'd5,
DIV_DIVUW = 4'd6,
DIV_REMW = 4'd7,
DIV_REMUW = 4'd8
} div_t;

2. 完成除法模块divide

为了复用除法模块,我们的目标是完成一个只负责无符号运算,兼容32位与64位运算及除零的divide模块。有符号运算会先在ex中进行预处理,这里按下不表。

1)核心逻辑与伪代码:从被除数的高位向低位依次处理。若余数大于除数,则减去除数,商对应位数上变成1。

1
2
3
4
5
6
7
8
rem = 0
quot = dividend
for i in range(rounds):
rem = {rem[62:0], quot[63]}
quot = {quot[62:0], 1'b0}
if rem >= divisor:
rem = rem - divisor
quot[i] = 1

2)代码如下:

初始化(reset略去了):
  • 根据是否是32位运算决定最多进行多少轮移位
    • 看情况将除数、被除数截取到32位
    • rounds表示轮数,count表示进行了几轮
  • 如果被除数是0,直接进入边界处理(结束计算、商全1、余数为被除数)
  • 否则进入BUSY模式开始计算:
    • 32位运算时,将被除数低32位放在商的高32位,后续全部为0,而不是低32位。 > 这样做是合理的:因为32位运算只进行32轮,因此从高到低的32位内必须把除法解决掉
    • 余数为0
  • 定义use_wdivisor的reg寄存器,用于保存输入内容;定义remainder的reg寄存器,用于保存中间结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
divide.sv
assign normalized_dividend = use_w ? {32'b0, dividend[31:0]} : dividend;
assign normalized_divisor = use_w ? {32'b0, divisor[31:0]} : divisor;
DIV_IDLE: begin
if (start) begin
count <= 7'b0;
rounds <= use_w ? 7'd32 : 7'd64;
use_w_reg <= use_w;
divisor_reg <= normalized_divisor;
remainder_reg <= 65'b0;
if (normalized_divisor == 64'b0) begin
state <= DIV_DONE;
quotient <= 64'hffff_ffff_ffff_ffff;
remainder <= normalized_dividend;
end else begin
state <= DIV_BUSY;
quotient <= use_w ? {dividend[31:0], 32'b0} : dividend;
remainder <= 64'b0;
end
end
end
运行:
  • 商左移1位,接上0;商的最高位接到余数后面
  • 若商大于余数:减去余数,商最后一位为1
  • remainder_shift需要在常规remainder的基础上额外继承商的一位,因此定义成65位寄存器。由循环不变量知remainder[65]=1不会造成溢出:
    • 上一轮结束后 remainder_reg < divisor。开始时remainder_reg是0,因此开始时成立。
    • 本轮:remainder_shift = remainder_reg * 2 + next_bit,所以remainder_shift < 2 * divisor
    • remainder_shift >= divisor 时:remainder_next = remainder_shift - divisor。因此remainder_next < divisor。因此remainder_next一定能放进64位
  • remainder_nextdivisor_ext需要参与remainder_shift的运算,所以都定义成65位寄存器
  • 如果只剩1轮,下次直接变回DIV_DONE,防止多一轮等待
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    divide.sv
    assign remainder_shift = {remainder_reg[63:0], quotient[63]};
    assign quotient_shift = {quotient[62:0], 1'b0};
    assign divisor_ext = {1'b0, divisor_reg};

    always_comb begin
    if (remainder_shift >= divisor_ext) begin
    remainder_next = remainder_shift - divisor_ext;
    quotient_next = quotient_shift | 64'd1;
    end else begin
    remainder_next = remainder_shift;
    quotient_next = quotient_shift;
    end
    end

    DIV_BUSY: begin
    count <= count + 7'd1;
    quotient <= quotient_next;
    remainder_reg <= remainder_next;

    if (count == (rounds - 7'd1)) begin
    state <= DIV_DONE;
    quotient <= quotient_result;
    remainder <= remainder_result;
    end
    end
状态接手
  • 向外展示除法器状态
  • 如果使用32位,则商和余数都截取低32位,高位全部为0
    1
    2
    3
    4
    5
    divide.sv
    assign busy = (state == DIV_BUSY);
    assign done = (state == DIV_DONE);
    assign quotient_result = use_w_reg ? {32'b0, quotient_next[31:0]} : quotient_next;
    assign remainder_result = use_w_reg ? {32'b0, remainder_next[31:0]} : remainder_next[63:0];

3. 在ex阶段处理除法器相关内容

divide模块只负责无符号除法,所以在ex阶段需要先把RISC-V除法指令转换成无符号除法器能处理的形式。这里主要完成四件事: - 根据div_op判断是否是32位运算、是否是有符号运算、最终返回商还是余数 - 对有符号除法先取绝对值,送入无符号divide - 处理除0和最小负数除以-1这两个边界条件 - 在多周期过程中锁存修正结果需要的信息,避免stall期间前递数据变化影响最终结果

首先在ex模块端口中加入除法控制信号,并定义除法器需要的中间变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ex.sv
input div_t div_op,
input u1 is_mul, is_div, mem_read, mem_write,

output u1 mul_done, div_done,

u64 alu_result_normal,mul_result,div_result;
u64 div_dividend,div_divisor,div_quotient,div_remainder;
u64 div_quotient_signed,div_remainder_signed,div_result_pre;
u64 div_zero_quotient,div_zero_remainder,div_overflow_quotient;
u64 div_original_dividend_reg;
u1 div_busy,div_start,div_use_w,div_is_signed,div_is_rem;
u1 div_rs1_sign,div_rs2_sign,div_quotient_neg;
u1 div_by_zero,div_overflow;
u1 div_use_w_reg,div_is_rem_reg,div_rs1_sign_reg,div_quotient_neg_reg;
u1 div_by_zero_reg,div_overflow_reg;

随后根据div_op拆分指令语义。div_use_w决定除法器执行32轮还是64轮;div_is_signed决定是否需要对操作数取绝对值;div_is_rem决定最终选择商还是余数。对于有符号除法,送入divide前需要将负数转为正数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ex.sv
assign div_start = is_div && ~div_busy;
assign div_use_w = (div_op == DIV_DIVW) || (div_op == DIV_DIVUW) ||
(div_op == DIV_REMW) || (div_op == DIV_REMUW);
assign div_is_signed = (div_op == DIV_DIV) || (div_op == DIV_REM) ||
(div_op == DIV_DIVW) || (div_op == DIV_REMW);
assign div_is_rem = (div_op == DIV_REM) || (div_op == DIV_REMU) ||
(div_op == DIV_REMW) || (div_op == DIV_REMUW);

assign div_rs1_sign = div_is_signed && (div_use_w ? forward_rs1_val[31] : forward_rs1_val[63]);
assign div_rs2_sign = div_is_signed && (div_use_w ? forward_rs2_val[31] : forward_rs2_val[63]);
assign div_quotient_neg = div_rs1_sign ^ div_rs2_sign;

assign div_dividend = div_is_signed ?
(div_use_w ?
{32'b0, (div_rs1_sign ? (~forward_rs1_val[31:0] + 32'd1) : forward_rs1_val[31:0])} :
(div_rs1_sign ? (~forward_rs1_val + 64'd1) : forward_rs1_val)) :
(div_use_w ? {32'b0, forward_rs1_val[31:0]} : forward_rs1_val);
assign div_divisor = div_is_signed ?
(div_use_w ?
{32'b0, (div_rs2_sign ? (~forward_rs2_val[31:0] + 32'd1) : forward_rs2_val[31:0])} :
(div_rs2_sign ? (~forward_rs2_val + 64'd1) : forward_rs2_val)) :
(div_use_w ? {32'b0, forward_rs2_val[31:0]} : forward_rs2_val);

除法的边界条件也在ex中统一处理。除数为0时,商为全1,余数为原被除数;有符号最小负数除以-1时,商保持最小负数,余数为0。由于除法器会运行多个周期,所以这些信息在div_start时锁存下来,保证DONE周期使用的仍然是同一条除法指令的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ex.sv
assign div_by_zero = div_use_w ? (forward_rs2_val[31:0] == 32'b0) : (forward_rs2_val == 64'b0);
assign div_overflow = div_is_signed &&
((div_use_w && (forward_rs1_val[31:0] == 32'h8000_0000) &&
(forward_rs2_val[31:0] == 32'hffff_ffff)) ||
(!div_use_w && (forward_rs1_val == 64'h8000_0000_0000_0000) &&
(forward_rs2_val == 64'hffff_ffff_ffff_ffff)));

always_ff @(posedge clk) begin
if (reset) begin
div_original_dividend_reg <= 64'b0;
div_use_w_reg <= 1'b0;
div_is_rem_reg <= 1'b0;
div_rs1_sign_reg <= 1'b0;
div_quotient_neg_reg <= 1'b0;
div_by_zero_reg <= 1'b0;
div_overflow_reg <= 1'b0;
end else if (div_start) begin
div_original_dividend_reg <= forward_rs1_val;
div_use_w_reg <= div_use_w;
div_is_rem_reg <= div_is_rem;
div_rs1_sign_reg <= div_rs1_sign;
div_quotient_neg_reg <= div_quotient_neg;
div_by_zero_reg <= div_by_zero;
div_overflow_reg <= div_overflow;
end
end

divide给出无符号商和余数后,ex再根据前面锁存的信息修正符号并选择最终结果。对于所有w后缀指令,最终都取低32位并符号扩展到64位。

1
2
3
4
5
6
7
8
9
10
ex.sv
assign div_quotient_signed = div_quotient_neg_reg ? (~div_quotient + 64'd1) : div_quotient;
assign div_remainder_signed = div_rs1_sign_reg ? (~div_remainder + 64'd1) : div_remainder;
assign div_zero_quotient = div_use_w_reg ? {32'b0, 32'hffff_ffff} : 64'hffff_ffff_ffff_ffff;
assign div_zero_remainder = div_use_w_reg ? {32'b0, div_original_dividend_reg[31:0]} : div_original_dividend_reg;
assign div_overflow_quotient = div_use_w_reg ? {32'b0, 32'h8000_0000} : 64'h8000_0000_0000_0000;
assign div_result_pre = div_by_zero_reg ? (div_is_rem_reg ? div_zero_remainder : div_zero_quotient) :
div_overflow_reg ? (div_is_rem_reg ? 64'b0 : div_overflow_quotient) :
div_is_rem_reg ? div_remainder_signed : div_quotient_signed;
assign div_result = div_use_w_reg ? {{32{div_result_pre[31]}}, div_result_pre[31:0]} : div_result_pre;

最后实例化divide模块,并在ex阶段输出结果时让除法优先于乘法和普通ALU结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ex.sv
divide u_divide(
.clk (clk),
.reset (reset),
.start (div_start),
.use_w (div_use_w),
.dividend (div_dividend),
.divisor (div_divisor),
.busy (div_busy),
.done (div_done),
.quotient (div_quotient),
.remainder (div_remainder)
);

assign alu_result = is_div ? div_result :
is_mul ? mul_result : alu_result_normal;

4. ctrl阶段复用乘法器逻辑

除法和乘法一样是多周期执行单元,所以流水线控制可以直接复用原先乘法的stall策略:当EX阶段存在除法指令且div_done还没有拉高时,冻结PC/IF_ID/ID_EX,同时向EX/MEM注入气泡;等DONE周期到来后,stall取消,除法结果进入后级。

为了避免为乘法和除法分别写两套控制,ctrl中将它们合并成multi_cycle_stall

1
2
3
4
5
6
7
ctrl.sv
input u1 ex_mem_read, should_jump, commit_flush, ex_is_mul, ex_mul_done, ex_is_div, ex_div_done,

logic mul_stall, div_stall, multi_cycle_stall;
assign mul_stall = ex_is_mul && ~ex_mul_done;
assign div_stall = ex_is_div && ~ex_div_done;
assign multi_cycle_stall = mul_stall || div_stall;

之后原先使用mul_stall的位置统一改为multi_cycle_stall。这样乘法和除法都遵循相同的流水线行为。

1
2
3
4
5
6
ctrl.sv
assign pc_enable = global_enable && ~load_use_stall && ~ibus_stall && ~multi_cycle_stall;
assign if_id_enable = global_enable && ~load_use_stall && ~multi_cycle_stall;
assign id_ex_enable = global_enable && ~multi_cycle_stall;

assign ex_mem_flush = (global_enable && commit_flush) || (global_enable && multi_cycle_stall);

5. 信号传递复用乘法器传递逻辑

除法信号的传递链路和乘法保持一致:decoder产生is_div/div_opid_ex寄存这两个信号,最后在core中分别送到ex执行单元和ctrl控制单元。

首先在decoder中增加除法输出信号。is_div只识别funct7=0000001funct3100/101/110/111的M扩展指令,避免和普通寄存器ALU指令混淆。

1
2
3
4
5
6
7
8
9
10
11
12
decoder.sv
output u1 is_mul,
output u1 is_div,
output u1 is_ecall,
output u1 is_valid_inst,
output xret_t xret_type,
output mul_t mul_op,
output div_t div_op

assign is_div = ((is_alu_r || is_alu_rw) && (funct7 == 7'b0000001) &&
((funct3 == 3'b100) || (funct3 == 3'b101) ||
(funct3 == 3'b110) || (funct3 == 3'b111)));

合法指令判断中也要放开除法和取余对应的funct3,否则这些指令会被非法指令异常截断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
decoder.sv
7'b0110011: begin // register ALU
case (funct7)
7'b0000001: is_valid_inst = (funct3 == 3'b000) ||
(funct3 == 3'b100) ||
(funct3 == 3'b101) ||
(funct3 == 3'b110) ||
(funct3 == 3'b111); // mul/div/rem
endcase
end

7'b0111011: begin // register word ALU
case (funct7)
7'b0000001: is_valid_inst = (funct3 == 3'b000) ||
(funct3 == 3'b100) ||
(funct3 == 3'b101) ||
(funct3 == 3'b110) ||
(funct3 == 3'b111); // mulw/divw/remw
endcase
end

随后根据funct3生成具体的div_op。普通64位指令和w后缀指令使用同一套funct3编码,只是枚举值不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
decoder.sv
else if (is_alu_r) begin
if (funct7 == 7'b0000001) begin
case(funct3)
3'b100: div_op = DIV_DIV;
3'b101: div_op = DIV_DIVU;
3'b110: div_op = DIV_REM;
3'b111: div_op = DIV_REMU;
endcase
end
end

else if (is_alu_rw) begin
if (funct7 == 7'b0000001) begin
case(funct3)
3'b100: div_op = DIV_DIVW;
3'b101: div_op = DIV_DIVUW;
3'b110: div_op = DIV_REMW;
3'b111: div_op = DIV_REMUW;
endcase
end
end

id_ex中加入is_div/div_op输入输出。和is_mul一样,is_div属于控制信号,只有valid_in有效时才允许传递;bubble时必须清零,防止无效指令误启动除法器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
id_ex.sv
input u1 is_mul_in, is_div_in,
input mul_t mul_op_in,
input div_t div_op_in,

output u1 is_mul_out, is_div_out,
output mul_t mul_op_out,
output div_t div_op_out

always_ff @(posedge clk) begin
if (reset) begin
is_div_out <= 1'b0;
div_op_out <= DIV_NONE;
end else if (enable) begin
div_op_out <= div_op_in;

if (valid_in) begin
is_div_out <= is_div_in;
end else begin
is_div_out <= 1'b0;
end
end
end

core中先声明decoder侧和ID/EX侧的除法信号,再把decoder实例、ID/EX实例、EX实例和ctrl实例串起来。

1
2
3
4
5
6
7
8
core.sv
u1 decoder_is_mul, decoder_is_div;
mul_t decoder_mul_op;
div_t decoder_div_op;

u1 id_ex_is_mul, id_ex_is_div;
mul_t id_ex_mul_op;
div_t id_ex_div_op;

decoder实例输出decoder_is_div/decoder_div_op

1
2
3
4
5
6
7
core.sv
decoder u_decoder(
.is_mul (decoder_is_mul),
.is_div (decoder_is_div),
.mul_op (decoder_mul_op),
.div_op (decoder_div_op)
);

ID/EX实例负责把decoder阶段的除法控制信号送到EX阶段。异常指令不应该启动除法器,所以is_div_inis_mul_in一样要与~id_exc_valid相与。

1
2
3
4
5
6
7
8
9
10
11
12
core.sv
id_ex u_id_ex(
.is_mul_in (decoder_is_mul && ~id_exc_valid),
.is_div_in (decoder_is_div && ~id_exc_valid),
.mul_op_in (decoder_mul_op),
.div_op_in (decoder_div_op),

.is_mul_out (id_ex_is_mul),
.is_div_out (id_ex_is_div),
.mul_op_out (id_ex_mul_op),
.div_op_out (id_ex_div_op)
);

最后,id_ex_is_div/id_ex_div_op送入ex真正启动除法器,ex_div_done再回到ctrl参与多周期stall判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
core.sv
ctrl u_ctrl(
.ex_is_mul (id_ex_is_mul),
.ex_mul_done (ex_mul_done),
.ex_is_div (id_ex_is_div),
.ex_div_done (ex_div_done)
);

ex u_ex(
.mul_op (id_ex_mul_op),
.div_op (id_ex_div_op),
.is_mul (id_ex_is_mul),
.is_div (id_ex_is_div),
.mul_done (ex_mul_done),
.div_done (ex_div_done)
);

五、通过截图

alt text

构建五级流水线——lab1_bonus
https://travellingsheep.github.io/2026/05/09/riscv/构建五级流水线——lab1-bonus/
作者
trs62
发布于
2026年5月9日
许可协议