| // 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 Register interface |
| * |
| * TODO(#15822): Note that due to some modules with special needs (like |
| * the vendored-in RV_DM), this module has been extended so that it |
| * supports use cases outside of the generated reg_top module. This makes |
| * this adapter and its parameterization options a bit heavy. |
| * |
| * We should in the future come back to this and refactor / align the |
| * module and its parameterization needs. |
| */ |
| |
| module tlul_adapter_reg |
| import tlul_pkg::*; |
| import prim_mubi_pkg::mubi4_t; |
| #( |
| 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 int RegAw = 8, // Width of register address |
| parameter int RegDw = 32, // Shall be matched with TL_DW |
| parameter int AccessLatency = 0, // 0: same cycle, 1: next cycle |
| localparam int RegBw = RegDw/8 |
| ) ( |
| 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, |
| output logic intg_error_o, |
| |
| // Register interface |
| output logic re_o, |
| output logic we_o, |
| output logic [RegAw-1:0] addr_o, |
| output logic [RegDw-1:0] wdata_o, |
| output logic [RegBw-1:0] be_o, |
| input busy_i, |
| // The following two signals are expected |
| // to be returned in AccessLatency cycles. |
| input [RegDw-1:0] rdata_i, |
| // This can be a write or read error. |
| input error_i |
| ); |
| |
| `ASSERT_INIT(AllowedLatency_A, AccessLatency inside {0, 1}) |
| |
| localparam int IW = $bits(tl_i.a_source); |
| localparam int SZW = $bits(tl_i.a_size); |
| |
| logic outstanding_q; // Indicates current request is pending |
| logic a_ack, d_ack; |
| |
| logic [RegDw-1:0] rdata, rdata_q; |
| logic error_q, error, err_internal, instr_error, intg_error; |
| |
| logic addr_align_err; // Size and alignment |
| logic malformed_meta_err; // User signal format error or unsupported |
| logic tl_err; // Common TL-UL error checker |
| |
| logic [IW-1:0] reqid_q; |
| logic [SZW-1:0] reqsz_q; |
| tl_d_op_e rspop_q; |
| |
| logic rd_req, wr_req; |
| |
| assign a_ack = tl_i.a_valid & tl_o.a_ready; |
| assign d_ack = tl_o.d_valid & tl_i.d_ready; |
| // Request signal |
| assign wr_req = a_ack & ((tl_i.a_opcode == PutFullData) | (tl_i.a_opcode == PutPartialData)); |
| assign rd_req = a_ack & (tl_i.a_opcode == Get); |
| |
| assign we_o = wr_req & ~err_internal; |
| assign re_o = rd_req & ~err_internal; |
| assign wdata_o = tl_i.a_data; |
| assign be_o = tl_i.a_mask; |
| |
| if (RegAw <= 2) begin : gen_only_one_reg |
| assign addr_o = '0; |
| end else begin : gen_more_regs |
| assign addr_o = {tl_i.a_address[RegAw-1:2], 2'b00}; // generate always word-align |
| end |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) outstanding_q <= 1'b0; |
| else if (a_ack) outstanding_q <= 1'b1; |
| else if (d_ack) outstanding_q <= 1'b0; |
| end |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| reqid_q <= '0; |
| reqsz_q <= '0; |
| rspop_q <= AccessAck; |
| end else if (a_ack) begin |
| reqid_q <= tl_i.a_source; |
| reqsz_q <= tl_i.a_size; |
| // Return AccessAckData regardless of error |
| rspop_q <= (rd_req) ? AccessAckData : AccessAck ; |
| end |
| end |
| |
| if (AccessLatency == 1) begin : gen_access_latency1 |
| logic wr_req_q, rd_req_q; |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| rdata_q <= '0; |
| error_q <= 1'b0; |
| wr_req_q <= 1'b0; |
| rd_req_q <= 1'b0; |
| end else begin |
| rd_req_q <= rd_req; |
| wr_req_q <= wr_req; |
| // Addressing phase |
| if (a_ack) begin |
| error_q <= err_internal; |
| // Response phase |
| end else begin |
| error_q <= error; |
| rdata_q <= rdata; |
| end |
| end |
| end |
| assign rdata = (error_i || error_q || wr_req_q) ? '1 : |
| (rd_req_q) ? rdata_i : |
| rdata_q; // backpressure case |
| assign error = (rd_req_q || wr_req_q) ? (error_q || error_i) : |
| error_q; // backpressure case |
| end else begin : gen_access_latency0 |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| rdata_q <= '0; |
| error_q <= 1'b0; |
| end else if (a_ack) begin |
| rdata_q <= (error_i || err_internal || wr_req) ? '1 : rdata_i; |
| error_q <= error_i || err_internal; |
| end |
| end |
| assign rdata = rdata_q; |
| assign error = error_q; |
| end |
| |
| tlul_pkg::tl_d2h_t tl_o_pre; |
| assign tl_o_pre = '{ |
| // busy is selected based on address |
| // thus if there is no valid transaction, we should ignore busy |
| a_ready: ~(outstanding_q | tl_i.a_valid & busy_i), |
| d_valid: outstanding_q, |
| d_opcode: rspop_q, |
| d_param: '0, |
| d_size: reqsz_q, |
| d_source: reqid_q, |
| d_sink: '0, |
| d_data: rdata, |
| d_user: '0, |
| d_error: error |
| }; |
| |
| // outgoing integrity generation |
| tlul_rsp_intg_gen #( |
| .EnableRspIntgGen(EnableRspIntgGen), |
| .EnableDataIntgGen(EnableDataIntgGen) |
| ) u_rsp_intg_gen ( |
| .tl_i(tl_o_pre), |
| .tl_o(tl_o) |
| ); |
| |
| if (CmdIntgCheck) begin : gen_cmd_intg_check |
| logic intg_error_q; |
| tlul_cmd_intg_chk u_cmd_intg_chk ( |
| .tl_i(tl_i), |
| .err_o(intg_error) |
| ); |
| // permanently latch integrity error until reset |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| intg_error_q <= 1'b0; |
| end else if (intg_error) begin |
| intg_error_q <= 1'b1; |
| end |
| end |
| assign intg_error_o = intg_error_q; |
| end else begin : gen_no_cmd_intg_check |
| assign intg_error = 1'b0; |
| assign intg_error_o = 1'b0; |
| end |
| |
| //////////////////// |
| // Error Handling // |
| //////////////////// |
| |
| // 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)); |
| |
| assign err_internal = addr_align_err | malformed_meta_err | tl_err | instr_error | intg_error; |
| |
| // Don't allow unsupported values. |
| assign malformed_meta_err = tl_a_user_chk(tl_i.a_user); |
| |
| // addr_align_err |
| // Raised if addr isn't aligned with the size |
| // Read size error is checked in tlul_assert.sv |
| // Here is it added due to the limitation of register interface. |
| always_comb begin |
| if (wr_req) begin |
| // Only word-align is accepted based on comportability spec |
| addr_align_err = |tl_i.a_address[1:0]; |
| end else begin |
| // No request |
| addr_align_err = 1'b0; |
| end |
| end |
| |
| // tl_err : separate checker |
| tlul_err u_err ( |
| .clk_i, |
| .rst_ni, |
| .tl_i, |
| .err_o (tl_err) |
| ); |
| |
| `ASSERT_INIT(MatchedWidthAssert, RegDw == top_pkg::TL_DW) |
| |
| endmodule |