(Verilog)单周期CPU设计

(Verilog)单周期CPU设计


首先是基础资料部分(借用学校资料):

一.实验内容

设计一个单周期CPU,该CPU至少能实现以下指令功能操作。需设计的指令与格式如下:

==> 算术运算指令

(1)add rd , rs, rt (说明:以助记符表示,是汇编指令;以代码表示,是机器指令)

000000 rs(5位) rt(5位) rd(5位) reserved

功能:rd←rs + rt。reserved为预留部分,即未用,一般填“0”。
(2)addi rt , rs ,immediate

000001 rs(5位) rt(5位) immediate(16位)

功能:rt←rs + (sign-extend)immediate;immediate符号扩展再参加“加”运算。
(3)sub rd , rs , rt

000010 rs(5位) rt(5位) rd(5位) reserved

完成功能:rd←rs - rt

==> 逻辑运算指令

(4)ori rt , rs ,immediate

010000 rs(5位) rt(5位) immediate(16位)

功能:rt←rs | (zero-extend)immediate;immediate做“0”扩展再参加“或”运算。
(5)and rd , rs , rt

010001 rs(5位) rt(5位) rd(5位) reserved

功能:rd←rs & rt;逻辑与运算。
(6)or rd , rs , rt

010010 rs(5位) rt(5位) rd(5位) reserved

功能:rd←rs | rt;逻辑或运算。

==> 传送指令

(7)move rd , rs

100000 rs(5位) 00000 rd(5位) reserved

功能:rd←rs + $0 ;$0=$zero=0。

==> 存储器读/写指令

(8)sw rt ,immediate(rs) 写存储器

100110 rs(5位) rt(5位) immediate(16位)

功能:memory[rs+ (sign-extend)immediate]←rt;immediate符号扩展再相加。
(9) lw rt , immediate(rs) 读存储器

100111 rs(5位) rt(5位) immediate(16位)

功能:rt ← memory[rs + (sign-extend)immediate];immediate符号扩展再相加。

==> 分支指令

(10)beq rs,rt,immediate

110000 rs(5位) rt(5位) immediate(位移量,16位)

功能:if(rs=rt) pc←pc + 4 + (sign-extend)immediate <<2;
特别说明:immediate是从PC+4地址开始和转移到的指令之间指令条数。immediate符号扩展之后左移2位再相加。为什么要左移2位?由于跳转到的指令地址肯定是4的倍数(每条指令占4个字节),最低两位是“00”,因此将immediate放进指令码中的时候,是右移了2位的,也就是以上说的“指令之间指令条数”。

==> 停机指令

(11)halt

111111 00000000000000000000000000(26位)

功能:停机;不改变PC的值,PC保持不变。


二.实验原理

单周期CPU指的是一条指令的执行在一个时钟周期内完成,然后开始下一条指令的执行,即一条指令用一个时钟周期完成。电平从低到高变化的瞬间称为时钟上升沿,两个相邻时钟上升沿之间的时间间隔称为一个时钟周期。
CPU在处理指令时,一般需要经过以下几个步骤:
(1) 取指令(IF):根据程序计数器PC中的指令地址,从存储器中取出一条指令,同时,PC根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入PC,当然得到的“地址”需要做些变换才送入PC。
(2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
(3) 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
(4) 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
(5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
单周期CPU,是在一个时钟周期内完成这五个阶段的处理。

这里写图片描述

图1 单周期CPU指令处理过程
MIPS32的指令的三种格式:
R类型:

31-26 25-21 20-16 15-11 10-6 5-0
op rs rt rd sa func
6位 5位 5位 5位 5位 6位

I类型:

31-26 25-21 20-16 15-0
op rs rt immediate
6位 5位 5位 16位

J类型:

31-26 25-0
op address
6位 26位

其中,
op:为操作码;
rs:为第1个源操作数寄存器,寄存器地址(编号)是00000~11111,00~1F;
rt:为第2个源操作数寄存器,或目的操作数寄存器,寄存器地址(同上);
rd:为目的操作数寄存器,寄存器地址(同上);
sa:为位移量(shift amt),移位指令用于指定移多少位;
func:为功能码,在寄存器类型指令中(R类型)用来指定指令的功能;
immediate:为16位立即数,用作无符号的逻辑操作数、有符号的算术操作数、数据加载(Laod)/数据保存(Store)指令的数据地址字节偏移量和分支指令中相对程序计数器(PC)的有符号偏移量;
address:为地址。
这里写图片描述
图2 单周期CPU数据通路和控制线路图

图2是一个简单的基本上能够在单周期上完成所要求设计的指令功能的数据通路和必要的控制线路图。其中指令和数据各存储在不同存储器中,即有指令存储器和数据存储器。访问存储器时,先给出地址,然后由读/写信号控制(1-写,0-读。当然,也可以由时钟信号控制,但必须在图上标出)。对于寄存器组,读操作时,先给出地址,输出端就直接输出相应数据;而在写操作时,在 WE使能信号为1时,在时钟边沿触发写入。图中控制信号作用如表1所示,表2是ALU运算功能表。

表1 控制信号的作用

控制信号名 状态“0” 状态“1”
PCWre PC不更改,相关指令:halt PC更改,相关指令:除指令halt外
ALUSrcB 来自寄存器堆data2输出,相关指令:add、sub、or、and、move、beq 来自sign或zero扩展的立即数,相关指令:addi、ori、sw、lw
ALUM2Reg 来自ALU运算结果的输出,相关指令:add、addi、sub、ori、or、and、move 来自数据存储器(Data MEM)的输出,相关指令:lw
RegWre 无写寄存器组寄存器,相关指令:sw、halt 寄存器组写使能,相关指令:add、addi、sub、ori、or、and、move、lw
InsMemRW 读指令存储器(Ins. Data),初始化为0 写指令存储器
DataMemRW 读数据存储器,相关指令:lw 写数据存储器,相关指令:sw
ExtSel 相关指令:ori,(zero-extend)immediate(0扩展) 相关指令:addi、sw、lw、beq,(sign-extend)immediate(符号扩展)
PCSrc PC←PC+4,相关指令:add、sub、ori、or、and、move、sw、lw、beq(zero=0) PC←PC+4+(sign-extend)immediate,同时zero=1,相关指令:beq
RegOut 写寄存器组寄存器的地址,来自rt字段,相关指令:addi、ori、lw 写寄存器组寄存器的地址,来自rd字段,相关指令:add、sub、and、or、move
ALUOp[2..0] ALU 8种运算功能选择(000-111),看功能表

相关部件及引脚说明:

  • Instruction Memory:指令存储器,
    • Iaddr,指令存储器地址输入端口
    • IDataIn,指令存储器数据输入端口(指令代码输入端口)
    • IDataOut,指令存储器数据输出端口(指令代码输出端口)
    • RW,指令存储器读写控制信号,为1写,为0读
  • Data Memory:数据存储器,
    • Daddr,数据存储器地址输入端口
    • DataIn,数据存储器数据输入端口
    • DataOut,数据存储器数据输出端口
    • RW,数据存储器读写控制信号,为1写,为0读
  • Register File:(寄存器组)
    • Read Reg1,rs寄存器地址输入端口
    • Read Reg2,rt寄存器地址输入端口
    • Write Reg,将数据写入的寄存器端口,其地址来源rt或rd字段
    • Write Data,写入寄存器的数据输入端口
    • Read Data1,rs寄存器数据输出端口
    • Read Data2,rt寄存器数据输出端口
    • WE,写使能信号,为1时,在时钟上升沿写入
  • ALU:
    • result,ALU运算结果
    • zero,运算结果标志,结果为0输出1,否则输出0

表2 ALU运算功能表

ALUOp[2..0] 功能 描述
000 A + B
001 A – B
010 B – A
011 A ∨ B
100 A ∧ B
101 /A ∧ B A非与B
110 A ⊕ B 异或
111 A ⊙ B 同或

需要说明的是根据要实现的指令功能要求画出以上数据通路图,和确定ALU的运算功能(当然,以上指令没有完全用到提供的ALU所有功能,但至少必须能实现以上指令功能操作)。从数据通路图上可以看出控制单元部分需要产生各种控制信号,当然,也有些信号必须要传送给控制单元。从指令功能要求和数据通路图的关系得出以上表1,这样,从表1可以看出各控制信号与相应指令之间的相互关系,根据这种关系就可以得出控制信号与指令之间的关系表(留给学生完成),再根据关系表可以写出各控制信号的逻辑表达式,这样控制单元部分就可实现了。
指令执行的结果总是在下个时钟到来前开始保存到寄存器、或存储器中,PC的改变也是在这个时候进行。另外,值得注意的问题,设计时,用模块化的思想方法设计,关于ALU设计、存储器设计、寄存器组设计等等,也是必须认真考虑的问题。可以参考其他资料文档,里面有相应的设计方法介绍。(资料文档:MIPS汇编与单周期CPU.ppt)。


前提条件搞定,正文来了:

先看数据通路图:
这里写图片描述

我遇到的一些基本问题:

  1. wire和reg是什么意思? —— 在verilog里面,变量有wire和reg两种类型,wire类型意为线,它不可储存中间结果,通过输入得出输出,类似纯函数,只要输入变化,输出立即变化,如果没有输入,自然就没有输出。reg类型意为寄存器,它可以赋初值,可以储存中间结果,只有当满足某种条件时(比如时钟上升沿),它才会变化,其他时间会一直保持最后一次变化的值。
  2. 指令怎么来? —— 在IM组件和RW组件分别开两个寄存器数组,用来模拟指令内存和数据内存,通过文件读取,从test.txt(test文件夹中)读指令到IM的指令内存(从0开始),IM组件通过输入的IAddr(数组地址下标),得到相应的指令。
  3. 指令怎么变化? —— 在PC端,有两个外部输入:CLK和Reset。其中PC内部有指令寄存器,每次CLK上升沿触发后,会改成新的指令,同时,当Reset=1时,指令寄存器也会置0。
  4. 模块和模块间怎么连接? —— 此时,需要一个顶层模块,相当于main函数,它会根据数据通路图,将一些下层模块的输出,连接到另一些下层模块的输入中。
  5. 写好的cpu怎么运行? —— 需要在顶层模块再添加一个测试文件,测试文件提供外部输入(CLK和Reset),然后模块就会自动运行得到相应的仿真结果。

实现思路:

每一个组件都能写成一个下层模块,实现相应的功能,并保证输入变化后输出跟着变化(不发生延迟)。
顶层模块调用各个下层模块,根据数据通路图将模块之间连线,保证PC指令改变后,其他所有的模块都根据控制信号,发生对应的变化。
测试模块控制CLK和Reset信号,控制PC指令的改变。


具体代码:

(懒得看的可以去我的github下载:链接点我点我点我)

每个组件都能写成下层模块,下面把每个组件都写成模块:

1. PC:CLK上升沿触发,更改指令地址
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date:    23:43:40 05/02/2017 
// Design Name: 
// Module Name:    PC 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module PC(
    input CLK,                         // 时钟
    input Reset,                       // 重置信号
    input PCWre,                       // PC是否更改,如果为0,PC不更改
    input [31:0] newAddress,           // 新指令
    output reg[31:0] currentAddress    // 当前指令
    );

    initial begin
        currentAddress <= 0;  // 非阻塞赋值
    end

    always@(posedge CLK or posedge Reset)
     begin
        if (Reset == 1)  currentAddress <= 0;  // 如果重置,赋值为0
        else 
         begin
            if (PCWre)  currentAddress <= newAddress;
            else  currentAddress <= currentAddress;
         end
     end

endmodule

输入:CLK,Reset,PCWre,newAddress
输出:currentAddress
解释:由于指令地址存储在寄存器里,一开始需要赋currentAddress为0。Reset是重置信号,当为1时,指令寄存器地址重置。PCWre的作用为保留现场,如果PCWre为0,指令地址不变。

2. InstructionMemory:储存指令,分割指令
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date:    00:10:27 05/03/2017 
// Design Name: 
// Module Name:    IM 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module InstructionMemory(
     input InsMemRW,            // 读写控制信号,1为写,0位读
    input [31:0] IAddr,        // 指令地址输入入口
     //input IDataIn,             // 没用到 

    output [5:0] op,
    output [4:0] rs,
    output [4:0] rt,
    output [4:0] rd,
    output [15:0] immediate    // 指令代码分时段输出
    );

    reg[7:0] mem[0:63];  // 新建一个32位的数组用于储存指令

    initial 
     begin
        $readmemb("test/test.txt", mem);  //读取测试文档中的指令
     end

    // 从地址取值,然后输出
    assign op = mem[IAddr][7:2];
    assign rs[4:3] = mem[IAddr][1:0];
    assign rs[2:0] = mem[IAddr + 1][7:5];
    assign rt = mem[IAddr + 1][4:0];
    assign rd = mem[IAddr + 2][7:3];
    assign immediate[15:8] = mem[IAddr + 2];
    assign immediate[7:0] = mem[IAddr + 3];

endmodule

输入:InsMenRW,IAddr
输出:op,rs,rt,rd,immediate
解释:该部分为指令寄存器,通过一个64大小的8位寄存器数组来保存从文件输入的全部指令。然后通过输入的地址,找到相应的指令,并分割成op,rs,rt,rd,immediate输出。(由于寄存器地址+4,所以不用右移变换成真正的地址)

3.RegisterFile:储存寄存器组,并根据地址对寄存器组进行读写
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date:    01:07:13 05/03/2017 
// Design Name: 
// Module Name:    RF 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module RegisterFile(
     input CLK,                 // 时钟
     input RegWre,              // 写使能信号,为1时,在时钟上升沿写入
    input [4:0] rs,            // rs寄存器地址输入端口
    input [4:0] rt,            // rt寄存器地址输入端口
    input [4:0] WriteReg,      // 将数据写入的寄存器端口,其地址来源rt或rd字段
    input [31:0] WriteData,    // 写入寄存器的数据输入端口
     output [31:0] ReadData1,   // rs寄存器数据输出端口
    output [31:0] ReadData2    // rt寄存器数据输出端口
    );


    reg [31:0] register[0:15];  // 新建16个寄存器,用于操作
    // 初始时,将32个寄存器全部赋值为0
    integer i;
    initial 
     begin
        for(i = 0; i < 16; i = i + 1)  register[i] <= 0;
     end

    // 读寄存器
    assign ReadData1 = register[rs];
    assign ReadData2 = register[rt];

    // 写寄存器
    always@(negedge CLK)
     begin
        // 如果寄存器不为0,并且RegWre为真,写入数据
        if (RegWre && WriteReg != 0)  register[WriteReg] = WriteData;
     end 

endmodule

输入:CLK,RegWre,rs,rt,WriteReg,WriteData
输出:ReadData1,ReadData2
解释:该部分为寄存器读写单元,RegWre的作用是控制寄存器是否写入。同上,通过一个16大小的32位寄存器数组来模拟寄存器,开始时全部置0。通过访问寄存器的地址,来获取寄存器里面的值,并进行操作。(PS:由于$0恒为0,所以写入寄存器的地址不能为0)

4.ALU(算术逻辑单元):用于逻辑指令计算和跳转指令比较
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date:    01:54:18 05/03/2017 
// Design Name: 
// Module Name:    ALU 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module ALU(
     input [2:0] ALUOp,           // ALU操作控制
    input [31:0] A,              // 输入1
    input [31:0] B,              // 输入2
    output reg zero,             // 运算结果result的标志,result为0输出1,否则输出0
    output reg [31:0] result     // ALU运算结果
    );

    // 进行ALU计算
    always@(*)
     begin
        // 进行运算
        case (ALUOp)
            3'b000 : result = A + B;  // 加法
            3'b001 : result = A - B;  // 减法
            3'b010 : result = B - A;  // 减法
            3'b011 : result = A | B;  // 或
            3'b100 : result = A & B;  // 与
            3'b101 : result = ~A & B;  // A非与B
            3'b110 : result = A ^ B;  // 异或
            3'b111 : result = ~A ^ B;  // 同或
            default : result = 0;
        endcase
        // 设置zero
        if (result)  zero = 0;
        else  zero = 1;
     end

endmodule

输入:ALUOp,A,B
输出:zero,result
解释:ALUOp用于控制算数的类型,AB为输入数,result为运算结果,zero主要用于beq和bne指令的判断。

5.SignZeroExtend:用于immediate的扩展
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date:    00:58:20 05/03/2017 
// Design Name: 
// Module Name:    EX 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module SignZeroExtend(
    input ExtSel,                  // 控制补位,如果为1,进行符号扩展,如果为0,全补0
    input [15:0] immediate,        // 16位立即数
    output [31:0] extendImmediate   // 输出的32位立即数
    );

    // 进行扩展
    assign extendImmediate[15:0] = immediate;
    assign extendImmediate[31:16] = ExtSel ? (immediate[15] ? 16'hffff : 16'h0000) : 16'h0000;

endmodule

输入:ExtSel,immediate
输出:extendImmediate
解释:比较简单的一个模块。ExtSel为控制补位信号。判断后,将extendImmediate的前16位全补1或0即可。

6.DataMemory:用于内存存储,内存读写
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date:    01:37:40 05/03/2017 
// Design Name: 
// Module Name:    DM 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module DataMemory(
     input DataMemRW,            // 数据存储器读写控制信号,为1写,为0读
    input [31:0] DAddr,         // 数据存储器地址输入端口
    input [31:0] DataIn,        // 数据存储器数据输入端口
    output reg [31:0] DataOut   // 数据存储器数据输出端口
    );

    // 模拟内存,以8位为一字节存储,共64字节
    reg [7:0] memory[0:63];

    // 初始赋值
    integer i;
    initial
     begin
        for (i = 0; i < 64; i = i + 1)  memory[i] <= 0;
     end

    // 读写内存
    always@(DAddr)
     begin

     end

    always@(DAddr or DataIn)
     begin
        // 写内存
        if (DataMemRW)
         begin
           memory[DAddr] <= DataIn[31:24];
            memory[DAddr + 1] <= DataIn[23:16];
            memory[DAddr + 2] <= DataIn[15:8];
            memory[DAddr + 3] <= DataIn[7:0];
         end
        // 读内存
        else
         begin
            DataOut[31:24] <= memory[DAddr];
            DataOut[23:16] <= memory[DAddr + 1];
            DataOut[15:8] <= memory[DAddr + 2];
            DataOut[7:0] <= memory[DAddr + 3];
         end
     end

endmodule

输入:DataMenRW,DAddr,DataIn
输出:DataOut
解释:该部分控制内存存储。同上,用64大小的8位寄存器数组模拟内存(内存小主要是因为编译快),内存部分采用小端模式。DataMenRW控制内存读写。由于指令为真实地址,所以不需要*4。

7.Multiplexer:5线和32线二路选择器
 module Multiplexer5(
     input control,
    input [4:0] in1,
    input [4:0] in0,
    output [4:0] out
    );

    // 5线多路选择器
    assign out = control ? in1 : in0;

endmodule
module Multiplexer32(
     input control,
    input [31:0] in1,
    input [31:0] in0,
    output [31:0] out
    );

    // 32线多路选择器
    assign out = control ? in1 : in0;

endmodule

输入:control,in1,in0
输出:out
解释:多路选择器,不用过多解释。

8.最重要的ControlUnit:控制信号模块,通过解析op得到该指令的各种控制信号

首先,需要得到控制信号表:
这里写图片描述

通过信号控制表,可以很轻松的写控制模块。

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date:    02:11:08 05/03/2017 
// Design Name: 
// Module Name:    CU 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module ControlUnit(
    input [5:0] op,         // op操作符
    input zero,             // ALU的zero输出

     // 一堆控制信号
     output reg PCSrc,           // 多路选择器
    output reg PCWre,           // (PC)PC是否更改,如果为0,PC不更改
    output reg ALUSrcB,         // 多路选择器
    output reg ALUM2Reg,        // 多路选择器
    output reg RegWre,          // (RF)写使能信号,为1时,在时钟上升沿写入
    output reg InsMemRW,        // (IM)读写控制信号,1为写,0位读
    output reg DataMemRW,       // (DM)数据存储器读写控制信号,为1写,为0读
    output reg ExtSel,          // (EXT)控制补位,如果为1,进行符号扩展,如果为0,全补0
    output reg RegOut,          // 多路选择器
    output reg [2:0] ALUOp      // (ALU)ALU操作控制 
    );

    // 进行各种赋值
    initial 
     begin
        ExtSel = 0;
        PCWre = 1;
        InsMemRW = 1;
        RegOut = 1;
        RegWre = 0;
        ALUOp = 0;
        PCSrc = 0;
        ALUSrcB = 0;
        DataMemRW = 0;
        ALUM2Reg = 0;
    end

    always@(op or zero)
    begin  
      case(op) 
            // add
            6'b000000:
          begin   //以下都是控制单元产生的控制信号
                PCWre = 1;
                ALUSrcB = 0;
                ALUM2Reg = 0;
                RegWre = 1;
                InsMemRW = 1;
                DataMemRW = 0;
                ExtSel = 0;
                PCSrc = 0;
                RegOut = 1;
                ALUOp = 000;
             end
            // addi
            6'b000001:
          begin   //以下都是控制单元产生的控制信号
                PCWre = 1;
                ALUSrcB = 1;
                ALUM2Reg = 0;
                RegWre = 1;
                InsMemRW = 1;
                DataMemRW = 0;
                ExtSel = 1;
                PCSrc = 0;
                RegOut = 0;
                ALUOp = 000;
             end
            // sub
            6'b000010:
          begin   //以下都是控制单元产生的控制信号
                PCWre = 1;
                ALUSrcB = 0;
                ALUM2Reg = 0;
                RegWre = 1;
                InsMemRW = 1;
                DataMemRW = 0;
                ExtSel = 0;
                PCSrc = 0;
                RegOut = 1;
                ALUOp = 001;
             end
            // ori
            6'b010000:
             begin   //以下都是控制单元产生的控制信号
                PCWre = 1;
                ALUSrcB = 1;
                ALUM2Reg = 0;
                RegWre = 1;
                InsMemRW = 1;
                DataMemRW = 0;
                ExtSel = 0;
                PCSrc = 0;
                RegOut = 0;
                ALUOp = 011;
          end
           // and
         6'b010001:
          begin   //以下都是控制单元产生的控制信号
                PCWre = 1;
                ALUSrcB = 0;
                ALUM2Reg = 0;
                RegWre = 1;
                InsMemRW = 1;
                DataMemRW = 0;
                ExtSel = 0;
                PCSrc = 0;
                RegOut = 1;
                ALUOp = 100;
          end
            // or
            6'b010010:
          begin   //以下都是控制单元产生的控制信号
                PCWre = 1;
                ALUSrcB = 0;
                ALUM2Reg = 0;
                RegWre = 1;
                InsMemRW = 1;
                DataMemRW = 0;
                ExtSel = 0;
                PCSrc = 0;
                RegOut = 1;
                ALUOp = 011;
             end
           // move
         6'b100000:
          begin   //以下都是控制单元产生的控制信号
                PCWre = 1;
                ALUSrcB = 0;
                ALUM2Reg = 0;
                RegWre = 1;
                InsMemRW = 1;
                DataMemRW = 0;
                ExtSel = 0;
                PCSrc = 0;
                RegOut = 1;
                ALUOp = 000;
          end
            // sw
            6'b100110:
          begin   //以下都是控制单元产生的控制信号
                PCWre = 1;
                ALUSrcB = 1;
                ALUM2Reg = 0;
                RegWre = 0;
                InsMemRW = 1;
                DataMemRW = 1;
                ExtSel = 1;
                PCSrc = 0;
                RegOut = 0;
                ALUOp = 000;
          end
         // lw
            6'b100111:
          begin   //以下都是控制单元产生的控制信号
                PCWre = 1;
                ALUSrcB = 1;
                ALUM2Reg = 1;
                RegWre = 1;
                InsMemRW = 1;
                DataMemRW = 0;
                ExtSel = 1;
                PCSrc = 0;
                RegOut = 0;
                ALUOp = 000;
          end
         // beq
           6'b110000:
          begin   //以下都是控制单元产生的控制信号
                if (zero) begin
                    PCSrc = 1;
                end else begin
                    PCSrc = 0;
                end
            ALUM2Reg = 0;
                PCWre = 1;
                ALUSrcB = 0;
                RegWre = 0;
                InsMemRW = 1;
                DataMemRW = 0;
                ExtSel = 1;
                RegOut = 0;
                ALUOp = 001;
          end
           // halt
           6'b111111:
             begin   //以下都是控制单元产生的控制信号
                PCWre = 0;
                ALUSrcB = 0;
                ALUM2Reg = 0;
                RegWre = 0;
                InsMemRW = 0;
                DataMemRW = 0;
                ExtSel = 0;
                PCSrc = 0;
                RegOut = 0;
                ALUOp = 000;
             end
        endcase
     end

endmodule

输入:op,zero
输出:各类控制信号
解释:通过上表,可以将每个指令case到相应的控制信号上。


然后,通过顶层模块,调用下层模块并将它们输入输出连在一起:

SingleCPU:顶层连接模块
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date:    23:43:17 05/02/2017 
// Design Name: 
// Module Name:    SingleCPU 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module SingleCPU(
    input CLK,
    input Reset,
    output [5:0] op,
     output [4:0] rs,
     output [4:0] rt,
     output [4:0] rd,
     output [15:0] immediate,
    output [31:0] ReadData1,
    output [31:0] ReadData2,
     output [31:0] WriteData,
     output [31:0] DataOut,
    output [31:0] currentAddress,
    output [31:0] result
    );

    // 各种临时变量
    wire [2:0] ALUOp; 
   wire [31:0] B, newAddress;
   wire [31:0] currentAddress_4, extendImmediate, currentAddress_immediate;   
   wire [4:0] WriteReg;  
   wire zero, PCSrc, PCWre, ALUSrcB, ALUM2Reg, RegWre, InsMemRW, DataMemRW, ExtSel, RegOut;


    /*module ControlUnit(
    input [5:0] op,         // op操作符
    input zero,             // ALU的zero输出
     // 一堆控制信号
     output PCSrc,           // 多路选择器
    output PCWre,           // (PC)PC是否更改,如果为0,PC不更改
    output ALUSrcB,         // 多路选择器
    output ALUM2Reg,        // 多路选择器
    output RegWre,          // (RF)写使能信号,为1时,在时钟上升沿写入
    output InsMemRW,        // (IM)读写控制信号,1为写,0位读
    output DataMemRW,       // (DM)数据存储器读写控制信号,为1写,为0读
    output ExtSel,          // (EXT)控制补位,如果为1,进行符号扩展,如果为0,全补0
    output RegOut,          // 多路选择器
    output [2:0] ALUOp      // (ALU)ALU操作控制 
    );*/
    ControlUnit cu(op, zero, PCSrc, PCWre, ALUSrcB, ALUM2Reg, RegWre, InsMemRW, DataMemRW, ExtSel, RegOut, ALUOp);

    /*module PC(
    input CLK,                         // 时钟
    input Reset,                       // 重置信号
    input PCWre,                       // PC是否更改,如果为0,PC不更改
    input [31:0] newAddress,           // 新指令
    output reg[31:0] currentAddress    // 当前指令
    );*/
    PC pc(CLK, Reset, PCWre, newAddress, currentAddress);

    /*module InstructionMemory(
     input InsMemRW,            // 读写控制信号,1为写,0位读
    input [31:0] IAddr,        // 指令地址输入入口
     //input IDataIn,             // 没用到 
    output [5:0] op,
    output [4:0] rs,
    output [4:0] rt,
    output [4:0] rd,
    output [15:0] immediate    // 指令代码分时段输出
    );*/
    InstructionMemory im(InsMemRW, currentAddress, op, rs, rt, rd, immediate);

    /*module RegisterFile(
     input CLK,                 // 时钟
     input RegWre,              // 写使能信号,为1时,在时钟上升沿写入
    input [4:0] rs,            // rs寄存器地址输入端口
    input [4:0] rt,            // rt寄存器地址输入端口
    input [4:0] WriteReg,      // 将数据写入的寄存器端口,其地址来源rt或rd字段
    input [31:0] WriteData,    // 写入寄存器的数据输入端口
    output [31:0] ReadData1,   // rs寄存器数据输出端口
    output [31:0] ReadData2    // rt寄存器数据输出端口
    );*/
    RegisterFile rf(CLK, RegWre, rs, rt, WriteReg, WriteData, ReadData1, ReadData2);

    /*module ALU(
     input [2:0] ALUOp,       // ALU操作控制
    input [31:0] A,          // 输入1
    input [31:0] B,          // 输入2
    output reg zero,             // 运算结果result的标志,result为0输出1,否则输出0
    output reg [31:0] result     // ALU运算结果
    );*/
    ALU alu(ALUOp, ReadData1, B, zero, result);

    /*module SignZeroExtend(
    input ExtSel,                  // 控制补位,如果为1,进行符号扩展,如果为0,全补0
    input [15:0] immediate,        // 16位立即数
    input [31:0] extendImmediate   // 输出的32位立即数
    );*/
    SignZeroExtend sze(ExtSel, immediate, extendImmediate);

    /*module DataMemory(
     input DataMemRW,            // 数据存储器读写控制信号,为1写,为0读
    input [31:0] DAddr,         // 数据存储器地址输入端口
    input [31:0] DataIn,        // 数据存储器数据输入端口
    output reg [31:0] DataOut   // 数据存储器数据输出端口
    );*/
    DataMemory dm(DataMemRW, result, ReadData2, DataOut);

    assign currentAddress_4 = currentAddress + 4;
    assign currentAddress_immediate = currentAddress_4 + (extendImmediate << 2);
    //ADD add1(currentAddress, 32'h00000004, currentAddress_4);
    //ADD add1(currentAddress_4, extendImmediate << 2, currentAddress_immediate);

    Multiplexer5 m5(RegOut, rd, rt, WriteReg);

    Multiplexer32 m321(ALUSrcB, extendImmediate, ReadData2, B);
    Multiplexer32 m322(ALUM2Reg, DataOut, result, WriteData);
    Multiplexer32 m323(PCSrc, currentAddress_immediate, currentAddress_4, newAddress);

endmodule

(PS:该模块只需要输入输出正确。)


最后,加入测试单元,即可进行仿真模拟:

    // 部分测试单元代码
    initial begin
        CLK = 0;
        Reset = 1;

        // Wait 50 ns for global reset to finish
        #50; // 刚开始设置pc为0
         CLK = !CLK;  // 下降沿,使PC先清零
      #50;
         Reset = 0;  // 清除保持信号
      forever #50
         begin // 产生时钟信号,周期为50s
         CLK = !CLK;
       end
    end

我使用的测试指令如下:

//addi $1, $0, 4
000001 00000 00001 0000000000000100
//addi $2, $0, 8
000001 00000 00010 0000000000001000
//sw $2, 0($2)
100110 00010 00010 0000000000000000
//add $3, $2, $1
000000 00010 00001 00011 00000000000
//sub $3, $3, $1
000010 00011 00001 00011 00000000000
//beq $2, $3, -2
110000 00010 00011 1111111111111110
//ori $1, $1, 1
010000 00001 00001 0000000000000001
//or $3, $2, $1
010010 00010 00001 00011 00000000000
//move $3, $2
100000 00010 00000 00011 00000000000
//and $1, $3, $2
010001 00011 00010 00001 00000000000
//lw $4, 0($2)
100111 00010 00100 0000000000000000
//halt 
111111 00000000000000000000000000
00000100
00000001
00000000
00000100
00000100
00000010
00000000
00001000
10011000
01000010
00000000
00000000
00000000
01000001
00011000
00000000
00001000
01100001
00011000
00000000
11000000
01000011
11111111
11111110
01000000
00100001
00000000
00000001
01001000
01000001
00011000
00000000
10000000
01000000
00011000
00000000
01000100
01100010
00001000
00000000
10011100
01000100
00000000
00000000
11111100
00000000
00000000
00000000

测试结果如下:

这里写图片描述

可以看出,结果还是符合预期的。


总结:

其实自己做了一遍后,发现,并不是很难。。。感觉代码部分只要结构严谨,语法规范,并没有造成多大的麻烦,还有,最最关键的一点,判断==是不靠谱的,很容易产生竞争冒险的问题。

展开阅读全文

没有更多推荐了,返回首页