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