中山大学16级计算机组成与设计实验——单周期CPU设计与实现


实验二 : 单周期CPU设计与实现

一.相关原理详解

关于单周期CPU的设计思路和相关原理分析可以参看个人的另一篇博客,博客链接如下:
单周期CPU设计与实现原理分析

二.实验目的

  1. 掌握单周期CPU数据通路图的构成、原理及其设计方法;
  2. 掌握单周期CPU的实现方法,代码实现方法;
  3. 认识和掌握指令与CPU的关系;
  4. 掌握测试单周期CPU的方法;
  5. 掌握单周期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

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

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

(3)sub rd , rs , rt

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

功能:rd←rs - rt

==> 逻辑运算指令

(4)ori rt , rs ,immediate

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

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

(5)and rd , rs , rt

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

功能:rd←rs & rt; 逻辑与运算。

(6)or rd , rs , rt

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

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

==>移位指令

(7)sll rd, rt,sa

011000未用rt(5位)rd(5位)sareserved

功能:rd<-rt<<(zero-extend)sa, 左移sa位 ,(zero-extend)sa

==>比较指令

(8)slti rt, rs,immediate 带符号

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

功能:if (rs <(sign-extend)immediate) rt =1 else rt=0, 具体请看表2 ALU运算功能表,带符号

==> 存储器读/写指令

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

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

功能:memory[rs+ (sign-extend)immediate]←rt; immediate符号扩展再相加。即将rt寄存器的内容保存到rs寄存器内容和立即数符号扩展后的数相加作为地址的内存单元中。

(10) lw rt , immediate(rs) 读存储器

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

功能:rt ← memory[rs + (sign-extend)immediate]; immediate符号扩展再相加。即读取rs寄存器内容和立即数符号扩展后的数相加作为地址的内存单元中的数,然后保存到rt寄存器中。

==> 分支指令

(11)beq rs,rt,immediate

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

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

(12)bne rs,rt,immediate

110001rs(5位)rt(5位)immediate

功能:if(rs!=rt) pc←pc + 4 + (sign-extend)immediate <<2 else pc ←pc + 4
特别说明:与beq不同点是,不等时转移,相等时顺序执行。

==>跳转指令

(13)j addr

111000addr[27..2]

功能:pc <-{(pc+4)[31…28],addr[27…2],2{0}}, 无条件跳转。
特别说明:由于MIPS32的指令代码长度占4个字节,所以指令地址二进制数最低2位均为0,将指令地址放进指令代码中时,可省掉!这样,除了最高6位操作码外,还有26位可用于存放地址,事实上,可存放28位地址了,剩下最高4位由pc+4最高4位拼接上。

==> 停机指令

(14)halt

11111100000000000000000000000000(26位)

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

四.实验原理

单周期CPU指的是一条指令的执行在一个时钟周期内完成,然后开始下一条指令的执行,即一条指令用一个时钟周期完成。电平从低到高变化的瞬间称为时钟上升沿,两个相邻时钟上升沿之间的时间间隔称为一个时钟周期。时钟周期一般也称振荡周期(如果晶振的输出没有经过分频就直接作为CPU的工作时钟,则时钟周期就等于振荡周期。若振荡周期经二分频后形成时钟脉冲信号作为CPU的工作时钟,这样,时钟周期就是振荡周期的两倍。

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

单周期CPU指令处理过程

MIPS指令的三种格式

MIPS指令的三种格式
其中,
op:为操作码;
rs:只读。为第1个源操作数寄存器,寄存器地址(编号)是0000011111,001F;
rt:可读可写。为第2个源操作数寄存器,或目的操作数寄存器,寄存器地址(同上);
rd:只写。为目的操作数寄存器,寄存器地址(同上);
sa:为位移量(shift amt),移位指令用于指定移多少位;
funct:为功能码,在寄存器类型指令中(R类型)用来指定指令的功能与操作码配合使用;
immediate:为16位立即数,用作无符号的逻辑操作数、有符号的算术操作数、数据加载(Load)/数据保存(Store)指令的数据地址字节偏移量和分支指令中相对程序计数器(PC)的有符号偏移量;
address:为地址。
 单周期CPU数据通路和控制线路图
:

图2 单周期CPU数据通路和控制线路图

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

表1 控制信号的作用
控制信号名状态“0”状态“1”
Reset初始化PC为0PC接收新地址
PCWrePC不更改,相关指令:haltPC更改,相关指令:除指令halt外
ALUSrcA来自寄存器堆data1输出,相关指令:add、sub、addi、or、and、ori、beq、bne、slti、sw、lw 来自移位数sa,同时,进行(zero-extend)sa,即 {{27{0}},sa},相关指令:sll
ALUSrcB来自寄存器堆data2输出,相关指令:add、sub、or、and、sll、beq、bne来自sign或zero扩展的立即数,相关指令:addi、ori、slti、sw、lw
DBDataSrc来自ALU运算结果的输出,相关指令:add、addi、sub、ori、or、and、slti、sll来自数据存储器(Data MEM)的输出,相关指令:lw
RegWre无写寄存器组寄存器,相关指令:beq、bne、sw、halt、j寄存器组写使能,相关指令:add、addi、sub、ori、or、and、slti、sll、lw
InsMemRW写指令存储器读指令存储器(Ins. Data)
mRD输出高阻态读数据存储器,相关指令:lw
mWR无操作写数据存储器,相关指令:sw
RegDst写寄存器组寄存器的地址,来自rt字段,相关指令:addi、ori、lw、slti写寄存器组寄存器的地址,来自rd字段,相关指令:add、sub、and、or、sll
ExtSel(zero-extend)immediate(0扩展),相关指令:ori(sign-extend)immediate(符号扩展),相关指令:addi、slti、sw、lw、beq、bne
PCSrc[1..0]00:pc←pc+4,相关指令:add、addi、sub、or、ori、and、slti、sll、sw、lw、beq(zero=0)、bne(zero=1);
01:pc←pc+4+(sign-extend)immediate,相关指令:beq(zero=1)、bne(zero=0);
10:pc←{(pc+4)[31:28],addr[27:2],2{0}},相关指令:j;
11:未用
ALUOp[2..0]ALU 8种运算功能选择(000-111),看功能表

1.相关部件及引脚说明(根据个人设计有所修改):

Instruction Memory:指令存储器(单个模块)

Iaddr:指令存储器地址输入端口
IDataOut:指令存储器数据输出端口(指令代码输出端口)
RW:指令存储器读写控制信号,为0写,为1读

PC:程序计数器(两个模块)

①PC地址计数模块:
CLK: CPU时钟,当CLK上升沿到来时PC值可以发生变化,为输入端口
Reset:重置信号输入端口,Reset为0时重置当前PC地址为0,为1时正常运行
PCWre:判定PC地址是否需要发生变化,0时不更改,1时更改,为输入端口
newAddress:下一个指令地址,为输入端口
PCAddr:PC当前的指令地址,为输出端口

②指令跳转控制模块:
PCSrc:用于判断下一条指令地址的跳转方式(具体功能见上表),为输入端口
CurPC:PC模块输出的当前地址,为输入端口
Immediat:从I型指令中读取的16-bit位宽立即数,为输入端口
addr:从J型指令中读取的26-bit位宽地址偏移量,为输入端口
newAddress:通过PCSrc确定的跳转方式得到的下一个指令地址,为输出端口

Data Memory:数据存储器(单个模块)

DAddr:数据存储器地址输入端口
CLK:CPU时钟,当CLK的下降沿到来时才会将数据写入数据存储器中,为输入端口
mRD:数据存储器读控制信号,为0读
mWR:数据存储器写控制信号,为0写
DataIn:数据存储器数据输入端口
DataOut:数据存储器数据输出端口

Register File:寄存器组(三个模块)

①rt,rd寄存器地址选择输入模块:
Select:选择信号输入端口,R、I类型指令时用以选择传入rt或rd寄存器地址
DataIn1:rt寄存器地址,为输入端口
DataIn2:rd寄存器地址,为输入端口
DataOut:选择要写入数据的寄存器地址,为输出端口

②寄存器值输入模块:
Select:选择信号输入端口,调用lw指令时选择从数据存储器读取对应数据
DataIn1:ALU模块输出值,为输入端口
DataIn2:数据存储器内存储的对应位置的值,为输入端口
DataOut:选择要写入寄存器的值,为输出端口

③寄存器管理模块:
WE:写使能信号,为1时,在时钟边沿触发写入,为输入端口
CLK:CPU时钟,当CLK的下降沿到来时才会将数据写入寄存器组中,为输入端口
ReadReg1:rs寄存器地址输入端口
ReadReg2:rt寄存器地址输入端口
WriteReg:将数据写入的寄存器端口,其地址来源rt或rd字段,为输入端口 WriteData:寄存器的数据输入端口,数据来源ALU模块输出或者数据存储器取值
ReadData1:rs寄存器数据输出端口
ReadData2:rt寄存器数据输出端口

ALU:算术逻辑单元(三个模块)

①A操作值输入选择模块:
Select:选择信号输入端口,来自ControlUnit模块,用以确定A操作值
DataIn1:来自寄存器组输出的32-bit位宽的rs寄存器值,为输入端口
sa:来自指令存储器输出的sa值,用作ALU功能中左移的左移量,为输入端口
DataOut:选择输入ALU模块的A操作数的值,为输出端口

②B操作值输入选择模块:
Select:选择信号输入端口,来自ControlUnit模块,用以确定B操作值
DataIn1:来自寄存器组输出的32-bit位宽的rt寄存器值,为输入端口
DataIn2:来自位宽扩展单元输出的一个32-bit位宽的立即数扩展值,为输入端口
DataOut:选择输入ALU模块的B操作数的值,为输出端口

③ALU逻辑操作模块:
A:A操作数,来源A操作值输入选择模块输出,为输入端口
B:B操作数,来源B操作值输入选择模块输出,为输入端口
ALUOp:ALU算术逻辑单元的操作选择信号输入端,来自控制单元ControlUnit
result:ALU模块运算结果,为输出端口
zero:运算结果标志,结果为0,则zero=1;否则zero=0,为输出端口

Sign_Zero_Extend:数据扩展单元(单个模块)

Imm_Number:用作扩展操作的16-bit位宽立即数,为输入端口
ExtSel:扩展选择信号输入端,用作判断扩展方式为带符号扩展或者无符号扩展
Result:32-bit位宽的扩展结果,为输出端口

ControlUnit:算术逻辑单元(单个模块)

opCode:指令前六位的6-bit位宽的操作数,来自指令存储器,为输入端口
zero:ALU算术逻辑单元计算结果的零标志位,来自ALU,为输入端口
ExtSel,PCWre,InsMemRW,RegDst,RegWre,ALUOp,PCSrc,ALUSrcA,ALUSrcB,RD,WR,DBDataSrc:根据执行指令得到的CPU各模块的控制信号,具体功能见上表,为输出端口

表2 ALU运算功能表
ALUOp[2…0]功能描述
000Y = A + B
001Y = A – B
010Y = B << AB左移A位
011Y = A ∨ B
100Y = A ∧ B
101Y=(A < B)?1: 0比较A与B
不带符号
110Y=(((rega < regb) && (rega[31] == regb[31] )) || ( ( rega[31] ==1 && regb[31] == 0))) ? 1:0比较A与B
带符号
111Y = A⊕B异或
表3 指令信号表

指令信号表
从指令功能要求和数据通路图的关系得出以上表3,这样,从表3可以看出各控制信号与相应指令之间的相互关系,根据关系表可以写出各控制信号的逻辑表达式,这样控制单元部分就可实现了。

2.FPGA板的端口与功能设计:

在此次试验中Basys3板用作单周期CPU结果、PC值、rs,rt寄存器地址和数据的输出。输出内容是通过7端数码管实现。数码管通过使能端的高低电平控制亮和灭(Basys3为共阳,所以低电平有效)。通过控制4个7端数码管相对应使能端按顺序快速地高低变化,实现肉眼可见的同时输出结果。

其中,Basys板设置四个开关,SW15,SW14,T17与V17,其中SW15与SW14作为显示内容的选择,T17用作CPU按键输入,V17用作Reset重置信号输入。指令执行采用单步(按键控制)执行方式,由T17按键提供单个的CPU时钟周期,开关(SW15、SW14)控制选择查看数码管上的相关信息,地址和数据。

地址或数据的输出经以下模块代码转换后接到数码管上。其中:
SW_in = 00:显示 当前 PC值:下条指令PC值
SW_in = 01:显示 RS寄存器地址:RS寄存器数据
SW_in = 10:显示 RT寄存器地址:RT寄存器数据
SW_in = 11:显示 ALU结果输出 :DB总线数据。

五.实验器材

电脑一台,Xilinx Vivado 软件一套,Basys3板一块。

六.仿真代码实现

1.单周期CPU模块代码

PC.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 01:43:15
// 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, 
    input [31:0] newAddress,
    output reg [31:0] PCAddr
    );
    
    initial begin  
        PCAddr = 0;  
    end
    
    always @(posedge CLK or negedge Reset) begin  
        if (Reset == 0) begin  
            PCAddr = 0;  
        end  
        else if (PCWre) begin 
            PCAddr = newAddress;
        end  
    end     
    
endmodule

InstructionMemory.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 01:28:02
// Design Name: 
// Module Name: InstructionMemory
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module InstructionMemory(
    input [31:0] IAddr,
    input RW,
    output reg [31:0] IDataOut
    );
    
    reg [7:0] InstMemory [255:0];
    
    initial begin
        $readmemb("F:/test.txt", InstMemory);
    end

    always@(IAddr or RW) begin
        if (RW == 1) begin
            IDataOut = { InstMemory[IAddr], InstMemory[IAddr + 1], InstMemory[IAddr + 2], InstMemory[IAddr + 3] };
        end
        $display("InstMem PC", IAddr, " INST: ", IDataOut);
    end  
      
endmodule

RegisterFile.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 01:35:43
// Design Name: 
// Module Name: RegisterFile
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module RegisterFile(
    input WE,
    input CLK,
    input [4:0] ReadReg1, ReadReg2,
    input [4:0] WriteReg,
    input [31:0] WriteData,
    output [31:0] ReadData1, ReadData2
    );
    
    reg [31:0] registers[1:31];
    
    assign ReadData1 = ( ReadReg1 == 0 ) ? 0 : registers[ReadReg1];
    assign ReadData2 = ( ReadReg2 == 0 ) ? 0 : registers[ReadReg2];

    always@( negedge CLK ) begin // д²Ù×÷
        if (( WriteReg != 0 ) && ( WE == 1 )) begin
            $display("WriteData: ", WriteData, " WriteReg: ", WriteReg);
            registers[WriteReg] <= WriteData;
        end
    end
    
endmodule

Sign_Zero_Extend.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 01:34:37
// Design Name: 
// Module Name: Sign_Zero_Extend
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module Sign_Zero_Extend(
    input [15 :0] Imm_Number,
    input ExtSel,
    output reg [31:0] Result
    );
    
    always@( Imm_Number or ExtSel) begin
        if (ExtSel == 0 || Imm_Number[15] == 0)
            Result = { 16'b0000000000000000, Imm_Number };
        else
            Result = { 16'b1111111111111111, Imm_Number };
    end
    
endmodule

ALU.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 01:51:09
// Design Name: 
// Module Name: ALU
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module ALU(
    input [31:0] A,
    input [31:0] B,
    input [2:0] ALUOp,
    output reg [31:0] result,
    output zero
    );
    
    assign zero = ( result == 0 ) ? 1 : 0;
    
   always@( ALUOp or A or B ) begin
       case (ALUOp)
          3'b000 : result = A + B; // Y = A + B
          3'b001 : result = A - B; // Y = A - B
          3'b010 : result = B << A; // Y = B << A
          3'b011 : result = A | B; // Y = A | B
          3'b100 : result = A & B; // Y = A & B
          3'b101 : result = (A < B) ? 1 : 0; // Y=£¨A < B£©? 1 : 0
          3'b110 : result = (((A < B) && (A[31] == B[31])) || ((A[31] ==1 && B[31] == 0))) ? 1:0; // Y=(((A<B) && (A[31] == B[31] )) ||( ( A[31] ==1 && B[31] == 0))) ? 1:0
          3'b111 : result = A ^ B; // Y = A ^ B
      endcase
    end
endmodule

DataMemory.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 01:40:45
// Design Name: 
// Module Name: DataMemory
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module DataMemory(
    input [31:0] DAddr,
    input CLK,
    input mRD,
    input mWR,
    input [31:0] DataIn,
    output reg [31:0] DataOut
    );
    
    reg [7:0] dataMemory [255:0];
    
    always@( mRD or DAddr ) begin 
        if (mRD == 1) begin
            DataOut[7:0] = dataMemory[DAddr + 3];
            DataOut[15:8] = dataMemory[DAddr + 2];
            DataOut[23:16] = dataMemory[DAddr + 1];
            DataOut[31:24] = dataMemory[DAddr];
        end
    end

    always@( negedge CLK ) begin //总是在时钟下降沿到来时触发
        if (mWR == 1) begin
            dataMemory[DAddr + 3] <= DataIn[7:0];
            dataMemory[DAddr + 2] <= DataIn[15:8];
            dataMemory[DAddr + 1] <= DataIn[23:16];
            dataMemory[DAddr] <= DataIn[31:24];
        end
    end
    
endmodule

ControlUnit.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 02:13:34
// Design Name: 
// Module Name: ControlUnit
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module ControlUnit(
    output ExtSel,
    output PCWre,
    output InsMemRW,
    output RegDst,
    output RegWre,
    output [2:0] ALUOp,
    output [1:0] PCSrc,
    output ALUSrcA,
    output ALUSrcB,
    output RD,
    output WR,
    output DBDataSrc,
    input [5:0] opCode,
    input zero
    );
    
    assign ExtSel = (opCode == 6'b010000 || opCode == 6'b011000) ? 0 : 1;
    assign PCWre = (opCode == 6'b111111) ? 0 : 1;
    assign InsMemRW = 0;
    assign RegDst = (opCode == 6'b000001 || opCode == 6'b010000 || opCode == 6'b100111 || opCode == 6'b011011) ? 0 : 1;
    assign RegWre = (opCode == 6'b100110 || opCode == 6'b110000 || opCode == 6'b110001 || opCode == 6'b111000) ? 0 : 1;
    assign PCSrc[0] = ((opCode == 6'b110000 && zero == 1) || (opCode == 6'b110001 && zero == 0)) ? 1 : 0;
    assign PCSrc[1] = (opCode == 6'b111000) ? 1 : 0;
    assign ALUSrcA = (opCode == 6'b011000) ? 1 : 0;
    assign ALUSrcB = (opCode == 6'b000001 || opCode == 6'b010000 || opCode == 6'b011011 || opCode == 6'b100110 || opCode == 6'b100111) ? 1 : 0;
    assign RD = (opCode == 6'b100111) ? 1 : 0;
    assign WR = (opCode == 6'b100110) ? 1 : 0;
    assign DBDataSrc = (opCode == 6'b100111) ? 1 : 0;
    assign ALUOp[0] = (opCode == 6'b000010 || opCode == 6'b010000 || opCode == 6'b010010 || opCode == 6'b110000 || opCode == 6'b110001) ? 1: 0;
    assign ALUOp[1] = (opCode == 6'b010000 || opCode == 6'b010010 || opCode == 6'b011000 || opCode == 6'b011011) ? 1 : 0;
    assign ALUOp[2] = (opCode == 6'b010001 || opCode == 6'b011011) ? 1 : 0;
    
endmodule

Mux_ThreeToOne.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/26 10:18:34
// Design Name: 
// Module Name: Mux_ThreeToOne
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module Mux_ThreeToOne(
    input [1:0] PCSrc,
    input [31:0] CurPC,
    input [31:0] immediate,
    input [27:2] addr,
    output reg [31:0] newAddress
    );
    
    wire [31:0] PCAddr4 = CurPC + 4;
    
    always@( PCSrc or CurPC or addr or immediate ) begin
    if ( PCSrc == 2'b00 )
        newAddress = PCAddr4;
    else if ( PCSrc == 2'b01 )
        newAddress = PCAddr4 + immediate * 4;
    else if ( PCSrc == 2'b10 )
        newAddress = {PCAddr4[31:28], addr[27:2],2'b00};
    end
endmodule

Mux_TwoToOne.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 02:11:51
// Design Name: 
// Module Name: Mux_TwoToOne
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module Mux_TwoToOne(
    input Select,
    input [31:0] DataIn1,
    input [31:0] DataIn2,
    output reg [31:0] DataOut
    );
    
    always@( Select or DataIn1 or DataIn2 ) begin
        if ( Select == 0 )
            DataOut = DataIn1;
        else
            DataOut = DataIn2;
    end
        
endmodule

Mux_TwoToOne_A.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 02:08:52
// Design Name: 
// Module Name: Mux_TwoToOne_A
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module Mux_TwoToOne_A(
    input Select,
    input [31:0] DataIn1,
    input [4:0] sa,
    output reg [31:0] DataOut
    );
    
     always@( Select or DataIn1 or sa ) begin
        if ( Select == 0 )
            DataOut = DataIn1;
        else
            DataOut = { 27'b000000000000000000000000000, sa }; // 对sa进行扩展
    end   
endmodule

Mux_TwoToOne_Reg.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 02:10:46
// Design Name: 
// Module Name: Mux_TwoToOne_Reg
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module Mux_TwoToOne_Reg(
    input Select,
    input [4:0] DataIn1,
    input [4:0] DataIn2,
    output reg [4:0] DataOut
    );
    
    always@( Select or DataIn1 or DataIn2 ) begin
        if ( Select == 0 )
            DataOut = DataIn1;
        else
            DataOut = DataIn2;
    end
       
endmodule

SCPU.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 02:44:32
// Design Name: 
// Module Name: SCPU
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module SCPU(
    input CLK, // 时钟信号
    input Reset, // 置零信号
    output [31:0] CurPC, // 当前指令地址
    output [31:0] newaddress, // 下一个指令地址
    output [31:0] instcode, // rs,rt寄存器所在指令
    output [31:0] Reg1Out, // 寄存器组rs寄存器的值 
    output [31:0] Reg2Out, // 寄存器组rt寄存器的值
    output [31:0] ALU_Out, // ALU的result输出值
    output [31:0] WriteData // DB总线值
    );
        
        wire ExtSel; // 位扩展信号,1为符号扩展,0为0扩展
        wire PCWre; // PC工作信号,0不更改,1更改
        wire InsMemRW; // 指令寄存器信号,0为写,1为读
        wire RegDst; // 指令读取时判断是rt还是rd进入寄存器组的写数据端,0为rt,1为rd
        wire RegWre; // 寄存器组是否需要写功能,0为无写功能,1为些功能
        wire [2:0] ALUOp; // ALU8种运算功能选择
        wire [1:0] PCSrc; // PC正常+4还是要跳转,0为正常+4,1为跳转
        wire ALUSrcA; // 寄存器组Data1的输出,0为寄存器本身输出,1为指令码的最后16位立即数
        wire ALUSrcB; // 寄存器组Data2的输出,0位本身的输出,1为扩展后的立即数
        wire RD; // 读数据存储器功能,0时读取
        wire WR; // 写数据存储器功能,1时写
        wire DBDataSrc; // 决定将什么数据传入寄存器组Write Data端,0为ALU结果,1为存储器
        wire [4:0] WriteRegAddr; // 寄存器组Write Reg输入端
        wire [31:0] ALU_Input_A; // ALU的A输入端
        wire [31:0] ALU_Input_B; // ALU的B输入端
        wire zero; // ALU的zero输出
        wire [31:0] MemOut; // 存储器的输出
        wire [31:0] Ext_Imm; // 位扩展后的立即数
        
        // Mux_ThreeToOne( PCSrc, CurPC, immediate, addr, newAddress)
        Mux_ThreeToOne my_Mux_ThreeToOne( PCSrc, CurPC, Ext_Imm, instcode[25:0], newaddress );
       
        // PC( CLK, Reset, PCWre, newAddress, PCAddr )
        PC my_PC( CLK, Reset, PCWre, newaddress, CurPC );
    
        // ALU( Reg1, Reg2, ALUOp, result, zero )
        ALU my_ALU( ALU_Input_A, ALU_Input_B, ALUOp, ALU_Out, zero );
    
        // DataMemory( DAddr, CLK, RD, WR, DataIn, DataOut)
        DataMemory my_DataMemory( ALU_Out, CLK, RD, WR, Reg2Out, MemOut );
    
        // Sign_Zero_Extend( Imm_Number, ExtSel, Result )
        Sign_Zero_Extend my_Sign_Zero_Extend( instcode[15:0], ExtSel, Ext_Imm );
    
        // Mux_TwoToOneReg( Select, DataIn1, DataIn2, DataOut )
        Mux_TwoToOne_Reg my_Mux_TwoToOneReg( RegDst, instcode[20:16], instcode[15:11], WriteRegAddr );
    
        // Mux_TwoToOne( Select, DataIn1, DataIn2, DataOut )
        Mux_TwoToOne_A my_Mux_TwoToOne_For_ALU_InputA( ALUSrcA, Reg1Out, instcode[10:6], ALU_Input_A );
    
        Mux_TwoToOne my_Mux_TwoToOne_For_ALU_InputB( ALUSrcB, Reg2Out, Ext_Imm, ALU_Input_B );
    
        Mux_TwoToOne my_Mux_TwoToOne_For_RegisterFile_WriteData( DBDataSrc, ALU_Out, MemOut, WriteData );
    
        // RegisterFile( RegWre, CLK, Reg1, Reg2, WriteReg, WriteData, DataOut1, DataOut2 )
        RegisterFile my_RegisterFile( RegWre, CLK, instcode[25:21], instcode[20:16], WriteRegAddr, WriteData, Reg1Out, Reg2Out );
    
        // ControlUnit( ExtSel, PCWre, InsMemRW, RegDst, RegWre, ALUOp, PCSrc, ALUSrcB, RD, WR, DBDataSrc, opCode, zero )
        ControlUnit my_ControlUnit( ExtSel, PCWre, InsMemRW, RegDst, RegWre, ALUOp, PCSrc, ALUSrcA, ALUSrcB, RD, WR, DBDataSrc, instcode[31:26], zero );
    
        // InstructionMemory( CurPC, instMemRW, instcode )
        InstructionMemory my_InstructionMemory( CurPC, InsMemRW, instcode );
endmodule

2.仿真文件

至此,单周期CPU的所有模块完成,其中SCPU.v是顶层文件,我们最后写一个仿真代码对单周期CPU进行仿真测试,经检查已知结果正确,仿真代码如下:

SCPU_sim.v
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/25 02:53:19
// Design Name: 
// Module Name: SCPU_sim
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module SCPU_sim;

    reg CLK; // 时钟信号
    reg Reset; // 置零信号

    SCPU my_SCPU( .CLK(CLK), 
                  .Reset(Reset)
                  );

                  always #30 CLK = !CLK; // 60ns为一周期
                  initial begin
                    CLK = 0;
                    Reset = 0;
                    #90;
                    Reset = 1;
                  end
endmodule

3.测试指令

这里写图片描述

七.Basys3实验板代码实现

本部分是实现在Basys3实验板上运行的相关代码,因为时钟设置原因(具体原因已在另一篇博客中进行讲解说明),在按键控制操作上存在着半个周期的延迟,这一点不影响实验板实现结果的正确性,但是主观上会觉得有些别扭,所以建议大家以参考为主,只要抓住该部分代码设计的核心是确定合适的数码管扫描频率这一点,相信实现过程并不困难。

SegLED.v(数码管显示译码模块)

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/27 11:21:21
// Design Name: 
// Module Name: SegLED
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module SegLED(
    input [3:0] Store,
	input Reset,
	output reg [7:0] Out
    );
    
    always @( Store or Reset) begin
        if(Reset == 0) begin
            Out = 8'b11111110;
        end 
        else begin
            case (Store)
                4'b0000 : Out = 8'b10000001; //0£»'0'-ÁÁµÆ£¬'1'-ϨµÆ
                4'b0001 : Out = 8'b11001111; //1
                4'b0010 : Out = 8'b10010010; //2
                4'b0011 : Out = 8'b10000110; //3
                4'b0100 : Out = 8'b11001100; //4
                4'b0101 : Out = 8'b10100100; //5
                4'b0110 : Out = 8'b10100000; //6
                4'b0111 : Out = 8'b10001111; //7
                4'b1000 : Out = 8'b10000000; //8
                4'b1001 : Out = 8'b10000100; //9
                4'b1010 : Out = 8'b10001000; //A
                4'b1011 : Out = 8'b11100000; //b
                4'b1100 : Out = 8'b10110001; //C
                4'b1101 : Out = 8'b11000010; //d
                4'b1110 : Out = 8'b10110000; //E
                4'b1111 : Out = 8'b10111000; //F
                default : Out = 8'b0000_0000; //no light
            endcase
        end
    end
    
endmodule

key_fangdou.v(按键消抖模块)

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/27 23:27:49
// Design Name: 
// Module Name: key_fangdou
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module key_fangdou(clk,key_in,key_out);
    parameter SAMPLE_TIME = 5000;
    input clk;
    input key_in;
    output key_out;

    reg [21:0] count_low;
    reg [21:0] count_high;

    reg key_out_reg;

    always @(posedge clk)
    if(key_in ==1'b0)
        count_low <= count_low + 1;
    else
        count_low <= 0;

    always @(posedge clk)
        if(key_in ==1'b1)
            count_high <= count_high + 1;
        else
            count_high <= 0;

    always @(posedge clk)
        if(count_high == SAMPLE_TIME)
            key_out_reg <= 1;
        else if(count_low == SAMPLE_TIME)
            key_out_reg <= 0;

    assign key_out = !key_out_reg;
    
endmodule 

Basys3_Out.v(Basys3顶层模块)

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2018/05/26 13:21:45
// Design Name: 
// Module Name: Basys3_Out
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module Basys3_Out(
    input CLK,
    input [1:0] SW, // 选择输出信号
    input Reset, // 重置按钮
    input Button, // 单脉冲
    output reg[3:0] AN, // 数码管位选择信号
    output [7:0] Out // 数码管输入信号
    );
    
    reg [16:0] showCounter;
    parameter T1MS = 100000;
    wire [31:0] ALU_Out; // ALU的result输出值
    wire [31:0] CurPC;
    wire [31:0] WriteData; // DB总线值
    wire [31:0] Reg1Out; // 寄存器组rs寄存器的值
    wire [31:0] Reg2Out; // 寄存器组rt寄存器的值
    wire [31:0] instcode;
    wire myCLK;
    reg [3:0] store; // 记录当前要显示位的值
    wire [31:0] newAddress;
    
    SCPU my_SCPU(myCLK, Reset, CurPC, newAddress, instcode, Reg1Out, Reg2Out, ALU_Out, WriteData);
    
    key_fangdou my_key_fangdou(CLK, Button, myCLK);
    
    initial begin
        showCounter <= 0;
        AN <= 4'b0111;
    end
        
        always@ (posedge CLK)
            begin
            if(Reset == 0) begin
              showCounter <= 0;
              AN <= 4'b0000;
            end else begin
                showCounter <= showCounter + 1;
                if(showCounter == T1MS)
                    begin
                        showCounter <= 0;
                        case(AN)
                            4'b1110 : begin
                                AN <= 4'b1101;
                            end
                            4'b1101 : begin
                                AN <= 4'b1011;
                            end
                            4'b1011 : begin
                                AN <= 4'b0111;
                            end
                            4'b0111 : begin
                                AN <= 4'b1110;
                            end
                           4'b0000 : begin
                                AN <= 4'b0111;
                           end
                        endcase
                    end
                end
            end
    
        SegLED led(store, Reset, Out);
        
        always@ (myCLK) begin
           case(AN)
                4'b1110 : begin
                    case(SW)
                        2'b00: store <= newAddress[3:0];
                        2'b01: store <= Reg1Out[3:0];
                        2'b10: store <= Reg2Out[3:0];
                        2'b11: store <= WriteData[3:0];
                    endcase
                end
                4'b1101 : begin
                    case(SW)
                        2'b00: store <= newAddress[7:4];
                        2'b01: store <= Reg1Out[7:4];
                        2'b10: store <= Reg2Out[7:4];
                        2'b11: store <= WriteData[7:4];
                    endcase
                end
                4'b1011 : begin
                    case(SW)
                        2'b00: store <= CurPC[3:0];
                        2'b01: store <= instcode[24:21];
                        2'b10: store <= instcode[19:16];
                        2'b11: store <= ALU_Out[3:0];
                    endcase
                end
                4'b0111 : begin
                    case(SW)
                        2'b00: store <= CurPC[7:4];
                        2'b01: store <= { 3'b000,instcode[25]};
                        2'b10: store <= { 3'b000,instcode[20]};
                        2'b11: store <= ALU_Out[7:4];
                    endcase
                end
            endcase
        end
            
endmodule

  • 14
    点赞
  • 115
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值