| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| // |
| // A counter module that drives the ROM accesses from the checker. |
| // |
| // This module doesn't need state hardening: an attacker that glitches its behaviour can stall the |
| // chip or read ROM data in the wrong order. Assuming we've picked a key for the ROM that ensures |
| // all words have different values, exploiting a glitch in this module to hide a ROM modification |
| // would still need a pre-image attack on SHA-3. |
| // |
| // RomDepth is the number of words in the ROM. RomTopCount is the number of those words (at the top |
| // of the address space) that are considered part of the expected hash. |
| // |
| // When it comes out of reset, the module starts reading from address zero. Once the reading is |
| // done, it will signal done_o. The surrounding (hardened) design should check that done_o never has |
| // a high -> low transition. |
| // |
| // The read_addr_o signal should be connected to the stateful mux that controls access to ROM. This |
| // mux gives access to the rom_ctrl_counter until done_o is asserted. The data_addr_o signal gives |
| // the address of the ROM word that was just read. |
| // |
| // The data_* signals are used to handshake with KMAC, although the surrounding FSM will step in |
| // once we've got to the top of memory. The counter uses the output buffer on the ROM instance to |
| // hold data and drives rom_addr_o and data_vld_o to make a rdy/vld interface with the ROM output. |
| // This interface should signal things correctly until done_o goes high. data_last_nontop_o is set |
| // on the last word before the top RomTopCount words. |
| // |
| |
| `include "prim_assert.sv" |
| |
| module rom_ctrl_counter |
| import prim_util_pkg::vbits; |
| #( |
| parameter int RomDepth = 16, |
| parameter int RomTopCount = 2 |
| ) ( |
| input clk_i, |
| input rst_ni, |
| |
| output done_o, |
| |
| output [vbits(RomDepth)-1:0] read_addr_o, |
| output read_req_o, |
| |
| output [vbits(RomDepth)-1:0] data_addr_o, |
| |
| input data_rdy_i, |
| output data_last_nontop_o |
| ); |
| |
| // The number of ROM entries that should be hashed. We assume there are at least 2, so that we can |
| // register the data_last_nontop_o signal. |
| localparam int RomNonTopCount = RomDepth - RomTopCount; |
| |
| `ASSERT_INIT(TopCountValid_A, 1 <= RomTopCount && RomTopCount < RomDepth) |
| `ASSERT_INIT(NonTopCountValid_A, 2 <= RomNonTopCount) |
| |
| localparam int AW = vbits(RomDepth); |
| |
| localparam int unsigned TopAddrInt = RomDepth - 1; |
| localparam int unsigned TNTAddrInt = RomNonTopCount - 2; |
| |
| localparam bit [AW-1:0] TopAddr = TopAddrInt[AW-1:0]; |
| localparam bit [AW-1:0] TNTAddr = TNTAddrInt[AW-1:0]; |
| |
| logic go; |
| logic req_q, vld_q; |
| logic [AW-1:0] addr_q, addr_d; |
| logic done_q, done_d; |
| logic last_nontop_q, last_nontop_d; |
| |
| assign done_d = addr_q == TopAddr; |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| done_q <= 1'b0; |
| end else begin |
| done_q <= done_d; |
| end |
| end |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| addr_q <= '0; |
| last_nontop_q <= 1'b0; |
| end else if (go) begin |
| addr_q <= addr_d; |
| last_nontop_q <= last_nontop_d; |
| end |
| end |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| req_q <= 1'b0; |
| vld_q <= 1'b0; |
| end else begin |
| // The first ROM request goes out immediately after reset (once we reach the top of ROM, we |
| // signal done_o, after which the request signal is unused). We could clear it again when we |
| // are done, but there's no need: the mux will switch away from us anyway. |
| req_q <= 1'b1; |
| |
| // ROM data is valid from one cycle after the request goes out. |
| vld_q <= req_q; |
| end |
| end |
| |
| assign go = data_rdy_i & vld_q & ~done_d; |
| |
| assign addr_d = addr_q + {{AW-1{1'b0}}, 1'b1}; |
| assign last_nontop_d = addr_q == TNTAddr; |
| |
| assign done_o = done_q; |
| assign read_addr_o = go ? addr_d : addr_q; |
| assign read_req_o = req_q; |
| assign data_addr_o = addr_q; |
| assign data_last_nontop_o = last_nontop_q; |
| |
| endmodule |