blob: f97960b2161b00e5cb41c21b392646ffbe0f0c46 [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 or error occurring
output err_bits_t err_bits_o, // valid when done_o is asserted
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,
// Error from fetch requested last cycle
input logic insn_fetch_err_i,
// Fetched/decoded instruction
input logic insn_valid_i,
input logic insn_illegal_i,
input logic [ImemAddrWidth-1:0] insn_addr_i,
// Decoded instruction data
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 rf_base_wr_commit_o,
output logic [31:0] rf_base_wr_data_no_intg_o,
output logic [BaseIntgWidth-1:0] rf_base_wr_data_intg_o,
output logic rf_base_wr_data_intg_sel_o,
output logic [4:0] rf_base_rd_addr_a_o,
output logic rf_base_rd_en_a_o,
input logic [BaseIntgWidth-1:0] rf_base_rd_data_a_intg_i,
output logic [4:0] rf_base_rd_addr_b_o,
output logic rf_base_rd_en_b_o,
input logic [BaseIntgWidth-1:0] rf_base_rd_data_b_intg_i,
output logic rf_base_rd_commit_o,
input logic rf_base_call_stack_err_i,
input logic rf_base_rd_data_err_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_no_intg_o,
output logic [ExtWLEN-1:0] rf_bignum_wr_data_intg_o,
output logic rf_bignum_wr_data_intg_sel_o,
output logic [4:0] rf_bignum_rd_addr_a_o,
output logic rf_bignum_rd_en_a_o,
input logic [ExtWLEN-1:0] rf_bignum_rd_data_a_intg_i,
output logic [4:0] rf_bignum_rd_addr_b_o,
output logic rf_bignum_rd_en_b_o,
input logic [ExtWLEN-1:0] rf_bignum_rd_data_b_intg_i,
input logic rf_bignum_rd_data_err_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,
input logic alu_bignum_selection_flag_i,
// Bignum MAC
output mac_bignum_operation_t mac_bignum_operation_o,
input logic [WLEN-1:0] mac_bignum_operation_result_i,
output logic mac_bignum_en_o,
// 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 [BaseIntgWidth-1:0] lsu_base_wdata_o,
output logic [ExtWLEN-1:0] lsu_bignum_wdata_o,
input logic [BaseIntgWidth-1:0] lsu_base_rdata_i,
input logic [ExtWLEN-1:0] lsu_bignum_rdata_i,
input logic lsu_rdata_err_i,
// 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,
output logic rnd_req_o,
output logic rnd_prefetch_req_o,
input logic rnd_valid_i,
input logic state_reset_i,
output logic [31:0] insn_cnt_o,
input logic illegal_bus_access_i,
input logic lifecycle_escalation_i
);
otbn_state_e state_q, state_d, state_raw;
logic err;
logic done_complete;
logic insn_fetch_req_valid_raw;
logic stall;
logic ispr_stall;
logic mem_stall;
logic jump_or_branch;
logic branch_taken;
logic insn_executing;
logic [ImemAddrWidth-1:0] branch_target;
logic branch_target_overflow;
logic [ImemAddrWidth:0] next_insn_addr_wide;
logic [ImemAddrWidth-1:0] next_insn_addr;
csr_e csr_addr;
logic [$clog2(BaseWordsPerWLEN)-1:0] csr_sub_addr;
logic [31:0] csr_rdata_raw;
logic [31:0] csr_rdata;
logic [BaseWordsPerWLEN-1:0] csr_rdata_mux [32];
logic [31:0] csr_wdata_raw;
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, ispr_rd_insn;
logic ispr_wr_base_insn;
logic ispr_wr_bignum_insn;
logic lsu_load_req_raw;
logic lsu_store_req_raw;
// Register read data with integrity stripped off
logic [31:0] rf_base_rd_data_a_no_intg;
logic [31:0] rf_base_rd_data_b_no_intg;
logic [WLEN-1:0] rf_bignum_rd_data_a_no_intg;
logic [WLEN-1:0] rf_bignum_rd_data_b_no_intg;
logic [ExtWLEN-1:0] selection_result;
// Computed increments for indirect register index and memory address in BN.LID/BN.SID/BN.MOVR
// instructions.
logic [5:0] rf_base_rd_data_a_inc;
logic [5:0] rf_base_rd_data_b_inc;
logic [26:0] rf_base_rd_data_a_wlen_word_inc;
// Output of mux taking the above increments as inputs and choosing one to write back to base
// register file with appropriate zero extension and padding to give a 32-bit result.
logic [31:0] increment_out;
// Loop control, used to start a new loop
logic loop_start_req;
logic loop_start_commit;
logic [11:0] loop_bodysize;
logic [31:0] loop_iterations;
// Loop generated jumps. The loop controller asks to jump when execution reaches the end of a loop
// body that hasn't completed all of its iterations.
logic loop_jump;
logic [ImemAddrWidth-1:0] loop_jump_addr;
logic [WLEN-1:0] mac_bignum_rf_wr_data;
logic csr_illegal_addr, wsr_illegal_addr, ispr_illegal_addr;
logic imem_addr_err, loop_err, ispr_err;
logic dmem_addr_err, dmem_addr_unaligned_base, dmem_addr_unaligned_bignum, dmem_addr_overflow;
logic rf_a_indirect_err, rf_b_indirect_err, rf_d_indirect_err, rf_indirect_err;
logic insn_cnt_en;
logic [31:0] insn_cnt_d, insn_cnt_q;
logic [4:0] ld_insn_bignum_wr_addr_q;
// 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_raw;
// Reads to RND must stall until data is available
assign ispr_stall = rnd_req_o & ~rnd_valid_i;
assign stall = mem_stall | ispr_stall;
// OTBN is done (raising the 'done' interrupt) either when it executes an ecall or an error
// occurs. The ecall triggered done is factored out as `done_complete` to avoid logic loops in the
// error handling logic.
assign done_complete = (insn_valid_i && insn_dec_shared_i.ecall_insn);
assign done_o = done_complete | err;
assign jump_or_branch = (insn_valid_i &
(insn_dec_shared_i.branch_insn | insn_dec_shared_i.jump_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)
assign branch_target = alu_base_operation_result_i[ImemAddrWidth-1:0];
assign branch_target_overflow = |alu_base_operation_result_i[31:ImemAddrWidth];
assign next_insn_addr_wide = {1'b0, insn_addr_i} + 'd4;
assign next_insn_addr = next_insn_addr_wide[ImemAddrWidth-1:0];
always_comb begin
// `state_raw` and `insn_fetch_req_valid_raw` are the values of `state_d` and
// `insn_fetch_req_valid_o` before any errors are considered.
state_raw = state_q;
insn_fetch_req_valid_raw = 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_raw = OtbnStateRun;
insn_fetch_req_addr_o = start_addr_i;
insn_fetch_req_valid_raw = 1'b1;
end
end
OtbnStateRun: begin
insn_fetch_req_valid_raw = 1'b1;
if (done_complete) begin
state_raw = OtbnStateHalt;
insn_fetch_req_valid_raw = 1'b0;
end else begin
// When stalling refetch the same instruction to keep decode inputs constant
if (stall) begin
state_raw = 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 if (loop_jump) begin
insn_fetch_req_addr_o = loop_jump_addr;
end else begin
insn_fetch_req_addr_o = next_insn_addr;
end
end
end
end
OtbnStateStall: begin
insn_fetch_req_valid_raw = 1'b1;
// When stalling refetch the same instruction to keep decode inputs constant
if (stall) begin
state_raw = OtbnStateStall;
insn_fetch_req_addr_o = insn_addr_i;
end else begin
if (loop_jump) begin
insn_fetch_req_addr_o = loop_jump_addr;
end else begin
insn_fetch_req_addr_o = next_insn_addr;
end
state_raw = OtbnStateRun;
end
end
default: ;
endcase
end
// Anything that moves us or keeps us in the stall state should cause `stall` to be asserted
`ASSERT(StallIfNextStateStall, insn_valid_i & (state_d == OtbnStateStall) |-> stall)
// On any error immediately halt and suppress any Imem request.
assign state_d = err ? OtbnStateHalt : state_raw;
assign insn_fetch_req_valid_o = err ? 1'b0 : insn_fetch_req_valid_raw;
// Determine if there are any errors related to the Imem fetch address.
always_comb begin
imem_addr_err = 1'b0;
if (insn_fetch_req_valid_raw) begin
if (|insn_fetch_req_addr_o[1:0]) begin
// Imem address is unaligned
imem_addr_err = 1'b1;
end else if (branch_taken) begin
imem_addr_err = branch_target_overflow;
end else begin
imem_addr_err = next_insn_addr_wide[ImemAddrWidth];
end
end
end
assign err_bits_o.fatal_lifecycle_escalation = lifecycle_escalation_i;
assign err_bits_o.fatal_illegal_bus_access = illegal_bus_access_i;
assign err_bits_o.fatal_reg = rf_base_rd_data_err_i | rf_bignum_rd_data_err_i;
assign err_bits_o.fatal_imem = insn_fetch_err_i;
assign err_bits_o.fatal_dmem = lsu_rdata_err_i;
assign err_bits_o.illegal_insn = insn_illegal_i | ispr_err | rf_indirect_err;
assign err_bits_o.bad_data_addr = dmem_addr_err;
assign err_bits_o.loop = loop_err;
assign err_bits_o.call_stack = rf_base_call_stack_err_i;
assign err_bits_o.bad_insn_addr = imem_addr_err;
assign err = |err_bits_o;
// Instructions must not execute if there is an error
assign insn_executing = insn_valid_i & ~err;
`ASSERT(ErrBitSetOnErr, err |-> |err_bits_o)
`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 insn_cnt_d = state_reset_i ? 32'd0 : (insn_cnt_q + 32'd1);
assign insn_cnt_en = (insn_executing & ~stall & (insn_cnt_q != 32'hffffffff)) | state_reset_i;
assign insn_cnt_o = insn_cnt_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
insn_cnt_q <= 32'd0;
end else if (insn_cnt_en) begin
insn_cnt_q <= insn_cnt_d;
end
end
otbn_loop_controller #(
.ImemAddrWidth(ImemAddrWidth)
) u_otbn_loop_controller (
.clk_i,
.rst_ni,
.state_reset_i,
.insn_valid_i,
.insn_addr_i,
.next_insn_addr_i (next_insn_addr),
.loop_start_req_i (loop_start_req),
.loop_start_commit_i (loop_start_commit),
.loop_bodysize_i (loop_bodysize),
.loop_iterations_i (loop_iterations),
.loop_jump_o (loop_jump),
.loop_jump_addr_o (loop_jump_addr),
.loop_err_o (loop_err),
.jump_or_branch_i (jump_or_branch),
.otbn_stall_i (stall)
);
// loop_start_req indicates the instruction wishes to start a loop, loop_start_commit confirms it
// should occur.
assign loop_start_req = insn_valid_i & insn_dec_shared_i.loop_insn;
assign loop_start_commit = insn_executing;
assign loop_bodysize = insn_dec_base_i.loop_bodysize;
assign loop_iterations = insn_dec_base_i.loop_immediate ? insn_dec_base_i.i :
rf_base_rd_data_a_no_intg;
// Compute increments which can be optionally applied to indirect register accesses and memory
// addresses in BN.LID/BN.SID/BN.MOVR instructions.
assign rf_base_rd_data_a_inc = rf_base_rd_data_a_no_intg[4:0] + 1'b1;
assign rf_base_rd_data_b_inc = rf_base_rd_data_b_no_intg[4:0] + 1'b1;
// We can avoid a full 32-bit adder here because the offset is 32-bit aligned, so we know the
// load/store address will only be valid if rf_base_rd_data_a_no_intg[4:0] is zero.
assign rf_base_rd_data_a_wlen_word_inc = rf_base_rd_data_a_no_intg[31:5] + 27'h1;
// Choose increment to write back to base register file, only one increment can be written as
// there is only one write port. Note that where an instruction is incrementing the indirect
// reference to its destination register (insn_dec_bignum_i.d_inc) that reference is read on the
// B read port so the B increment is written back.
always_comb begin
unique case (1'b1)
insn_dec_bignum_i.a_inc: begin
increment_out = {26'b0, rf_base_rd_data_a_inc};
end
insn_dec_bignum_i.b_inc: begin
increment_out = {26'b0, rf_base_rd_data_b_inc};
end
insn_dec_bignum_i.d_inc: begin
increment_out = {26'b0, rf_base_rd_data_b_inc};
end
insn_dec_bignum_i.a_wlen_word_inc: begin
increment_out = {rf_base_rd_data_a_wlen_word_inc, 5'b0};
end
default: begin
// Whenever increment_out is written back to the register file, exactly one of the
// increment selector signals is high. To prevent the automatic inference of latches in
// case nothing is written back (rf_wdata_sel != RfWdSelIncr) and to save logic, we choose
// a valid output as default.
increment_out = {26'b0, rf_base_rd_data_a_inc};
end
endcase
end
// Base RF read/write address, enable and commit control
always_comb begin
rf_base_rd_addr_a_o = insn_dec_base_i.a;
rf_base_rd_addr_b_o = insn_dec_base_i.b;
rf_base_wr_addr_o = insn_dec_base_i.d;
// Only commit read or write if the instruction is executing (in particular a read commit pops
// the call stack so must not occur where a valid instruction sees an error and doesn't
// execute).
rf_base_rd_commit_o = insn_executing;
rf_base_wr_commit_o = insn_executing;
rf_base_rd_en_a_o = 1'b0;
rf_base_rd_en_b_o = 1'b0;
rf_base_wr_en_o = 1'b0;
if (insn_valid_i) begin
if (insn_dec_shared_i.st_insn) begin
// For stores, both base reads happen in the same cycle as the request because they give the
// address and data, which make up the request.
rf_base_rd_en_a_o = insn_dec_base_i.rf_ren_a & lsu_store_req_raw;
rf_base_rd_en_b_o = insn_dec_base_i.rf_ren_b & lsu_store_req_raw;
// Bignum stores can update the base register file where an increment is used.
rf_base_wr_en_o = (insn_dec_shared_i.subset == InsnSubsetBignum) &
insn_dec_base_i.rf_we &
lsu_store_req_raw;
end else if (insn_dec_shared_i.ld_insn) begin
// For loads, both base reads happen in the same cycle as the request. The address is
// required for the request and the indirect destination register (only used for Bignum
// loads) is flopped in ld_insn_bignum_wr_addr_q to correctly deal with the case where it's
// updated by an increment.
rf_base_rd_en_a_o = insn_dec_base_i.rf_ren_a & lsu_load_req_raw;
rf_base_rd_en_b_o = insn_dec_base_i.rf_ren_b & lsu_load_req_raw;
if (insn_dec_shared_i.subset == InsnSubsetBignum) begin
// Bignum loads can update the base register file where an increment is used. This must
// always happen in the same cycle as the request as this is where both registers are
// read.
rf_base_wr_en_o = insn_dec_base_i.rf_we & lsu_load_req_raw;
end else begin
// For Base loads write the base register file when the instruction is unstalled (meaning
// the load data is available).
rf_base_wr_en_o = insn_dec_base_i.rf_we & ~stall;
end
end else begin
// For all other instructions the read and write happen when the instruction is unstalled.
rf_base_rd_en_a_o = insn_dec_base_i.rf_ren_a & ~stall;
rf_base_rd_en_b_o = insn_dec_base_i.rf_ren_b & ~stall;
rf_base_wr_en_o = insn_dec_base_i.rf_we & ~stall;
end
end
if (insn_dec_shared_i.subset == InsnSubsetBignum) begin
unique case (1'b1)
insn_dec_bignum_i.a_inc,
insn_dec_bignum_i.a_wlen_word_inc: begin
rf_base_wr_addr_o = insn_dec_base_i.a;
end
insn_dec_bignum_i.b_inc,
insn_dec_bignum_i.d_inc: begin
rf_base_wr_addr_o = insn_dec_base_i.b;
end
default: ;
endcase
end
end
// 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_no_intg;
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_no_intg;
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_no_intg;
OpBSelImmediate: alu_base_operation_o.operand_b = insn_dec_base_i.i;
default: alu_base_operation_o.operand_b = rf_base_rd_data_b_no_intg;
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_no_intg;
assign alu_base_comparison_o.operand_b = rf_base_rd_data_b_no_intg;
assign alu_base_comparison_o.op = insn_dec_base_i.comparison_op;
assign rf_base_rd_data_a_no_intg = rf_base_rd_data_a_intg_i[31:0];
assign rf_base_rd_data_b_no_intg = rf_base_rd_data_b_intg_i[31:0];
// TODO: For now integrity bits from RF base are ignored in the controller, remove this when end
// to end integrity features that use them are implemented
logic unused_rf_base_rd_a_intg_bits;
logic unused_rf_base_rd_b_intg_bits;
assign unused_rf_base_rd_a_intg_bits = |rf_base_rd_data_a_intg_i[38:32];
assign unused_rf_base_rd_b_intg_bits = |rf_base_rd_data_b_intg_i[38:32];
// Register file write MUX
always_comb begin
// Write data mux for anything that needs integrity computing during register write
unique case (insn_dec_base_i.rf_wdata_sel)
RfWdSelEx: rf_base_wr_data_no_intg_o = alu_base_operation_result_i;
RfWdSelNextPc: rf_base_wr_data_no_intg_o = {{(32-(ImemAddrWidth+1)){1'b0}},
next_insn_addr_wide};
RfWdSelIspr: rf_base_wr_data_no_intg_o = csr_rdata;
RfWdSelIncr: rf_base_wr_data_no_intg_o = increment_out;
default: rf_base_wr_data_no_intg_o = alu_base_operation_result_i;
endcase
// Write data mux for anything that provides its own integrity
unique case (insn_dec_base_i.rf_wdata_sel)
RfWdSelLsu: begin
rf_base_wr_data_intg_sel_o = 1'b1;
rf_base_wr_data_intg_o = lsu_base_rdata_i;
end
default: begin
rf_base_wr_data_intg_sel_o = 1'b0;
rf_base_wr_data_intg_o = '0;
end
endcase
end
for (genvar i = 0; i < BaseWordsPerWLEN; ++i) begin : g_rf_bignum_rd_data
assign rf_bignum_rd_data_a_no_intg[i * 32 +: 32] = rf_bignum_rd_data_a_intg_i[i * 39 +: 32];
assign rf_bignum_rd_data_b_no_intg[i * 32 +: 32] = rf_bignum_rd_data_b_intg_i[i * 39 +: 32];
end
assign rf_bignum_rd_addr_a_o = insn_dec_bignum_i.rf_a_indirect ? rf_base_rd_data_a_no_intg[4:0] :
insn_dec_bignum_i.a;
assign rf_bignum_rd_en_a_o = insn_dec_bignum_i.rf_ren_a & insn_valid_i;
assign rf_bignum_rd_addr_b_o = insn_dec_bignum_i.rf_b_indirect ? rf_base_rd_data_b_no_intg[4:0] :
insn_dec_bignum_i.b;
assign rf_bignum_rd_en_b_o = insn_dec_bignum_i.rf_ren_b & insn_valid_i;
assign alu_bignum_operation_o.operand_a = rf_bignum_rd_data_a_no_intg;
// Base ALU Operand B MUX
always_comb begin
unique case (insn_dec_bignum_i.alu_op_b_sel)
OpBSelRegister: alu_bignum_operation_o.operand_b = rf_bignum_rd_data_b_no_intg;
OpBSelImmediate: alu_bignum_operation_o.operand_b = insn_dec_bignum_i.i;
default: alu_bignum_operation_o.operand_b = rf_bignum_rd_data_b_no_intg;
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.alu_shift_right;
assign alu_bignum_operation_o.shift_amt = insn_dec_bignum_i.alu_shift_amt;
assign alu_bignum_operation_o.flag_group = insn_dec_bignum_i.alu_flag_group;
assign alu_bignum_operation_o.sel_flag = insn_dec_bignum_i.alu_sel_flag;
assign alu_bignum_operation_o.alu_flag_en = insn_dec_bignum_i.alu_flag_en & insn_executing;
assign alu_bignum_operation_o.mac_flag_en = insn_dec_bignum_i.mac_flag_en & insn_executing;
assign mac_bignum_operation_o.operand_a = rf_bignum_rd_data_a_no_intg;
assign mac_bignum_operation_o.operand_b = rf_bignum_rd_data_b_no_intg;
assign mac_bignum_operation_o.operand_a_qw_sel = insn_dec_bignum_i.mac_op_a_qw_sel;
assign mac_bignum_operation_o.operand_b_qw_sel = insn_dec_bignum_i.mac_op_b_qw_sel;
assign mac_bignum_operation_o.wr_hw_sel_upper = insn_dec_bignum_i.mac_wr_hw_sel_upper;
assign mac_bignum_operation_o.pre_acc_shift_imm = insn_dec_bignum_i.mac_pre_acc_shift;
assign mac_bignum_operation_o.zero_acc = insn_dec_bignum_i.mac_zero_acc;
assign mac_bignum_operation_o.shift_acc = insn_dec_bignum_i.mac_shift_out;
assign mac_bignum_en_o = insn_executing & insn_dec_bignum_i.mac_en;
// Move / Conditional Select. Only select B register data when a selection instruction is being
// executed and the selection flag isn't set.
`ASSERT(SelFlagValid, insn_valid_i & insn_dec_bignum_i.sel_insn |->
insn_dec_bignum_i.alu_sel_flag inside {FlagC, FlagL, FlagM, FlagZ})
assign selection_result =
~insn_dec_bignum_i.sel_insn | alu_bignum_selection_flag_i ? rf_bignum_rd_data_a_intg_i :
rf_bignum_rd_data_b_intg_i;
// Bignum Register file write control
always_comb begin
// By default write nothing
rf_bignum_wr_en_o = 2'b00;
// Only write if executing instruction wants a bignum rf write and it isn't stalled and there is
// no error
if (insn_executing && insn_dec_bignum_i.rf_we && !err && !stall) begin
if (insn_dec_bignum_i.mac_en && insn_dec_bignum_i.mac_shift_out) begin
// Special handling for BN.MULQACC.SO, only enable upper or lower half depending on
// mac_wr_hw_sel_upper.
rf_bignum_wr_en_o = insn_dec_bignum_i.mac_wr_hw_sel_upper ? 2'b10 : 2'b01;
end else begin
// For everything else write both halves immediately.
rf_bignum_wr_en_o = 2'b11;
end
end
end
// For BN.LID sample the indirect destination register index in first cycle as an increment might
// change it for the second cycle where the load data is written to the bignum register file.
always_ff @(posedge clk_i) begin
if (insn_dec_bignum_i.rf_d_indirect & lsu_load_req_raw) begin
ld_insn_bignum_wr_addr_q <= rf_base_rd_data_b_no_intg[4:0];
end
end
always_comb begin
rf_bignum_wr_addr_o = insn_dec_bignum_i.d;
if (insn_dec_bignum_i.rf_d_indirect) begin
if (insn_dec_shared_i.ld_insn) begin
// Use sampled register index from first cycle of the load (in case the increment has
// changed the value in the mean-time).
rf_bignum_wr_addr_o = ld_insn_bignum_wr_addr_q;
end else begin
// Use read register index directly
rf_bignum_wr_addr_o = rf_base_rd_data_b_no_intg[4:0];
end
end
end
// For the shift-out variant of BN.MULQACC the bottom half of the MAC result is written to one
// half of a desintation register specified by the instruction (mac_wr_hw_sel_upper). The bottom
// half of the MAC result must be placed in the appropriate half of the write data (the RF only
// accepts write data for the top half in the top half of the write data input). Otherwise
// (shift-out to bottom half and all other BN.MULQACC instructions) simply pass the MAC result
// through unchanged as write data.
assign mac_bignum_rf_wr_data[WLEN-1:WLEN/2] =
insn_dec_bignum_i.mac_wr_hw_sel_upper &&
insn_dec_bignum_i.mac_shift_out ? mac_bignum_operation_result_i[WLEN/2-1:0] :
mac_bignum_operation_result_i[WLEN-1:WLEN/2];
assign mac_bignum_rf_wr_data[WLEN/2-1:0] = mac_bignum_operation_result_i[WLEN/2-1:0];
always_comb begin
// Write data mux for anything that needs integrity computing during register write
// TODO: ISPR data will go via direct mux below once integrity has been implemented for
// them.
unique case (insn_dec_bignum_i.rf_wdata_sel)
RfWdSelEx: rf_bignum_wr_data_no_intg_o = alu_bignum_operation_result_i;
RfWdSelIspr: rf_bignum_wr_data_no_intg_o = ispr_rdata_i;
RfWdSelMac: rf_bignum_wr_data_no_intg_o = mac_bignum_rf_wr_data;
default: rf_bignum_wr_data_no_intg_o = alu_bignum_operation_result_i;
endcase
// Write data mux for anything that provides its own integrity
unique case (insn_dec_bignum_i.rf_wdata_sel)
RfWdSelMovSel: begin
rf_bignum_wr_data_intg_sel_o = 1'b1;
rf_bignum_wr_data_intg_o = selection_result;
end
RfWdSelLsu: begin
rf_bignum_wr_data_intg_sel_o = 1'b1;
rf_bignum_wr_data_intg_o = lsu_bignum_rdata_i;
end
default: begin
rf_bignum_wr_data_intg_sel_o = 1'b0;
rf_bignum_wr_data_intg_o = '0;
end
endcase
end
assign rf_a_indirect_err = insn_dec_bignum_i.rf_a_indirect &
(|rf_base_rd_data_a_no_intg[31:5]) &
rf_base_rd_en_a_o;
assign rf_b_indirect_err = insn_dec_bignum_i.rf_b_indirect &
(|rf_base_rd_data_b_no_intg[31:5]) &
rf_base_rd_en_b_o;
assign rf_d_indirect_err = insn_dec_bignum_i.rf_d_indirect &
(|rf_base_rd_data_b_no_intg[31:5]) &
rf_base_rd_en_b_o;
assign rf_indirect_err =
insn_valid_i & (rf_a_indirect_err | rf_b_indirect_err | rf_d_indirect_err);
// 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]);
assign csr_sub_addr = insn_dec_base_i.i[$clog2(BaseWordsPerWLEN)-1:0];
always_comb begin
ispr_addr_base = IsprMod;
ispr_word_addr_base = '0;
csr_illegal_addr = 1'b0;
unique case (csr_addr)
CsrFlags, CsrFg0, CsrFg1 : 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_sub_addr;
end
CsrRndPrefetch: begin
// Reading from RND_PREFETCH results in 0, there is no ISPR to read so no address is set.
// The csr_rdata mux logic takes care of producing the 0.
end
CsrRnd: begin
ispr_addr_base = IsprRnd;
ispr_word_addr_base = '0;
end
CsrUrnd: begin
ispr_addr_base = IsprUrnd;
ispr_word_addr_base = '0;
end
default: csr_illegal_addr = 1'b1;
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_raw[i_bit] = |csr_rdata_mux[i_bit];
end
// Specialised read data handling for CSR reads where raw read data needs modification.
always_comb begin
csr_rdata = csr_rdata_raw;
unique case (csr_addr)
// For FG0/FG1 select out appropriate bits from FLAGS ISPR and pad the rest with zeros.
CsrFg0: csr_rdata = {28'b0, csr_rdata_raw[3:0]};
CsrFg1: csr_rdata = {28'b0, csr_rdata_raw[7:4]};
CsrRndPrefetch: csr_rdata = '0;
default: ;
endcase
end
assign csr_wdata_raw = insn_dec_shared_i.ispr_rs_insn ? csr_rdata | rf_base_rd_data_a_no_intg :
rf_base_rd_data_a_no_intg;
// Specialised write data handling for CSR writes where raw write data needs modification.
always_comb begin
csr_wdata = csr_wdata_raw;
unique case (csr_addr)
// For FG0/FG1 only modify relevant part of FLAGS ISPR.
CsrFg0: csr_wdata = {24'b0, csr_rdata_raw[7:4], csr_wdata_raw[3:0]};
CsrFg1: csr_wdata = {24'b0, csr_wdata_raw[3:0], csr_rdata_raw[3:0]};
default: ;
endcase
end
// ISPR RS (read and set) must not be combined with ISPR RD or WR (read or write). ISPR RD and
// WR (read and write) is allowed.
`ASSERT(NoIsprRorWAndRs, insn_valid_i |-> ~(insn_dec_shared_i.ispr_rs_insn &
(insn_dec_shared_i.ispr_rd_insn |
insn_dec_shared_i.ispr_wr_insn)))
assign wsr_addr = wsr_e'(insn_dec_bignum_i.i[WsrNumWidth-1:0]);
always_comb begin
ispr_addr_bignum = IsprMod;
wsr_illegal_addr = 1'b0;
unique case (wsr_addr)
WsrMod: ispr_addr_bignum = IsprMod;
WsrRnd: ispr_addr_bignum = IsprRnd;
WsrUrnd: ispr_addr_bignum = IsprUrnd;
WsrAcc: ispr_addr_bignum = IsprAcc;
default: wsr_illegal_addr = 1'b1;
endcase
end
assign wsr_wdata = insn_dec_shared_i.ispr_rs_insn ? ispr_rdata_i | rf_bignum_rd_data_a_no_intg :
rf_bignum_rd_data_a_no_intg;
assign ispr_illegal_addr = insn_dec_shared_i.subset == InsnSubsetBase ? csr_illegal_addr :
wsr_illegal_addr;
assign ispr_err = ispr_illegal_addr & insn_valid_i & (insn_dec_shared_i.ispr_rd_insn |
insn_dec_shared_i.ispr_wr_insn |
insn_dec_shared_i.ispr_rs_insn);
assign ispr_wr_insn = insn_dec_shared_i.ispr_wr_insn | insn_dec_shared_i.ispr_rs_insn;
assign ispr_rd_insn = insn_dec_shared_i.ispr_rd_insn | insn_dec_shared_i.ispr_rs_insn;
// Write to RND_PREFETCH must not produce ISR write
assign ispr_wr_base_insn =
ispr_wr_insn & (insn_dec_shared_i.subset == InsnSubsetBase) & (csr_addr != CsrRndPrefetch);
assign ispr_wr_bignum_insn = ispr_wr_insn & (insn_dec_shared_i.subset == InsnSubsetBignum);
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{ispr_wr_base_insn & insn_executing}} &
ispr_word_sel_base;
assign ispr_bignum_wdata_o = wsr_wdata;
assign ispr_bignum_wr_en_o = ispr_wr_bignum_insn & insn_executing;
// lsu_load_req_raw/lsu_store_req_raw indicate an instruction wishes to perform a store or a load.
// lsu_load_req_o/lsu_store_req_o factor in whether an instruction is actually executing (it may
// be suppressed due an error) and command the load or store to happen when asserted.
assign lsu_load_req_raw = insn_valid_i & insn_dec_shared_i.ld_insn & (state_q == OtbnStateRun);
assign lsu_load_req_o = insn_executing & lsu_load_req_raw;
assign lsu_store_req_raw = insn_valid_i & insn_dec_shared_i.st_insn & (state_q == OtbnStateRun);
assign lsu_store_req_o = insn_executing & lsu_store_req_raw;
assign lsu_req_subset_o = insn_dec_shared_i.subset;
assign lsu_addr_o = alu_base_operation_result_i[DmemAddrWidth-1:0];
assign lsu_base_wdata_o = rf_base_rd_data_b_intg_i;
assign lsu_bignum_wdata_o = rf_bignum_rd_data_b_intg_i;
assign dmem_addr_unaligned_bignum =
(lsu_req_subset_o == InsnSubsetBignum) & (|lsu_addr_o[$clog2(WLEN/8)-1:0]);
assign dmem_addr_unaligned_base =
(lsu_req_subset_o == InsnSubsetBase) & (|lsu_addr_o[1:0]);
assign dmem_addr_overflow = |alu_base_operation_result_i[31:DmemAddrWidth];
assign dmem_addr_err =
insn_valid_i & (lsu_load_req_raw | lsu_store_req_raw) & (dmem_addr_overflow |
dmem_addr_unaligned_bignum |
dmem_addr_unaligned_base);
assign rnd_req_o = insn_valid_i & ispr_rd_insn & (ispr_addr_o == IsprRnd);
assign rnd_prefetch_req_o = insn_valid_i & ispr_wr_insn &
(insn_dec_shared_i.subset == InsnSubsetBase) & (csr_addr == CsrRndPrefetch);
endmodule