精华内容
下载资源
问答
  • 单周期CPU设计verilog

    2017-05-10 22:42:35
    单周期CPU设计verilog,课程设计
  • single_period_CPU_opt.zip 内部完整代码 仿真测试都通过
  • 第一章 单周期CPU设计原理 为实现单周期CPU,本文首先研究了单周期CPU的相关理论和工作原理。本章首先简要介绍单周期CPU的基本概念,然后对CPU的工作原理进行简要分析,以便为单周期CPU设计和开发提供理论基础...

    第一章 单周期CPU的设计原理

    1.1 单周期CPU概述

    1.2 CPU工作原理

    第二章 单周期CPU的设计内容

    2.1 指令系统的设计

    2.1.1 概述

    2.1.2 运算类指令的设计

    2.1.3 传送类指令的设计

    2.1.4 存储类指令的设计

    2.1.5 控制类指令的设计

    2.2 整体框架的设计

    2.3 数据通路的设计

    2.4 控制信号的设计

    第三章 单周期CPU的具体实现

    3.1 底层模块的实现

    3.1.1 程序计数器PC

    3.1.2 指令存储器InstructionMemory

    3.1.3 寄存器组RegisterFile

    3.1.4 算术逻辑单元ALU

    3.1.5 数据存储器DataMemory

    3.1.6 控制单元ControlUnit

    3.2 顶层模块的实现

    第四章 单周期CPU的仿真验证

    4.1 测试程序

    4.2 波形仿真


    第一章 单周期CPU的设计原理

    为实现单周期CPU,本文首先研究了单周期CPU的相关理论和工作原理。本章首先简要介绍单周期CPU的基本概念,然后对CPU的工作原理进行简要分析,以便为单周期CPU的设计和开发提供理论基础。

    1.1 单周期CPU概述

    中央处理器,即CPU,作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。在CPU内部,电平从低到高变化的瞬间称为时钟上升沿,两个相邻时钟上升沿之间的时间间隔称为一个时钟周期。单周期CPU指的是一条指令的执行在一个时钟周期内完成,然后开始下一条指令的执行,即一条指令用一个时钟周期完成。

    1.2 CPU工作原理

    CPU在处理指令时,一般需要经过以下几个步骤:

    (1) 取指令(IF):根据程序计数器PC中的指令地址,从存储器中取出一条指令,同时,PC根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入PC,当然得到的“地址”需要做些变换才送入PC。

    (2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。

    (3) 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。

    (4) 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。

    (5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。

    CPU的指令处理过程如下:

     

    单周期CPU,是在一个时钟周期内完成这五个阶段的处理。

    第二章 单周期CPU的设计内容

    2.1 指令系统的设计

    2.1.1 概述

    本文所设计的单周期CPU的指令系统采用类似MIPS的设计风格,包括以下四类指令:

    (1) 运算类指令;

    (2) 传送类指令;

    (3) 存储类指令;

    (4) 控制类指令;

    其中,所有指令的操作码部分用4位二进制表示,寄存器编号用3位二进制表示。在下述的具体设计表示中,以助记符表示的是汇编指令;以代码表示的则是二进制机器指令。

    2.1.2 运算类指令的设计

    运算类指令共有8条,具体的指令功能与指令格式如下:

    (1) add rd,rs,rt

    0000

    rs(3位)

    rt(3位)

    rd(3位)

    reserved(未用)

    功能:将寄存器rs和rt中的值相加并将结果写回寄存器rd。reserved为预留部分,即未用,一般填“0”。

    (2) addi rt,rs,immediate

    0001

    rs(3位)

    rt(3位)

    immediate(6位)

    功能:将立即数immediate进行符号扩展后再和寄存器rs中的值相加并将结果写回寄存器rt。

    (3) sub rd,rs,rt

    0010

    rs(3位)

    rt(3位)

    rd(3位)

    reserved(未用)

    功能:用寄存器rs中的值减去rt中的值并将结果写回寄存器rd。reserved为预留部分,即未用,一般填“0”。

    (4) or rd,rs,rt

    0011

    rs(3位)

    rt(3位)

    rd(3位)

    reserved(未用)

    功能:将寄存器rs和rt中的值进行按位或运算并将结果写回寄存器rd。reserved为预留部分,即未用,一般填“0”。

    (5) ori rt,rs,immediate

    0100

    rs(3位)

    rt(3位)

    immediate(6位)

    功能:将立即数immediate进行“0”扩展后再和寄存器rs中的值进行按位或运算并将结果写回寄存器rt。

    (6) and rd,rs,rt

    0101

    rs(3位)

    rt(3位)

    rd(3位)

    reserved(未用)

    功能:将寄存器rs和rt中的值进行按位与运算并将结果写回寄存器rd。reserved为预留部分,即未用,一般填“0”。

    (7) sll rd,rt,sa

    0110

    未用(3位)

    rt(3位)

    rd(3位)

    sa(3位)

    功能:先将sa表示的立即数进行“0”拓展,然后将寄存器rt中的值左移sa位并将结果写回寄存器rd。

    (8) slt rd,rs,rt

    0111

    rs(3位)

    rt(3位)

    rd(3位)

    reserved(未用)

    功能:将寄存器rs和rt中的值作为带符号数进行比较,若rs中的值更小,则将寄存器rd中的值置1,否则置0。

    2.1.3 传送类指令的设计

    传送类指令共有2条,具体的指令功能与指令格式如下:

    (1) mov rd,rt

    1000

    未用(3位)

    rt(3位)

    rd(3位)

    reserved(未用)

    功能:将寄存器rt中的值传送到寄存器rd。reserved为预留部分,即未用,一般填“0”。

    (2) movi rt,immediate

    1001

    未用(3位)

    rt(3位)

    immediate(6位)

    功能:将立即数immediate进行符号扩展后传送到寄存器rt。

    2.1.4 存储类指令的设计

    存储类指令共有2条,具体的指令功能与指令格式如下:

    (1) sw  rt,immediate(rs)

    1010

    rs(3位)

    rt(3位)

    immediate(6位)

    功能:将寄存器rt中的值存储到内存中,存储单元的地址为寄存器rs中的值加上经符号拓展的立即数immediate。

    (2) lw  rt,immediate(rs)

    1011

    rs(3位)

    rt(3位)

    immediate(6位)

    功能:将内存中的值加载到寄存器rt中,存储单元的地址为寄存器rs中的值加上经符号拓展的立即数immediate。

    2.1.5 控制类指令的设计

    控制类指令共有4条,具体的指令功能与指令格式如下:

    (1) beq  rs,rt,immediate

    1100

    rs(3位)

    rt(3位)

    immediate(6位)

    功能:比较寄存器rs和rt中的值,若相等则跳转到目标处执行,否则继续执行下一条指令。跳转的目标地址与当前指令的下一条指令的相对位移由立即数immediate给出。

    (2) bgtz  rs,immediate

    1101

    rs(3位)

    000

    immediate(6位)

    功能:判断寄存器rs中的值是否大于0,若大于0则跳转到目标处执行,否则继续执行下一条指令。跳转的目标地址与当前指令的下一条指令的相对位移由立即数immediate给出。

    (3) j  addr

    1110

    addr(12位)

    功能:执行无条件跳转,跳转的目的地址组成如下:高3位为PC中值的高3位,第1-12位由addr给出,最低位为0。

    (4) halt

    1111

    000000000000(12位)

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

    2.2 整体框架的设计

    本文所设计的单周期CPU的整体框架主要包括七部分:程序计数器、指令寄存器、寄存器组、算术逻辑单元、数据存储器、控制单元和顶层模块。具体框架如下:

    2.3 数据通路的设计

    本文所设计的单周期CPU的数据通路如下:

    其中Ins.MEM为指令存储器,Data.MEM为数据存储器。访问存储器时,先给出内存地址,然后由读或写信号控制操作。对于寄存器组Register File,先给出寄存器编号,读操作时,输出端就直接输出相应数据;而在写操作时,当写使能信号为1时,在时钟边沿触发将数据写入寄存器。

    主要模块接口说明如下:

    (1) Instruction Memory:指令存储器。

    IAddr,指令存储器地址输入端口

    IDataOut,指令存储器数据输出端口(指令代码输出端口)

    InsMemRW,指令存储器读写控制信号,为0写,为1读

    (2) Data Memory:数据存储器。

    DAddr,数据存储器地址输入端口

    DataIn,数据存储器数据输入端口

    DataOut,数据存储器数据输出端口

    RD,数据存储器读控制信号,为0读

    WR,数据存储器写控制信号,为0写

    (3) Register File:寄存器组。

    Read Reg1,rs寄存器地址输入端口

    Read Reg2,rt寄存器地址输入端口

    Write Reg,将数据写入的寄存器端口,其地址来源为rt或rd字段

    Write Data,写入寄存器的数据输入端口

    Read Data1,rs寄存器数据输出端口

    Read Data2,rt寄存器数据输出端口

    WE,写使能信号,为1时,在时钟边沿触发写入

    (4) ALU:算术逻辑单元。

    A,操作数A输入端口

    B,操作数B输入端口

    result,ALU运算结果

    zero,运算结果标志,结果为0,则zero=1;否则zero=0

    sign,运算结果标志,结果最高位为0,则sign=0,正数;否则,sign=1,负数

    2.4 控制信号的设计

    本文所设计的单周期CPU的各控制信号如下:

     各控制信号的功能如下:

    控制信号名

    状态“0”

    状态“1”

    Reset

    初始化PC为0

    PC接收新地址

    PCWre

    PC不更改,相关指令:halt

    PC更改,相关指令:除指令halt外

    ALUSrcA

    来自寄存器堆data1输出,相关指令:add、addi、sub、or、ori、and、slt、mov、movi、beq、bgtz、sw、lw

    来自经过“0”拓展的移位数sa,即 {{13{0}},sa},相关指令:sll

    ALUSrcB

    来自寄存器堆data2输出,相关指令:add、sub、or、and、sll、slt、mov、beq、bgtz

    来自sign或zero扩展的立即数,相关指令:addi、ori、movi、sw、lw

    DBDataSrc

    来自ALU运算结果的输出,相关指令:add、addi、sub、or、ori、and、sll、slt、mov、movi

    来自数据存储器(Data MEM)的输出,相关指令:lw

    RegWre

    寄存器组写不使能,相关指令:sw、beq、bgtz、j、halt

    寄存器组写使能,相关指令:add、addi、sub、or、ori、and、sll、slt、mov、movi、lw

    InsMemRW

    写指令存储器

    读指令存储器

    RD

    读数据存储器,相关指令:lw

    无操作

    WR

    写数据存储器,相关指令:sw

    无操作

    RegDst

    写寄存器组寄存器的地址,来自rt字段,相关指令:addi、ori、movi、lw

    写寄存器组寄存器的地址,来自rd字段,相关指令:add、sub、or、and、sll、slt、mov

    ExtSel

    (zero-extend)immediate0扩展),相关指令:ori

    (sign-extend)immediate符号扩展,相关指令:addi、movi、sw、lw、beq、bgtz

    PCSrc[1..0]

    00:pc<-pc+2,相关指令:add、addi、sub、or、ori、and、sll、slt、sw、lw、beq(zero=0)、bgtz(sign=1,或zero=1);

    01:pc<-pc+2+(sign-extend)immediate,相关指令:beq(zero=1)、bgtz(sign=0,zero=0);

    10:pc<-{(pc+2)[15..13],addr[12..1],0},相关指令:j;

    11:未用

    ALUOp[2..0]

    ALU 8种运算功能选择(000-111),具体见ALU功能表

    ALU运算功能表如下:

    ALUOp[2..0]

    功能

    描述

    000

    Y = A+B

    001

    Y = A-B

    010

    Y = B<<A

    B左移A位

    011

    Y = A∨B

    100

    Y = A∧B

    101

    Y=(A<B)?1: 0

    比较A与B

    不带符号

    110

    if (A<B && (A[15] == B[15] ) )

        Y = 1;

    else if ( A[15] && !B[15)  Y = 1;

    else Y = 0; 

    比较A与B

    带符号

    111

    Y = B

    直送

    在具体进行控制时,PC的改变是在时钟上升沿进行的,指令执行的结果总是在时钟下降沿保存到寄存器和存储器中,这样稳定性较好。

    第三章 单周期CPU的具体实现

    3.1 底层模块的实现

    单周期CPU每个时钟周期可被划分为五个阶段,对应的有五个最关键的底层模块:IF阶段对应PC,ID阶段对应InstructionMemory和RegisterFile,EXE阶段对应ALU,MEM阶段对应DataMemory,WB阶段也对应RegisterFile。除此之外,还需要生成控制信号的控制单元(ControlUnit)等。下面具体介绍上述各主要模块的设计与实现。

    3.1.1 程序计数器PC

    PC为时序逻辑,在时钟上升沿到来时,若PCWre为1,则输出下一条待处理的指令地址,否则不输出新的指令地址。若Reset为0,则设置输出的指令地址为0。具体代码如下:

    module PC(    
        input clk,    
        input [15:0] PCin,    
        input PCWre,    
        input Reset,    
        output reg [15:0] PCout    
    );    
          
        initial begin    
            PCout <= 0;   
        end  
            
        always@(posedge clk) begin    
            if(Reset == 0) begin    
                PCout <= 0;    
            end    
            else if(PCWre == 0) begin    
                PCout <= PCout;    
            end    
            else begin    
                PCout <= PCin;    
            end    
        end  
            
    endmodule    
    

    3.1.2 指令存储器InstructionMemory

    InstructionMemory为组合逻辑,内含128个字节的mem。mem中存放要执行的测试程序段的机器码,使用initial语句中的$readmemb伪指令从Instructions.txt文件中读取出来。当输入一个16位长的指令地址,输出对应地址中的16位机器指令。具体代码如下:

    module InsMemory(    
        input InsMemRW,    
        input [15:0] address,    
        output reg [15:0] DataOut    
    );    
    
        reg [7:0] mem [0:127];   
    
        initial begin    
            DataOut = 16'b1111000000000000;  
            $readmemb("Instructions.txt", mem);      
        end   
          
        always@(*) begin   
            DataOut[15:8] <= mem[address];    
            DataOut[7:0] <= mem[address+1];  
        end   
           
    endmodule    
    

    3.1.3 寄存器组RegisterFile

    RegisterFile中定义了一个含有8个寄存器的寄存器组,设计逻辑包括两部分:一方面,读寄存器组为组合逻辑,当输入一个3位长的寄存器编号时,输出对应寄存器中的数值。另一方面,写寄存器组是时序逻辑,在时钟下降沿到来时触发写入。具体代码如下:

    module RegFile(    
        input CLK,    
        input RST,    
        input RegWre,    
        input [2:0] ReadReg1,    
        input [2:0] ReadReg2,    
        input [2:0] WriteReg,    
        input [15:0] WriteData,    
        output [15:0] ReadData1,    
        output [15:0] ReadData2    
    );    
      
        reg [15:0] regFile[0:7];  
           
        integer i;    
          
        assign ReadData1 = regFile[ReadReg1];   
        assign ReadData2 = regFile[ReadReg2];  
            
        always @ (negedge CLK) begin    
            if (RST == 0) begin    
                for(i=1;i<8;i=i+1)    
                    regFile[i] <= 0;    
            end    
            else if(RegWre == 1 && WriteReg != 0) begin    
                regFile[WriteReg] <= WriteData;    
            end    
        end    
                
    endmodule    
    

    3.1.4 算术逻辑单元ALU

    ALU为组合逻辑,根据控制信号对两个操作数进行相应的运算,并输出结果。具体代码如下:

    module ALU(    
        input [2:0] ALUopcode,    
        input [15:0] rega,    
        input [15:0] regb,    
        output reg [15:0] result,    
        output zero,    
        output sign    
    );    
      
        assign zero = (result==0)?1:0;    
        assign sign = result[15];    
          
        always @( ALUopcode or rega or regb ) begin    
            case (ALUopcode)    
                3'b000 : result = rega + regb;    
                3'b001 : result = rega - regb;    
                3'b010 : result = regb << rega;    
                3'b011 : result = rega | regb;    
                3'b100 : result = rega & regb;    
                3'b101 : result = (rega < regb)?1:0;     
                3'b110 : begin    
                    if (rega<regb &&(( rega[15] == 0 && regb[15]==0) ||    
                    (rega[15] == 1 && regb[15]==1))) result = 1;    
                    else if (rega[15] == 0 && regb[15]==1) result = 0;    
                    else if ( rega[15] == 1 && regb[15]==0) result = 1;    
                    else result = 0;    
                end    
                3'b111 : result = regb;    
            endcase    
        end   
           
    endmodule    
    

    3.1.5 数据存储器DataMemory

    DataMemory中包含128个字节的ram,设计逻辑包括两部分:一方面,读存储器为组合逻辑,当输入一个16位长的数据地址时,输出对应地址中的16位数据。另一方面,写存储器是时序逻辑,在时钟下降沿到来时触发写入。具体代码如下:

    module DataMemory(    
        input clk,    
        input [15:0] address,    
        input RD,    
        input WR,    
        input [15:0] DataIn,    
        output [15:0] DataOut    
    );    
        
        reg [7:0] ram[0:127];  
            
        integer i;    
          
        initial begin;    
            for(i=0;i<128;i=i+1)    
                ram[i]<=0;    
        end    
           
        assign DataOut[7:0] = (RD == 0)? ram[address+1]:8'bz;    
        assign DataOut[15:8] = (RD == 0)? ram[address]:8'bz;    
       
        always@(negedge clk) begin    
            if(WR == 0) begin    
                if(address>=0 && address<128) begin    
                    ram[address] <= DataIn[15:8];    
                    ram[address+1] <= DataIn[7:0];     
                end    
            end    
        end  
            
    endmodule    
    

    3.1.6 控制单元ControlUnit

    ControlUnit为组合逻辑,将机器码中的操作码(opcode)转换为各个控制信号,从而控制不同的指令在不同的数据通路中传输。具体代码如下:

    module ControlUnit(    
        input [3:0] opcode,    
        input zero,    
        input sign,    
        output reg PCWre,    
        output reg ALUSrcA,    
        output reg ALUSrcB,    
        output reg DBDataSrc,    
        output reg RegWre,    
        output reg InsMemRW,    
        output reg RD,    
        output reg WR,    
        output reg RegDst,    
        output reg ExtSel,    
        output reg [1:0] PCSrc,    
        output reg [2:0] ALUOp    
    );    
      
        initial begin    
            RD = 1;    
            WR = 1;    
            RegWre = 0;   
            PCWre = 0;   
            InsMemRW = 1;    
        end   
           
        always@ (opcode) begin    
            case(opcode)     
                4'b0000:begin // add    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 0;    
                    DBDataSrc = 0;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    RegDst = 1;    
                    ALUOp = 3'b000;    
                end    
                4'b0001:begin //addi    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 1;    
                    DBDataSrc = 0;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    RegDst = 0;    
                    ExtSel = 1;    
                    ALUOp = 3'b000;    
                end    
                4'b0010:begin //sub    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 0;    
                    DBDataSrc = 0;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    RegDst = 1;    
                    ALUOp = 3'b001;    
                end  
                4'b0011:begin // or    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 0;    
                    DBDataSrc = 0;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    RegDst = 1;    
                    ALUOp = 3'b011;    
                end     
                4'b0100:begin // ori    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 1;    
                    DBDataSrc = 0;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    RegDst = 0;    
                    ExtSel = 0;    
                    ALUOp = 3'b011;    
                end    
                4'b0101:begin //and    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 0;    
                    DBDataSrc = 0;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    RegDst = 1;    
                    ALUOp = 3'b100;    
                end    
                4'b0110:begin //sll    
                    PCWre = 1;    
                    ALUSrcA = 1;    
                    ALUSrcB = 0;    
                    DBDataSrc = 0;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    RegDst = 1;    
                    ALUOp = 3'b010;    
                end    
                4'b0111:begin //slt    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 0;    
                    DBDataSrc = 0;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    RegDst = 1;    
                    ALUOp = 3'b110;    
                end  
                4'b1000:begin //mov    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 0;    
                    DBDataSrc = 0;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    RegDst = 1;    
                    ExtSel = 1;    
                    ALUOp = 3'b111;    
                end  
                4'b1001:begin //movi    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 1;    
                    DBDataSrc = 0;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    RegDst = 0;    
                    ExtSel = 1;    
                    ALUOp = 3'b111;    
                end      
                4'b1010:begin //sw    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 1;    
                    RegWre = 0;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 0;    
                    ExtSel =1;    
                    ALUOp = 3'b000;    
                end    
                4'b1011:begin //lw    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 1;    
                    DBDataSrc = 1;    
                    RegWre = 1;    
                    InsMemRW = 1;    
                    RD = 0;    
                    WR = 1;    
                    RegDst = 0;    
                    ExtSel = 1;    
                    ALUOp = 3'b000;    
                end   
                4'b1100:begin //beq    
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 0;    
                    RegWre = 0;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    ExtSel = 1;    
                    ALUOp = 3'b001;    
                end    
                4'b1101:begin  //bgtz  
                    PCWre = 1;    
                    ALUSrcA = 0;    
                    ALUSrcB = 0;    
                    RegWre = 0;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    ExtSel = 1;    
                    ALUOp = 3'b001;    
                end    
                4'b1110:begin //j    
                    PCWre = 1;    
                    RegWre = 0;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                    ALUOp = 3'b010;    
                end    
                4'b1111:begin //halt    
                    PCWre = 0;    
                    RegWre = 0;    
                    InsMemRW = 1;    
                    RD = 1;    
                    WR = 1;    
                end    
                default:begin    
                    RD = 1;    
                    WR = 1;    
                    RegWre = 0;    
                    InsMemRW = 0;    
                end    
            endcase    
        end   
           
        always@(opcode or zero or sign) begin    
            if(opcode == 4'b1110) // j    
                PCSrc = 2'b10;    
            else if(opcode == 4'b1100) begin  
                if(zero == 1)  
                    PCSrc = 2'b01;  
                else  
                    PCSrc = 2'b00;  
            end  
            else if(opcode == 4'b1101) begin  
                if(zero == 0 && sign == 0)    
                    PCSrc = 2'b01;    
                else    
                    PCSrc = 2'b00;  
            end    
            else begin    
                PCSrc = 2'b00;    
            end    
        end   
           
    endmodule    
    

    3.2 顶层模块的实现

    在顶层模块中,通过实例化各个底层模块,并使用导线将它们按照数据通路图连接起来,构成单周期CPU的完整结构,具体原理图如下:

    第四章 单周期CPU的仿真验证

    根据上述实现的单周期CPU,利用指令系统中所有的指令设计一个测试程序,对该CPU进行如下仿真验证。

    4.1 测试程序

    使用该指令系统中所有指令,设计出如下一段具有实际意义的测试程序。该程序计算数字1到5的和以及按位或的和,并将两者中较大者存储到内存中地址为14的存储单元中,最后读出该存储单元中的值。具体指令序列如下:

    地址

    汇编程序

    机器指令代码(二进制)

    0x0000

    movi     $0,0

    10010000 00000000

    0x0002

    mov      $1,$0

    10000000 00001000

    0x0004

    and      $3,$1,$0

    01010010 00011000

    0x0006

    addi      $2,$0,6

    00010000 10000110

    0x0008

    ori      $3,$0,1

    01000000 11000001

    0x000A

    add      $5,$0,$3

    00000000 11101000

    0x000C

    or       $6,$1,$3

    00110010 11110000

    0x000E

    mov      $0,$5

    10000001 01000000

    0x0010

    mov      $1,$6

    10000001 10001000

    0x0012

    addi      $4,$3,1

    00010111 00000001

    0x0014

    mov      $3,$4

    10000001 00011000

    0x0016

    beq      $3,$2,1

    11000110 10000001

    0x0018

    j         5

    11100000 00000101

    0x001A

    slt      $5,$0,$1

    01110000 01101000

    0x001C

    movi     $4,4

    10010001 00000100

    0x001E

    sub     $6,$2,$4

    00100101 00110000

    0x0020

    sll       $7,$6,1

    01100001 10111001

    0x0022

    bgtz      $5,2

    11011010 00000010

    0x0024

    sw      $0,10($7)

    10101110 00001010

    0x0026

    j         21

    11100000 00010101

    0x0028

    sw      $1,10($7)

    10101110 01001010

    0x002A

    lw       $6,10($7)

    10111111 10001010

    0x002C

    halt

    11110000 00000000

    4.2 波形仿真

    在仿真过程中,设置如下输入和输出,并显示如下寄存器中的内容:

    输入

    clk

    CPU时钟信号,周期为10ns

    reset

    CPU复位信号,0为复位

    输出

    nowIns

    当前指令

    nextPC

    下一条指令地址

    nowPC

    当前PC地址

    ALUSrcA

    ALU操作数A

    ALUSrcB

    ALU操作数

    ALUResult

    ALU运算结果

    finalData

    写回的结果

    寄存器

    RegFile[0]-RegFile[7]

    寄存器组

    DataMemory[10]-DataMemory[15]

    数据存储器

    仿真结果如下:

    (1) movi $0,0至ori $3,$0,1

    (2) add $5,$0,$3至j 5 (第一轮循环)

    (3) add $5,$0,$3至j 5 (第二轮循环)

    (4) add $5,$0,$3至j 5 (第三轮循环)

    (5) add $5,$0,$3至j 5 (第四轮循环)

    (6) add $5,$0,$3至beq $3,$2,1 (第五轮循环)

    (7) slt $5,$0,$1至sll $7,$6,1

    (8) bgtz $5,2至halt

     

    展开全文
  • 单周期CPU设计(Verilog)

    万次阅读 多人点赞 2017-05-07 10:46:00
    当时单周期cpu写的比较仓促,没有深入的进行调试,我准备在放假的时候重构一下代码, 然后把博文改进一下,现在实在没有时间,很抱歉~ 不过多周期我有调试过的,所以有需要的可以移步到我的多周期cpu设计一、 ...

    2017/06/08: 当时单周期cpu写的比较仓促,没有深入的进行调试,我准备在放假的时候重构一下代码, 然后把博文改进一下,现在实在没有时间,很抱歉~ 不过多周期我有调试过的,所以有需要的可以移步到我的多周期cpu设计


    一、 实验目的

    (1) 掌握单周期CPU数据通路图的构成、原理及其设计方法;
    (2) 掌握单周期CPU的实现方法,代码实现方法;
    (3) 认识和掌握指令与CPU的关系;
    (4) 掌握测试单周期CPU的方法。

    二、 实验内容

    设计一个单周期CPU,该CPU至少能实现以下指令功能操作。需设计的指令与格式如下:
    这里写图片描述
    这里写图片描述
    特别说明:
    immediate是从PC+4地址开始和转移到的指令之间指令条数。immediate符号扩展之后左移2位再相加。为什么要左移2位?由于跳转到的指令地址肯定是4的倍数(每条指令占4个字节),最低两位是“00”,因此将immediate放进指令码中的时候,是右移了2位的,也就是以上说的“指令之间指令条数”。
    这里写图片描述

    补充:

    1、PC、寄存器组和存储器写状态使用时钟触发。
    2、指令存储器和数据存储器存储单元宽度一律使用8位,即一个字节的存储单位。不能使用32位作为存储器存储单元宽度。
    3、控制器部分要学会用控制信号真值表方法分析问题并写出逻辑表达式;或者用case语句方法逐个产生各指令控制信号。
    4、必须写一段测试用的汇编程序,而且必须包含所要求的所有指令,beq指令必须检查两种情况:“等”和“不等”。

    三、实验原理

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

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

    这里写图片描述

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

    这里写图片描述

    相关部件及引脚说明:
    Instruction Memory:指令存储器,
    Iaddr,指令存储器地址输入端口
    IDataIn,指令存储器数据输入端口(指令代码输入端口)
    IDataOut,指令存储器数据输出端口(指令代码输出端口)
    RW,指令存储器读写控制信号,为1写,为0读
    Data Memory:数据存储器,
    Daddr,数据存储器地址输入端口
    DataIn,数据存储器数据输入端口
    DataOut,数据存储器数据输出端口
    RD,数据存储器读控制信号,为1读
    WR,数据存储器写控制信号,为1写
    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

    这里写图片描述

    需要说明的是以上数据通路图是根据要实现的指令功能的要求画出来的,同时,还必须确定ALU的运算功能(当然,以上指令没有完全用到提供的ALU所有功能,但至少必须能实现以上指令功能操作)。从数据通路图上可以看出控制单元部分需要产生各种控制信号,当然,也有些信号必须要传送给控制单元。从指令功能要求和数据通路图的关系得出以上表1,这样,从表1可以看出各控制信号与相应指令之间的相互关系,根据这种关系就可以得出控制信号与指令之间的关系表(留给学生完成),再根据关系表可以写出各控制信号的逻辑表达式,这样控制单元部分就可实现了。

    指令执行的结果总是在时钟下降沿开始保存到寄存器和存储器中,PC的改变是在时钟上升沿进行的,这样稳定性较好。另外,值得注意的问题,设计时,用模块化的思想方法设计,关于ALU设计、存储器设计、寄存器组设计等等,也是必须认真考虑的问题。

    四、实验设备

    PC机一台,BASYS 3 板一块,Xilinx Vivado 开发软件一套。

    五. 实验分析与设计

    下面是我设计单周期CPU的详细过程:
    1.设计Control Unit
    由数据通路图可知,
    **输入信号为:**opCode、zero
    输出信号为: PCWre, ALUSrcA, ALUSrcB,DBDataSrc,RegWre,InsMemRW, RD,WR, ExtSel,RegDst,PCSrc,ALUOp
    设计control unit,必须列出控制信号与指令的关系表
    这里写图片描述
    然后根据该表assign对应的值。

    `timescale 1ns / 1ps
    //
    // Company: 
    // Engineer: 
    // 
    // Create Date: 2017/04/23 20:43:40
    // Design Name: 
    // Module Name: controlUnit
    // Project Name: 
    // Target Devices: 
    // Tool Versions: 
    // Description: 
    // 
    // Dependencies: 
    // 
    // Revision:
    // Revision 0.01 - File Created
    // Additional Comments:
    // 
    //
    
    
    module controlUnit(
        // 根据数据通路图定义输入和输出
        input [5:0] opCode,
        input zero,
        output PCWre,
        output ALUSrcA,
        output ALUSrcB,
        output DBDataSrc,
        output RegWre,
        output InsMemRW,
        output RD,
        output WR,
        output ExtSel,
        output RegDst,
        output PCSrc,
        output [2:0] ALUOp
        );
        // 根据opcode定义控制信号为1或0
        assign PCWre = (opCode == 6'b111111) ? 0 : 1;
        assign ALUSrcA = (opCode == 6'b011000) ? 1 : 0;
        assign ALUSrcB = (opCode == 6'b000001 || opCode == 6'b010000 || opCode == 6'b100110 || opCode == 6'b100111) ? 1 : 0;
        assign DBDataSrc = (opCode == 6'b100111) ? 1 : 0;
        assign RegWre = (opCode == 6'b100110 || opCode == 6'b110000 || opCode == 6'b111111) ? 0 : 1;
        assign InsMemRW = 0;
        assign RD = (opCode == 6'b100111) ? 0 : 1;
        assign WR = (opCode == 6'b100110) ? 0 : 1;
        assign ExtSel = (opCode == 6'b010000) ? 0 : 1;
        assign RegDst = (opCode == 6'b000001 || opCode == 6'b010000 || opCode == 6'b100111) ? 0 : 1;
        assign PCSrc = (opCode == 6'b110000 && zero == 1) ? 1 : 0;
        assign ALUOp[2] = (opCode == 6'b010000 || opCode == 6'b010001 || opCode == 6'b010000) ? 1 : 0;
        assign ALUOp[1] = 0;
        assign ALUOp[0] = (opCode == 6'b000010 || opCode == 6'b010001 || opCode == 6'b110000) ? 1 : 0;
    endmodule
    

    2.设计ALU
    由数据通路图可知,
    输入信号为: ReadData1, ReadData2,inExt,insa,ALUSrcA,ALUSrcB,ALUOp
    输出信号为: zero, result
    然后根据表2 ALU运算功能表对result与zero赋值

    `timescale 1ns / 1ps
    //
    // Company: 
    // Engineer: 
    // 
    // Create Date: 2017/04/23 21:32:08
    // 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] ReadData1, 
        input [31:0] ReadData2,
        input [31:0] inExt,
        input [5:0] insa,
        input ALUSrcA, ALUSrcB,
        input [2:0] ALUOp,
        output reg zero,
        output reg [31:0] result
        );
    
        // 定义两个输入端口
        wire [31:0] A;
        wire [31:0] B;
    
        // ALU输入端口的数据选择器
        assign A = ALUSrcA ? insa :ReadData1;
        assign B = ALUSrcB? inExt : ReadData2;
    
        // 只要输入的值发生变化,就执行begin与end之间的内容
        always @(ReadData1 or ReadData2 or inExt or ALUSrcA or ALUSrcB or ALUOp or A or B)  
            begin  
                 case(ALUOp)
                 // 根据ALUOp相应的实现运算功能
                        3'b000: begin  
                            result = A + B;  
                            zero = (result == 0)? 1 : 0;  
                        end  
                        3'b001: begin  
                            result = A - B;  
                            zero = (result == 0)? 1 : 0;  
                        end  
                        3'b010: begin  
                            result = (A < B) ? 1 : 0;  
                            zero = (result == 0)? 1 : 0;  
                        end  
                        3'b100: begin  
                            result = A | B;  
                            zero = (result == 0)? 1 : 0;  
                        end  
                        3'b101: begin  
                            result = A & B;  
                            zero = (result == 0)? 1 : 0;  
                        end  
                        3'b011: begin  
                            result = B << A;  
                            zero = (result == 0)? 1 : 0;  
                        end   
                        3'b110: begin  
                            result = A ^ B;  
                            zero = (result == 0)? 1 : 0;  
                        end  
                        3'b111: begin  
                            result = A ^~ B;  
                            zero = (result == 0)? 1 : 0;  
                        end  
                 endcase  
             end  
    endmodule 
    

    3.设计PC
    由数据通路图可知,
    输入信号为: clk, Reset, PCWre, PCSrc, immediate,
    输出信号为: Address
    判断是否有Reset信号,如果有,将PC置为0;
    判断是否有PCSrc信号,如果有,将immediate作为偏移值加上PC中原有值存在pc中;
    否则pc自增。

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Company: 
    // Engineer: 
    // 
    // Create Date: 2017/04/23 21:47:33
    // Design Name: 
    // Module Name: PC
    // Project Name: 
    // Target Devices: 
    // Tool Versions: 
    // Description: 
    // 
    // Dependencies: 
    // 
    // Revision:
    // Revision 0.01 - File Created
    // Additional Comments:
    // 
    //////////////////////////////////////////////////////////////////////////////////
    
    
    
    module PC(clk, Reset, PCWre, PCSrc, immediate, Address); 
         // 根据数据通路图定义输入输出 
         input clk, Reset, PCWre, PCSrc;  
         input [15:0] immediate;  // 从指令中取出进行符号扩展后得来的
         output [31:0] Address;  
         reg [31:0] Address;  
    
         // clock上升沿到来或Reset下降沿到来时,执行下列函数
         always @(posedge clk or negedge Reset)  
             begin  
                  if (Reset == 0) begin  
                        Address = 0;  
                    end  
                    else if (PCWre) begin  // PCWre为1时PC更改,PCWre为0时PC不更改
                        if (PCSrc) Address = Address + 4 + immediate*4;  // 跳转指令
                         else Address = Address + 4;  // 跳转到下一指令
                    end  
              end  
    
    endmodule  
    

    4.设计signZeroExtend
    由数据通路图可知,
    输入信号为: immediate, ExtSel
    输出信号为: out
    符号扩展很简单,根据立即数的最高位进行补位:
    如果立即数最高位为1,则前面全补1;
    如果立即数最高位为0,则前面全补0.

    `timescale 1ns / 1ps
    //
    // Company: 
    // Engineer: 
    // 
    // Create Date: 2017/04/23 21:52:27
    // Design Name: 
    // Module Name: signZeroExtend
    // Project Name: 
    // Target Devices: 
    // Tool Versions: 
    // Description: 
    // 
    // Dependencies: 
    // 
    // Revision:
    // Revision 0.01 - File Created
    // Additional Comments:
    // 
    //
    
    
    module signZeroExtend(
        // 根据数据通路图定义输入和输出
        input [15:0] immediate,
        input ExtSel,
        output [31:0] out
        );
        assign out[15:0] = immediate;  // 后15位存储立即数
        assign out[31:16] = ExtSel? (immediate[15]? 16'hffff : 16'h0000) : 16'h0000; // 前16位根据立即数符号进行补1或0的操作 
    endmodule
    

    5.设计DataMemory
    由数据通路图可知,
    输入信号为: Daddr, DataIn,RD,WR
    输出信号为: DataOut
    根据WR,RD判断数据的读写,然后执行相应的读写操作。

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Company: 
    // Engineer: 
    // 
    // Create Date: 2017/04/23 22:06:47
    // 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 [31:0] DataIn,
        input RD,
        input WR,
        output reg [31:0] DataOut
        );
         // 实验要求:指令存储器和数据存储器存储单元宽度一律使用8位
         // 所以将一个32位的数据拆成4个8位的存储器单元存储
         // 将4个8位存储器恢复成32位存储器
         reg[7:0] memory[0:127];
         reg[31:0] address;      
             // read data  
             always @(RD) begin  
                 if (RD == 0) begin
                     // 因为一条指令由4个存储单元存储,所以要乘以4
                     address = (DAddr << 2);
                     // DataOut是32位的,将4个八位的内存单元合并生成32位
                     // 左移24位用于设置前八位,以此类推
                     DataOut = (memory[address]<<24)+(memory[address+1]<<16)+(memory[address+2]<<8)+memory[address+3];
                 end
             end  
    
    
             // write data  
             integer i;  
             initial begin  
                 for (i = 0; i < 128; i = i+1) memory[i] <= 0;  
             end  
             always @(WR or DAddr or DataIn)  
                 begin  
                      if (WR == 0) begin
                          address = DAddr << 2;
                          memory[address] = DataIn[31:24];
                          memory[address+1]= DataIn[23:16];
                          memory[address+2]=DataIn[15:8];
                          memory[address+3]=DataIn[7:0];
                      end  
                  end  
    endmodule
    

    6.设计instructionMemory
    由数据通路图可知,
    instructionMemory
    输入信号为: pc
    输出信号为: op, rs, rt, rd, immediate,sa

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Company: 
    // Engineer: 
    // 
    // Create Date: 2017/04/23 23:00:48
    // 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] pc,   
        output [5:0] op,   
        output [4:0] rs, rt, rd,  
        output [15:0] immediate,
        output [5:0] sa);
    
        // 实验要求:指令存储器和数据存储器存储单元宽度一律使用8位
        // 所以将一个32位的指令拆成4个8位的存储器单元存储
        // 从文件取出后将他们合并为32位的指令
        reg [7:0] mem[0:127];
        reg [31:0] address;
        reg [31:0] instruction;
        initial begin
            $readmemb("D:/Xilinx/VivadoProject/SingleCPU/instructions.txt", mem); // 从文件中读取指令二进制代码赋值给mem
            instruction = 0; // 指令初始化
        end
    
        always @(pc) begin
             // pc中一个单元是1byte,即8位,那么32位地址需要4个单元
             // pc++ <=> pc += 4(100),即pc的最后两位都为0
             // 从第三位开始取,即是代表指令的个数
             address = pc[5:2] << 2; // 因为4个内存单元存储一个指令,所以除以4得到第一个内存单元的下标
             // 将4个8位的内存单元合并为32位的指令
             instruction = (mem[address]<<24) + (mem[address+1]<<16) + (mem[address+2]<<8) + mem[address+3];
        end
        // output  
        assign op = instruction[31:26];  
        assign rs = instruction[25:21];  
        assign rt = instruction[20:16];  
        assign rd = instruction[15:11];  
        assign immediate = instruction[15:0];
        assign sa = instruction[10:6];
    
    endmodule 

    Instructions文件的部分截图:
    这里写图片描述
    每4行构成一个32位的指令,对照指令表便可写出来。

    7.设计Regfile
    由数据通路图可知,
    输入信号为: clk, RegWre, RegOut, opCode, rs, rt, rd, im, ALUM2Reg,
    dataFromALU, dataFromRW
    输出信号为: Data1,Data2
    下面列出我测试的指令列表:
    这里写图片描述

    `timescale 1ns / 1ps
    //
    // Company: 
    // Engineer: 
    // 
    // Create Date: 2017/04/23 22:18:52
    // Design Name: 
    // Module Name: Regfile
    // Project Name: 
    // Target Devices: 
    // Tool Versions: 
    // Description: 
    // 
    // Dependencies: 
    // 
    // Revision:
    // Revision 0.01 - File Created
    // Additional Comments:
    // 
    //
    
    
    module Regfile(clk, RegWre, RegDst, opCode, rs, rt, rd, im, DBDataSrc, dataFromALU, dataFromRW, Data1, Data2); 
         // 根据数据通路图定义输入和输出
         input clk, RegDst, RegWre, DBDataSrc;
         input [5:0] opCode;
         input [4:0] rs, rt, rd;
         input [10:0] im;
         input [31:0] dataFromALU, dataFromRW;  
         output [31:0] Data1, Data2;
    
         wire [4:0] writeReg;  // 要写的寄存器端口
         wire [31:0] writeData;  // 要写的数据
         // RegDst为真时,处理R型指令,rd为目标操作数寄存器,为假时处理I型指令,详见控制信号作用表
         assign writeReg = RegDst? rd : rt;
         // ALUM2Reg为0时,使用来自ALU的输出,为1时,使用来自数据存储器(DM)的输出,详见控制信号作用表
         assign writeData = DBDataSrc? dataFromRW : dataFromALU;  // 实现数据选择器
    
         // 初始化寄存器
         reg [31:0] register[0:31];  
         integer i;  
         initial begin  
             for (i = 0; i < 32; i = i+1) register[i] <= 0;  
         end  
    
         // output:随register变化而变化
         // Data1 为ALU运算时的A,当指令为sll时,A的值从立即数的16位中获得
         // Data2 位ALU运算中的B,其值始终是为rt
         assign Data1 = (opCode == 6'b011000) ? im[10:6] : register[rs];
         assign Data2 = register[rt];
    
         always @(RegDst or RegWre or DBDataSrc or writeReg or writeData) begin  
             if (RegWre && writeReg) register[writeReg] = writeData;  // 防止数据写入0号寄存器(writeReg=0)
         end  
    
    endmodule
    

    8.编写顶层模块
    定义各个模块的input与output,然后调用各个模块。

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Company: 
    // Engineer: 
    // 
    // Create Date: 2017/04/24 00:56:01
    // Design Name: 
    // Module Name: SingleCycleCPU
    // Project Name: 
    // Target Devices: 
    // Tool Versions: 
    // Description: 
    // 
    // Dependencies: 
    // 
    // Revision:
    // Revision 0.01 - File Created
    // Additional Comments:
    // 
    //////////////////////////////////////////////////////////////////////////////////
    
    module SingleCycleCPU(
        input clk, Reset, CLR,
        output wire[5:0] opCode,
        output wire[31:0] Out1, Out2, curPC, Result
    );
        wire[2:0] ALUOp;
        wire[31:0] Extout, DMOut;
        wire[15:0] immediate;
        wire[4:0] rs, rt, rd;
        wire[5:0] sa;
        wire zero, PCWre, ALUSrcA, ALUSrcB, DBDataSrc, RegWre,InsMemRW, RD, WR, ExtSel, RegDst, PCSrc;
    
        ALU alu(Out1, Out2, Extout, sa, ALUSrcA, ALUSrcB, ALUOp, zero, Result);
        PC pc(clk, Reset, PCWre, PCSrc, immediate, curPC);
        controlUnit CU(opCode, zero, PCWre, ALUSrcA, ALUSrcB, DBDataSrc, RegWre, InsMemRW, RD, WR, ExtSel, RegDst, PCSrc, ALUOp);
        dataMemory dm(Result, Out2, RD, WR, DMOut);
        instructionMemory im(curPC, opCode, rs, rt, rd, immediate, sa);
        Regfile registerfile(clk, RegWre, RegDst, opCode, rs, rt, rd, immediate, DBDataSrc, Result, DMOut, Out1, Out2);
        signZeroExtend sze(immediate, ExtSel, Extout);
    endmodule
    

    9.编写测试代码
    由于输入只有clk与reset信号,所以测试文件中只需要输入这两个值即可。为了显示更多结果,我在测试文件中也添加了相应代码以方便模拟。

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Company: 
    // Engineer: 
    // 
    // Create Date: 2017/04/24 01:21:55
    // Design Name: 
    // Module Name: cpu_sim
    // Project Name: 
    // Target Devices: 
    // Tool Versions: 
    // Description: 
    // 
    // Dependencies: 
    // 
    // Revision:
    // Revision 0.01 - File Created
    // Additional Comments:
    // 
    //////////////////////////////////////////////////////////////////////////////////
    
    /*    input clk, Reset,
        output wire[5:0] opCode,
        output wire[31:0] Out1, Out2, curPC, Result*/
    
    module SingleCircleCPUTest;
    
        // Inputs
        reg CLK;
        reg Reset;
    
        // Outputs
        wire [31:0] Out1;
        wire [31:0] Out2;
        wire [31:0] curPC;
        wire [31:0] Result;
        wire [5:0] opCode;
    
        // Instantiate the Unit Under Test (UUT)
        SingleCycleCPU uut (
            .clk(CLK), 
            .Reset(Reset),
            .opCode(opCode),
            .Out1(Out1),
            .Out2(Out2),
            .curPC(curPC),
            .Result(Result)
        );
    
        initial begin
            // Initialize Inputs
            CLK = 0;
            Reset = 0;
            #50; // 刚开始设置pc为0
                CLK = 1;
            #50;
                Reset = 1;
            forever #50 begin // 产生时钟信号
                CLK = !CLK;
            end
        end
    
    endmodule

    实验截图:

    addi操作:
    这里写图片描述

    ori操作:
    这里写图片描述

    add操作:

    这里写图描述
    sub操作:这里写图片描述
    and操作:这里写图片描述or操作:这里写图片描述beq操作:这里写图片描述sll操作sw操作这里写图片描述lw操作这里写图片描述beq操作:这里写图片描述halt操作:这里写图片描述


    以上内容皆为本人观点,欢迎大家提出批评和指导,我们一起探讨!


    展开全文
  • 计算机组成课程作业源码。MIPS单周期/多周期流水线设计,多周期流水线实现了数据冒险,控制冒险。代码结构清晰,欢迎交流讨论。
  • 31条指令单周期cpu设计(Verilog)-(二)总体设计

    万次阅读 多人点赞 2018-07-14 11:27:27
    31条指令单周期cpu设计(Verilog)-(一)相关软件 31条指令单周期cpu设计(Verilog)-(二)总体设计 31条指令单周期cpu设计(Verilog)-(三)指令分析 (重要) 31条指令单周期cpu设计(Verilog)-(四)数据输入输出关系表 ...
    • 目录

    31条指令单周期cpu设计(Verilog)-(一)相关软件

    31条指令单周期cpu设计(Verilog)-(二)总体设计

    31条指令单周期cpu设计(Verilog)-(三)指令分析                       (重要)

    31条指令单周期cpu设计(Verilog)-(四)数据输入输出关系表     (重要)

    31条指令单周期cpu设计(Verilog)-(五)整体数据通路图设计     (重要)

    31条指令单周期cpu设计(Verilog)-(六)指令操作时间表设计     (重要)

    31条指令单周期cpu设计(Verilog)-(七)整体代码结构                (重要)

    31条指令单周期cpu设计(Verilog)-(八)上代码→指令译码以及控制器(不重要)

    31条指令单周期cpu设计(Verilog)-(九)上代码→基本模块                  (不重要)

    31条指令单周期cpu设计(Verilog)-(十)上代码→顶层模块设计&总结

    • 说在前面

    开发环境:Vivado

    语言:Verilog

    cpu框架:Mips

    控制器:组合逻辑

    单周期处理器是指所有的指令在一个时钟周期内完成的处理器,尽管不同指令执行时间不同,但对单周期处理器而言,时钟周期必须设计成对所有指令都等长。在单周期处理器中,一条指令执行过程中数据通路的任何资源都不能被重复使用,因此,任何需要被多次使用的资源(如加法器)都需要设置多个,否则就会发生资源冲突。

    • 总体设计

    常规套路是从简单的八条指令cpu讲起,然后自己去扩展,31条指令cpu按照八条的那个流程来,无非是工作量大了点,网上八条指令cpu的教程也挺多的。

     

    指令介绍

    cpu的理论知识就不再多讲了,首先来看看我们要实现的31条指令。

     

    这些指令的详细介绍mips官方有给出:

    mips指令集

    在这部分(这个pdf带跳转功能的)

     

    在实现过程中,这31条指令看起来挺多,但是实际上部分指令的数据通路几乎一样,比如add---nor这几个,一定程度上减少了工作量。

    注意:这里面比较难的几条指令是lw,sw,jal

     

    设计流程

     

    1. 确定各条指令所需要的部件
    2. 确定各条指令中各个部件的输入输出关系
    3. 确定数据通路总图
    4. 根据各条指令所需要的部件以及总通路图,确定指令操作时间表(真值表)
    5. 根据指令操作时间表,设计控制器
    6. 根据总通路图以及指令操作时间表,码代码去,编写Verilog代码

     

    先放张总图压压

     

    1. PC存放指令地址(通过pc+4来读取下一条指令)
    2. (ram1)IMEM存放指令集
    3. 根据指令地址IMEM中取出对应指令
    4. 指令读取出来后送入指令译码器(具体是哪一条指令)
    5. 译码后将结果送入控制器,将该条指令相关部件的控制信号置为1(激活),使得cpu中该条指令的数据通路激活,进而执行此指令
    6. pc+4获取下一条指令,循环操作

    以上就是cpu的具体执行过程了,看起来还是挺简单的嘛。

     


     以上就是这节的内容了,下一节将具体介绍各条指令各个部件。

     

     

     

     

     

    展开全文
  • 16位单周期处理器的verilog实现。包括存储模块和仿真模块,结构很清晰,大学计算机组成原理课程必备。
  • 单周期CPUVerilog实现

    2012-03-19 22:34:37
    单周期CPU,实现了lw,sw,add,sub,slt,jmp指令
  • 设计实现MIPS单周期CPUVerilog语言设计实现
  • 单周期CPU——verilog语言实现

    万次阅读 多人点赞 2018-06-29 11:28:38
    单周期CPU设计与实现 关于单周期CPU设计问题,相信各位在课程上也有所学习,以下是个人的一些理解 整个项目的资源下载:这里写链接内容 实验内容 基本要求 PC和寄存器组写状态使用时钟触发,这是必须...

    单周期CPU的设计与实现

    关于单周期CPU的设计问题,相信各位在课程上也有所学习,以下是个人的一些理解

    实验内容

    基本要求

    1. PC和寄存器组写状态使用时钟触发,这是必须的!
    2. 指令存储器和数据存储器存储单元宽度一律使用8位,即一个字节的存储单位。不能使用32位作为存储器存储单元宽度。
    3. 控制器部分要学会用控制信号真值表方法分析问题并写出逻辑表达式;或者用case语句方法逐个产生各指令控制信号。注意:控制信号的产生不能使用时钟触发!
    4. 必须写一段测试用的汇编程序,而且必须包含所要求的所有指令,slti指令必须检查两种情况:“小于”和“大于等于”;beq、bne:“不等”和“等”。这段汇编程序必须尽量优化且出现在实验报告中,同时,给出每条指令在内存中的地址。检查实验时,必须提供。
    5. Always@(…)的敏感信号表中,时序触发和电平触发不能同时出现,即不能混用。
    6. 实验报告中应该有每条指令执行的波形(截图),用于说明该指令的正确性。

    指令的分析

    同时该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)sll rd, rt,sa
    011000 未用 rt(5位) rd(5位) sa reserved
    功能:rd<-rt<<(zero-extend)sa,左移sa位 ,(zero-extend)sa

    ==>比较指令
    (8)slti rt, rs,immediate 带符号
    011011 rs(5位) rt(5位) immediate(16位)
    功能:if (rs <(sign-extend)immediate) rt =1 else rt=0, 具体请看表2 ALU运算功能表,带符号

    ==> 存储器读/写指令
    (9)sw rt ,immediate(rs) 写存储器
    100110 rs(5位) rt(5位) immediate(16位)
    功能:memory[rs+ (sign-extend)immediate]←rt;immediate符号扩展再相加。即将rt寄存器的内容保存到rs寄存器内容和立即数符号扩展后的数相加作为地址的内存单元中。

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

    ==> 分支指令
    (11)beq rs,rt,immediate
    110000 rs(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
    110001 rs(5位) rt(5位) immediate
    功能:if(rs!=rt) pc←pc + 4 + (sign-extend)immediate <<2 else pc ←pc + 4
    特别说明:与beq不同点是,不等时转移,相等时顺序执行。

    ==>跳转指令
    (13)j addr
    111000 addr[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
    111111 00000000000000000000000000(26位)
    功能:停机;不改变PC的值,PC保持不变。

    实验原理

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

    这里写图片描述

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

    这里写图片描述
    图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),看功能表

    相关部件及引脚说明:

    • Instruction Memory:指令存储器,
    • Iaddr,指令存储器地址输入端口
    • IDataIn,指令存储器数据输入端口(指令代码输入端口)
    • IDataOut,指令存储器数据输出端口(指令代码输出端口)
    • RW,指令存储器读写控制信号,为0写,为1读
    • Data Memory:数据存储器,
    • Daddr,数据存储器地址输入端口
    • DataIn,数据存储器数据输入端口
    • DataOut,数据存储器数据输出端口
    • /RD,数据存储器读控制信号,为0读
    • /WR,数据存储器写控制信号,为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,则zero=1;否则zero=0

    表2 ALU运算功能表
    这里写图片描述

    需要说明的是以上数据通路图是根据要实现的指令功能的要求画出来的,同时,还必须确定ALU的运算功能(当然,以上指令没有完全用到提供的ALU所有功能,但至少必须能实现以上指令功能操作)。从数据通路图上可以看出控制单元部分需要产生各种控制信号,当然,也有些信号必须要传送给控制单元。从指令功能要求和数据通路图的关系得出以上表1,这样,从表1可以看出各控制信号与相应指令之间的相互关系,根据这种关系就可以得出控制信号与指令之间的关系表(留给学生完成),再根据关系表可以写出各控制信号的逻辑表达式,这样控制单元部分就可实现了。
    指令执行的结果总是在时钟下降沿保存到寄存器和存储器中,PC的改变是在时钟上升沿进行的,这样稳定性较好。另外,值得注意的问题,设计时,用模块化、层次化的思想方法设计,关于如何划分模块、如何整合成一个系统等等,是必须认真考虑的问题。

    实验器材

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

    实验分析

    1. CPU的设计,主要是依照实验原理中的数据通路图以及控制信号的表格进行坟墓快设计的。其中分为十个底层模块以及一个顶层模块,具体的模块内容及作用如下:
    顶层模块(1个):
    (1) Main
    作用:负责统筹11个模块的输入输出参数,通过设置多个wire类型的变量,及时拿到各个模块的输出,并传入到需要改输入参数的模块中去。

    关键代码:

    1.  module Main(  
    2.      input CLK,  //时钟  
    3.      input Reset,    //重置信号  
    4.      output zero,pcWre,  //ALU结果是否为0, PC更改信号  
    5.      output[1:0] pcSrc,  //PC四选一选择器的控制信号  
    6.      output[2:0] aluop,  //ALU的执行信号  
    7.      output[5:0] op, //指令操作的代码  
    8.      output[31:0] readData1, readData2, extendData, writeData, o_p, currentAdd, Result   //寄存器1读取的值,寄存器2读取的值,拓展符号信号,写入寄存器的数据, 32位的整条指令, 当前指令的地址, ALU输出的结果  
    9.      );  
    底层模块(10个):
    

    (1) PC
    作用:根据传入的PCWre控制信号,进行地址移位操作。当PCWre为0时,指令地址保持不变;当PCWre为1时,指令地址变更为新地址。

    代码:

    1.  module PC(  
    2.      input CLK,  
    3.      input Reset,  
    4.      input PCWre,//值为0时不更改,代表停机指令;值为1的时候进行更改  
    5.      input [31:0] NewAdd,  
    6.      output reg[31:0] CurrentAdd  
    7.      );  
    8.        
    9.      initial begin  
    10.         CurrentAdd <= -8;  
    11.     end  
    12.       
    13.     always@(posedge CLK or posedge Reset)  
    14.         begin  
    15.             if(Reset == 0) CurrentAdd <= 0;  
    16.             else   
    17.                 begin  
    18.                     if(PCWre == 0) CurrentAdd <= CurrentAdd;  
    19.                     else CurrentAdd <= NewAdd;  
    20.                 end  
    21.         end  
    22. endmodule  

    (2) ROM(指令存储器)
    作用:通过从文件中读取相关的指令,并按照实验要求存储在8位的寄存器中,并根据传入的地址,输出对应地址的指令。

    代码:

    1.  module ROM ( rd, addr, dataOut); // 存储器模块  
    2.      input rd; // 读使能信号  
    3.      input [ 31:0] addr; // 存储器地址  
    4.      output reg [31:0] dataOut; // 输出的数据  
    5.    
    6.      reg [7:0] rom [99:0]; // 存储器定义必须用reg类型, 存储器存储单元8位长度, 共100个存储单元  
    7.        
    8.      initial // 加载数据到存储器rom。 注意: 必须使用绝对路径, 如: E:/Xlinx/VivadoProject/ROM/(自己定)  
    9.      begin       
    10.         $readmemb ("D:/Xlinx/VivadoProject/CPU/rom_data.txt", rom); // 数据文件rom_data(.coe或.txt) 。 未指定, 就从0地址开始存放。  
    11.         //rom[addr][7:0] = 1'b00000100;  
    12.         dataOut = 0;  
    13.     end  
    14.       
    15.     always @( rd or addr ) begin  
    16.         if (rd==1) begin // 为0, 读存储器。 大端数据存储模式  
    17.             dataOut[31:24] = rom[addr];  
    18.             dataOut[23:16] = rom[addr+1];  
    19.             dataOut[15:8] = rom[addr+2];  
    20.             dataOut[7:0] = rom[addr+3];  
    21.         end  
    22.     end  
    23. endmodule  

    (3) ControlUnit
    作用:根据PC传出的指令,设置相关的控制信号,相关信号的设置,详见实验原理中的控制信号作用表

    代码:

    1.  module ControlUnit(  
    2.      input [5:0] op,  
    3.      input zero,  
    4.      output reg Reset,  
    5.      output reg PCWre,  
    6.      output reg ALUSrcA,  
    7.      output reg ALUSrcB,  
    8.      output reg DBDataSrc,  
    9.      output reg RegWre,  
    10.     output reg InsMemRW,  
    11.     output reg mRD,  
    12.     output reg mWR,  
    13.     output reg RegDst,  
    14.     output reg ExtSel,  
    15.     output reg [1:0] PCSrc,  
    16.     output reg [2:0] ALUOp  
    17.     );  

    (4) RegFile(寄存器组)
    作用:暂时存储指令操作过程中产生的数据。按照实验的要求,设置成8位的寄存器。而且根据输入的读取数据的地址,读取对应寄存器的数据,并根据输入信号RegWre,确定是否能够对寄存器进行写入操作。当RegWre为0时,无法进行写入操作,当RegWre为1时,进行写入操作

    代码:

    1.  module RegFile(CLK,RST,RegWre,ReadReg1,ReadReg2,WriteReg,WriteData,  
    2.      ReadData1,ReadData2);  
    3.      input CLK;  
    4.      input RST;  
    5.      input RegWre;  
    6.      input [4:0] ReadReg1,ReadReg2,WriteReg;  
    7.      input [31:0] WriteData;  
    8.      output [31:0] ReadData1,ReadData2;  
    9.        
    10.     reg [31:0] regFile[1:31]; // 寄存器定义必须用reg类型  
    11.       
    12.     initial begin  
    13.         for(i=1;i<32;i=i+1)  
    14.                 regFile[i] <= 0;  
    15.     end  
    16.     integer i;  
    17.     assign ReadData1 = (ReadReg1 == 0) ? 0 : regFile[ReadReg1]; // 读寄存器数据  
    18.     assign ReadData2 = (ReadReg2 == 0) ? 0 : regFile[ReadReg2];  
    19.       
    20.     always @ (negedge CLK or negedge RST) begin // 必须用时钟边沿触发  
    21.         if (RST==0) begin  
    22.             for(i=1;i<32;i=i+1)  
    23.             regFile[i] <= 0;  
    24.         end  
    25.         else if(RegWre == 1 && WriteReg != 0) // WriteReg != 0, $0寄存器不能修改  
    26.             regFile[WriteReg] <= WriteData; // 写寄存器  
    27.     end  
    28.       
    29. endmodule  

    (5) ALU
    作用:根据传入的控制信号,对传入的两个值进行相关的算术运算或逻辑运算,除了返回结果外,还有一个输出信号记录结果是否为0。

    代码:

    1.  module ALU(  
    2.      input [2:0] ALUopcode,  
    3.      input [31:0] rega,  
    4.      input [31:0] regb,  
    5.      output reg [31:0] result,  
    6.      output zero  
    7.      );  
    8.      initial  
    9.      begin  
    10.         result = 0;  
    11.     end  
    12.     assign zero = (result==0)?1:0;  
    13.     always @( ALUopcode or rega or regb ) begin  
    14.         case (ALUopcode)  
    15.             3'b000 : result = rega + regb;  
    16.             3'b001 : result = rega - regb;  
    17.             3'b010 : result = regb << rega;   
    18.             3'b011 : result = rega | regb;  
    19.             3'b100 : result = rega & regb;  
    20.             3'b101 : result = (rega < regb)?1:0; // 不带符号比较  
    21.             3'b110 : begin // 带符号比较  
    22.                 if(rega < regb && (rega[31] == regb[31]))result = 1;  
    23.                 else if (rega[31] == 1 && regb[31] == 0) result = 1;  
    24.                 else result = 0;  
    25.                 end  
    26.             3'b111 : result = rega ^ regb;  
    27.             default : begin  
    28.                 result = 32'h00000000;  
    29.                 $display (" no match");  
    30.                 end  
    31.         endcase  
    32.     end  
    33. endmodule  

    (6) RAM(数据存储器)
    作用:按照要求,利用8位的寄存器来存储数据。根据传入的控制信号来进行读写操作。当nRD为1时,进行读取操作,当为0时,不进行读取操作,并输出高阻态;当nWR为0时,进行写入操作,当为1时,不进行写入操作。

    代码:

    1.  module RAM(  
    2.      input clk,  
    3.      input [31:0] address,  
    4.      input [31:0] writeData, // [31:24], [23:16], [15:8], [7:0]  
    5.      input nRD, // 为0, 正常读; 为1,输出高组态  
    6.      input nWR, // 为1, 写; 为0, 无操作  
    7.      output [31:0] Dataout  
    8.      );  
    9.        
    10.     reg [7:0] ram [0:60]; // 存储器定义必须用reg类型  
    11.       
    12.     // 读  
    13.     //assign是随时赋值语句,Data有变化,随时写进到ram中去  
    14.     assign Dataout[7:0] = (nRD==1)?ram[address + 3]:8'bz; // z 为高阻态  
    15.     assign Dataout[15:8] = (nRD==1)?ram[address + 2]:8'bz;  
    16.     assign Dataout[23:16] = (nRD==1)?ram[address + 1]:8'bz;  
    17.     assign Dataout[31:24] = (nRD==1)?ram[address ]:8'bz;  
    18.     // 写  
    19.     always@( negedge clk ) begin // 用时钟下降沿触发写存储器, 个例  
    20.         if( nWR==0 ) begin  
    21.             ram[address] <= writeData[31:24];  
    22.             ram[address+1] <= writeData[23:16];  
    23.             ram[address+2] <= writeData[15:8];  
    24.             ram[address+3] <= writeData[7:0];  
    25.         end  
    26.     end  
    27. endmodule  

    (7) Pccounter
    作用:根据传入的信号,进行传入PC的新地址。当PCSrc为0时,新地址将读取下一条指令,即地址加4;当PCSrc为1时,在原有地址的基础上,跳转立即数的指令数目;当PCSrc为2时,跳转到j指令所指向的指令地址。

    代码:

    1.  module PCcounter(  
    2.      input [1:0] PCSrc,  
    3.      input [31:0] currentAddress,  
    4.      output reg [31:0] newAddress,  
    5.      input [31:0] outData,  
    6.      input [25:0] jAddress  
    7.      );  
    8.        
    9.      wire [31:0] temp_one, temp_two, temp_three;  
    10.     assign temp_one = currentAddress + 4;  
    11.     assign temp_two[25:0] = jAddress[25:0];  
    12.     assign temp_three = temp_two << 2;  
    13.     always@(PCSrc or currentAddress or outData or jAddress or temp_one)  
    14.         begin  
    15.             case(PCSrc)  
    16.                 2'b00:  newAddress = currentAddress+4;  
    17.                 2'b01:    
    18.                     begin  
    19.                         newAddress = (currentAddress+4)+ (outData << 2);  
    20.                         newAddress[1:0] = 1'b00;  
    21.                     end  
    22.                   
    23.                 2'b10:   
    24.                 begin  
    25.                      newAddress[31:28] = temp_one[31:28];  
    26.                      newAddress[27:0] = temp_three[27:0];  
    27.                 end  
    28.              endcase  
    29.         end  
    30. endmodule  

    (8) Sign、zero extend
    作用:根据传入的控制信号,对出入的数据进行相对应的拓展。当ExtSel为0时,对传入的立即数进行零拓展;当ExtSel为1时,对传入的立即数进行信号拓展。
    代码:

    1.  module Extend(  
    2.      input [15:0] immediate,  
    3.      input ExtSel,  
    4.      output reg[31:0] outData  
    5.      );  
    6.        
    7.      always@(immediate or ExtSel)  
    8.      begin  
    9.          case(ExtSel)  
    10.             1'b0:  
    11.                 begin  
    12.                     outData[15:0] = immediate;  
    13.                     outData[31:16] = 16'h0000;  
    14.                 end  
    15.             1'b1:  
    16.                 begin  
    17.                     outData[15:0] = immediate;  
    18.                     outData[31:16] = (immediate[15])? 16'hffff : 16'h0000;  
    19.                 end   
    20.         endcase  
    21.     end  
    22. endmodule  

    (9) 5bit_selecter
    作用:根据传入的控制信号,对两个传入的5位数据进行二选一操作。具体应用在选择rt或者rs作为写入地址。

    代码:

    1.  module select_5_bit(  
    2.      input [4:0] select_one,  
    3.      input [4:0] select_two,  
    4.      input control,  
    5.      output [4:0] result  
    6.      );  
    7.        
    8.      assign result = (control == 1'b0 ? select_one : select_two);  
    9.  endmodule  

    (10) 32bit_selecter
    作用:根据传入的控制信号,对两个传入的32位数据进行二选一操作。具体应用在传入ALU的数据A的选择、传入ALU的数据B的选择以及写入寄存器中数据的选择。

    代码:

    1.  module select_32_bit(  
    2.      input [31:0] select_one,  
    3.      input [31:0] select_two,  
    4.      input control,  
    5.      output [31:0] result  
    6.      );  
    7.        
    8.      assign result = (control == 1'b0 ? select_one : select_two);  
    9.  endmodule  

    2. CPU正确性的验证
    为验证所设计CPU的正确性,通过输入以下指令,观察结果来确认

    地址指令op(6)rs(5)rt(5)rd(5)/immediate (16)16进制数代码
    0x00000000addi 1, 1 , 0,800000100000000010000 0000 0000 1000= 04010008
    0x00000004ori 2, 2 , 0,201000000000000100000 0000 0000 001040400002
    0x00000008add 3, 3 , 2,$100000000010000010001 1000 0000 000000411800
    0x0000000Csub 5, 5 , 3,$200001000011000100010 1000 0000 000008622800
    0x00000010and 4, 4 , 5,$201000100101000100010 0000 0000 000044A22000
    0x00000014or 8, 8 , 4,$201001000100000100100 0000 0000 000048824000
    0x00000018sll 8, 8 , 8,101100000000010000100 0000 0100 000060084040
    0x0000001Cbne 8, 8 , 1,-2 (≠,转18)11000101000000011111 1111 1111 1110C501FFF2
    0x00000020slti 6, 6 , 2,801101100010001100000 0000 0000 10006C460008
    0x00000024slti 7, 7 , 6,001101100110001110000 0000 0000 00006CC70000
    0x00000028addi 7, 7 , 7,800000100111001110000 0000 0000 100004E70008
    0x0000002Cbeq 7, 7 , 1,-2 (=,转28)11000000111000011111 1111 1111 1110C0E1FFFE
    0x00000030sw 2,4( 2 , 4 ( 1)10011000001000100000 0000 0000 010098220004
    0x00000034lw 9,4( 9 , 4 ( 1)10011100001010010000 0000 0000 01009C290004
    0x00000038j 0x0000004011100000000000000000 0000 0001 0000E0000010
    0x0000003Caddi 10, 10 , 0,1000000100000010100000 0000 0000 1010040A000A
    0x00000040halt11111100000000000000000000000000 =FC000000
    0x00000044
    0x00000048
    0x0000004C

    测试代码跟其他的配置文件见文章开头的CSDN下载

    展开全文
  • Verilog单周期CPU设计(超详细)

    万次阅读 多人点赞 2019-01-19 12:27:24
    实验 单周期CPU一、设计目的与目标实验内容实验要求二、课程设计器材硬件平台软件平台三、 CPU逻辑设计总体方案指令模块MIPS指令格式指令处理流程数据通路总体结构图设计流程逻辑图四、模块详细设计PCAdd4...
  • 单周期的整个项目,在电脑上安装vivado即可添加项目,我个人使用的是15版的。另外需要看波形图的,点击仿真,调节相关参数即可
  • (Verilog)单周期CPU设计

    万次阅读 多人点赞 2017-05-23 23:53:54
    (Verilog)单周期CPU设计首先是基础资料部分(借用学校资料):一.实验目的(1) 掌握单周期CPU数据通路图的构成、原理及其设计方法; (2) 掌握单周期CPU的实现方法,代码实现方法; (3) 认识和掌握指令与CPU的关系;...
  • 单周期CPU设计,使用结构级语句与描述级语句构建寄存器堆、ALU、CONUNIT等模块,支持12条指令:add、sub、j、bne、bnq等
  • Verilog 单周期cpu设计
  • 单周期 CPU verilog编写

    2009-12-22 21:04:55
    verilog语言编写的单周期CPU,QuartusII平台运行。
  • verilog单周期CPU设计

    2017-12-03 16:48:16
    支持指令集为:{addu, subu, ori, lw, sw, beq, lui, jal, jr,nop,sll,j,lh,sh}。 处理器为单周期设计
  • Verilog单周期CPU

    2018-07-07 09:54:27
    Verilog 单周期CPU设计 能通过仿真 相关测试文件已经放在压缩包

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,673
精华内容 669
关键字:

单周期cpu设计verilog