赞
踩
顶层模块用于对之前文章里介绍的五级流水线的各个模块进行例化,也就是连线,那么顶层模块的输入输出接口如何呢?
首先输入要有时钟复位信号,还要有一个来接收指令存储器里的数据记为rom_data_i
输出因为要去读取指令存储器中的数据,所以要输出读地址以及一个使能信号。
具体实现就参照我们上一节所做好的数据通路进行连线,连接和数据通路图如下:CPU设计实战-第一条指令ori的实现即最简单的五级流水线的实现
-
- module my_mips(
-
- input clk,
- input rst,
-
- input [31:0] rom_data_i,
-
- output [4:0] rom_addr_o,
- output rom_ce_o
-
- );
-
- wire [31:0] pc;
-
- //if/id输出与id模块输入模块的连接
- wire [31:0] id_pc_i;
- wire [31:0] id_inst_i;
- //ID模块的输出与ID/EX输入的连接
- wire [7:0] id_aluop_o ;
- wire [2:0] id_alusel_o;
- wire [31:0] id_reg1_o ;
- wire [31:0] id_reg2_o ;
- wire [4:0] id_wd_o ;
- wire id_wreg_o ;
- //ID/EX模块的输出与EX输入的连接
- wire [7:0] ex_aluop_i ;
- wire [2:0] ex_alusel_i;
- wire [31:0] ex_reg1_i ;
- wire [31:0] ex_reg2_i ;
- wire [4:0] ex_wd_i ;
- wire ex_wreg_i ;
- //EX模块的输出与EX/MEM输入的连接
- wire [31:0] ex_wdata_o ;
- wire [4:0] ex_wd_o ;
- wire ex_wreg_o ;
- //EX/MEM模块的输出与MEM输入的连接
- wire [31:0] mem_wdata_i;
- wire [4:0] mem_wd_i ;
- wire mem_wreg_i ;
- //MEM模块的输出与MEM/WB输入的连接
- wire [31:0] mem_wdata_o;
- wire [4:0] mem_wd_o ;
- wire mem_wreg_o ;
- //MEM/WB模块输出与regfile寄存器堆的连接
- wire [31:0] wb_wdata_i;
- wire [4:0] wb_wd_i ;
- wire wb_reg_i ;
- //ID译码模块与寄存器堆模块的连接
- wire [4:0] reg1_addr ;
- wire [4:0] reg2_addr ;
- wire reg1_read ;
- wire reg2_read ;
- wire [31:0] reg1_data ;
- wire [31:0] reg2_data ;
-
- pc_reg u_pc_reg (
- .clk ( clk ),
- .rst ( rst ),
-
- .pc ( pc ),
- .ce ( rom_ce_o )
- );
-
- //输出到指令存储器的地址就是pc地址
- assign rom_addr_o = pc;
-
- if_id u_if_id (
- .clk ( clk ),
- .rst ( rst ),
- .if_pc ( pc),
- .if_inst ( rom_data_i ),
-
- .id_pc ( id_pc_i ),
- .id_inst ( id_inst_i)
- );
-
- id u_id (
- .clk ( clk ),
- .rst ( rst ),
- .pc_i ( id_pc_i ),
- .inst_i ( id_inst_i),
- .reg1_data_i ( reg1_data ),
- .reg2_data_i ( reg2_data ),
-
- .reg1_addr_o ( reg1_addr ),
- .reg2_addr_o ( reg2_addr ),
- .reg1_read_o ( reg1_read ),
- .reg2_read_o ( reg2_read ),
- .aluop_o ( id_aluop_o ),
- .alusel_o ( id_alusel_o ),
- .reg1_o ( id_reg1_o ),
- .reg2_o ( id_reg2_o ),
- .wd_o ( id_wd_o ),
- .wreg_o ( id_wreg_o )
- );
-
- id_ex u_id_ex (
- .clk ( clk ),
- .rst ( rst ),
- .id_aluop ( id_aluop_o ),
- .id_alusel ( id_alusel_o ),
- .id_reg1 ( id_reg1_o ),
- .id_reg2 ( id_reg2_o ),
- .id_wd ( id_wd_o ),
- .id_wreg ( id_wreg_o ),
-
- .ex_aluop ( ex_aluop_i ),
- .ex_alusel ( ex_alusel_i ),
- .ex_reg1 ( ex_reg1_i ),
- .ex_reg2 ( ex_reg2_i ),
- .ex_wd ( ex_wd_i ),
- .ex_wreg ( ex_wreg_i )
- );
-
- ex u_ex (
- .rst ( rst ),
- .aluop_i ( ex_aluop_i ),
- .alusel_i ( ex_alusel_i ),
- .reg1_i ( ex_reg1_i ),
- .reg2_i ( ex_reg2_i ),
- .wd_i ( ex_wd_i ),
- .wreg_i ( ex_wreg_i ),
-
- .wdata_o ( ex_wdata_o ),
- .wd_o ( ex_wd_o ),
- .wreg_o ( ex_wreg_o )
- );
-
- ex_mem u_ex_mem (
- .clk ( clk ),
- .rst ( rst ),
- .ex_wdata ( ex_wdata_o ),
- .ex_wd ( ex_wd_o ),
- .ex_wreg ( ex_wreg_o ),
-
- .mem_wdata ( mem_wdata_i ),
- .mem_wd ( mem_wd_i ),
- .mem_wreg ( mem_wreg_i )
- );
-
- mem u_mem (
- .rst ( rst ),
- .wdata_i ( mem_wdata_i ),
- .wd_i ( mem_wd_i ),
- .wreg_i ( mem_wreg_i ),
-
- .wdata_o ( mem_wdata_o ),
- .wd_o ( mem_wd_o ),
- .wreg_o ( mem_wreg_o )
- );
-
- mem_wb u_mem_wb (
- .clk ( clk ),
- .rst ( rst ),
- .mem_wdata ( mem_wdata_o ),
- .mem_wd ( mem_wd_o ),
- .mem_wreg ( mem_wreg_o ),
-
- .wb_wdata ( wb_wdata_i ),
- .wb_wd ( wb_wd_i ),
- .wb_reg ( wb_reg_i )
- );
-
- regfile u_regfile (
- .clk ( clk ),
- .rst ( rst ),
- .raddr1 ( reg1_addr ),
- .re1 ( reg1_read ),
- .raddr2 ( reg2_addr ),
- .re2 ( reg2_read ),
- .waddr ( wb_wd_i ),
- .wdata ( wb_wdata_i ),
- .we ( wb_reg_i ),
-
- .rdata1 ( reg1_data ),
- .rdata2 ( reg2_data )
- );
-
- endmodule

之前说过我们的my_mips模块的输入输出都需要从指令存储器进行关联。
指令存储器中存储的指令码,需要进行的操作就是对其按照指令地址进行读取指令码
故输入指令地址addr,输出指令码inst,另外需要一个使能信号,如下图:
具体实现过程注意以下几点:
1.首先要定义这个rom的大小,方法和实现寄存器堆一样,本质上是一个数组寄存器堆实现
2.然后要把指令写入这个rom,这里采用系统函数¥readmemh,它可以将指定的文件中的数据写入rom中
3.最后进行读操作时需要注意,指令存储器的寻址方式是字节型,而我们pc端口模块给出的地址是32位的,所以需要除以4再写入地址,具体的操作用左移两位来实现的。那么既然要移位地址肯定要知道地址的宽度,需要知道地址宽度如何计算。
2^地址宽度=元素大小,根据define文件发现定义的元素大小为131071即128k,那么地址宽度为17
- `include "defines.v"
-
- module inst_rom(
- input ce,
- input [4:0] addr,
- output reg [31:0] inst
- );
-
- reg[31:0] inst_mem[0:`InstMemNum-1];
-
- initial $readmemh ("inst_rom.data", inst_mem);
-
- always @(*) begin
- if (!ce) begin
- inst = 0;
- end
- else begin
- inst = inst_mem[addr[`InstMemNumLog2+1:2]];//左移两位实现除以4
- end
- end
-
-
- endmodule

- inst_rom.data
-
- 34011100
- 34210020
- 3421ff00
- 342100ff
分析一下data文件中指令数据,其是按照十六进制存储的,我们展开一个第一个来看看
001101 | 00000 | 00001 | 0001 0001 0000 0000 |
ORI指令的op | rs | rt | imm立即数 |
回顾ORI指令的功能
指令用法为: ori rs, rt, immediate,作用是将指令中的16位立即数immediate进行无符号
扩展至32位,然后与索引为rs的通用寄存器的值进行逻辑“或”运算,运算结果保存到索引
为rt的通用寄存器中。
可以明白第一行指令的操作是将0号寄存器中的值和立即数进行逻辑或运算,由于0号寄存器中的值恒为0,故结果还是原来的imm立即数,并将这个结果保存到1号寄存器中。剩余指令以此类推
有了cpu和指令存储器,我们大概可以搭建一个最简单的SOPC,将两个模块连接如下:
最小SOPC对应的模块为mymips_min_sopc,其输入只有时钟和复位信号,然后按照上图进行例化即可。
-
-
- module mymips_min_sopc(
-
- input clk,
- input rst
- );
-
- wire rom_ce;
- wire [31:0] inst;
- wire [4:0] inst_addr;
-
- my_mips u_my_mips (
- .clk ( clk ),
- .rst ( rst ),
- .rom_data_i ( inst ),
-
- .rom_addr_o ( inst_addr ),
- .rom_ce_o ( rom_ce )
- );
-
- inst_rom u_inst_rom (
- .ce ( rom_ce ),
- .addr ( inst_addr ),
-
- .inst ( inst)
- );
-
- endmodule

读者可能还是会疑惑data文件里的数据是怎么得来的,其实是基于我们编写的测试文件inst_rom.S
此文件为汇编文件,用于测试ori指令
- ori $1,$0,0x1100
- ori $2,$0,0x0020
- ori $3,$0,0xff00
- ori $4,$0,0xffff
可以知道此代码的功能是将立即数与0号寄存器(恒为0)进行或运算故结果还是原来的立即数
按照正常顺序是通过编译器编译此文件得出指令寄存器中的二进制data文件,由于配置编译环境花费额外篇幅讲解,此处采用手动编译的方法。
例如第一行指令对应的二进制如下图:
转化为16进制为data文件中第一行的数据,其他直径以此类推,这就解释了data文件中的数据是怎么来的。
接下来就可以写testbech来测试我们的程序了,仿真文件很好写,对于sopc模块只需要给与时钟信号和复位信号即可,通过观察仿真文件里的寄存器的值即可知道指令运行是否正常。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。