P7-CPU设计文档

本文最后更新于 2024年12月3日 中午

P7 debug总结

阻塞转发错误

上机的过程中遇到了转发错误,再三排查后,发现是mtc0产生了不应该有的转发.

在ID的控制模块CTRL中,我采用了一种过于"取巧"的方法,将rd写入了ID_A3(解码阶段输出的写入寄存器地址(向后传递直至WB阶段))

1
2
3
assign ID_A3 = ... :
(add || sub || and_ || or_ || slt || sltu || mflo || mfhi || mtc0) ? rd :
0;

这个ID_A3会随着流水线向后传递,并传递给HAZARD_CTRL(冒险控制模块)中用于处理阻塞和转发的问题.例如:

1
2
3
4
5
// 转发逻辑
assign ID_RD1_forward = (ID_A1 == 5'b0) ? 0 :
(ID_A1 == MEM_A3) ? MEM_WD : // 这里的MEM_A3是上一周期的EX_A3
(ID_A1 == WB_A3) ? WB_WD : // 这里的WB_A3是上一周期的WB_A3
ID_RD1;

那么问题来了,我的阻塞逻辑代码中,

1
assign STALL = ... || (ID_Eret && ((EX_MTC0 && EX_A3 == 5'D14) || (MEM_MTC0 && MEM_A3 == 5'd14))); 

这样看似没有问题.但其实这里mtc0指令导入的A3并不是真正用于写入寄存器堆的地址,而是CP0的对应地址.因此有可能产生本不应该有的转发.例如以下代码

1
2
3
ori $1, $0, 0x1234
mtc0 $1, $12
add $13, $0, $12

该代码中,第二行指令mtc0错误地向add指令转发了$12的值,但它们指向的$12根本不是同一个寄存器!

我的对应措施是取消执行mtc0指令时rd写入ID_A3的做法,在HARZARD_CTRL中,传入MEM_instrEX_instr(MEM,EX区当前各自传递的指令)

1
assign STALL = ... || (ID_Eret && ((EX_MTC0 && EX_instr[15:11] == 5'D14) || (MEM_MTC0 && MEM_instr[15:11] == 5'd14))); 

中断错误

错误情况:

1
2
3
4
7163: we got @3556:$31 <= 00005ec5 when we expected @356c:$18 <= 00000000
7164: we got @3548:$ 4 <= 0000355c when we expected @3510:$ 5 <= 00003544
7165: we got too much
7170: we got @3078:$12 <= 0000240c when we expected @3080:$30 <= 0000216f

这个bug在课上并没有发现,喜提计组"再来一次"大奖

首先说错误点:

**在发生阻塞时,IDEX间的流水寄存器接收到冲洗信号,但应当保留PCBD(branch delay)*两种信息的传递,也就是按正常传递的逻辑传递PCBD**

前者PC比较好理解,课下思考题也有问到这个问题.为了保证macroscopic_pc不出现诸如0x0000_0000这类的值.

但是BD稍微有些理解的困难度.当外部中断信号产生时,CP0会根据接受到BD来决定EPC存储的值,如果BD有误,那么PC跳转错误就很有可能发生,

1
2
3
4
5
6
7
else if (enable) begin
if (flush)begin
EX_PC <= ID_PC;
EX_BD <= ID_BD;
...
// 其余信号全部传递0
end

我验证了多种ID,EX间流水寄存器处理阻塞的错误方法,例如

1
2
EX_PC <= EX_PC; // PC不变,传递上一个PC
EX_BD <= 1'b0;
1
2
EX_PC <= EX_PC;
EX_BD <= EX_BD;
1
2
EX_PC <= ID_PC;
EX_BD <= 1'b0;

我们来看官方tb中的中断逻辑处理代码:

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
parameter target_pc = 32'h00003010;

integer count;

initial begin
count = 0;
end

always @(negedge clk) begin
if (reset) begin
interrupt = 0;
end
else begin
if (interrupt) begin
if (|m_int_byteen && (m_int_addr & 32'hfffffffc) == 32'h7f20) begin
interrupt = 0;
end
end
else if (fixed_macroscopic_pc == target_pc) begin
if (count == 0) begin
count = 1;
interrupt = 1;
end
end
end
end

用文字简述该逻辑:当macroscopic_pc到达tb指定的target_pc时,interupt置为1,即外部产生中断信号.

所以我们可以很容易的排除C:

当延迟槽指令位于ID区时,与流水线后面的指令发生阻塞冲突时(这种例子确实不太好列举):

1
2
3
4
5
6
7
8
9
ori $20, 0x1001          # 3000
mtc0 $20, $12 // 允许中断 # 3004
ori $1, $0, 0x1234 # 3008
ori $2, $0, 0x2345 # 300c
mult $1, $2 # 3010
jal 0x3020 # 3014
mflo $3 # 3018(发生中断)
ori $5, $0, 0x5555 # 301c
ori $6, $0, 0x6666 # 3020

我们指定target_pc0x3018,mflo会与EX区的MDU模块产生的busy信号发生冲突,因此在C情况下,IDEX间的流水寄存器产生了nop指令,该指令携带着0x3018PC0BD,传递到MEM区时,PC传递给macroscopic_pc输出到外界,触发了外界中断信号的产生,CP0接受到该中断信号进入中断处理逻辑,这时该模块依据接受到的BD处理EPC,但是BD是错误的,也就最终导致了跳转错误.

至于A和B,上述代码确实无法证明它的错误,我猜测是课上测试tb会有更复杂的中断信号产生的逻辑,总之,EX_PC <= ID_PC;EX_BD <= ID_BD;确实是最好的,因为它保证了两点:

  1. 发生阻塞的指令对应PC会多次出现在macroscopic_pc中(A,B无法做到这一点)
  2. 指令传递中,PC一定与正确的BD对应

P7-CPU设计文档
https://meteor041.git.io/2024/12/02/P7 debug总结/
作者
meteor041
发布于
2024年12月2日
许可协议