blob: d962c2e2d9251f2b9864d19003e6daab1280391d [file] [log] [blame]
// 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