| `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 |