精华内容
下载资源
问答
  • 多周期CPU-源码

    2021-02-09 17:59:01
    多周期CPU
  • 周期CPU,多周期CPU

    千次阅读 2021-05-29 21:06:48
    周期CPU:一个时钟周期完成一条指令,如果一个程序有条指令,则时钟周期的时间根据执行时间最长的那条指令为主。执行一条指令就需要一个时钟周期则CPI为1。 多周期CPU:一条指令被分成了若干个阶段,假设为n个,...
    1. 单周期CPU:一个时钟周期完成一条指令,如果一个程序有多条指令,则时钟周期的时间根据执行时间最长的那条指令为主。执行一条指令就需要一个时钟周期则CPI为1。
    2. 多周期CPU:一条指令被分成了若干个阶段,假设为n个,每执行一条指令需要花费n个时钟周期,所以执行一条指令就需要n个时钟周期CPI为n。
      多周期CPU比单周期CPU的优势在于:因为一个程序的不同指令所需要的执行时间是不同的,所以如果按照单周期处理的话,无论什么指令我都按照最长的那条指令去处理,可能我只要占用CPU1s,但是你给了我100s,其中99sCPU都在等待,闲着没事干,这完全是在浪费CPU。多周期CPU就是程序中的每一条指令要多少时间我就给你多少时间,比如第一条指令要是2s,那我就给你2s的CPU,第二条指令要5s,我就给你5s,多周期CPU完成这2条指令一共是7s,如果是单周期总时间就需要10s,多周期的CPU的效率比单周期高吧。但是多周期CPU也有缺点,就是同一时间不能运行多条指令无法实现CPU并行工作,因为有的时候一个程序执行的不同指令可能用的是CPU中的不同部件,如果可以让CPU中的所有部件都能不闲着那效率不就更高了,所以就有了指令流水线。
    展开全文
  • Verilog多周期CPU

    2018-07-07 10:11:46
    Verilog多周期CPU 已通过仿真测试 相关文件均在压缩包
  • MIPS多周期CPU设计

    2016-12-15 23:40:47
    设计一个多周期CPU,该CPU至少能实现以下指令功能操作。 多周期CPU指的是将整个CPU的执行过程分成几个阶段,每个阶段用一个时钟去完成,然后开始下一条指令的执行,而每种指令执行时所用的时钟数不尽相同,这就是...
  • 多周期CPU控制单元

    2018-12-29 20:13:08
    多周期CPU设计的时候弄的控制单元里各个控制信号的取值……可能会有点小问题,但是设计时没有出现大错误
  • 多周期CPU设计

    万次阅读 多人点赞 2016-05-24 22:47:01
    和单周期CPU的设计相同,都是为了实现一系列的指令功能,但需要指出的是何为周期(注意与前面写道的单周期的区别,这也是设计的关键之处) 多周期CPU指的是将整个CPU的执行过程分成几个阶段,每个阶段用一个时钟去...

    ------更新一下bug(测试代码有毒)-------

    和单周期CPU的设计相同,都是为了实现一系列的指令功能,但需要指出的是何为多周期(注意与前面写道的单周期的区别,这也是设计的关键之处)

    多周期CPU指的是将整个CPU的执行过程分成几个阶段,每个阶段用一个时钟去完成,然后开始下一条指令的执行,而每种指令执行时所用的时钟数不尽相同,这就是所谓的多周期CPU。

    理解完多周期与单周期的区别后,开始我们的多周期CPU设计之路(可以随时对应单周期的设计,注意联系与区别)。

    需要设计的指令及格式如下:

    ==>算术运算指令

    (1)add rd, rs, rt

    000000

    rs(5位)

    rt(5位)

    rd(5位)

    reserved

    功能:rd<-rs + rt

        (2)sub rd, rs, rt

    000001

    rs(5位)

    rt(5位)

    rd(5位)

    reserved

    完成功能:rd<-rs - rt

    (3)addi  rt, rs, immediate

    000010

    rs(5位)

    rt(5位)

    immediate(16位)

    功能:rt<-rs + (sign-extend)immediate

     

    ==>逻辑运算指令

    (4)or rd, rs, rt

    010000

    rs(5位)

    rt(5位)

    rd(5位)

    reserved

    功能:rd<-rs | rt

    (5)and rd, rs, rt

    010001

    rs(5位)

    rt(5位)

    rd(5位)

    reserved

    功能:rd<-rs & rt

    (6)ori rt, rs, immediate

    010010

    rs(5位)

    rt(5位)

    immediate

    功能:rt<-rs | (zero-extend)immediate

     

    ==>移位指令

    (7)sll rd, rs,sa

    011000

    rs(5位)

    未用

    rd(5位)

    sa

    reserved

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

     

    ==>传送指令

        (8)move  rd, rs

    100000

    rs(5位)

    00000

    rd(5位)

    reserved

    功能:rd<-rs + $0

     

    ==>比较指令

    (9) slt rd, rs, rt

    100111

    rs(5位)

    rt(5位)

    rd(5位)

    reserved

    功能:如果(rs<rt),则rd=1;  否则 rd=0

     

    ==>存储器读写指令

    (10)sw rt, immediate(rs)

    110000

    rs(5位)

    rt(5位)

    immediate(16位)

        功能:memory[rs+ (sign-extend)immediate]<-rt

    (11)lw rt, immediate(rs)

    110001

    rs(5位)

    rt(5位)

    immediate(16位)

    功能:rt <- memory[rs + (sign-extend)immediate]

     

    ==>分支指令

        (12)beq rs,rt, immediate (说明:immediate是从pc+4开始和转移到的指令之间间隔条数)    

    110100

    rs(5位)

    rt(5位)

    immediate(16位)

    功能:if(rs=rt) pc <-pc+ 4 + (sign-extend)immediate <<2

     

    ==>跳转指令

    (13)j addr    

    111000

    addr[27..2]

    功能:pc <{pc[31..28],addr[27..2],0,0},转移

    (14)jr rs    

    111001

    rs(5位)

    未用

    未用

    reserved

    功能:pc<-rs,转移

     

    ==>调用子程序指令

    (15)jal addr    

    111010

    addr[27..2]

    功能:调用子程序,pc <- {pc[31..28],addr[27..2],0,0};$31<-pc+4,返回地址设置;子程序返回,需用指令 jr  $31。

    ==>停机指令

    (16)halt (停机指令)

    111111

    00000000000000000000000000(26位)

    不改变pc的值,pc保持不变。


    设计原理

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

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

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

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

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

        实验中就按照这五个阶段进行设计,这样一条指令的执行最长需要五个(小)时钟周期才能完成,但具体情况怎样?要根据该条指令的情况而定,有些指令不需要五个时钟周期的,这就是多周期的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:为地址。


    状态的转移有的是无条件的,例如从IF状态转移到ID 和 EXE状态就是无条件的;有些是有条件的,例如ID 或 EXE状态之后不止一个状态,到底转向哪个状态由该指令功能,即指令操作码决定。每个状态代表一个时钟周期。


    图3是多周期CPU控制部件的电路结构,三个D触发器用于保存当前状态,是时序逻辑电路,RST用于初始化状态“000“,另外两个部分都是组合逻辑电路,一个用于产生下一个阶段的状态,另一个用于产生每个阶段的控制信号。从图上可看出,下个状态取决于指令操作码和当前状态;而每个阶段的控制信号取决于指令操作码、当前状态和反映运算结果的状态zero标志等。

     

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

    特别提示,图上增加IR指令寄存器,目的是使指令代码保持稳定,还有pc增加写使能控制信号pcWre,也是确保pc适时修改,原因都是和多周期工作的CPU有关。ADR、BDR、ALUout、ALUM2DR四个寄存器不需要写使能信号,其作用是切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延时变为多个分段小延时。

    表1 控制信号作用

    控制信号名

    状态“0”

    状态“1”

    PCWre

    PC不更改,相关指令:halt

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

    ALUSrcB

    来自寄存器堆data2输出,相关指令:add、sub、addi、or、and、ori、move、beq、slt

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

    ALUM2Reg

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

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

    RegWre

    无写寄存器组寄存器,相关指令:

    beq、j、sw、jr、halt

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

    WrRegData

    写入寄存器组寄存器的数据来自pc+4(pc4),相关指令:jal,写$31

    写入寄存器组寄存器的数据来自存储器、寄存器组寄存器和ALU运算结果,相关指令:add、addi、sub、or、and、ori、slt、sll、move、lw

    InsMemRW

    读指令存储器(Ins. Data),初始化为0

    写指令存储器

    DataMemRW

    读数据存储器(Data MEM),相关指令:lw

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

    IRWre

    IR(指令寄存器)不更改

    IR寄存器写使能。向指令存储器发出读指令代码后,这个信号也接着发出,在时钟上升沿,IR接收从指令存储器送来的指令代码。与每条指令都相关。

     

    ALUOp[2..0]

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

    PCSrc[1..0]

    00:pc<-pc+4,相关指令:add、addi、sub、or、ori、and、move、

        slt、sll、sw、lw、beq(zero=0)

    01:pc<-pc+4+(sign-extend)immediate,同时zero=1,相关指令:beq

    10:pc<-rs,相关指令:jr

    11:pc<-pc(31..28],addr,0,0  ,相关指令:j、jal

    RegOut[1..0]

    写寄存器组寄存器的地址,来自:

    00:0x1F($31),相关指令:jal,用于保存返回地址($31<-pc+4)

    01:rt字段,相关指令:addi、ori、lw

    10:rd字段,相关指令:add、sub、or、and、move、slt、sll

    11:未用

    ExtSel[1..0]

    00:(zero-extend)sa,相关指令:sll

    01:(zero-extend)immediate,相关指令:ori

    10:(sign-extend)immediate,相关指令:addi、lw、sw、beq

    11:未用

    相关部件及引脚说明:

    InstructionMemory指令存储器

            Iaddr,指令地址输入端口

            DataIn,存储器数据输入端口

            DataOut,存储器数据输出端口

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

    DataMemory数据存储器

            Daddr,数据地址输入端口

            DataIn,存储器数据输入端口

            DataOut,存储器数据输出端口

            RW,数据存储器读写控制信号,为1写,为0读

    RegisterFile:(寄存器组)

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

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

            Write Reg,将数据写入的寄存器,其地址输入端口(rt、rd)

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

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

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

            WE,写使能信号,为1时,在时钟上升沿写入

    IR:    指令寄存器,用于存放正在执行的指令代码

    ALU

            result,ALU运算结果

            zero,运算结果标志,结果为0输出1,否则输出0

     

    表2 ALU运算功能表       

    ALUOp[2..0]

    功能

    描述

    000

    Y = A + B

    001

    Y = A – B

    010

    if (A<B)

    Y = 1; else Y = 0;

    比较A与B

    011

    Y = A>>B

    A右移B位

    100

    Y = A<<B

    A左移B位

    101

    Y = A ∨ B

    110

    Y = A ∧ B

    111

    Y = A ⊕ B

    异或

     


    分析与设计

    此次实验是在上次单周期CPU基础上的改进,基本框架是相同的,但是相比单周期CPU的设计最大的不同就是“多周期”,何为多周期,在实现上与单周期又有何区别?简单的来说就是每个时钟周期内只执行一个阶段,而不是像单周期那样一个时钟周期就执行完整个指令,这就是单周期与多周期的主要区别。当然,由此也衍生出了其他的几个区别,比如,数据传输的延迟问题,增加的跳转指令等使得数据通路图变得复杂了很多。

    根据这些区别,就可以开始在单周期CPU基础进行改进了。具体如下:

    首先,确定每个指令的状态转化关系,具体转化图见上面原理分析,例如指令add的指令状态转化是IF(000)->ID(001)->EXE(110)->WB(111)->IF;所以,需要设置两个3位的状态变量(stage)和(next_stage)来表示状态的转变。由于指令是用来控制指令执行的,所以需要把指令状态的转变实现发在控制单元(controlUnit)中。

    其次,就是数据传输延迟的问题,从数据通路图中可以看出,寄存器(RegisterFile)输出处存在两个延迟(ADR)和(BDR),计算单元(ALU)的输出处存在一个延迟,数据存储器(DataMemory)输出存在一个延迟,指令寄存器(InsMemory)输出处存在一个延迟,当然这里延迟需要控制信号IRWre的额外控制。综上来看,前四个延迟可以设计一个叫DataLate的简单模板模块(因为它们的输入、输出完全相同),具体实现如下。最后一个延迟可以放在INSMemory模块中。

    module DataLate(input [31:0] i_data,

                    input clk,

                    output reg [31:0] o_data);

     

       always @(negedge clk) begin

           o_data = i_data;

        end

    endmodule

    最后就是融入增加的跳转指令,比如根据数据通路图增添了一个如下的地址模块


    另外,其他增加的数据线的增加就具体加入到相应模块中作为输入、输出。

    所以,现在在单周期CPU的基础上,可以画出整个多周期CPU的逻辑图。


    一、         控制单元(controlUnit.v)

    相比单周期的CU,多周期的CU在输入输出上大致相同,但具体控制内容、存在比较大的差别。

    1、指令状态转化的实现,前面已经提到。

    2、控制信号的赋值。由于多周期指令信号控制状态的不同而可能改变,所以这里实现各控制信号的时候不再像单周期那样单纯利用操作码来实现。

    类似的,写出控制信号与指令、指令状态的关系表,如下:

    Stage

    Ins

    Zero

    PCWre

    ALUSrcB

    ALUM2Reg

    RegWre

    WrRegData

    InsMemRW

    DataMemRW

    IRWre

    ExtSel[1..0]

    PCSrc

    [1..0]

    RegOut

    [1..0]

    ALUOp[2..0]

    sif

    (000)

    x

    x

    1

    x

    x

    0

    x

    1

    0

    1

    xx

    xx

    xx

    xxx

    sid

    (001)

    j

    x

    0

    x

    x

    0

    x

    x

    0

    0

    xx

    11

    xx

    xxx

    jal

    x

    0

    x

    x

    1

    0

    x

    0

    0

    xx

    11

    00

    xxx

    jr

    x

    0

    x

    x

    0

    x

    x

    0

    0

    xx

    10

    xx

    xxx

    halt

    x

    0

    x

    x

    0

    x

    x

    0

    0

    xx

    xx

    xx

    xxx

    exe1

    (110)

    add

    x

    0

    0

    x

    0

    x

    x

    0

    0

    xx

    xx

    xx

    000

    sub

    x

    0

    0

    x

    0

    x

    x

    0

    0

    xx

    xx

    xx

    001

    addi

    x

    0

    1

    x

    0

    x

    x

    0

    0

    10

    xx

    xx

    000

    or

    x

    0

    0

    x

    0

    x

    x

    0

    0

    xx

    xx

    xx

    101

    and

    x

    0

    0

    x

    0

    x

    x

    0

    0

    xx

    xx

    xx

    110

    ori

    x

    0

    1

    x

    0

    x

    x

    0

    0

    01

    xx

    xx

    101

    move

    x

    0

    0

    x

    0

    x

    x

    0

    0

    xx

    xx

    xx

    000

    slt

    x

    0

    0

    x

    0

    x

    x

    0

    0

    xx

    xx

    xx

    010

    sll

    x

    0

    1

    x

    0

    x

    x

    0

    0

    00

    xx

    xx

    100

    exe2

    (101)

    beq

    0

    0

    0

    x

    0

    x

    x

    0

    0

    10

    00

    xx

    001

    beq

    1

    0

    0

    x

    0

    x

    x

    0

    0

    10

    01

    xx

    001

    exe3

    (010)

    sw

    x

    0

    1

    x

    0

    x

    x

    0

    0

    10

    xx

    xx

    000

    lw

    x

    0

    1

    x

    0

    x

    x

    0

    0

    10

    xx

    xx

    000

    smem

    (011)

    sw

    x

    0

    x

    x

    0

    x

    x

    1

    0

    10

    00

    xx

    xxx

    lw

    x

    0

    x

    x

    0

    x

    x

    0

    0

    10

    xx

    xx

    xxx

    wb1

    (111)

    add

    x

    0

    x

    0

    1

    1

    x

    0

    0

    xx

    00

    10

    xxx

    sub

    x

    0

    x

    0

    1

    1

    x

    0

    0

    xx

    00

    10

    xxx

    addi

    x

    0

    x

    0

    1

    1

    x

    0

    0

    xx

    00

    01

    xxx

    or

    x

    0

    x

    0

    1

    1

    x

    0

    0

    xx

    00

    10

    xxx

    and

    x

    0

    x

    0

    1

    1

    x

    0

    0

    xx

    00

    10

    xxx

    ori

    x

    0

    x

    0

    1

    1

    x

    0

    0

    xx

    00

    01

    xxx

    move

    x

    0

    x

    0

    1

    1

    x

    0

    0

    xx

    00

    10

    xxx

    slt

    x

    0

    x

    0

    1

    1

    x

    0

    0

    xx

    00

    10

    xxx

    sll

    x

    0

    x

    0

    1

    1

    x

    0

    0

    xx

    00

    10

    xxx

    wb2

    (100)

    lw

    x

    0

    x

    1

    1

    1

    x

    0

    0

    xx

    00

    01

    xxx

     

    根据以上关系表,写出对应控制信号的实现。

    `timescale 1ns / 1ps
    
    
    module controlUnit(input [5:0] opcode, 
                       input zero, clk, Reset,
    		   output reg PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, ALUM2Reg, DataMemRW,
    		   output reg [1:0] ExtSel, RegOut, PCSrc,
    						 output reg [2:0] ALUOp, state_out);
        parameter [2:0] sif = 3'b000,   // IF state
    	                 sid = 3'b001,   // ID state
    						  exe1 = 3'b110,  // add、sub、addl、or、and、ori、move、slt、sll
    						  exe2 = 3'b101,  // beq
    						  exe3 = 3'b010,  // sw、lw
    						  smem = 3'b011,  // MEM state
    						  wb1 = 3'b111,   // add、sub、addl、or、and、ori、move、slt、sll
    						  wb2 = 3'b100;   // lw
    						  
    	 parameter [5:0] addi = 6'b000010,
                        ori = 6'b010010,
                        sll = 6'b011000,
                        add = 6'b000000,
                        sub = 6'b000001,
                        move = 6'b100000,
                        slt = 6'b100111,
                        sw = 6'b110000,
                        lw = 6'b110001,
                        beq = 6'b110100,
                        j = 6'b111000,
                        jr = 6'b111001,
                        Or = 6'b010000,
                        And = 6'b010001,
                        jal = 6'b111010,
                        halt = 6'b111111;
    								 
    	 reg [2:0] state, next_state;
    	
    	initial begin
    	   PCWre = 0;
    		InsMemRW = 0;
    		IRWre = 0;
    		WrRegData = 0;
    		RegWre = 0;
    		ALUSrcB = 0;
    		ALUM2Reg = 0;
    		DataMemRW = 0;
    		ExtSel = 2'b11;
    		RegOut = 2'b11;
    		PCSrc = 2'b00;
    		ALUOp = 0;
    		state = sif;
    		state_out = state;
    	end
    	
    	always @(posedge clk) begin
    	     if (Reset == 0) begin
    		      state <= sif;
    		  end else begin
    		      state <= next_state;
    		  end
    		  state_out = state;
    	 end
    	
    	always @(state or opcode) begin
    	case(state)
    	    sif: next_state = sid;
    		 sid: begin
    		     case (opcode[5:3])
    			      3'b111: next_state = sif; // j, jal, jr, halt等指令
                   3'b110: begin
                       if (opcode == 6'b110100) next_state = exe2; // beq指令
                       else next_state = exe3; // sw, lw指令
                   end
                   default: next_state = exe1; // add, sub, slt, sll等指令
               endcase
    		 end
    		 exe1: next_state = wb1;
           exe2: next_state = sif;
           exe3: next_state = smem;
    		 smem: begin
    		     if (opcode == 6'b110001) next_state = wb2; // lw指令
                    else next_state = sif; // sw指令
           end
           wb1: next_state = sif;
           wb2: next_state = sif;
           default: next_state = sif;
    	endcase
    	end
    		 
    	always @(state) begin
    	
            // 确定PCWre的值
            if (state == sif && opcode != halt) PCWre = 1;
            else PCWre = 0;
    		  
            // 确定InsMemRW的值
            InsMemRW = 1;
    		  
            // 确定IRWre的值
            if (state == sif) IRWre = 1;
            else IRWre = 0;
    		  
            // 确定WrRegData的值
            if (state == wb1 || state == wb2) WrRegData = 1;
            else WrRegData = 0;
    		  
            // 确定RegWre的值
            if (state == wb1 || state == wb2 || opcode == jal) RegWre = 1;
            else RegWre = 0;
            
    		  // 确定ALUSrcB的值
            if (opcode == addi || opcode == ori || opcode == sll || opcode == sw || opcode == lw) ALUSrcB = 1;
            else ALUSrcB = 0;
            
    		  // 确定DataMemRW的值
            if (state == smem && opcode == sw) DataMemRW = 1;
            else DataMemRW = 0;
            
    		  // 确定ALUM2Reg的值
            if (state == wb2) ALUM2Reg = 1;
            else ALUM2Reg = 0;
            
    		  // 确定ExtSel的值
            if (opcode == ori) ExtSel = 2'b01;
            else if (opcode == sll) ExtSel = 2'b00;
            else ExtSel = 2'b10;
            
    		  // 确定RegOut的值
            if (opcode == jal) RegOut = 2'b00;
            else if (opcode == addi || opcode == ori || opcode == lw) RegOut = 2'b01;
            else RegOut = 2'b10;
            
    		  // 确定PCSrc的值
            case(opcode)
                j: PCSrc = 2'b11;
                jal: PCSrc = 2'b11;
                jr: PCSrc = 2'b10;
                beq: begin
                    if (zero) PCSrc = 2'b01;
                    else PCSrc = 2'b00;
                end
                default: PCSrc = 2'b00;
            endcase
            
    		  // 确定ALUOp的值
            case(opcode)
                sub: ALUOp = 3'b001;
                Or: ALUOp = 3'b101;
                And: ALUOp = 3'b110;
                ori: ALUOp = 3'b101;
                slt: ALUOp = 3'b010;
                sll: ALUOp = 3'b100;
                beq: ALUOp = 3'b001;
                default: ALUOp = 3'b000;
            endcase
            
    		  // 防止在IF阶段写数据
            if (state == sif) begin
                RegWre = 0;
                DataMemRW = 0;
            end
        end
    	
    	 
    endmodule

    二、         算术运算单元(ALU)

    模块ALU接收寄存器的数据和控制信号作为输入,将结果输出,具体设计如下:

    `timescale 1ns / 1ps
    module ALU(input [31:0] ReadData1, ReadData2, inExt,
               input ALUSrcB,
    			  input [2:0] ALUOp,
    			  output wire zero,
    			  output reg [31:0] result);
    	 
    	 initial begin
            result = 0;
        end
    
        wire [31:0] B;
    	 assign B = ALUSrcB? inExt : ReadData2;
    	 assign zero = (result? 0 : 1);
    	 
    	 always @(ReadData1 or ReadData2 or B or ALUOp) begin
            case(ALUOp)
                3'b000: result = ReadData1 + B;  // A + B
                3'b001: result = ReadData1 - B;  // A - B
                3'b010: result = (ReadData1 < B ? 1 : 0);  // 比较A与B
                3'b011: result = ReadData1 >> B; // A右移B位
                3'b100: result = ReadData1 << B; // A左移B位
                3'b101: result = ReadData1 | B; // 或
                3'b110: result = ReadData1 & B; // 与
                3'b111: result = (~ReadData1 & B) | (ReadData1 & ~B); // 异或
            default: result = 0;
        endcase
      end
    	 
    endmodule
    


    三、         PC模块(PC)

    相比单周期的PC单元,这里的PC模块中多了一个四选一的的地址数据选择器,目的在于根据控制信号正确匹配pc地址,同样输出当前PC地址,具体设计如下:

    `timescale 1ns / 1ps
    module PC(input clk, Reset, PCWre,
              input [1:0] PCSrc,
              input wire [31:0] imm, addr, RDout1,
              output reg [31:0] Address);
    			 
    	 always @(PCWre or negedge Reset) begin // 这里和单周期不太一样,存在延迟的问题,只有当pcWre改变的时候或者Reset改变的时候再检测
            if (Reset == 0) begin
                Address = 0;
            end else if (PCWre) begin
                if (PCSrc == 2'b00) begin
    				    Address = Address+4;
    				end else if (PCSrc == 2'b01) begin
    				    Address = imm*4+Address+4;
    				end else if (PCSrc == 2'b10) begin
    				    Address = RDout1;
    				end else if (PCSrc == 2'b11) begin
    				    Address = addr;
    				end
            end
        end
    
    endmodule
    

    四、         PCAddr模块(补充address)

    用于跳转指令的地址补充,输出32位的地址,模块实现如下:

    `timescale 1ns / 1ps
    
    module PCAddr(input [25:0] in_addr,
                  input [31:0] PC0,
    				  output reg [31:0] addr);
        wire [27:0] mid;
    	 assign mid = in_addr << 2;
        always @(in_addr) begin
            addr <= {PC0[31:28], mid[27:0]};
        end
    
    endmodule

    五、         扩展单元(Extend)

    相比单周期的扩展,此处的扩展内容多了一些,包括sa扩展、立即数扩展等,扩展选择由控制信号ExtSel控制,最后输出完整32位数据。

    `timescale 1ns / 1ps
    
    module Extend(input [15:0] in_num,
                  input [1:0] ExtSel,
    				  output reg [31:0] out);
    				  
        always @(in_num or ExtSel) begin
            case(ExtSel)
                2'b00: out <= {{27{0}}, in_num[10:6]}; // 扩充 sa
                2'b01: out <= {{16{0}}, in_num[15:0]}; // 扩充立即数, 如 ori指令
                2'b10: out <= {{16{in_num[15]}}, in_num[15:0]}; // 符号扩充立即数,如addi、lw、sw、beq指令
                default: out <= {{16{in_num[15]}}, in_num[15:0]}; // 默认符号扩展
            endcase
        end
    
    endmodule

    六、         数据存储单元(DataMemory)

    数据存储单元的功能是读取数据,根据数据通路图可以有如下模块设计:

    `timescale 1ns / 1ps
    
    module DataMemory(input [31:0] addr, Data2,
                      input DataMemRW,
    		  output reg [31:0] DataOut);
    					  
    	 reg [7:0] memory [0:63];
    	 integer i;
    	 initial begin
    	     for (i = 0; i < 64; i = i+1) memory[i] <= 0;
    	 end
    	 
        always @(addr or Data2 or DataMemRW) begin
          if (DataMemRW) begin // write data
              memory[addr] = Data2[31:24];
              memory[addr+1] = Data2[23:16];
              memory[addr+2] = Data2[15:8];
              memory[addr+3] = Data2[7:0];
          end else begin // read data
              DataOut[31:24] = memory[addr];
              DataOut[23:16] = memory[addr+1];
              DataOut[15:8] = memory[addr+2];
              DataOut[7:0] = memory[addr+3];
          end
        end
    
    endmodule
    

    七、         指令存储单元(InsMemory)

    将指令集以二进制的文件(my_store.txt)存入当前目录,然后通过读取文件的方式将指令存储到内存中,最后实现指令的读取。其中,

    内部指令实现:

    将需要测试的汇编指令程序转化为指令代码,如下:

    地址

    汇编程序

    指令代码

    op(6)

    rs(5)

    rt(5)

    rd(5)/immediate (16)

    16进制数代码

    0x00000000

    j 0x00000008

    111000

    00 00000000 00000000 00000010

     

    0x00000004

    jr  $31

    111001

    11111

    00000

    0x00000008

    addi  $1,$0,8

    000010

    00000

    00001

    0000 0000 0000 1000

    =

    08010008

    0x0000000C

    ori  $2,$0,2

    010010

    00000

    00010

    0000 0000 0000 0010

    =

    48020002

    0x00000010

    add  $3,$1,$2

    000000

    00001

    00010

    00011 00000000000

    =

    00221800

    0x00000014

    sub  $4,$1,$2

    000001

    00001

    00010

    00100 00000000000

    =

    04222000

    0x00000018

    and  $5,$3,$2

    010001

    00011

    00010

    00101 00000000000

    =

    44622800

    0x0000001C

    or  $6,$1,$2

    010000

    00001

    00010

    00110 00000000000

    =

    40223000

    0x00000020

    move  $11,$1

    100000

    00001

    00000

    01011 00000000000

    =

    80205800

    0x00000024

    slt  $7,$1,$2

    100111

    00001

    00010

    00111 00000000000

    =

    9C223800

    0x00000028

    slt  $8,$2,$1

    100111

    00010

    00001

    01000 00000000000

    =

    9C414000

    0x0000002C

    sll  $2,$2,2

    011000

    00010

    00000

    00010 00010 000000

    =

    60401080

    0x00000030

    beq  $1,$2,-2 转02C

    110100

    00001

    00010

    1111 1111 1111 1110

    =

    D022FFFE

    0x00000034

    sw  $9,0($3)

    110000

    00011

    01001

    0000 0000 0000 0000

    =

    C0690000

    0x00000038

    jal  0x00000004

    111010

    00 00000000 00000000 0000001

    =

     

    0x0000003C

    lw  $10,2($1)

    110001

    00001

    01010

    0000 0000 0000 0010

    =

    C42A0002

    0x00000040

    halt

    111111

    00000

    00000

    0000000000000000

    =

    FC000000

     

    根据上表,可以创建my_store.txt的二进制指令文件,从而进行存取,二进制文件如下:


    最终模块设计如下:

    `timescale 1ns / 1ps
    
    module InsMemory(input [31:0] addr,
                     input InsMemRW, IRWre, clk,
    					  output reg [31:0] ins);
    					  
        reg [31:0] ins_out;
    	 reg [7:0] mem [0:127];
    	 
    	 initial begin
    	     $readmemb("my_store.txt", mem);
    		  //ins_out = 0;
    	 end
    
        always @( addr or InsMemRW) begin
            if (InsMemRW) begin
              ins_out[31:24] = mem[addr];
              ins_out[23:16] = mem[addr+1];
              ins_out[15:8] = mem[addr+2];
              ins_out[7:0] = mem[addr+3];
            end
    	 end
    	 
    	 always @(posedge clk) begin
    	     if (IRWre) ins <= ins_out;
    	 end
    
    endmodule

    八、         寄存器单元(RegFile)

    寄存器文件单元的功能是接收instructionMemory中的rs,rt,rd作为输入,输出对应寄存器的数据,从而达到取寄存器里的数据的目的,具体设计如下:

    需要注意的是,在其内部实现的过程中,为了防止0号寄存器写入数据需要在writeReg的时候多加入一个判断条件,即writeReg不等于0时写入数据。

    `timescale 1ns / 1ps
    
    module RegFile(input [4:0] rs, rt, rd,
                   input clk, RegWre, WrRegData,
    		      	input [1:0] RegOut,
    			  	   input [31:0] PC4, memData,
    			 	   output reg [31:0] data1, data2);
    
        reg [31:0] i_data;
    	 
    	 
    	 reg [4:0] temp;
    	 
    	 reg [31:0] register [0:31];
    	 integer i;
        initial begin
            for (i = 0 ; i < 32; i = i+1) 
    		      register[i] = 0;
        end
        
        always @(negedge clk) begin
    	     case(RegOut)
    	         2'b00: temp = 5'b11111;
    		      2'b01: temp = rt;
    		      2'b10: temp = rd;
    				default temp = 0;
            endcase
    		  assign i_data = WrRegData? memData : PC4;
    	     assign data1 = register[rs];
            assign data2 = register[rt];
            if ((temp != 0) && (RegWre == 1)) begin // temp != 0 确保零号寄存器不会改变
                register[temp] <= i_data;
            end
        end
    
    endmodule
    

    九、         二选一数据模块(DataSelect_2)

    简单的数据二选一,用于数据存储单元后面的数据选择,可见数据通路图,实现如下:

    `timescale 1ns / 1ps
    
    module DataSelect_2(input [31:0] A, B,
                        input Sign,
    						  output wire [31:0] Get);
    
        assign Get = Sign ? B : A;
    
    endmodule
    

    十、         数据延迟模板模块(DataLate)

    用于数据延迟,目的是使得数据正常输入输出,从数据通路图中可知此模板可在四处地方有用,已分析,所以具体模板实现如下:

    `timescale 1ns / 1ps
    
    module DataLate(input [31:0] i_data,
                    input clk,
                    output reg [31:0] o_data);
    
        always @(negedge clk) begin
            o_data = i_data;
        end
    
    endmodule

    十一、顶层模块(Top)

    顶层模块(Top)是整个CPU的控制模块,通过连接各个子模块来达到运行CPU的目的,整个模块设计可以如下:

    `include "ALU.v"
    `include "DataLate.v"
    `include "DataMemory.v"
    `include "DataSelect_2.v"
    `include "Extend.v"
    `include "InsMemory.v"
    `include "PC.v"
    `include "PCAddr.v"
    `include "RegFile.v"
    `include "ControlUnit.v"
    `timescale 1ns / 1ps
    
    module Top(input clk, reset,
               output wire [2:0] state_out,
               output wire [5:0] opcode,
    			  output wire [4:0] rs, rt, rd,
               // output ins[31:26], ins[25:21], ins[20:16], ins[15:11],
               output wire [31:0] ins, ReadData1, ReadData2, pc0, result);
    			  
    	 assign opcode = ins[31:26];
    	 assign rs = ins[25:21];
    	 assign rt = ins[20:16];
    	 assign rd = ins[15:11];
    
        // 数据通路
        wire [31:0] j_addr, out1, out2, result1, i_IR, extendData, LateOut1, LateOut2, DataOut;
        wire zero;
    	 
        // 控制信号
        wire [2:0] ALUOp;
        wire [1:0] ExtSel, RegOut, PCSrc;
        wire PCWre, IRWre, InsMemRW, WrRegData, RegWre, ALUSrcB, DataMemRW, ALUM2Reg;
    
    	 PC pc(clk, reset, PCWre, PCSrc, extendData, j_addr, ReadData1, pc0);
    
    	 InsMemory insmemory(pc0, InsMemRW, IRWre, clk, ins);
    	
    	 PCAddr pcaddr(ins[25:0], pc0, j_addr);
    	 
    	 RegFile regfile(ins[25:21], ins[20:16], ins[15:11], clk, RegWre, WrRegData, RegOut, (pc0+4), LateOut2, ReadData1, ReadData2);
    	
             DataLate ADR(ReadData1, clk, out1);
    	 DataLate BDR(ReadData2, clk, out2);
    	
    	 Extend extend(ins[15:0], ExtSel, extendData);
    	 
             ALU alu(out1, out2, extendData, ALUSrcB, ALUOp, zero, result);
    	 
    	 DataLate ALUout(result, clk, result1);
    	 
    	 DataMemory datamemory(result1, out2, DataMemRW, DataOut);
    	 
    	 DataSelect_2 dataselect_2(result, DataOut, ALUM2Reg, LateOut1);
    	
    	 DataLate ALUM2DR(LateOut1, clk, LateOut2);
    	 
    	controlUnit control(ins[31:26], zero, clk, reset,PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, ALUM2Reg, DataMemRW, ExtSel, RegOut, PCSrc, ALUOp, state_out);
    	
    
    endmodule
    


    十二、测试程序(test)

    从顶层模块中可以看出整个CPU的输入只有时钟信号clk和重置信号Reset,所以测试程序代码比较简单。(参照单周期CPU)

                 clk = 0;

                  reset= 0;

                  clk= ~clk;

     

                  //Wait 100 ns for global reset to finish

                  #100;

                 reset = 1;

                  forever #100 clk = ~clk;

     

    最后,套路贴结果(部分):



    至此,完工!!!

    展开全文
  • 用Verilog语言设计的多周期CPU,资源里包含了源代码及多周期CPU结构图,与大家分享下。 用Verilog语言设计的多周期CPU,资源里包含了源代码及多周期CPU结构图,与大家分享下。
  • 北航MIPS多周期CPU

    2018-03-19 15:28:35
    北航MIPS多周期CPU,使用大量的寄存器,请使用大容量的FPGA
  • 多周期CPU的设计

    2013-11-26 21:58:14
    计算机组织与系统结构多周期CPU的设计,所有代码,quarters运行
  • RISC_V 多周期CPU设计,里面包含基于最新提出的RISC_V指令集设计的多周期CPU,使用Verilog语言,代码注释详细,提供官方给出的测试样例,RV32I 基本整数指令四十条指令都有实现,波形仿真通过。
  • 多周期cpu,multicycle_cpu

    2012-03-08 17:01:00
    多周期cpu,multi_cycle_cpu,南京大学计算机系计算机组成原理实验-Of multi-cycle cpu, multi_cycle_cpu, Nanjing University Department of Computer Science Computer principle experiment
  • 多周期CpU设计与分析

    2015-03-22 23:45:15
    这是机组实验,多周期CPU MIPS设计,包含代码以及测试
  • MIPS架构多周期CPU的设计.pdf
  • 使用Verilog语言对多周期CPU进行仿真设计,其中有实验原理、实验设计、实验代码等。
  • 多周期CPU仿真

    千次阅读 2018-06-19 21:33:55
    经过单周期CPU的洗礼,我们接下来要做的就是多周期CPU了。因为有很单周期的代码可以复用,所以周期写起来没有那么困难。 多周期CPU原理 多周期CPU就是指一条指令在个时钟周期内完成,本身并不涉及...

    经过单周期CPU的洗礼,我们接下来要做的就是多周期CPU了。因为有很多单周期的代码可以复用,所以多周期写起来没有那么困难。

    多周期CPU原理

    多周期CPU就是指一条指令在多个时钟周期内完成,本身并不涉及多级流水线的设计,所以执行指令的性能反而不如单周期CPU。不过每一条指令在不同的时钟周期完成不同的阶段,因此执行指令的流程会更容易理解。每一条指令最多包含以下五个阶段:

    1. 取指令:sIF状态,读取下一条指令地址 。
    2. 指令译码:sID状态,读取下一条指令。
    3. 指令执行:sEXE状态,执行运算。
    4. 存储器访问:sMEM状态,读写数据存储器。
    5. 写回:sWB状态,写入寄存器。

    多周期CPU设计

    多周期CPU和单周期CPU基本框架差不多,直接复用代码并稍作调整。然后因为每一条指令需要多个时钟周期执行,所以需要记录当前阶段(即状态)以及保存各阶段数据,其中指令的读取、状态的转移、寄存器的写入和数据寄存器的刷新均由时钟信号触发,如何分配上升沿和下降沿触发是多周期CPU设计的难点。

    我的设计是由状态驱动指令的执行,即所有控制信号都由当前的状态和指令操作码共同确定,这就需要严格保证在每一个阶段先转移状态再执行指令。具体来说就是在每个时钟周期的上升沿触发状态转移,然后在下降沿读指令、写寄存器、刷新数据寄存器。因为上升沿只触发状态转移,所以状态转移不会与其它任何时序逻辑发生冲突,从而保证在下降沿当前状态可以确定当前阶段。虽然下降沿同时执行各种操作,但它们互不干扰,这是因为时序逻辑模块的触发不会影响整个CPU,影响的范围从当前时序逻辑模块的输出到下一个时序逻辑模块的输入为止。那么根据当前阶段就可以保证对应时序逻辑模块的触发的正确性,至于其它时序逻辑模块的触发则有可能是正确的,也有可能是不正确的。对于读操作,不正确的触发不会对程序的运行造成实质性的影响;对于写操作,不正确的触发会导致错误,因此必须通过控制信号禁止在正确的阶段以外进行写操作。

    经过测试发现有两个特殊情况:

    1. lw指令在写回阶段的下降沿要先刷新总线数据寄存器,再写寄存器,因此无法保证触发的先后顺序。
    2. jal指令要在指令译码阶段下降沿先刷新指令寄存器,再写寄存器,同样无法保证触发的先后顺序。

    我将指令寄存器的刷新调整为上升沿触发,这样不但可以同时解决这两个问题,而且不会对状态转移造成干扰。首先,对于第二个问题,在指令译码阶段上升沿刷新指令寄存器,在下降沿写寄存器,可以保证触发的先后顺序。然后,对于第一个问题,实际上在指令译码阶段下降沿会不正确地触发数据寄存器的刷新,从而提前完成指令执行阶段,并且对于在指令译码阶段之后的所有读操作都将提前一个时钟周期完成。因为lw指令除了写寄存器以外均为读操作,所以在访问寄存器阶段下降沿就会提前刷新总线数据寄存器,那么在写回阶段下降沿就可以保证写寄存器的正确性。最后,虽然指令寄存器的刷新和状态转移都是上升沿触发,但两者其实并不需要保证触发的先后顺序,因为两者的输出均作为控制单元的输入,而控制单元为组合逻辑电路,所以无论哪一个先触发都可以保证在指令译码下降沿之前确定控制信号。

    使用工具

    vivado软件和verilog语言

    多周期CPU实现

    多周期CPU的实现和单周期CPU差不多,除了加入数据寄存器模块和调整时序逻辑以外,唯一需要注意的是单周期CPU仿真初始时钟信号应为1,多周期CPU仿真初始时钟信号应为0,因为单周期CPU在执行第一条指令时不需要取指令(上升沿),多周期CPU在执行第一条指令时也不需要取指令(下降沿)。

    展开全文
  • 计算机组成原理实验 多周期CPU设计 Vivado MIPS基本指令都有实现,包括bgtz j jal bne 等等基本指令
  • 基于MIPS架构的多周期CPU设计.pdf
  • MIPS 54条多周期CPU.rar

    2020-06-08 00:08:46
    MIPS 54条多周期CPU.rar
  • 浙江大学计算机组成实验12指令扩展多周期CPU实现
  • (Verilog)多周期CPU设计

    千次阅读 2017-05-24 13:37:38
    (Verilog)多周期CPU设计 写在前面:在参考别人的博客自己做了一遍单周期cpu后,觉得不是很难,于是自己尝试了做一下多周期cpu,然后被各种bug糊脸。。。果然,自己尝试和有大佬指路还是有很大区别。。。 先把代码...

    (Verilog)多周期CPU设计

    写在前面:在参考别人的博客自己做了一遍单周期cpu后,觉得不是很难,于是自己尝试了做一下多周期cpu,然后被各种bug糊脸。。。果然,自己尝试和有大佬指路还是有很大区别。。。


    先把代码链接发上:多周期CPU代码


    依旧还是基础资料:

    一.实验内容

    设计一个多周期CPU,该CPU至少能实现以下指令功能操作。需设计的指令与格式如下:(说明:操作码按照以下规定使用,都给每类指令预留扩展空间,后续实验相同。)

    ==>算术运算指令

    (1)add rd, rs, rt

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

    功能:rd<-rs + rt
    (2)sub rd, rs, rt

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

    完成功能:rd<-rs - rt
    (3)addi rt, rs, immediate
    000010 rs(5位) rt(5位) immediate(16位)
    功能:rt<-rs + (sign-extend)immediate

    ==>逻辑运算指令

    (4)or rd, rs, rt

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

    功能:rd<-rs | rt
    (5)and rd, rs, rt

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

    功能:rd<-rs & rt
    (6)ori rt, rs, immediate

    010010rs(5位)rt(5位)immediate

    功能:rt<-rs | (zero-extend)immediate

    ==>移位指令

    (7)sll rd, rs,sa

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

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

    ==>传送指令

    (8)move rd, rs

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

    功能:rd<-rs + $0

    ==>比较指令

    (9) slt rd, rs, rt

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

    功能:如果(rs < rt),则rd=1; 否则 rd=0

    ==>存储器读写指令

    (10)sw rt, immediate(rs)

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

    功能:memory[rs+ (sign-extend)immediate]<-rt
    (11)lw rt, immediate(rs)

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

    功能:rt <- memory[rs + (sign-extend)immediate]

    ==>分支指令

    (12)beq rs,rt, immediate (说明:immediate是从pc+4开始和转移到的指令之间间隔条数)

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

    功能:if(rs=rt) pc <-pc + 4 + (sign-extend)immediate <<2

    ==>跳转指令

    (13)j addr

    111000addr[27..2]

    功能:pc <{pc[31..28],addr[27..2],0,0},转移
    (14)jr rs

    111001rs(5位)未用未用reserved

    功能:pc <- rs,转移

    ==>调用子程序指令

    (15)jal addr

    111010addr[27..2]

    功能:调用子程序,pc <- {pc[31..28],addr[27..2],0,0};$31<-pc+4,返回地址设置;子程序返回,需用指令 jr $31。

    ==>停机指令

    (16)halt (停机指令)

    11111100000000000000000000000000(26位)

    不改变pc的值,pc保持不变。

    二.实验原理

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

    这里写图片描述
    图1 多周期CPU指令处理过程
    MIPS32的指令的三种格式:
    R类型:

    31-2625-2120-1615-1110-65-0
    oprsrtrdsafunc
    6位5位5位5位5位6位

    I类型:

    31-2625-2120-1615-0
    oprsrtimmediate
    6位5位5位16位

    J类型:

    31-2625-0
    opaddress
    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状态转移图
    状态的转移有的是无条件的,例如从IF状态转移到ID 和 EXE状态就是无条件的;有些是有条件的,例如ID 或 EXE状态之后不止一个状态,到底转向哪个状态由该指令功能,即指令操作码决定。每个状态代表一个时钟周期。

    这里写图片描述
    图3 多周期CPU控制部件的原理结构图
    图3是多周期CPU控制部件的电路结构,三个D触发器用于保存当前状态,是时序逻辑电路,RST用于初始化状态“000“,另外两个部分都是组合逻辑电路,一个用于产生下一个阶段的状态,另一个用于产生每个阶段的控制信号。从图上可看出,下个状态取决于指令操作码和当前状态;而每个阶段的控制信号取决于指令操作码、当前状态和反映运算结果的状态zero标志等。

    这里写图片描述
    图4 多周期CPU数据通路和控制线路图

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

    特别提示,图上增加IR指令寄存器,目的是使指令代码保持稳定,还有pc增加写使能控制信号pcWre,也是确保pc适时修改,原因都是和多周期工作的CPU有关。ADR、BDR、ALUout、ALUM2DR四个寄存器不需要写使能信号,其作用是切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延时变为多个分段小延时。

    表1 控制信号作用

    控制信号名状态“0”状态“1”
    PCWrePC不更改,相关指令:haltPC更改,相关指令:除指令halt外
    ALUSrcB来自寄存器堆data2输出,相关指令:add、sub、addi、or、and、ori、move、beq、slt来自sign或zero扩展的立即数,相关指令:addi、ori、lw、sw、sll
    ALUM2Reg来自ALU运算结果的输出,相关指令:add、sub、addi、or、and、ori、slt、sll、move来自数据存储器(Data MEM)的输出,相关指令:lw
    RegWre无写寄存器组寄存器,相关指令:beq、j、sw、jr、halt寄存器组寄存器写使能,相关指令:add、sub、addi、or、and、ori、move、slt、sll、lw、jal
    WrRegData写入寄存器组寄存器的数据来自pc+4(pc4),相关指令:jal,写$31写入寄存器组寄存器的数据来自存储器、寄存器组寄存器和ALU运算结果,相关指令:add、addi、sub、or、and、ori、slt、sll、move、lw
    InsMemRW读指令存储器(Ins. Data),初始化为0写指令存储器
    DataMemRW读数据存储器(Data MEM),相关指令:lw写数据存储器,相关指令:sw
    IRWreIR(指令寄存器)不更改IR寄存器写使能。向指令存储器发出读指令代码后,这个信号也接着发出,在时钟上升沿,IR接收从指令存储器送来的指令代码。与每条指令都相关。

    特别信号:
    这里写图片描述

    相关部件及引脚说明:

    • Instruction Memory:指令存储器,
      • Iaddr,指令地址输入端口
      • DataIn,存储器数据输入端口
      • DataOut,存储器数据输出端口
      • 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时,在时钟上升沿写入
    • IR: 指令寄存器,用于存放正在执行的指令代码
    • ALU:
      • result,ALU运算结果
      • zero,运算结果标志,结果为0输出1,否则输出0

    表2 ALU运算功能表
    PS:功能和单周期并不一样

    ALUOp[2..0]功能描述
    000Y = A + B
    001Y = A – B
    010if (A < B)Y = 1; else Y = 0;比较A与B
    011Y = A >> BA右移B位
    100Y = A << BA左移B位
    101Y = A ∨ B
    110Y = A ∧ B
    111Y = A ⊕ B异或

    值得注意的问题,设计时,用模块化的思想方法设计,关于Control Unit 设计、ALU设计、存储器设计、寄存器组设计等等,是必须认真考虑的问题。


    科普部分:

    首先,进行操作之前,需要先了解什么是多周期cpu:

    多周期不是流水线!
    多周期不是流水线!
    多周期不是流水线!
    重要的事情先说三次。

    Q:什么是多周期?和单周期区别在哪?
    A:单周期是一个大时钟周期(不妨叫指令周期),完成IF,ID,EXE,MEM,WB五个模块。多周期是把这个大的时钟周期,分成五个小的时钟周期,每个时钟周期只执行IF,或ID…..等其中一个小功能。
    说这么多,不如来张图吧:
    这里写图片描述
    可以看出,其实多周期和单周期执行时间并没有什么区别,只是把一个大时钟周期拆开成了五个时钟周期而已。

    所以问题又来了:
    Q:单周期cpu改成多周期cpu会有什么提升呢?
    A:单周期所有的指令都要按顺序执行IF->ID->EXE->MEM->WB(无论有没有用上)。但多周期可以不用,比如beq指令就只需要IF->ID->EXE,jal指令只需要IF->ID,就可以省很多不必要的时间。
    举个例子,让单周期和多周期同时执行j和beq两条指令,结果如下:
    这里写图片描述
    时间短了一倍,很明显看得出多周期的优点了。

    Q:怎么把一个指令周期变成多个小时钟周期?
    A:首先我们得先明白一点:我们仿真模拟设置的时钟周期时间实际上比执行时间大太多,导致其实在时钟上升沿一刹那后,所有结果就计算出来了。所以,要把单周期分成多周期,我们就要强行延长周期,让IF结果在第一个时钟周期完成,让ID结果在第二个时钟周期完成,以此类推。关键的来了,用什么去实现呢?用上升沿触发的寄存器。

    Q:寄存器怎么实现延迟时钟周期的效果?
    A:读万卷书,不如行万里路。来实验下就知道了:
    我先用一个小程序Test来模拟这个效果:(Test代码点我下载)

    主模块(把四个上升沿触发的寄存器串联):

    module Test(
        input CLK,
         input value
        );
        wire value1;
        wire value2;
        wire value3;
        wire value4;
        wire value5;
    
        WireToReg r1(CLK, 1, value, value1);
        WireToReg r2(CLK, 1, value1, value2);
        WireToReg r3(CLK, 1, value2, value3);
        WireToReg r4(CLK, 1, value3, value4);
        WireToReg r5(CLK, 1, value4, value5);
    
    
    endmodule

    WireToReg(上升沿触发的寄存器):

    module WireToReg(
         input CLK,
         input Enable,
         input [31:0] in,
         output reg[31:0] out
        );
    
        initial
         begin
            out = 0;
         end
    
        always@(posedge CLK)
         begin
            out = Enable ? in : out;
         end
    
    endmodule

    测试文件(简单的时钟周期变化):

        initial begin
            // Initialize Inputs
            CLK = 0;
            value = 0;
    
            // Wait 100 ns for global reset to finish
            #100;
            value = 1;  // 将初始值变成1
            // Add stimulus here
    
            // 时钟周期变化
            forever #10
             begin
                CLK = !CLK;
             end
    
        end

    简单的测试一下,得到结果:
    这里写图片描述
    很明显看出来,本来应该同时变成1的五个value成功的被延时,顺序的在上升沿变成了1,这5个值就代表着多周期的五个时钟周期。

    原因:由于所有寄存器是在上升沿同时触发,并同时根据输入改变输出,由于有微小的时间差,所以其实得到的输入是上一个周期的输入。
    画个图来表示:
    这里写图片描述
    由于同时触发,所以value1和value2的值在t1时刻判断输入,此时输入value为1,value1为0,所以value1改变,value2不改变,value1在t2时刻改成1,但是此时已经过了时钟上升沿,所以value2不会改变,因此value2被延迟了一个周期。

    结论:上升沿触发的寄存器,输出改变时间会比输入改变时间多一个周期。

    多周期的原理和实现方法已经科普完了,下面就是实现部分了。


    正文部分:

    数据通路图:
    这里写图片描述
    经过科普,可以知道那几个新的寄存器的作用了,就是为了延长时钟周期。
    (PS:数据通路图我改了一下右下角,只有这样才能使算术逻辑指令只经过3个寄存器,只执行4个周期。)

    实现思路:

    每一个组件都能写成一个下层模块,实现相应的功能。
    对比单周期cpu,新加入一个WireToReg和PCJUMP模块(左下角那个),用于分割时钟周期和补全J型指令的address。
    顶层模块调用各个下层模块,根据数据通路图将模块之间连线,保证有限状态机状态改变后,控制模块改变控制信号,然后其他所有的模块都根据控制信号的改变,发生对应的变化。
    测试模块控制CLK和Reset信号,使有限状态机中的状态发生改变。


    具体代码:

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

    新加入模块:

    我新加入了几个辅助模块,分别是四路选择器,线转寄存器模块和新的指令JUMP模块。
    PS:由于RW模块所需要的寄存器IR太多,因此合在了一起。

    1.多路选择器(32线和5线,4路输入和2路输入):

    module MUX4L_5(
         input [1:0] control,
        input [4:0] in00,
        input [4:0] in01,
         input [4:0] in10,
         input [4:0] in11,
        output [4:0] out
        );
    
        // 5线4路选择器
        assign out = control[0] ? (control[1] ? in11 : in01) : (control[1] ? in10 : in00);
    
    endmodule
    module MUX4L_32(
         input [1:0]control,
        input [31:0] in00,
        input [31:0] in01,
         input [31:0] in10,
         input [31:0] in11,
        output [31:0] out
        );
    
        // 32线4路选择器
        assign out = control[0] ? (control[1] ? in11 : in01) : (control[1] ? in10 : in00);
    
    endmodule
    module MUX2L_32(
         input control,
        input [31:0] in0,
        input [31:0] in1,
        output [31:0] out
        );
    
        // 32线2路选择器
        assign out = control ? in1 : in0;
    
    endmodule

    输入:四/两个输入
    控制信号:control
    输出:out
    解释:很简单,就不解释了。

    2.WireToReg:线转寄存器,用来分割时钟周期

    module WireToReg(
         input CLK,
         input Enable,
         input [31:0] in,
         output reg[31:0] out
        );
    
        initial
         begin
            out = 0;
         end
    
        always@(posedge CLK)
         begin
            out = Enable ? in : out;
         end
    
    endmodule

    输入:in
    控制信号:CLK,Enable
    输出:out
    解释:CLK上升沿修改,Enable为使能端,也不多解释了。

    3.PCJUMP:address补全成指令地址

    module PCJUMP(
        input [31:0] PC0,          // 指令
        input [25:0] inAddress,    // 输入地址
        output [31:0] outAddress   // 输出地址(指令)
        );
    
        // outAddress = PC + inAddress + 00
        assign outAddress[31:28] = PC0[31:28];
        assign outAddress[27:2] = inAddress;
        assign outAddress[1:0] = 2'b00;
    
    endmodule

    输入:PC0,inAddress
    控制信号:无
    输出:outAddress
    解释:outAddress = PC0[31:28] + inAddress << 2 + 00(主要为了实现j型指令)

    单周期cpu原有模块修改:

    根据通路图,我修改了不少模块,对每个原有模块都进行了改进,下面我会对每个模块进行说明。

    4.PC:CLK上升沿触发,更改指令地址

    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

    修改:毫无修改,因此直接拿的上一个实验,区别是PCWre终于有实际的作用了(不再一直为1)。
    输入:newAddress
    控制信号:CLK,Reset,PCWre
    输出:currentAddress
    解释:由于指令地址存储在寄存器里,一开始需要赋currentAddress为0。Reset是重置信号,当为1时,指令寄存器地址重置。PCWre的作用为保留现场,如果PCWre为0,指令地址不变。

    5.InstructionMemory:储存指令,分割指令(包含寄存器)

    module InstructionMemory(
         input InsMemRW,            // 读写控制信号,1为写,0位读
        input [31:0] IAddr,        // 指令地址输入入口
         //input IDataIn,           // 没用到 
    
         input CLK,                  // 时钟信号
         input IRWre,                // 输出寄存器写使能
    
        output reg[5:0] op,
        output reg[4:0] rs,
        output reg[4:0] rt,
        output reg[4:0] rd,
        output reg[15:0] immediate, // 指令代码分时段输出
         output reg[25:0] address
        );
    
        reg[7:0] mem[0:63];  // 新建一个32位的数组用于储存指令
    
        initial 
         begin
            // 初始化
            op <= 0;
            rs <= 0;
            rt <= 0;
            rd <= 0;
            immediate <= 0;
            address <= 0;
            $readmemb("test/test.txt", mem);  //读取测试文档中的指令
         end
    
        // 从地址取值,然后输出
        always@(posedge CLK or posedge IRWre)
         begin
            if (IRWre == 1)
             begin
                op = mem[IAddr][7:2];
                rs[4:3] = mem[IAddr][1:0];
                rs[2:0] = mem[IAddr + 1][7:5];
                rt = mem[IAddr + 1][4:0];
                rd = mem[IAddr + 2][7:3];
                immediate[15:8] = mem[IAddr + 2];
                immediate[7:0] = mem[IAddr + 3];
    
                // 地址赋值
                address[25:21] = rs;
                address[20:16] = rt;
                address[15:0] = immediate;
             end
         end
    
    endmodule

    修改:将输出的指令变成了寄存器,并且新加了address输出,同时新加入CLK和IRWre控制信号
    输入:IAddr
    控制信号:InsMenRW,CLK,IRWre
    输出:op,rs,rt,rd,immediate,address
    解释:该部分为指令寄存器,通过一个64大小的8位寄存器数组来保存从文件输入的全部指令。然后通过输入的地址,找到相应的指令,并分割成op,rs,rt,rd,immediate,address输出。其中InsMenRW并没有什么卵用,IRWre为寄存器使能端。(由于寄存器地址+4,所以不用右移变换成真正的地址)

    6.RegisterFile:储存寄存器组,并根据地址对寄存器组进行读写

    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:31];  // 新建32个寄存器,用于操作
        // 初始时,将32个寄存器和ReadData全部赋值为0
        integer i;
        initial 
         begin
            for(i = 0; i < 32; i = i + 1)  register[i] <= 0;
         end
    
        // 直接读寄存器
        assign ReadData1 = register[rs];
        assign ReadData2 = register[rt];
    
        // 接受信号并读寄存器
        always@(posedge RegWre)
         begin
            // 如果寄存器不为0,并且RegWre为真,写入数据
            if (RegWre && WriteReg != 0)  register[WriteReg] = WriteData;
         end 
    
    endmodule

    修改:毫无修改,仅仅把寄存器数量从16个扩充成32个(为了实现jal指令)
    输入:rs,rt,WriteReg,WriteData
    控制信号:CLK,RegWre
    输出:ReadData1,ReadData2
    解释:该部分为寄存器读写单元,RegWre的作用是控制寄存器是否写入。同上,通过一个32大小的32位寄存器数组来模拟寄存器,开始时全部置0。通过访问寄存器的地址,来获取寄存器里面的值,并进行操作。在jal指令执行时,返回的指令会保存 31PS 0恒为0,所以写入寄存器的地址不能为0)

    7.ALU(算术逻辑单元):用于逻辑指令计算和跳转指令比较

    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运算结果
        );
    
        initial 
         begin
            zero <= 0;
         end
    
        // 进行ALU计算
        always@(*)
         begin
            // 进行ALU直接运算
            case (ALUOp)
                3'b000 :  result <= A + B;             // 加法
                3'b001 :  result <= A - B;             // 减法
                3'b010 :  result <= (A < B) ? 1 : 0;   // 判断A是否<B,z只能这么赋值
                3'b011 :  result <= A >> B;            // A右移B位
                3'b100 :  result <= A << B;            // A左移B位
                3'b101 :  result <= A | B;             // A或B
                3'b110 :  result <= A & B;             // A与B
                3'b111 :  result <= A ^ B;             // 异或
            endcase
    
            // 设置zero
            if (result)  zero = 0;
            else  zero = 1;
         end
    
    endmodule

    修改:用于ALU表变了,所以指令代表的操作也发生了变化,因此进行了操作的修改
    输入:A,B
    控制信号:ALUOp
    输出:zero,result
    解释:ALUOp用于控制算数的类型,AB为输入数,result为运算结果,zero主要用于beq和bne指令的判断。

    8.SignZeroExtend:用于immediate和sa的扩展

    module SignZeroExtend(
        input [1:0]ExtSel,              // 控制补位,如果为1X,进行符号扩展
                                         // 如果为01,immediate全补0
                                         // 如果为00,sa全补0
        input [15:0] immediate,         // 16位立即数
        output [31:0] extendImmediate   // 输出的32位立即数
        );
    
    
        // 进行扩展
        assign extendImmediate[4:0] = (ExtSel == 2'b00) ? immediate[10:6] : immediate[4:0];
        assign extendImmediate[15:5] = (ExtSel == 2'b00) ? 3'b00000000000 : immediate[15:5];
        // 前半段填充
        assign extendImmediate[31:16] = (ExtSel == 2'b10) ? (immediate[15] ? 16'hffff : 16'h0000) : 16'h0000;
    
    endmodule
    

    修改:由于控制信号由一位变成了两位(新加入了sa的扩展),因此赋值也进行了修改
    输入:immediate
    控制信号:ExtSel
    输出:extendImmediate
    解释:比较简单的一个模块。ExtSel为控制补位信号。判断0位确定扩充immediate还是sa,判断1位决定是否进行符号扩展。

    9.DataMemory:用于内存存储,内存读写

    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 or DataMemRW)
         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
    

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


    最重要的ControlUnit模块:

    可能做完单周期cpu,会觉得这个模块也不过如此,但是在多周期cpu中,这个模块可是重中之重,因此千万不能小看!(我bug全出在这里了)
    下面结合图解进行说明:
    这里写图片描述
    首先是控制信号表,乍一看可能和单周期一样,但是却略有不同。在表中,控制信号分为三种颜色:黑色,红色,蓝色,下面我会一一分析颜色代表的意思。

    黑色:黑色意味着这种状态可以在一个指令的全部时钟周期一直保持(即可以保持二-五个时钟周期)。因为:
    1.黑色控制信号状态不变并不会导致寄存器一直写入,而影响寄存器的值。
    2.就算输入信号改变,但是输出信号要么不变,要么不会影响本次操作的中间值或结果。
    红色:红色意味着这个状态没有什么卵用,完全可以去掉:因为:
    1.红色控制信号在多周期CPU中没起到控制作用,拿来摆设。(InsMemRW)
    2.红色控制信号保持一个值,在所有指令中都没有改变过。(IRWre)
    蓝色:蓝色意味着这个状态只能持续一个时钟周期(即一个状态机),过了这个时钟周期后必须改变。因为:
    1.蓝色控制信号在一个指令中只能执行其中一个时钟周期。比如PC赋值,只能在一个指令中赋值一次。
    2.蓝色控制信号和寄存器或内存写入有关,执行多次会发生奇怪bug(心痛= =)。
    因此,控制信号写法就很明了了。红色信号一开始赋值即可,之后不变,黑色根据指令不同赋值不同,在每个IF状态赋值即可,蓝色信号随着状态机变化赋值。

    下表是蓝色状态随着状态机赋值的标准:
    这里写图片描述

    得到这些信息,就可以写这个模块了。

    输入输出部分:

    module ControlUnit(
         input CLK,              // 时钟
         input reset,            // 重置信号
        input [5:0] op,         // op操作符
        input zero,             // ALU的zero输出
    
         // 一堆控制信号
        output reg PCWre,           // (PC)PC是否更改,如果为0,PC不更改,
                                              // 另外,除D_Tri == 000状态之外,其余状态也不能改变PC的值。
        output reg ALUSrcB,         // 多路选择器
        output reg ALUM2Reg,        // 多路选择器
        output reg RegWre,          // (RF)写使能信号,为1时,在时钟上升沿写入
         output reg WrRegData,       // 2路选择器,判断数据写入是否为PC指令,如果为1,则不是,jar用到
        output reg InsMemRW,        // (IM)读写控制信号,1为写,0位读,固定为0
        output reg DataMemRW,       // (DM)数据存储器读写控制信号,为1写,为0读
         output reg IRWre,           // 寄存器写使能,暂时没什么用,固定为1
        output reg[1:0] ExtSel,     // (EXT)控制补位,如果为1,进行符号扩展,如果为0,全补0
         output reg[1:0] PCSrc,      // 4路选择器,选择PC指令来源
        output reg[1:0] RegOut,     // 4路选择器,判断写寄存器地址的来源
        output reg[2:0] ALUOp       // (ALU)ALU操作控制 
        );

    输入:无
    控制信号:CLK,reset,op,zero
    输出:各类控制信号
    解释:和单周期CPU差别不大,主要在于新加入了新的控制信号和状态机。

    指令定义:类似于常量赋值

        // 有限状态机宏定义
        parameter [2:0] 
            IF = 3'b000,
            ID = 3'b001,
            EXELS = 3'b010,
            MEM = 3'b011,
            WBL = 3'b100,
            EXEBR = 3'b101,
            EXEAL = 3'b110,
            WBAL = 3'b111;
    
        // 指令宏定义,由于有些指令为关键字,因此全部首字符大写
        parameter [5:0]
            Add = 6'b000000,
            Addi = 6'b000010, 
            Sub = 6'b000001, 
          Ori = 6'b010010,  
            And = 6'b010001,
            Or = 6'b010000,
          Sll = 6'b011000,  
          Move = 6'b100000,  
          Slt = 6'b100111,  
          Sw = 6'b110000,  
          Lw = 6'b110001,  
          Beq = 6'b110100,  
          J = 6'b111000,  
          Jr = 6'b111001,   
          Jal = 6'b111010,  
          Halt = 6'b111111; 

    状态机变化:(为了避免竞争冒险,我将D触发器改成了下降沿触发)

    D触发器用3线寄存器模拟。

        // 3位D触发器,代表8个状态
        /* 000 -> IF
         * 001 -> ID
         * 010 -> EXELS
         * 011 -> MEM
         * 100 -> WBL
         * 101 -> EXEBR
         * 110 -> EXEAL
         * 111 -> WBAL
         */
        reg [2:0] D_Tri;
    
        // D触发器变化,PS:为了避免竞争冒险,所有值变化改为下降沿触发
        // PCWre,RegWre和DataMemRW的变化影响很大,要在这里写
        always@(negedge CLK or posedge reset)
         begin
            // 重置属性
            if (reset)  
             begin
                D_Tri = IF;
                PCWre = 0;
                RegWre = 0;
             end
            else
             begin
                case (D_Tri)
                    // IF -> ID
                    IF:
                     begin
                        D_Tri <= ID;
                        // 禁止写指令,寄存器,和内存
                        PCWre = 0;
                        RegWre = 0;
                        DataMemRW = 0;
                     end
                    // ID -> EXE
                    ID:
                     begin
                        case (op)
                            // 如果是beq指令,跳到EXEBR
                            Beq:  D_Tri <= EXEBR;
                            // 如果是sw,lw指令,跳到EXELS
                            Sw, Lw:  D_Tri <= EXELS;
                            // 如果是j,jal,jr,halt,跳到IF
                            J, Jal, Jr, Halt:
                             begin
                               D_Tri = IF;
                                // 如果指令是halt,禁止写指令
                                if (op == Halt)  PCWre = 0;  
                                else  PCWre = 1;
                                // 如果指令是jal,允许写寄存器
                                if (op == Jal)  RegWre = 1;
                                else  RegWre = 0;
                             end
                            // 其他,跳到EXEAL
                            default:  D_Tri = EXEAL;
                        endcase
                     end
                    // EXEAL -> WBAL
                    EXEAL:
                     begin
                        D_Tri = WBAL;
                        // 允许写寄存器
                        RegWre = 1; 
                     end 
                    // EXELS -> MEM
                    EXELS:  
                     begin
                        D_Tri = MEM;
                        // 如果指令为sw,允许写内存
                        if (op == Sw)  DataMemRW = 1;
                     end
                    // MEM -> WBL
                    MEM:
                     begin
                        // 如果指令为sw,MEM -> IF
                        if (op == Sw)
                         begin
                            D_Tri = IF;
                            // 允许写指令
                            PCWre = 1;
                         end
                        // 如果指令为lw,MEM -> WBL
                        else
                         begin
                            D_Tri = WBL;
                            // 允许写寄存器
                            RegWre = 1;
                         end
                     end 
                    // 其他 -> IF
                    default:
                     begin
                        D_Tri = IF;
                        // 允许写指令
                        PCWre = 1;
                        // 禁止写寄存器
                        RegWre = 0;
                     end
                endcase
             end
         end

    (其他部分同单周期CPU,略)


    编写主模块和测试单元:

    最水的模块,还是那句话,只要底层ok,图ok,这个模块就不会有问题。

    主模块:(没什么好说的,就是把线连到一起)

    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,
         output PCWre
        );
    
        // 各种临时变量
       wire [31:0] B, newAddress;
       wire [31:0] currentAddress_4, extendImmediate, currentAddress_immediate, outAddress, ALUM2DR;      
       wire [4:0] WriteReg;  
        wire [25:0] address;
    
       wire zero, ALUSrcB, ALUM2Reg, RegWre, WrRegData, InsMemRW, DataMemRW, IRWre;
        wire [1:0] ExtSel, PCSrc, RegOut;
        wire [2:0] ALUOp;
    
        // 寄存器输出值
        wire [31:0] RegReadData1, RegReadData2, RegResult, RegDataOut;
    
        /*module ControlUnit(
         input CLK,              // 时钟
         input reset,            // 重置信号
        input [5:0] op,         // op操作符
        input zero,             // ALU的zero输出
    
         // 一堆控制信号
        output reg PCWre,           // (PC)PC是否更改,如果为0,PC不更改,
                                              // 另外,除D_Tri == 000状态之外,其余状态也不能改变PC的值。
        output reg ALUSrcB,         // 多路选择器
        output reg ALUM2Reg,        // 多路选择器
        output reg RegWre,          // (RF)写使能信号,为1时,在时钟上升沿写入
         output reg WrRegData,       // 2路选择器,判断数据写入是否为PC指令,如果为1,则不是,jar用到
        output reg InsMemRW,        // (IM)读写控制信号,1为写,0位读,固定为0
        output reg DataMemRW,       // (DM)数据存储器读写控制信号,为1写,为0读
         output reg IRWre,           // 寄存器写使能,暂时没什么用,固定为1
        output reg[1:0] ExtSel,     // (EXT)控制补位,如果为1,进行符号扩展,如果为0,全补0
         output reg[1:0] PCSrc,      // 4路选择器,选择PC指令来源
        output reg[1:0] RegOut,     // 4路选择器,判断写寄存器地址的来源
        output reg[2:0] ALUOp       // (ALU)ALU操作控制 
        );*/
        ControlUnit cu(CLK, Reset, op, zero, PCWre, ALUSrcB, ALUM2Reg,
            RegWre, WrRegData, InsMemRW, DataMemRW, IRWre, ExtSel, PCSrc, 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,           // 没用到 
    
         input CLK,                  // 时钟信号
         input IRWre,                // 输出寄存器写使能
    
        output reg[5:0] op,
        output reg[4:0] rs,
        output reg[4:0] rt,
        output reg[4:0] rd,
        output reg[15:0] immediate, // 指令代码分时段输出
         output reg[25:0] address
        );*/
        InstructionMemory im(InsMemRW, currentAddress, CLK, IRWre, op, rs, rt, rd, immediate, address);
    
        /*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 [1:0]ExtSel,              // 控制补位,如果为1X,进行符号扩展
                                         // 如果为01,immediate全补0
                                         // 如果为00,sa全补0
        input [15:0] immediate,         // 16位立即数
        output [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, RegResult, RegReadData2, DataOut);
    
        /*module PCJUMP(
        input [31:0] PC0,          // 指令
        input [25:0] inAddress,    // 输入地址
        output [31:0] outAddress   // 输出地址(指令)
        );*/
        PCJUMP pcj(currentAddress, address, outAddress);
    
        assign currentAddress_4 = currentAddress + 4;
        assign currentAddress_immediate = currentAddress_4 + (extendImmediate << 2);
    
        // 线转寄存器
        WireToReg wtrA(CLK, 1, ReadData1, RegReadData1);
        WireToReg wtrB(CLK, 1, ReadData2, RegReadData2);
        WireToReg wtrALU(CLK, 1, result, RegResult);
        WireToReg wtrMEM(CLK, 1, DataOut, RegDataOut);
    
        // 2路选择器
        MUX2L_32 mux2_1(WrRegData, currentAddress_4, ALUM2DR, WriteData);
        MUX2L_32 mux2_2(ALUSrcB, RegReadData2, extendImmediate, B);
        MUX2L_32 mux2_3(ALUM2Reg, result, RegDataOut, ALUM2DR);
    
        // 4路选择器
        MUX4L_5 mux4_1(RegOut, 5'b11111, rt, rd, 5'b00000, WriteReg);
        MUX4L_32 mux4_2(PCSrc, currentAddress_4, currentAddress_immediate,
            ReadData1, outAddress, newAddress);
    
    endmodule
    

    测试文件:(将单周期CPU的指令进行少许修改,其实并没什么变化)

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

    测试结果:

    在单周期指令的基础上,新加入了几个测试指令:

    //addi $1, $0, 4
    000010 00000 00001 0000000000000100
    //addi $2, $0, 8
    000010 00000 00010 0000000000001000
    //sw $2, 0($2)
    110000 00010 00010 0000000000000000
    //add $3, $2, $1
    000000 00010 00001 00011 00000000000
    //sub $3, $3, $1
    000001 00011 00001 00011 00000000000
    //beq $2, $3, -2
    110100 00010 00011 1111111111111110
    //jal 0x00000008 (ori指令)
    111010 00000000000000000000001000
    //halt 
    111111 00000000000000000000000000
    //ori $1, $1, 1
    010010 00001 00001 0000000000000001
    //or $3, $2, $1
    010000 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)
    110001 00010 00100 0000000000000000
    //sll $4, $4, 2
    011000 00100 00000 00100 00010 000000
    //slt $2, $4, $5
    100111 00010 00100 00101 00000000000
    //jr $31
    111001 11111 000000000000000000000
    00001000
    00000001
    00000000
    00000100
    00001000
    00000010
    00000000
    00001000
    11000000
    01000010
    00000000
    00000000
    00000000
    01000001
    00011000
    00000000
    00000100
    01100001
    00011000
    00000000
    11010000
    01000011
    11111111
    11111110
    11101000
    00000000
    00000000
    00001000
    11111100
    00000000
    00000000
    00000000
    01001000
    00100001
    00000000
    00000001
    01000000
    01000001
    00011000
    00000000
    10000000
    01000000
    00011000
    00000000
    01000100
    01100010
    00001000
    00000000
    11000100
    01000100
    00000000
    00000000
    01100000
    10000000
    00100000
    10000000
    10011100
    01000100
    00101000
    00000000
    11100111
    11100000
    00000000
    00000000
    

    测试结果如下:
    这里写图片描述
    结果并没有什么问题。


    总结:

    写多周期cpu,让我知道了什么才是竞争冒险,怎么解决竞争冒险。还有想吐槽的一点,这软件也太难debug了吧,完全靠脑测啊= =

    展开全文
  • 基于有限状态机的多周期CPU实验设计.pdf
  • 多周期CPU的实现,在15版的vivado上可以打开,如果需要看到仿真的波形图,可以跑仿真,调节相关参数即可显示出来
  • 多周期CPU——Verilog语言实现

    千次阅读 2018-06-29 19:28:19
    多周期CPU的设计与实现 本次实验是在单周期CPU的基础上完成了,将每条指令只需要一个周期,切割成sIF、sID、sEXE、sMEM、sWB五个周期 单周期CPU的内容详见我的另外一篇CSDN博客:单周期CPU 多周期CPU的整个项目...
  • 本资源包含了计组2的实验工程源码以及实验报告,基于多周期CPU的32位模型计算机,实现是基于minisys,可使用仿真进行验证。实验报告里也包含了完整的实验过程以及验证结果,工程源码也可直接打开
  • 多周期cpu设计与实现

    万次阅读 2016-05-23 21:52:09
    多周期cpu设计与实现  1、原理:  多周期CPU指的是将整个CPU的执行过程分成几个阶段,每个阶段用一个时钟去完成,然后开始下一条指令的执行,而每种指令执行时所用的时钟数不尽相同,这就是所谓的多周期CPU。CPU在...
  • 多周期CPU制作,烧纸到basy3板子,可运行,有四个状态。
  • MIPS 32位多周期CPU

    2012-04-23 10:39:34
    MPIS 32位多周期CPU,使用模块设计,简单易懂。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 291,715
精华内容 116,686
关键字:

多周期cpu