blob: 4a50c1e4dcaf427d93049ee6de5c977872c516bd [file] [log] [blame]
`include "vscale_ctrl_constants.vh"
`include "vscale_alu_ops.vh"
`include "rv32_opcodes.vh"
`include "vscale_csr_addr_map.vh"
`include "vscale_md_constants.vh"
module vscale_ctrl(
input clk,
input reset,
input [`INST_WIDTH-1:0] inst_DX,
input imem_wait,
input imem_badmem_e,
input dmem_wait,
input dmem_badmem_e,
input cmp_true,
input [`PRV_WIDTH-1:0] prv,
output reg [`PC_SRC_SEL_WIDTH-1:0] PC_src_sel,
output reg [`IMM_TYPE_WIDTH-1:0] imm_type,
output bypass_rs1,
output bypass_rs2,
output reg [`SRC_A_SEL_WIDTH-1:0] src_a_sel,
output reg [`SRC_B_SEL_WIDTH-1:0] src_b_sel,
output reg [`ALU_OP_WIDTH-1:0] alu_op,
output wire dmem_en,
output wire dmem_wen,
output wire [2:0] dmem_size,
output wire [`MEM_TYPE_WIDTH-1:0] dmem_type,
output md_req_valid,
input md_req_ready,
output reg md_req_in_1_signed,
output reg md_req_in_2_signed,
output reg [`MD_OP_WIDTH-1:0] md_req_op,
output reg [`MD_OUT_SEL_WIDTH-1:0] md_req_out_sel,
input md_resp_valid,
output wire eret,
output [`CSR_CMD_WIDTH-1:0] csr_cmd,
output reg csr_imm_sel,
input illegal_csr_access,
input interrupt_pending,
input interrupt_taken,
output wire wr_reg_WB,
output reg [`REG_ADDR_WIDTH-1:0] reg_to_wr_WB,
output reg [`WB_SRC_SEL_WIDTH-1:0] wb_src_sel_WB,
output wire stall_IF,
output wire kill_IF,
output wire stall_DX,
output wire kill_DX,
output wire stall_WB,
output wire kill_WB,
output wire exception_WB,
output wire [`ECODE_WIDTH-1:0] exception_code_WB,
output wire retire_WB
);
// IF stage ctrl pipeline registers
reg replay_IF;
// IF stage ctrl signals
wire ex_IF;
// DX stage ctrl pipeline registers
reg had_ex_DX;
reg prev_killed_DX;
// DX stage ctrl signals
wire [6:0] opcode = inst_DX[6:0];
wire [6:0] funct7 = inst_DX[31:25];
wire [11:0] funct12 = inst_DX[31:20];
wire [2:0] funct3 = inst_DX[14:12];
wire [`REG_ADDR_WIDTH-1:0] rs1_addr = inst_DX[19:15];
wire [`REG_ADDR_WIDTH-1:0] rs2_addr = inst_DX[24:20];
wire [`REG_ADDR_WIDTH-1:0] reg_to_wr_DX = inst_DX[11:7];
reg illegal_instruction;
reg ebreak;
reg ecall;
reg eret_unkilled;
reg fence_i;
wire [`ALU_OP_WIDTH-1:0] add_or_sub;
wire [`ALU_OP_WIDTH-1:0] srl_or_sra;
reg [`ALU_OP_WIDTH-1:0] alu_op_arith;
reg branch_taken_unkilled;
wire branch_taken;
reg dmem_en_unkilled;
reg dmem_wen_unkilled;
reg jal_unkilled;
wire jal;
reg jalr_unkilled;
wire jalr;
wire redirect;
reg wr_reg_unkilled_DX;
wire wr_reg_DX;
reg [`WB_SRC_SEL_WIDTH-1:0] wb_src_sel_DX;
wire new_ex_DX;
wire ex_DX;
reg [`ECODE_WIDTH-1:0] ex_code_DX;
wire killed_DX;
reg uses_md_unkilled;
wire uses_md;
reg wfi_unkilled_DX;
wire wfi_DX;
reg [`CSR_CMD_WIDTH-1:0] csr_cmd_unkilled;
// WB stage ctrl pipeline registers
reg wr_reg_unkilled_WB;
reg had_ex_WB;
reg [`ECODE_WIDTH-1:0] prev_ex_code_WB;
reg store_in_WB;
reg dmem_en_WB;
reg prev_killed_WB;
reg uses_md_WB;
reg wfi_unkilled_WB;
// WB stage ctrl signals
wire ex_WB;
reg [`ECODE_WIDTH-1:0] ex_code_WB;
wire dmem_access_exception;
wire exception = ex_WB;
wire killed_WB;
wire load_in_WB;
wire active_wfi_WB;
// Hazard signals
wire load_use;
reg uses_rs1;
reg uses_rs2;
wire raw_rs1;
wire raw_rs2;
wire raw_on_busy_md;
// IF stage ctrl
always @(posedge clk) begin
if (reset) begin
replay_IF <= 1'b1;
end else begin
replay_IF <= (redirect && imem_wait) || (fence_i && store_in_WB);
end
end
// interrupts kill IF, DX instructions -- WB may commit
assign kill_IF = stall_IF || ex_IF || ex_DX || ex_WB || redirect || replay_IF || interrupt_taken;
assign stall_IF = stall_DX ||
((imem_wait && !redirect) && !(ex_WB || interrupt_taken));
assign ex_IF = imem_badmem_e && !imem_wait && !redirect && !replay_IF;
// DX stage ctrl
always @(posedge clk) begin
if (reset) begin
had_ex_DX <= 0;
prev_killed_DX <= 0;
end else if (!stall_DX) begin
had_ex_DX <= ex_IF;
prev_killed_DX <= kill_IF;
end
end
// interrupts kill IF, DX instructions -- WB may commit
// Exceptions never show up falsely due to hazards -- don't get exceptions on stall
assign kill_DX = stall_DX || ex_DX || ex_WB || interrupt_taken;
assign stall_DX = stall_WB ||
(( // internal hazards
load_use ||
raw_on_busy_md ||
(fence_i && store_in_WB) ||
(uses_md_unkilled && !md_req_ready)
) && !(ex_DX || ex_WB || interrupt_taken));
assign new_ex_DX = ebreak || ecall || illegal_instruction || illegal_csr_access;
assign ex_DX = had_ex_DX || new_ex_DX; // TODO: add causes
assign killed_DX = prev_killed_DX || kill_DX;
always @(*) begin
ex_code_DX = `ECODE_INST_ADDR_MISALIGNED;
if (had_ex_DX) begin
ex_code_DX = `ECODE_INST_ADDR_MISALIGNED;
end else if (illegal_instruction) begin
ex_code_DX = `ECODE_ILLEGAL_INST;
end else if (illegal_csr_access) begin
ex_code_DX = `ECODE_ILLEGAL_INST;
end else if (ebreak) begin
ex_code_DX = `ECODE_BREAKPOINT;
end else if (ecall) begin
ex_code_DX = `ECODE_ECALL_FROM_U + prv;
end
end // always @ begin
/*
Note: the convention is to use an initial default
assignment for all control signals (except for
illegal instructions) and override the default
values when appropriate, rather than using the
default keyword. The exception is for illegal
instructions; in the interest of brevity, this
signal is set in the default case of any case
statement after initially being zero.
*/
assign dmem_size = {1'b0,funct3[1:0]};
assign dmem_type = funct3;
always @(*) begin
illegal_instruction = 1'b0;
csr_cmd_unkilled = `CSR_IDLE;
csr_imm_sel = funct3[2];
ecall = 1'b0;
ebreak = 1'b0;
eret_unkilled = 1'b0;
fence_i = 1'b0;
branch_taken_unkilled = 1'b0;
jal_unkilled = 1'b0;
jalr_unkilled = 1'b0;
uses_rs1 = 1'b1;
uses_rs2 = 1'b0;
imm_type = `IMM_I;
src_a_sel = `SRC_A_RS1;
src_b_sel = `SRC_B_IMM;
alu_op = `ALU_OP_ADD;
dmem_en_unkilled = 1'b0;
dmem_wen_unkilled = 1'b0;
wr_reg_unkilled_DX = 1'b0;
wb_src_sel_DX = `WB_SRC_ALU;
uses_md_unkilled = 1'b0;
wfi_unkilled_DX = 1'b0;
case (opcode)
`RV32_LOAD : begin
dmem_en_unkilled = 1'b1;
wr_reg_unkilled_DX = 1'b1;
wb_src_sel_DX = `WB_SRC_MEM;
end
`RV32_STORE : begin
uses_rs2 = 1'b1;
imm_type = `IMM_S;
dmem_en_unkilled = 1'b1;
dmem_wen_unkilled = 1'b1;
end
`RV32_BRANCH : begin
uses_rs2 = 1'b1;
branch_taken_unkilled = cmp_true;
src_b_sel = `SRC_B_RS2;
case (funct3)
`RV32_FUNCT3_BEQ : alu_op = `ALU_OP_SEQ;
`RV32_FUNCT3_BNE : alu_op = `ALU_OP_SNE;
`RV32_FUNCT3_BLT : alu_op = `ALU_OP_SLT;
`RV32_FUNCT3_BLTU : alu_op = `ALU_OP_SLTU;
`RV32_FUNCT3_BGE : alu_op = `ALU_OP_SGE;
`RV32_FUNCT3_BGEU : alu_op = `ALU_OP_SGEU;
default : illegal_instruction = 1'b1;
endcase // case (funct3)
end
`RV32_JAL : begin
jal_unkilled = 1'b1;
uses_rs1 = 1'b0;
src_a_sel = `SRC_A_PC;
src_b_sel = `SRC_B_FOUR;
wr_reg_unkilled_DX = 1'b1;
end
`RV32_JALR : begin
illegal_instruction = (funct3 != 0);
jalr_unkilled = 1'b1;
src_a_sel = `SRC_A_PC;
src_b_sel = `SRC_B_FOUR;
wr_reg_unkilled_DX = 1'b1;
end
`RV32_MISC_MEM : begin
case (funct3)
`RV32_FUNCT3_FENCE : begin
if ((inst_DX[31:28] == 0) && (rs1_addr == 0) && (reg_to_wr_DX == 0))
; // most fences are no-ops
else
illegal_instruction = 1'b1;
end
`RV32_FUNCT3_FENCE_I : begin
if ((inst_DX[31:20] == 0) && (rs1_addr == 0) && (reg_to_wr_DX == 0))
fence_i = 1'b1;
else
illegal_instruction = 1'b1;
end
default : illegal_instruction = 1'b1;
endcase // case (funct3)
end
`RV32_OP_IMM : begin
alu_op = alu_op_arith;
wr_reg_unkilled_DX = 1'b1;
end
`RV32_OP : begin
uses_rs2 = 1'b1;
src_b_sel = `SRC_B_RS2;
alu_op = alu_op_arith;
wr_reg_unkilled_DX = 1'b1;
if (funct7 == `RV32_FUNCT7_MUL_DIV) begin
uses_md_unkilled = 1'b1;
wb_src_sel_DX = `WB_SRC_MD;
end
end
`RV32_SYSTEM : begin
wb_src_sel_DX = `WB_SRC_CSR;
wr_reg_unkilled_DX = (funct3 != `RV32_FUNCT3_PRIV);
case (funct3)
`RV32_FUNCT3_PRIV : begin
if ((rs1_addr == 0) && (reg_to_wr_DX == 0)) begin
case (funct12)
`RV32_FUNCT12_ECALL : ecall = 1'b1;
`RV32_FUNCT12_EBREAK : ebreak = 1'b1;
`RV32_FUNCT12_ERET : begin
if (prv == 0)
illegal_instruction = 1'b1;
else
eret_unkilled = 1'b1;
end
`RV32_FUNCT12_WFI : wfi_unkilled_DX = 1'b1;
default : illegal_instruction = 1'b1;
endcase // case (funct12)
end // if ((rs1_addr == 0) && (reg_to_wr_DX == 0))
end // case: `RV32_FUNCT3_PRIV
`RV32_FUNCT3_CSRRW : csr_cmd_unkilled = (rs1_addr == 0) ? `CSR_READ : `CSR_WRITE;
`RV32_FUNCT3_CSRRS : csr_cmd_unkilled = (rs1_addr == 0) ? `CSR_READ : `CSR_SET;
`RV32_FUNCT3_CSRRC : csr_cmd_unkilled = (rs1_addr == 0) ? `CSR_READ : `CSR_CLEAR;
`RV32_FUNCT3_CSRRWI : csr_cmd_unkilled = (rs1_addr == 0) ? `CSR_READ : `CSR_WRITE;
`RV32_FUNCT3_CSRRSI : csr_cmd_unkilled = (rs1_addr == 0) ? `CSR_READ : `CSR_SET;
`RV32_FUNCT3_CSRRCI : csr_cmd_unkilled = (rs1_addr == 0) ? `CSR_READ : `CSR_CLEAR;
default : illegal_instruction = 1'b1;
endcase // case (funct3)
end
`RV32_AUIPC : begin
uses_rs1 = 1'b0;
src_a_sel = `SRC_A_PC;
imm_type = `IMM_U;
wr_reg_unkilled_DX = 1'b1;
end
`RV32_LUI : begin
uses_rs1 = 1'b0;
src_a_sel = `SRC_A_ZERO;
imm_type = `IMM_U;
wr_reg_unkilled_DX = 1'b1;
end
default : begin
illegal_instruction = 1'b1;
end
endcase // case (opcode)
end // always @ (*)
assign add_or_sub = ((opcode == `RV32_OP) && (funct7[5])) ? `ALU_OP_SUB : `ALU_OP_ADD;
assign srl_or_sra = (funct7[5]) ? `ALU_OP_SRA : `ALU_OP_SRL;
assign md_req_valid = uses_md;
always @(*) begin
md_req_op = `MD_OP_MUL;
md_req_in_1_signed = 0;
md_req_in_2_signed = 0;
md_req_out_sel = `MD_OUT_LO;
case (funct3)
`RV32_FUNCT3_MUL : begin
end
`RV32_FUNCT3_MULH : begin
md_req_in_1_signed = 1;
md_req_in_2_signed = 1;
md_req_out_sel = `MD_OUT_HI;
end
`RV32_FUNCT3_MULHSU : begin
md_req_in_1_signed = 1;
md_req_out_sel = `MD_OUT_HI;
end
`RV32_FUNCT3_MULHU : begin
md_req_out_sel = `MD_OUT_HI;
end
`RV32_FUNCT3_DIV : begin
md_req_op = `MD_OP_DIV;
md_req_in_1_signed = 1;
md_req_in_2_signed = 1;
end
`RV32_FUNCT3_DIVU : begin
md_req_op = `MD_OP_DIV;
end
`RV32_FUNCT3_REM : begin
md_req_op = `MD_OP_REM;
md_req_in_1_signed = 1;
md_req_in_2_signed = 1;
md_req_out_sel = `MD_OUT_REM;
end
`RV32_FUNCT3_REMU : begin
md_req_op = `MD_OP_REM;
md_req_out_sel = `MD_OUT_REM;
end
endcase
end
always @(*) begin
case (funct3)
`RV32_FUNCT3_ADD_SUB : alu_op_arith = add_or_sub;
`RV32_FUNCT3_SLL : alu_op_arith = `ALU_OP_SLL;
`RV32_FUNCT3_SLT : alu_op_arith = `ALU_OP_SLT;
`RV32_FUNCT3_SLTU : alu_op_arith = `ALU_OP_SLTU;
`RV32_FUNCT3_XOR : alu_op_arith = `ALU_OP_XOR;
`RV32_FUNCT3_SRA_SRL : alu_op_arith = srl_or_sra;
`RV32_FUNCT3_OR : alu_op_arith = `ALU_OP_OR;
`RV32_FUNCT3_AND : alu_op_arith = `ALU_OP_AND;
default : alu_op_arith = `ALU_OP_ADD;
endcase // case (funct3)
end // always @ begin
assign branch_taken = branch_taken_unkilled && !kill_DX;
assign jal = jal_unkilled && !kill_DX;
assign jalr = jalr_unkilled && !kill_DX;
assign eret = eret_unkilled && !kill_DX;
assign dmem_en = dmem_en_unkilled && !kill_DX;
assign dmem_wen = dmem_wen_unkilled && !kill_DX;
assign wr_reg_DX = wr_reg_unkilled_DX && !kill_DX;
assign uses_md = uses_md_unkilled && !kill_DX;
assign wfi_DX = wfi_unkilled_DX && !kill_DX;
assign csr_cmd = (kill_DX) ? `CSR_IDLE : csr_cmd_unkilled;
assign redirect = branch_taken || jal || jalr || eret;
always @(*) begin
if (exception || interrupt_taken) begin
PC_src_sel = `PC_HANDLER;
end else if (replay_IF || (stall_IF && !imem_wait)) begin
PC_src_sel = `PC_REPLAY;
end else if (eret) begin
PC_src_sel = `PC_EPC;
end else if (branch_taken) begin
PC_src_sel = `PC_BRANCH_TARGET;
end else if (jal) begin
PC_src_sel = `PC_JAL_TARGET;
end else if (jalr) begin
PC_src_sel = `PC_JALR_TARGET;
end else begin
PC_src_sel = `PC_PLUS_FOUR;
end
end // always @ begin
// WB stage ctrl
always @(posedge clk) begin
if (reset) begin
prev_killed_WB <= 0;
had_ex_WB <= 0;
wr_reg_unkilled_WB <= 0;
store_in_WB <= 0;
dmem_en_WB <= 0;
uses_md_WB <= 0;
wfi_unkilled_WB <= 0;
end else if (!stall_WB) begin
prev_killed_WB <= killed_DX;
had_ex_WB <= ex_DX;
wr_reg_unkilled_WB <= wr_reg_DX;
wb_src_sel_WB <= wb_src_sel_DX;
prev_ex_code_WB <= ex_code_DX;
reg_to_wr_WB <= reg_to_wr_DX;
store_in_WB <= dmem_wen;
dmem_en_WB <= dmem_en;
uses_md_WB <= uses_md;
wfi_unkilled_WB <= wfi_DX;
end
end
// WFI handling
// can't be killed while in WB stage
assign active_wfi_WB = !prev_killed_WB && wfi_unkilled_WB
&& !(interrupt_taken || interrupt_pending);
assign kill_WB = stall_WB || ex_WB;
assign stall_WB = ((dmem_wait && dmem_en_WB) || (uses_md_WB && !md_resp_valid) || active_wfi_WB) && !exception;
assign dmem_access_exception = dmem_badmem_e;
assign ex_WB = had_ex_WB || dmem_access_exception;
assign killed_WB = prev_killed_WB || kill_WB;
always @(*) begin
ex_code_WB = prev_ex_code_WB;
if (!had_ex_WB) begin
if (dmem_access_exception) begin
ex_code_WB = wr_reg_unkilled_WB ?
`ECODE_LOAD_ADDR_MISALIGNED :
`ECODE_STORE_AMO_ADDR_MISALIGNED;
end
end
end
assign exception_WB = ex_WB;
assign exception_code_WB = ex_code_WB;
assign wr_reg_WB = wr_reg_unkilled_WB && !kill_WB;
assign retire_WB = !(kill_WB || killed_WB);
// Hazard logic
assign load_in_WB = dmem_en_WB && !store_in_WB;
assign raw_rs1 = wr_reg_WB && (rs1_addr == reg_to_wr_WB)
&& (rs1_addr != 0) && uses_rs1;
assign bypass_rs1 = !load_in_WB && raw_rs1;
assign raw_rs2 = wr_reg_WB && (rs2_addr == reg_to_wr_WB)
&& (rs2_addr != 0) && uses_rs2;
assign bypass_rs2 = !load_in_WB && raw_rs2;
assign raw_on_busy_md = uses_md_WB && (raw_rs1 || raw_rs2) && !md_resp_valid;
assign load_use = load_in_WB && (raw_rs1 || raw_rs2);
endmodule // vscale_ctrl