| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| `include "prim_assert.sv" |
| |
| /** |
| * OTBN Controller |
| */ |
| module otbn_controller |
| import otbn_pkg::*; |
| #( |
| // Size of the instruction memory, in bytes |
| parameter int ImemSizeByte = 4096, |
| // Size of the data memory, in bytes |
| parameter int DmemSizeByte = 4096, |
| |
| localparam int ImemAddrWidth = prim_util_pkg::vbits(ImemSizeByte), |
| localparam int DmemAddrWidth = prim_util_pkg::vbits(DmemSizeByte) |
| ) ( |
| input logic clk_i, |
| input logic rst_ni, |
| |
| input logic start_i, // start the processing at start_addr_i |
| output logic done_o, // processing done, signaled by ECALL |
| input logic [ImemAddrWidth-1:0] start_addr_i, |
| |
| // Next instruction selection (to instruction fetch) |
| output logic insn_fetch_req_valid_o, |
| output logic [ImemAddrWidth-1:0] insn_fetch_req_addr_o, |
| |
| // Fetched/decoded instruction |
| input logic insn_valid_i, |
| input logic [ImemAddrWidth-1:0] insn_addr_i, |
| |
| // Decoded instruction data, matching the "Decoding" section of the specification. |
| input insn_dec_base_t insn_dec_base_i, |
| input insn_dec_bignum_t insn_dec_bignum_i, |
| input insn_dec_shared_t insn_dec_shared_i, |
| |
| // Base register file |
| output logic [4:0] rf_base_wr_addr_o, |
| output logic rf_base_wr_en_o, |
| output logic [31:0] rf_base_wr_data_o, |
| |
| output logic [4:0] rf_base_rd_addr_a_o, |
| input logic [31:0] rf_base_rd_data_a_i, |
| |
| output logic [4:0] rf_base_rd_addr_b_o, |
| input logic [31:0] rf_base_rd_data_b_i, |
| |
| // Bignum register file (WDRs) |
| output logic [4:0] rf_bignum_wr_addr_o, |
| output logic [1:0] rf_bignum_wr_en_o, |
| output logic [WLEN-1:0] rf_bignum_wr_data_o, |
| |
| output logic [4:0] rf_bignum_rd_addr_a_o, |
| input logic [WLEN-1:0] rf_bignum_rd_data_a_i, |
| |
| output logic [4:0] rf_bignum_rd_addr_b_o, |
| input logic [WLEN-1:0] rf_bignum_rd_data_b_i, |
| |
| // Execution units |
| |
| // Base ALU |
| output alu_base_operation_t alu_base_operation_o, |
| output alu_base_comparison_t alu_base_comparison_o, |
| input logic [31:0] alu_base_operation_result_i, |
| input logic alu_base_comparison_result_i, |
| |
| // Bignum ALU |
| output alu_bignum_operation_t alu_bignum_operation_o, |
| input logic [WLEN-1:0] alu_bignum_operation_result_i, |
| |
| // LSU |
| output logic lsu_load_req_o, |
| output logic lsu_store_req_o, |
| output insn_subset_e lsu_req_subset_o, |
| output logic [DmemAddrWidth-1:0] lsu_addr_o, |
| |
| output logic [31:0] lsu_base_wdata_o, |
| output logic [WLEN-1:0] lsu_bignum_wdata_o, |
| |
| input logic [31:0] lsu_base_rdata_i, |
| input logic [WLEN-1:0] lsu_bignum_rdata_i, |
| input logic [1:0] lsu_rdata_err_i, // Bit1: Uncorrectable, Bit0: Correctable |
| |
| // Internal Special-Purpose Registers (ISPRs) |
| output ispr_e ispr_addr_o, |
| output logic [31:0] ispr_base_wdata_o, |
| output logic [BaseWordsPerWLEN-1:0] ispr_base_wr_en_o, |
| output logic [WLEN-1:0] ispr_bignum_wdata_o, |
| output logic ispr_bignum_wr_en_o, |
| input logic [WLEN-1:0] ispr_rdata_i |
| ); |
| |
| typedef enum logic [1:0] { |
| OtbnStateHalt, |
| OtbnStateRun, |
| OtbnStateStall |
| } otbn_state_e; |
| |
| otbn_state_e state_q, state_d; |
| |
| logic stall; |
| logic mem_stall; |
| logic branch_taken; |
| logic [ImemAddrWidth-1:0] branch_target; |
| logic [ImemAddrWidth-1:0] next_insn_addr; |
| |
| csr_e csr_addr; |
| logic [31:0] csr_rdata; |
| logic [BaseWordsPerWLEN-1:0] csr_rdata_mux [32]; |
| logic [31:0] csr_wdata; |
| |
| wsr_e wsr_addr; |
| logic [WLEN-1:0] wsr_wdata; |
| |
| ispr_e ispr_addr_base; |
| logic [$clog2(BaseWordsPerWLEN)-1:0] ispr_word_addr_base; |
| logic [BaseWordsPerWLEN-1:0] ispr_word_sel_base; |
| |
| ispr_e ispr_addr_bignum; |
| |
| logic ispr_wr_insn; |
| |
| // Stall a cycle on loads to allow load data writeback to happen the following cycle. Stall not |
| // required on stores as there is no response to deal with. |
| // TODO: Possibility of error response on store? Probably still don't need to stall in that case |
| // just ensure incoming store error stops anything else happening. |
| assign mem_stall = lsu_load_req_o; |
| |
| assign stall = mem_stall; |
| assign done_o = insn_valid_i && insn_dec_shared_i.ecall_insn; |
| |
| // Branch taken when there is a valid branch instruction and comparison passes or a valid jump |
| // instruction (which is always taken) |
| assign branch_taken = insn_valid_i & ((insn_dec_shared_i.branch_insn & alu_base_comparison_result_i) | |
| insn_dec_shared_i.jump_insn); |
| // Branch target computed by base ALU (PC + imm) |
| // TODO: Implement error on branch out of range |
| assign branch_target = alu_base_operation_result_i[ImemAddrWidth-1:0]; |
| |
| assign next_insn_addr = insn_addr_i + 'd4; |
| |
| always_comb begin |
| state_d = state_q; |
| insn_fetch_req_valid_o = 1'b0; |
| insn_fetch_req_addr_o = start_addr_i; |
| |
| // TODO: Harden state machine |
| // TODO: Jumps/branches |
| unique case (state_q) |
| OtbnStateHalt: begin |
| if (start_i) begin |
| state_d = OtbnStateRun; |
| insn_fetch_req_addr_o = start_addr_i; |
| insn_fetch_req_valid_o = 1'b1; |
| end |
| end |
| OtbnStateRun: begin |
| insn_fetch_req_valid_o = 1'b1; |
| |
| if (done_o) begin |
| state_d = OtbnStateHalt; |
| insn_fetch_req_valid_o = 1'b0; |
| end else begin |
| // When stalling refetch the same instruction to keep decode inputs constant |
| if (stall) begin |
| state_d = OtbnStateStall; |
| insn_fetch_req_addr_o = insn_addr_i; |
| end else begin |
| if (branch_taken) begin |
| insn_fetch_req_addr_o = branch_target; |
| end else begin |
| insn_fetch_req_addr_o = next_insn_addr; |
| end |
| end |
| end |
| end |
| OtbnStateStall: begin |
| // Only ever stall for a single cycle |
| // TODO: Any more than one cycle stall cases? |
| insn_fetch_req_valid_o = 1'b1; |
| insn_fetch_req_addr_o = next_insn_addr; |
| state_d = OtbnStateRun; |
| end |
| default: ; |
| endcase |
| end |
| |
| `ASSERT(ControllerStateValid, state_q inside {OtbnStateHalt, OtbnStateRun, OtbnStateStall}); |
| // Branch only takes effect in OtbnStateRun so must not go into stall state for branch |
| // instructions. |
| `ASSERT(NoStallOnBranch, insn_valid_i & insn_dec_shared_i.branch_insn |-> state_q != OtbnStateStall); |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| state_q <= OtbnStateHalt; |
| end else begin |
| state_q <= state_d; |
| end |
| end |
| |
| assign rf_base_rd_addr_a_o = insn_dec_base_i.a; |
| assign rf_base_rd_addr_b_o = insn_dec_base_i.b; |
| |
| // Base ALU Operand A MUX |
| always_comb begin |
| unique case (insn_dec_base_i.op_a_sel) |
| OpASelRegister: |
| alu_base_operation_o.operand_a = rf_base_rd_data_a_i; |
| OpASelZero: |
| alu_base_operation_o.operand_a = '0; |
| OpASelCurrPc: |
| alu_base_operation_o.operand_a = {{(32 - ImemAddrWidth){1'b0}}, insn_addr_i}; |
| default: |
| alu_base_operation_o.operand_a = rf_base_rd_data_a_i; |
| endcase |
| end |
| |
| // Base ALU Operand B MUX |
| always_comb begin |
| unique case (insn_dec_base_i.op_b_sel) |
| OpBSelRegister: |
| alu_base_operation_o.operand_b = rf_base_rd_data_b_i; |
| OpBSelImmediate: |
| alu_base_operation_o.operand_b = insn_dec_base_i.i; |
| default: |
| alu_base_operation_o.operand_b = rf_base_rd_data_b_i; |
| endcase |
| end |
| |
| assign alu_base_operation_o.op = insn_dec_base_i.alu_op; |
| |
| assign alu_base_comparison_o.operand_a = rf_base_rd_data_a_i; |
| assign alu_base_comparison_o.operand_b = rf_base_rd_data_b_i; |
| assign alu_base_comparison_o.op = insn_dec_base_i.comparison_op; |
| |
| // Register file write MUX |
| // Suppress write for loads when controller isn't in stall state as load data for writeback is |
| // only available in the stall state. |
| assign rf_base_wr_en_o = insn_dec_base_i.rf_we & |
| ~(insn_dec_shared_i.ld_insn & (state_q != OtbnStateStall)); |
| |
| assign rf_base_wr_addr_o = insn_dec_base_i.d; |
| |
| always_comb begin |
| unique case (insn_dec_base_i.rf_wdata_sel) |
| RfWdSelEx: |
| rf_base_wr_data_o = alu_base_operation_result_i; |
| RfWdSelLsu: |
| rf_base_wr_data_o = lsu_base_rdata_i; |
| RfWdSelNextPc: |
| rf_base_wr_data_o = {{(32-ImemAddrWidth){1'b0}}, next_insn_addr}; |
| RfWdSelIspr: |
| rf_base_wr_data_o = csr_rdata; |
| default: |
| rf_base_wr_data_o = alu_base_operation_result_i; |
| endcase |
| end |
| |
| assign rf_bignum_rd_addr_a_o = insn_dec_bignum_i.a; |
| assign rf_bignum_rd_addr_b_o = insn_dec_bignum_i.b; |
| |
| assign alu_bignum_operation_o.operand_a = rf_bignum_rd_data_a_i; |
| |
| // Base ALU Operand B MUX |
| always_comb begin |
| unique case (insn_dec_bignum_i.op_b_sel) |
| OpBSelRegister: |
| alu_bignum_operation_o.operand_b = rf_bignum_rd_data_b_i; |
| OpBSelImmediate: |
| alu_bignum_operation_o.operand_b = insn_dec_bignum_i.i; |
| default: |
| alu_bignum_operation_o.operand_b = rf_bignum_rd_data_b_i; |
| endcase |
| end |
| |
| assign alu_bignum_operation_o.op = insn_dec_bignum_i.alu_op; |
| assign alu_bignum_operation_o.shift_right = insn_dec_bignum_i.shift_right; |
| assign alu_bignum_operation_o.shift_amt = insn_dec_bignum_i.shift_amt; |
| assign alu_bignum_operation_o.flag_group = insn_dec_bignum_i.flag_group; |
| |
| // Register file write MUX |
| // Suppress write for loads when controller isn't in stall state as load data for writeback is |
| // only available in the stall state. |
| assign rf_bignum_wr_en_o = |
| {2{insn_dec_bignum_i.rf_we & ~(insn_dec_shared_i.ld_insn & (state_q != OtbnStateStall))}}; |
| |
| assign rf_bignum_wr_addr_o = insn_dec_bignum_i.d; |
| |
| always_comb begin |
| unique case (insn_dec_bignum_i.rf_wdata_sel) |
| RfWdSelEx: |
| rf_bignum_wr_data_o = alu_bignum_operation_result_i; |
| RfWdSelLsu: |
| rf_bignum_wr_data_o = lsu_bignum_rdata_i; |
| RfWdSelIspr: |
| rf_bignum_wr_data_o = ispr_rdata_i; |
| default: |
| rf_bignum_wr_data_o = alu_bignum_operation_result_i; |
| endcase |
| end |
| |
| // CSR/WSR/ISPR handling |
| // ISPRs (Internal Special Purpose Registers) are the internal registers. CSRs and WSRs are the |
| // ISA visible versions of those registers in the base and bignum ISAs respectively. |
| |
| assign csr_addr = csr_e'(insn_dec_base_i.i[11:0]); |
| |
| always_comb begin |
| ispr_addr_base = IsprMod; |
| ispr_word_addr_base = '0; |
| |
| unique case (csr_addr) |
| CsrFlags: begin |
| ispr_addr_base = IsprFlags; |
| ispr_word_addr_base = '0; |
| end |
| CsrMod0,CsrMod1,CsrMod2,CsrMod3,CsrMod4,CsrMod5,CsrMod6,CsrMod7: begin |
| ispr_addr_base = IsprMod; |
| ispr_word_addr_base = csr_addr[2:0]; |
| end |
| CsrRnd: begin |
| ispr_addr_base = IsprRnd; |
| ispr_word_addr_base = '0; |
| end |
| // TODO: Illegal addr handling |
| default: ; |
| endcase |
| end |
| |
| for (genvar i_word = 0; i_word < BaseWordsPerWLEN; i_word++) begin : g_ispr_word_sel_base |
| assign ispr_word_sel_base[i_word] = ispr_word_addr_base == i_word; |
| end |
| |
| for (genvar i_bit = 0; i_bit < 32; i_bit++) begin : g_csr_rdata_mux |
| for (genvar i_word = 0; i_word < BaseWordsPerWLEN; i_word++) begin : g_csr_rdata_mux_inner |
| assign csr_rdata_mux[i_bit][i_word] = ispr_rdata_i[i_word*32 + i_bit] & ispr_word_sel_base[i_word]; |
| end |
| |
| assign csr_rdata[i_bit] = |csr_rdata_mux[i_bit]; |
| end |
| |
| assign csr_wdata = insn_dec_shared_i.ispr_rs_insn ? csr_rdata | rf_base_rd_data_a_i : rf_base_rd_data_a_i; |
| |
| assign wsr_addr = wsr_e'(insn_dec_bignum_i.i[WsrNumWidth-1:0]); |
| |
| always_comb begin |
| ispr_addr_bignum = IsprMod; |
| |
| unique case (wsr_addr) |
| WsrMod: ispr_addr_bignum = IsprMod; |
| WsrRnd: ispr_addr_bignum = IsprRnd; |
| WsrAcc: ispr_addr_bignum = IsprAcc; |
| default: ; |
| endcase |
| end |
| |
| assign wsr_wdata = insn_dec_shared_i.ispr_rs_insn ? ispr_rdata_i | rf_bignum_rd_data_a_i : rf_bignum_rd_data_a_i; |
| |
| assign ispr_wr_insn = insn_dec_shared_i.ispr_rw_insn | insn_dec_shared_i.ispr_rs_insn; |
| |
| assign ispr_addr_o = insn_dec_shared_i.subset == InsnSubsetBase ? ispr_addr_base : ispr_addr_bignum; |
| assign ispr_base_wdata_o = csr_wdata; |
| assign ispr_base_wr_en_o = {BaseWordsPerWLEN{(insn_dec_shared_i.subset == InsnSubsetBase) & ispr_wr_insn}} & ispr_word_sel_base; |
| assign ispr_bignum_wdata_o = wsr_wdata; |
| assign ispr_bignum_wr_en_o = (insn_dec_shared_i.subset == InsnSubsetBignum) & ispr_wr_insn; |
| |
| // TODO: Add error on unaligned/out of bounds |
| assign lsu_load_req_o = insn_valid_i & insn_dec_shared_i.ld_insn & (state_q == OtbnStateRun); |
| assign lsu_store_req_o = insn_valid_i & insn_dec_shared_i.st_insn & (state_q == OtbnStateRun); |
| assign lsu_req_subset_o = insn_dec_shared_i.subset; |
| // TODO: Switch between address from base/bignum |
| assign lsu_addr_o = alu_base_operation_result_i[DmemAddrWidth-1:0]; |
| assign lsu_base_wdata_o = rf_base_rd_data_b_i; |
| |
| // TODO: Bignum load/store |
| assign lsu_bignum_wdata_o = '0; |
| endmodule |