• cpu设计和实现(流水线上的第一条指令)


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

            读书的时候,《计算机组成原理》也看了,《计算机体系结构》也学了,老师也给我们讲了各种各样的流水线知识,但是实践的机会很少,感觉就是没有把理论转成实际的东西。工作之后,倒是有机会接触各种各样的开源代码,这里面也包括了开源cpu代码,比如openrisc(https://github.com/openrisc/or1200/tree/master/rtl/verilog)这样的开源代码。但是内容又过于复杂,学习的曲线比较陡,难度很高。所以至此,很多同学就云里雾里不知道流水线和cpu的指令是怎么完成的,总是搞不清楚。

            前面分析过,一条指令的过程是按照取指、译码、执行、访存、写回这5个步骤来完成的。那么今天,就可以用一条5级流水线来实现ori指令。

    1、 题外话的一个知识点

            之前我们在编写取指那篇文章的时候,谈到过组合逻辑和时序逻辑的区别。很多刚学verilog的同学,很容易把wire看成是组合逻辑,把reg看成是时序逻辑,这是不对的。是组合逻辑,还是时序逻辑,归根到底还是要看触发条件,比如之前的那个代码,我们稍微调整下,

    1. module test(clk, rst, in, out_a, out_b);
    2. input wire clk;
    3. input wire rst;
    4. input wire in;
    5. output wire out_a;
    6. output reg out_b;
    7. assign out_a = in;
    8. always@(*) begin
    9. if(rst)
    10. out_b <= 1'b0;
    11. else
    12. out_b <= in;
    13. end
    14. endmodule

             这里的out_b从类型上看,好像是register。但是它的触发条件却是always(*),这就意味着,在这段电路描述中,只要rst或者in发生改变,out_b就会随之改变。这不就是组合逻辑的思路吗?同样,可以借助于gtkwave观察下图形确认一下,

             简单分析下,这里的out_b在rst置位的时候,一直输出为0。但是当rst撤去之后,out_b就开始随着in的改变而改变。这个示例告诉我们,组合逻辑和时序逻辑,最终都要通过触发条件来进行区别和判断。如果心里还是拿不准,就仿真看一下波形图结果就好了。

    2、流水线编写

            注1:关于本章所有的verilog代码,参考这个地址,https://github.com/feixiaoxing/design_mips_cpu/tree/master/rtl/day03 。

            注2:文中涉及代码,来自于《自己动手写cpu》,向原作者雷思磊表示感谢。

    2.1 取指if

            取指这部分之前已经描述过了(https://feixiaoxing.blog.csdn.net/article/details/127914989?spm=1001.2014.3001.5502)。主要包含了两个部分,一个是地址的生成,一个是rom数据的读取。做好了这两个,取指的工作就完成了。其中,地址pc生成是时序逻辑,rom读取是组合逻辑。

    2.2 指令传递if-id

            指令传送是一个时序逻辑。在这个模块,需要把指令传递给下一个模块,内容不复杂,

    1. `include "defines.v"
    2. module if_id(
    3. input wire clk,
    4. input wire rst,
    5. input wire[`InstAddrBus] if_pc,
    6. input wire[`InstBus] if_inst,
    7. output reg[`InstAddrBus] id_pc,
    8. output reg[`InstBus] id_inst
    9. );
    10. always @(posedge clk) begin
    11. if(rst == `RstEnable) begin
    12. id_pc <= `ZeroWord;
    13. id_inst <= `ZeroWord;
    14. end else begin
    15. id_pc <= if_pc;
    16. id_inst <= if_inst;
    17. end
    18. end
    19. endmodule

    2.3 译码id

            译码是流水线中很重要的工作。它主要的目的,就是从指令数据中提取到合适的信息。比如当前操作是寄存器操作,还是访存操作。如果是寄存器操作,是逻辑运算,还是数学运算。如果是逻辑运算,源操作数1是哪个,源操作数2是哪个,目的操作数是哪个,是什么样的逻辑运算等等。

            另外,在译码的过程中,对cpu通用寄存器的操作也是很重要的,这部分可以看一下,

    1. `include "defines.v"
    2. module regfile(
    3. input wire clk,
    4. input wire rst,
    5. // write
    6. input wire we,
    7. input wire[`RegAddrBus] waddr,
    8. input wire[`RegBus] wdata,
    9. // read1
    10. input wire re1,
    11. input wire[`RegAddrBus] raddr1,
    12. output reg[`RegBus] rdata1,
    13. // read2
    14. input wire re2,
    15. input wire[`RegAddrBus] raddr2,
    16. output reg[`RegBus] rdata2
    17. );
    18. reg[`RegBus] regs[0:`RegNum-1];
    19. always @(posedge clk) begin
    20. if(rst == `RstDisable) begin
    21. if((we == `WriteEnable) && (waddr!= `RegNumLog2'h0)) begin
    22. regs[waddr] <= wdata;
    23. end
    24. end
    25. end
    26. always@(*) begin
    27. if(rst == `RstEnable) begin
    28. rdata1 <= `ZeroWord;
    29. end else if(raddr1 == `RegNumLog2'h0) begin
    30. rdata1 <= `ZeroWord;
    31. end else if((raddr1 == waddr) && (we == `WriteEnable) && (re1 == `ReadEnable)) begin
    32. rdata1 <= wdata;
    33. end else if(re1 == `ReadEnable) begin
    34. rdata1 <= regs[raddr1];
    35. end else begin
    36. rdata1 <= `ZeroWord;
    37. end
    38. end
    39. always@(*) begin
    40. if(rst == `RstEnable) begin
    41. rdata2 <= `ZeroWord;
    42. end else if(raddr2 == `RegNumLog2'h0) begin
    43. rdata2 <= `ZeroWord;
    44. end else if((raddr2 == waddr) && (we == `WriteEnable) && (re2 == `ReadEnable)) begin
    45. rdata2 <= wdata;
    46. end else if(re2 == `ReadEnable) begin
    47. rdata2 <= regs[raddr1];
    48. end else begin
    49. rdata2 <= `ZeroWord;
    50. end
    51. end
    52. endmodule

            从代码上看,regfile最多支持两个register的读取和一个register的写入。注意,这里读取动作是组合逻辑,写入动作是时序逻辑,这非常重要。此外,不管寄存器是这样,rom操作、ram操作也是这样,读取一般都是组合逻辑,而写入才是时序逻辑。

            说完了寄存器访问,下面就是具体的译码工作了,

    1. `include "defines.v"
    2. module id(
    3. input wire rst,
    4. input wire[`InstAddrBus] pc_i,
    5. input wire[`InstBus] inst_i,
    6. input wire[`RegBus] reg1_data_i,
    7. input wire[`RegBus] reg2_data_i,
    8. output reg reg1_read_o, // read signal
    9. output reg reg2_read_o, // read signal
    10. output reg[`RegAddrBus] reg1_addr_o,
    11. output reg[`RegAddrBus] reg2_addr_o,
    12. output reg[`AluOpBus] aluop_o,
    13. output reg[`AluSelBus] alusel_o,
    14. output reg[`RegBus] reg1_o,
    15. output reg[`RegBus] reg2_o,
    16. output reg[`RegAddrBus] wd_o,
    17. output reg wreg_o
    18. );
    19. wire[5:0] op = inst_i[31:26];
    20. wire [4:0] op2 = inst_i[10:6];
    21. wire [5:0] op3 = inst_i[5:0];
    22. wire [4:0] op4 = inst_i[20:16];
    23. reg[`RegBus] imm;
    24. reg instvalid;
    25. always @(*) begin
    26. if(rst == `RstEnable) begin
    27. aluop_o <= `EXE_NOP_OP;
    28. alusel_o <= `EXE_RES_NOP;
    29. wd_o <= `NOPRegAddr;
    30. wreg_o <= `WriteDisable;
    31. instvalid <= `InstValid;
    32. reg1_read_o <= 1'b0;
    33. reg2_read_o <= 1'b0;
    34. reg1_addr_o <= `NOPRegAddr;
    35. reg2_addr_o <= `NOPRegAddr;
    36. imm <= 32'h0;
    37. end else begin
    38. aluop_o <= `EXE_NOP_OP;
    39. alusel_o <= `EXE_RES_NOP;
    40. wd_o <= inst_i[15:11];
    41. wreg_o <= `WriteDisable;
    42. instvalid <= `InstValid;
    43. reg1_read_o <= 1'b0;
    44. reg2_read_o <= 1'b0;
    45. reg1_addr_o <= inst_i[25:21];
    46. reg2_addr_o <= inst_i[20:16];
    47. imm <= `ZeroWord;
    48. case(op)
    49. `EXE_ORI: begin
    50. wreg_o <= `WriteEnable;
    51. aluop_o <= `EXE_OR_OP;
    52. alusel_o <= `EXE_RES_LOGIC;
    53. reg1_read_o <= 1'b1;
    54. reg2_read_o <= 1'b0;
    55. imm <= {16'h0, inst_i[15:0]};
    56. wd_o <= inst_i[20:16];
    57. instvalid <= `InstValid;
    58. end
    59. default: begin
    60. end
    61. endcase
    62. end
    63. end
    64. always @(*) begin
    65. if(rst == `RstEnable) begin
    66. reg1_o <= `ZeroWord;
    67. end else if(reg1_read_o == 1'b1) begin
    68. reg1_o <= reg1_data_i;
    69. end else if(reg1_read_o == 1'b0) begin
    70. reg1_o <= imm;
    71. end else begin
    72. reg1_o <= `ZeroWord;
    73. end
    74. end
    75. always @(*) begin
    76. if(rst == `RstEnable) begin
    77. reg2_o <= `ZeroWord;
    78. end else if(reg2_read_o == 1'b1) begin
    79. reg2_o <= reg2_data_i;
    80. end else if(reg2_read_o == 1'b0) begin
    81. reg2_o <= imm;
    82. end else begin
    83. reg2_o <= `ZeroWord;
    84. end
    85. end
    86. endmodule

            译码的工作其实也分成了两个部分。一部分,就是之前讨论的指令解析。这里分析的指令是EXE_ORI,所以看到wreg_o、aluop_o、alusel_o等这样的赋值动作。另一部分,就是寄存器的读取动作,至于reg1_o和reg_o是访存register寄存器,还是直接从imm立即数中获取,这取决于具体的情况。就EXE_ORI而言,reg1_o来自于reg1_data_i,reg2_o来自于imm。

    2.4 操作数传递id-exe

            操作数的传递也是一个时序逻辑。它的主要功能就是把读取到的操作数、操作方式、写入地址告诉exe模块,

    1. `include "defines.v"
    2. module id_ex(
    3. input wire clk,
    4. input wire rst,
    5. input wire[`AluOpBus] id_aluop,
    6. input wire[`AluSelBus] id_alusel,
    7. input wire[`RegBus] id_reg1,
    8. input wire[`RegBus] id_reg2,
    9. input wire[`RegAddrBus] id_wd,
    10. input wire id_wreg,
    11. output reg[`AluOpBus] ex_aluop,
    12. output reg[`AluSelBus] ex_alusel,
    13. output reg[`RegBus] ex_reg1,
    14. output reg[`RegBus] ex_reg2,
    15. output reg[`RegAddrBus] ex_wd,
    16. output reg ex_wreg
    17. );
    18. always @(posedge clk) begin
    19. if(rst == `RstEnable) begin
    20. ex_aluop <= `EXE_NOP_OP;
    21. ex_alusel <= `EXE_RES_NOP;
    22. ex_reg1 <= `ZeroWord;
    23. ex_reg2 <= `ZeroWord;
    24. ex_wd <= `NOPRegAddr;
    25. ex_wreg <= `WriteDisable;
    26. end else begin
    27. ex_aluop <= id_aluop;
    28. ex_alusel <= id_alusel;
    29. ex_reg1 <= id_reg1;
    30. ex_reg2 <= id_reg2;
    31. ex_wd <= id_wd;
    32. ex_wreg <= id_wreg;
    33. end
    34. end
    35. endmodule

    2.5 执行exe

            有了从id模块获取的操作数和写入地址,这里只要完成对应的操作就可以。注意,执行exe属于组合逻辑,

    1. `include "defines.v"
    2. module ex(
    3. input wire rst,
    4. input wire[`AluOpBus] aluop_i,
    5. input wire[`AluSelBus] alusel_i,
    6. input wire[`RegBus] reg1_i,
    7. input wire[`RegBus] reg2_i,
    8. input wire[`RegAddrBus] wd_i,
    9. input wire wreg_i,
    10. output reg[`RegAddrBus] wd_o,
    11. output reg wreg_o,
    12. output reg[`RegBus] wdata_o
    13. );
    14. reg[`RegBus] logicout;
    15. always@(*) begin
    16. if(rst == `RstEnable) begin
    17. logicout <= `ZeroWord;
    18. end else begin
    19. case (aluop_i)
    20. `EXE_OR_OP: begin
    21. logicout <= reg1_i | reg2_i;
    22. end
    23. default: begin
    24. logicout <= `ZeroWord;
    25. end
    26. endcase
    27. end
    28. end
    29. always@(*) begin
    30. wd_o <= wd_i;
    31. wreg_o <= wreg_i;
    32. case(alusel_i)
    33. `EXE_RES_LOGIC: begin
    34. wdata_o <= logicout;
    35. end
    36. default: begin
    37. wdata_o <= `ZeroWord;
    38. end
    39. endcase
    40. end
    41. endmodule

            因为exe中可能会有逻辑运算、数学运算、移位运算等等,所以一般这里都会先进行一下区分,最后把结果汇总上来。如上面的代码所示,logicout就是汇总之前的计算,而最终输出的数据时wdata_o。

    2.6 执行传递ex-mem

            做完了exe,下面就需要把结果传递给mem这个模块了。也许有同学说,这里不是不需要访存吗,为什么还要传递给mem。这主要是因为之前设计的就是5级流水线,即使最终用不到这一块内容,也需要透传一下。

    1. `include "defines.v"
    2. module ex_mem(
    3. input wire clk,
    4. input wire rst,
    5. input wire[`RegAddrBus] ex_wd,
    6. input wire ex_wreg,
    7. input wire[`RegBus] ex_wdata,
    8. output reg[`RegAddrBus] mem_wd,
    9. output reg mem_wreg,
    10. output reg[`RegBus] mem_wdata
    11. );
    12. always @(posedge clk) begin
    13. if(rst ==`RstEnable) begin
    14. mem_wd <= `NOPRegAddr;
    15. mem_wreg <= `WriteDisable;
    16. mem_wdata <= `ZeroWord;
    17. end else begin
    18. mem_wd <= ex_wd;
    19. mem_wreg <= ex_wreg;
    20. mem_wdata <= ex_wdata;
    21. end
    22. end
    23. endmodule

    2.7 访问mem

            之前说过,这部分其实不需要,只是流水线已经设计好,所以需要的操作就是继续透传下去。接着,我们可以看下verilog代码,

    1. `include "defines.v"
    2. module mem(
    3. input wire rst,
    4. input wire[`RegAddrBus] wd_i,
    5. input wire wreg_i,
    6. input wire[`RegBus] wdata_i,
    7. output reg[`RegAddrBus] wd_o,
    8. output reg wreg_o,
    9. output reg[`RegBus] wdata_o
    10. );
    11. always @(*) begin
    12. if(rst ==`RstEnable) begin
    13. wd_o <= `NOPRegAddr;
    14. wreg_o <= `WriteDisable;
    15. wdata_o <= `ZeroWord;
    16. end else begin
    17. wd_o <= wd_i;
    18. wreg_o <= wreg_i;
    19. wdata_o <= wdata_i;
    20. end
    21. end
    22. endmodule

    2.8 访存传递mem-wb

            有了mem阶段的处理,这部分就可以正式交给wb了。需要注意的是,mem访问是组合逻辑,而mem-wb是时序逻辑。

    1. `include "defines.v"
    2. module mem_wb(
    3. input wire clk,
    4. input wire rst,
    5. input wire[`RegAddrBus] mem_wd,
    6. input wire mem_wreg,
    7. input wire[`RegBus] mem_wdata,
    8. output reg[`RegAddrBus] wb_wd,
    9. output reg wb_wreg,
    10. output reg[`RegBus] wb_wdata
    11. );
    12. always @(posedge clk) begin
    13. if(rst ==`RstEnable) begin
    14. wb_wd <= `NOPRegAddr;
    15. wb_wreg <= `WriteDisable;
    16. wb_wdata <= `ZeroWord;
    17. end else begin
    18. wb_wd <= mem_wd;
    19. wb_wreg <= mem_wreg;
    20. wb_wdata <= mem_wdata;
    21. end
    22. end
    23. endmodule

    2.9  wb写回

            很多同学会问,问什么没有wb写回的组合逻辑和时序逻辑。一般来说,wb阶段就是把数据直接写到寄存器里面,这个阶段一般是不会有什么问题的。而写回的数据、寄存器地址,直接给regfile这个模块就可以了,大家可以从openmips.v这个文件看的出来,

    1. `include "defines.v"
    2. module openmips(
    3. input wire clk,
    4. input wire rst,
    5. input wire[`RegBus] rom_data_i,
    6. output wire[`RegBus] rom_addr_o,
    7. output wire rom_ce_o
    8. );
    9. wire[`InstAddrBus] pc;
    10. wire[`InstAddrBus] id_pc_i;
    11. wire[`InstBus] id_inst_i;
    12. wire[`AluOpBus] id_aluop_o;
    13. wire[`AluSelBus] id_alusel_o;
    14. wire[`RegBus] id_reg1_o;
    15. wire[`RegBus] id_reg2_o;
    16. wire id_wreg_o;
    17. wire[`RegAddrBus] id_wd_o;
    18. wire[`AluOpBus] ex_aluop_i;
    19. wire[`AluSelBus] ex_alusel_i;
    20. wire[`RegBus] ex_reg1_i;
    21. wire[`RegBus] ex_reg2_i;
    22. wire ex_wreg_i;
    23. wire[`RegAddrBus] ex_wd_i;
    24. wire ex_wreg_o;
    25. wire[`RegAddrBus] ex_wd_o;
    26. wire[`RegBus] ex_wdata_o;
    27. wire mem_wreg_i;
    28. wire[`RegAddrBus] mem_wd_i;
    29. wire[`RegBus] mem_wdata_i;
    30. wire mem_wreg_o;
    31. wire[`RegAddrBus] mem_wd_o;
    32. wire[`RegBus] mem_wdata_o;
    33. wire wb_wreg_i;
    34. wire[`RegAddrBus] wb_wd_i;
    35. wire[`RegBus] wb_wdata_i;
    36. wire reg1_read;
    37. wire reg2_read;
    38. wire[`RegBus] reg1_data;
    39. wire[`RegBus] reg2_data;
    40. wire[`RegAddrBus] reg1_addr;
    41. wire[`RegAddrBus] reg2_addr;
    42. // initialize pc_reg
    43. pc_reg pc_reg0(
    44. .clk(clk),
    45. .rst(rst),
    46. .pc(pc),
    47. .ce(rom_ce_o)
    48. );
    49. assign rom_addr_o = pc;
    50. // initialize if_id
    51. if_id if_id0(
    52. .clk(clk),
    53. .rst(rst),
    54. .if_pc(pc),
    55. .if_inst(rom_data_i),
    56. .id_pc(id_pc_i),
    57. .id_inst(id_inst_i)
    58. );
    59. // initialize id
    60. id id0(
    61. .rst(rst),
    62. .pc_i(id_pc_i),
    63. .inst_i(id_inst_i),
    64. .reg1_data_i(reg1_data),
    65. .reg2_data_i(reg2_data),
    66. .reg1_read_o(reg1_read),
    67. .reg2_read_o(reg2_read),
    68. .reg1_addr_o(reg1_addr),
    69. .reg2_addr_o(reg2_addr),
    70. .aluop_o(id_aluop_o),
    71. .alusel_o(id_alusel_o),
    72. .reg1_o(id_reg1_o),
    73. .reg2_o(id_reg2_o),
    74. .wd_o(id_wd_o),
    75. .wreg_o(id_wreg_o)
    76. );
    77. // initialize regfile
    78. regfile regfile1(
    79. .clk(clk),
    80. .rst(rst),
    81. .we(wb_wreg_i),
    82. .waddr(wb_wd_i),
    83. .wdata(wb_wdata_i),
    84. .re1(reg1_read),
    85. .raddr1(reg1_addr),
    86. .rdata1(reg1_data),
    87. .re2(reg2_read),
    88. .raddr2(reg2_addr),
    89. .rdata2(reg2_data)
    90. );
    91. // initialize idid_ex
    92. id_ex id_ex0(
    93. .clk(clk),
    94. .rst(rst),
    95. .id_aluop(id_aluop_o),
    96. .id_alusel(id_alusel_o),
    97. .id_reg1(id_reg1_o),
    98. .id_reg2(id_reg2_o),
    99. .id_wd(id_wd_o),
    100. .id_wreg(id_wreg_o),
    101. .ex_aluop(ex_aluop_i),
    102. .ex_alusel(ex_alusel_i),
    103. .ex_reg1(ex_reg1_i),
    104. .ex_reg2(ex_reg2_i),
    105. .ex_wd(ex_wd_i),
    106. .ex_wreg(ex_wreg_i)
    107. );
    108. // initialize ex
    109. ex ex0(
    110. .rst(rst),
    111. .aluop_i(ex_aluop_i),
    112. .alusel_i(ex_alusel_i),
    113. .reg1_i(ex_reg1_i),
    114. .reg2_i(ex_reg2_i),
    115. .wd_i(ex_wd_i),
    116. .wreg_i(ex_wreg_i),
    117. .wd_o(ex_wd_o),
    118. .wreg_o(ex_wreg_o),
    119. .wdata_o(ex_wdata_o)
    120. );
    121. // initialize ex_mem
    122. ex_mem ex_mem0(
    123. .clk(clk),
    124. .rst(rst),
    125. .ex_wd(ex_wd_o),
    126. .ex_wreg(ex_wreg_o),
    127. .ex_wdata(ex_wdata_o),
    128. .mem_wd(mem_wd_i),
    129. .mem_wreg(mem_wreg_i),
    130. .mem_wdata(mem_wdata_i)
    131. );
    132. // initialize mem
    133. mem mem0(
    134. .rst(rst),
    135. .wd_i(mem_wd_i),
    136. .wreg_i(mem_wreg_i),
    137. .wdata_i(mem_wdata_i),
    138. .wd_o(mem_wd_o),
    139. .wreg_o(mem_wreg_o),
    140. .wdata_o(mem_wdata_o)
    141. );
    142. // initialize mem_wb
    143. mem_wb mem_wb0(
    144. .clk(clk),
    145. .rst(rst),
    146. .mem_wd(mem_wd_o),
    147. .mem_wreg(mem_wreg_o),
    148. .mem_wdata(mem_wdata_o),
    149. .wb_wd(wb_wd_i),
    150. .wb_wreg(wb_wreg_i),
    151. .wb_wdata(wb_wdata_i)
    152. );
    153. endmodule

            直接观察wb_wd_i、wb_wreg_i、wb_wdata_i这几个信号,最终是返回给了regfile1这个模块,这也从形式上面完成了流水线的闭环操作。

    2.10 仿真和测试

            为了仿真和测试,需要做两步。第一步,给openmips准备一个小的soc模块,把cpu和rom加进去,比如像这样,

    1. `include "defines.v"
    2. module openmips_min_sopc(
    3. input wire clk,
    4. input wire rst
    5. );
    6. wire[`InstAddrBus] inst_addr;
    7. wire [`InstBus] inst;
    8. wire rom_ce;
    9. openmips openmips0(
    10. .clk(clk),
    11. .rst(rst),
    12. .rom_addr_o(inst_addr),
    13. .rom_data_i(inst),
    14. .rom_ce_o(rom_ce)
    15. );
    16. inst_rom inst_rom0(
    17. .ce(rom_ce),
    18. .addr(inst_addr),
    19. .inst(inst)
    20. );
    21. endmodule

            其次,给这个soc模块准备一个testbench测试,发一下激励信号。

    1. `timescale 1ns/1ns
    2. module openmips_min_sopc_tb();
    3. reg CLOCK_50;
    4. reg rst;
    5. initial begin
    6. CLOCK_50 = 1'b0;
    7. forever #10 CLOCK_50=~CLOCK_50;
    8. end
    9. initial begin
    10. rst = `RstEnable;
    11. #195 rst = `RstDisable;
    12. #1000 $stop;
    13. end
    14. openmips_min_sopc openmips_min_sopc0(
    15. .clk(CLOCK_50),
    16. .rst(rst)
    17. );
    18. initial
    19. begin
    20. $dumpfile("hello.vcd");
    21. $dumpvars(0, openmips_min_sopc_tb);
    22. end
    23. endmodule

            注意,测试romdata也发生了变化,

    1. 34011100
    2. 34020020
    3. 3403ff00
    4. 3404ffff

            有了这两步,就可以用iverilog和gtkwave开始测试了,

            上面的图形中,其实regfile1的部分没有显示出来,可以通过regfile.v中添加一些代码,比如这样,

    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. assign regs0_wire = regs[0];
    6. assign regs1_wire = regs[1];
    7. assign regs2_wire = regs[2];
    8. assign regs3_wire = regs[3];

            这样波形图就看的比较清楚了,

     

    3、调试方法

            调试的时候,可以优先测试register,也就是时序逻辑。如果时序逻辑没有问题,再对组合逻辑进行问题和验证。测试往往是一个循环往复的过程,需要不断进行,更需要找到root cause。

  • 相关阅读:
    需求分析和常见的需求问题解决
    极智开发 | 讲解 React 组件三大属性之三:refs
    【MySQL】事务的概念、特性及其分类
    [DOM]获取元素:根据ID、标签名、HTML5新增的方法、特殊元素获取
    电子章盖章软件怎么找?从需求、功能、成本、口碑四方面入手
    滴滴秋招提前批正式开始,现在投递免笔试
    【无标题】
    Simulink从0搭建模型03-Enabled Subsystem 使能子系统
    Qlik部署动态经营分析,实时帮助企业掌控盈利能力
    C++算法之旅、09 力扣篇 | 常见面试笔试题(上)算法小白专用
  • 原文地址:https://blog.csdn.net/feixiaoxing/article/details/127932768