blob: f194840c0089f78ce76192c169ec25ef4487b667 [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 alu block for the bignum instruction subset
*
* This ALU supports all of the 'plain' arithmetic and logic bignum instructions, BN.MULQACC is
* implemented in a separate block.
*
* One barrel shifter and two adders (X and Y) are implemented along with the logic operators
* (AND,OR,XOR,NOT).
*
* The adders have 256-bit operands with a carry_in and optional invert on the second operand. This
* can be used to implement subtraction (a - b == a + ~b + 1). BN.SUBB/BN.ADDC are implemented by
* feeding in the carry flag as carry in rather than a fixed 0 or 1.
*
* The shifter takes a 512-bit input (to implement BN.RSHI, concatenate and right shift) and shifts
* right by up to 256-bits. The lower (256-bit) half of the input and output can be reversed to
* allow left shift implementation. There is no concatenate and left shift instruction so reversing
* isn't required over the full width.
*
* The dataflow between the adders and shifter is in the diagram below. This arrangement allows the
* implementation of the pseudo-mod (BN.ADDM/BN.SUBM) instructions in a single cycle whilst
* minimising the critical path. The pseudo-mod instructions do not have a shifted input so X can
* compute the initial add/sub and Y computes the pseudo-mod result. For all other add/sub
* operations Y computes the operation with one of the inputs supplied by the shifter and the other
* from operand_a.
*
* Both adder X and the shifter get supplied with operand_a and operand_b from the operation_i
* input. In addition the shifter gets a shift amount (shift_amt) and can use 0 instead of
* operand_a. The shifter concatenates operand_a (or 0) and operand_b together before shifting with
* operand_a in the upper (256-bit) half {operand_a/0, operand_b}. This allows the shifter to pass
* through operand_b simply by not performing a shift.
*
* Blanking is employed on the ALU data paths. This holds unused data paths to 0 to reduce side
* channel leakage. The lower-case 'b' on the digram below indicates points in the data path that
* get blanked. Note that Adder X is never used in isolation, it is always combined with Adder Y so
* there is no need for blanking between Adder X and Adder Y.
*
* A B A B
* | | | |
* b b b b shift_amt
* | | | | |
* +-----------+ +-----------+
* | Adder X | | Shifter |
* +-----------+ +-----------+
* | |
* |----+ +----|
* | | | |
* X result | | Shifter result
* | |
* A | |
* | | | +-----------+
* b | b +---| MOD WSR |
* | | | | +-----------+
* \-----/ \-----/
* \---/ \---/
* | |
* | |
* +-----------+
* | Adder Y |
* +-----------+
* |
* Y result
*/
module otbn_alu_bignum
import otbn_pkg::*;
(
input logic clk_i,
input logic rst_ni,
input alu_bignum_operation_t operation_i,
input logic operation_valid_i,
input logic operation_commit_i,
output logic [WLEN-1:0] operation_result_o,
output logic selection_flag_o,
input alu_predec_bignum_t alu_predec_bignum_i,
input ispr_predec_bignum_t ispr_predec_bignum_i,
input ispr_e ispr_addr_i,
input logic [31:0] ispr_base_wdata_i,
input logic [BaseWordsPerWLEN-1:0] ispr_base_wr_en_i,
input logic [ExtWLEN-1:0] ispr_bignum_wdata_intg_i,
input logic ispr_bignum_wr_en_i,
input logic ispr_wr_commit_i,
input logic ispr_init_i,
output logic [ExtWLEN-1:0] ispr_rdata_intg_o,
input logic ispr_rd_en_i,
input logic [ExtWLEN-1:0] ispr_acc_intg_i,
output logic [ExtWLEN-1:0] ispr_acc_wr_data_intg_o,
output logic ispr_acc_wr_en_o,
output logic reg_intg_violation_err_o,
input logic sec_wipe_mod_urnd_i,
input logic sec_wipe_zero_i,
input flags_t mac_operation_flags_i,
input flags_t mac_operation_flags_en_i,
input logic [WLEN-1:0] rnd_data_i,
input logic [WLEN-1:0] urnd_data_i,
input logic [1:0][SideloadKeyWidth-1:0] sideload_key_shares_i,
output logic alu_predec_error_o,
output logic ispr_predec_error_o
);
///////////
// ISPRs //
///////////
flags_t flags_q [NFlagGroups];
flags_t flags_d [NFlagGroups];
logic [NFlagGroups*FlagsWidth-1:0] flags_flattened;
logic [NFlagGroups-1:0] flags_en;
logic [NFlagGroups-1:0] is_operation_flag_group;
flags_t selected_flags;
flags_t adder_update_flags;
logic adder_update_flags_en, adder_update_flags_en_raw;
flags_t logic_update_flags;
logic logic_update_flags_en, logic_update_flags_en_raw;
flags_t mac_update_flags;
logic mac_update_flags_en;
logic ispr_update_flags_en;
logic [NIspr-1:0] expected_ispr_rd_en_onehot;
logic [NIspr-1:0] expected_ispr_wr_en_onehot;
logic ispr_wr_en;
assign adder_update_flags_en = operation_i.alu_flag_en &
operation_commit_i &
adder_update_flags_en_raw;
assign logic_update_flags_en = operation_i.alu_flag_en &
operation_commit_i &
logic_update_flags_en_raw;
assign mac_update_flags_en = operation_i.mac_flag_en & operation_commit_i;
assign ispr_update_flags_en = ispr_base_wr_en_i[0] &
ispr_wr_commit_i &
(ispr_addr_i == IsprFlags);
`ASSERT(UpdateFlagsOnehot, $onehot0({ispr_init_i, adder_update_flags_en, logic_update_flags_en,
mac_update_flags_en, ispr_update_flags_en}))
assign selected_flags = flags_q[operation_i.flag_group];
assign mac_update_flags = (selected_flags & ~mac_operation_flags_en_i) |
(mac_operation_flags_i & mac_operation_flags_en_i);
for (genvar i_fg = 0; i_fg < NFlagGroups; i_fg++) begin : g_flag_groups
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
flags_q[i_fg] <= '{Z : 1'b0, L : 1'b0, M : 1'b0, C : 1'b0};
end else if (flags_en[i_fg]) begin
flags_q[i_fg] <= flags_d[i_fg];
end
end
assign is_operation_flag_group[i_fg] = operation_i.flag_group == i_fg;
assign flags_flattened[i_fg*FlagsWidth+:FlagsWidth] = flags_q[i_fg];
// Flag updates can come from the Y adder result, the logical operation result or from an ISPR
// write.
always_comb begin
flags_d[i_fg] = adder_update_flags;
unique case (1'b1)
ispr_init_i: flags_d[i_fg] = '0;
adder_update_flags_en: flags_d[i_fg] = adder_update_flags;
logic_update_flags_en: flags_d[i_fg] = logic_update_flags;
mac_update_flags_en: flags_d[i_fg] = mac_update_flags;
ispr_update_flags_en: flags_d[i_fg] = ispr_base_wdata_i[i_fg*FlagsWidth+:FlagsWidth];
sec_wipe_zero_i: flags_d[i_fg] = '0;
default: ;
endcase
end
assign flags_en[i_fg] = ispr_init_i | ispr_update_flags_en |
(adder_update_flags_en & is_operation_flag_group[i_fg]) |
(logic_update_flags_en & is_operation_flag_group[i_fg]) |
(mac_update_flags_en & is_operation_flag_group[i_fg]) |
sec_wipe_zero_i;
end
logic [ExtWLEN-1:0] mod_intg_q;
logic [ExtWLEN-1:0] mod_intg_d;
logic [BaseWordsPerWLEN-1:0] mod_ispr_wr_en;
logic [BaseWordsPerWLEN-1:0] mod_wr_en;
logic [ExtWLEN-1:0] ispr_mod_bignum_wdata_intg_blanked;
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(ExtWLEN)) u_ispr_mod_bignum_wdata_blanker (
.in_i (ispr_bignum_wdata_intg_i),
.en_i (ispr_predec_bignum_i.ispr_wr_en[IsprMod]),
.out_o(ispr_mod_bignum_wdata_intg_blanked)
);
// If the blanker is enabled, the output will not carry the correct ECC bits. This is not
// a problem because a blanked value should never be written to the register. If the blanked
// value is written to the register nonetheless, an integrity error arises.
logic [WLEN-1:0] mod_no_intg_d;
logic [WLEN-1:0] mod_no_intg_q;
logic [ExtWLEN-1:0] mod_intg_calc;
logic [2*BaseWordsPerWLEN-1:0] mod_intg_err;
for (genvar i_word = 0; i_word < BaseWordsPerWLEN; i_word++) begin : g_mod_words
prim_secded_inv_39_32_enc i_secded_enc (
.data_i (mod_no_intg_d[i_word*32+:32]),
.data_o (mod_intg_calc[i_word*39+:39])
);
prim_secded_inv_39_32_dec i_secded_dec (
.data_i (mod_intg_q[i_word*39+:39]),
.data_o (/* unused because we abort on any integrity error */),
.syndrome_o (/* unused */),
.err_o (mod_intg_err[i_word*2+:2])
);
assign mod_no_intg_q[i_word*32+:32] = mod_intg_q[i_word*39+:32];
always_ff @(posedge clk_i) begin
if (mod_wr_en[i_word]) begin
mod_intg_q[i_word*39+:39] <= mod_intg_d[i_word*39+:39];
end
end
always_comb begin
mod_no_intg_d[i_word*32+:32] = '0;
unique case (1'b1)
// Non-encoded inputs have to be encoded before writing to the register.
sec_wipe_mod_urnd_i: begin
// In a secure wipe, `urnd_data_i` is written to the register before the zero word. The
// ECC bits should not matter between the two writes, but nonetheless we encode
// `urnd_data_i` so there is no spurious integrity error.
mod_no_intg_d[i_word*32+:32] = urnd_data_i[i_word*32+:32];
mod_intg_d[i_word*39+:39] = mod_intg_calc[i_word*39+:39];
end
// Pre-encoded inputs can directly be written to the register.
default: mod_intg_d[i_word*39+:39] = ispr_mod_bignum_wdata_intg_blanked[i_word*39+:39];
endcase
unique case (1'b1)
ispr_init_i: mod_intg_d[i_word*39+:39] = EccZeroWord;
ispr_base_wr_en_i[i_word]: begin
mod_no_intg_d[i_word*32+:32] = ispr_base_wdata_i;
mod_intg_d[i_word*39+:39] = mod_intg_calc[i_word*39+:39];
end
default: ;
endcase
end
`ASSERT(ModWrSelOneHot, $onehot0({ispr_init_i, ispr_base_wr_en_i[i_word]}))
assign mod_ispr_wr_en[i_word] = (ispr_addr_i == IsprMod) &
(ispr_base_wr_en_i[i_word] | ispr_bignum_wr_en_i) &
ispr_wr_commit_i;
assign mod_wr_en[i_word] = ispr_init_i |
mod_ispr_wr_en[i_word] |
sec_wipe_mod_urnd_i;
end
assign ispr_acc_wr_en_o =
((ispr_addr_i == IsprAcc) & ispr_bignum_wr_en_i & ispr_wr_commit_i) | ispr_init_i;
logic [ExtWLEN-1:0] ispr_acc_bignum_wdata_intg_blanked;
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(ExtWLEN)) u_ispr_acc_bignum_wdata_intg_blanker (
.in_i (ispr_bignum_wdata_intg_i),
.en_i (ispr_predec_bignum_i.ispr_wr_en[IsprAcc]),
.out_o(ispr_acc_bignum_wdata_intg_blanked)
);
// If the blanker is enabled, the output will not carry the correct ECC bits. This is not
// a problem because a blanked value should never be used. If the blanked value is used
// nonetheless, an integrity error arises.
assign ispr_acc_wr_data_intg_o = ispr_init_i ? EccWideZeroWord
: ispr_acc_bignum_wdata_intg_blanked;
// ISPR read data is muxed out in two stages:
// 1. Select amongst the ISPRs that have no integrity bits. The output has integrity calculated
// for it.
// 2. Select between the ISPRs that have integrity bits and the result of the first stage.
// Number of ISPRs that have integrity protection
localparam int NIntgIspr = 2;
// IDs fpr ISPRs with integrity
localparam int IsprModIntg = 0;
localparam int IsprAccIntg = 1;
// ID representing all ISPRs with no integrity
localparam int IsprNoIntg = 2;
logic [NIntgIspr:0] ispr_rdata_intg_mux_sel;
logic [ExtWLEN-1:0] ispr_rdata_intg_mux_in [NIntgIspr+1];
logic [WLEN-1:0] ispr_rdata_no_intg_mux_in [NIspr];
// First stage
// MOD and ACC supply their own integrity so these values are unused
assign ispr_rdata_no_intg_mux_in[IsprMod] = 0;
assign ispr_rdata_no_intg_mux_in[IsprAcc] = 0;
assign ispr_rdata_no_intg_mux_in[IsprRnd] = rnd_data_i;
assign ispr_rdata_no_intg_mux_in[IsprUrnd] = urnd_data_i;
assign ispr_rdata_no_intg_mux_in[IsprFlags] = {{(WLEN - (NFlagGroups * FlagsWidth)){1'b0}},
flags_flattened};
// SEC_CM: KEY.SIDELOAD
assign ispr_rdata_no_intg_mux_in[IsprKeyS0L] = sideload_key_shares_i[0][255:0];
assign ispr_rdata_no_intg_mux_in[IsprKeyS0H] = {{(WLEN - (SideloadKeyWidth - 256)){1'b0}},
sideload_key_shares_i[0][SideloadKeyWidth-1:256]};
assign ispr_rdata_no_intg_mux_in[IsprKeyS1L] = sideload_key_shares_i[1][255:0];
assign ispr_rdata_no_intg_mux_in[IsprKeyS1H] = {{(WLEN - (SideloadKeyWidth - 256)){1'b0}},
sideload_key_shares_i[1][SideloadKeyWidth-1:256]};
logic [WLEN-1:0] ispr_rdata_no_intg;
logic [ExtWLEN-1:0] ispr_rdata_intg_calc;
// SEC_CM: DATA_REG_SW.SCA
prim_onehot_mux #(
.Width (WLEN),
.Inputs (NIspr)
) u_ispr_rdata_no_intg_mux (
.clk_i,
.rst_ni,
.in_i (ispr_rdata_no_intg_mux_in),
.sel_i (ispr_predec_bignum_i.ispr_rd_en),
.out_o (ispr_rdata_no_intg)
);
for (genvar i_word = 0; i_word < BaseWordsPerWLEN; i_word++) begin : g_rdata_enc
prim_secded_inv_39_32_enc i_secded_enc (
.data_i(ispr_rdata_no_intg[i_word * 32 +: 32]),
.data_o(ispr_rdata_intg_calc[i_word * 39 +: 39])
);
end
// Second stage
assign ispr_rdata_intg_mux_in[IsprModIntg] = mod_intg_q;
assign ispr_rdata_intg_mux_in[IsprAccIntg] = ispr_acc_intg_i;
assign ispr_rdata_intg_mux_in[IsprNoIntg] = ispr_rdata_intg_calc;
assign ispr_rdata_intg_mux_sel[IsprModIntg] = ispr_predec_bignum_i.ispr_rd_en[IsprMod];
assign ispr_rdata_intg_mux_sel[IsprAccIntg] = ispr_predec_bignum_i.ispr_rd_en[IsprAcc];
assign ispr_rdata_intg_mux_sel[IsprNoIntg] =
|{ispr_predec_bignum_i.ispr_rd_en[IsprKeyS1H:IsprKeyS0L],
ispr_predec_bignum_i.ispr_rd_en[IsprUrnd],
ispr_predec_bignum_i.ispr_rd_en[IsprFlags],
ispr_predec_bignum_i.ispr_rd_en[IsprRnd]};
// If we're reading from an ISPR we must be using the ispr_rdata_intg_mux
`ASSERT(IsprRDataIntgMuxSelIfIsprRd_A,
|ispr_predec_bignum_i.ispr_rd_en |-> |ispr_rdata_intg_mux_sel)
// If we're reading from MOD or ACC we must not take the read data from the calculated integrity
// path
`ASSERT(IsprModMustTakeIntg_A,
ispr_predec_bignum_i.ispr_rd_en[IsprMod] |-> !ispr_rdata_intg_mux_sel[IsprNoIntg])
`ASSERT(IsprAccMustTakeIntg_A,
ispr_predec_bignum_i.ispr_rd_en[IsprAcc] |-> !ispr_rdata_intg_mux_sel[IsprNoIntg])
prim_onehot_mux #(
.Width (ExtWLEN),
.Inputs (NIntgIspr+1)
) u_ispr_rdata_intg_mux (
.clk_i,
.rst_ni,
.in_i (ispr_rdata_intg_mux_in),
.sel_i (ispr_rdata_intg_mux_sel),
.out_o (ispr_rdata_intg_o)
);
prim_onehot_enc #(
.OneHotWidth (NIspr)
) u_expected_ispr_rd_en_enc (
.in_i(ispr_addr_i),
.en_i (ispr_rd_en_i),
.out_o (expected_ispr_rd_en_onehot)
);
assign ispr_wr_en = |{ispr_bignum_wr_en_i, ispr_base_wr_en_i};
prim_onehot_enc #(
.OneHotWidth (NIspr)
) u_expected_ispr_wr_en_enc (
.in_i(ispr_addr_i),
.en_i (ispr_wr_en),
.out_o (expected_ispr_wr_en_onehot)
);
// SEC_CM: CTRL.REDUN
assign ispr_predec_error_o =
|{expected_ispr_rd_en_onehot != ispr_predec_bignum_i.ispr_rd_en,
expected_ispr_wr_en_onehot != ispr_predec_bignum_i.ispr_wr_en};
/////////////
// Shifter //
/////////////
logic [WLEN-1:0] shifter_in_upper, shifter_in_lower, shifter_in_lower_reverse;
logic [WLEN*2-1:0] shifter_in;
logic [WLEN*2-1:0] shifter_out;
logic [WLEN-1:0] shifter_out_lower_reverse, shifter_res, unused_shifter_out_upper;
logic [WLEN-1:0] shifter_operand_a_blanked;
logic [WLEN-1:0] shifter_operand_b_blanked;
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(WLEN)) u_shifter_operand_a_blanker (
.in_i (operation_i.operand_a),
.en_i (alu_predec_bignum_i.shifter_a_en),
.out_o(shifter_operand_a_blanked)
);
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(WLEN)) u_shifter_operand_b_blanker (
.in_i (operation_i.operand_b),
.en_i (alu_predec_bignum_i.shifter_b_en),
.out_o(shifter_operand_b_blanked)
);
// Operand A is only used for BN.RSHI, otherwise the upper input is 0. For all instructions other
// than BN.RHSI alu_predec_bignum_i.shifter_a_en will be 0, resulting in 0 for the upper input.
assign shifter_in_upper = shifter_operand_a_blanked;
assign shifter_in_lower = shifter_operand_b_blanked;
for (genvar i = 0; i < WLEN; i++) begin : g_shifter_in_lower_reverse
assign shifter_in_lower_reverse[i] = shifter_in_lower[WLEN-i-1];
end
assign shifter_in = {shifter_in_upper,
alu_predec_bignum_i.shift_right ? shifter_in_lower : shifter_in_lower_reverse};
assign shifter_out = shifter_in >> alu_predec_bignum_i.shift_amt;
for (genvar i = 0; i < WLEN; i++) begin : g_shifter_out_lower_reverse
assign shifter_out_lower_reverse[i] = shifter_out[WLEN-i-1];
end
assign shifter_res =
alu_predec_bignum_i.shift_right ? shifter_out[WLEN-1:0] : shifter_out_lower_reverse;
// Only the lower WLEN bits of the shift result are returned.
assign unused_shifter_out_upper = shifter_out[WLEN*2-1:WLEN];
//////////////////
// Adders X & Y //
//////////////////
logic [WLEN:0] adder_x_op_a_blanked, adder_x_op_b, adder_x_op_b_blanked;
logic adder_x_carry_in;
logic adder_x_op_b_invert;
logic [WLEN+1:0] adder_x_res;
logic [WLEN:0] adder_y_op_a, adder_y_op_b;
logic adder_y_carry_in;
logic adder_y_op_b_invert;
logic [WLEN+1:0] adder_y_res;
logic [WLEN-1:0] adder_y_op_a_blanked;
logic [WLEN-1:0] adder_y_op_shifter_res_blanked;
logic [WLEN-1:0] shift_mod_mux_out;
logic [WLEN-1:0] x_res_operand_a_mux_out;
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(WLEN+1)) u_adder_x_op_a_blanked (
.in_i ({operation_i.operand_a, 1'b1}),
.en_i (alu_predec_bignum_i.adder_x_en),
.out_o(adder_x_op_a_blanked)
);
assign adder_x_op_b = {adder_x_op_b_invert ? ~operation_i.operand_b : operation_i.operand_b,
adder_x_carry_in};
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(WLEN+1)) u_adder_x_op_b_blanked (
.in_i (adder_x_op_b),
.en_i (alu_predec_bignum_i.adder_x_en),
.out_o(adder_x_op_b_blanked)
);
assign adder_x_res = adder_x_op_a_blanked + adder_x_op_b_blanked;
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(WLEN)) u_adder_y_op_a_blanked (
.in_i (operation_i.operand_a),
.en_i (alu_predec_bignum_i.adder_y_op_a_en),
.out_o(adder_y_op_a_blanked)
);
assign x_res_operand_a_mux_out =
alu_predec_bignum_i.x_res_operand_a_sel ? adder_x_res[WLEN:1] : adder_y_op_a_blanked;
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(WLEN)) u_adder_y_op_shifter_blanked (
.in_i (shifter_res),
.en_i (alu_predec_bignum_i.adder_y_op_shifter_en),
.out_o(adder_y_op_shifter_res_blanked)
);
assign shift_mod_mux_out =
alu_predec_bignum_i.shift_mod_sel ? adder_y_op_shifter_res_blanked : mod_no_intg_q;
assign adder_y_op_a = {x_res_operand_a_mux_out, 1'b1};
assign adder_y_op_b = {adder_y_op_b_invert ? ~shift_mod_mux_out : shift_mod_mux_out,
adder_y_carry_in};
assign adder_y_res = adder_y_op_a + adder_y_op_b;
assign adder_update_flags.C = (operation_i.op == AluOpBignumAdd ||
operation_i.op == AluOpBignumAddc) ? adder_y_res[WLEN+1] :
~adder_y_res[WLEN+1];
assign adder_update_flags.M = adder_y_res[WLEN];
assign adder_update_flags.L = adder_y_res[1];
assign adder_update_flags.Z = ~|adder_y_res[WLEN:1];
// The LSb of the adder results are unused.
logic unused_adder_x_res_lsb, unused_adder_y_res_lsb;
assign unused_adder_x_res_lsb = adder_x_res[0];
assign unused_adder_y_res_lsb = adder_y_res[0];
//////////////////////////////
// Shifter & Adders control //
//////////////////////////////
logic expected_adder_x_en;
logic expected_x_res_operand_a_sel;
logic expected_adder_y_op_a_en;
logic expected_adder_y_op_shifter_en;
logic expected_shifter_a_en;
logic expected_shifter_b_en;
logic expected_shift_right;
logic expected_shift_mod_sel;
logic expected_logic_a_en;
logic expected_logic_shifter_en;
logic [3:0] expected_logic_res_sel;
always_comb begin
adder_x_carry_in = 1'b0;
adder_x_op_b_invert = 1'b0;
adder_y_carry_in = 1'b0;
adder_y_op_b_invert = 1'b0;
adder_update_flags_en_raw = 1'b0;
logic_update_flags_en_raw = 1'b0;
expected_adder_x_en = 1'b0;
expected_x_res_operand_a_sel = 1'b0;
expected_adder_y_op_a_en = 1'b0;
expected_adder_y_op_shifter_en = 1'b0;
expected_shifter_a_en = 1'b0;
expected_shifter_b_en = 1'b0;
expected_shift_right = 1'b0;
expected_shift_mod_sel = 1'b1;
expected_logic_a_en = 1'b0;
expected_logic_shifter_en = 1'b0;
expected_logic_res_sel = '0;
unique case (operation_i.op)
AluOpBignumAdd: begin
// Shifter computes B [>>|<<] shift_amt
// Y computes A + shifter_res
// X ignored
adder_y_carry_in = 1'b0;
adder_y_op_b_invert = 1'b0;
adder_update_flags_en_raw = 1'b1;
expected_adder_y_op_shifter_en = 1'b1;
expected_adder_y_op_a_en = 1'b1;
expected_shifter_b_en = 1'b1;
expected_shift_right = operation_i.shift_right;
end
AluOpBignumAddc: begin
// Shifter computes B [>>|<<] shift_amt
// Y computes A + shifter_res + flags.C
// X ignored
adder_y_carry_in = selected_flags.C;
adder_y_op_b_invert = 1'b0;
adder_update_flags_en_raw = 1'b1;
expected_adder_y_op_shifter_en = 1'b1;
expected_adder_y_op_a_en = 1'b1;
expected_shifter_b_en = 1'b1;
expected_shift_right = operation_i.shift_right;
end
AluOpBignumAddm: begin
// X computes A + B
// Y computes adder_x_res - mod = adder_x_res + ~mod + 1
// Shifter ignored
// Output mux chooses result based on top bit of X result (whether mod subtraction in
// Y should be applied or not)
adder_x_carry_in = 1'b0;
adder_x_op_b_invert = 1'b0;
adder_y_carry_in = 1'b1;
adder_y_op_b_invert = 1'b1;
expected_adder_x_en = 1'b1;
expected_x_res_operand_a_sel = 1'b1;
expected_shift_mod_sel = 1'b0;
end
AluOpBignumSub: begin
// Shifter computes B [>>|<<] shift_amt
// Y computes A - shifter_res = A + ~shifter_res + 1
// X ignored
adder_y_carry_in = 1'b1;
adder_y_op_b_invert = 1'b1;
adder_update_flags_en_raw = 1'b1;
expected_adder_y_op_shifter_en = 1'b1;
expected_adder_y_op_a_en = 1'b1;
expected_shifter_b_en = 1'b1;
expected_shift_right = operation_i.shift_right;
end
AluOpBignumSubb: begin
// Shifter computes B [>>|<<] shift_amt
// Y computes A - shifter_res + ~flags.C = A + ~shifter_res + flags.C
// X ignored
adder_y_carry_in = ~selected_flags.C;
adder_y_op_b_invert = 1'b1;
adder_update_flags_en_raw = 1'b1;
expected_adder_y_op_shifter_en = 1'b1;
expected_adder_y_op_a_en = 1'b1;
expected_shifter_b_en = 1'b1;
expected_shift_right = operation_i.shift_right;
end
AluOpBignumSubm: begin
// X computes A - B = A + ~B + 1
// Y computes adder_x_res + mod
// Shifter ignored
// Output mux chooses result based on top bit of X result (whether subtraction in Y should
// be applied or not)
adder_x_carry_in = 1'b1;
adder_x_op_b_invert = 1'b1;
adder_y_carry_in = 1'b0;
adder_y_op_b_invert = 1'b0;
expected_adder_x_en = 1'b1;
expected_x_res_operand_a_sel = 1'b1;
expected_shift_mod_sel = 1'b0;
end
AluOpBignumRshi: begin
// Shifter computes {A, B} >> shift_amt
// X, Y ignored
// Feed blanked shifter output (adder_y_op_shifter_res_blanked) to Y to avoid undesired
// leakage in the zero flag computation.
expected_shifter_a_en = 1'b1;
expected_shifter_b_en = 1'b1;
expected_shift_right = 1'b1;
end
AluOpBignumXor,
AluOpBignumOr,
AluOpBignumAnd,
AluOpBignumNot: begin
// Shift computes one operand for the logical operation
// X & Y ignored
// Feed blanked shifter output (adder_y_op_shifter_res_blanked) to Y to avoid undesired
// leakage in the zero flag computation.
logic_update_flags_en_raw = 1'b1;
expected_shifter_b_en = 1'b1;
expected_shift_right = operation_i.shift_right;
expected_logic_a_en = operation_i.op != AluOpBignumNot;
expected_logic_shifter_en = 1'b1;
expected_logic_res_sel[AluOpLogicXor] = operation_i.op == AluOpBignumXor;
expected_logic_res_sel[AluOpLogicOr] = operation_i.op == AluOpBignumOr;
expected_logic_res_sel[AluOpLogicAnd] = operation_i.op == AluOpBignumAnd;
expected_logic_res_sel[AluOpLogicNot] = operation_i.op == AluOpBignumNot;
end
// No operation, do nothing.
AluOpBignumNone: ;
default: ;
endcase
end
logic [$clog2(WLEN)-1:0] expected_shift_amt;
assign expected_shift_amt = operation_i.shift_amt;
// SEC_CM: CTRL.REDUN
assign alu_predec_error_o =
|{expected_adder_x_en != alu_predec_bignum_i.adder_x_en,
expected_x_res_operand_a_sel != alu_predec_bignum_i.x_res_operand_a_sel,
expected_adder_y_op_a_en != alu_predec_bignum_i.adder_y_op_a_en,
expected_adder_y_op_shifter_en != alu_predec_bignum_i.adder_y_op_shifter_en,
expected_shifter_a_en != alu_predec_bignum_i.shifter_a_en,
expected_shifter_b_en != alu_predec_bignum_i.shifter_b_en,
expected_shift_right != alu_predec_bignum_i.shift_right,
expected_shift_amt != alu_predec_bignum_i.shift_amt,
expected_shift_mod_sel != alu_predec_bignum_i.shift_mod_sel,
expected_logic_a_en != alu_predec_bignum_i.logic_a_en,
expected_logic_shifter_en != alu_predec_bignum_i.logic_shifter_en,
expected_logic_res_sel != alu_predec_bignum_i.logic_res_sel};
////////////////////////
// Logical operations //
////////////////////////
logic [WLEN-1:0] logical_res_mux_in [4];
logic [WLEN-1:0] logical_res;
logic [WLEN-1:0] logical_op_a_blanked;
logic [WLEN-1:0] logical_op_shifter_res_blanked;
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(WLEN)) u_logical_op_a_blanker (
.in_i (operation_i.operand_a),
.en_i (alu_predec_bignum_i.logic_a_en),
.out_o(logical_op_a_blanked)
);
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(WLEN)) u_logical_op_shifter_res_blanker (
.in_i (shifter_res),
.en_i (alu_predec_bignum_i.logic_shifter_en),
.out_o(logical_op_shifter_res_blanked)
);
assign logical_res_mux_in[AluOpLogicXor] = logical_op_a_blanked ^ logical_op_shifter_res_blanked;
assign logical_res_mux_in[AluOpLogicOr] = logical_op_a_blanked | logical_op_shifter_res_blanked;
assign logical_res_mux_in[AluOpLogicAnd] = logical_op_a_blanked & logical_op_shifter_res_blanked;
assign logical_res_mux_in[AluOpLogicNot] = ~logical_op_shifter_res_blanked;
// SEC_CM: DATA_REG_SW.SCA
prim_onehot_mux #(
.Width (WLEN),
.Inputs(4)
) u_logical_res_mux (
.clk_i,
.rst_ni,
.in_i (logical_res_mux_in),
.sel_i (alu_predec_bignum_i.logic_res_sel),
.out_o (logical_res)
);
// Logical operations only update M, L and Z; C must remain at its old value.
assign logic_update_flags.C = selected_flags.C;
assign logic_update_flags.M = logical_res[WLEN-1];
assign logic_update_flags.L = logical_res[0];
assign logic_update_flags.Z = ~|logical_res;
/////////////////////////////////
// Conditional Select Flag Mux //
/////////////////////////////////
always_comb begin
unique case (operation_i.sel_flag)
FlagC: selection_flag_o = selected_flags.C;
FlagM: selection_flag_o = selected_flags.M;
FlagL: selection_flag_o = selected_flags.L;
// FlagZ case
default: selection_flag_o = selected_flags.Z;
endcase
end
////////////////////////
// Output multiplexer //
////////////////////////
logic adder_y_res_used;
always_comb begin
operation_result_o = adder_y_res[WLEN:1];
adder_y_res_used = 1'b1;
unique case(operation_i.op)
AluOpBignumAdd,
AluOpBignumAddc,
AluOpBignumSub,
AluOpBignumSubb: begin
operation_result_o = adder_y_res[WLEN:1];
adder_y_res_used = 1'b1;
end
// For pseudo-mod operations the result depends upon initial a + b / a - b result that is
// computed in X. Operation to add/subtract mod (X + mod / X - mod) is computed in Y.
// Subtraction is computed using in the X & Y adders as a - b == a + ~b + 1. Note that for
// a - b the top bit of the result will be set if a - b >= 0 and otherwise clear.
// BN.ADDM - X = a + b, Y = X - mod, subtract mod if a + b >= mod
// * If X generates carry a + b > mod (as mod is 256-bit) - Select Y result
// * If Y generates carry X - mod == (a + b) - mod >= 0 hence a + b >= mod, note this is only
// valid if X does not generate carry - Select Y result
// * If neither happen a + b < mod - Select X result
AluOpBignumAddm: begin
// `adder_y_res` is always used: either as condition in the following `if` statement or, if
// the `if` statement short-circuits, in the body of the `if` statement.
adder_y_res_used = 1'b1;
if (adder_x_res[WLEN+1] || adder_y_res[WLEN+1]) begin
operation_result_o = adder_y_res[WLEN:1];
end else begin
operation_result_o = adder_x_res[WLEN:1];
end
end
// BN.SUBM - X = a - b, Y = X + mod, add mod if a - b < 0
// * If X generates carry a - b >= 0 - Select X result
// * Otherwise select Y result
AluOpBignumSubm: begin
if (adder_x_res[WLEN+1]) begin
operation_result_o = adder_x_res[WLEN:1];
adder_y_res_used = 1'b0;
end else begin
operation_result_o = adder_y_res[WLEN:1];
adder_y_res_used = 1'b1;
end
end
AluOpBignumRshi: begin
operation_result_o = shifter_res[WLEN-1:0];
adder_y_res_used = 1'b0;
end
AluOpBignumXor,
AluOpBignumOr,
AluOpBignumAnd,
AluOpBignumNot: begin
operation_result_o = logical_res;
adder_y_res_used = 1'b0;
end
default: ;
endcase
end
// Determine if `mod_intg_q` is used. The control signals are only valid if `operation_i.op` is
// not none. If `shift_mod_sel` is low, `mod_intg_q` flows into `adder_y_op_b` and from there
// into `adder_y_res`. In this case, `mod_intg_q` is used iff `adder_y_res` flows into
// `operation_result_o`.
logic mod_used;
assign mod_used = operation_valid_i & (operation_i.op != AluOpBignumNone)
& !alu_predec_bignum_i.shift_mod_sel & adder_y_res_used;
`ASSERT_KNOWN(ModUsed_A, mod_used)
// Raise a register integrity violation error iff `mod_intg_q` is used and (at least partially)
// invalid.
assign reg_intg_violation_err_o = mod_used & |(mod_intg_err);
`ASSERT_KNOWN(RegIntgErrKnown_A, reg_intg_violation_err_o)
// Blanking Assertions
// All blanking assertions are reset with predec_error or overall error in the whole system
// -indicated by operation_commit_i port- as OTBN does not guarantee blanking in the case
// of an error.
// adder_x_res related blanking
`ASSERT(BlankingBignumAluXOp_A,
!expected_adder_x_en |-> {adder_x_op_a_blanked, adder_x_op_b_blanked,adder_x_res} == '0,
clk_i, !rst_ni || alu_predec_error_o || !operation_commit_i)
// adder_y_res related blanking
`ASSERT(BlankingBignumAluYOpA_A,
!expected_adder_y_op_a_en |-> adder_y_op_a_blanked == '0,
clk_i, !rst_ni || alu_predec_error_o || !operation_commit_i)
`ASSERT(BlankingBignumAluYOpShft_A,
!expected_adder_y_op_shifter_en |-> adder_y_op_shifter_res_blanked == '0,
clk_i, !rst_ni || alu_predec_error_o || !operation_commit_i)
// Adder Y must be blanked when its result is not used, with one exception: For `BN.SUBM` with
// `a >= b` (thus the result of Adder X has the carry bit set), the result of Adder Y is not used
// but it cannot be blanked solely based on the carry bit.
`ASSERT(BlankingBignumAluYResUsed_A,
!adder_y_res_used && !(operation_i.op == AluOpBignumSubm && adder_x_res[WLEN+1])
|-> {x_res_operand_a_mux_out, adder_y_op_b} == '0,
clk_i, !rst_ni || alu_predec_error_o || !operation_commit_i)
// shifter_res related blanking
`ASSERT(BlankingBignumAluShftA_A,
!expected_shifter_a_en |-> shifter_operand_a_blanked == '0,
clk_i, !rst_ni || alu_predec_error_o || !operation_commit_i)
`ASSERT(BlankingBignumAluShftB_A,
!expected_shifter_b_en |-> shifter_operand_b_blanked == '0,
clk_i, !rst_ni || alu_predec_error_o || !operation_commit_i)
`ASSERT(BlankingBignumAluShftRes_A,
!(expected_shifter_a_en || expected_shifter_b_en) |-> shifter_res == '0,
clk_i, !rst_ni || alu_predec_error_o || !operation_commit_i)
// logical_res related blanking
`ASSERT(BlankingBignumAluLogicOpA_A,
!expected_logic_a_en |-> logical_op_a_blanked == '0,
clk_i, !rst_ni || alu_predec_error_o || !operation_commit_i)
`ASSERT(BlankingBignumAluLogicShft_A,
!expected_logic_shifter_en |-> logical_op_shifter_res_blanked == '0,
clk_i, !rst_ni || alu_predec_error_o || !operation_commit_i)
`ASSERT(BlankingBignumAluLogicRes_A,
!(expected_logic_a_en || expected_logic_shifter_en) |-> logical_res == '0,
clk_i, !rst_ni || alu_predec_error_o || !operation_commit_i)
// MOD ISPR Blanking
`ASSERT(BlankingIsprMod_A,
!(|mod_wr_en) |-> ispr_mod_bignum_wdata_intg_blanked == '0,
clk_i, !rst_ni || ispr_predec_error_o || alu_predec_error_o || !operation_commit_i)
// ACC ISPR Blanking
`ASSERT(BlankingIsprACC_A,
!(|ispr_acc_wr_en_o) |-> ispr_acc_bignum_wdata_intg_blanked == '0,
clk_i, !rst_ni || ispr_predec_error_o || alu_predec_error_o || !operation_commit_i)
endmodule