当前位置:   article > 正文

CPU设计实战-最小SOC的实现

cpu设计实战

一 顶层模块的实现

顶层模块用于对之前文章里介绍的五级流水线的各个模块进行例化,也就是连线,那么顶层模块的输入输出接口如何呢?

首先输入要有时钟复位信号,还要有一个来接收指令存储器里的数据记为rom_data_i

输出因为要去读取指令存储器中的数据,所以要输出读地址以及一个使能信号。

具体实现就参照我们上一节所做好的数据通路进行连线,连接和数据通路图如下:CPU设计实战-第一条指令ori的实现即最简单的五级流水线的实现

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

二 指令存储器ROM的实现

之前说过我们的my_mips模块的输入输出都需要从指令存储器进行关联。

指令存储器中存储的指令码,需要进行的操作就是对其按照指令地址进行读取指令码

故输入指令地址addr,输出指令码inst,另外需要一个使能信号,如下图:

具体实现过程注意以下几点:

1.首先要定义这个rom的大小,方法和实现寄存器堆一样,本质上是一个数组寄存器堆实现

2.然后要把指令写入这个rom,这里采用系统函数¥readmemh,它可以将指定的文件中的数据写入rom中

3.最后进行读操作时需要注意,指令存储器的寻址方式是字节型,而我们pc端口模块给出的地址是32位的,所以需要除以4再写入地址,具体的操作用左移两位来实现的。那么既然要移位地址肯定要知道地址的宽度,需要知道地址宽度如何计算。

2^地址宽度=元素大小,根据define文件发现定义的元素大小为131071即128k,那么地址宽度为17

  1. `include "defines.v"
  2. module inst_rom(
  3. input ce,
  4. input [4:0] addr,
  5. output reg [31:0] inst
  6. );
  7. reg[31:0] inst_mem[0:`InstMemNum-1];
  8. initial $readmemh ("inst_rom.data", inst_mem);
  9. always @(*) begin
  10. if (!ce) begin
  11. inst = 0;
  12. end
  13. else begin
  14. inst = inst_mem[addr[`InstMemNumLog2+1:2]];//左移两位实现除以4
  15. end
  16. end
  17. endmodule

 三 data文件分析(由于编译环境还未搭建,本质上是一个手动编译指令的操作)

  1. inst_rom.data
  2. 34011100
  3. 34210020
  4. 3421ff00
  5. 342100ff

分析一下data文件中指令数据,其是按照十六进制存储的,我们展开一个第一个来看看

00110100000000010001 0001 0000 0000
ORI指令的oprsrtimm立即数

回顾ORI指令的功能

指令用法为: ori rs, rt, immediate,作用是将指令中的16位立即数immediate进行无符号
扩展至32位,然后与索引为rs的通用寄存器的值进行逻辑“或”运算,运算结果保存到索引
为rt的通用寄存器中。

 可以明白第一行指令的操作是将0号寄存器中的值和立即数进行逻辑或运算,由于0号寄存器中的值恒为0,故结果还是原来的imm立即数,并将这个结果保存到1号寄存器中。剩余指令以此类推

四 最小SOPC的实现

有了cpu和指令存储器,我们大概可以搭建一个最简单的SOPC,将两个模块连接如下:

最小SOPC对应的模块为mymips_min_sopc,其输入只有时钟和复位信号,然后按照上图进行例化即可。

  1. module mymips_min_sopc(
  2. input clk,
  3. input rst
  4. );
  5. wire rom_ce;
  6. wire [31:0] inst;
  7. wire [4:0] inst_addr;
  8. my_mips u_my_mips (
  9. .clk ( clk ),
  10. .rst ( rst ),
  11. .rom_data_i ( inst ),
  12. .rom_addr_o ( inst_addr ),
  13. .rom_ce_o ( rom_ce )
  14. );
  15. inst_rom u_inst_rom (
  16. .ce ( rom_ce ),
  17. .addr ( inst_addr ),
  18. .inst ( inst)
  19. );
  20. endmodule

五 编写测试程序

读者可能还是会疑惑data文件里的数据是怎么得来的,其实是基于我们编写的测试文件inst_rom.S

此文件为汇编文件,用于测试ori指令

  1. ori $1,$0,0x1100
  2. ori $2,$0,0x0020
  3. ori $3,$0,0xff00
  4. ori $4,$0,0xffff

可以知道此代码的功能是将立即数与0号寄存器(恒为0)进行或运算故结果还是原来的立即数

按照正常顺序是通过编译器编译此文件得出指令寄存器中的二进制data文件,由于配置编译环境花费额外篇幅讲解,此处采用手动编译的方法。

例如第一行指令对应的二进制如下图:

 转化为16进制为data文件中第一行的数据,其他直径以此类推,这就解释了data文件中的数据是怎么来的。

接下来就可以写testbech来测试我们的程序了,仿真文件很好写,对于sopc模块只需要给与时钟信号和复位信号即可,通过观察仿真文件里的寄存器的值即可知道指令运行是否正常。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/941984
推荐阅读
相关标签
  

闽ICP备14008679号