计组实验-CPU设计-指令添加
一、 单周期 CPU
概述
1.单周期 CPU
CPU每个时钟周期跑一个指令
2.支持的指令:
(最后一行为按照实验要求自行添加的)
-
add/sub/and/or/slt/sltu/addu/subu
-
addi/ori/lw/sw/beq
-
j/jal
-
sll/nor/lui/slti/bne/andi/srl/sllv/srlv/jr/jalr
3.主要模块:
-
alu.v:计算模块,负责各个指令在 EX 段的算术操作。
-
crtl.v:译码模块,将 MIPS 汇编语言程序形成的机械指令进行译码。
-
crtl_encode.v:编码模块,存放各种编码。
-
dm.v :存储器操作模块,负责进行MEM操作。
-
EXT.v:数字扩展模块,对一些立即数进行符号拓展或零拓展。
-
im.v :指令储存模块,进行取指操作。
-
mux.v:选择器模块,根据指令的不同,来进行各阶段相应数据的选择。
-
NPC.v:指令地址计算模块,负责计算下一条指令的地址。
-
PC.v:程序计数器,负责生成下一条指令的地址。
-
sscomp_tb.v:顶层实体文件
-
sccpu.v:主模块,负责连接各个模块进行统一操作
-
sccomp.v:顶层模块,连接CPU和存储器。
-
RF.v:寄存器堆,负责寄存器的写入和输出。
重要部件
PC(程序计数器)
根据当前具体的指令,决定下一条指令的地址。
接口
信号名 | 方向 | 描述 |
---|---|---|
Clk | Input | 时钟信号 |
Rst | Input | 复位信号 |
NPC | Input | 计算下一条指令地址的模块 |
PC | Output | 输出 |
RF(寄存器文件)
功能描述 读出和写入寄存器
读出:不需要控制
写入:RFWr 为 1
接口:
信号名 | 方向 | 描述 |
---|---|---|
Clk | Input | 时钟信号 |
Rst | Input | 复位信号 |
RFWr | Input | 写回信号 |
A1[4:0] | Input | 寄存器Rs的地址寻址信号 |
A2[4:0] | Input | 寄存器Rt的地址寻址信号 |
A3[4:0] | Input | 寄存器Rd的地址寻址信号 |
WD[32:0] | Input | 写回的数据 |
Reg_sel | Input | 写入信号选择(Rd or Shamt) |
RD1[31:0] | Output | 寄存器输出 |
RD2[31:0] | Output | 寄存器输出 |
Reg_data[31:0] | Output | 根据Reg_sel选择的输出信号 |
module RF( input clk,
input rst,
input RFWr,
input [4:0] A1, A2, A3,
input [31:0] WD,
output [31:0] RD1, RD2,
input [4:0] reg_sel,
output [31:0] reg_data);
reg [31:0] rf[31:0];
if (RFWr) begin
rf[A3] <= WD;
end
assign RD1 = (A1 != 0) ? rf[A1] : 0;
assign RD2 = (A2 != 0) ? rf[A2] : 0;
assign reg_data = (reg_sel != 0) ? rf[reg_sel] : 0;
endmodule
ALU
功能描述:执行 CPU 的各种计算
ALUOp[2:0]:决定执行何种计算
其中 A、B 取决于一些选择信号
mux2 #(32) U_MUX_ALU_A(
.d0(RD1),.d1(shamt),.s(AregSel),.y(A)
);
// mux for ALU B
mux2 #(32) U_MUX_ALU_B (
.d0(writedata), .d1(Imm32), .s(ALUSrc), .y(B)
);
// instantiation of alu
alu U_ALU (
.A(A), .B(B), .ALUOp(ALUOp), .C(aluout), .Zero(Zero)
);
MUX
// mux for register data to write
mux4 #(5) U_MUX4_GPR_A3 (
.d0(rd), .d1(rt), .d2(5'b11111), .d3(5'b0), .s(GPRSel), .y(A3)
);
//选择要写入数据的寄存器,A3在RF中用作RFWr,即写入寄存器的序号
// mux for register address to write
mux4 #(32) U_MUX4_GPR_WD (
.d0(aluout), .d1(readdata), .d2(PC + 4), .d3(32'b0), .s(WDSel), .y(WD)
);
// 这里在选择把哪个数据写回到寄存器中,可能是ALU计算的结果(移位),可能是从存储器中读出的数据(lw),可能是暂存的下一条指令地址(j型),可能什么也没有(不写入)
mux2 #(32) U_MUX_ALU_A(
.d0(RD1),.d1(shamt),.s(AregSel),.y(A)
);
// 在ALU内部起作用,决定是用rd来逻辑移位,还是用shamt来逻辑移位
// mux for ALU B
mux2 #(32) U_MUX_ALU_B (
.d0(writedata), .d1(Imm32), .s(ALUSrc), .y(B)
);
// 寄存器读出的第一个数据肯定会被送进ALU,这里是在选择是把寄存器读出的第二个数据放进ALU,还是放立即数,之所以叫writedata,是因为这个数据同时也会被送到MEM中
添加指令
bne 不相等跳转分支指令 I 型
bne rs, rt, label1 形式为 6 5 5 16(offeset)
func[5:0]:000101
1.crtl.v 模块添加相应wire 信号
2.ALU 模块中添加运算
ALIOP 0010 与 sub 相同,本质上就是相减判断是否为0
`ALU_SUB: C = A - B; // SUB
3.由于 bne 指令要跳转,所以要修改 npc操作码
assign NPCOp[0] = (i_beq & Zero)|(i_bne &~ Zero) | i_jr | i_jalr;
sll & srl & sllv & srlv 移位指令 R型
sll 逻辑左移 R型 funct 000000 sll rd, rt, shamt rd =rt << shamt 655556 形式
srl 逻辑右移 R型 funct 000010 srl rd, rt, shamt rd =rt >> shamt 655556 形式
sllv 逻辑左移变量 R型 funct 000100 srllv rd, rt, rs rd =rt << rs 655556
srlv 逻辑右移变量 R型 funct 000110 srlv rd, rt, rs rd =rt >> rs 65556
1.crtl.v 中需要添加多路选择器,判断选择 rs 还是 shamt
assign AregSel = i_sll | i_srl;
2.sccpu.v 模块绑定信号 AregSel,shamt,Alu_A
对 shamt 信号格式进行定义, 前 27 位为 0,取 6 到 10 位指令
assign shamt={27'b0,instr[10:6]};
3.补充多路选择器 mux2
4.alu.v 模块输入端口进行修改
sllv 和 srlv 在原来的基础上只需要修改译码和操作数
slti 小于立即数则置位 I 型 001010
slti rt, rs, imm 65516
1.指令译码,Crtl.v 添加对应信号
2.寄存器写信号
3.Aluop 操作码与 slt 一致
nor 异或 R型 funct100111
nor rd, rs, rt 0 rs rt rd 0 0x27(6 5 5 5 5 6)
rd = ~(rs|rt)
1.ALU 模块进行扩充
ALU_NOR: C = ~(A | B);
2.Crtl_encode 添加相应的操作码
3.Crtl.v 添加译码
4.Aluop 操作控制码修改
assign ALUOp[3]= i_sll | i_sllv | i_srl | i_srlv | i_nor | i_lui ;
lui 立即数高 16 位放进一个寄存器 I 型 funct001111
lui rt, imm RT={imm,16’b0} 6 5 5 16
需要 16 位立即数符号扩展,再左移 16 位
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5HCU4wc9-1647494648255)(https://raw.githubusercontent.com/Liqu1d-G/Cloud_img/master/image-20211208182354123.png)]
1.Ctrl.v 添加指令译码信号
2.寄存器写信号
3.写回 rt 寄存器
4.零扩展
5.Alu_op 操作码修改
6.ALU 运算取数据信号修改,取一个立即数
andi 立即数与 I 型 op:001100
andi rt rs imm (6 5 5 16)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BW5lxR8W-1647494648255)(https://raw.githubusercontent.com/Liqu1d-G/Cloud_img/master/image-20211208184647663.png)]
1.crtl.v 添加译码
2.零扩展信号
3.添加寄存器写信号
4.Alu 需要进行选择,添加 Alu_op
5.Alu 端口需要进行选择,修改 AluSrc
output ALUSrc; // ALU source for A
assign ALUSrc = i_lw | i_sw | i_addi | i_ori | i_slti | i_lui | i_andi;
// ALU B is from instruction immediate
jr 寄存器跳转 R型 001000
jr rs(无条件跳转到由寄存器 rs 指定的指令) 0 rs 0 8(6 5 15 6)
pc=GPR[rs]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WYYiM4yH-1647494648255)(https://raw.githubusercontent.com/Liqu1d-G/Cloud_img/master/image-20211208184730702.png)]
1.Crtl.v 添加指令译码信号
2.NPCOp 修改
assign NPCOp[1] = i_j | i_jal | i_jr | i_jalr;
3.Crtl_encode 修改
4.NPC 模块修改
`NPC_JR: NPC = PCJR;
jalr 跳转并链接到寄存器 R型 001001
jalr rs rd 0 rs 0 rd 0 9(6 5 5 5 5 6)
pc=GPR[rs]; GPR[rd]=pc+4
1.Crtl.v 添加指令译码信号
2.添加寄存器写信号
3.选择需要写的来自 PC 数据__
4.对 NPC 选择
二、 多周期 CPU
在单周期基础上的修改
1、 在单周期 CPU 模块设计上进行改进,采用类似的思路。各个模块之间相互连接,实现将一条指令的五个阶段顺序执行的过程。
2、 模块设计以功能部件为基础进行构造,进而构造顶层实体
3、 模块组成: alu.v,crtl.v,crtl_encode_def.v,dm.v,EXT.v,flopenr.v,flopr.v,mc comp.v,mmcpu.v,mux.v,RF.v
4、 多周期 CPU 特征模块介绍
Crtl部分的状态转移
与单周期控制模块不同,多周期 cpu 控制模块增加了指令的执行的状态,对于指令的五个阶段分别进行相应的操作。根据指令的特征选择指令具有的阶段。
对于状态转移的控制
reg [2:0] nextstate;
reg [2:0] state;
always @(posedge clk or posedge rst) begin
if ( rst )
state <= sif;
else
state <= nextstate;
end // end always
parameter [2:0] sif = 3'b000, // IF state
sid = 3'b001, // ID state
sexe = 3'b010, // EXE state
smem = 3'b011, // MEM state
swb = 3'b100; // WB state
/*************************************************/
/****** Control Signal ******/
always @(*) begin
RegWrite = 0;
MemWrite = 0;
PCWrite = 0;
IRWrite = 0;
EXTOp = 1; // signed extension
ALUSrcA = 1; // 1 - ReadData1
ALUSrcB = 2'b00; // 0 - ReadData2
ALUOp = 4'b0001; // ALU_ADD 3'b001
GPRSel = 2'b00; // GPRSel_RD 2'b00
WDSel = 2'b00; // WDSel_FromALU 2'b00
PCSource = 3'b000; // PC + 4 (ALU)
IorD = 0; // 0-memory access for instruction
AregSel = 0;
case (state)
sif: begin
PCWrite = 1;
IRWrite = 1;
ALUSrcA = 0; // PC
ALUSrcB = 2'b01; // 4
nextstate = sid;
end
sid: begin
if (i_j) begin
PCSource = 3'b010; // JUMP address
PCWrite = 1;
nextstate = sif;
end else if (i_jal) begin
PCSource = 3'b010; // JUMP address
PCWrite = 1;
RegWrite = 1;
WDSel = 2'b10; // WDSel_FromPC 2'b10
GPRSel = 2'b10; // GPRSel_31 2'b10
nextstate = sif;
end else if (i_jr) begin
PCSource = 3'b100;
PCWrite = 1;
nextstate = sif;
end else if (i_jalr) begin
PCSource = 3'b100;
PCWrite = 1;
RegWrite = 1;
WDSel = 2'b10; // WDSel_FromPC 2'b10
GPRSel = 2'b10; // GPRSel_31 2'b10
nextstate = sif;
end else begin
ALUSrcA = 0; // PC
ALUSrcB = 2'b11; // branch offset
nextstate = sexe;
end
end
// ALU_NOP 4'b0000
// ALU_ADD 4'b0001
// ALU_SUB 4'b0010
// ALU_AND 4'b0011
// ALU_OR 4'b0100
// ALU_SLT 4'b0101
// ALU_SLTU 4'b0110
// ALU_SLL 4'b0111
// ALU_SRL 4'b1000
// ALU_NOR 4'b1001
// ALU_LUI 4'b1010
sexe: begin
ALUOp[0] = i_add | i_lw | i_sw | i_addi | i_and | i_slt | i_addu | i_sll | i_sllv | i_slti | i_nor | i_andi;
ALUOp[1] = i_sub | i_beq | i_and | i_sltu | i_subu | i_bne | i_sll | i_sllv | i_lui | i_andi;
ALUOp[2] = i_or | i_ori | i_slt | i_sltu | i_sll | i_sllv | i_slti;
ALUOp[3] = i_srl | i_srlv | i_nor | i_lui;
if (i_beq) begin
PCSource = 3'b001; // ALUout, branch address
PCWrite = i_beq & Zero;
nextstate = sif;
end else if(i_bne) begin
PCSource = 3'b001;
PCWrite = i_bne & ~Zero;
nextstate =sif;
end else if (i_lw || i_sw) begin
ALUSrcB = 2'b10; // select offset
nextstate = smem;
end else begin
if (i_addi || i_ori || i_slti || i_lui || i_andi)
ALUSrcB = 2'b10; // select immediate
if (i_ori || i_andi)
EXTOp = 0; // zero extension
if (i_sll | i_srl)
AregSel = 1;
nextstate = swb;
end
end
smem: begin
IorD = 1; // memory address = ALUout
if (i_lw) begin
nextstate = swb;
end else begin // i_sw
MemWrite = 1;
nextstate = sif;
end
end
swb: begin
if (i_lw)
WDSel = 2'b01; // WDSel_FromMEM 2'b01
if (i_lw | i_addi | i_ori | i_slti | i_lui | i_andi) begin
GPRSel = 2'b01; // GPRSel_RT 2'b01
end
RegWrite = 1;
nextstate = sif;
end
default: begin
nextstate = sif;
end
endcase
end // end always