单周期31条指令CPU设计bug—总结
- vivado 2016.2
- verilog
- modelsim
- Mars标准
-声明:该篇总结的bug是在编写代码,并进行测试过程中遇到问题,并及时记录。并不具有普适性,但可以提供相关的思路,帮助您去寻找出错的关键
文中使用的变量意义声明
变量名 | 含义 |
---|---|
pc | 32位,下一条指令在指令存储器中的地址,起始地址为32’h0040_0000 |
inst | 32位,对应MIPS指令 |
RF_W | 1位,寄存器堆regfile的写信号,高电平有效 |
wdata | 32位,寄存器堆regfile的写数据 |
waddr | 32位,寄存器堆regfile的写地址 |
1. 在顶层测试tb文件中使用$display系统函数无法读出pc以及对应的inst
$fdisplay(file_output,"pc: %h",uut.pc); //file_out指定输出结果的文件
$fdisplay(file_output,"instr: %h",uut.instruction);
建议:需要理清楚$fdisplay的层次关系。
解释:以上的代码是最后正确的代码,其中uut是在tb文件中实例化的cpu的名字,pc是cpu的接口之一。用C++中的概念来说,设cpu模块是一个类,那么$fdisplay中都是对象名,也就是实例化后的模块名。如,cpu uut(clk_in,reset,pc,inst)
不断向下挖,一直到最底层的接口为止。
2.一直循环读取第一条,第二条指令
在cpu中需要一个指令存储器instmem,接口如下:
接口类型 | 名称 | 含义 |
---|---|---|
input[31:0] | address | 下一条指令在指令存储器中的位置 |
output[31:0] | inst | 读出来的指令 |
读出什么指令,取决于两方面
- 指令寄存器中的数据是否正确
- 传入的地址是否正确
通过观察modelsim看到,address一直在0,1之间徘徊,也没有报地址溢出的错误,所以问题关键,肯定在于address。
对于address,也需要从两方面进行考虑
- 外部传入时,是否出错
- 在内部处理时,是否出错。
在debug过程中,在分析清楚出错可能的原因之后,一定是先从容易的方面入手。
很幸运,在检查内部处理的部分中,找到了根源。在过程中使用了一个临时变量“接”address,实际应该是用一个32位的临时变量,但编写过程中,不小心使用了一个一位的变量,导致这个临时变量不是0就是1
3.regfile无法写入数据
思路:
- 列出regfile相关的变量:RF_W,wdata,wdata
- 将这些变量加入cpu的接口列表中
- 注释掉cpu模块代码主体中对这些变量的声明
- 修改对应的tb文件中对cpu的实例化
- simulation,观察modelsim中这些变量的值,是否符合预想
以上这个思路是在debug我们的cpu的时候最常用的方法,可以帮助我们检查到出错的量。在此基础上,顺藤摸瓜,直到找到问题的根源。
最终解决:出错根源是,由于regfile写入的wdata有多个来源,需要数据选择器进行选择。在cpu模块中实例化数据选择器时,选择信号M7手误写成了m7。由于我的代码是在sumlime中编写好后,再copy到vivado中,所以vivado并没有及时检查出拼写的错误。
4.不能将测试文件中的指令读完
ps:纯经验
在tb文件中需要自行模拟时钟信号
always #50 clk=~clk
**解决:**将#50改成#4或者#2,总之改小
解释:#50说明,时钟周期为100个时间单位,频率为1M。也就是说,假设这时候在规定时间内,只能做x次取指操作,那么,改成#2之后,就能做25次取指操作。
5.sra测试错误
如何从测试结果找到这个问题根源请参照上述3,这边主要讲一个注意点:
- 算术右移符>>>失效了
**解决办法:**手动通过拼接的方式写了算术右移功能