../src/lowrisc_tlul_adapter_sram_0.1/rtl/tlul_adapter_sram.sv Cov: 99.4%

   1: // Copyright lowRISC contributors.
   2: // Licensed under the Apache License, Version 2.0, see LICENSE for details.
   3: // SPDX-License-Identifier: Apache-2.0
   4: 
   5: /**
   6:  * Tile-Link UL adapter for SRAM-like devices
   7:  *
   8:  * - Intentionally omitted BaseAddr in case of multiple memory maps are used in a SoC,
   9:  *   it means that aliasing can happen if target slave size in TL-UL crossbar is bigger
  10:  *   than SRAM size
  11:  */
  12: 
  13: `include "prim_assert.sv"
  14: 
  15: module tlul_adapter_sram #(
  16:   parameter int SramAw      = 12,
  17:   parameter int SramDw      = 32, // Current version supports TL-UL width only
  18:   parameter int Outstanding = 1,  // Only one request is accepted
  19:   parameter bit ByteAccess  = 1,  // 1: true, 0: false
  20:   parameter bit ErrOnWrite  = 0,  // 1: Writes not allowed, automatically error
  21:   parameter bit ErrOnRead   = 0   // 1: Reads not allowed, automatically error
  22: ) (
  23:   input   clk_i,
  24:   input   rst_ni,
  25: 
  26:   // TL-UL interface
  27:   input   tlul_pkg::tl_h2d_t  tl_i,
  28:   output  tlul_pkg::tl_d2h_t  tl_o,
  29: 
  30:   // SRAM interface
  31:   output logic              req_o,
  32:   input                     gnt_i,
  33:   output logic              we_o,
  34:   output logic [SramAw-1:0] addr_o,
  35:   output logic [SramDw-1:0] wdata_o,
  36:   output logic [SramDw-1:0] wmask_o,
  37:   input        [SramDw-1:0] rdata_i,
  38:   input                     rvalid_i,
  39:   input        [1:0]        rerror_i // 2 bit error [1]: Uncorrectable, [0]: Correctable
  40: );
  41: 
  42:   import tlul_pkg::*;
  43: 
  44:   localparam int SramByte = SramDw/8; // TODO: Fatal if SramDw isn't multiple of 8
  45:   localparam int DataBitWidth = $clog2(SramByte);
  46: 
  47:   typedef enum logic [1:0] {
  48:     OpWrite,
  49:     OpRead,
  50:     OpUnknown
  51:   } req_op_e ;
  52: 
  53:   typedef struct packed {
  54:     req_op_e                    op ;
  55:     logic                       error ;
  56:     logic [top_pkg::TL_SZW-1:0] size ;
  57:     logic [top_pkg::TL_AIW-1:0] source ;
  58:   } req_t ;
  59: 
  60:   typedef struct packed {
  61:     logic [SramDw-1:0] data ;
  62:     logic              error ;
  63:   } rsp_t ;
  64: 
  65:   localparam int ReqFifoWidth = $bits(req_t) ;
  66:   localparam int RspFifoWidth = $bits(rsp_t) ;
  67: 
  68:   // FIFO signal in case OutStand is greater than 1
  69:   // If request is latched, {write, source} is pushed to req fifo.
  70:   // Req fifo is popped when D channel is acknowledged (v & r)
  71:   // D channel valid is asserted if it is write request or rsp fifo not empty if read.
  72:   logic reqfifo_wvalid, reqfifo_wready;
  73:   logic reqfifo_rvalid, reqfifo_rready;
  74:   req_t reqfifo_wdata,  reqfifo_rdata;
  75: 
  76:   logic [top_pkg::TL_DBW-1:0] maskfifo_wdata, maskfifo_rdata;
  77:   logic maskfifo_wvalid, maskfifo_wready;
  78:   logic maskfifo_rready;
  79: 
  80:   logic rspfifo_wvalid, rspfifo_wready;
  81:   logic rspfifo_rvalid, rspfifo_rready;
  82:   rsp_t rspfifo_wdata,  rspfifo_rdata;
  83: 
  84:   logic error_internal; // Internal protocol error checker
  85:   logic wr_attr_error;
  86:   logic wr_vld_error;
  87:   logic rd_vld_error;
  88:   logic tlul_error;     // Error from `tlul_err` module
  89: 
  90:   logic a_ack, d_ack, sram_ack;
  91:   assign a_ack    = tl_i.a_valid & tl_o.a_ready ;
  92:   assign d_ack    = tl_o.d_valid & tl_i.d_ready ;
  93:   assign sram_ack = req_o        & gnt_i ;
  94: 
  95:   // Valid handling
  96:   logic d_valid, d_error;
  97:   always_comb begin
  98:     d_valid = 1'b0;
  99: 
 100:     if (reqfifo_rvalid) begin
 101:       if (reqfifo_rdata.error) begin
 102:         // Return error response. Assume no request went out to SRAM
 103:         d_valid = 1'b1;
 104:       end else if (reqfifo_rdata.op == OpRead) begin
 105:         d_valid = rspfifo_rvalid;
 106:       end else begin
 107:         // Write without error
 108:         d_valid = 1'b1;
 109:       end
 110:     end else begin
 111:       d_valid = 1'b0;
 112:     end
 113:   end
 114: 
 115:   always_comb begin
 116:     d_error = 1'b0;
 117: 
 118:     if (reqfifo_rvalid) begin
 119:       if (reqfifo_rdata.op == OpRead) begin
 120:         d_error = rspfifo_rdata.error | reqfifo_rdata.error;
 121:       end else begin
 122:         d_error = reqfifo_rdata.error;
 123:       end
 124:     end else begin
 125:       d_error = 1'b0;
 126:     end
 127:   end
 128: 
 129:   assign tl_o = '{
 130:       d_valid  : d_valid ,
 131:       d_opcode : (d_valid && reqfifo_rdata.op != OpRead) ? AccessAck : AccessAckData,
 132:       d_param  : '0,
 133:       d_size   : (d_valid) ? reqfifo_rdata.size : '0,
 134:       d_source : (d_valid) ? reqfifo_rdata.source : '0,
 135:       d_sink   : 1'b0,
 136:       d_data   : (d_valid && rspfifo_rvalid && reqfifo_rdata.op == OpRead)
 137:                  ? rspfifo_rdata.data : '0,
 138:       d_user   : '0,
 139:       d_error  : d_valid && d_error,
 140: 
 141:       a_ready  : (gnt_i | error_internal) & reqfifo_wready & maskfifo_wready
 142:   };
 143: 
 144:   // a_ready depends on the FIFO full condition and grant from SRAM (or SRAM arbiter)
 145:   // assemble response, including read response, write response, and error for unsupported stuff
 146: 
 147:   // Output to SRAM:
 148:   //    Generate request only when no internal error occurs. If error occurs, the request should be
 149:   //    dropped and returned error response to the host. So, error to be pushed to reqfifo.
 150:   //    In this case, it is assumed the request is granted (may cause ordering issue later?)
 151:   assign req_o    = tl_i.a_valid & reqfifo_wready & ~error_internal;
 152:   assign we_o     = tl_i.a_valid & logic'(tl_i.a_opcode inside {PutFullData, PutPartialData});
 153:   assign addr_o   = (tl_i.a_valid) ? tl_i.a_address[DataBitWidth+:SramAw] : '0;
 154: 
 155:   `ASSERT_INIT(TlUlEqualsToSramDw, top_pkg::TL_DW == SramDw)
 156: 
 157:   // Convert byte mask to SRAM bit mask.
 158:   logic [top_pkg::TL_DW-1:0] rmask;
 159:   always_comb begin
 160:     for (int i = 0 ; i < top_pkg::TL_DW/8 ; i++) begin
 161:       wmask_o[8*i+:8] = (tl_i.a_valid) ? {8{tl_i.a_mask[i]}} : '0;
 162:       // only forward valid data here.
 163:       wdata_o[8*i+:8] = (tl_i.a_mask[i] && we_o) ? tl_i.a_data[8*i+:8] : '0;
 164:       // mask for read data
 165:       rmask[8*i+:8] = {8{maskfifo_rdata[i]}};
 166:     end
 167:   end
 168: 
 169:   // Begin: Request Error Detection
 170: 
 171:   // wr_attr_error: Check if the request size,mask are permitted.
 172:   //    Basic check of size, mask, addr align is done in tlul_err module.
 173:   //    Here it checks any partial write if ByteAccess isn't allowed.
 174:   assign wr_attr_error = (tl_i.a_opcode == PutFullData || tl_i.a_opcode == PutPartialData) ?
 175:                          (ByteAccess == 0) ? (tl_i.a_mask != '1 || tl_i.a_size != 2'h2) : 1'b0 :
 176:                          1'b0;
 177: 
 178:   if (ErrOnWrite == 1) begin : gen_no_writes
 179:     assign wr_vld_error = tl_i.a_opcode != Get;
 180:   end else begin : gen_writes_allowed
 181:     assign wr_vld_error = 1'b0;
 182:   end
 183: 
 184:   if (ErrOnRead == 1) begin: gen_no_reads
 185:     assign rd_vld_error = tl_i.a_opcode == Get;
 186:   end else begin : gen_reads_allowed
 187:     assign rd_vld_error = 1'b0;
 188:   end
 189: 
 190:   tlul_err u_err (
 191:     .clk_i,
 192:     .rst_ni,
 193:     .tl_i,
 194:     .err_o (tlul_error)
 195:   );
 196: 
 197:   assign error_internal = wr_attr_error | wr_vld_error | rd_vld_error | tlul_error;
 198:   // End: Request Error Detection
 199: 
 200:   assign reqfifo_wvalid = a_ack ; // Push to FIFO only when granted
 201:   assign reqfifo_wdata  = '{
 202:     op:     (tl_i.a_opcode != Get) ? OpWrite : OpRead, // To return AccessAck for opcode error
 203:     error:  error_internal,
 204:     size:   tl_i.a_size,
 205:     source: tl_i.a_source
 206:   }; // Store the request only. Doesn't have to store data
 207:   assign reqfifo_rready = d_ack ;
 208: 
 209:   // push together with ReqFIFO, pop upon returning read
 210:   assign maskfifo_wdata = tl_i.a_mask;
 211:   assign maskfifo_wvalid = sram_ack & ~we_o;
 212:   assign maskfifo_rready = rspfifo_wvalid;
 213: 
 214:   assign rspfifo_wvalid = rvalid_i & reqfifo_rvalid;
 215:   assign rspfifo_wdata  = '{
 216:     data:  rdata_i & rmask, // make sure only requested bytes are forwarded
 217:     error: rerror_i[1] // Only care for Uncorrectable error
 218:   };
 219:   assign rspfifo_rready = (reqfifo_rdata.op == OpRead & ~reqfifo_rdata.error)
 220:                         ? reqfifo_rready : 1'b0 ;
 221: 
 222:   // FIFO instance: REQ, RSP
 223: 
 224:   // ReqFIFO is to store the Access type to match to the Response data.
 225:   //    For instance, SRAM accepts the write request but doesn't return the
 226:   //    acknowledge. In this case, it may be hard to determine when the D
 227:   //    response for the write data should send out if reads/writes are
 228:   //    interleaved. So, to make it in-order (even TL-UL allows out-of-order
 229:   //    responses), storing the request is necessary. And if the read entry
 230:   //    is write op, it is safe to return the response right away. If it is
 231:   //    read reqeust, then D response is waiting until read data arrives.
 232: 
 233:   // Notes:
 234:   // The oustanding+1 allows the reqfifo to absorb back to back transactions
 235:   // without any wait states.  Alternatively, the depth can be kept as
 236:   // oustanding as long as the outgoing ready is qualified with the acceptance
 237:   // of the response in the same cycle.  Doing so however creates a path from
 238:   // ready_i to ready_o, which may not be desireable.
 239:   prim_fifo_sync #(
 240:     .Width  (ReqFifoWidth),
 241:     .Pass   (1'b0),
 242:     .Depth  (Outstanding)
 243:   ) u_reqfifo (
 244:     .clk_i,
 245:     .rst_ni,
 246:     .clr_i  (1'b0),
 247:     .wvalid (reqfifo_wvalid),
 248:     .wready (reqfifo_wready),
 249:     .wdata  (reqfifo_wdata),
 250:     .depth  (),
 251:     .rvalid (reqfifo_rvalid),
 252:     .rready (reqfifo_rready),
 253:     .rdata  (reqfifo_rdata)
 254:   );
 255: 
 256:   // MaskFIFO:
 257:   //    While the ReqFIFO holds the request until it is sent back via TL-UL, the
 258:   //    MaskFIFO only needs to hold the mask value until the read data returns
 259:   //    from memory.
 260:   prim_fifo_sync #(
 261:     .Width  ($bits(tl_i.a_mask)),
 262:     .Pass   (1'b0),
 263:     .Depth  (Outstanding)
 264:   ) u_maskfifo (
 265:     .clk_i,
 266:     .rst_ni,
 267:     .clr_i  (1'b0),
 268:     .wvalid (maskfifo_wvalid),
 269:     .wready (maskfifo_wready),
 270:     .wdata  (maskfifo_wdata),
 271:     .depth  (),
 272:     .rvalid (),
 273:     .rready (maskfifo_rready),
 274:     .rdata  (maskfifo_rdata)
 275:   );
 276: 
 277:   // Rationale having #Outstanding depth in response FIFO.
 278:   //    In normal case, if the host or the crossbar accepts the response data,
 279:   //    response FIFO isn't needed. But if in any case it has a chance to be
 280:   //    back pressured, the response FIFO should store the returned data not to
 281:   //    lose the data from the SRAM interface. Remember, SRAM interface doesn't
 282:   //    have back-pressure signal such as read_ready.
 283:   prim_fifo_sync #(
 284:     .Width (RspFifoWidth),
 285:     .Pass  (1'b1),
 286:     .Depth (Outstanding)
 287:   ) u_rspfifo (
 288:     .clk_i,
 289:     .rst_ni,
 290:     .clr_i  (1'b0),
 291:     .wvalid (rspfifo_wvalid),
 292:     .wready (rspfifo_wready),
 293:     .wdata  (rspfifo_wdata),
 294:     .depth  (),
 295:     .rvalid (rspfifo_rvalid),
 296:     .rready (rspfifo_rready),
 297:     .rdata  (rspfifo_rdata)
 298:   );
 299: 
 300:   // below assertion fails when SRAM rvalid is asserted even though ReqFifo is empty
 301:   `ASSERT(rvalidHighReqFifoEmpty, rvalid_i |-> reqfifo_rvalid)
 302: 
 303:   // below assertion fails when outstanding value is too small (SRAM rvalid is asserted
 304:   // even though the RspFifo is full)
 305:   `ASSERT(rvalidHighWhenRspFifoFull, rvalid_i |-> rspfifo_wready)
 306: 
 307:   // If both ErrOnWrite and ErrOnRead are set, this block is useless
 308:   `ASSERT_INIT(adapterNoReadOrWrite, (ErrOnWrite & ErrOnRead) == 0)
 309: 
 310:   // make sure outputs are defined
 311:   `ASSERT_KNOWN(TlOutKnown_A,    tl_o   )
 312:   `ASSERT_KNOWN(ReqOutKnown_A,   req_o  )
 313:   `ASSERT_KNOWN(WeOutKnown_A,    we_o   )
 314:   `ASSERT_KNOWN(AddrOutKnown_A,  addr_o )
 315:   `ASSERT_KNOWN(WdataOutKnown_A, wdata_o)
 316:   `ASSERT_KNOWN(WmaskOutKnown_A, wmask_o)
 317: 
 318: endmodule
 319: