• cpu设计和实现(数据预取)


    【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

            前面说过了一条指令经过cpu处理的时候需要经历几个阶段。通过实验,我们发现,哪怕是再简单的ori指令也要经历取指、译码、执行、访存和写回这五个阶段。这从之前的波形图上面可以看的很明显。

    1、关于使用iverilog仿真

            很多朋友也会使用《自己动手写cpu》这本书上的代码来仿真。这是完全可以的。目前来说,代码不太好找,我找了一个,上传到GitHub上面,有需要的朋友可以到这里来下载,https://github.com/feixiaoxing/design_mips_cpu/blob/master/Examples-in-book-write-your-own-cpu-master.zip。测试的时候,有几个地方需要注意下,

    1)defines.v中指令的数量需要修改,比如修改成64,不然iverilog编译不过,之前作者可能使用的是modelsim软件,对于InstMemNum这一点可能不太那么讲究。

    `define InstMemNum 64

    2)在openmips_min_sopc_tb.v文件中添加生成波形文件的内容,不然无法显示波形,

    1. initial
    2. begin
    3. $dumpfile("hello.vcd");
    4. $dumpvars(0, openmips_min_sopc_tb);
    5. end

    3)如果需要debug某一个module文件中的register内容,建议用wire拖出来一下即可

    1. // wire[0:31] regs0_wire;
    2. // wire[0:31] regs1_wire;
    3. // wire[0:31] regs2_wire;
    4. // wire[0:31] regs3_wire;
    5. // wire[0:31] regs4_wire;
    6. // assign regs0_wire = regs[0];
    7. // assign regs1_wire = regs[1];
    8. // assign regs2_wire = regs[2];
    9. // assign regs3_wire = regs[3];
    10. // assign regs4_wire = regs[4];

            平时测试的时候可以放开,后续编译烧入的时候可以重新注释上。

    4)另外测试的过程中,可能把需要查看的信号先保存成gtkw文件。这样修改后,直接用gtkwave查看gtkw文件,所有的信号都可以快速看到。不需要再一条一条拖出来查看了,否则效率太低了。

    5)对于不同模块之间的代码差异,建议直接用beyond compare查看代码差异,这样理解的更快一点。

    2、继续分析流水线

            流水线相比较单周期cpu有很多的优点,但是它的缺点也是很明显的。这些缺点就需要我们一个一个去处理和解决。数据依赖就是其中的一种,假设我们的指令是这样的,

    1. ori $1, $0, 0x1100
    2. ori $1, $1, 0x0020
    3. ori $1, $1, 0x4400
    4. ori $1, $1, 0x0044

            这是一个比较极端的例子,但是却是合理的,我们可以按照现在流水线的做法分析下。当前默认$1寄存器的读取只能从regfile中来,但是实际cpu在处理的时候,这个寄存器的来源还有可能来自于写回、访存和执行。如果一味地认为寄存器只能来自于regfile,这其实是要出问题的,因为这样我们拿到的寄存器数据很有可能不是最新的内容。所以,在id译码的时候,要对原来的数据读取过程做一些调整,

    1. always @(*) begin
    2. if(rst == `RstEnable) begin
    3. reg1_o <= `ZeroWord;
    4. end else if((reg1_read_o == 1'b1) && (ex_wreg_i == 1'b1) && (ex_wd_i == reg1_addr_o)) begin
    5. reg1_o <= ex_wdata_i;
    6. end else if((reg1_read_o == 1'b1) && (mem_wreg_i == 1'b1) && (mem_wd_i == reg1_addr_o)) begin
    7. reg1_o <= mem_wdata_i;
    8. end else if(reg1_read_o == 1'b1) begin
    9. reg1_o <= reg1_data_i;
    10. end else if(reg1_read_o == 1'b0) begin
    11. reg1_o <= imm;
    12. end else begin
    13. reg1_o <= `ZeroWord;
    14. end
    15. end
    16. always @(*) begin
    17. if(rst == `RstEnable) begin
    18. reg2_o <= `ZeroWord;
    19. end else if((reg2_read_o == 1'b1) && (ex_wreg_i == 1'b1) && (ex_wd_i == reg2_addr_o)) begin
    20. reg2_o <= ex_wdata_i;
    21. end else if((reg2_read_o == 1'b1) && (mem_wreg_i == 1'b1) && (mem_wd_i == reg2_addr_o)) begin
    22. reg2_o <= mem_wdata_i;
    23. end else if(reg2_read_o == 1'b1) begin
    24. reg2_o <= reg2_data_i;
    25. end else if(reg2_read_o == 1'b0) begin
    26. reg2_o <= imm;
    27. end else begin
    28. reg2_o <= `ZeroWord;
    29. end
    30. end

            因为写回的部分在regfile.v已经处理过了,所以这里reg1_o和reg2_o只多增加了两个部分内容。即1,如果register发现在执行阶段有更新,那么优先从执行阶段获取数值;2、如果发现在访存阶段有更新,那么从访存阶段来获取数值。注意,这里处理的逻辑顺序,其实代表了数据优先级的获取顺序。

            实验验证的方法也非常简单,我们可以看下ex_reg1每次的数值是不是最新的数值即可,

            通过观察波形图,可以比较明显地发现,数据都是最新的数值。比如第一次是0x0,第二次是0x0010,这代表数值虽然还没有写回到寄存器,但是提前获取到了。第三次是0x1120,代表ex_reg1又被更新到了最新的数值,依次类推。这就是数据预取方法。在后续的场合,我们会多次看到这个方法的使用之处。 

            另外,有需要进行代码调试的朋友,也可以在这个地址直接获取测试代码,https://github.com/feixiaoxing/design_mips_cpu/tree/master/rtl/day04。有了这个代码,加上iverilog+gtkwave就可以在windows平台仿真测试了。

  • 相关阅读:
    VideoScribe Expert:制作专业视频
    【Docker】基于Dockerfile构建镜像介绍
    普冉PY32F071单片机简单介绍,QFN64 48封装,支持 8 * 36 / 4 * 40 LCD
    前端构建工具 webpack 笔记
    csdn,是时候说再见!
    策略模式适用场景与具体实例解析
    套接字Socket编程实践(C语言版)
    web前端网页设计期末课程大作业:旅游网页主题网站设计——紫色的旅游开发景点网站静态模板(4页)HTML+CSS+JavaScript
    由Long类型引发的生产事故
    python 实现贪心算法
  • 原文地址:https://blog.csdn.net/feixiaoxing/article/details/128026712