blob: 072d6af78432443fdb3740508870d4790c315694 [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"
module otbn_mac_bignum
import otbn_pkg::*;
(
input logic clk_i,
input logic rst_ni,
input mac_bignum_operation_t operation_i,
input logic mac_en_i,
input logic mac_commit_i,
output logic [WLEN-1:0] operation_result_o,
output flags_t operation_flags_o,
output flags_t operation_flags_en_o,
output logic operation_intg_violation_err_o,
input mac_predec_bignum_t mac_predec_bignum_i,
output logic predec_error_o,
input logic [WLEN-1:0] urnd_data_i,
input logic sec_wipe_acc_urnd_i,
output logic [ExtWLEN-1:0] ispr_acc_intg_o,
input logic [ExtWLEN-1:0] ispr_acc_wr_data_intg_i,
input logic ispr_acc_wr_en_i
);
// The MAC operates on quarter-words, QWLEN gives the number of bits in a quarter-word.
localparam int unsigned QWLEN = WLEN / 4;
logic [WLEN-1:0] adder_op_a;
logic [WLEN-1:0] adder_op_b;
logic [WLEN-1:0] adder_result;
logic [1:0] adder_result_hw_is_zero;
logic [QWLEN-1:0] mul_op_a;
logic [QWLEN-1:0] mul_op_b;
logic [WLEN/2-1:0] mul_res;
logic [WLEN-1:0] mul_res_shifted;
logic [ExtWLEN-1:0] acc_intg_d;
logic [ExtWLEN-1:0] acc_intg_q;
logic [WLEN-1:0] acc_blanked;
logic acc_en;
logic [WLEN-1:0] operand_a_blanked, operand_b_blanked;
logic expected_acc_rd_en, expected_op_en;
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(WLEN)) u_operand_a_blanker (
.in_i (operation_i.operand_a),
.en_i (mac_predec_bignum_i.op_en),
.out_o(operand_a_blanked)
);
// SEC_CM: DATA_REG_SW.SCA
prim_blanker #(.Width(WLEN)) u_operand_b_blanker (
.in_i (operation_i.operand_b),
.en_i (mac_predec_bignum_i.op_en),
.out_o(operand_b_blanked)
);
// Extract QWLEN multiply operands from WLEN operand inputs based on chosen quarter word from the
// instruction (operand_[a|b]_qw_sel).
always_comb begin
mul_op_a = '0;
mul_op_b = '0;
unique case (operation_i.operand_a_qw_sel)
2'd0: mul_op_a = operand_a_blanked[QWLEN*0+:QWLEN];
2'd1: mul_op_a = operand_a_blanked[QWLEN*1+:QWLEN];
2'd2: mul_op_a = operand_a_blanked[QWLEN*2+:QWLEN];
2'd3: mul_op_a = operand_a_blanked[QWLEN*3+:QWLEN];
default: mul_op_a = '0;
endcase
unique case (operation_i.operand_b_qw_sel)
2'd0: mul_op_b = operand_b_blanked[QWLEN*0+:QWLEN];
2'd1: mul_op_b = operand_b_blanked[QWLEN*1+:QWLEN];
2'd2: mul_op_b = operand_b_blanked[QWLEN*2+:QWLEN];
2'd3: mul_op_b = operand_b_blanked[QWLEN*3+:QWLEN];
default: mul_op_b = '0;
endcase
end
`ASSERT_KNOWN_IF(OperandAQWSelKnown, operation_i.operand_a_qw_sel, mac_en_i)
`ASSERT_KNOWN_IF(OperandBQWSelKnown, operation_i.operand_b_qw_sel, mac_en_i)
// The reset signal is not used for any registers in this module but for assertions. As those
// assertions are not visible to EDA tools working with the synthesizable subset of the code
// (e.g., Verilator), they cause lint errors in some of those tools. Prevent these errors by
// assigning the reset signal to a signal that is okay to be unused.
logic unused_ok;
assign unused_ok = ^(rst_ni);
assign mul_res = mul_op_a * mul_op_b;
// Shift the QWLEN multiply result into a WLEN word before accumulating using the shift amount
// supplied in the instruction (pre_acc_shift_imm).
always_comb begin
mul_res_shifted = '0;
unique case (operation_i.pre_acc_shift_imm)
2'd0: mul_res_shifted = {{QWLEN * 2{1'b0}}, mul_res};
2'd1: mul_res_shifted = {{QWLEN{1'b0}}, mul_res, {QWLEN{1'b0}}};
2'd2: mul_res_shifted = {mul_res, {QWLEN * 2{1'b0}}};
2'd3: mul_res_shifted = {mul_res[63:0], {QWLEN * 3{1'b0}}};
default: mul_res_shifted = '0;
endcase
end
`ASSERT_KNOWN_IF(PreAccShiftImmKnown, operation_i.pre_acc_shift_imm, mac_en_i)
// ECC encode and decode of accumulator register
logic [WLEN-1:0] acc_no_intg_d;
logic [WLEN-1:0] acc_no_intg_q;
logic [ExtWLEN-1:0] acc_intg_calc;
logic [2*BaseWordsPerWLEN-1:0] acc_intg_err;
for (genvar i_word = 0; i_word < BaseWordsPerWLEN; i_word++) begin : g_acc_words
prim_secded_inv_39_32_enc i_secded_enc (
.data_i (acc_no_intg_d[i_word*32+:32]),
.data_o (acc_intg_calc[i_word*39+:39])
);
prim_secded_inv_39_32_dec i_secded_dec (
.data_i (acc_intg_q[i_word*39+:39]),
.data_o (/* unused because we abort on any integrity error */),
.syndrome_o (/* unused */),
.err_o (acc_intg_err[i_word*2+:2])
);
assign acc_no_intg_q[i_word*32+:32] = acc_intg_q[i_word*39+:32];
end
// Propagate integrity error only if accumulator register is used: `acc_intg_q` flows into
// `operation_result_o` via `acc`, `adder_op_b`, and `adder_result` iff the MAC is enabled and the
// current operation does not zero the accumulation register.
logic acc_used;
assign acc_used = mac_en_i & ~operation_i.zero_acc;
assign operation_intg_violation_err_o = acc_used & |(acc_intg_err);
// Accumulator logic
// SEC_CM: DATA_REG_SW.SCA
// acc_rd_en is so if .Z set in MULQACC (zero_acc) so accumulator reads as 0
prim_blanker #(.Width(WLEN)) u_acc_blanker (
.in_i (acc_no_intg_q),
.en_i (mac_predec_bignum_i.acc_rd_en),
.out_o(acc_blanked)
);
// Add shifted multiplier result to current accumulator.
assign adder_op_a = mul_res_shifted;
assign adder_op_b = acc_blanked;
assign adder_result = adder_op_a + adder_op_b;
// Split zero check between the two halves of the result. This is used for flag setting (see
// below).
assign adder_result_hw_is_zero[0] = adder_result[WLEN/2-1:0] == 'h0;
assign adder_result_hw_is_zero[1] = adder_result[WLEN/2+:WLEN/2] == 'h0;
assign operation_flags_o.L = adder_result[0];
// L is always updated for .WO, and for .SO when writing to the lower half-word
assign operation_flags_en_o.L = operation_i.shift_acc ? ~operation_i.wr_hw_sel_upper : 1'b1;
// For .SO M is taken from the top-bit of shifted out half-word, otherwise it is taken from the
// top-bit of the full result.
assign operation_flags_o.M = operation_i.shift_acc ? adder_result[WLEN/2-1] :
adder_result[WLEN-1];
// M is always updated for .WO, and for .SO when writing to the upper half-word.
assign operation_flags_en_o.M = operation_i.shift_acc ? operation_i.wr_hw_sel_upper : 1'b1;
// For .SO Z is calculated from the shifted out half-word, otherwise it is calculated on the full
// result.
assign operation_flags_o.Z = operation_i.shift_acc ? adder_result_hw_is_zero[0] :
&adder_result_hw_is_zero;
// Z is updated for .WO. For .SO updates are based upon result and half-word:
// - When writing to lower half-word always update Z.
// - When writing to upper half-word clear Z if result is non-zero otherwise leave it alone.
assign operation_flags_en_o.Z =
operation_i.shift_acc & operation_i.wr_hw_sel_upper ? ~adder_result_hw_is_zero[0] :
1'b1;
// MAC never sets the carry flag
assign operation_flags_o.C = 1'b0;
assign operation_flags_en_o.C = 1'b0;
always_comb begin
acc_no_intg_d = '0;
unique case (1'b1)
// Non-encoded inputs have to be encoded before writing to the register.
sec_wipe_acc_urnd_i: begin
acc_no_intg_d = urnd_data_i;
acc_intg_d = acc_intg_calc;
end
default: begin
// If performing an ACC ISPR write the next accumulator value is taken from the ISPR write
// data, otherwise it is drawn from the adder result. The new accumulator can be optionally
// shifted right by one half-word (shift_acc).
if (ispr_acc_wr_en_i) begin
acc_intg_d = ispr_acc_wr_data_intg_i;
end else begin
acc_no_intg_d = operation_i.shift_acc ? {{QWLEN*2{1'b0}}, adder_result[QWLEN*2+:QWLEN*2]}
: adder_result;
acc_intg_d = acc_intg_calc;
end
end
endcase
end
// Only write to accumulator if the MAC is enabled or an ACC ISPR write is occuring or secure
// wipe of the internal state is occuring.
assign acc_en = (mac_en_i & mac_commit_i) | ispr_acc_wr_en_i | sec_wipe_acc_urnd_i;
always_ff @(posedge clk_i) begin
if (acc_en) begin
acc_intg_q <= acc_intg_d;
end
end
assign ispr_acc_intg_o = acc_intg_q;
// The operation result is taken directly from the adder, shift_acc only applies to the new value
// written to the accumulator.
assign operation_result_o = adder_result;
assign expected_op_en = mac_en_i;
assign expected_acc_rd_en = ~operation_i.zero_acc & mac_en_i;
// SEC_CM: CTRL.REDUN
assign predec_error_o = |{expected_op_en != mac_predec_bignum_i.op_en,
expected_acc_rd_en != mac_predec_bignum_i.acc_rd_en};
`ASSERT(NoISPRAccWrAndMacEn, ~(ispr_acc_wr_en_i & mac_en_i))
endmodule