blob: ec764cbefadad08ee54b7f6c78c4af418ad28896 [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"
/**
* Tile-Link UL adapter for SRAM-like devices
*
* - Intentionally omitted BaseAddr in case of multiple memory maps are used in a SoC,
* it means that aliasing can happen if target device size in TL-UL crossbar is bigger
* than SRAM size
* - At most one of EnableDataIntgGen / EnableDataIntgPt can be enabled. However it
* possible for both to be disabled.
* A module can neither generate an integrity response nor pass through any pre-existing
* integrity. This might be the case for non-security critical memories where there is
* no stored integrity AND another entity upstream is already generating returning integrity.
* There is however no case where EnableDataIntgGen and EnableDataIntgPt are both true.
*/
module tlul_adapter_sram
import tlul_pkg::*;
import prim_mubi_pkg::mubi4_t;
#(
parameter int SramAw = 12,
parameter int SramDw = 32, // Must be multiple of the TL width
parameter int Outstanding = 1, // Only one request is accepted
parameter bit ByteAccess = 1, // 1: Enables sub-word write transactions. Note that this
// results in read-modify-write operations for integrity
// re-generation if EnableDataIntgPt is set to 1.
parameter bit ErrOnWrite = 0, // 1: Writes not allowed, automatically error
parameter bit ErrOnRead = 0, // 1: Reads not allowed, automatically error
parameter bit CmdIntgCheck = 0, // 1: Enable command integrity check
parameter bit EnableRspIntgGen = 0, // 1: Generate response integrity
parameter bit EnableDataIntgGen = 0, // 1: Generate response data integrity
parameter bit EnableDataIntgPt = 0, // 1: Passthrough command/response data integrity
parameter bit SecFifoPtr = 0, // 1: Duplicated fifo pointers
localparam int WidthMult = SramDw / top_pkg::TL_DW,
localparam int IntgWidth = tlul_pkg::DataIntgWidth * WidthMult,
localparam int DataOutW = EnableDataIntgPt ? SramDw + IntgWidth : SramDw
) (
input clk_i,
input rst_ni,
// TL-UL interface
input tl_h2d_t tl_i,
output tl_d2h_t tl_o,
// control interface
input mubi4_t en_ifetch_i,
// SRAM interface
output logic req_o,
output mubi4_t req_type_o,
input gnt_i,
output logic we_o,
output logic [SramAw-1:0] addr_o,
output logic [DataOutW-1:0] wdata_o,
output logic [DataOutW-1:0] wmask_o,
output logic intg_error_o,
input [DataOutW-1:0] rdata_i,
input rvalid_i,
input [1:0] rerror_i // 2 bit error [1]: Uncorrectable, [0]: Correctable
);
localparam int SramByte = SramDw/8;
localparam int DataBitWidth = prim_util_pkg::vbits(SramByte);
localparam int WoffsetWidth = (SramByte == top_pkg::TL_DBW) ? 1 :
DataBitWidth - prim_util_pkg::vbits(top_pkg::TL_DBW);
logic error_det; // Internal protocol error checker
logic error_internal; // Internal protocol error checker
logic wr_attr_error;
logic instr_error;
logic wr_vld_error;
logic rd_vld_error;
logic rsp_fifo_error;
logic intg_error;
logic tlul_error;
// integrity check
if (CmdIntgCheck) begin : gen_cmd_intg_check
tlul_cmd_intg_chk u_cmd_intg_chk (
.tl_i(tl_i),
.err_o (intg_error)
);
end else begin : gen_no_cmd_intg_check
assign intg_error = '0;
end
// permanently latch integrity error until reset
logic intg_error_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
intg_error_q <= '0;
end else if (intg_error || rsp_fifo_error) begin
intg_error_q <= 1'b1;
end
end
// integrity error output is permanent and should be used for alert generation
// or other downstream effects
assign intg_error_o = intg_error | rsp_fifo_error | intg_error_q;
// wr_attr_error: Check if the request size, mask are permitted.
// Basic check of size, mask, addr align is done in tlul_err module.
// Here it checks any partial write if ByteAccess isn't allowed.
assign wr_attr_error = (tl_i.a_opcode == PutFullData || tl_i.a_opcode == PutPartialData)
? ((ByteAccess == 0) ?
(tl_i.a_mask != '1 || tl_i.a_size != 2'h2) : 1'b0)
: 1'b0;
// An instruction type transaction is only valid if en_ifetch is enabled
// If the instruction type is completely invalid, also considered an instruction error
assign instr_error = prim_mubi_pkg::mubi4_test_invalid(tl_i.a_user.instr_type) |
(prim_mubi_pkg::mubi4_test_true_strict(tl_i.a_user.instr_type) &
prim_mubi_pkg::mubi4_test_false_loose(en_ifetch_i));
if (ErrOnWrite == 1) begin : gen_no_writes
assign wr_vld_error = tl_i.a_opcode != Get;
end else begin : gen_writes_allowed
assign wr_vld_error = 1'b0;
end
if (ErrOnRead == 1) begin: gen_no_reads
assign rd_vld_error = tl_i.a_opcode == Get;
end else begin : gen_reads_allowed
assign rd_vld_error = 1'b0;
end
// tlul protocol check
tlul_err u_err (
.clk_i,
.rst_ni,
.tl_i(tl_i),
.err_o (tlul_error)
);
// error return is transactional and thus does not used the "latched" intg_err signal
assign error_det = wr_attr_error | wr_vld_error | rd_vld_error | instr_error |
tlul_error | intg_error;
// from sram_byte to adapter logic
tl_h2d_t tl_i_int;
// from adapter logic to sram_byte
tl_d2h_t tl_o_int;
// from sram_byte to rsp_gen
tl_d2h_t tl_out;
// not all parts of tl_i_int are used
logic unused_tl_i_int;
assign unused_tl_i_int = ^tl_i_int;
tlul_rsp_intg_gen #(
.EnableRspIntgGen(EnableRspIntgGen),
.EnableDataIntgGen(EnableDataIntgGen)
) u_rsp_gen (
.tl_i(tl_out),
.tl_o
);
// byte handling for integrity
tlul_sram_byte #(
.EnableIntg(ByteAccess & EnableDataIntgPt & !ErrOnWrite),
.Outstanding(Outstanding)
) u_sram_byte (
.clk_i,
.rst_ni,
.tl_i,
.tl_o(tl_out),
.tl_sram_o(tl_i_int),
.tl_sram_i(tl_o_int),
.error_i(error_det),
.error_o(error_internal)
);
typedef struct packed {
logic [top_pkg::TL_DBW-1:0] mask ; // Byte mask within the TL-UL word
logic [WoffsetWidth-1:0] woffset ; // Offset of the TL-UL word within the SRAM word
} sram_req_t ;
typedef enum logic [1:0] {
OpWrite,
OpRead,
OpUnknown
} req_op_e ;
typedef struct packed {
req_op_e op ;
logic error ;
prim_mubi_pkg::mubi4_t instr_type;
logic [top_pkg::TL_SZW-1:0] size ;
logic [top_pkg::TL_AIW-1:0] source ;
} req_t ;
typedef struct packed {
logic [top_pkg::TL_DW-1:0] data ;
logic [DataIntgWidth-1:0] data_intg ;
logic error ;
} rsp_t ;
localparam int SramReqFifoWidth = $bits(sram_req_t) ;
localparam int ReqFifoWidth = $bits(req_t) ;
localparam int RspFifoWidth = $bits(rsp_t) ;
// FIFO signal in case OutStand is greater than 1
// If request is latched, {write, source} is pushed to req fifo.
// Req fifo is popped when D channel is acknowledged (v & r)
// D channel valid is asserted if it is write request or rsp fifo not empty if read.
logic reqfifo_wvalid, reqfifo_wready;
logic reqfifo_rvalid, reqfifo_rready;
req_t reqfifo_wdata, reqfifo_rdata;
logic sramreqfifo_wvalid, sramreqfifo_wready;
logic sramreqfifo_rready;
sram_req_t sramreqfifo_wdata, sramreqfifo_rdata;
logic rspfifo_wvalid, rspfifo_wready;
logic rspfifo_rvalid, rspfifo_rready;
rsp_t rspfifo_wdata, rspfifo_rdata;
logic a_ack, d_ack, sram_ack;
assign a_ack = tl_i_int.a_valid & tl_o_int.a_ready ;
assign d_ack = tl_o_int.d_valid & tl_i_int.d_ready ;
assign sram_ack = req_o & gnt_i ;
// Valid handling
logic d_valid, d_error;
always_comb begin
d_valid = 1'b0;
if (reqfifo_rvalid) begin
if (reqfifo_rdata.error) begin
// Return error response. Assume no request went out to SRAM
d_valid = 1'b1;
end else if (reqfifo_rdata.op == OpRead) begin
d_valid = rspfifo_rvalid;
end else begin
// Write without error
d_valid = 1'b1;
end
end else begin
d_valid = 1'b0;
end
end
always_comb begin
d_error = 1'b0;
if (reqfifo_rvalid) begin
if (reqfifo_rdata.op == OpRead) begin
d_error = rspfifo_rdata.error | reqfifo_rdata.error;
end else begin
d_error = reqfifo_rdata.error;
end
end else begin
d_error = 1'b0;
end
end
logic vld_rd_rsp;
assign vld_rd_rsp = d_valid & reqfifo_rvalid & rspfifo_rvalid & (reqfifo_rdata.op == OpRead);
// If the response data is not valid, we set it to an illegal blanking value which is determined
// by whether the current transaction is an instruction fetch or a regular read operation.
logic [top_pkg::TL_DW-1:0] error_blanking_data;
assign error_blanking_data = (prim_mubi_pkg::mubi4_test_true_strict(reqfifo_rdata.instr_type)) ?
DataWhenInstrError :
DataWhenError;
// Since DataWhenInstrError and DataWhenError can be arbitrary parameters
// we statically calculate the correct integrity values for these parameters here so that
// they do not have to be supplied externally.
logic [top_pkg::TL_DW-1:0] unused_instr, unused_data;
logic [DataIntgWidth-1:0] error_instr_integ, error_data_integ;
tlul_data_integ_enc u_tlul_data_integ_enc_instr (
.data_i(DataMaxWidth'(DataWhenInstrError)),
.data_intg_o({error_instr_integ, unused_instr})
);
tlul_data_integ_enc u_tlul_data_integ_enc_data (
.data_i(DataMaxWidth'(DataWhenError)),
.data_intg_o({error_data_integ, unused_data})
);
logic [DataIntgWidth-1:0] error_blanking_integ;
assign error_blanking_integ = (prim_mubi_pkg::mubi4_test_true_strict(reqfifo_rdata.instr_type)) ?
error_instr_integ :
error_data_integ;
logic [top_pkg::TL_DW-1:0] d_data;
assign d_data = (vld_rd_rsp & ~d_error) ? rspfifo_rdata.data // valid read
: error_blanking_data; // write or TL-UL error
// If this a write response with data fields set to 0, we have to set all ECC bits correctly
// since we are using an inverted Hsiao code.
logic [DataIntgWidth-1:0] data_intg;
assign data_intg = (vld_rd_rsp && reqfifo_rdata.error) ? error_blanking_integ : // TL-UL error
(vld_rd_rsp) ? rspfifo_rdata.data_intg : // valid read
prim_secded_pkg::SecdedInv3932ZeroEcc; // valid write
assign tl_o_int = '{
d_valid : d_valid ,
d_opcode : (d_valid && reqfifo_rdata.op != OpRead) ? AccessAck : AccessAckData,
d_param : '0,
d_size : (d_valid) ? reqfifo_rdata.size : '0,
d_source : (d_valid) ? reqfifo_rdata.source : '0,
d_sink : 1'b0,
d_data : d_data,
d_user : '{default: '0, data_intg: data_intg},
d_error : d_valid && d_error,
a_ready : (gnt_i | error_internal) & reqfifo_wready & sramreqfifo_wready
};
// a_ready depends on the FIFO full condition and grant from SRAM (or SRAM arbiter)
// assemble response, including read response, write response, and error for unsupported stuff
// Output to SRAM:
// Generate request only when no internal error occurs. If error occurs, the request should be
// dropped and returned error response to the host. So, error to be pushed to reqfifo.
// In this case, it is assumed the request is granted (may cause ordering issue later?)
assign req_o = tl_i_int.a_valid & reqfifo_wready & ~error_internal;
assign req_type_o = tl_i_int.a_user.instr_type;
assign we_o = tl_i_int.a_valid & (tl_i_int.a_opcode inside {PutFullData, PutPartialData});
assign addr_o = (tl_i_int.a_valid) ? tl_i_int.a_address[DataBitWidth+:SramAw] : '0;
// Support SRAMs wider than the TL-UL word width by mapping the parts of the
// TL-UL address which are more fine-granular than the SRAM width to the
// SRAM write mask.
logic [WoffsetWidth-1:0] woffset;
if (top_pkg::TL_DW != SramDw) begin : gen_wordwidthadapt
assign woffset = tl_i_int.a_address[DataBitWidth-1:prim_util_pkg::vbits(top_pkg::TL_DBW)];
end else begin : gen_no_wordwidthadapt
assign woffset = '0;
end
// The size of the data/wmask depends on whether passthrough integrity is enabled.
// If passthrough integrity is enabled, the data is concatenated with the integrity passed through
// the user bits. Otherwise, it is the data only.
localparam int DataWidth = EnableDataIntgPt ? top_pkg::TL_DW + DataIntgWidth : top_pkg::TL_DW;
// Final combined wmask / wdata
logic [WidthMult-1:0][DataWidth-1:0] wmask_combined;
logic [WidthMult-1:0][DataWidth-1:0] wdata_combined;
// Original tlul portion
logic [WidthMult-1:0][top_pkg::TL_DW-1:0] wmask_int;
logic [WidthMult-1:0][top_pkg::TL_DW-1:0] wdata_int;
// Integrity portion
logic [WidthMult-1:0][DataIntgWidth-1:0] wmask_intg;
logic [WidthMult-1:0][DataIntgWidth-1:0] wdata_intg;
always_comb begin
wmask_int = '0;
wdata_int = '0;
if (tl_i_int.a_valid) begin
for (int i = 0 ; i < top_pkg::TL_DW/8 ; i++) begin
wmask_int[woffset][8*i +: 8] = {8{tl_i_int.a_mask[i]}};
wdata_int[woffset][8*i +: 8] = (tl_i_int.a_mask[i] && we_o) ? tl_i_int.a_data[8*i+:8] : '0;
end
end
end
always_comb begin
wmask_intg = '0;
wdata_intg = '0;
if (tl_i_int.a_valid) begin
wmask_intg[woffset] = {DataIntgWidth{1'b1}};
wdata_intg[woffset] = tl_i_int.a_user.data_intg;
end
end
for (genvar i = 0; i < WidthMult; i++) begin : gen_write_output
if (EnableDataIntgPt) begin : gen_combined_output
assign wmask_combined[i] = {wmask_intg[i], wmask_int[i]};
assign wdata_combined[i] = {wdata_intg[i], wdata_int[i]};
end else begin : gen_ft_output
logic unused_w;
assign wmask_combined[i] = wmask_int[i];
assign wdata_combined[i] = wdata_int[i];
assign unused_w = |wmask_intg & |wdata_intg;
end
end
assign wmask_o = wmask_combined;
assign wdata_o = wdata_combined;
assign reqfifo_wvalid = a_ack ; // Push to FIFO only when granted
assign reqfifo_wdata = '{
op: (tl_i_int.a_opcode != Get) ? OpWrite : OpRead, // To return AccessAck for opcode error
error: error_internal,
instr_type: tl_i_int.a_user.instr_type,
size: tl_i_int.a_size,
source: tl_i_int.a_source
}; // Store the request only. Doesn't have to store data
assign reqfifo_rready = d_ack ;
// push together with ReqFIFO, pop upon returning read
assign sramreqfifo_wdata = '{
mask : tl_i_int.a_mask,
woffset : woffset
};
assign sramreqfifo_wvalid = sram_ack & ~we_o;
assign sramreqfifo_rready = rspfifo_wvalid;
assign rspfifo_wvalid = rvalid_i & reqfifo_rvalid;
// Make sure only requested bytes are forwarded
logic [WidthMult-1:0][DataWidth-1:0] rdata_reshaped;
logic [DataWidth-1:0] rdata_tlword;
// This just changes the array format so that the correct word can be selected by indexing.
assign rdata_reshaped = rdata_i;
if (EnableDataIntgPt) begin : gen_no_rmask
always_comb begin
// If the read mask is set to zero, all read data is zeroed out by the mask.
// We have to set the ECC bits accordingly since we are using an inverted Hsiao code.
rdata_tlword = prim_secded_pkg::SecdedInv3932ZeroWord;
// Otherwise, if at least one mask bit is nonzero, we are passing through the integrity.
// In that case we need to feed back the entire word since otherwise the integrity
// will not calculate correctly.
if (|sramreqfifo_rdata.mask) begin
// Select correct word.
rdata_tlword = rdata_reshaped[sramreqfifo_rdata.woffset];
end
end
end else begin : gen_rmask
logic [DataWidth-1:0] rmask;
always_comb begin
rmask = '0;
for (int i = 0 ; i < top_pkg::TL_DW/8 ; i++) begin
rmask[8*i +: 8] = {8{sramreqfifo_rdata.mask[i]}};
end
end
// Select correct word and mask it.
assign rdata_tlword = rdata_reshaped[sramreqfifo_rdata.woffset] & rmask;
end
assign rspfifo_wdata = '{
data : rdata_tlword[top_pkg::TL_DW-1:0],
data_intg : EnableDataIntgPt ? rdata_tlword[DataWidth-1 -: DataIntgWidth] : '0,
error : rerror_i[1] // Only care for Uncorrectable error
};
assign rspfifo_rready = (reqfifo_rdata.op == OpRead & ~reqfifo_rdata.error)
? reqfifo_rready : 1'b0 ;
// This module only cares about uncorrectable errors.
logic unused_rerror;
assign unused_rerror = rerror_i[0];
// FIFO instance: REQ, RSP
// ReqFIFO is to store the Access type to match to the Response data.
// For instance, SRAM accepts the write request but doesn't return the
// acknowledge. In this case, it may be hard to determine when the D
// response for the write data should send out if reads/writes are
// interleaved. So, to make it in-order (even TL-UL allows out-of-order
// responses), storing the request is necessary. And if the read entry
// is write op, it is safe to return the response right away. If it is
// read reqeust, then D response is waiting until read data arrives.
prim_fifo_sync #(
.Width (ReqFifoWidth),
.Pass (1'b0),
.Depth (Outstanding)
) u_reqfifo (
.clk_i,
.rst_ni,
.clr_i (1'b0),
.wvalid_i(reqfifo_wvalid),
.wready_o(reqfifo_wready),
.wdata_i (reqfifo_wdata),
.rvalid_o(reqfifo_rvalid),
.rready_i(reqfifo_rready),
.rdata_o (reqfifo_rdata),
.full_o (),
.depth_o (),
.err_o ()
);
// sramreqfifo:
// While the ReqFIFO holds the request until it is sent back via TL-UL, the
// sramreqfifo only needs to hold the mask and word offset until the read
// data returns from memory.
prim_fifo_sync #(
.Width (SramReqFifoWidth),
.Pass (1'b0),
.Depth (Outstanding)
) u_sramreqfifo (
.clk_i,
.rst_ni,
.clr_i (1'b0),
.wvalid_i(sramreqfifo_wvalid),
.wready_o(sramreqfifo_wready),
.wdata_i (sramreqfifo_wdata),
.rvalid_o(),
.rready_i(sramreqfifo_rready),
.rdata_o (sramreqfifo_rdata),
.full_o (),
.depth_o (),
.err_o ()
);
// Rationale having #Outstanding depth in response FIFO.
// In normal case, if the host or the crossbar accepts the response data,
// response FIFO isn't needed. But if in any case it has a chance to be
// back pressured, the response FIFO should store the returned data not to
// lose the data from the SRAM interface. Remember, SRAM interface doesn't
// have back-pressure signal such as read_ready.
prim_fifo_sync #(
.Width (RspFifoWidth),
.Pass (1'b1),
.Depth (Outstanding),
.Secure (SecFifoPtr)
) u_rspfifo (
.clk_i,
.rst_ni,
.clr_i (1'b0),
.wvalid_i(rspfifo_wvalid),
.wready_o(rspfifo_wready),
.wdata_i (rspfifo_wdata),
.rvalid_o(rspfifo_rvalid),
.rready_i(rspfifo_rready),
.rdata_o (rspfifo_rdata),
.full_o (),
.depth_o (),
.err_o (rsp_fifo_error)
);
// below assertion fails when SRAM rvalid is asserted even though ReqFifo is empty
`ASSERT(rvalidHighReqFifoEmpty, rvalid_i |-> reqfifo_rvalid)
// below assertion fails when outstanding value is too small (SRAM rvalid is asserted
// even though the RspFifo is full)
`ASSERT(rvalidHighWhenRspFifoFull, rvalid_i |-> rspfifo_wready)
// If both ErrOnWrite and ErrOnRead are set, this block is useless
`ASSERT_INIT(adapterNoReadOrWrite, (ErrOnWrite & ErrOnRead) == 0)
`ASSERT_INIT(SramDwHasByteGranularity_A, SramDw % 8 == 0)
`ASSERT_INIT(SramDwIsMultipleOfTlulWidth_A, SramDw % top_pkg::TL_DW == 0)
// These parameter options cannot both be true at the same time
`ASSERT_INIT(DataIntgOptions_A, ~(EnableDataIntgGen & EnableDataIntgPt))
// make sure outputs are defined
`ASSERT_KNOWN(TlOutKnown_A, tl_o.d_valid)
`ASSERT_KNOWN_IF(TlOutPayloadKnown_A, tl_o, tl_o.d_valid)
`ASSERT_KNOWN(ReqOutKnown_A, req_o )
`ASSERT_KNOWN(WeOutKnown_A, we_o )
`ASSERT_KNOWN(AddrOutKnown_A, addr_o )
`ASSERT_KNOWN(WdataOutKnown_A, wdata_o)
`ASSERT_KNOWN(WmaskOutKnown_A, wmask_o)
endmodule