第一个bug
debug_wb_pc = 0xxxxxxxxx
可看到信号为X,X是不定值,而这种错误往往是下面两种原因之一导致的:
-
RTL里声明为reg型的变量从未被赋值
-
RTL里存在多驱动的代码
首先,从第一种情况开始排查,由变量名debug_wb_pc可知该变量应该位于写回阶段里,打开WB_stage.v,查找变量debug_wb_pc,
发现:
assign debug_wb_pc = ws_pc;
可知debug_wb_pc的值由ws_pc得到,所以下一步就是查找wc_pc的值是从哪里得到的,发现:
assign {ws_gr_we , //69:69
ws_dest , //68:64
ws_final_result, //63:32
ws_pc //31:0
} = ms_to_ws_bus_r;
ws_pc的值与ms_to_ws_bus_r有关,继续查找ms_to_ws_bus_r的值是从哪里得到的,发现:
always @(posedge clk) begin
if (reset) begin
ws_valid <= 1'b0;
end
else if (ws_allowin) begin
ws_valid <= ms_to_ws_valid;
end
if (ms_to_ws_valid && ws_allowin) begin
ms_to_ws_bus_r = ms_to_ws_bus;
end
end
发现ms_to_ws_bus_r的值与ms_to_ws_bus有关,实际上这里有一个小bug,在always语句里应该使用非阻塞赋值,而这里使用的是阻塞赋值,将ms_to_ws_bus_r = ms_to_ws_bus;修改为ms_to_ws_bus_r <= ms_to_ws_bus;我原本以为这就是第一个bug,结果我运行仿真的时候发现还是报debug_wb_pc = 0xxxxxxxxx这个错,那说明真的的bug我还没找到,所以就继续以ms_to_ws_bus往下找,由名字ms_to_ws_bus可看出它与访存阶段和写回阶段有关,写回阶段没发现什么问题,所以去访存阶段里找,打开MEM_stage.v,发现:
assign ms_to_ws_bus = {ms_gr_we , //69:69
ms_dest , //68:64
ms_final_result, //63:32
ms_pc //31:0
};
ms_to_ws_bus与ms_gr_we,ms_dest,ms_final_result,ms_pc 有关,接着继续找ms_gr_we,ms_dest,ms_final_result,ms_pc这些变量和谁有关,发现:
assign {ms_res_from_mem, //70:70
ms_gr_we , //69:69
ms_dest , //68:64
ms_alu_result , //63:32
ms_pc //31:0
} = es_to_ms_bus_r;
与es_to_ms_bus_r有关,而:
es_to_ms_bus_r <= es_to_ms_bus;
所以接下来要排查的变量便是es_to_ms_bus;到这里就可以发现接下来的步骤就和上面排查ms_to_ws_bus的步骤一样了。打开EXE.stage.v,可发现es_to_ms_bus与ds_to_es_bus_r有关,而ds_to_es_bus_r的值又与ds_to_es_bus相关,所以下一步就是打开ID_stage.v继续排查,步骤还是和上面一样,首先发现ds_to_es_bus与fs_to_ds_bus_r有关,到这里终于发现了问题:
always @(posedge clk) begin
if (fs_to_ds_valid && ds_allowin) begin
fs_to_ds_bus_r <= fs_to_ds_bus;
end
end
发现在这个always中没有对ds_valid进行赋值,ds_valid是译码级的有效位,当他的值为1时表示该流水线上当前时钟周期存在有效的数据,值为0时表示该流水线上当前时钟周期为空。具体可看CPU设计实战P55页。
将代码修改为:
always @(posedge clk) begin
//第一个bug
if(reset) begin
ds_valid = 1'b0;
end
if(ds_allowin) begin
ds_valid <= fs_to_ds_valid;
end
//
if (fs_to_ds_valid && ds_allowin) begin
fs_to_ds_bus_r <= fs_to_ds_bus;
end
end
解决第一个bug。
第二个bug
[ 2067 ns] Error!!! reference: PC = 0xbfc00000, wb_rf_wnum = 0x08, wb_rf_wdata = 0xffffffff mycpu : PC = 0xbfc00000, wb_rf_wnum = 0x08, wb_rf_wdata = 0xxxxxxxxX
由报错信息可知wb_rf_wdata的值应该为0xffffffff,而现在却是0xxxxxxxxX,而出现X的常见原因在第一个bug里已说明,下面要做的就是找到bug位于哪里,由变量名wb_rf_wdata可知,该变量应该在访存阶段,打开WB.stage.v,找到wb_rf_wdata变量发现:
assign debug_wb_rf_wdata = ws_final_result;
继续查找wc_final_result与什么有关:
assign {ws_gr_we , //69:69
ws_dest , //68:64
ws_final_result, //63:32
ws_pc //31:0
} = ms_to_ws_bus_r;
发现wc_final_result是由ms_to_ws_bus_r赋值的,由ms_to_ws_bus_r继续找下去,发现:
ms_to_ws_bus_r <= ms_to_ws_bus;
所以接下来找ms_to_ws_bus和什么相关,打开MEM_stage.v查找ms_to_ws_bus发现:
assign ms_to_ws_bus = {ms_gr_we , //69:69
ms_dest , //68:64
ms_final_result, //63:32
ms_pc //31:0
};
由此可知,ws_final_result的值是由ms_final_result传递过去的,接下来查找ms_final_result发现:
assign ms_final_result = ms_res_from_mem ? mem_result
: ms_alu_result;
这时候就找到问题所在了,查看该波形发现ms_res_from_mem的值为Z,这便是导致出错的其中一个原因,但是这并不是最根本的原因,根本原因是找到ms_res_from_mem的值为什么是Z。而信号为Z这种错误往往是由下面两个原因导致的:
-
RTL里声明为wire型的变量从未被赋值。
-
模块调用的信号未连接导致信号悬空。
接下来就是排查ms_res_from_mem出现Z的原因,发现:
assign {ms_res_from_mem, //70:70
ms_gr_we , //69:69
ms_dest , //68:64
ms_alu_result , //63:32
ms_pc //31:0
} = es_to_ms_bus_r;
所以接下来查找es_to_ms_bus_r和什么有关,发现es_to_ms_bus_r的值是由es_to_ms_bus赋值得到的,所以接下来排查es_to_ms_bus,打开MEM_stage.v,查找es_to_ms_bus发现:
assign es_to_ms_bus = {es_res_from_mem, //70:70
es_gr_we , //69:69
es_dest , //68:64
es_alu_result , //63:32
es_pc //31:0
};
ms_res_from_mem的值由es_res_from_mem得到,紧接着发现:
assign es_res_from_mem = es_load_op;
查找es_load_op发现:
assign {es_alu_op , //135:124
es_load_op , //123:123
es_src1_is_sa , //122:122
es_src1_is_pc , //121:121
es_src2_is_imm , //120:120
es_src2_is_8 , //119:119
es_gr_we , //118:118
es_mem_we , //117:117
es_dest , //116:112
es_imm , //111:96
es_rs_value , //95 :64
es_rt_value , //63 :32
es_pc //31 :0
} = ds_to_es_bus_r;
发现es_load_op与ds_to_es_bus_r有关,而ds_to_es_bus_r的值又与ds_to_es_bus有关,接下来排查ds_to_es_bus,打开ID_stage.v,查找ds_to_es_bus,发现:
assign ds_to_es_bus = {alu_op , //135:124
load_op , //123:123
src1_is_sa , //122:122
src1_is_pc , //121:121
src2_is_imm , //120:120
src2_is_8 , //119:119
gr_we , //118:118
mem_we , //117:117
dest , //116:112
imm , //111:96
rs_value , //95 :64
rt_value , //63 :32
ds_pc //31 :0
};
与上面的ds_to_es_bus_r对照发现es_load_op的值由load_op得到,接下来查找load_op,发现load_op只进行了定义:
wire load_op;
却未对load_op赋值,这就是导致前面信号出现Z的原因,接下来我们要给load_op赋值,而给load_op赋什么值呢?我们需要看load_op用于什么指令,因为load_op用在ds_to_es_bus,所以我们向执行阶段找,回到MEM_stage.v,查找ds_to_es_bus发现ds_to_es_bus_r用到了ds_to_es_bus的值,继续查看ds_to_es_bus_r发现:
assign {es_alu_op , //135:124
es_load_op , //123:123
es_src1_is_sa , //122:122
es_src1_is_pc , //121:121
es_src2_is_imm , //120:120
es_src2_is_8 , //119:119
es_gr_we , //118:118
es_mem_we , //117:117
es_dest , //116:112
es_imm , //111:96
es_rs_value , //95 :64
es_rt_value , //63 :32
es_pc //31 :0
} = ds_to_es_bus_r;
其中包括es_load_op,在前面的分析中我们知道es_load_op和load_op的值一样,所以接下来我们排查es_load_op发现:
assign es_res_from_mem = es_load_op;
发现与es_res_from_mem有关,从这可以看出load_op指令是一条与访存有关的指令。(实际上该结论在我们前面排查的时候应该就已经得到了)而lab3一共实现了19条指令,这19条指令里只有两条和访存有关,①LW(取字),②SW(存字)。这里很明显访存是取字,所以我们知道了load_op在这里应该执行的是LW指令,所以回到ID_stage.v添加代码给load_op赋值:
assign load_op = inst_lw;
至此第二个bug解决。
第三个bug
[ 2067 ns] Error!!! reference: PC = 0xbfc00000, wb_rf_wnum = 0x08, wb_rf_wdata = 0xffffffff mycpu : PC = 0xbfc00000, wb_rf_wnum = 0x08, wb_rf_wdata = 0xfffffffe
从报错可以看出是wb_rf_wdata的值应该是0xffffffff而此时是0xfffffffe,下面要做的就是找到bug位于哪里,由变量名wb_rf_wdata可知,该变量应该在访存阶段,打开WB.stage.v,找到wb_rf_wdata变量发现:
assign debug_wb_rf_wdata = ws_final_result;
继续查找wc_final_result与什么有关:
assign {ws_gr_we , //69:69
ws_dest , //68:64
ws_final_result, //63:32
ws_pc //31:0
} = ms_to_ws_bus_r;
发现wc_final_result是由ms_to_ws_bus_r赋值的,由ms_to_ws_bus_r继续找下去,发现:
ms_to_ws_bus_r <= ms_to_ws_bus;
所以接下来找ms_to_ws_bus和什么相关,打开MEM_stage.v查找ms_to_ws_bus发现:
assign ms_to_ws_bus = {ms_gr_we , //69:69
ms_dest , //68:64
ms_final_result, //63:32
ms_pc //31:0
};
由此可知,ws_final_result的值是由ms_final_result传递过去的,接下来查找ms_final_result发现:
assign ms_final_result = ms_res_from_mem ? mem_result
: ms_alu_result;
ms_final_result的值由该问号表达式决定,通过查看波形发现ms_res_from_mem的值为0,所以ms_final_result=ms_alu_result,ms_alu_result的值出错就是导致错误的原因,下面继续追ms_alu_result发现:
assign {ms_res_from_mem, //70:70
ms_gr_we , //69:69
ms_dest , //68:64
ms_alu_result , //63:32
ms_pc //31:0
} = es_to_ms_bus_r;
ms_alu_result与es_to_ms_bus_r有关,而es_to_ms_bus_r又与es_to_ms_bus有关,接下来打开EXE_stage.v,查找es_to_ms_bus发现:
assign es_to_ms_bus = {es_res_from_mem, //70:70
es_gr_we , //69:69
es_dest , //68:64
es_alu_result , //63:32
es_pc //31:0
};
所以 ms_alu_result的值由es_alu_result得到,接下来排查es_alu_result发现:
alu u_alu(
.alu_op (es_alu_op ),
.alu_src1 (es_alu_src2 ), //调用错误
.alu_src2 (es_alu_src2 ),
.alu_result (es_alu_result)
);
这这里终于发现了错误:.alu_src1传参错误,应该传入es_alu_src1才对,修改代码如下:
alu u_alu(
.alu_op (es_alu_op ),
.alu_src1 (es_alu_src1 ),
.alu_src2 (es_alu_src2 ),
.alu_result (es_alu_result)
);
至此第三个bug解决。
第四个bug
[ 2107 ns] Error!!! reference: PC = 0xbfc0038c, wb_rf_wnum = 0x04, wb_rf_wdata = 0xbfb00000 mycpu : PC = 0xbfc00010, wb_rf_wnum = 0x08, wb_rf_wdata = 0x80000000
看到这个报错后,发现是PC的值出错,PC此时的值应该是0xbfc0038c,但是实际PC的值确是0xbfc00010,运行仿真后,查看各阶段的PC:
在PC=0xbfc00010以前,发现均是按顺序执行PC=PC+4,未发现什么异常,而结果却显示此时正确的PC值应该是0xbfc0038c,所以想到可能是PC值为0xbfc00010前面的指令中包含有跳转指令但实际却未执行跳转指令导致的,要想知道这个需要打开CPU_CDE\soft\func_lab3\obj目录下的test.s(该文件是生成的反汇编文件)文件,打开后可看到指令的执行过程,查找0xbfc00010前的指令,结果发现在PC=0xbfc00008时,应该执行跳转指令且可看到跳转的地址刚好是正确的PC地址,现在我们找到了错误的原因,接下来就是解决为什么在PC=0xbfc00008时没有发生跳转。
由于取指阶段使用来取出指令的,所以我们先看取指阶段,打开IF_stage.v,找和pc有关的变量,首先是fs_pc,发现:
fs_pc <= nextpc;
发现去与nextpc有关,接着找nextpc的和什么相关,发现:
assign nextpc = br_taken ? br_target : seq_pc;
assign seq_pc = fs_pc + 3'h4;
可看到当br_taken为0时nextpc=seq_pc,而seq_pc=fs_pc+3'h4,即当br_taken为0时顺序执行,当br_taken值为1时,nextpc =br_target,此时将执行跳转。通过查看波形可看到,在fs_pc=bfc00008时,br_taken的值为0,这便是错误的原因,此时应该执行跳转指令,br_taken的值应该为1才对。下面就分析为什么br_taken的值出现错误。
首先,发现:
assign {br_taken,br_target} = br_bus;
br_taken的值与br_bus有关,接下来寻找br_bus和什么有关,打开ID_stage.v,查找br_bus,发现在译码阶段:
assign br_bus = {br_taken,br_target};
而br_taken,和br_target的值又和下面的变量有关:
assign br_taken = ( inst_beq && rs_eq_rt
|| inst_bne && !rs_eq_rt
|| inst_jal
|| inst_jr
) && ds_valid;
assign br_target = (inst_beq || inst_bne) ? (fs_pc + {{14{imm[15]}}, imm[15:0], 2'b0}) :
(inst_jr) ? rs_value :
/*inst_jal*/ {fs_pc[31:28], jidx[25:0], 2'b0};
查看波形可知此时br_taken的值为1,在当前阶段时正确的,可传给br_bus后就出错了,这时发现br_bus好像并没有接受到br_taken的值,因为此时assign br_bus = {br_taken,br_target};应该是拼接后的结果,可看到br_bus只与br_target的值相同,细心观察后发现,br_bus的位宽设置错了,应该是33位,而这里却只有32位,这便是出错的原因,br_bus根本没用接收到br_taken的值。
查找定义br_bus的位置,发现:
output [`BR_BUS_WD -1:0] br_bus
打开mycpu.h
`ifndef MYCPU_H
`define MYCPU_H
`define BR_BUS_WD 32
`define FS_TO_DS_BUS_WD 64
`define DS_TO_ES_BUS_WD 136
`define ES_TO_MS_BUS_WD 71
`define MS_TO_WS_BUS_WD 70
`define WS_TO_RF_BUS_WD 38
`endif
果然发现BR_BUS_WD为32,而它应该是33,修改代码如下:
`ifndef MYCPU_H
`define MYCPU_H
`define BR_BUS_WD 33
`define FS_TO_DS_BUS_WD 64
`define DS_TO_ES_BUS_WD 136
`define ES_TO_MS_BUS_WD 71
`define MS_TO_WS_BUS_WD 70
`define WS_TO_RF_BUS_WD 38
`endif
至此第四个bug解决。
第五个bug
----[ 716985 ns] Number 8'd09 Functional Test Point PASS!!!
FATAL_ERROR: Vivado Simulator kernel has discovered an exceptional condition from which it cannot recover. Process will terminate. For technical support on this issue, please open a WebCase with this project attached at Support. Time: 717915 ns Iteration: 1 Process: /tb_top/soc_lite/data_ram/inst/native_mem_module.blk_mem_gen_v8_4_4_inst/NetRegassign3314_721 File: H:/CPU/CPU_CDE/mycpu_verify/run_vivado/mycpu_prj1/mycpu_prj1.ip_user_files/ipstatic/simulation/blk_mem_gen_v8_4.v
这个报错很奇怪,实际上也算不上是报错,只不过是运行着的时候一直卡死在Number 8'd09 Functional Test Point PASS!!!,卡的时间长了就出现了FATAL_ERROR。CPU设计实战P119页介绍了出现cpu卡死的情况该怎么去调,结合课本的内容、查看反汇编代码、以及比对golden_trace.txt。找了大半天都没发现问题出在哪里,最后去求助了学长,发现问题出现在alu.v中,里面出现了环路导致CPU卡死。具体原因如下:
assign or_result = alu_src1 | alu_src2 | alu_result;
assign alu_result = ({32{op_add|op_sub}} & add_sub_result)
| ({32{op_slt }} & slt_result)
| ({32{op_sltu }} & sltu_result)
| ({32{op_and }} & and_result)
| ({32{op_nor }} & nor_result)
| ({32{op_or }} & or_result)
| ({32{op_xor }} & xor_result)
| ({32{op_lui }} & lui_result)
| ({32{op_sll }} & sll_result)
| ({32{op_srl|op_sra}} & sr_result);
alu_result的结果由or_result赋值,而or_result的结果却又由alu_result赋值,就这样产生了环路,导致结果出错,修改代码如下:
assign or_result = alu_src1 | alu_src2;
assign alu_result = ({32{op_add|op_sub}} & add_sub_result)
| ({32{op_slt }} & slt_result)
| ({32{op_sltu }} & sltu_result)
| ({32{op_and }} & and_result)
| ({32{op_nor }} & nor_result)
| ({32{op_or }} & or_result)
| ({32{op_xor }} & xor_result)
| ({32{op_lui }} & lui_result)
| ({32{op_sll }} & sll_result)
| ({32{op_srl|op_sra}} & sr_result);
至此第五个bug解决(还是不太清楚怎么调试出来的)。
第六个bug
[ 888537 ns] Error!!! reference: PC = 0xbfc88c90, wb_rf_wnum = 0x02, wb_rf_wdata = 0xadff20c0 mycpu : PC = 0xbfc88c90, wb_rf_wnum = 0x02, wb_rf_wdata = 0x2dff20c0
从报错信息上来看,PC值是对的,但是wb_rf_wdata的值是错的,从报错可以看出是wb_rf_wdata的值应该是0xadff20c0而此时是0x2dff20c0,下面要做的就是找到bug位于哪里,由变量名wb_rf_wdata可知,该变量应该在访存阶段,打开WB.stage.v,找到wb_rf_wdata变量发现:
assign debug_wb_rf_wdata = ws_final_result;
继续查找wc_final_result与什么有关:
assign {ws_gr_we , //69:69
ws_dest , //68:64
ws_final_result, //63:32
ws_pc //31:0
} = ms_to_ws_bus_r;
发现wc_final_result是由ms_to_ws_bus_r赋值的,由ms_to_ws_bus_r继续找下去,发现:
ms_to_ws_bus_r <= ms_to_ws_bus;
所以接下来找ms_to_ws_bus和什么相关,打开MEM_stage.v查找ms_to_ws_bus发现:
assign ms_to_ws_bus = {ms_gr_we , //69:69
ms_dest , //68:64
ms_final_result, //63:32
ms_pc //31:0
};
由此可知,ws_final_result的值是由ms_final_result传递过去的,接下来查找ms_final_result发现:
assign ms_final_result = ms_res_from_mem ? mem_result
: ms_alu_result;
ms_final_result的值由该问号表达式决定,通过查看波形发现ms_res_from_mem的值为0,所以ms_final_result=ms_alu_result,ms_alu_result的值出错就是导致错误的原因,下面继续追ms_alu_result发现:
assign {ms_res_from_mem, //70:70
ms_gr_we , //69:69
ms_dest , //68:64
ms_alu_result , //63:32
ms_pc //31:0
} = es_to_ms_bus_r;
ms_alu_result与es_to_ms_bus_r有关,而es_to_ms_bus_r又与es_to_ms_bus有关,接下来打开EXE_stage.v,查找es_to_ms_bus发现:
assign es_to_ms_bus = {es_res_from_mem, //70:70
es_gr_we , //69:69
es_dest , //68:64
es_alu_result , //63:32
es_pc //31:0
};
所以 ms_alu_result的值由es_alu_result得到,接下来排查es_alu_result发现:
alu u_alu(
.alu_op (es_alu_op ),
.alu_src1 (es_alu_src1 ),
.alu_src2 (es_alu_src2 ),
.alu_result (es_alu_result)
);
通过波形查看es_alu_result的值发现,此时es_alu_result的值为0x2dff20c0,因为.alu_result是output变量,所以判断是ALU内部出现了错误,我们先查看一下PC=0xbfc88c90执行的是什么操作,打开test.s查看反汇编代码,搜索xbfc88c90,发现:
bfc88c90: 00081002 srl v0,t0,0x0
所以执行的是SRL逻辑右移操作,接下来打开alu.v排查SRL相关代码,首先看到控制信号op_srl,通过波形查看到op_srl此时的值确实是1,接着继续查找发现:
assign alu_result = ({32{op_add|op_sub}} & add_sub_result)
| ({32{op_slt }} & slt_result)
| ({32{op_sltu }} & sltu_result)
| ({32{op_and }} & and_result)
| ({32{op_nor }} & nor_result)
| ({32{op_or }} & or_result)
| ({32{op_xor }} & xor_result)
| ({32{op_lui }} & lui_result)
| ({32{op_sll }} & sll_result)
| ({32{op_srl|op_sra}} & sr_result); // op_srl表示逻辑右移,op_sra表示算术右移
可看到当op_srl的值为1的时候,alu_result=sr_result,接下来查看sr_result的值和什么有关,发现:
// SRL, SRA result
assign sr64_result = {{32{op_sra & alu_src2[31]}}, alu_src2[31:0]} >> alu_src1[4:0];
assign sr_result = sr64_result[30:0];
看到这里,通过波形查看sr64_result的值,发现是0xadff20c0(10101101111111110010000011000000),而sr_result的值是0x2dff20c0(00101101111111110010000011000000)只有一位的差别,这下就明白了,错误的原因就是sr64_result[30:0]这里位宽写错了,应该是32位,而这里只有31位,修改代码如下:
// SRL, SRA result
assign sr64_result = {{32{op_sra & alu_src2[31]}}, alu_src2[31:0]} >> alu_src1[4:0];
assign sr_result = sr64_result[31:0];
值得一提的是这里通过利用op_sra & alu_src2[31]巧妙的将逻辑右移和算术右移的结果都用sr_result来表示,当op_sra为0时表示执行的是SRL(逻辑右移),右移后左边补32个0,sr64_result[31:0]就是逻辑右移的结果。当op_sra为1时,表示执行的是SRA(算术右移),右移后左边补32个符号位(正数补0,负数补1),此时sr64_result[31:0]就是算术右移的结果。
至此第六个bug解决,第四章实践任务一的所有bug全部解决。
此时测试已通过。