| // 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 |