推荐一些学长的博客

P5 课下学习 — 流水线 CPU 设计 (1) - 北航计算机组成原理 | Test Blog = FlyingLandlord’s Blog

P5 课上测试 1&2 - 北航计算机组成原理 | Test Blog = FlyingLandlord’s Blog

BUAA-CO-Lab| P5 流水线 CPU-lite | ROIFE BLOG

计算机组成实验 P5 回顾|bluebean

【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级写过了,不用写了)

addsub 等指令,需要经过所有的五个阶段才能完成整个指令的工作

sw 等指令,仅仅到达访存 M 阶段就已经结束

beq 等指令,在执行 D 阶段完成 PC 更新后,就不再有具体的工作了。

模块设计

命名格式:

元件名称:级别_元件名E_ALU

流水线寄存器:前级_后级F_D

接线:级别_线名称

因此我们首先要搞清楚的是每一级都需要什么数据,每一级又向后输出什么数据,然后只需要考虑每个层级里面的逻辑就行了

结构设计

p5_v1

我的流水线大致长这个样子

Controller

提一点点关于这个模块j跳转指令实现的建议,方便课上指令添加的操作

直接在Controller模块里进行A3地址的选择

assign D_A3	= 	(jal) 				? 5'd31 :		//chooose 31 as A3
( ori | lui | lw ) ? Instr[20:16]: //chooose rt as A3
( add | sub ) ? Instr[15:11]: //chooose rd as A3 (jalr)
5'd0;

跳转控制信号

assign D_Jump_addr    = jal | 1'b0;//jump to instr_index
assign D_Jump_reg = jr | 1'b0; //jump to RD1
assign D_Jump_link = jal | 1'b0;//GPR[rd] <- PC+8 (主要是用来选PC+8的)

Forwarding(转发模块)

转发模块要进行的转发是:M 级向 E 级转发,M 级向 D 级转发、W 级向 E 级转发、W 级向 M 级转发、W 级向 D 级转发、以及寄存器堆的内部转发。

延迟槽保证没有跳转指令,但可能用到上面的寄存器,也就是说jal后面一句不是b或者j指令,不需要E级往D级转发

寄存器堆的内部转发:在同一时刻,我们可能对同一个寄存器既读又写。在这种情况,我们读出的数据应当是要写入的数据(写入的数据是更新的数据)

寄存器堆内部转发实现

assign RD1 = (A1 == 5'd0) ? 32'h0000_0000:
(A1 == A3)&&(WE == 1) ? WD3 : RF[A1];
assign RD2 = (A2 == 5'd0) ? 32'h0000_0000:
(A2 == A3)&&(WE == 1) ? WD3 : RF[A2];

其他的转发模块

对于这部分的转发,我们可以列举每种转发对于的情况,来理解。

lw $s0,4($0) //阻塞+W给E转发
lw $s1,4($s0)

jal+下一句jr

//M->E
add $s0,$t0,$t1
add $s1,$s0,$t0
//(or) lw $s1,4($s0)

//M->D
add $s0,$t0,$t1
beq $s0,$t0,...

//W->M
lw $s0,4($0)
sw $s0,8($0)

//W->E
lw $s0,4($0)
add $s1,$s0,$t0
//(or) lw $s1,4($s0)转发+阻塞一周期

//W->D
lw $s0,4($0)
beq $s0,$t0,...

转发是从每级的流水线寄存器后,转发到每个需要使用转发数据的零件前,相当于从W、M级向M、E、D级连线,画图方便理解:image-20231130171844360

我们用一个模块来做所有的转发操作,相当于接了很多条tunnel到每级使用的数据前后

理解了转发的逻辑和方式之后,注意转发模块书写时的易错点

  1. 若对0寄存器操作,则不转发
  2. RegWrite==1Tnew==1时,若寄存器地址相等,则转发
  3. 转发时M级的优先级在W级前,这样获得的数据是更新的数据
assign D_RD1_FW = 	(D_A1 == 5'b0) ? 32'h0000_0000 :
(D_A1 == M_A3)&&(M_RegWrite)&&(M_Tnew == 0) ? M_ALU_Result :
(D_A1 == W_A3)&&(W_RegWrite)&&(W_Tnew == 0) ? W_RegData : D_RD1 ;
//D_RD1_FW:D级的RD1经过转发后得到的值
//M_RegWrite:M级的写使能信号
//M_Tnew:M级Tnew的值
//M_ALU_Result:E级ALU_Result经过流水线寄存器后的结果
//W_RegData:要写入GRF的值

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

image-20231130173833463

不管需不需要做阻塞操作,都可以转发

T_use为:这条指令位于 D 级的时候,再经过多少个时钟周期就必须要使用相应的数据。

T_new为:位于某个流水级的某个指令,它经过多少个时钟周期可以算出结果并且存储到流水级寄存器里。

当一个指令到达 D 级后,我们需要将它的 T_use 值与后面每一级的 T_new 进行比较(当然还有 A 值的校验)
T_use≥T_new 时,我们需要进行转发。
T_use<T_new 时,需要阻塞。

我们的控制器需要产生的信号包括但不限于冻结信号,刷新信号,供给者选择器信号,需求者选择器信号等。

阻塞模块

阻塞时,只可能在 D 级进行阻塞,阻塞控制器接受 D,E,M 级的指令输入,处理分析指令类别,并给他们赋上不同的TuseTnew 的值,然后用组合逻辑判断,如果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_A1 == E_A3)&&(D_rs_Tuse < E_Tnew)&&(E_RegWrite == 1) ? 1'b1: 1'b0;
......
assign Stall = Stall_E_A1 | Stall_E_A2 | Stall_M_A1 | Stall_M_A2 | 1'b0;
//Stall:需要阻塞

在D级进行阻塞的操作如下

assign PC_RegWE  = !Stall;//PC寄存器的使能信号
assign F_D_RegWE = !Stall;//F_D流水线寄存器的使能信号
assign D_E_clear = Stall;//D_E寄存器清零

测试指令

可能可以帮助你de出来一些很笨蛋的bug,覆盖率并不高

ori $a1, $0, 4
ori $a2, $0, 0x3008
jr $a2
add $a2, $a2, $a1

(检查alu和jr冲突)

lui $a1 0xffff
jr $a1
nop

(检查jr地址测试阻塞和转发是否正确)

ori $a1, $0, 4
jal label
add $a2, $31, 4
label:
jr $31
nop

(检查jal的tnew是否与转发阻塞设计相符)

ori $a1, $0, 4
label:
sw $a1, 0($a1)
lw $a2, 0($a1)
beq $a2, $a1, label:
nop

课上

添加指令

计算指令

  1. D_Controller,加入新指令的判断wire new=...
  2. control表格和AT表格,改D_Controllercontrollerregwrite等等等等forward_control ,加入新指令的AT判断
  3. E_ALUmo模块的输出
integer i;
reg [31:0] temp;
always @(*)begin
temp=32'd0;
for(i=0;i<32;i=i+1)begin
if(SrcA[i]==1)begin
temp=temp+32'd1;
end
end
end

跳转指令

这部分指令通常会以II+1的形式出现,建议去学习一下教程中机器码指令解释的内容

跳转指令无非就是几种情况的组合,跳转地址b地址/jr地址条件跳转/无条件跳转条件link/无条件link(link地址选择),清空延迟槽。自己课下扩展搭出Condition信号,把这个信号传到需要的模块中,就可以实现条件跳转/link。

  1. D_Controller,加入新指令的判断D_Is_Newregwrite等等等等

  2. AT表格,改D_Controllerforward_control ,加入新指令的AT判断

  3. 对于 $GPR[rd]←PC+8$ 这一指令,将D_Jump_link置1,同时直接在Controller里选择A3地址

    也可能是D_Condition==1,时将 $GPR[rd]←PC+8$。

    对于这样的情况,在Contoller里的操作有:选择A3地址rdD_Jump_link=1D_Is_New=1D_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;

  4. D_CMP模块的输出,当D_Is_New满足条件D_Condition=1

    也就是说D_Condition==1中蕴含了D_Is_New==1的条件。但是要注意,D_Condition==0时,不包含D_Is_New==1的条件

  5. D_NPC模块,当D_Condition == 1时跳转到sign_outRD1

    条件跳转,不需要将D_Jump_reg=1,在D_NPC里先特判这个条件D_Is_New==1&&D_Condition == 1就行。

    (无条件跳转其实也不需要将D_Jump_reg=1,在D_NPC里先特判D_Is_New==1就行。)

  6. 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清空。

存储指令

  1. D_Controller,加入新指令的判断D_Is_Newregwrite等等等等

  2. AT表格,改D_Controllerforward_control ,加入新指令的AT判断

  3. 改阻塞逻辑,这里的指令大概率是在M级才会计算出要存到哪个计算器中。

    如果是在M级时,从rd31两个地址中选一个存储(即地址时确定的),就使用下面代码中注释的部分来进行阻塞,即遇到31rd的地址就阻塞。

    如果在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'h3000h

  4. 改地址选择,在模块外改就可以。注意!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 :
//(E_Is_New==1)&&((D_A1 == E_A3)||(D_A1 == 5'd31))&&(D_rs_Tuse < E_Tnew)&&(E_RegWrite == 1) ? 1'b1 :
(D_A1 == E_A3)&&(D_rs_Tuse < E_Tnew)&&(E_RegWrite == 1) ? 1'b1: 1'b0;

wire Stall_E_A2 = (D_A2 == 0) ? 1'b0 :
//(E_Is_New==1)&&((D_A2 == E_A3)||(D_A2 == 5'd31))&&(D_rt_Tuse < E_Tnew)&&(E_RegWrite == 1) ? 1'b1 :
(D_A2 == E_A3)&&(D_rt_Tuse < E_Tnew)&&(E_RegWrite == 1) ? 1'b1: 1'b0;

wire Stall_M_A1 = (D_A1 == 0) ? 1'b0 :
//(M_Is_New==1)&&((D_A1 == M_A3)||(D_A1 == 5'd31))&&(D_rs_Tuse < M_Tnew)&&(M_RegWrite == 1) ? 1'b1 :
(D_A1 == M_A3)&&(D_rs_Tuse < M_Tnew)&&(M_RegWrite == 1) ? 1'b1: 1'b0;

wire Stall_M_A2 = (D_A2 == 0) ? 1'b0 :
//(M_Is_New==1)&&((D_A2 == M_A3)||(D_A2 == 5'd31))&&(D_rt_Tuse < M_Tnew)&&(M_RegWrite == 1) ? 1'b1 :
(D_A2 == M_A3)&&(D_rt_Tuse < M_Tnew)&&(M_RegWrite == 1) ? 1'b1: 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_TnewW_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 ;