blob: 3800d5da2cc508e2b62ac326f841505cc06a4b96 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// AES high-bandwidth pseudo-random number generator for masking
//
// This module uses multiple parallel LFSRs each one of them followed by an aligned permutation, a
// non-linear layer (PRINCE S-Boxes) and another permutation layer spanning across all LFSRs to
// generate pseudo-random data for masking the AES cipher core. The LFSRs can be reseeded using an
// external interface.
///////////////////////////////////////////////////////////////////////////////////////////////////
// IMPORTANT NOTE: //
// DO NOT USE THIS BLINDLY! //
// //
// It has not yet been verified that this initial implementation produces pseudo-random numbers //
// of sufficient quality in terms of uniformity and independence, and that it is indeed suitable //
// for masking purposes. //
///////////////////////////////////////////////////////////////////////////////////////////////////
`include "prim_assert.sv"
module aes_prng_masking import aes_pkg::*;
#(
parameter int unsigned Width = WidthPRDMasking, // Must be divisble by ChunkSize and 8
parameter int unsigned ChunkSize = ChunkSizePRDMasking, // Width of the LFSR primitives
parameter int unsigned EntropyWidth = edn_pkg::ENDPOINT_BUS_WIDTH,
parameter bit SecAllowForcingMasks = 0, // Allow forcing masks to constant values using
// force_masks_i. Useful for SCA only.
parameter bit SecSkipPRNGReseeding = 0, // The current SCA setup doesn't provide
// sufficient resources to implement the
// infrastructure required for PRNG reseeding.
// To enable SCA resistance evaluations, we
// need to skip reseeding requests.
localparam int unsigned NumChunks = Width/ChunkSize, // derived parameter
parameter masking_lfsr_seed_t RndCnstLfsrSeed = RndCnstMaskingLfsrSeedDefault,
parameter masking_lfsr_perm_t RndCnstLfsrPerm = RndCnstMaskingLfsrPermDefault
) (
input logic clk_i,
input logic rst_ni,
input logic force_masks_i,
// Connections to AES internals, PRNG consumers
input logic data_update_i,
output logic [Width-1:0] data_o,
input logic reseed_req_i,
output logic reseed_ack_o,
// Connections to outer world, LFSR reseeding
output logic entropy_req_o,
input logic entropy_ack_i,
input logic [EntropyWidth-1:0] entropy_i
);
logic [NumChunks-1:0] prng_seed_en;
logic [NumChunks-1:0][ChunkSize-1:0] prng_seed;
logic prng_en;
logic [NumChunks-1:0][ChunkSize-1:0] prng_state, perm;
logic [Width-1:0] prng_b, perm_b;
logic phase_q;
/////////////
// Control //
/////////////
// The data requests are fed from the LFSRs. Reseed requests take precedence internally to the
// LFSRs. If there is an outstanding reseed request, the PRNG can keep updating and providing
// pseudo-random data (using the old seed). If the reseeding is taking place, the LFSRs will
// provide fresh pseudo-random data (the new seed) in the next cycle anyway. This means the
// PRNG is always ready to provide new pseudo-random data.
// In the current SCA setup, we don't have sufficient resources to implement the infrastructure
// required for PRNG reseeding (CSRNG, EDN, etc.). Therefore, we skip any reseeding requests if
// the SecSkipPRNGReseeding parameter is set. Performing the reseeding without proper entropy
// provided from CSRNG would result in quickly repeating, fully deterministic PRNG output,
// which prevents meaningful SCA resistance evaluations.
// Create a lint error to reduce the risk of accidentally enabling this feature.
`ASSERT_STATIC_LINT_ERROR(AesSecAllowForcingMasksNonDefault, SecAllowForcingMasks == 0)
if (SecAllowForcingMasks == 0) begin : gen_unused_force_masks
logic unused_force_masks;
assign unused_force_masks = force_masks_i;
end
// PRNG control
assign prng_en = (SecAllowForcingMasks && force_masks_i) ? 1'b0 : data_update_i;
// Create a lint error to reduce the risk of accidentally enabling this feature.
`ASSERT_STATIC_LINT_ERROR(AesSecSkipPRNGReseedingNonDefault, SecSkipPRNGReseeding == 0)
// Width adaption for reseeding interface. We get EntropyWidth bits at a time.
if (ChunkSize == EntropyWidth) begin : gen_counter
// We can reseed chunk by chunk as we get fresh entropy. Need to keep track of which chunk to
// reseed next.
localparam int unsigned ChunkIdxWidth = prim_util_pkg::vbits(NumChunks);
logic [ChunkIdxWidth-1:0] chunk_idx_d, chunk_idx_q;
logic prng_reseed_done;
// Stop requesting entropy once every chunk got reseeded.
assign entropy_req_o = SecSkipPRNGReseeding ? 1'b0 : reseed_req_i;
assign reseed_ack_o = SecSkipPRNGReseeding ? reseed_req_i : prng_reseed_done;
// Counter
assign prng_reseed_done =
(chunk_idx_q == ChunkIdxWidth'(NumChunks - 1)) & entropy_req_o & entropy_ack_i;
assign chunk_idx_d = prng_reseed_done ? '0 :
entropy_req_o && entropy_ack_i ? chunk_idx_q + ChunkIdxWidth'(1) : chunk_idx_q;
always_ff @(posedge clk_i or negedge rst_ni) begin : reg_chunk_idx
if (!rst_ni) begin
chunk_idx_q <= '0;
end else begin
chunk_idx_q <= chunk_idx_d;
end
end
// The entropy input is forwarded to all chunks, we just control the seed enable.
for (genvar c = 0; c < NumChunks; c++) begin : gen_seeds
assign prng_seed[c] = entropy_i;
assign prng_seed_en[c] = (c == chunk_idx_q) ? entropy_req_o & entropy_ack_i : 1'b0;
end
end else begin : gen_packer
// Upsizing of entropy input to correct width for reseeding the full PRNG in one shot.
logic [Width-1:0] seed;
logic seed_valid;
// Stop requesting entropy once the desired amount is available.
assign entropy_req_o = SecSkipPRNGReseeding ? 1'b0 : reseed_req_i & ~seed_valid;
assign reseed_ack_o = SecSkipPRNGReseeding ? reseed_req_i : seed_valid;
prim_packer_fifo #(
.InW ( EntropyWidth ),
.OutW ( Width ),
.ClearOnRead ( 1'b0 )
) u_prim_packer_fifo (
.clk_i ( clk_i ),
.rst_ni ( rst_ni ),
.clr_i ( 1'b0 ), // Not needed.
.wvalid_i ( entropy_ack_i ),
.wdata_i ( entropy_i ),
.wready_o ( ), // Not needed, we're always ready to sink data at this point.
.rvalid_o ( seed_valid ),
.rdata_o ( seed ),
.rready_i ( 1'b1 ), // We're always ready to receive the packed output word.
.depth_o ( ) // Not needed.
);
// Extract chunk seeds. All chunks get reseeded together.
for (genvar c = 0; c < NumChunks; c++) begin : gen_seeds
assign prng_seed[c] = seed[c * ChunkSize +: ChunkSize];
assign prng_seed_en[c] = SecSkipPRNGReseeding ? 1'b0 : seed_valid;
end
end
///////////
// LFSRs //
///////////
// We use multiple LFSR instances each having a width of ChunkSize.
for (genvar c = 0; c < NumChunks; c++) begin : gen_lfsrs
prim_lfsr #(
.LfsrType ( "GAL_XOR" ),
.LfsrDw ( ChunkSize ),
.StateOutDw ( ChunkSize ),
.DefaultSeed ( RndCnstLfsrSeed[c * ChunkSize +: ChunkSize] ),
.StatePermEn ( 1'b0 ),
.NonLinearOut ( 1'b1 )
) u_lfsr_chunk (
.clk_i ( clk_i ),
.rst_ni ( rst_ni ),
.seed_en_i ( prng_seed_en[c] ),
.seed_i ( prng_seed[c] ),
.lfsr_en_i ( prng_en ),
.entropy_i ( '0 ),
.state_o ( prng_state[c] )
);
end
// Add a permutation layer spanning across all LFSRs to break linear shift patterns.
assign prng_b = prng_state;
for (genvar b = 0; b < Width; b++) begin : gen_perm
assign perm_b[b] = prng_b[RndCnstLfsrPerm[b]];
end
assign perm = perm_b;
/////////////
// Outputs //
/////////////
// To achieve independence of input and output masks (the output mask of round X is the input
// mask of round X+1), we assign the scrambled chunks to the output data in alternating fashion.
assign data_o = phase_q ? {perm[0], perm[NumChunks-1:1]} : perm;
always_ff @(posedge clk_i or negedge rst_ni) begin : reg_phase
if (!rst_ni) begin
phase_q <= '0;
end else if (prng_en) begin
phase_q <= ~phase_q;
end
end
/////////////////
// Asssertions //
/////////////////
// Width must be divisible by ChunkSize
`ASSERT_INIT(AesPrngMaskingWidthByChunk, Width % ChunkSize == 0)
// Width must be divisible by 8
`ASSERT_INIT(AesPrngMaskingWidthBy8, Width % 8 == 0)
// the code below is not meant to be synthesized,
// but it is intended to be used in simulation and FPV
`ifndef SYNTHESIS
// Check that the supplied permutation is valid.
logic [Width-1:0] perm_test;
initial begin : p_perm_check
perm_test = '0;
for (int k = 0; k < Width; k++) begin
perm_test[RndCnstLfsrPerm[k]] = 1'b1;
end
// All bit positions must be marked with 1.
`ASSERT_I(PermutationCheck_A, &perm_test)
end
`endif
endmodule