blob: fc49a67ba3404e5f815550f4c8da0bc78da80c96 [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"
/**
* OpenTitan Big Number Accelerator (OTBN)
*/
module otbn
import prim_alert_pkg::*;
import otbn_pkg::*;
import otbn_reg_pkg::*;
#(
parameter regfile_e RegFile = RegFileFF,
parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}}
) (
input clk_i,
input rst_ni,
input tlul_pkg::tl_h2d_t tl_i,
output tlul_pkg::tl_d2h_t tl_o,
// Inter-module signals
output logic idle_o,
// Interrupts
output logic intr_done_o,
output logic intr_err_o,
// Alerts
input prim_alert_pkg::alert_rx_t [NumAlerts-1:0] alert_rx_i,
output prim_alert_pkg::alert_tx_t [NumAlerts-1:0] alert_tx_o
// CSRNG interface
// TODO: Needs to be connected to RNG distribution network (#2638)
);
import prim_util_pkg::vbits;
// The OTBN_*_SIZE parameters are auto-generated by regtool and come from the
// bus window sizes; they are given in bytes.
localparam int ImemSizeByte = otbn_reg_pkg::OTBN_IMEM_SIZE;
localparam int DmemSizeByte = otbn_reg_pkg::OTBN_DMEM_SIZE;
localparam int ImemAddrWidth = vbits(ImemSizeByte);
localparam int DmemAddrWidth = vbits(DmemSizeByte);
`ifdef OTBN_MODEL
localparam int OTBNModel = 1;
`else
localparam int OTBNModel = 0;
`endif
logic start;
logic busy_d, busy_q;
logic done;
logic err_valid;
logic [31:0] err_code;
logic [ImemAddrWidth-1:0] start_addr;
otbn_reg2hw_t reg2hw;
otbn_hw2reg_t hw2reg;
// Bus device windows, as specified in otbn.hjson
typedef enum int {
TlWinImem = 0,
TlWinDmem = 1
} tl_win_e;
tlul_pkg::tl_h2d_t tl_win_h2d [2];
tlul_pkg::tl_d2h_t tl_win_d2h [2];
// Inter-module signals ======================================================
// TODO: Better define what "idle" means -- only the core, or also the
// register interface?
assign idle_o = ~busy_q | ~start;
// Interrupts ================================================================
prim_intr_hw #(
.Width(1)
) u_intr_hw_done (
.clk_i,
.rst_ni,
.event_intr_i (done),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.done.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.done.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.done.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.done.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.done.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.done.d),
.intr_o (intr_done_o)
);
prim_intr_hw #(
.Width(1)
) u_intr_hw_err (
.clk_i,
.rst_ni,
.event_intr_i (err_valid),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.err.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.err.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.err.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.err.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.err.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.err.d),
.intr_o (intr_err_o)
);
// Registers =================================================================
otbn_reg_top u_reg (
.clk_i,
.rst_ni,
.tl_i,
.tl_o,
.tl_win_o (tl_win_h2d),
.tl_win_i (tl_win_d2h),
.reg2hw,
.hw2reg,
.devmode_i (1'b1)
);
// CMD register
assign start = reg2hw.cmd.start.qe & reg2hw.cmd.start.q;
// STATUS register
assign hw2reg.status.busy.d = busy_q;
assign hw2reg.status.dummy.d = 1'b0;
// ERR_CODE register
assign hw2reg.err_code.de = err_valid;
assign hw2reg.err_code.d = err_code;
// START_ADDR register
assign start_addr = reg2hw.start_addr.q[ImemAddrWidth-1:0];
// Errors ====================================================================
// err_valid goes high if there is a new error this cycle. This causes the
// register block to take a new error code (stored as ERR_CODE) and triggers
// an interrupt. To ensure software on the host CPU only sees the first event
// in a series, err_valid is squashed if there is an existing error. Software
// should read the ERR_CODE register before clearing the interrupt to avoid
// race conditions.
assign err_valid = ~reg2hw.intr_state.err.q &
(1'b0); // TODO: OR error signals here.
always_comb begin
err_code = ErrCodeNoError;
unique case (1'b1)
// TODO: Add more errors here.
default: begin
err_code = ErrCodeNoError;
end
endcase
end
// Instruction Memory (IMEM) =================================================
localparam int ImemSizeWords = ImemSizeByte / 4;
localparam int ImemIndexWidth = vbits(ImemSizeWords);
// Access select to IMEM: core (1), or bus (0)
logic imem_access_core;
logic imem_req;
logic imem_write;
logic [ImemIndexWidth-1:0] imem_index;
logic [31:0] imem_wdata;
logic [31:0] imem_wmask;
logic [31:0] imem_rdata;
logic imem_rvalid;
logic [1:0] imem_rerror;
logic imem_req_core;
logic imem_write_core;
logic [ImemIndexWidth-1:0] imem_index_core;
logic [31:0] imem_wdata_core;
logic [31:0] imem_rdata_core;
logic imem_rvalid_core;
logic [1:0] imem_rerror_core;
logic imem_req_bus;
logic imem_write_bus;
logic [ImemIndexWidth-1:0] imem_index_bus;
logic [31:0] imem_wdata_bus;
logic [31:0] imem_wmask_bus;
logic [31:0] imem_rdata_bus;
logic imem_rvalid_bus;
logic [1:0] imem_rerror_bus;
logic [ImemAddrWidth-1:0] imem_addr_core;
assign imem_index_core = imem_addr_core[ImemAddrWidth-1:2];
logic [1:0] unused_imem_addr_core_wordbits;
assign unused_imem_addr_core_wordbits = imem_addr_core[1:0];
prim_ram_1p_adv #(
.Width (32),
.Depth (ImemSizeWords),
.DataBitsPerMask (32), // Write masks are not supported.
.CfgW (8)
) u_imem (
.clk_i,
.rst_ni,
.req_i (imem_req),
.write_i (imem_write),
.addr_i (imem_index),
.wdata_i (imem_wdata),
.wmask_i (imem_wmask),
.rdata_o (imem_rdata),
.rvalid_o (imem_rvalid),
.rerror_o (imem_rerror),
.cfg_i ('0)
);
// IMEM access from main TL-UL bus
logic imem_gnt_bus;
assign imem_gnt_bus = imem_req_bus & ~imem_access_core;
tlul_adapter_sram #(
.SramAw (ImemIndexWidth),
.SramDw (32),
.Outstanding (1),
.ByteAccess (0),
.ErrOnRead (0)
) u_tlul_adapter_sram_imem (
.clk_i,
.rst_ni,
.tl_i (tl_win_h2d[TlWinImem]),
.tl_o (tl_win_d2h[TlWinImem]),
.req_o (imem_req_bus ),
.gnt_i (imem_gnt_bus ),
.we_o (imem_write_bus ),
.addr_o (imem_index_bus ),
.wdata_o (imem_wdata_bus ),
.wmask_o (imem_wmask_bus ),
.rdata_i (imem_rdata_bus ),
.rvalid_i (imem_rvalid_bus),
.rerror_i (imem_rerror_bus)
);
// Mux core and bus access into IMEM
assign imem_access_core = busy_q;
assign imem_req = imem_access_core ? imem_req_core : imem_req_bus;
assign imem_write = imem_access_core ? imem_write_core : imem_write_bus;
assign imem_index = imem_access_core ? imem_index_core : imem_index_bus;
assign imem_wdata = imem_access_core ? imem_wdata_core : imem_wdata_bus;
// The instruction memory only supports 32b word writes, so we hardcode its
// wmask here.
//
// Since this could cause confusion if the bus tried to do a partial write
// (which wasn't caught in the TLUL adapter for some reason), we assert that
// the wmask signal from the bus is indeed '1 when it requests a write. We
// don't have the corresponding check for writes from the core because the
// core cannot perform writes (and has no imem_wmask_o port).
assign imem_wmask = 32'hFFFFFFFF;
`ASSERT(ImemWmaskBusIsFullWord_A,
imem_req_bus && imem_write_bus |-> imem_wmask_bus == 32'hFFFFFFFF)
// Explicitly tie off bus interface during core operation to avoid leaking
// the currently executed instruction from IMEM through the bus
// unintentionally.
assign imem_rdata_bus = !imem_access_core ? imem_rdata : 32'b0;
assign imem_rdata_core = imem_rdata;
assign imem_rvalid_bus = !imem_access_core ? imem_rvalid : 1'b0;
assign imem_rvalid_core = imem_access_core ? imem_rvalid : 1'b0;
// Since rerror depends on rvalid we could save this mux, but could
// potentially leak rerror to the bus. Err on the side of caution.
assign imem_rerror_bus = !imem_access_core ? imem_rerror : 2'b00;
assign imem_rerror_core = imem_rerror;
// Data Memory (DMEM) ========================================================
localparam int DmemSizeWords = DmemSizeByte / (WLEN / 8);
localparam int DmemIndexWidth = vbits(DmemSizeWords);
// Access select to DMEM: core (1), or bus (0)
logic dmem_access_core;
logic dmem_req;
logic dmem_write;
logic [DmemIndexWidth-1:0] dmem_index;
logic [WLEN-1:0] dmem_wdata;
logic [WLEN-1:0] dmem_wmask;
logic [WLEN-1:0] dmem_rdata;
logic dmem_rvalid;
logic [1:0] dmem_rerror;
logic dmem_req_core;
logic dmem_write_core;
logic [DmemIndexWidth-1:0] dmem_index_core;
logic [WLEN-1:0] dmem_wdata_core;
logic [WLEN-1:0] dmem_wmask_core;
logic [WLEN-1:0] dmem_rdata_core;
logic dmem_rvalid_core;
logic [1:0] dmem_rerror_core;
logic dmem_req_bus;
logic dmem_write_bus;
logic [DmemIndexWidth-1:0] dmem_index_bus;
logic [WLEN-1:0] dmem_wdata_bus;
logic [WLEN-1:0] dmem_wmask_bus;
logic [WLEN-1:0] dmem_rdata_bus;
logic dmem_rvalid_bus;
logic [1:0] dmem_rerror_bus;
logic [DmemAddrWidth-1:0] dmem_addr_core;
assign dmem_index_core = dmem_addr_core[DmemAddrWidth-1:DmemAddrWidth-DmemIndexWidth];
prim_ram_1p_adv #(
.Width (WLEN),
.Depth (DmemSizeWords),
.DataBitsPerMask (32), // 32b write masks for 32b word writes from bus
.CfgW (8)
) u_dmem (
.clk_i,
.rst_ni,
.req_i (dmem_req),
.write_i (dmem_write),
.addr_i (dmem_index),
.wdata_i (dmem_wdata),
.wmask_i (dmem_wmask),
.rdata_o (dmem_rdata),
.rvalid_o (dmem_rvalid),
.rerror_o (dmem_rerror),
.cfg_i ('0)
);
// DMEM access from main TL-UL bus
logic dmem_gnt_bus;
assign dmem_gnt_bus = dmem_req_bus & ~dmem_access_core;
tlul_adapter_sram #(
.SramAw (DmemIndexWidth),
.SramDw (WLEN),
.Outstanding (1),
.ByteAccess (0),
.ErrOnRead (0)
) u_tlul_adapter_sram_dmem (
.clk_i,
.rst_ni,
.tl_i (tl_win_h2d[TlWinDmem]),
.tl_o (tl_win_d2h[TlWinDmem]),
.req_o (dmem_req_bus ),
.gnt_i (dmem_gnt_bus ),
.we_o (dmem_write_bus ),
.addr_o (dmem_index_bus ),
.wdata_o (dmem_wdata_bus ),
.wmask_o (dmem_wmask_bus ),
.rdata_i (dmem_rdata_bus ),
.rvalid_i (dmem_rvalid_bus),
.rerror_i (dmem_rerror_bus)
);
// Mux core and bus access into dmem
assign dmem_access_core = busy_q;
assign dmem_req = dmem_access_core ? dmem_req_core : dmem_req_bus;
assign dmem_write = dmem_access_core ? dmem_write_core : dmem_write_bus;
assign dmem_wmask = dmem_access_core ? dmem_wmask_core : dmem_wmask_bus;
assign dmem_index = dmem_access_core ? dmem_index_core : dmem_index_bus;
assign dmem_wdata = dmem_access_core ? dmem_wdata_core : dmem_wdata_bus;
// Explicitly tie off bus interface during core operation to avoid leaking
// DMEM data through the bus unintentionally.
assign dmem_rdata_bus = !dmem_access_core ? dmem_rdata : '0;
assign dmem_rdata_core = dmem_rdata;
assign dmem_rvalid_bus = !dmem_access_core ? dmem_rvalid : 1'b0;
assign dmem_rvalid_core = dmem_access_core ? dmem_rvalid : 1'b0;
// Since rerror depends on rvalid we could save this mux, but could
// potentially leak rerror to the bus. Err on the side of caution.
assign dmem_rerror_bus = !dmem_access_core ? dmem_rerror : 2'b00;
assign dmem_rerror_core = dmem_rerror;
// Alerts ====================================================================
logic [NumAlerts-1:0] alerts;
assign alerts[AlertImemUncorrectable] = imem_rerror[1];
assign alerts[AlertDmemUncorrectable] = dmem_rerror[1];
assign alerts[AlertRegUncorrectable] = 1'b0; // TODO: Implement
for (genvar i = 0; i < NumAlerts; i++) begin: gen_alert_tx
prim_alert_sender #(
.AsyncOn(AlertAsyncOn[i])
) i_prim_alert_sender (
.clk_i,
.rst_ni,
.alert_i (alerts[i] ),
.alert_rx_i (alert_rx_i[i]),
.alert_tx_o (alert_tx_o[i])
);
end
// OTBN Core =================================================================
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
busy_q <= 1'b0;
end else begin
busy_q <= busy_d;
end
end
assign busy_d = (busy_q | start) & ~done;
if (OTBNModel) begin : gen_impl_model
localparam ImemScope = "..u_imem.u_mem.gen_generic.u_impl_generic";
localparam DmemScope = "..u_dmem.u_mem.gen_generic.u_impl_generic";
otbn_core_model #(
.DmemSizeByte(DmemSizeByte),
.ImemSizeByte(ImemSizeByte),
.DmemScope(DmemScope),
.ImemScope(ImemScope),
.DesignScope("")
) u_otbn_core_model (
.clk_i,
.rst_ni,
.start_i (start),
.done_o (done),
.start_addr_i (start_addr)
);
end else begin : gen_impl_rtl
otbn_core #(
.RegFile(RegFile),
.DmemSizeByte(DmemSizeByte),
.ImemSizeByte(ImemSizeByte)
) u_otbn_core (
.clk_i,
.rst_ni,
.start_i (start),
.done_o (done),
.start_addr_i (start_addr),
.imem_req_o (imem_req_core),
.imem_addr_o (imem_addr_core),
.imem_wdata_o (imem_wdata_core),
.imem_rdata_i (imem_rdata_core),
.imem_rvalid_i (imem_rvalid_core),
.imem_rerror_i (imem_rerror_core),
.dmem_req_o (dmem_req_core),
.dmem_write_o (dmem_write_core),
.dmem_addr_o (dmem_addr_core),
.dmem_wdata_o (dmem_wdata_core),
.dmem_wmask_o (dmem_wmask_core),
.dmem_rdata_i (dmem_rdata_core),
.dmem_rvalid_i (dmem_rvalid_core),
.dmem_rerror_i (dmem_rerror_core)
);
end
// The core can never signal a write to IMEM
assign imem_write_core = 1'b0;
// LFSR ======================================================================
// TODO: Potentially insert local LFSR, or use output from RNG distribution
// network directly, depending on availability. Revisit once CSRNG interface
// is known (#2638).
// Asserts ===================================================================
// All outputs should be known value after reset
`ASSERT_KNOWN(TlODValidKnown_A, tl_o.d_valid)
`ASSERT_KNOWN(TlOAReadyKnown_A, tl_o.a_ready)
`ASSERT_KNOWN(IntrDoneOKnown_A, intr_done_o)
`ASSERT_KNOWN(IntrErrOKnown_A, intr_err_o)
`ASSERT_KNOWN(AlertTxOKnown_A, alert_tx_o)
`ASSERT_KNOWN(IdleOKnown_A, idle_o)
endmodule