注:单周期CPU设计请移步我的另一篇博文:
单周期CPU设计(Verilog)
一、 实验目的
(1) 认识和掌握多周期数据通路原理及其设计方法;
(2) 掌握多周期CPU的实现方法,代码实现方法;
(3) 编写一个编译器,将MIPS汇编程序编译为二进制机器码;
(4) 掌握多周期CPU的测试方法。
二、 实验内容
设计一个多周期CPU,该CPU至少能实现以下指令功能操作。需设计的指令与格式如下:
(说明:操作码按照以下规定使用,都给每类指令预留扩展空间,后续实验相同。)
三、 实验原理
多周期CPU指的是将整个CPU的执行过程分成几个阶段,每个阶段用一个时钟去完成,然后开始下一条指令的执行,而每种指令执行时所用的时钟数不尽相同,这就是所谓的多周期CPU。CPU在处理指令时,一般需要经过以下几个阶段:
(1) 取指令(IF):根据程序计数器pc中的指令地址,从存储器中取出一条指令,同时,pc根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入pc,当然得到的“地址”需要做些变换才送入pc。
(2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
(3) 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
(4) 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
(5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
实验中就按照这五个阶段进行设计,这样一条指令的执行最长需要五个(小)时钟周期才能完成,但具体情况怎样?要根据该条指令的情况而定,有些指令不需要五个时钟周期的,这就是多周期的CPU。
特别提示,图上增加IR指令寄存器,目的是使指令代码保持稳定,pc写使能控制信号PCWre,是确保pc适时修改,原因都是和多周期工作的CPU有关。ADR、BDR、ALUoutDR、DBDR四个寄存器不需要写使能信号,其作用是切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延迟变为多个分段小延迟。
相关部件及引脚说明:
- Instruction Memory:指令存储器
- Iaddr,指令地址输入端口
- DataIn,存储器数据输入端口
- DataOut,存储器数据输出端口
- 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: 指令寄存器,用于存放正在执行的指令代码
- ALU: 算术逻辑单元
- result,ALU运算结果
- zero,运算结果标志,结果为0输出1,否则输出0
四、实验设备
PC机一台,BASYS 3 实验板一块,Xilinx Vivado 开发软件一套。
五、实验分析与设计
六、实验心得
七、代码
代码放的次序有点乱,实现模块的顺序可以参见我的实验过程设计,也就是第五部分!
// 顶层模块的实现
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; // 31号寄存器
// 数据通路
wire [31:0] pc, pc0, pc4, i_IR, instruction, pcChoose3, pcChoose1, extendData, ALUResult, WriteData, ReadData1, ReadData2, DataOut;
wire [31:0] o_ADR, o_BDR, o_ALUout, i_ALUM2DR,i_ALUData1,i_ALUData2;
wire zero;
// 控制信号
wire [2:0] ALUOp;
wire [1:0] ExtSel, RegDst, PCSrc;
wire PCWre, IRWre, InsMemRW, WrRegData, RegWre, ALUSrcB, DataMemRW, DBDataSrc;
// 数据选择输出
wire [4:0] fiveChooseData;
wire [31:0] InputWriteData1;
// 引脚输出
assign ins = instruction;
assign now_pc = pc0;
PC PC(CLK, pc, PCWre, RST, outside_pc, pc0); // 添加了外部pc
PCAddFour PCAddFour(pc0, pc4);
InstructionMEM InstructionMEM(pc0, InsMemRW, i_IR); // 添加了外部pc
IR IR(i_IR, CLK, IRWre, instruction);
PCJump PCJump(pc0, instruction[25:0], pcChoose3);
DataSelector_3to1 DataSelector_3to1(endReg, instruction[20:16], instruction[15:11], RegDst, fiveChooseData);
RegFile RegFile(instruction[25:21], instruction[20:16], fiveChooseData, WriteData, RegWre, CLK, ReadData1, ReadData2);
ADR ADR(ReadData1, CLK, o_ADR);
BDR BDR(ReadData2, CLK, o_BDR);
SignExtend SignExtend(instruction[15:0], ExtSel, extendData);
DataSelector_2to1_sa DataSelector_2to1_1(o_ADR, instruction[10:6] , ALUSrcA, i_ALUData1);
DataSelector_2to1 DataSelector_2to1_2(o_BDR, extendData, ALUSrcB, i_ALUData2);
ALU ALU(i_ALUData1, i_ALUData2, ALUOp, zero, ALUResult);
ALUoutDR ALUoutDR(ALUResult, CLK, o_ALUout);
DataMEM DataMEM(o_BDR, o_ALUout, DataMemRW, DataOut);
DataSelector_2to1 DataSelector_2to1_3(ALUResult, DataOut, DBDataSrc, i_ALUM2DR);
DBDR DBDR(i_ALUM2DR, CLK, InputWriteData1);
DataSelector_2to1 DataSelector_2to1_4(pc4, InputWriteData1, WrRegData, WriteData);
PCAddImm PCAddImm(pc4, extendData, pcChoose1);
DataSelector_4to1 DataSelector_4to1(pc4, pcChoose1, ReadData1, pcChoose3, PCSrc, pc);
ControlUnit ControlUnit(instruction[31:26], CLK, RST, zero, PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcA, ALUSrcB, DataMemRW, DBDataSrc, ExtSel, RegDst, PCSrc, ALUOp);
endmodule
// PC模块的实现
// @param clk 时钟信号
// @param pcWre信号
// @param reset信号
// @param i_pc 输入的pc值
// @param o_pc 输出的pc值
// @param outside_pc ???
module PC(clk, i_pc, pcWre, reset, outside_pc, o_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改变的时候再检测
// 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
// 实现PC递增
// @param i_pc 输入的pc值
// @param o_pc 输出的pc值
module PCAddFour(i_pc, o_pc);
input wire [31:0] i_pc;
output wire [31:0] o_pc;