CO | P5_流水线CPU设计
推荐一些学长的博客
P5 课下学习 — 流水线 CPU 设计 (1) - 北航计算机组成原理 | Test Blog = FlyingLandlord’s Blog
P5 课上测试 1&2 - 北航计算机组成原理 | Test Blog = FlyingLandlord’s Blog
BUAA-CO-Lab| P5 流水线 CPU-lite | ROIFE BLOG
【BUAA_CO_LAB】p5&p6碎碎念_buaa p5-CSDN博客
P5 用verilog描述流水线CPU的学习笔记和总结_设计与测试说明 处理器为32位处理器。 处理器应支持的指令集为:{addu, subu, ori,_佛系甜胖妮的博客-CSDN博客
五段式指令流水线
IF:取指令
IFU
ID:译码
GRF
Controller(将Controller和Splitter合并)
CMP
NPC
EX:
ALU
lw/sw 指令: 计算内存地址
其他指令: 执行其它算术运算或逻辑运算
MEM:
DM存取
lw: 从内存中的数据 读到 CPU 寄存器
sw: 把寄存器的值 写到内存中
WB:写回数据
GRF(在D级写过了,不用写了)
add 、sub 等指令,需要经过所有的五个阶段才能完成整个指令的工作
sw 等指令,仅仅到达访存 M 阶段就已经结束
beq 等指令,在执行 D 阶段完成 PC 更新后,就不再有具体的工作了。
模块设计
命名格式:
元件名称:
级别_元件名,E_ALU流水线寄存器:
前级_后级,F_D接线:
级别_线名称
因此我们首先要搞清楚的是每一级都需要什么数据,每一级又向后输出什么数据,然后只需要考虑每个层级里面的逻辑就行了
结构设计
我的流水线大致长这个样子
Controller
提一点点关于这个模块j跳转指令实现的建议,方便课上指令添加的操作
直接在Controller模块里进行A3地址的选择
assign D_A3 = (jal) ? 5'd31 : //chooose 31 as A3 |
跳转控制信号
assign D_Jump_addr = jal | 1'b0;//jump to instr_index |
Forwarding(转发模块)
转发模块要进行的转发是:M 级向 E 级转发,M 级向 D 级转发、W 级向 E 级转发、W 级向 M 级转发、W 级向 D 级转发、以及寄存器堆的内部转发。
延迟槽保证没有跳转指令,但可能用到上面的寄存器,也就是说jal后面一句不是b或者j指令,不需要E级往D级转发
寄存器堆的内部转发:在同一时刻,我们可能对同一个寄存器既读又写。在这种情况,我们读出的数据应当是要写入的数据(写入的数据是更新的数据)
寄存器堆内部转发实现
assign RD1 = (A1 == 5'd0) ? 32'h0000_0000: |
其他的转发模块
对于这部分的转发,我们可以列举每种转发对于的情况,来理解。
lw $s0,4($0) //阻塞+W给E转发 |
转发是从每级的流水线寄存器后,转发到每个需要使用转发数据的零件前,相当于从W、M级向M、E、D级连线,画图方便理解:
我们用一个模块来做所有的转发操作,相当于接了很多条tunnel到每级使用的数据前后
理解了转发的逻辑和方式之后,注意转发模块书写时的易错点
- 若对
0寄存器操作,则不转发- 当
RegWrite==1且Tnew==1时,若寄存器地址相等,则转发- 转发时M级的优先级在W级前,这样获得的数据是更新的数据
assign D_RD1_FW = (D_A1 == 5'b0) ? 32'h0000_0000 : |
Tuse 和 Tnew 的分析
T~use~ 表示数据到了 D 级之后还需要多少个周期要使用,每个指令的 T~use~ 是固定不变的。
在 D 级使用:T~use~=0
在 E 级使用:T~use~=1
在 M 级使用:T~use~=2
T~new~ 表示数据还有多长时间产生,会随着数据的流水动态的减少。
| 指令 | D_rs_T~use~ | D_rt_T~use~ | D_T~new~ | E_T~new~ | M_T~new~ | W_T~new~ |
|---|---|---|---|---|---|---|
| add | 1 | 1 | 2 | 1 | 0 | 0 |
| sub | 1 | 1 | 2 | 1 | 0 | 0 |
| ori | 1 | x | 2 | 1 | 0 | 0 |
| lw | 1 | x | 3 | 2 | 1 | 0 |
| sw | 1 | 2 | x | x | 0 | 0 |
| beq | 0 | 0 | x | x | 0 | 0 |
| lui | x | x | 2 | 1 | 0 | 0 |
| jal | x | x | 1 | 0(在D级存入) | 0 | 0 |
| jr | 0 | x | x | x(在D级取出) | 0 | 0 |

不管需不需要做阻塞操作,都可以转发
T_use为:这条指令位于 D 级的时候,再经过多少个时钟周期就必须要使用相应的数据。
T_new为:位于某个流水级的某个指令,它经过多少个时钟周期可以算出结果并且存储到流水级寄存器里。
当一个指令到达 D 级后,我们需要将它的 T_use 值与后面每一级的 T_new 进行比较(当然还有 A 值的校验)
T_use≥T_new 时,我们需要进行转发。
T_use<T_new 时,需要阻塞。我们的控制器需要产生的信号包括但不限于冻结信号,刷新信号,供给者选择器信号,需求者选择器信号等。
阻塞模块
阻塞时,只可能在 D 级进行阻塞,阻塞控制器接受 D,E,M 级的指令输入,处理分析指令类别,并给他们赋上不同的Tuse和Tnew 的值,然后用组合逻辑判断,如果Tuse<Tnew 就直接阻塞 D 级,直到Tuse=Tnew时再继续执行
在D级阻塞时,将PC、F_D暂停(RegWE置0),将D_E清空。
不用F级的指令,就把F_D_clear置1,同理
此模块在D级,要和E、M级的T_new比较,(W级都==0,不需要比较)
wire Stall_E_A1 = (D_A1 == 0) ? 1'b0 : |
在D级进行阻塞的操作如下
assign PC_RegWE = !Stall;//PC寄存器的使能信号 |
测试指令
可能可以帮助你de出来一些很笨蛋的bug,覆盖率并不高
ori $a1, $0, 4 |
(检查alu和jr冲突)
lui $a1 0xffff |
(检查jr地址测试阻塞和转发是否正确)
ori $a1, $0, 4 |
(检查jal的tnew是否与转发阻塞设计相符)
ori $a1, $0, 4 |
课上
添加指令
计算指令
- 改
D_Controller,加入新指令的判断wire new=...- 画
control表格和AT表格,改D_Controller的controller如regwrite等等等等,forward_control,加入新指令的AT判断- 改
E_ALUmo模块的输出
integer i; |
跳转指令
这部分指令通常会以I和I+1的形式出现,建议去学习一下教程中机器码指令解释的内容
跳转指令无非就是几种情况的组合,跳转地址b地址/jr地址,条件跳转/无条件跳转,条件link/无条件link(link地址选择),清空延迟槽。自己课下扩展搭出Condition信号,把这个信号传到需要的模块中,就可以实现条件跳转/link。
改
D_Controller,加入新指令的判断D_Is_New和regwrite等等等等画
AT表格,改D_Controller的forward_control,加入新指令的AT判断对于 $GPR[rd]←PC+8$ 这一指令,将
D_Jump_link置1,同时直接在Controller里选择A3地址也可能是
D_Condition==1,时将 $GPR[rd]←PC+8$。对于这样的情况,在
Contoller里的操作有:选择A3地址rd、D_Jump_link=1、D_Is_New=1、D_Reg_Write=1。在到D_E流水线寄存器前,如果D_Is_New==1&&D_Condition == 0,就让A3=5'b0,等价于D_Reg_Write置0。
wire [4:0] D_A3_ch = (D_Is_New==1&&D_Condition == 0)?5'd0:D_A3;Tips:下面这种写法是有逻辑问题的,注意辨析
wire [4:0] D_A3_ch = (D_Is_New==1&&D_Condition == 1)?D_A3:5'd0;改
D_CMP模块的输出,当D_Is_New且满足条件时D_Condition=1也就是说
D_Condition==1中蕴含了D_Is_New==1的条件。但是要注意,D_Condition==0时,不包含D_Is_New==1的条件改
D_NPC模块,当D_Condition == 1时跳转到sign_out或RD1等条件跳转,不需要将
D_Jump_reg=1,在D_NPC里先特判这个条件D_Is_New==1&&D_Condition == 1就行。(无条件跳转其实也不需要将
D_Jump_reg=1,在D_NPC里先特判D_Is_New==1就行。)当
D_Is_New==1&&D_Condition == 0,清空延迟槽(当不是阻塞时(Stall==0)将F_D_clear=1)
assign F_D_clear = (D_Is_New == 1&&D_Condition == 0&&Stall==0) ? 1'b1 : 1'b0;
清空延迟槽:F_D_clear=1
在D级阻塞时,将PC、F_D暂停(RegWE置0),将D_E清空。
存储指令
改
D_Controller,加入新指令的判断D_Is_New和regwrite等等等等画
AT表格,改D_Controller的forward_control,加入新指令的AT判断改阻塞逻辑,这里的指令大概率是在M级才会计算出要存到哪个计算器中。
如果是在M级时,从
rd和31两个地址中选一个存储(即地址时确定的),就使用下面代码中注释的部分来进行阻塞,即遇到31或rd的地址就阻塞。如果在M级得到的存储地址不确定(由DM中取出来的值决定),
(E_Is_New==1)&&(D_rs_Tuse < E_Tnew)&&(E_RegWrite == 1) ? 1'b1 :,就这样书写。只要读到的是这条新指令,就进行阻塞。有的题可能M级得到的存储地址有范围,比如下面这个例子,地址的范围是
0~16,判断条件改成(E_Is_New==1)&&((D_A1<=5'b10000)?1'b1:1'b0)&&(D_rs_Tuse < E_Tnew)&&(E_RegWrite == 1) ? 1'b1 :,全都阻塞会TLE
$$
\begin{flalign}
&vaddr ← GPR[base] + sign_extend(offset) \
&paddr ← vaddr&0xfffffffc \
&Word ← memory[paddr] \
&number ← Word_{31}-Word_{30}+Word_{29}-Word_{28}+…+Word_{1}-Word_{0}+16\
&GPR[number>>1]←Word
\end{flalign}
$$
另一个题:寄存器编号必为偶数
32'h3000是h!改地址选择,在模块外改就可以。注意!
M_Is_New不能少!!需要保证M_Is_New==0时,grfindex的值延用之前逻辑的值一定要考虑清楚在
controller里给M_A3赋的值是谁的地址,接着,在M_Is_New==1的大条件下,根据cond的值改变grfindex的值
wire cond =m_data_rdata[0];//m_data_rdata对应memWord
wire [4:0] grfindex = (M_Is_New)?(cond ? M_A3 : M_A1):M_A3;
//或者
wire [4:0] grfindex = (M_Is_New==1 && cond==0) ? M_A1 : M_A3;
wire Stall_E_A1 = (D_A1 == 0) ? 1'b0 : |
第一次课上
T1 gtb
| 31……26 | 25……21 | 20……16 | 15…11 | 10…6 | 5……0 |
|---|---|---|---|---|---|
| op 000000 |
rs | rt | rd | 0 00000 |
gtb 111010 |
题目描述:
向GPR[rd]中写入GPR[rs]中比GPR[rt]大的位的个数
操作:
$$
\begin{align}
&count ← 0 \
&\qquad if\quad GPR[rs]_i=1\ &\ GPR[rt]_i=0\quad then\
&\qquad \qquad count++\
&GPR[rd]←count
\end{align}
$$
T2 jalrr
| 31……26 | 25……21 | 20……16 | 15…11 | 10…6 | 5……0 |
|---|---|---|---|---|---|
| op 000000 |
rs | 0 00000 |
rd | 0 00000 |
jalrr 111011 |
题目描述:
类似于jal,增加了跳转并连接的条件。当rs不等于rd时,进行与jal指令相同的操作
操作:
$$
\begin{flalign}
&I:\
&\qquad if\quad rs\neq rd \quad then\
&\qquad \qquad GPR[rd] ← PC+8\
&\qquad endif\
&I+1:\
&\qquad if\quad rs\neq rd \quad then\
&\qquad \qquad PC ← GPR[rs]\
&\qquad endif
\end{flalign}
$$
T3 lwtkr
| 31……26 | 25……21 | 20……16 | 15……0 |
|---|---|---|---|
| op 111101 |
base | rt | offset |
题目描述:
将访存结果与0x80000000比较,若访存结果大于0x80000000,则存入k0,否则,存入k1
操作:
$$
\begin{flalign}
&paddr ← GPR[base] + sign_extend(offset) \
&text ← memory[paddr] \
&if\quad text<0x80000000\quad then\
&\qquad GPR[k0]←text\
&else\
&\qquad GPR[k1]←text\
&endif
\end{flalign}
$$
第二次课上
T1 swc
Shift Word Sircular
| 31……26 | 25……21 | 20……16 | 15…11 | 10…6 | 5……0 |
|---|---|---|---|---|---|
| op 101010 |
rs | rt | rd | 0 00000 |
swc 101110 |
题目描述:
当GPR[rt]中为偶数时,向GPR[rd]中写入GPR[rs]向右循环移位GPR[rt]位后的数,
当GPR[rt]中为奇数时,向GPR[rd]中写入GPR[rs]向左循环移位GPR[rt]位后的数。
操作:
$$
\begin{align}
&s ← GPR[rt]{4…0} \
&if\ s=0^5\ then\
&\qquad GPR[rd]←GPR[rs]\
&else \ if\ \ GPR[rt]0=1\
&\qquad GPR[rd]←GPR[rs]{(31-s)…0}||GPR[rs]{31…(32-s)} \
&else \ if\ \ GPR[rt]0=0\
&\qquad GPR[rd]←GPR[rs]{(31-s)…0}||GPR[rs]_{31…(32-s)} \
&endif
\end{align}
$$
T2 bpnal
Branch if Palindrome Number And Link
| 31……26 | 25……21 | 20……16 | 15……0 |
|---|---|---|---|
| op 101100 |
rs | rt | offset |
题目描述:
若GPR[rs]在二进制下是回文数,跳转到$PC+4+sign_extend(offset||0^2)$并link
操作:
$$
\begin{flalign}
&I:\
&\qquad Condition ← (GPR[rs]{31}=GPR[rs]{0})and…and(GPR[rs]{16}=GPR[rs]{15})\
&\qquad temp← PC+4 + sign_extend(offset||0^2) \
&\qquad if\quad Condition :\
&\qquad \qquad GPR[31] ← PC+8\
&\qquad endif\
&I+1:\
&\qquad if\quad Condition:\
&\qquad \qquad PC ← temp\
&\qquad endif
\end{flalign}
$$
T3 lwtbi
Load Word To Bigger Tndex
| 31……26 | 25……21 | 20……16 | 15……0 |
|---|---|---|---|
| op 111000 |
base | rt | offset |
题目描述:
在lw指令的基础上,将要写入的寄存器编号改为rt和访存结果前五位这两者无符号比较结果的最大值
操作:
$$
\begin{flalign}
&vaddr ← GPR[base] + sign_extend(offset) \
&paddr ← vaddr_{31…2}||0^2\
&memWord ← memory[paddr] \
&index ← max(memWord_{31…27},\ rt)\
&GPR[index] ←memWord
\end{flalign}
$$
Tips:每个人的CPU实现方式不同,扩展指令实现方式也不同,我的错误只是给大家提供一些参考
这个题第一遍交的时候wa了两个点,是转发模块出了问题,我的转发模块写的转发条件没有
M_Tnew和W_Tnew这两个条件。对于课下的测试点,整个流水过程中对于同一条指令
A3的地址不会改变,所以后面的转发会覆盖前面错误的值,这样写没有问题。但是课上这个指令在中间改变了
A3的地址,可能在M转发到rt的地址里去,在W级又存入了memWord_31...27的地址,导致rt获得了错误的值,却没有被覆盖。我在课上采取的操作是,给转发模块M_A3的信号接上M_A3_ch(改变后的地址),看起来不是特别规矩的方法,但可以成功解决这个问题
//我的转发模块
assign D_RD1_FW = (D_A1use == 1'b0) ? D_RD1 :
(D_A1 == 5'b0) ? 32'h0000_0000 :
(D_A1 == M_A3)&&(M_RegWrite) ? M_ALU_Result :
(D_A1 == W_A3)&&(W_RegWrite)? W_RegData : D_RD1 ;



