[置顶] MIPS多周期CPU设计(Verilog)

请勿转载,本人对该文章保留有所有权利,如果需要转载请联系[email protected],经本人同意后才可转载

2016/05/12最新更改:RAM模块有错误,不需要乘四,因为ALU计算的结果直接就是地址了。
2016/05/13最新更改:更改了RegFile模块的触发条件,不用CLK上升沿信号触发,因为与CtrlUnit共享CLK会发生延迟,导致下一个时钟上升沿才做相应动作,因此在这里我使用地址和输入数据的更改作为触发条件,这样更加便于模块化思想,因为这不需要在WB阶段的上一个阶段就发出相应信号,而我们只需考虑当前阶段的信号问题就可以了。

前言:设计完了单周期CPU之后,接下来很自然地需要设计多周期CPU,如果以后有机会的话,我非常希望可以再写一篇关于MIPS流水线CPU的设计博客。因为最近这几周忙着考公选以及写论文所以blog更新的略慢,对此造成的困扰十分抱歉。那么话不多说,接下来就跟着我开始多周期CPU设计的历程吧!如果你之前有看了我的单周期CPU设计博客并且理解了其中的思想,那么接下来的设计肯定也不会难倒你的。

  • 该文仅供参考,其中也许很多bug,请注意
  • 仅供复习,学习用,若有问题或者建议请在评论区留言,我会尽快回复
  • 这个设计增加了一个功能,就是在初始化的时候可以通过外部的pc来确定代码段的存放地址,例如在我的测试代码中设置的outside_pc为4,所以ROM会在地址为4的地方开始放置代码以及reset的时候会从地址为4的地方开始执行。
  • 在实验过程中发现多周期一个很重大的问题就是延迟的问题,必须合理设计才不会掉坑,请注意。

多周期CPU概念

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

[置顶] MIPS多周期CPU设计(Verilog)_第1张图片

系统概述

下图是多周期CPU的数据通路图,可以看到这个数据通路和单周期CPU数据通路图基本一致,但是不一样的地方有以下几点:(1) ControlUnit增加了Reset信号和时钟,这是因为需要在内部实现状态的转移和控制信号的重置;(2)增加了IR寄存器以及ADR,BDR,ALUout,ALUM2DR –>IR指令寄存器,目的是使指令代码保持稳定,还有pc增加写使能控制信号pcWre,也是确保pc适时修改,原因都是和多周期工作的CPU有关。ADR、BDR、ALUout、ALUM2DR四个寄存器不需要写使能信号,其作用是切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延时变为多个分段小延时;举个例子:假设在我们在ALU计算出来数据打算写到RAM里面,那么在EXE阶段过后我们需要进入到MEM阶段,在MEM阶段这个计算的结果在ALUout这个小的延迟能够很好地保持数据的稳定,就算是ALU在这个阶段中产生了其他的计算结果对存入的数据也没有影响。(3)左下角增加了一个小部件,这个部件的作用是用于计算 j 和 jal 跳转指令产生的跳转地址(4)右上角的PC选择端多了几个数据接口,这是因为需要满足j 、jal、jr 跳转指令的需求进而增加的(5) 数据扩展器多了一位选择,这是因为需要满足移位指令扩展(用于sll指令),零扩展(用于ori指令),符号扩展(用于addi等指令);(6) 寄存器写数据端多了一个PC4的输入,这是因为需要满足jal指令的需求;(7)写寄存器地址多了一个$31,这是因为需要满足jal指令的需求。

[置顶] MIPS多周期CPU设计(Verilog)_第2张图片

下图是ALU计算功能表,接下来的ALU模块会根据这个表进行编写。

[置顶] MIPS多周期CPU设计(Verilog)_第3张图片

需要实现的指令如下

[置顶] MIPS多周期CPU设计(Verilog)_第4张图片
[置顶] MIPS多周期CPU设计(Verilog)_第5张图片
[置顶] MIPS多周期CPU设计(Verilog)_第6张图片
[置顶] MIPS多周期CPU设计(Verilog)_第7张图片

控制模块是最复杂的,接下来请仔细看这个实现的代码,下面是它的状态转移图和控制模块的逻辑原理结构图,当然最重要的还是具体的控制信号表,都在下面,棒棒哒!!!

[置顶] MIPS多周期CPU设计(Verilog)_第8张图片

[置顶] MIPS多周期CPU设计(Verilog)_第9张图片

[置顶] MIPS多周期CPU设计(Verilog)_第10张图片

代码实现区

主要的实现思想:

(1)和单周期CPU相同,我们这次也一样分成很多的小模块,这样实现起来debug很快,因为代码分离了,所以更改起来也会更加方面,可以看到模块化的思想很重要;

(2)我们需要一个主模块将所有的子模块通过连线囊括起来,这个主模块有两个输入<1.CLK时钟,2.Reset重置信号>,在整个系统中,这个主模块起到一个提供数据通路(也就是图中所看到的一些线路)的角色。

Main.v 模块

`timescale 1ns / 1ps
//主模块,用于小模块连线作用
module Main(CLK, RST, outside_pc, ins, now_pc);
  input CLK, RST;
  input [31:0] outside_pc;
  output [31:0] ins, now_pc;
  parameter endReg = 5'b11111;

  // 数据通路
  wire [31:0] pc, pc0, pc4, i_IR, instruction, pcChoose3, pcChoose1, extendData, ALUResult, WirteData, ReadData1, ReadData2, DataOut;
  wire [31:0] o_ADR, o_BDR, o_ALUout, i_ALUM2DR, i_ALUData2;
  wire zero;
  // 控制信号
  wire [2:0] ALUOp;
  wire [1:0] ExtSel, RegOut, PCSrc;
  wire PCWre, IRWre, InsMemRW, WrRegData, RegWre, ALUSrcB, DataMemRW, ALUM2Reg;

  // 数据选择输出
  wire [4:0] fiveChooseData;
  wire [31:0] InputWriteData1;

  // 引脚输出
  assign ins = instruction;
  assign now_pc = pc0;

  PC PC(CLK, pc, PCWre, RST, pc0, outside_pc); // 添加了外部pc
  PCAddFour PCAddFour(pc0, pc4);
  ROM ROM(i_IR, pc0, InsMemRW, outside_pc); // 添加了外部pc
  IR IR(i_IR, CLK, IRWre, instruction);
  PCJump PCJump(pc0, instruction[25:0], pcChoose3);
  DataSelect_5_Bit DataSelect_5_Bit(endReg, instruction[20:16], instruction[15:11], RegOut, fiveChooseData);
  RegFile RegFile(instruction[25:21], instruction[20:16], fiveChooseData, WirteData, RegWre, CLK, ReadData1, ReadData2);
  DataReg ADR(ReadData1, CLK, o_ADR);
  DataReg BDR(ReadData2, CLK, o_BDR);
  SignExtend SignExtend(instruction[15:0], ExtSel, extendData);
  DataSelect_2To1_32Bit ALU_DATA2(o_BDR, extendData, ALUSrcB, i_ALUData2);
  ALU ALU(o_ADR, i_ALUData2, ALUOp, zero, ALUResult);
  DataReg ALUout(ALUResult, CLK, o_ALUout);
  RAM RAM(o_BDR, o_ALUout, DataMemRW, DataOut);
  DataSelect_2To1_32Bit Write_Data(ALUResult, DataOut, ALUM2Reg, i_ALUM2DR);
  DataReg ALUM2DR(i_ALUM2DR, CLK, InputWriteData1);
  DataSelect_2To1_32Bit WrRegDataChoose(pc4, InputWriteData1, WrRegData, WirteData);
  PCAddImm PCAddImm(pc4, extendData, pcChoose1);
  DataSelect_4To1_32Bit PCSelect(pc4, pcChoose1, ReadData1, pcChoose3, PCSrc, pc);
  CtrlUnit CtrlUnit(instruction[31:26], CLK, RST, zero, PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, DataMemRW, ALUM2Reg, ExtSel, RegOut, PCSrc, ALUOp);
endmodule

接下来是控制模块,控制模块按照上面的原理结构图我们可以细分为三个小模块,这里我分别命名为DFlipFlop.v(触发器模块)、NextState.v(下一状态模块)、OutputFunc.v(输出函数模块)

`timescale 1ns / 1ps
// 控制模块,包含了D触发器,状态转移,输出函数;
module CtrlUnit(opcode, clk, reset, zero, PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, DataMemRW, ALUM2Reg, ExtSel, RegOut, PCSrc, ALUOp);
    input [5:0]opcode;
    input zero, clk, reset;
    output PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, DataMemRW, ALUM2Reg;
    output [1:0]ExtSel, RegOut, PCSrc;
    output [2:0]ALUOp;

    wire [2:0]i_state, o_state;

    DFilpFlop DFilpFlop(i_state, reset, clk, o_state);
    NextState NextState(o_state, opcode, i_state);
    OutputFunc OutputFunc(o_state, opcode, zero, PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, DataMemRW, ALUM2Reg, ExtSel, RegOut, PCSrc, ALUOp);

endmodule

DFlipFlop.v 模块,这是CtrlUnit.v的一个子模块

`timescale 1ns / 1ps
// D触发器模块
module DFilpFlop(i_state, reset, clk, o_state);
    input [2:0]i_state;
    input reset, clk;
    output reg[2:0]o_state;
    always @(posedge clk) begin
        if (reset) o_state = 3'b000;
        else o_state = i_state;
    end
endmodule

NextState.v 模块,这是CtrlUnit.v的一个子模块

`timescale 1ns / 1ps
// 下一个状态模块
module NextState(i_state, opcode, next_state);
    input [2:0]i_state;
    input [5:0]opcode;
    output reg[2:0]next_state;
    parameter [2:0] IF = 3'b000, // IF状态
                         ID = 3'b001, // ID状态
                         aEXE = 3'b110, // add等指令的EXE状态
                         bEXE = 3'b101, // beq指令的EXE状态
                         cEXE = 3'b010, // sw,lw指令的EXE状态
                         MEM = 3'b011, // MEM状态
                         aWB = 3'b111, //add等指令的WB状态
                         cWB = 3'b100; // lw指令的WB状态
    always @(i_state or opcode) begin
        case (i_state)
            IF: next_state = ID;
            ID: begin
                case (opcode[5:3])
                    3'b111: next_state = IF; // j, jal, jr, halt等指令
                    3'b110: begin
                        if (opcode == 6'b110100) next_state = bEXE; // beq指令
                        else next_state = cEXE; // sw, lw指令
                    end
                    default: next_state = aEXE; // add, sub等指令
                endcase
            end
            aEXE: next_state = aWB;
            bEXE: next_state = IF;
            cEXE: next_state = MEM;
            MEM: begin
                if (opcode == 6'b110001) next_state = cWB; // lw指令
                else next_state = IF; // sw指令
            end
            aWB: next_state = IF;
            cWB: next_state = IF;
            default: next_state = IF;
        endcase
    end
endmodule

OutputFunc.v 模块,这是CtrlUnit.v的一个子模块,注意到这里的控制信号是根据上面那个表来定义的

`timescale 1ns / 1ps
// 输出函数模块
module OutputFunc(state, opcode, zero, PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, DataMemRW, ALUM2Reg, ExtSel, RegOut, PCSrc, ALUOp);
    input [2:0]state;
    input [5:0]opcode;
    input zero;
    output reg PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, DataMemRW, ALUM2Reg;
    output reg[1:0]ExtSel, RegOut, PCSrc;
    output reg[2:0]ALUOp;
    parameter [2:0] IF = 3'b000, // IF状态
                         ID = 3'b001, // ID状态
                         aEXE = 3'b110, // add等指令的EXE状态
                         bEXE = 3'b101, // beq指令的EXE状态
                         cEXE = 3'b010, // sw,lw指令的EXE状态
                         MEM = 3'b011, // MEM状态
                         aWB = 3'b111, //add等指令的WB状态
                         cWB = 3'b100; // lw指令的WB状态
    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;

    always @(state) begin
        // 对PCWre定值
        if (state == IF && opcode != halt) PCWre = 1;
        else PCWre = 0;
        // 对InsMemRW定值
        InsMemRW = 1;
        // 对IRWre定值
        if (state == IF) IRWre = 1;
        else IRWre = 0;
        // 对WrRegData定值
        if (state == aWB || state == cWB) WrRegData = 1;
        else WrRegData = 0;
        // 对RegWre定值
        if (state == aWB || state == cWB || 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 == MEM && opcode == sw) DataMemRW = 1;
        else DataMemRW = 0;
        // 对ALUM2Reg定值
        if (state == cWB) 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 == IF) begin
            RegWre = 0;
            DataMemRW = 0;
        end
    end

endmodule

PC.v模块

`timescale 1ns / 1ps
// PC模块
module PC(clk, i_pc, pcWre, reset, o_pc, outside_pc);
  input wire clk, pcWre, reset;
  input wire [31:0] i_pc, outside_pc;
  output reg [31:0] o_pc;
  always @(pcWre or reset) begin // 这里和单周期不太一样,存在延迟的问题,只有当pcWre改变的时候或者reset改变的时候再检测
    if (reset) begin
      o_pc = outside_pc;
    end else if (pcWre) begin
      o_pc = i_pc;
    end else if (!pcWre) begin //停机时候指令不变
        o_pc = o_pc;
     end
  end
endmodule

PCAddFour.v模块 PC加4模块

`timescale 1ns / 1ps
// PC加4模块
module PCAddFour(i_pc, o_pc);
  input wire [31:0] i_pc;
  output wire [31:0] o_pc;
  assign o_pc[31:0] = i_pc[31:0] + 4;
endmodule

ROM.v 模块 , 指令寄存器模块

`timescale 1ns / 1ps
// 寄存器
module ROM (instruction, addr, read_en_, outside_pc);
    input read_en_;
    input [31:0] addr, outside_pc;
    output reg [31:0] instruction;
    reg [7:0] mem [0:127]; //最大读取32条指令
     initial begin
        #1; // 延迟是为了让outside_pc的值能够安全地进来
        $readmemb("my_rom_data.coe", mem, outside_pc);
          instruction = 0;
     end
    always @( addr or read_en_)
        if (read_en_) begin
          instruction[31:24] = mem[addr];
          instruction[23:16] = mem[addr+1];
          instruction[15:8] = mem[addr+2];
          instruction[7:0] = mem[addr+3];
        end
endmodule

IR.v 模块,IR指令寄存器模块

`timescale 1ns / 1ps
// IR指令寄存器,目的是使指令代码保持稳定
module IR(i_data, clk, IRWre, o_data);
  input clk, IRWre;
  input [31:0] i_data;
  output reg[31:0] o_data;
  always @(negedge clk) begin // 存在延迟的问题,所以用下降沿触发,对数据传输没有什么影响
    if (IRWre) begin
        o_data = i_data;
     end
  end
endmodule

PCJump.v 模块,用于计算j,jal指令的跳转位置的模块

`timescale 1ns / 1ps
// 计算PC直接跳转位置模块
module PCJump(PC0, i_addr, o_addr);
  input [31:0] PC0;
  input [25:0] i_addr;
  output reg[31:0] o_addr;
  reg [27:0] mid;
  always @(i_addr) begin
     mid = i_addr << 2;
     o_addr <= {PC0[31:28], mid[27:0]};
  end
endmodule

DataSelect_5_Bit.v 模块,这是用于选择寄存器输入端地址的

`timescale 1ns / 1ps
// 数据选择器 (五位数据选择)
module DataSelect_5_Bit(A, B, C, Ctrl, S);
  input [4:0] A, B, C; // 三个数据
  input [1:0]Ctrl; //控制信号
  output reg[4:0] S;
  always @(Ctrl or A or B or C) begin
    case(Ctrl)
       2'b00: S = A;
        2'b01: S = B;
        2'b10: S = C;
        default: S = 0; // 给零号寄存器赋值不会有影响,因为寄存器模块不会修改零号寄存器的值
     endcase
  end
endmodule

RegFile.v 模块,寄存器组模块

`timescale 1ns / 1ps
// 寄存器组
module RegFile (rs, rt, rd, i_data, we, clk, o_data_1, o_data_2);
  input [4:0] rs, rt, rd;
  input [31:0] i_data;
  input we, clk;
  output [31:0] o_data_1, o_data_2;
  reg [31:0] register [0:31];
  initial begin
    register[0] = 0; // 只需要确定零号寄存器的值就好,$0恒等于0
  end
  assign o_data_1 = register[rs];
  assign o_data_2 = register[rt];
  always @(i_data or rd) begin // 这里有更改,不用CLK上升沿信号触发,因为与CtrlUnit共享CLK会发生延迟问题,导致下一个时钟上升沿才做相应动作
    if ((rd != 0) && (we == 1)) begin // rd != 0 是确保零号寄存器不会改变的作用
      register[rd] = i_data;
    end
  end
endmodule

DataReg.v 模块,用于上面介绍的ADR、BDR、ALUout、ALUM2DR这些数据延迟小模块

`timescale 1ns / 1ps
// 切分数据通路模块
module DataReg(i_data, clk, o_data);
  input clk;
  input [31:0] i_data;
  output reg[31:0] o_data;
  always @(posedge clk) begin
    o_data = i_data;
  end
endmodule

SignExtend.v模块,数据扩展作用

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

两个类似的数据选择器,所用地方请看数据通路图

`timescale 1ns / 1ps
module DataSelect_2To1_32Bit(A, B, Ctrl, S);
  input [31:0] A, B; //两个数据
  input Ctrl; //控制信号
  output [31:0] S;
  assign S = (Ctrl == 1'b0 ? A : B);
endmodule

`timescale 1ns / 1ps
module DataSelect_4To1_32Bit(A, B, C, D, Ctrl, S);
  input [31:0] A, B, C, D; // 四个数据
  input [1:0]Ctrl; //控制信号
  output reg[31:0] S;
  always @(Ctrl or A or B or C or D) begin
    case(Ctrl)
       2'b00: S = A;
        2'b01: S = B;
        2'b10: S = C;
        2'b11: S = D;
        default: S = 0; // 给零号寄存器赋值不会有影响,因为寄存器模块不会修改零号寄存器的值
     endcase
  end
endmodule

ALU.v 模块,算术逻辑单元

`timescale 1ns / 1ps
// ALU模块 ,算术逻辑单元
module ALU(A, B, ALUOp, zero, result);
  input [31:0] A, B;
  input [2:0] ALUOp;
  output zero;
  output reg [31:0] result;
  initial begin
        result = 0;
  end
  assign zero = (result? 0 : 1);
  always @(A or B or ALUOp) begin
    case(ALUOp)
      3'b000: result = A + B; 3'b001: result = A - B;
      3'b010: result = (A < B ? 1 : 0); 3'b011: result = A >> B;
      3'b100: result = A << B; 3'b101: result = A | B;
      3'b110: result = A & B; 3'b111: result = (~A & B) | (A & ~B);
      default: result = 0;
    endcase
  end
endmodule

RAM.v模块,注意这次使用对大端模式

`timescale 1ns / 1ps
// 内存模块
module RAM (i_data, addr, rw, o_data);
    input [31:0] i_data;
    input [31:0] addr;
    input rw;
    output reg [31:0] o_data;
    reg [7:0] memory [0:63]; // 最多16个数据,因为好像模拟器上限好像是64个寄存器
     initial begin
        o_data = 0;
     end
    always @(addr or i_data or rw) begin // 使用大端方式储存,这里有更改(不需要乘4),请注意
      if (rw) begin // 1 为 写
        memory[addr] = i_data[31:24];
          memory[addr+1] = i_data[23:16];
          memory[addr+2] = i_data[15:8];
          memory[addr+3] = i_data[7:0];
      end else begin // 0 为 读
        o_data[31:24] = memory[addr];
          o_data[23:16] = memory[addr+1];
          o_data[15:8] = memory[addr+2];
          o_data[7:0] = memory[addr+3];
      end
    end
endmodule 

PCAddImm.v 模块,是用来计算PC加立即数的

`timescale 1ns / 1ps
// PC 加立即数模块
module PCAddImm(now_pc, addNum, o_pc);
  input [31:0] now_pc, addNum;
  output [31:0] o_pc;
  assign o_pc = now_pc + (addNum << 2);
endmodule

测试代码区

test.v 模块,要添加什么观察的变量可以在main模块添加,或者在模拟器增加观察变量,这里就不赘述了,因为这是和单周期CPU类似的代码

`timescale 1ns / 1ps
module test;

    // Inputs
    reg CLK;
    reg RST;
    reg [31:0] outside_pc;

    // Outputs
    wire [31:0] ins, now_pc;

    // Instantiate the Unit Under Test (UUT)
    Main uut (
        .CLK(CLK), 
        .RST(RST), 
        .outside_pc(outside_pc), 
        .ins(ins),
        .now_pc(now_pc)
    );

    initial begin
        // Initialize Inputs
      CLK = 0;
      RST = 1;
      outside_pc = 4; // 这里设置外部pc
      #50; // 刚开始设置pc为0
          CLK = !CLK;
      #50;
          RST = 0;
      forever #50 begin // 产生时钟信号
          CLK = !CLK;
      end
    end

endmodule

我测试的代码,可以自己写,仅供参考

// 注意,这里为了测试outside_pc的功能所以初始地址为4

// j 3 这里直接跳到第三条指令也就是addi $1, $0, 4
111000 00000000000000000000000011
// jr  $31
111001 11111 000000000000000000000
//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
//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 
// sll  $1, $2, 2  ===> 这个时候 $1 = $2 = $3 = 1000, 得到结果是 $1 = 100000
011000 00010 00000 00001 00010 000000
// slt  $6, $1, $2  ===> 因为 $1 > $2 所以 $6 = 0
100111 00001 00010 00110 00000000000
// slt  $7, $2, $1  ===> 因为 $1 > $2 所以 $7 = 1
100111 00010 00001 00111 00000000000
//jal 2
111010 00000000000000000000000010
//lw $4, 0($2)
110001 00010 00100 0000000000000000
//halt
111111 00000000000000000000000000

二进制文件(my_rom_data.coe)

11100000
00000000
00000000
00000011
11100111
11100000
00000000
00000000
00001000
00000001
00000000
00000100
00001000
00000010
00000000
00001000
11000000
01000010
00000000
00000000
00000000
01000001
00011000
00000000
00000100
01100001
00011000
00000000
11010000
01000011
11111111
11111110
01001000
00100001
00000000
00000001
01000000
01000001
00011000
00000000
10000000
01000000
00011000
00000000
01000100
01100010
00001000
00000000
01100000
01000000
00001000
10000000
10011100
00100010
00110000
00000000
10011100
01000001
00111000
00000000
11101000
00000000
00000000
00000010
11000100
01000100
00000000
00000000
11111100
00000000
00000000
00000000

最后的一点体会

感觉和单周期CPU设计的思想是差不多的,最复杂的是控制模块,实现了这个模块基本上复杂的都完成了,继续加油!

你可能感兴趣的:(cpu,设计)