Menu
    1. 实验目的
    2. 实验内容
      1. 算术运算指令
        1. add rd rs rt
        2. sub rd rs rt
        3. addiu rt rs immediate
      2. 逻辑运算指令
        1. and rd rs rt
        2. andi rt rs immediate
        3. ori rt rs immediate
        4. xori rd rs rt
      3. 移位指令
        1. sll rd rt sa
      4. 比较指令
        1. slti rt rs immediate
        2. slt rd rs rt
      5. 存储器读/写指令
        1. sw rt immediate(rs)
        2. lw rt immediate(rs)
      6. 分支指令
        1. beq rs rt immediate
        2. bne rs rt immediate
        3. bltz rs immediate
      7. 跳转指令
        1. j addr
        2. jr rs
      8. 调用子程序指令
        1. jal addr
      9. 停机指令
        1. halt
    3. 实验原理
      1. CPU 在处理指令的几个步骤
        1. 取指令(IF)
        2. 指令译码(ID)
        3. 指令执行(EXE)
        4. 存储器访问(MEM)
        5. 结果写回(WB)
      2. MIPS 指令的三种格式
        1. R 类型
        2. I 类型
        3. J 类型
      3. 多周期 CPU 状态转移图
      4. 多周期 CPU 控制部件的原理结构图
      5. 多周期 CPU 数据通路和控制线路图
      6. 控制信号的作用表
        1. ALUOp 的功能表
        2. ExtSel 的功能表
        3. PCSrc 的功能表
        4. RegDs 的功能表
      7. 相关部件及引脚说明
        1. Instruction Memory
        2. Data Memory
        3. Register File
        4. IR
        5. ALU:算术逻辑单元
    4. 实验器材
    5. 实验过程与结果
      1. 代码实现
        1. MultipleCPU.v
        2. ControlUnit.v
        3. WireToReg.v
        4. ALU.v
        5. DataMemory.v
        6. InstructionMemory.v
        7. MUX2L_32.v
        8. MUX4L_32.v
        9. MUX4L_5.v
        10. PC.v
        11. PCjump.v
        12. RegisterFile.v
        13. SignZeroExtend.v
      2. 仿真检验
        1. 编写一个编译器,将 MIPS 汇编程序编译为二进制机器码
        2. Sim.v
        3. 仿真波形
      3. 烧写到 Basys3 实验板
        1. Basys3.v
        2. Debounce.v
        3. SegLED.v
        4. 运行结果
          1. 端口映射
          2. 初始化
          3. 第 1 条指令addiu $1,$0,8
          4. 第 2 条指令ori $2,$0,2
          5. 第 3 条指令xori $3,$2,8
          6. 第 4 条指令sub $4,$3,$1
          7. 第 5 条指令and $5,$4,$2
    6. 实验心得

    多周期CPU设计

    post on 23 Dec 2018 about 29562words require 99min
    CC BY 4.0 (除特别声明或转载文章外)
    如果这篇博客帮助到你,可以请我喝一杯咖啡~

    (更新于 2022)给 YatCPU 打个广告。

    检查的时候被 TA 现场查出了一个 BUG,搞得这门课最后只拿到 97 分(Rank1 98)…

    具体来说,就是MultipleCPU.v的输出应该是RegReadData1这种而不该是ReadData1。在仿真波形上看RegReadData1没发现问题,但是写板之后就会出现计算结果早于预期周期出现的情况。

    代码中这里没有改,前排提醒。

    实验目的

    1. 认识和掌握多周期数据通路图的构成、原理及其设计方法;
    2. 掌握多周期 CPU 的实现方法,代码实现方法;
    3. 编写一个编译器,将 MIPS 汇编程序编译为二进制机器码;
    4. 掌握多周期 CPU 的测试方法;
    5. 掌握多周期 CPU 的实现方法。

    实验内容

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

    算术运算指令

    add rd rs rt

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

    功能:rd←rs + rt;reserved 为预留部分,即未用,一般填「0」。

    sub rd rs rt

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

    功能:rd←rs - rt

    addiu rt rs immediate

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

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

    逻辑运算指令

    and rd rs rt

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

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

    andi rt rs immediate

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

    功能:rt←rs & (zero-extend)immediate;immediate 做「0」扩展再参加「与」运算。

    ori rt rs immediate

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

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

    xori rd rs rt

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

    功能:rt←rs$\oplus$(zero-extend)immediate;immediate 做「0」扩展再参加「异或」运算。

    移位指令

    sll rd rt sa

    011000 未用 rt(5 位) rd(5 位) sa(5 位) reserved
               

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

    比较指令

    slti rt rs immediate

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

    功能:if (rs< (sign-extend)immediate) rt =1 else rt=0, 带符号比较,详见 ALU 运算功能表。

    slt rd rs rt

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

    功能:if (rs<rt) rd =1 else rd=0, 具体请看 ALU 运算功能表,带符号。

    存储器读/写指令

    sw rt immediate(rs)

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

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

    lw rt immediate(rs)

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

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

    分支指令

    beq rs rt immediate

    110100 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 位的,也就是以上说的「指令之间指令条数」。

    bne rs rt immediate

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

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

    bltz rs immediate

    110110 rs(5 位) 00000 immediate(16 位)
           

    功能:if(rs<$zero) pc←pc + 4 + (sign-extend)immediate <<2 else pc ←pc + 4

    跳转指令

    j addr

    111000 addr[27:2]
       

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

    jr rs

    111001 rs(5 位) 未用 未用 reserved
             

    功能:pc <- rs,跳转。

    调用子程序指令

    jal addr

    111010 addr[27:2]
       

    功能:调用子程序,pc <- {(pc+4)[31:28],addr[27:2],2'b00}$31<-pc+4,返回地址设置;子程序返回,需用指令 jr $31。跳转地址的形成同 j addr 指令。

    停机指令

    halt

    111111 00000000000000000000000000(26 位)
       

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

    实验原理

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

    CPU 在处理指令的几个步骤

    flowchart LR
    取指令IF-->指令译码ID
    指令译码ID-->指令执行EXE
    指令执行EXE-->存储器访问MEM
    存储器访问MEM-->结果写回WB
    结果写回WB-->取指令IF
    

    图 1 CPU 指令处理过程

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

    取指令(IF)

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

    指令译码(ID)

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

    指令执行(EXE)

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

    存储器访问(MEM)

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

    结果写回(WB)

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

    MIPS 指令的三种格式

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

    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 位

    多周期 CPU 状态转移图

    多周期CPU状态转移图

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

    多周期 CPU 控制部件的原理结构图

    多周期CPU控制部件的原理结构图

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

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

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

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

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

    其中指令和数据各存储在不同存储器中,即有指令存储器和数据存储器。访问存储器时,先给出内存地址,然后由读或写信号控制操作。对于寄存器组,先给出寄存器地址,读操作时不需要时钟信号,输出端就直接输出相应数据;而在写操作时,在 WE 使能信号为 1 时,在时钟边沿触发将数据写入寄存器。

    控制信号的作用表

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

    控制信号名 状态「0」 状态「1」
    RST 对于 PC,初始化 PC 为程序首地址 对于 PC,PC 接收下一条指令地址
    PCWre PC 不更改,相关指令:halt,另外,除‘000’状态之外,其余状态慎改 PC 的值。 PC 更改,相关指令:除指令 halt 外,另外,在‘000’状态时,修改 PC 的值合适。
    ALUSrcA 来自寄存器堆 data1 输出,相关指令:add、sub、addiu、and、andi、ori、xori、slt、slti、sw、lw、beq、bne、bltz 来自移位数 sa,同时,进行(zero-extend)sa,即{ {27{1'b0},sa},相关指令:sll
    ALUSrcB 来自寄存器堆 data2 输出,相关指令:add、sub、and、slt、beq、bne、bltz 来自 sign 或 zero 扩展的立即数,相关指令:addiu、andi、ori、xori、slti、lw、sw、sll
    DBDataSrc 来自 ALU 运算结果的输出,相关指令:add、sub、addiu、and、andi、ori、xori、sll、slt、slti 来自数据存储器(Data MEM)的输出,相关指令:lw
    RegWre 无无写寄存器组寄存器,相关指令:beq、bne、bltz、j、sw、jr、halt 寄存器组寄存器写使能,相关指令:add、sub、addiu、and、andi、ori、xori、sll、slt、slti、lw、jal
    WrRegDSrc 写入寄存器组寄存器的数据来自 pc+4(pc4),相关指令:jal,写$31 写入寄存器组寄存器的数据来自 ALU 运算结果或存储器读出的数据,相关指令:add、addiu、sub、and、andi、ori、xori、sll、slt、slti、lw
    InsMemRW 写指令存储器 读指令存储器(Ins. Data)
    mRD 输出高阻态 读数据存储器,相关指令:lw
    mWR 无操作 写数据存储器,相关指令:sw
    IRWre IR(指令寄存器)不更改 IR 寄存器写使能。向指令存储器发出读指令代码后,这个信号也接着发出,在时钟上升沿,IR 接收从指令存储器送来的指令代码。与每条指令都相关。

    ALUOp 的功能表

    ALUOp[2..0] 功能 描述 相关指令
    000 Y=A+B add、addiu、sw、lw
    001 Y=A–B sub、beq、bne、bltz
    010 Y=B«A B 左移 A 位 sll
    011 Y=A∨B ori
    100 Y=A∧B andi、and
    101 Y=A<B 不带符号比较 A<B  
    110 Y=A[31]!=B[31]?A[31]>B[31]:A<B 带符号比较 A<B slti、slt
    111 Y=A^B 异或 xori

    ExtSel 的功能表

    ExtSel[1..0] 功能 相关指令
    00 (zero-extend)sa sll
    01 (zero-extend)immediate andi、xori、ori
    10 (sign-extend)immediate addiu、slti、lw、sw、beq、bne、bltz
    11 未用  

    PCSrc 的功能表

    PCSrc[1..0] 功能 相关指令
    00 pc<-pc+4 add、addiu、sub、and、andi、ori、xori、slt、slti、sll、sw、lw、beq(zero=0)、bne(zero=1)、bltz(sign=0)
    01 pc<-pc+4+(sign-extend)immediate ×4 beq(zero=1)、 bne(zero=0)、bltz(sign=1)
    10 pc<-rs jr
    11 pc<-{pc[31:28],addr[27:2],2’b00} j、jal

    RegDs 的功能表

    RegDs[1..0] 写寄存器组寄存器的地址 相关指令
    00 0x1F($31) jal(用于保存返回地址$31<-pc+4
    01 rt addiu、andi、ori、xori、slti、lw
    10 rd add、sub、and、slt、sll
    11 未用  

    相关部件及引脚说明

    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 时,在时钟边沿触发写入

    IR

    指令寄存器,用于存放正在执行的指令代码。由于 RW 模块所需要的寄存器 IR 太多,因此合在了一起。

    ALU:算术逻辑单元

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

    实验器材

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

    实验过程与结果

    代码实现

    MultipleCPU.v

    多周期 CPU 的顶层连接文件,主要是调用下层模块并将它们输入输出连在一起。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    
    `timescale 1ns / 1ps
    module MultipleCPU(
    	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] newAddress,
    	output [31:0] result,
    	output PCWre
    );
    	wire [31:0] A,B;
    	wire [31:0] currentAddress_4, extendImmediate, currentAddress_immediate, outAddress, ALUM2DR;
    	wire [4:0] WriteReg;
    	wire [25:0] address;
    	wire zero,sign, ALUSrcA,ALUSrcB, ALUM2Reg, RegWre, WrRegData, InsMemRW, DataMemRW, IRWre;
    	wire [1:0] ExtSel, PCSrc, RegOut;
    	wire [2:0] ALUOp;
    	wire [31:0] RegReadData1, RegReadData2, RegResult, RegDataOut;
    	ControlUnit cu(CLK, Reset, op, zero,sign,PCWre, ALUSrcA, ALUSrcB, ALUM2Reg,
    		RegWre, WrRegData, InsMemRW, DataMemRW, IRWre, ExtSel, PCSrc, RegOut, ALUOp);
    	PC pc(CLK, Reset, PCWre, newAddress, currentAddress);
    	InstructionMemory im(InsMemRW, currentAddress, CLK, IRWre, op, rs, rt, rd, immediate, address);
    	RegisterFile rf(CLK, RegWre, rs, rt, WriteReg, WriteData, ReadData1, ReadData2);
    	ALU alu(ALUOp, A, B, zero, result,sign);
    	SignZeroExtend sze(ExtSel, immediate, extendImmediate);
    	DataMemory dm(DataMemRW, RegResult, RegReadData2, DataOut);
    	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_4(ALUSrcA, RegReadData1, RegReadData2, A);
    	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
    

    ControlUnit.v

    控制信号模块,通过解析 op 得到该指令的各种控制信号。定义了很多用到的常量,可读性还是比较高的。 控制单元通过输入的 zero 零标志位与当前指令中对应的指令部分来确定当前整个 CPU 程序中各模块的工作和协作情况,根据 CPU 运行逻辑,事先对整个 CPU 中控制信号的控制,以此来达到指挥各个模块协同工作的目的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    
    `timescale 1ns / 1ps
    module ControlUnit(
    	input CLK,	//时钟
    	input reset,	//重置信号
    	input [5:0] op,	//op操作符
    	input zero,	//ALU的zero输出
    	input sign,
    	output reg PCWre,	//(PC)PC是否更改,如果为0,PC不更改,另外,除D_Tri == 000状态之外,其余状态也不能改变PC的值。
    	output reg ALUSrcA,
    	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操作控制
    );
    	parameter[2:0]
    		_ADD=	3'b000,
    		_SUB=	3'b001,
    		_SLL=	3'b010,
    		_OR=	3'b011,
    		_AND=	3'b100,
    		_SLTU=	3'b101,
    		_SLT=	3'b110,
    		_XOR=	3'b111,
    		IF=	3'b000,	//3位D触发器,代表8个状态
    		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,
    		SUB=	6'b000001,
    		ADDIU=	6'b000010,
    		AND=	6'b010000,
    		ANDI=	6'b010001,
    		ORI=	6'b010010,
    		XORI=	6'b010011,
    		SLL=	6'b011000,
    		SLTI=	6'b100110,
    		SLT=	6'b100111,
    		SW=	6'b110000,
    		LW=	6'b110001,
    		BEQ=	6'b110100,
    		BNE=	6'b110101,
    		BLTZ=	6'b110110,
    		J=	6'b111000,
    		JR=	6'b111001,
    		JAL=	6'b111010,
    		HALT=	6'b111111;
    	reg[2:0] D_Tri;
    	initial begin
    		PCWre=0;
    		ALUSrcB=0;
    		ALUM2Reg=0;
    		RegWre=0;
    		WrRegData=0;
    		//no change
    		InsMemRW=0;
    		DataMemRW=0;
    		//no change
    		IRWre=1;
    		ExtSel=0;
    		PCSrc=0;
    		RegOut=0;
    		ALUOp=0;
    		D_Tri=0;
    	end
    	//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, BNE, BLTZ:  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
    	always@(op or zero)begin//一般信号
    		ALUSrcA=	op==SLL;
    		ALUSrcB=	op==ADDIU||op==ANDI||op==ORI||op==XORI||op==SLTI||op==LW||op==SW||op==SLL;
    		ALUM2Reg=	op==LW;
    		WrRegData=	op!=JAL;	//2路选择器,判断数据写入是否为PC指令,如果为1,则不是,jar用到
    		InsMemRW=	0;//(IM)读写控制信号,1为写,0位读,固定为0
    		IRWre=	1;   //寄存器写使能,暂时没什么用,固定为1
    		ExtSel=	op==SLL?2'b00:op==ANDI||op==XORI||op==ORI?2'b01:2'b10; //(EXT)控制补位,如果为1,进行符号扩展,如果为0,全补0
    		PCSrc=	op==J||op==JAL?2'b11:
    				op==JR?2'b10:
    				(op==BEQ&&zero==1)||(op==BNE&&zero==0)||(op==BLTZ&&sign==1)?2'b01:
    				2'b00;  //4路选择器,选择PC指令来源
    		RegOut=	op==JAL?2'b00:
    				op==ADD||op==SUB||op==AND||op==SLT||op==SLL?2'b10:
    				2'b01; //4路选择器,判断写寄存器地址的来源
    		ALUOp=	op==SUB||op==BEQ||op==BNE||op==BLTZ?_SUB:
    				op==SLL?_SLL:
    				op==ORI?_OR:
    				op==ANDI||op==AND?_AND:
    				op==SLT||op==SLTI?_SLT:
    				op==XORI?_XOR:_ADD;	//(ALU)ALU操作控制
    	 end
    endmodule
    

    WireToReg.v

    本次实验老师重点检查的对象,一定要在时钟下降沿才能将计算结果写入到寄存器里面去。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    `timescale 1ns / 1ps
    module WireToReg(
    	input CLK,
    	input RW, //未用
    	input [31:0]data1,
    	output reg[31:0]data2
    );
    	integer i;
    	always@(negedge CLK)begin
    		for (i = 0; i < 32; i = i + 1)data2[i] = data1[i];
    	end
    endmodule
    

    ALU.v

    该部分为算术逻辑单元,用于逻辑指令计算和跳转指令比较。ALUOp 用于控制算数的类型,A、B 为输入数,result 为运算结果,zero、sign 主要用于 beq、bne、bltz 等指令的判断。 ALU 算术逻辑单元的功能是根据控制信号从输入的数据中选取对应的操作数,根据操作码进行运算并输出结果与零标志位。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    `timescale 1ns / 1ps
    module ALU(
    	input[2:0] ALUOp,
    	input[31:0] A,
    	input[31:0] B,
    	output zero,
    	output reg[31:0] result,
    	output sign
    );
    	parameter _ADD=	3'b000;
    	parameter _SUB=	3'b001;
    	parameter _SLL=	3'b010;
    	parameter _OR=	3'b011;
    	parameter _AND=	3'b100;
    	parameter _SLTU=	3'b101;
    	parameter _SLT=	3'b110;
    	parameter _XOR=	3'b111;
    	assign zero=	result==0;
    	assign sign=	result[31];
    	always@(*)begin
    		case(ALUOp)
    		_ADD:	result=	A+B;
    		_SUB:	result=	A-B;
    		_SLL:	result=	A<<B;
    		_OR:	result=	A|B;
    		_AND:	result=	A&B;
    		_SLTU:	result=	A<B;
    		_SLT:	result=	A[31]!=B[31]?A[31]>B[31]:A<B;
    		_XOR:	result=	A^B;
    		default:	result=	0;
    		endcase
    	end
    endmodule
    

    DataMemory.v

    该部分控制内存存储,用于内存存储、读写。用 256 大小的 8 位寄存器数组模拟内存,采用小端模式。 DataMenRW 控制内存读写。由于指令为真实地址,所以不需要<<2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    `timescale 1ns / 1ps
    module DataMemory(
    	input DataMemRW,
    	input [31:0] DAddr,
    	input [31:0] DataIn,
    	output reg [31:0] DataOut
    );
    	reg [7:0] memory[0:255];
    	integer i;
    	initial  begin
    		for (i = 0; i < 256; i = i + 1)memory[i] <= 0;
    		for (i = 0; i < 32; i = i + 1)DataOut[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
    

    InstructionMemory.v

    该部分为指令寄存器,通过一个 256 大小的 8 位寄存器数组来保存从文件输入的全部指令。然后通过输入的地址,找到相应的指令,输出到 IDataOut。 指令存储器的功能是存储读入的所有 32-bit 位宽的指令,根据程序计数器 PC 中的指令地址进行取指令操作并对指令类型进行分析,通过指令类型对所取指令的各字段进行区分识别,最后将对应部分传递给其他模块进行后续处理。 指令存储器中每个单元的位宽为 8-bit,也就是存储每条 32-bit 位宽的指令都需要占据 4 个单元,所以第 n(n 大于或等于 0)条指令所对应的起始地址为 4n,且占据第 4n,4n+1,4n+2,4n+3 这四个单元。取出指令就是将这四个单元分别取出,因为指令的存储服从高位指令存储在低位地址的规则,所以 4n 单元中的字段是该条指令的最高 8 位,后面以此类推,并通过左移操作将指令的四个单元部分移动到相对应的位置,以此来得到所存指令。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
    `timescale 1ns / 1ps
    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:255];	//新建一个256位的数组用于储存指令
    	initial begin
    		//初始化
    		op <= 0;
    		rs <= 0;
    		rt <= 0;
    		rd <= 0;
    		immediate <= 0;
    		address <= 0;
    		$readmemb("C:/Users/wukan/Documents/VIVADO/MultipleCPU/input.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
    

    MUX2L_32.v

    三十二线双路选择器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    `timescale 1ns / 1ps
    module MUX2L_32(
    	input control,
    	input [31:0] in0,
    	input [31:0] in1,
    	output [31:0] out
    );
    	assign out = control ? in1 : in0;
    endmodule
    

    MUX4L_32.v

    三十二线四路选择器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    `timescale 1ns / 1ps
    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
    );
    	assign out = control[0] ? (control[1] ? in11 : in01) : (control[1] ? in10 : in00);
    endmodule
    

    MUX4L_5.v

    五线四路选择器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    `timescale 1ns / 1ps
    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
    );
    	assign out = control[0] ? (control[1] ? in11 : in01) : (control[1] ? in10 : in00);
    endmodule
    

    PC.v

    CLK 上升沿触发,更改指令地址。由于指令地址存储在寄存器里,一开始需要赋 currentAddress 为 0。Reset 是重置信号,当为 1 时,指令寄存器地址重置。PCWre 的作用为保留现场,如果 PCWre 为 0,指令地址不变。 PC 程序计数器用于存放当前指令的地址,当 PC 的值发生改变的时候,CPU 会根据程序计数器 PC 中新得到的指令地址,从指令存储器中取出对应地址的指令,根据 PCSrc 控制信号的值选择指令地址是要进行 PC+4 或者跳转等操作。若 PC 程序计数器检测到 Reset 输入信号为 1 时,则对程序计数器存储的当前指令地址进行清零处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    `timescale 1ns / 1ps
    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
    

    PCjump.v

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    `timescale 1ns / 1ps
    module PCjump(
    	input [31:0] PC0,	//指令
    	input [25:0] inAddress,	//输入地址
    	output [31:0] outAddress	//输出地址(指令)
    );
    	assign outAddress[31:28] = PC0[31:28];
    	assign outAddress[27:2] = inAddress;
    	assign outAddress[1:0] = 2'b00;
    endmodule
    

    RegisterFile.v

    该部分为寄存器读写单元,储存寄存器组,并根据地址对寄存器组进行读写。WE 的作用是控制寄存器是否写入。同上,通过一个 32 大小的 32 位寄存器数组来模拟寄存器,开始时全部置 0。通过访问寄存器的地址,来获取寄存器里面的值,并进行操作。(由于$0 恒为 0,所以写入寄存器的地址不能为 0) 寄存器组中的每个寄存器位宽 32-bit,是存放 ALU 计算所需要的临时数据的,与数据存储器不同,可能会在程序执行的过程中被多次覆盖,而数据存储器内的数据一般只有 sw 指令才能进行修改覆盖。寄存器组会根据操作码 opCode 与 rs,rt 字段相应的地址读取数据,同时将 rs,rt 寄存器的地址和其中的数据输出,在 CLK 的下降沿到来时将数据存放到 rd 或者 rt 字段的相应地址的寄存器内。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    `timescale 1ns / 1ps
    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 CLK)begin
    		//如果寄存器不为0,并且RegWre为真,写入数据
    		if (RegWre && WriteReg != 0)  register[WriteReg] = WriteData;
    	end
    endmodule
    

    SignZeroExtend.v

    比较简单的一个模块,用于立即数的扩展。ExtSel 为控制补位信号。判断后,将 extendImmediate 的前 16 位全补 1 或 0 即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    `timescale 1ns / 1ps
    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
    

    仿真检验

    编写一个编译器,将 MIPS 汇编程序编译为二进制机器码

    使用支持c++11以上标准的编译器编译下述代码,得到编译器wkmips

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    
    #include <bits/stdc++.h>
    #define TRANS(s, len) (bitset<len>(stoi(s, 0, 0)).to_string())
    using namespace std;
    string compiler(string s)
    {
    	static unordered_map<string, string> mp{
    		{"add", "000000"},
    		{"sub", "000001"},
    		{"addiu", "000010"},
    		{"and", "010000"},
    		{"andi", "010001"},
    		{"ori", "010010"},
    		{"xori", "010011"},
    		{"sll", "011000"},
    		{"slti", "100110"},
    		{"slt", "100111"},
    		{"sw", "110000"},
    		{"lw", "110001"},
    		{"beq", "110100"},
    		{"bne", "110101"},
    		{"bltz", "110110"},
    		{"j", "111000"},
    		{"jr", "111001"},
    		{"jal", "111010"},
    		{"halt", "111111"}};
    	vector<string> v;
    	for (char &c : s)
    		if (!isgraph(c) || c == '$' || c == '(' || c == ')')
    			c = ',';
    	for (istringstream sin(s); getline(sin, s, ',');)
    		if (!s.empty())
    			v.push_back(s);
    	if (v.empty())
    		return "";
    	s = mp[v[0]];
    	if (v.size() == 1)
    		s += string(26, '0');
    	else if (v.size() == 2)
    	{
    		if (v[0] == "jr")
    			s += TRANS(v[1], 5) + string(21, '0');
    		else
    			s += bitset<26>(stoi(v[1], 0, 0) >> 2).to_string();
    	}
    	else if (v.size() == 3)
    	{
    		if (v[0] == "bltz")
    			s += TRANS(v[1], 5) + string(5, '0') + TRANS(v[2], 16);
    		else
    			s += TRANS(v[1], 5) + TRANS(v[2], 21);
    	}
    	else
    	{
    		if (v[0] == "sw" || v[0] == "lw")
    			swap(v[2], v[3]);
    		if (v[0] == "sll")
    		{
    			s += string(5, '0');
    			s += TRANS(v[2], 5) + TRANS(v[1], 5);
    			s += TRANS(v[3], 5) + string(6, '0');
    		}
    		else if (v[0].find('i') != v[0].npos || v[0] == "sw" || v[0] == "lw" || v[0] == "beq" || v[0] == "bne")
    		{
    			s += TRANS(v[2], 5) + TRANS(v[1], 5);
    			s += TRANS(v[3], 16);
    		}
    		else
    		{
    			s += TRANS(v[2], 5) + TRANS(v[3], 5);
    			s += TRANS(v[1], 5) + string(11, '0');
    		}
    	}
    	return s;
    }
    int main(int argc, char **argv)
    {
    	if (argc < 2)
    		return 0;
    	ifstream fin(argv[1]);
    	ofstream fout(argv[argc - 1]);
    	for (string s; getline(fin, s); fout << '\n')
    	{
    		s = compiler(s);
    		for (int i = 0; i < s.size(); ++i)
    		{
    			fout << s[i];
    			if (i % 8 == 7)
    				fout << ' ';
    		}
    	}
    }
    

    待转换的 MIPS 代码test.txt内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    addiu  $1,$0,8
    ori  $2,$0,2
    xori  $3,$2,8
    sub  $4,$3,$1
    and  $5,$4,$2
    sll   $5,$5,2
    beq  $5,$1,-2
    jal  0x0000050
    slt  $8,$13,$1
    addiu  $14,$0,-2
    slt  $9,$8,$14
    slti  $10,$9,2
    slti  $11,$10,0
    add  $11,$11,$10
    bne  $11,$2,-2
    addiu  $12,$0,-2
    addiu  $12,$12,1
    bltz  $12,-2
    andi  $12,$2,2
    j  0x000005C
    sw  $2,4($1)
    lw  $13,4($1)
    jr  $31
    halt
    

    在命令行中键入./wkmips test.txt input.txt,得到编译后文件input.txt

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    00001000 00000001 00000000 00001000
    01001000 00000010 00000000 00000010
    01001100 01000011 00000000 00001000
    00000100 01100001 00100000 00000000
    01000000 10000010 00101000 00000000
    01100000 00000101 00101000 10000000
    11010000 00100101 11111111 11111110
    11101000 00000000 00000000 00010100
    10011101 10100001 01000000 00000000
    00001000 00001110 11111111 11111110
    10011101 00001110 01001000 00000000
    10011001 00101010 00000000 00000010
    10011001 01001011 00000000 00000000
    00000001 01101010 01011000 00000000
    11010100 01001011 11111111 11111110
    00001000 00001100 11111111 11111110
    00001001 10001100 00000000 00000001
    11011001 10000000 11111111 11111110
    01000100 01001100 00000000 00000010
    11100000 00000000 00000000 00010111
    11000000 00100010 00000000 00000100
    11000100 00101101 00000000 00000100
    11100111 11100000 00000000 00000000
    11111100 00000000 00000000 00000000
    

    检验:手动将指令转换成二进制代码如下表,可对比检验上述编译器转换结果无误。

    地址 汇编程序 op(6) rs(5) rt(5) rd(5)/immediate(16) 16 进制数代码
    0x00000000 addiu $1,$0,8 000010 00000 00001 00000000 00001000 08010008
    0x00000004 ori $2,$0,2 010010 00000 00010 00000000 00000010 48020002
    0x00000008 xori $3,$2,8 010011 00010 00011 00000000 00001000 4c430008
    0x0000000c sub $4,$3,$1 000001 00011 00001 00100000 00000000 04612000
    0x00000010 and $5,$4,$2 010000 00100 00010 00101000 00000000 40822800
    0x00000014 sll $5,$5,2 011000 00000 00101 00101000 10000000 60052880
    0x00000018 beq $5,$1,-2 110100 00001 00101 11111111 11111110 d025fffe
    0x0000001c jal 0x0000050 111010 00000 00000 00000000 00010100 e8000014
    0x00000020 slt $8,$13,$1 100111 01101 00001 01000000 00000000 9da14000
    0x00000024 addiu $14,$0,-2 000010 00000 01110 11111111 11111110 080efffe
    0x00000028 slt $9,$8,$14 100111 01000 01110 01001000 00000000 9d0e4800
    0x0000002c slti $10,$9,2 100110 01001 01010 00000000 00000010 992a0002
    0x00000030 slti $11,$10,0 100110 01010 01011 00000000 00000000 994b0000
    0x00000034 add $11,$11,$10 000000 01011 01010 01011000 00000000 016a5800
    0x00000038 bne $11,$2,-2 110101 00010 01011 11111111 11111110 d44bfffe
    0x0000003c addiu $12,$0,-2 000010 00000 01100 11111111 11111110 080cfffe
    0x00000040 addiu $12,$12,1 000010 01100 01100 00000000 00000001 098c0001
    0x00000044 bltz $12,-2 110110 01100 00000 11111111 11111110 d980fffe
    0x00000048 andi $12,$2,2 010001 00010 01100 00000000 00000010 444c0002
    0x0000004c j 0x000005C 111000 00000 00000 00000000 00010111 e0000017
    0x00000050 sw $2,4($1) 110000 00001 00010 00000000 00000100 c0220004
    0x00000054 lw $13,4($1) 110001 00001 01101 00000000 00000100 c42d0004
    0x00000058 jr $31 111001 11111 00000 00000000 00000000 e7e00000
    0x0000005c halt 111111 00000 00000 00000000 00000000 fc000000

    Sim.v

    仿真模块。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    `timescale 1ns / 1ps
    module Sim;
    	reg CLK;	//时钟信号
    	reg Reset;	//置零信号
    	MultipleCPU mcpu(CLK,Reset);
    	initial begin
    		CLK=	0;
    		Reset=	1;	//刚开始设置pc为0
    		#50;	//等待Reset完成
    		CLK=	!CLK;	//下降沿,使PC先清零
    		#50;
    		Reset=	0;	//清除保持信号
    	  	forever #50 begin	//产生时钟信号,周期为50s
    			CLK=	!CLK;
    		end
    	end
    endmodule
    

    仿真波形

    波形比较长,分成三部分逐一分析。

    在这里插入图片描述

    第一次执行到地址0x00000018的时候,执行了beq $5,$1,-2,此时$5$1都是 8,回退到0x00000014;第二次执行到地址0x00000018的时候,$5是 32,$1都是 8,不等,继续执行下一条指令jal 0x0000050。于是跳到0x00000050,并在0x00000058地址上跳回0x00000020

    在这里插入图片描述

    第一次执行到地址0x00000038的时候,执行了bne $11,$2,-2,此时$11是 1,$2是 2,不等,回退到0x00000034;第二次执行到地址0x00000038的时候,$11$2都是 2,继续执行下一条指令。

    在这里插入图片描述

    第一次执行到地址0x00000044的时候,执行了bltz $12,-2,此时$12是-1,小于 0,回退到0x00000040;第二次执行到地址0x00000044的时候,$12是 0,继续执行下一条指令。 执行到地址0x0000004C的时候,执行了j 0x000005C,直接跳转到0x000005C,于是执行该地址上的Halt,波形不再变化。

    烧写到 Basys3 实验板

    Basys3.v

    顶层模块。和单周期没有太大区别。(Reset 的取值)改了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    
    `timescale 1ns / 1ps
    module Basys3(
    	input CLK,
    	input[1:0] SW,	//选择输出信号
    	input Reset,	//重置按钮
    	input Button,	//单脉冲
    	output reg[3:0] AN,	//数码管位选择信号
    	output[7:0] Out	//数码管输入信号
    );
    	parameter T1MS=	100000;
    	reg[16:0] showCounter;
    	wire[31:0] ALU_Out;	//ALU的result输出值
    	wire[31:0] CurPC;
    	wire[31:0] WriteData;	//DB总线值
    	wire[31:0] Reg1Out;	//寄存器组rs寄存器的值
    	wire[31:0] Reg2Out;	//寄存器组rt寄存器的值
    	wire[31:0] DataOut;
    	wire[31:0] instcode;
    	wire myCLK;
    	reg[3:0] store;	//记录当前要显示位的值
    	wire[31:0] newAddress;
    
    	MultipleCPU mcpu(myCLK,Reset,instcode[31:26],instcode[25:21],instcode[20:16],instcode[15:11],instcode[15:0],Reg1Out,Reg2Out,WriteData,DataOut,CurPC,newAddress,ALU_Out);
    	Debounce debounce(CLK,Button,myCLK);
    
    	initial begin
    		showCounter<=	0;
    		AN<=	4'b0111;
    	end
    	always@(posedge CLK)
    		begin
    		if(Reset==1)begin
    		  showCounter<=	0;
    		  AN<=	4'b0000;
    		end else begin
    			showCounter<=	showCounter+1;
    			if(showCounter==T1MS)
    				begin
    					showCounter<=	0;
    					case(AN)
    						4'b1110:	begin
    							AN<=	4'b1101;
    						end
    						4'b1101:	begin
    							AN<=	4'b1011;
    						end
    						4'b1011:	begin
    							AN<=	4'b0111;
    						end
    						4'b0111:	begin
    							AN<=	4'b1110;
    						end
    						4'b0000:	begin
    							AN<=	4'b0111;
    					   end
    					endcase
    				end
    			end
    		end
    	SegLED led(store,Reset,Out);
    	always@(myCLK)begin
    	   case(AN)
    			4'b1110:	begin
    				case(SW)
    					2'b00:	store<=	newAddress[3:0];
    					2'b01:	store<=	Reg1Out[3:0];
    					2'b10:	store<=	Reg2Out[3:0];
    					2'b11:	store<=	WriteData[3:0];
    				endcase
    			end
    			4'b1101:	begin
    				case(SW)
    					2'b00:	store<=	newAddress[7:4];
    					2'b01:	store<=	Reg1Out[7:4];
    					2'b10:	store<=	Reg2Out[7:4];
    					2'b11:	store<=	WriteData[7:4];
    				endcase
    			end
    			4'b1011:	begin
    				case(SW)
    					2'b00:	store<=	CurPC[3:0];
    					2'b01:	store<=	instcode[24:21];
    					2'b10:	store<=	instcode[19:16];
    					2'b11:	store<=	ALU_Out[3:0];
    				endcase
    			end
    			4'b0111 : begin
    				case(SW)
    					2'b00:	store<=	CurPC[7:4];
    					2'b01:	store<=	{3'b000,instcode[25]};
    					2'b10:	store<=	{3'b000,instcode[20]};
    					2'b11:	store<=	ALU_Out[7:4];
    				endcase
    			end
    		endcase
    	end
    endmodule
    

    Debounce.v

    和单周期一样的按键消抖模块。Basys3 板采用的是机械按键,在按下按键时按键会出现人眼无法观测但是系统会检测到的抖动变化,这可能会使短时间内电平频繁变化,导致程序接收到许多错误的触发信号而出现许多不可知的错误。消抖操作是每当检测到 CLK 上升沿到来时检测一次当前电平信号并记录,同计数器开始计数,若在计数器达到 5000 之前电平发生变化,则将计数器清零,若达到 5000,则将该记录电平取反输出。 因为程序开始时已经运行第一条指令,为避免跳过第一条指令计算值的写入,我们的输入需要从下降沿开始,因此我们给按键信号取反后再输入。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    `timescale 1ns / 1ps
    module Debounce(clk,key_in,key_out);
        parameter SAMPLE_TIME = 5000;
        input clk;
        input key_in;
        output key_out;
        reg [21:0] count_low;
        reg [21:0] count_high;
        reg key_out_reg;
        always @(posedge clk)
        if(key_in ==1'b0)
            count_low <= count_low + 1;
        else
            count_low <= 0;
        always @(posedge clk)
            if(key_in ==1'b1)
                count_high <= count_high + 1;
            else
                count_high <= 0;
        always @(posedge clk)
            if(count_high == SAMPLE_TIME)
                key_out_reg <= 1;
            else if(count_low == SAMPLE_TIME)
                key_out_reg <= 0;
        assign key_out = !key_out_reg;
    endmodule
    

    SegLED.v

    数码管译码模块。译码模块将 CPU 运算的结果转换成 7 段数码管中各个数码管显示所需的高低电平信号,该单元的输入为 4-bit 位宽的二进制数。其中,七段数码管的八个电平控制输出中最低位是小数点的显示信号,但小数点在 CPU 运行时没有用到,恰好用于标记 Reset 状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    `timescale 1ns / 1ps
    module SegLED(
    	input[3:0] Store,
    	input Reset,
    	output reg[7:0] Out
    );
    	always@(Store or Reset)begin
    		begin
    			case(Store)
    				4'b0000:	Out=	8'b00000011;	//0
    				4'b0001:	Out=	8'b10011111;	//1
    				4'b0010:	Out=	8'b00100101;	//2
    				4'b0011:	Out=	8'b00001101;	//3
    				4'b0100:	Out=	8'b10011001;	//4
    				4'b0101:	Out=	8'b01001001;	//5
    				4'b0110:	Out=	8'b01000001;	//6
    				4'b0111:	Out=	8'b00011111;	//7
    				4'b1000:	Out=	8'b00000001;	//8
    				4'b1001:	Out=	8'b00001001;	//9
    				4'b1010:	Out=	8'b00010001;	//A
    				4'b1011:	Out=	8'b11000001;	//b
    				4'b1100:	Out=	8'b01100011;	//C
    				4'b1101:	Out=	8'b10000101;	//d
    				4'b1110:	Out=	8'b01100001;	//E
    				4'b1111:	Out=	8'b01110001;	//F
    				default:	Out=	8'b00000000;	//all light
    			endcase
    		end
    	end
    endmodule
    

    运行结果

    端口映射

    在这里插入图片描述

    初始化

    在这里插入图片描述

    所有寄存器被初始化为 0。

    第 1 条指令addiu $1,$0,8

    在这里插入图片描述

    当前地址 00,下一地址 04。

    在这里插入图片描述

    0 号寄存器,值为 0。

    在这里插入图片描述

    1 号寄存器。立即数 8。

    在这里插入图片描述 运算结果 8。

    第 2 条指令ori $2,$0,2

    在这里插入图片描述

    当前地址 04,下一地址 08。

    在这里插入图片描述

    0 号寄存器,值为 0。

    在这里插入图片描述

    2 号寄存器,立即数 2。

    在这里插入图片描述

    ALU 结果为 2。

    第 3 条指令xori $3,$2,8

    在这里插入图片描述

    当前地址 08,下一地址 0c。

    在这里插入图片描述

    2 号寄存器,值为 02。

    在这里插入图片描述

    3 号寄存器,值为 0a(这里是已经写入后的结果)。

    在这里插入图片描述

    ALU 结果为 0a。

    第 4 条指令sub $4,$3,$1

    在这里插入图片描述

    当前地址 0c,下一地址 10。

    在这里插入图片描述

    3 号寄存器,值为 0a。

    在这里插入图片描述

    1 号寄存器,值为 08。

    在这里插入图片描述

    ALU 结果为 02。

    第 5 条指令and $5,$4,$2

    在这里插入图片描述

    当前地址 10,下一地址 14。

    在这里插入图片描述

    4 号寄存器,值为 2。

    在这里插入图片描述

    2 号寄存器,值为 2。

    在这里插入图片描述

    ALU 结果为 2。

    实验心得

    多周期 CPU 相对于单周期主要是要多思考一下自动状态机转换的问题。此外,还需要单独增加几个寄存器用于接收中间的结果,并且需要连上时钟。

    一个学期的实验课终于到此告一段落。作为公认较难的一门课,即使学分较为少,我觉得对人的锻炼却是非常大的。每一年计组的课题都会略有区别,这让我们很难找到参考的地方;甚至很多东西往年的资料和今年的完全相反,如果不加思考完全照抄可能反而会带来误导。因此,虽然难度较大,但是却更加符合我们日后工作科研中可能会遇到的情况。不管怎样,自己总是算是做掉了多周期 CPU,几天来的心血没有白费。

    Loading comments...