龙芯比赛vivado调试试错手记
第一个阶段:除掉所有的语法错误
- 刚开始在文件夹里加了我的CPU文件但是在工程里面没添加,然后报错:找不到你的的CPU
- IP核没更新到最新版本,由于我的是2019.2的vivado,但用的是2019.1的工程和IP核,所以打开工程时IP核都上着锁。解决办法是:Reports->Report IP Status;然后点击upgrade->OK
- 报错:
[VRFC 10-3180] cannot find port 'en_exception_pc' on this module ["C:/Users/dell/Desktop/nscscc2019_release_v0.01/func_test_v0.01/soc_sram_func/rtl/myCPU/mycpu_top.v":429]
知道是端口的问题,但是改了,没用,倒腾了好久,后来发现是改了之前写的代码,没有改拷贝到工程文件夹下的代码,冤。
报的那两个端口错误,一个是真的接口不对,上下接口不一致——设计有点不一致;另一个是拼写错误en_exception_pc写成了en_excption_pc
错误弄完了,出现了仿真的图片,根据现在的情况,看来我要进行调出绿线的尝试了
第二个阶段:调出绿线
初始状态:
- 绿线问过同学,同学讲要点run,就是那个灰色的右箭头,或者run 10ns,点了果然就出来了。好了今天的任务目的算是达到了……
第三个阶段:调出trace对比机制
- 这里没有什么对比,出来就是刷屏的时间和pc。如何调出有正常的log呢?看mycpu_tb.v
$fscanf(文件指针,读取格式,数组);
display,write用于信息的显示和输出。
display与write的区别是:display会在每次显示信息后自动换行,write不会换行。
- 图中信号无法在波形图中显示,使用“add to wave”:在箭头所指框框内右键,就有这个命令。可以把所浏览的模块的信号添加到左数第三个列表内。此时,点击图线即可观察信号情况。
- 很多线都不是绿色的,蓝线是高阻态,同学说要赋初值,经观察,这些信号之所以呈现高阻态是因为只有一端和信号端口连接,这些信号要么没用,要么需要重新连接,要么是接口信号宽度不对。
- 以上的东西改完了,目前目测没有高阻态了,但是还有好多X,这些X应该是没用到的变量。很奇怪的是有的变量先是变绿了,后又变成X态了,留作之后考虑。这个标号要踩的坑是:
为什么pc一拍取一个,inst却是有三拍才能取到的呢?——后面也有可以一拍取到的。
而且发现有的pc也是持续了两拍,目测可能和跳转指令有关
第一版的图形:
可以看出框出来的这四个变量相等,而它们本不该相等
观察代码:
猜测是stall未赋初值的缘故,赋初值:
再次运行,那四个值不相等了。
然后继续解决那个指令持续三拍的问题:
尝试把flush也赋上初值,收效甚微,但是我无意中看到了inst_sram_rdata,这个原始一点的信号:
可以看出取指没问题,有问题的是电路,一定有什么东西使它延迟了一拍。
最是套路得人心,我现在,欠的债总要还,路还得自己体验,这个居然是取指也要提前准备出来地址,它也得晚一拍。之前问大佬的时候早就知道数据存储器是这样的,另一个大佬讲指令存储器不用晚一拍。现在我死了,调试了好久,找了两小时原因,居然是这个。
目前它正确了,并且目测那些奇怪的“指令也持续好几拍的情况”也没了。
但是仍旧没有能出现trace对比机制,从tb中了解到和trace答案出现有关的信号是最后一级的写使能,排查写使能信号,发现wb级代码错误——一个宏定义写错了,改正后虽然debug_wb_rf_wen出来了,但是出现了另一个错误:在tb中的finish出现了一个错误。
哦,我明白了,这是trace出来了,虽然不知道错误是什么
在finish处暂停了:退出模拟并将控制权交还给操作系统。
由tb代码可知,这里是每当有错误就会停止。
- 往前推进了一点,大概是六条指令的样子。现在是早上六点四十,看来早睡早起对我是有好处的,昨晚调不出来的时候应该睡觉。
错误原因是
下面展示一些内联代码片
。
assign inst_sram_en = resetn ? 1'b1 : 1'b0;
这句代码片的冒号左右写反了。
第四阶段:各种调试
- 既然第一步已经走出来了,说明剩下的只需要慢慢调了,以下的步骤均在说明调试中出现的问题
- 遇到ori指令,经分析,译码没有问题,取数据和立即数都没问题,而出现算数错误,考虑在该算数代码模块寻找,发现是extend_unsigned和flag_unsigned写串了,而且符号扩展被我写成了1扩展。
[ 2287 ns] Error!!!
reference: PC = 0x9fc05100, wb_rf_wnum = 0x10, wb_rf_wdata = 0x00000001
mycpu : PC = 0x9fc00704, wb_rf_wnum = 0x1f, wb_rf_wdata = 0x9fc0070c
猜测是一个组合型的错误:跳转计算和地址错误处理。
以上猜测正确性好像不太对:
现发现,应该是以下跳转的问题:
不管之后结果怎样,我现在发现这里少了个default:
不知道是不是必须的,先试试:
好的,从七点半到十点半,又往前推进了一点点
4. 来解决上面那个新的问题:
搞不好将来只剩几秒钟了
ps:在这个调试过程中我逐渐熟悉了vivado,有时间要把vivado的使用小技巧写一写。不过还是有点东西不太方便,我还要再加油。
出错的图是这样的:
反观前面的问题(下图),这样的信号仿佛就意味着跳转
发现跳转模块,没补全if,相同的问题,我希望这次先查漏补缺一下,再进行调试:
以上是一处补全了if。结果是推进了一个数量级:
目前,已补全id_branch模块的if和case(考虑排查到某一模块错误时,看代码的时候先看这样的错误),以下是最新的错误:
5. 解决上图(下代码片)中的错误:
[ 14097 ns] Error!!!
reference: PC = 0x9fc00d58, wb_rf_wnum = 0x0a, wb_rf_wdata = 0x0000aaaa
mycpu : PC = 0x9fc00d58, wb_rf_wnum = 0x0a, wb_rf_wdata = 0x00000000
ps:在分析机器码的时候我用了进制码转换器,算是一个小技巧
经查证,这是条lw指令,但是很奇怪的是,若是按计算结果进行取数据,地址是没错的,使能信号也有,但是取出的数据却是00000000,和结果不符。
和同学交换意见,认为可能和虚拟地址映射到物理地址的变换有关,尝试。
虚拟地址倒是解决了,也取得了数据,但是进行比较的只是前一拍得到的alu_result和data_sram_rdata。
经过延时或者添加#1仍无法解决,而且这样不利于实验,以是保存下该版本的CPU,然后进行更改,使对alu_result和data_sram_rdata的选择在wb级实现。
然后一夜回到*——我现在又回到了2000多ns。
如果拒绝改革,那就只好流血到底。
我把之前备份的CPU放回来,改了一个特别小的bug,运行之后,时钟又多跑了十秒,但是之前的错误没有消失。
我回来了,问题好像出在宏定义上:
学到一个调试小技巧:$display();函数,会在控制台输出数据,从而看出你是从哪里过的。
我的宏定义if(en_write_mm != `EN_WRITE_M2R),此处EN_WRITE_M2R值该是010,但是它过来之后不是010,遂改成if(en_write_mm != 2),发现之后改变了:
这下可以正儿八经地讲运行到了下一条指令了。
- 发现了一个问题,defines里的宏定义有的用数字替换之后会运行不出结果。
不明原因造成宏定义出现偏差:
比如此处,stall只该有00111的情况。
以及上图中,一个hazard_stall信号,出现了不稳定的赋值。原因不明。
好了,现在原因明白了,全都是我define的时候没有写位数,就是5’b00000写成了00000。
新错误。
一件坑爹的事情发生了,网上的那个进制转换计算器出错了,垃圾……搞的我还以为test.S文件有错呢……
好了,我现在找到和错误有关的信号了。
OS:有没有觉得我现在调试的手段更加高级了?
黄色小旗子的右边本该和左边一样的【粉色信号】,别的地方都是符合if_id_pc和reg_s_addr等相对应的。
问题应该是id_ins的问题,应该是stall后又用了新的地址取到了新的指令。解决一波。
解决方法如下:
在if_id流水线寄存器加两个信号,一个为上一条指令的指令操作,一个为上一条指令是不是要stall,id级的操作码由以上两个信号加上从指令SRAM里取出的操作码,三个构成选择电路。
另一个经验是,vivado存储了指令信号之后真好用,而且目测还可以保存调试过程【所用的信号】
我还学会了反思,等到功能点都过了以后,为了验证自己的想法而不是糊里糊涂得过且过,我滚了回去,看之前的标志小旗子那里有没有如我所想的进行改变
果不其然!
结果就是一口气过完第九个测试点,以下是新的错误:
8. 我好想明白了这样的三个联排是什么情况了
如下:【就是不知道tb是如何检测判别是否比较的】
学习有个阶段性:第一阶段是痛苦的填鸭式教育,要死记住大量知识。第二阶段是快乐些的事情,15%阶段,这个阶段时刻会平均接触15%的新知识,新奇有挑战而不会让人很绝望。我不知道自己现在算不算处于此阶段。也许到调试完成的那刻才能知道自己到底如何吧。
写这些话是为了记录,完全不可沾沾自喜,保佑不要万抽为R。球球了!
又踩一个坑,发现vivado不能连续运行两次,否则就是错上加错,要relaunch,才能出现正确的错误位置的PC【又浪费了一小时】;另外,只比对golden_trace.txt中的值,这可能就是为什么会出现连续好几个相同PC的情况吧?
- 留作之后考虑!
这个问题由于我关于stlu的指令未处理完善,我的代码如下:
第一版:
assign tmp_sub = {operand1[31], operand1} - {operand2[31], operand2};
`SLT: begin
if(!flag_unsigned && operand1[31] != operand2[31]) begin
overflow <= 1'b0;
alu_result <= operand1[31] ? 32'b1 : 32'b0;
end else begin
overflow <= 1'b0;
alu_result <= tmp_sub[32] ? 32'b1 : 32'b0;
end
reg_addr <= tmp_reg_addr;
flag_common <= 1'b1;
end
第二版:
`SLT: begin
if(!flag_unsigned && operand1[31] != operand2[31]) begin
overflow <= 1'b0;
alu_result <= operand1[31] ? 32'b1 : 32'b0;
end else begin
overflow <= 1'b0;
alu_result <= {1'b0, operand1} < {1'b0, operand2} ? 32'b1 : 32'b0;
end
reg_addr <= tmp_reg_addr;
flag_common <= 1'b1;
end
猜测是无符号比较什么的问题
- 过完立刻考虑
此处是发现了一个alu的错误,下次遇到关于sub等测试点可以考虑alu方面的错误。
- 这是第九个错误,解决了上一个问题【花了三个小时】,我惊悚地发现CPU一直跑了下去,反正是一段有点长的时间,我以为又出现什么不出现trace对比机制的错误了,毕竟之前两三次都是比较难缠的东西。但是我现在居然一下子过了37个功能点了……
稳住,稳住,稳住就是胜利。我现在都不敢用感叹号,什么都小心翼翼的。
还好想了一会儿觉得奇怪就去问大佬了,果然有人在前面踩坑是挺爽的,唉,团队合作真好,有人和我做一样的事情是真开心。虽然生活中很多东西是无解的……
亏我不知道这一点,连蒙带猜还能过到这里……
这一点是:当有数据写回的时候参考PC才会变化
这就意味着很多跳转指令都是不涉及参考PC的变化的
这个问题是我关于bgtz的代码写错了一个字符,关键代码,果然写错一点都是致命的,这个测试集真好,看来能把整个电路都跑一遍。
我是把该写成data1的地方写成了data2。
现在又过了一个点。可以总结一下几条:
我需要把这些跳转的代码提前再检查一下了,因为这是一段跳转区。不能一条一条地调试和卡顿。
调试的步骤,先看pc,再阅读和推断汇编指令,然后看信号,再看代码,重点浏览有关区间的关键代码——因为我现在已经基本解决了冒险等问题,冒险一般不会出错了。
顺便:写代码不长脑子,调代码就得献祭肝脏。呕……
不过调试这个还挺好玩的。
我感觉前面的指令可能批量应用了bne这个简单指令,现在应该在大规模调试跳转。容我先看看这段代码有没有问题,我感觉有点可疑。
先跑着,不知道别处有没有问题,反正我好像犯了同一个错误,应该是从bgtz复制粘贴来的【反之亦然】,反正这俩应该是互相粘贴的。
- 好了,通过第43个测试点了。后面的是乘除法了。
容我先看两眼就去问大佬。先问问,应该就少走些弯路。唉,不过趣味性就会减少好多……
若是采用display函数,请一定要搭配上系统时间time函数,否则不知道对应的是哪条指令
bug撒地跑,我暂时想不出前面的为啥不对,只能看看后面不对的信号是如何不对的。
乍一看看不出啥,但是这是啥:
……
王德发!为什么!你也看到了,波形就是不对,可是在控制台输出了个东西之后它就对了。这和CppDev 的debug是一个娘生的吗?
然而我是一个较真的人,我想起自己在改那个的时候还无意中改了个什么:
if(op == `MFHI || op == `MFLO) begin
if(op == `MFHI && en_write_ex == `EN_WRITE_HI)begin//两个mux都有,两个都要,最后用一个就行
data <= value_from_ex;
end
else if(op == `MFHI && en_write_mm == `EN_WRITE_HI) begin
data <= value_from_mm_alu_result;
end
else if(op == `MFHI && en_write_ex == `EN_WRITE_HILO) begin
data <= value_from_hilo_ex[63:32];
end
else if(op == `MFHI && en_write_mm == `EN_WRITE_HILO) begin
data <= value_from_hilo_mm[63:32];
end
else begin
data <= value_from_hi_regs;
end
if(op == `MFLO && en_write_ex == `EN_WRITE_LO) begin
data <= value_from_ex;
end
else if(op == `MFLO && en_write_mm == `EN_WRITE_LO) begin
data <= value_from_mm_alu_result;
end
else if(op == `MFLO && en_write_ex == `EN_WRITE_HILO) begin
data <= value_from_hilo_ex[31:0];
end
else if(op == `MFLO && en_write_mm == `EN_WRITE_HILO) begin
data <= value_from_hilo_mm[31:0];
end
else begin
data <= value_from_lo_regs;
end
hazard_stall <= 1'h0;
end
乍一看,很容易觉得没有问题,而且输入的信号都对,结果就是不对
此处涨了一个经验:输入信号都对,但是结果不对的,很大很大概率是此处代码内部问题。
if(op == `MFHI || op == `MFLO) begin
if(op == `MFHI)begin
if(op == `MFHI && en_write_ex == `EN_WRITE_HI)begin//两个mux都有,两个都要,最后用一个就行
data <= value_from_ex;
end
else if(op == `MFHI && en_write_ex == `EN_WRITE_HILO) begin
data <= value_from_hilo_ex[63:32];
end
else if(op == `MFHI && en_write_mm == `EN_WRITE_HILO) begin
data <= value_from_hilo_mm[63:32];
end
else if(op == `MFHI && en_write_mm == `EN_WRITE_HI) begin
data <= value_from_mm;
end
else if(op == `MFHI) begin
data <= value_from_hi_regs;
end end
if(op == `MFLO)begin
$display("001",$time);
if(op == `MFLO && en_write_ex == `EN_WRITE_LO) begin
data <= value_from_ex;
end
else if(op == `MFLO && en_write_mm == `EN_WRITE_LO) begin
data <= value_from_mm;
end
else if(op == `MFLO && en_write_ex == `EN_WRITE_HILO) begin
data <= value_from_hilo_ex[31:0];
end
else if(op == `MFLO && en_write_mm == `EN_WRITE_HILO) begin
data <= value_from_hilo_mm[31:0];
end
else if(op == `MFLO) begin
data <= value_from_lo_regs;
end end
hazard_stall <= 1'h0;
end
本来是为了分段display,结果发现这样就正确了,然后发现原来的那个else语句写得逻辑有点不对
虽然依旧不知道这个不对为什么会造成这样的结果。
我现在改了最后的那个逻辑
if(op == `MFHI || op == `MFLO) begin
if(op == `MFHI && en_write_ex == `EN_WRITE_HI)begin//两个mux都有,两个都要,最后用一个就行
data <= value_from_ex;
end
else if(op == `MFHI && en_write_ex == `EN_WRITE_HILO) begin
data <= value_from_hilo_ex[63:32];
end
else if(op == `MFHI && en_write_mm == `EN_WRITE_HILO) begin
data <= value_from_hilo_mm[63:32];
end
else if(op == `MFHI && en_write_mm == `EN_WRITE_HI) begin
data <= value_from_mm;
end
else if(op == `MFHI) begin
data <= value_from_hi_regs;
end
if(op == `MFLO && en_write_ex == `EN_WRITE_LO) begin
data <= value_from_ex;
end
else if(op == `MFLO && en_write_mm == `EN_WRITE_LO) begin
data <= value_from_mm;
end
else if(op == `MFLO && en_write_ex == `EN_WRITE_HILO) begin
data <= value_from_hilo_ex[31:0];
end
else if(op == `MFLO && en_write_mm == `EN_WRITE_HILO) begin
data <= value_from_hilo_mm[31:0];
end
else if(op == `MFLO) begin
data <= value_from_lo_regs;
end
hazard_stall <= 1'h0;
end
两年了,连个if-else语句都写不好,白学计算机了不是?
- 第十二个错误根据信号溯源,这个比较好查找,不是莫名原因的“什么选择信号对但是结果不对”什么的。这个是个:
信号位数不对。
果然,bug撒地跑。
只有你对值得的人毫无保留地贡献经验值,你才会被当做有价值的人,你才可能融入大家的圈子里。前提是你能力还行,能赶上大家的进度,甚至某些方面比大家进度要快。
这个,是冒险的先后顺序出错了,应该先判断从ex级传来的数据,再判断mm级,以下是改正后的代码:
14.
这是一个关于lb取数据的坑,大佬和我讲的,soc_lite_top中是这样写的:
这说明返回的数据取哪一个其实是个问题。
下图可以看出,取数的地址应该没问题,所以取出的数据假设也没问题,问题是选择哪几位是要考虑的。
写使能wen需要变换,读取数据后也需要判断
此是一个对齐问题。
由于我不理解那段【封装里的】代码想让我咋写,我就人肉试错了一小时:
发现大概是上面那个样子——取数据的时候。
现在过了62个功能点了。
好了,现在人肉试错到64个功能点了,62-64的坑是在:
存数据的时候,字节和半字都不仅是wen变化,输入的数据也要做相应的变换,换到相应的位置上。
另一个我发现的东西就是这个测试集还挺人性化的,比如它这个指令是sw+sb然后再是lw,让你看清楚lw中那个夹心的结果。而且那些结果都是你自己运算出来的,数字都是熟悉的。
现在是12:19,中午休息会儿,13:30开始搞cp0和异常处理。
现在是晚上八点,明天九点就要答辩了,我还没写完报告,而且还没跑完CPU。
好在不算一夜回到*,写了一下午cp0和异常处理,虽然没对……出现的还是那个熟悉的令人恐慌的,每次都很难处理的不出现trace的情况。
未知苦处,不信神佛。
我现在,祈求老天保佑,给我开个光好不好,球球了,别让我明天再弄完,这样顶不住啊!
这是出现时序的问题了?
又是后面的信号用了前面的信号的值。
尝试改正,将流水线寄存器的flush传入exception模块,用它把关,做监督
可以看出,那个异常处理的地址被取代了,不再是bfc00380了,这就是精确异常吗?看起来挺不错的样子。再看一眼之前出现异常的断点信号是否如我所想。
flush不见了,难道是被隐藏了?
哦,我觉得好像不是,因为,这个信号没了之后,就没有异常处理了,从后面的ref_pc_wb可以知道,bfc00380还是要执行的,而且还是要出现在debug信号里的。
可以看出这有点奇怪……
猜测和stall相同,flush也得把从指令存储器取指停下来。和stall类似地改正,结果是:
此处bug应该是改掉了。
现在大概已经知道什么样的波形可能会是位数不对了。
发现哪里的判断信号正确但是结果不对,就要紧紧锁定这一块儿,这里的逻辑一定不对!【这是第二或三遍强调这一点了】
王德发,这是啥,告诉我,延迟槽咋会这样搞?虽然现实是这样的,但是你现实中还有摩擦力呢,你力学什么分析的时候不是最会忽略摩擦力了吗?你居然给我搞摩擦力!我吐给你看,呕!
由于瞎眼,我把这个指令码看成了未定义的指令码,其实是eret……跑完syscall你得返回一波啊。
我还跟着指令和信号跑了一遍汇编……挺好,下学期一定要显得厉害一点,要不就白学了……【?你是为了这个才学习的吗?】
错误是:eret和if_en_exception的判断顺序和赋值,我的失误把eret当成了error信号,把if_en_exception赋值为1了。
接下来的错误是一个看起来跨度很大的错误,前面应该是存了数据,后面读取的时候出了错。
-
开了八小时的会,收获好像不是特别大,我好像明白了一点——数据寄存器和指令寄存器都有那个异常处理之后的指令的问题,比如异常处理后的指令若是sw,那么那个前置就会提前写回存储器,造成数据错误。
解决方法是:把上一级产生的flush信号和本级产生的异常信号传入取数据模块,当这些有一或多个为有效的时候,不取值。 -
出现了,出现了:
多个控制信号一起为有效的时候,该选择哪一个呢?
我想了想:这是一个冒险,若是前面一个指令写的是异常处理寄存器,而后一条就是异常,那么该将异常epc等写入异常处理寄存器。但是想一下,若非写的是异常处理寄存器,而是其他的协处理器,就不用将后面的写入了。所以这要分开讨论。
讨论了,改正了,又有下一个错误了。
刚才那个错误18其实没有完全解决,造成了错误19,事实上,这里的冒险和if-else语句控制关系其实有点难顶。一个错误解决不完整了就很容易造成另一个错误。
解决完成后【可能算完成了】跑过了69个功能点。
半小时到四十分钟之内通过阅读一份错误,找到了一个数据接口位数不对的错误,下次直接先看log文件,改改warning,大概可以吧?是不是会提醒这些拼写错误什么的?或者接口不匹配什么的?
看了log文件,没什么大问题,现在的错误是
----[1336185 ns] Number 8'd75 Functional Test Point PASS!!!
FATAL_ERROR: Iteration limit 10000 is reached. Possible zero delay oscillation detected where simulation time can not advance. Please check your source code. Note that the iteration limit can be changed using switch -maxdeltaid.
Time: 1336555 ns Iteration: 10000
当然,好消息是我已经过了75个点了,坏消息是,我的手快被空调吹废了,或者是用了一天,疼。
网上讲,这样的错误是一个迭代次数超过限制了,可能是形成了组合环。
并给出了解决方法:观察出现问题的时刻,信号的变化,理论上讲: 所在在那一时刻变化的信号都有嫌疑。
我想着也只能是这样了,不然还能怎样?
找错误一定要找那个附近的没错,不过要找的还有一个原则,对于难度递增的测试集来说,要找新出现的指令,这样才可能找到错误所在。
此处组合环的出现,是异常处理在解决数据存储器的问题的时候输入了ex级中由它输出的信号得到的信号。
已经解决了组合环的问题,现在停止在76个点上。
之所以早上好解决问题是因为,晚上记忆力很好,半夜可以睡着觉想,早上逻辑思维很好,就莫名想出来了。
发现了这样一条指令:我跳我自己。
观察到前面都是移动到cp0的指令:
说明应该只需要写入EPC了,命名为branch_after_branch错误。
这个测试点好像叫软件中断测试,那么这种死循环应该就算是了吧。
通过了这个循环。
还真有这种智障的操作?存后取?存取的还是零号寄存器?
哦哦,我明白了,这三个令人智熄的操作是为了分开连在一起的存后取。
找了两个多小时的那段报错信号的原因,跟着路径溯源到很久以前,也没查到原因。后来看了看:
这个数字在信号里常常出现。
又看了看:
有七处这样的东西。
这样的东西也有七处。
这其实和cause有关,看A03中的cause的定义:
往里面写000ff2ff岂不是中断?
联想到这是在考什么——软件中断。
哦,破案了。
我没写软件异常处理。
现在过了89个功能点了,我只是修改了一些cp0的写入【异常处理】,之后的那些寄存器写回中看起来只有一半的我都去默认修改cp0了,大概就是那种一眼就看出来的错误。估计是和后面的跳转分支有关,如cause.BD段。
由于我要补信号实验报告了【祝我自己没有凉】,所以这个事情告一段落——告一下午的段落。
以下就放上一张图好了:
纪念一下我的第一次……【诶,哪里怪怪的】
OS:之后可能会补一个总结,然后开始写个人赛的东西了。
本文地址:https://blog.csdn.net/zerolord/article/details/107144556
上一篇: 只有药剂师能看懂他的字