构建五级流水线——lab1_bonus
本文最后更新于 2026年5月9日 14:27:36
一、lab1背景介绍
在lab1中,我们完成了基本的加减法运算,在bonus部分,我们将会实现乘除法。为什么乘除法不能像加减法那样在alu中用*和/来简单表示呢?这里我们需要对cpu频率和周期数的计算有更深入的了解:
在cpu中,每当时钟信号来临时,寄存器的值会被输入到各个模块中进行运算,等待其中的组合逻辑运算完成并将数据存入寄存器之后才能进入下一个时钟周期。这意味着,时钟周期受限于关键路径,即时钟周期必须大于等于两个寄存器之间延迟最长的组合逻辑。对于加法和减法,使用超前进位加法器可以在O(lgn)时间内完成计算,但乘除法复杂度高很多,强行放在在一个时钟周期内完成会导致时钟频率大大下降,严重拖慢cpu的运行速率。
为了确保cpu的速度,我们需要将乘除法分散在多个周期内完成,也就是本次lab的主题。
二、lab1目标
在lab1——bonus中,我们需要完成以下操作:
- 基础乘法指令——mul(multiply):
1 | |
这个指令会截断最终结果,只保留后32位。而 $X_{unsigned} = X_{signed} - 2^{32} $ ,截断操作相当于对$2^{32}$进行取模运算,因此mul指令对于有符号数得到的结果完全相同,不需要加以区分
- 基础除法与求余指令——div, divu, rem, remu:
1 | |
- 以上四种指令带w后缀的32位版本
这些指令需要将操作数截断至32位,最后的结果也截断至32位,最后符号扩展至64位。直接针对32位的版本进行扩展即可,这与真正的程序是一致的。
三、乘法
我首先实现了一版比较简单的版本——移位加法乘法器,思路非常接近快速幂:
对于一个x位乘法A*B,进行x轮操作,每轮操作将A左移一位,再将B右移一位,每次根据B的最低位判断是否要加上A,最终汇报结果
这里的乘法器是一个状态机,需要存储当前的状态,后续状态也与当前状态有关,可以看作是米里模型?
乘法器共有三种状态:IDLE,BUSY,DONE,分别表示空闲,正在进行运算和运算完成。
- IDLE表示乘法器可以接收start信号开始计算,将操作数加载进来
- BUSY是乘法器正在被占用
- DONE用于指示当前拍已经完成计算,可以让下一条指令进来了,下一拍再次变回IDLE完成循环。
具体实现如下:
加入新的指令
首先,我们需要先为两条乘法指令在mypkg中定义枚举类型:
typedef enum logic [1:0] {
MUL_NONE = 2’d0,
MUL_MUL = 2’d1,
MUL_MULW = 2’d2
} mul_t;接着完成乘法器主题部分
在utils中新建multiplier。作为一个时序逻辑,它需要接受clk信号和reset信号。同时,decoder将会提供各类操作信号(start,use_w,rs1_num,rs2_num),其中start是新出现的符号,表示乘法器需要开始运算了。最终,乘法器要将内部状态暴露出去,输出busy和done,以及计算结果result。(TOREVIEW这里只用DONE是不是就可以知道什么时候让指令进来了?
1 | |
接着定义内部状态state,acc表示accumulator,用于存放中间运算结果,multiplicand存放被乘数rs1,multiplier_num存放乘数rs2。count用于计数,round则是循环上限。busy和done信号通过内部状态导出。
1 | |
然后是主体循环部分,假设每个时钟上升沿到来进行一轮运算,利用count进行计数。addend是每轮乘法的加数,根据当前乘数尾数决定是否增加被乘数,acc_next是下一步计算结果,利用历史结果acc加上addend得到。
之后根据内部状态决定如何运行:start会让IDLE转为BUSY,并加载数据;BUSY一共round-1轮,每次将acc更新为acc_next,改变乘数、被乘数;当进行round-1轮之后,更新状态为DONE,同时直接将acc_next的数据拿出来交给结果;一轮之后将DONE还原为IDLE。
1 | |