三个 D 触发器用于保存当前状态,是时序逻辑电路,RST用于初始化状态“000“,另外两个部分都是组合逻辑电路,一个用于产生 下一个阶段的状态,另一个用于产生每个阶段的控制信号。从图上可看出,下个状态取决于 指令操作码和当前状态;而每个阶段的控制信号取决于指令操作码、当前状态和反映运算结果的状态 zero 标志和符号 sign标志。
always @(posedge CLK or negedge RST) begin
if (!RST) State <= `STATE_IF;
else begin
case (State)
`STATE_IF: State <= `STATE_ID;
`STATE_ID: begin
case (OpCode)
`OP_ADD, `OP_SUB, `OP_ADDIU, `OP_AND, `OP_ANDI, `OP_ORI,
`OP_XORI, `OP_SLL, `OP_SLTI, `OP_SLT: State <= `STATE_EXE_AL;
`OP_BNE, `OP_BEQ, `OP_BLTZ: State <= `STATE_EXE_BR;
`OP_SW, `OP_LW: State <= `STATE_EXE_LS;
`OP_J, `OP_JAL, `OP_JR, `OP_HALT: State <= `STATE_IF;
default: State <= `STATE_EXE_AL;
endcase
end
`STATE_EXE_AL: State <= `STATE_WB_AL;
`STATE_EXE_BR: State <= `STATE_IF;
`STATE_EXE_LS: State <= `STATE_MEM;
`STATE_WB_AL: State <= `STATE_IF;
`STATE_MEM: begin
case (OpCode)
`OP_SW: State <= `STATE_IF;
`OP_LW: State <= `STATE_WB_LD;
endcase
end
`STATE_WB_LD: State <= `STATE_IF;
default: State <= `STATE_IF;
endcase
end
end
PCWre:ID 阶段 J、JAL、JR,或 EXE 阶段 BEQ、BNE、BLTZ,或 MEM 阶段 SW,或 WB 阶段。另外,为保证在每条指令最初阶段的时钟上升沿 PC 发生改变,需要在上一条指令的最后一个下降沿将 PCWre 设置为 1,这样才能保证 PC 在每条指令最开始的时钟上升沿改变。
always @(negedge CLK) begin
case (State)
`STATE_ID: begin
if (OpCode == `OP_J || OpCode == `OP_JAL || OpCode == `OP_JR) PCWre <= 1;
end
`STATE_EXE_AL, `STATE_EXE_BR, `STATE_EXE_LS: begin
if (OpCode == `OP_BEQ || OpCode == `OP_BNE || OpCode == `OP_BLTZ) PCWre <= 1;
end
`STATE_MEM: begin
if (OpCode == `OP_SW) PCWre <= 1;
end
`STATE_WB_AL, `STATE_WB_LD: PCWre <= 1;
default: PCWre <= 0;
endcase
end
逻辑算术运算单元
该模块是一个32位的ALU单元,会根据控制信号对输入的操作数进行不同的运算,例如加、减、与、或等。
module ALU(
input [2:0] ALUOp,
input [31:0] A,
input [31:0] B,
output Sign,
output Zero,
output reg [31:0] Result
);
always @(*) begin
case (ALUOp)
`ALU_OP_ADD: Result = (A + B);
`ALU_OP_SUB: Result = (A - B);
`ALU_OP_SLL: Result = (B << A);
`ALU_OP_OR: Result = (A | B);
`ALU_OP_AND: Result = (A & B);
`ALU_OP_LT: Result = (A < B) ? 1 : 0;
`ALU_OP_SLT: Result = (((A < B) && (A[31] == B[31])) || ((A[31] && !B[31]))) ? 1 : 0;
`ALU_OP_XOR: Result = (A ^ B);
endcase
$display("[ALU] calculated result [%h] from a = [%h] aluOpCode = [%b] b = [%h]", Result, A, ALUOp, B);
end
assign Zero = (Result == 0) ? 1 : 0;
assign Sign = Result[31];
endmodule
PC的下一条指令可能是当前 PC+4,也可能是跳转指令地址,还有可能因为停机而不变。 因此还需要设计一个选择器来选择下一条指令地址的计算方式,为此创建了 JumpPCHelper用于计算 j 指令的下一条 PC 地址,和 NextPCHelper 用于根据指令选择不同的计算方式。
module PC(
input CLK,
input RST,
input PCWre,
input [31:0] PCAddr,
output reg [31:0] NextPCAddr
);
initial NextPCAddr = 0;
always @(posedge CLK or negedge RST) begin
if (!RST) NextPCAddr <= 0;
else if (PCWre || !PCAddr) NextPCAddr <= PCAddr;
end
endmodule
module JumpPCHelper(
input [31:0] PC,
input [25:0] NextPCAddr,
output reg [31:0] JumpPC);
wire [27:0] tmp;
assign tmp = NextPCAddr << 2; // address * 4
always @(*) begin
JumpPC[31:28] = PC[31:28];
JumpPC[27:2] = tmp[27:2];
JumpPC[1:0] = 0;
end
endmodule
module NextPCHelper(
input RST,
input [1:0] PCSrc,
input [31:0] PC,
input [31:0] Immediate,
input [31:0] RegPC,
input [31:0] JumpPC,
output reg [31:0] NextPC);
always @(RST or PCSrc or PC or Immediate or RegPC or JumpPC) begin
if (!RST) NextPC = PC + 4;
else begin
case (PCSrc)
`PC_NEXT: NextPC = PC + 4;
`PC_REL_JUMP: NextPC = PC + 4 + (Immediate << 2);
`PC_REG_JUMP: NextPC = RegPC;
`PC_ABS_JUMP: NextPC = JumpPC;
default: NextPC = PC + 4;
endcase
end
end
endmodule
选择器
数据选择,用于数据存储单元之后的选择,这里需要二选一和三选一数据选择器。
module Selector1In2#(
parameter WIDTH = 5
)(
input Sel,
input [WIDTH-1:0] A,
input [WIDTH-1:0] B,
output [WIDTH-1:0] Y);
assign Y = Sel ? B : A;
endmodule
module Selector1In3#(
parameter WIDTH = 5
)(
input [1:0] Sel,
input [WIDTH-1:0] A,
input [WIDTH-1:0] B,
input [WIDTH-1:0] C,
output reg [WIDTH-1:0] Y);
always @(Sel or A or B or C) begin
case (Sel)
2'b00: Y <= A;
2'b01: Y <= B;
2'b10: Y <= C;
default: Y <= 0;
endcase
end
endmodule
指令寄存器
用时钟信号 CLK 驱动,采用边缘触发写入指令二进制码。
module IR(
input CLK,
input IRWre,
input [31:0] DataIn,
output reg [31:0] DataOut
);
always @(posedge CLK) begin
if (IRWre) begin
DataOut <= DataIn;
end
end
endmodule