feat(fpga): Add supporting IPs for Kelvin SoC Change-Id: I127d67f8c0e87d13972c7b804ba8db8dc4ffc202
diff --git a/fpga/ip/kelvin_tlul/BUILD b/fpga/ip/kelvin_tlul/BUILD new file mode 100644 index 0000000..d78f918 --- /dev/null +++ b/fpga/ip/kelvin_tlul/BUILD
@@ -0,0 +1,23 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "rtl_files", + srcs = glob([ + "*.sv", + "*.core", + ]), +)
diff --git a/fpga/ip/kelvin_tlul/kelvin_tlul.core b/fpga/ip/kelvin_tlul/kelvin_tlul.core new file mode 100644 index 0000000..37336d3 --- /dev/null +++ b/fpga/ip/kelvin_tlul/kelvin_tlul.core
@@ -0,0 +1,21 @@ +CAPI=2: +name: "kelvinv2:ip:kelvin_tlul:0.1" +description: "Kelvin TL-UL components" + +filesets: + rtl: + depend: + - "lowrisc:tlul:headers" + files: + - kelvin_tlul_pkg_128.sv + - kelvin_tlul_pkg_32.sv + - tlul_fifo_async_128.sv + - tlul_fifo_sync_128.sv + - tlul_socket_1n_128.sv + - tlul_socket_m1_128.sv + file_type: systemVerilogSource + +targets: + default: + filesets: + - rtl
diff --git a/fpga/ip/kelvin_tlul/kelvin_tlul_pkg_128.sv b/fpga/ip/kelvin_tlul/kelvin_tlul_pkg_128.sv new file mode 100644 index 0000000..9239448 --- /dev/null +++ b/fpga/ip/kelvin_tlul/kelvin_tlul_pkg_128.sv
@@ -0,0 +1,77 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kelvin_tlul_pkg_128; + import tlul_pkg::*; + import top_pkg::*; + + parameter ArbiterImpl = "PPC"; + + localparam int TL_DW = 128; + localparam int TL_DBW = TL_DW / 8; + localparam int TL_SZW = $clog2(TL_DW); + localparam int TL_AIW = 8; + + typedef struct packed { + logic [RsvdWidth - 1 : 0] rsvd; + prim_mubi_pkg::mubi4_t instr_type; + logic [H2DCmdIntgWidth - 1 : 0] cmd_intg; + logic [DataIntgWidth - 1 : 0] data_intg; + } tl_a_user_t; + + typedef struct packed { + logic a_valid; + tl_a_op_e a_opcode; + logic [2 : 0] a_param; + logic [TL_SZW - 1 : 0] a_size; + logic [TL_AIW - 1 : 0] a_source; + logic [TL_AW - 1 : 0] a_address; + logic [TL_DBW - 1 : 0] a_mask; + logic [TL_DW - 1 : 0] a_data; + tl_a_user_t a_user; + logic d_ready; + } tl_h2d_t; + + typedef struct packed { + logic d_valid; + tl_d_op_e d_opcode; + logic [2 : 0] d_param; + logic [TL_SZW - 1 : 0] d_size; + logic [TL_AIW - 1 : 0] d_source; + logic [TL_DIW - 1 : 0] d_sink; + logic [TL_DW - 1 : 0] d_data; + tl_d_user_t d_user; + logic d_error; + logic a_ready; + } tl_d2h_t; + + localparam logic [top_pkg::TL_DW - 1 : 0] BlankedAData = { + top_pkg::TL_DW{1'b1}}; + + // return inverted integrity for command payload + function automatic logic [H2DCmdIntgWidth - 1 : 0] get_bad_cmd_intg + (tl_h2d_t tl); + logic [H2DCmdIntgWidth - 1 : 0] cmd_intg; + cmd_intg = get_cmd_intg(tl); + return ~cmd_intg; + endfunction // get_bad_cmd_intg + + // return inverted integrity for data payload + function automatic logic [H2DCmdIntgWidth - 1 : 0] get_bad_data_intg + (logic [top_pkg::TL_DW - 1 : 0] data); + logic [H2DCmdIntgWidth - 1 : 0] data_intg; + data_intg = get_data_intg(data); + return ~data_intg; + endfunction // get_bad_data_intg +endpackage
diff --git a/fpga/ip/kelvin_tlul/kelvin_tlul_pkg_32.sv b/fpga/ip/kelvin_tlul/kelvin_tlul_pkg_32.sv new file mode 100644 index 0000000..5a989db --- /dev/null +++ b/fpga/ip/kelvin_tlul/kelvin_tlul_pkg_32.sv
@@ -0,0 +1,45 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kelvin_tlul_pkg_32; + + import tlul_pkg::*; + import top_pkg::*; + + typedef struct packed { + logic a_valid; + tl_a_op_e a_opcode; + logic [2 : 0] a_param; + logic [TL_SZW - 1 : 0] a_size; + logic [TL_AIW - 1 : 0] a_source; + logic [TL_AW - 1 : 0] a_address; + logic [TL_DBW - 1 : 0] a_mask; + logic [TL_DW - 1 : 0] a_data; + tl_a_user_t a_user; + logic d_ready; + } tl_h2d_t; + + typedef struct packed { + logic d_valid; + tl_d_op_e d_opcode; + logic [2 : 0] d_param; + logic [TL_SZW - 1 : 0] d_size; + logic [TL_AIW - 1 : 0] d_source; + logic [TL_DIW - 1 : 0] d_sink; + logic [TL_DW - 1 : 0] d_data; + tl_d_user_t d_user; + logic d_error; + logic a_ready; + } tl_d2h_t; +endpackage
diff --git a/fpga/ip/kelvin_tlul/tlul_fifo_async_128.sv b/fpga/ip/kelvin_tlul/tlul_fifo_async_128.sv new file mode 100644 index 0000000..22894bb --- /dev/null +++ b/fpga/ip/kelvin_tlul/tlul_fifo_async_128.sv
@@ -0,0 +1,71 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// TL-UL fifo, used to add elasticity or an asynchronous clock crossing +// to an TL-UL bus. This instantiates two FIFOs, one for the request side, +// and one for the response side. + +`include "prim_assert.sv" + +module tlul_fifo_async_128 + #(parameter int unsigned ReqDepth = 4, + parameter int unsigned RspDepth = 4) + (input clk_h_i, + input rst_h_ni, + input clk_d_i, + input rst_d_ni, + input kelvin_tlul_pkg_128::tl_h2d_t tl_h_i, + output kelvin_tlul_pkg_128::tl_d2h_t tl_h_o, + output kelvin_tlul_pkg_128::tl_h2d_t tl_d_o, + input kelvin_tlul_pkg_128::tl_d2h_t tl_d_i); + + // Put everything on the request side into one FIFO + localparam int unsigned REQFIFO_WIDTH = + $bits(kelvin_tlul_pkg_128::tl_h2d_t) - 2; + + prim_fifo_async #(.Width(REQFIFO_WIDTH), + .Depth(ReqDepth), + .OutputZeroIfInvalid(1)) + reqfifo(.clk_wr_i(clk_h_i), + .rst_wr_ni(rst_h_ni), + .clk_rd_i(clk_d_i), + .rst_rd_ni(rst_d_ni), + .wvalid_i(tl_h_i.a_valid), + .wready_o(tl_h_o.a_ready), + .wdata_i({tl_h_i.a_opcode, tl_h_i.a_param, tl_h_i.a_size, + tl_h_i.a_source, tl_h_i.a_address, tl_h_i.a_mask, + tl_h_i.a_data, tl_h_i.a_user}), + .rvalid_o(tl_d_o.a_valid), + .rready_i(tl_d_i.a_ready), + .rdata_o({tl_d_o.a_opcode, tl_d_o.a_param, tl_d_o.a_size, + tl_d_o.a_source, tl_d_o.a_address, tl_d_o.a_mask, + tl_d_o.a_data, tl_d_o.a_user}), + .wdepth_o(), + .rdepth_o()); + + // Put everything on the response side into the other FIFO + + localparam int unsigned RSPFIFO_WIDTH = + $bits(kelvin_tlul_pkg_128::tl_d2h_t) - 2; + + prim_fifo_async #(.Width(RSPFIFO_WIDTH), + .Depth(RspDepth), + .OutputZeroIfInvalid(1)) + rspfifo(.clk_wr_i(clk_d_i), + .rst_wr_ni(rst_d_ni), + .clk_rd_i(clk_h_i), + .rst_rd_ni(rst_h_ni), + .wvalid_i(tl_d_i.d_valid), + .wready_o(tl_d_o.d_ready), + .wdata_i({tl_d_i.d_opcode, tl_d_i.d_param, tl_d_i.d_size, + tl_d_i.d_source, tl_d_i.d_sink, tl_d_i.d_data, + tl_d_i.d_user, tl_d_i.d_error}), + .rvalid_o(tl_h_o.d_valid), + .rready_i(tl_h_i.d_ready), + .rdata_o({tl_h_o.d_opcode, tl_h_o.d_param, tl_h_o.d_size, + tl_h_o.d_source, tl_h_o.d_sink, tl_h_o.d_data, + tl_h_o.d_user, tl_h_o.d_error}), + .wdepth_o(), + .rdepth_o()); +endmodule
diff --git a/fpga/ip/kelvin_tlul/tlul_fifo_sync_128.sv b/fpga/ip/kelvin_tlul/tlul_fifo_sync_128.sv new file mode 100644 index 0000000..af404f1 --- /dev/null +++ b/fpga/ip/kelvin_tlul/tlul_fifo_sync_128.sv
@@ -0,0 +1,78 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// TL-UL fifo, used to add elasticity or an asynchronous clock crossing +// to an TL-UL bus. This instantiates two FIFOs, one for the request side, +// and one for the response side. + +module tlul_fifo_sync_128 + #(parameter bit ReqPass = 1'b1, + parameter bit RspPass = 1'b1, + parameter int unsigned ReqDepth = 2, + parameter int unsigned RspDepth = 2, + parameter int unsigned SpareReqW = 1, + parameter int unsigned SpareRspW = 1) + (input clk_i, + input rst_ni, + input kelvin_tlul_pkg_128::tl_h2d_t tl_h_i, + output kelvin_tlul_pkg_128::tl_d2h_t tl_h_o, + output kelvin_tlul_pkg_128::tl_h2d_t tl_d_o, + input kelvin_tlul_pkg_128::tl_d2h_t tl_d_i, + input [SpareReqW - 1 : 0] spare_req_i, + output [SpareReqW - 1 : 0] spare_req_o, + input [SpareRspW - 1 : 0] spare_rsp_i, + output [SpareRspW - 1 : 0] spare_rsp_o); + import kelvin_tlul_pkg_128::*; + // Put everything on the request side into one FIFO + localparam int unsigned REQFIFO_WIDTH = $bits(kelvin_tlul_pkg_128::tl_h2d_t) - + 2 + SpareReqW; + + prim_fifo_sync #(.Width(REQFIFO_WIDTH), + .Pass(ReqPass), + .Depth(ReqDepth)) + reqfifo(.clk_i, + .rst_ni, + .clr_i(1'b0), + .wvalid_i(tl_h_i.a_valid), + .wready_o(tl_h_o.a_ready), + .wdata_i({tl_h_i.a_opcode, tl_h_i.a_param, tl_h_i.a_size, + tl_h_i.a_source, tl_h_i.a_address, tl_h_i.a_mask, + tl_h_i.a_data, tl_h_i.a_user, spare_req_i}), + .rvalid_o(tl_d_o.a_valid), + .rready_i(tl_d_i.a_ready), + .rdata_o({tl_d_o.a_opcode, tl_d_o.a_param, tl_d_o.a_size, + tl_d_o.a_source, tl_d_o.a_address, tl_d_o.a_mask, + tl_d_o.a_data, tl_d_o.a_user, spare_req_o}), + .full_o(), + .depth_o(), + .err_o()); + + // Put everything on the response side into the other FIFO + + localparam int unsigned RSPFIFO_WIDTH = $bits(kelvin_tlul_pkg_128::tl_d2h_t) - + 2 + SpareRspW; + + prim_fifo_sync #(.Width(RSPFIFO_WIDTH), + .Pass(RspPass), + .Depth(RspDepth)) + rspfifo(.clk_i, + .rst_ni, + .clr_i(1'b0), + .wvalid_i(tl_d_i.d_valid), + .wready_o(tl_d_o.d_ready), + .wdata_i({tl_d_i.d_opcode, tl_d_i.d_param, tl_d_i.d_size, + tl_d_i.d_source, tl_d_i.d_sink, + (tl_d_i.d_opcode == tlul_pkg::AccessAckData) + ? tl_d_i.d_data + : {kelvin_tlul_pkg_128::TL_DW{1'b0}}, + tl_d_i.d_user, tl_d_i.d_error, spare_rsp_i}), + .rvalid_o(tl_h_o.d_valid), + .rready_i(tl_h_i.d_ready), + .rdata_o({tl_h_o.d_opcode, tl_h_o.d_param, tl_h_o.d_size, + tl_h_o.d_source, tl_h_o.d_sink, tl_h_o.d_data, + tl_h_o.d_user, tl_h_o.d_error, spare_rsp_o}), + .full_o(), + .depth_o(), + .err_o()); +endmodule
diff --git a/fpga/ip/kelvin_tlul/tlul_socket_1n_128.sv b/fpga/ip/kelvin_tlul/tlul_socket_1n_128.sv new file mode 100644 index 0000000..2b333f0 --- /dev/null +++ b/fpga/ip/kelvin_tlul/tlul_socket_1n_128.sv
@@ -0,0 +1,243 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// TL-UL socket 1:N module +// +// configuration settings +// device_count: 4 +// +// Verilog parameters +// HReqPass: if 1 then host requests can pass through on empty fifo, +// default 1 +// HRspPass: if 1 then host responses can pass through on empty fifo, +// default 1 +// DReqPass: (one per device_count) if 1 then device i requests can +// pass through on empty fifo, default 1 +// DRspPass: (one per device_count) if 1 then device i responses can +// pass through on empty fifo, default 1 +// HReqDepth: Depth of host request FIFO, default 2 +// HRspDepth: Depth of host response FIFO, default 2 +// DReqDepth: (one per device_count) Depth of device i request FIFO, +// default 2 +// DRspDepth: (one per device_count) Depth of device i response FIFO, +// default 2 +// ExplicitErrs: This module always returns a request error if dev_select_i +// is greater than N-1. If ExplicitErrs is set then the width +// of the dev_select_i signal will be chosen to make sure that +// this is possible. This only makes a difference if N is a +// power of 2. +// +// Requests must stall to one device until all responses from other devices +// have returned. Need to keep a counter of all outstanding requests and +// wait until that counter is zero before switching devices. +// +// This module will return a request error if the input value of 'dev_select_i' +// is not within the range 0..N-1. Thus the instantiator of the socket +// can indicate error by any illegal value of dev_select_i. 4'b1111 is +// recommended for visibility +// +// The maximum value of N is 63 + +`include "prim_assert.sv" + +module tlul_socket_1n_128 + #(parameter int unsigned N = 4, + parameter bit HReqPass = 1'b1, + parameter bit HRspPass = 1'b1, + parameter bit [N - 1 : 0] DReqPass = {N{1'b1}}, + parameter bit [N - 1 : 0] DRspPass = {N{1'b1}}, + parameter bit [3 : 0] HReqDepth = 4'h1, + parameter bit [3 : 0] HRspDepth = 4'h1, + parameter bit [N * 4 - 1 : 0] DReqDepth = {N{4'h1}}, + parameter bit [N * 4 - 1 : 0] DRspDepth = {N{4'h1}}, + parameter bit ExplicitErrs = 1'b1, + + // The width of dev_select_i. We must be able to select any of the N + // devices (i.e. values 0..N-1). If ExplicitErrs is set, we also need to + // be able to represent N. + localparam int unsigned NWD = $clog2(ExplicitErrs ? N + 1 : N)) + (input clk_i, + input rst_ni, + input kelvin_tlul_pkg_128::tl_h2d_t tl_h_i, + output kelvin_tlul_pkg_128::tl_d2h_t tl_h_o, + output kelvin_tlul_pkg_128::tl_h2d_t tl_d_o[N], + input kelvin_tlul_pkg_128::tl_d2h_t tl_d_i[N], + input [NWD - 1 : 0] dev_select_i); + + `ASSERT_INIT(maxN, N < 64) + + // Since our steering is done after potential FIFOing, we need to + // shove our device select bits into spare bits of reqfifo + + // instantiate the host fifo, create intermediate bus 't' + + // FIFO'd version of device select + logic [NWD - 1 : 0] dev_select_t; + + kelvin_tlul_pkg_128::tl_h2d_t tl_t_o; + kelvin_tlul_pkg_128::tl_d2h_t tl_t_i; + + tlul_fifo_sync_128 #(.ReqPass(HReqPass), + .RspPass(HRspPass), + .ReqDepth(HReqDepth), + .RspDepth(HRspDepth), + .SpareReqW(NWD)) + fifo_h(.clk_i, + .rst_ni, + .tl_h_i, + .tl_h_o, + .tl_d_o(tl_t_o), + .tl_d_i(tl_t_i), + .spare_req_i(dev_select_i), + .spare_req_o(dev_select_t), + .spare_rsp_i(1'b0), + .spare_rsp_o()); + + // We need to keep track of how many requests are outstanding, + // and to which device. New requests are compared to this and + // stall until that number is zero. + localparam int MaxOutstanding = + 2 ** top_pkg::TL_AIW; // Up to 256 outstanding + localparam int OutstandingW = $clog2(MaxOutstanding + 1); + logic [OutstandingW - 1 : 0] num_req_outstanding; + logic [NWD - 1 : 0] dev_select_outstanding; + logic hold_all_requests; + logic accept_t_req, accept_t_rsp; + + assign accept_t_req = tl_t_o.a_valid & tl_t_i.a_ready; + assign accept_t_rsp = tl_t_i.d_valid & tl_t_o.d_ready; + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + num_req_outstanding <= '0; + dev_select_outstanding <= '0; + end else if (accept_t_req) begin + if (!accept_t_rsp) begin + num_req_outstanding <= num_req_outstanding + 1'b1; + end + dev_select_outstanding <= dev_select_t; + end else if (accept_t_rsp) begin + num_req_outstanding <= num_req_outstanding - 1'b1; + end + end + + `ASSERT(NotOverflowed_A, accept_t_req && !accept_t_rsp -> + num_req_outstanding <= MaxOutstanding) + + assign hold_all_requests = (num_req_outstanding != '0) & + (dev_select_t != dev_select_outstanding); + + // Make N copies of 't' request side with modified reqvalid, call + // them 'u[0]' .. 'u[n-1]'. + + kelvin_tlul_pkg_128::tl_h2d_t tl_u_o[N + 1]; + kelvin_tlul_pkg_128::tl_d2h_t tl_u_i[N + 1]; + + // ensure that when a device is not selected, both command + // data integrity can never match + kelvin_tlul_pkg_128::tl_a_user_t blanked_auser; + assign blanked_auser = '{ + rsvd: tl_t_o.a_user.rsvd, + instr_type: tl_t_o.a_user.instr_type, + cmd_intg: kelvin_tlul_pkg_128::get_bad_cmd_intg(tl_t_o), + data_intg: kelvin_tlul_pkg_128::get_bad_data_intg( + kelvin_tlul_pkg_128::BlankedAData) + }; + + // if a host is not selected, or if requests are held off, blank the bus + for (genvar i = 0; i < N; i++) begin : gen_u_o + logic dev_select; + assign dev_select = dev_select_t == NWD'(i) & ~hold_all_requests; + + assign tl_u_o[i].a_valid = tl_t_o.a_valid & dev_select; + assign tl_u_o[i].a_opcode = tl_t_o.a_opcode; + assign tl_u_o[i].a_param = tl_t_o.a_param; + assign tl_u_o[i].a_size = tl_t_o.a_size; + assign tl_u_o[i].a_source = tl_t_o.a_source; + assign tl_u_o[i].a_address = tl_t_o.a_address; + assign tl_u_o[i].a_mask = tl_t_o.a_mask; + assign tl_u_o[i].a_data = + dev_select ? tl_t_o.a_data : kelvin_tlul_pkg_128::BlankedAData; + assign tl_u_o[i].a_user = dev_select ? tl_t_o.a_user : blanked_auser; + + assign tl_u_o[i].d_ready = tl_t_o.d_ready; + end + + kelvin_tlul_pkg_128::tl_d2h_t tl_t_p; + + // for the returning reqready, only look at the device we're addressing + logic hfifo_reqready; + always_comb begin + hfifo_reqready = tl_u_i[N].a_ready; // default to error + for (int idx = 0; idx < N; idx++) begin + // if (dev_select_outstanding == NWD'(idx)) hfifo_reqready = + // tl_u_i[idx].a_ready; + if (dev_select_t == NWD'(idx)) hfifo_reqready = tl_u_i[idx].a_ready; + end + if (hold_all_requests) hfifo_reqready = 1'b0; + end + // Adding a_valid as a qualifier. This prevents the a_ready from having + // unknown value when the address is unknown and the Host TL-UL FIFO is bypass + // mode. + assign tl_t_i.a_ready = tl_t_o.a_valid & hfifo_reqready; + + always_comb begin + tl_t_p = tl_u_i[N]; + for (int idx = 0; idx < N; idx++) begin + if (dev_select_outstanding == NWD'(idx)) tl_t_p = tl_u_i[idx]; + end + end + assign tl_t_i.d_valid = tl_t_p.d_valid; + assign tl_t_i.d_opcode = tl_t_p.d_opcode; + assign tl_t_i.d_param = tl_t_p.d_param; + assign tl_t_i.d_size = tl_t_p.d_size; + assign tl_t_i.d_source = tl_t_p.d_source; + assign tl_t_i.d_sink = tl_t_p.d_sink; + assign tl_t_i.d_data = tl_t_p.d_data; + assign tl_t_i.d_user = tl_t_p.d_user; + assign tl_t_i.d_error = tl_t_p.d_error; + + // Instantiate all the device FIFOs + for (genvar i = 0; i < N; i++) begin : gen_dfifo + tlul_fifo_sync_128 #(.ReqPass(DReqPass[i]), + .RspPass(DRspPass[i]), + .ReqDepth(DReqDepth[i * 4 +: 4]), + .RspDepth(DRspDepth[i * 4 +: 4])) + fifo_d(.clk_i, + .rst_ni, + .tl_h_i(tl_u_o[i]), + .tl_h_o(tl_u_i[i]), + .tl_d_o(tl_d_o[i]), + .tl_d_i(tl_d_i[i]), + .spare_req_i(1'b0), + .spare_req_o(), + .spare_rsp_i(1'b0), + .spare_rsp_o()); + end + + // Instantiate the error responder. It's only needed if a value greater than + // N-1 is actually representable in NWD bits. + if ($clog2(N + 1) <= NWD) begin : gen_err_resp + assign tl_u_o[N].d_ready = tl_t_o.d_ready; + assign tl_u_o[N].a_valid = + tl_t_o.a_valid & (dev_select_t >= NWD'(N)) & ~hold_all_requests; + assign tl_u_o[N].a_opcode = tl_t_o.a_opcode; + assign tl_u_o[N].a_param = tl_t_o.a_param; + assign tl_u_o[N].a_size = tl_t_o.a_size; + assign tl_u_o[N].a_source = tl_t_o.a_source; + assign tl_u_o[N].a_address = tl_t_o.a_address; + assign tl_u_o[N].a_mask = tl_t_o.a_mask; + assign tl_u_o[N].a_data = tl_t_o.a_data; + assign tl_u_o[N].a_user = tl_t_o.a_user; + tlul_err_resp err_resp(.clk_i, + .rst_ni, + .tl_h_i(tl_u_o[N]), + .tl_h_o(tl_u_i[N])); + end else begin : gen_no_err_resp // block: gen_err_resp + assign tl_u_o[N] = '0; + assign tl_u_i[N] = '0; + logic unused_sig; + assign unused_sig = ^tl_u_o[N]; + end +endmodule
diff --git a/fpga/ip/kelvin_tlul/tlul_socket_m1_128.sv b/fpga/ip/kelvin_tlul/tlul_socket_m1_128.sv new file mode 100644 index 0000000..cded907 --- /dev/null +++ b/fpga/ip/kelvin_tlul/tlul_socket_m1_128.sv
@@ -0,0 +1,243 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// TL-UL socket M:1 module +// +// Verilog parameters +// M: Number of host ports. +// HReqPass: M bit array to allow requests to pass through the host i +// FIFO with no clock delay if the request FIFO is empty. If +// 1'b0, at least one clock cycle of latency is created. +// Default is 1'b1. +// HRspPass: Same as HReqPass but for host response FIFO. +// HReqDepth: Mx4 bit array. bit[i*4+:4] is depth of host i request FIFO. +// Depth of zero is allowed if ReqPass is true. A maximum value +// of 16 is allowed, default is 2. +// HRspDepth: Same as HReqDepth but for host response FIFO. +// DReqPass: Same as HReqPass but for device request FIFO. +// DRspPass: Same as HReqPass but for device response FIFO. +// DReqDepth: Same as HReqDepth but for device request FIFO. +// DRspDepth: Same as HReqDepth but for device response FIFO. + +`include "prim_assert.sv" + +module tlul_socket_m1_128 + #(parameter int unsigned M = 4, + parameter bit [M - 1 : 0] HReqPass = {M{1'b1}}, + parameter bit [M - 1 : 0] HRspPass = {M{1'b1}}, + parameter bit [M * 4 - 1 : 0] HReqDepth = {M{4'h1}}, + parameter bit [M * 4 - 1 : 0] HRspDepth = {M{4'h1}}, + parameter bit DReqPass = 1'b1, + parameter bit DRspPass = 1'b1, + parameter bit [3 : 0] DReqDepth = 4'h1, + parameter bit [3 : 0] DRspDepth = 4'h1) + (input clk_i, + input rst_ni, + + input kelvin_tlul_pkg_128::tl_h2d_t tl_h_i[M], + output kelvin_tlul_pkg_128::tl_d2h_t tl_h_o[M], + + output kelvin_tlul_pkg_128::tl_h2d_t tl_d_o, + input kelvin_tlul_pkg_128::tl_d2h_t tl_d_i); + + `ASSERT_INIT(maxM, M < 16) + + // Signals + // + // tl_h_i/o[0] | tl_h_i/o[1] | ... | tl_h_i/o[M-1] + // | | | + // u_hostfifo[0] u_hostfifo[1] u_hostfifo[M-1] + // | | | + // hreq_fifo_o(i) / hrsp_fifo_i(i) + // --------------------------------------- + // | request/grant/req_data | + // | | + // | PRIM_ARBITER | + // | | + // | arb_valid / arb_ready / arb_data | + // --------------------------------------- + // | + // dreq_fifo_i / drsp_fifo_o + // | + // u_devicefifo + // | + // tl_d_o/i + // + // Required ID width to distinguish between host ports + // Used in response steering + localparam int unsigned IDW = top_pkg::TL_AIW; + localparam int unsigned STIDW = $clog2(M); + + kelvin_tlul_pkg_128::tl_h2d_t hreq_fifo_o[M]; + kelvin_tlul_pkg_128::tl_d2h_t hrsp_fifo_i[M]; + + logic [M - 1 : 0] hrequest; + logic [M - 1 : 0] hgrant; + + kelvin_tlul_pkg_128::tl_h2d_t dreq_fifo_i; + kelvin_tlul_pkg_128::tl_d2h_t drsp_fifo_o; + + logic arb_valid; + logic arb_ready; + kelvin_tlul_pkg_128::tl_h2d_t arb_data; + + // Host Req/Rsp FIFO + for (genvar i = 0; i < M; i++) begin : gen_host_fifo + kelvin_tlul_pkg_128::tl_h2d_t hreq_fifo_i; + + // ID Shifting + logic [STIDW - 1 : 0] reqid_sub; + logic [IDW - 1 : 0] shifted_id; + assign reqid_sub = i; // can cause conversion error? + assign shifted_id = {tl_h_i[i].a_source[0 +: (IDW - STIDW)], reqid_sub}; + + `ASSERT(idInRange, tl_h_i[i].a_valid |-> + tl_h_i[i].a_source[IDW - 1 -: STIDW] == '0) + + // assign not connected bits to nc_* signal to make lint happy + logic [IDW - 1 : IDW - STIDW] unused_tl_h_source; + assign unused_tl_h_source = tl_h_i[i].a_source[IDW - 1 -: STIDW]; + + // Put shifted ID + assign hreq_fifo_i = '{ + a_valid: tl_h_i[i].a_valid, + a_opcode: tl_h_i[i].a_opcode, + a_param: tl_h_i[i].a_param, + a_size: tl_h_i[i].a_size, + a_source: shifted_id, + a_address: tl_h_i[i].a_address, + a_mask: tl_h_i[i].a_mask, + a_data: tl_h_i[i].a_data, + a_user: tl_h_i[i].a_user, + d_ready: tl_h_i[i].d_ready + }; + + tlul_fifo_sync_128 #(.ReqPass(HReqPass[i]), + .RspPass(HRspPass[i]), + .ReqDepth(HReqDepth[i * 4 +: 4]), + .RspDepth(HRspDepth[i * 4 +: 4]), + .SpareReqW(1)) + u_hostfifo(.clk_i, + .rst_ni, + .tl_h_i(hreq_fifo_i), + .tl_h_o(tl_h_o[i]), + .tl_d_o(hreq_fifo_o[i]), + .tl_d_i(hrsp_fifo_i[i]), + .spare_req_i(1'b0), + .spare_req_o(), + .spare_rsp_i(1'b0), + .spare_rsp_o()); + end + + // Device Req/Rsp FIFO + tlul_fifo_sync_128 #(.ReqPass(DReqPass), + .RspPass(DRspPass), + .ReqDepth(DReqDepth), + .RspDepth(DRspDepth), + .SpareReqW(1)) + u_devicefifo(.clk_i, + .rst_ni, + .tl_h_i(dreq_fifo_i), + .tl_h_o(drsp_fifo_o), + .tl_d_o(tl_d_o), + .tl_d_i(tl_d_i), + .spare_req_i(1'b0), + .spare_req_o(), + .spare_rsp_i(1'b0), + .spare_rsp_o()); + + // Request Arbiter + for (genvar i = 0; i < M; i++) begin : gen_arbreqgnt + assign hrequest[i] = hreq_fifo_o[i].a_valid; + end + + assign arb_ready = drsp_fifo_o.a_ready; + + if (kelvin_tlul_pkg_128::ArbiterImpl == "PPC") begin : gen_arb_ppc + prim_arbiter_ppc #(.N(M), + .DW($bits(kelvin_tlul_pkg_128::tl_h2d_t))) + u_reqarb(.clk_i, + .rst_ni, + .req_chk_i(1'b0), + // TL-UL allows dropping valid without ready. See #3354. + .req_i(hrequest), + .data_i(hreq_fifo_o), + .gnt_o(hgrant), + .idx_o(), + .valid_o(arb_valid), + .data_o(arb_data), + .ready_i(arb_ready)); + end else if (kelvin_tlul_pkg_128::ArbiterImpl == "BINTREE") + begin : gen_tree_arb + prim_arbiter_tree #(.N(M), + .DW($bits(kelvin_tlul_pkg_128::tl_h2d_t))) + u_reqarb(.clk_i, + .rst_ni, + .req_chk_i(1'b0), + // TL-UL allows dropping valid without ready. See #3354. + .req_i(hrequest), + .data_i(hreq_fifo_o), + .gnt_o(hgrant), + .idx_o(), + .valid_o(arb_valid), + .data_o(arb_data), + .ready_i(arb_ready)); + end else begin : gen_unknown + `ASSERT_INIT(UnknownArbImpl_A, 0) + end + + logic [M - 1 : 0] hfifo_rspvalid; + logic [M - 1 : 0] dfifo_rspready; + logic [IDW - 1 : 0] hfifo_rspid; + logic dfifo_rspready_merged; + + // arb_data --> dreq_fifo_i + // dreq_fifo_i.hd_rspready <= dfifo_rspready + + assign dfifo_rspready_merged = |dfifo_rspready; + assign dreq_fifo_i = '{ + a_valid: arb_valid, + a_opcode: arb_data.a_opcode, + a_param: arb_data.a_param, + a_size: arb_data.a_size, + a_source: arb_data.a_source, + a_address: arb_data.a_address, + a_mask: arb_data.a_mask, + a_data: arb_data.a_data, + a_user: arb_data.a_user, + + d_ready: dfifo_rspready_merged + }; + + // Response ID steering + // drsp_fifo_o --> hrsp_fifo_i[i] + + // Response ID shifting before put into host fifo + assign hfifo_rspid = {{STIDW{1'b0}}, drsp_fifo_o.d_source[IDW - 1 : STIDW]}; + for (genvar i = 0; i < M; i++) begin : gen_idrouting + assign hfifo_rspvalid[i] = + drsp_fifo_o.d_valid & (drsp_fifo_o.d_source[0 +: STIDW] == i); + assign dfifo_rspready[i] = hreq_fifo_o[i].d_ready & + (drsp_fifo_o.d_source[0 +: STIDW] == i) & + drsp_fifo_o.d_valid; + + assign hrsp_fifo_i[i] = '{ + d_valid: hfifo_rspvalid[i], + d_opcode: drsp_fifo_o.d_opcode, + d_param: drsp_fifo_o.d_param, + d_size: drsp_fifo_o.d_size, + d_source: hfifo_rspid, + d_sink: drsp_fifo_o.d_sink, + d_data: drsp_fifo_o.d_data, + d_user: drsp_fifo_o.d_user, + d_error: drsp_fifo_o.d_error, + a_ready: hgrant[i] + }; + end + + // this assertion fails when rspid[0+:STIDW] not in [0..M-1] + `ASSERT(rspIdInRange, drsp_fifo_o.d_valid |-> + drsp_fifo_o.d_source[0 +: STIDW] >= 0 && + drsp_fifo_o.d_source[0 +: STIDW] < M) +endmodule
diff --git a/fpga/ip/rvv_core_mini_tlul/BUILD b/fpga/ip/rvv_core_mini_tlul/BUILD new file mode 100644 index 0000000..e8edeb8 --- /dev/null +++ b/fpga/ip/rvv_core_mini_tlul/BUILD
@@ -0,0 +1,42 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +genrule( + name = "rvv_core_mini_tlul_verilog", + srcs = ["//hdl/chisel/src/kelvin:RvvCoreMiniTlul.sv"], + outs = ["RvvCoreMiniTlul.sv"], + cmd = "cp $< $@", +) + +genrule( + name = "rvv_core_mini_tlul_core", + srcs = [ + "rvv_core_mini_tlul.core.tpl", + ":rvv_core_mini_tlul_verilog", + ], + outs = ["rvv_core_mini_tlul.core"], + cmd = "sed 's|__VERILOG_FILE__|RvvCoreMiniTlul.sv|' $(location rvv_core_mini_tlul.core.tpl) > $@", +) + +filegroup( + name = "rtl_files", + srcs = glob([ + "*.sv", + "*.core", + ]) + [ + ":RvvCoreMiniTlul.sv", + ], +)
diff --git a/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core b/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core new file mode 100644 index 0000000..0b58a34 --- /dev/null +++ b/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core
@@ -0,0 +1,31 @@ +CAPI=2: +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "google:kelvin:rvv_core_mini_tlul" +description: "RvvCoreMini with TileLink interface" + +filesets: + files_rtl: + depend: + - "lowrisc:prim:all" + - "lowrisc:prim_generic:all" + files: + - RvvCoreMiniTlul.sv: { file_type: systemVerilogSource } + file_type: systemVerilogSource + +targets: + default: + filesets: + - files_rtl
diff --git a/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core.tpl b/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core.tpl new file mode 100644 index 0000000..b48b939 --- /dev/null +++ b/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core.tpl
@@ -0,0 +1,31 @@ +CAPI=2: +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "google:kelvin:rvv_core_mini_tlul" +description: "RvvCoreMini with TileLink interface" + +filesets: + files_rtl: + depend: + - "lowrisc:prim:all" + - "lowrisc:prim_generic:all" + files: + - __VERILOG_FILE__: { file_type: systemVerilogSource } + file_type: systemVerilogSource + +targets: + default: + filesets: + - files_rtl
diff --git a/fpga/ip/sram/BUILD b/fpga/ip/sram/BUILD new file mode 100644 index 0000000..d78f918 --- /dev/null +++ b/fpga/ip/sram/BUILD
@@ -0,0 +1,23 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "rtl_files", + srcs = glob([ + "*.sv", + "*.core", + ]), +)
diff --git a/fpga/ip/sram/Sram.sv b/fpga/ip/sram/Sram.sv new file mode 100644 index 0000000..6cc1e3f --- /dev/null +++ b/fpga/ip/sram/Sram.sv
@@ -0,0 +1,40 @@ +// A simple SRAM model, rewritten for BRAM inference +module Sram + #(parameter int Width = 32, + parameter int Depth = 1024) + (input clk_i, + input req_i, + input we_i, + input [$clog2(Depth) - 1 : 0] addr_i, + input [Width - 1 : 0] wdata_i, + input [Width / 8 - 1 : 0] wmask_i, + output logic [Width - 1 : 0] rdata_o, + output logic rvalid_o); + + logic [Width - 1 : 0] mem[Depth - 1 : 0]; + logic [$clog2(Depth) - 1 : 0] raddr; + + assign rdata_o = mem[raddr]; + + always_ff @(posedge clk_i) begin + if (req_i) begin + if (we_i) begin + for (int i = 0; i < Width / 8; i++) begin + if (wmask_i[i]) begin + mem[addr_i][i * 8 +: 8] <= wdata_i[i * 8 +: 8]; + end + end + end + // The read address is registered to ensure a synchronous read. + raddr <= addr_i; + end + end + + // The rvalid signal is simply a delayed version of req_i. + always_ff @(posedge clk_i) begin + rvalid_o <= req_i; + end + + localparam MemInitFile = ""; +`include "prim_util_memload.svh" +endmodule \ No newline at end of file
diff --git a/fpga/ip/sram/sram.core b/fpga/ip/sram/sram.core new file mode 100644 index 0000000..0efd821 --- /dev/null +++ b/fpga/ip/sram/sram.core
@@ -0,0 +1,14 @@ +CAPI=2: +name: "kelvinv2:ip:sram:0.1" +description: "SRAM for Kelvin" + +filesets: + rtl: + files: + - Sram.sv + file_type: systemVerilogSource + +targets: + default: + filesets: + - rtl
diff --git a/fpga/ip/tlul_width_bridge/BUILD b/fpga/ip/tlul_width_bridge/BUILD new file mode 100644 index 0000000..d78f918 --- /dev/null +++ b/fpga/ip/tlul_width_bridge/BUILD
@@ -0,0 +1,23 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "rtl_files", + srcs = glob([ + "*.sv", + "*.core", + ]), +)
diff --git a/fpga/ip/tlul_width_bridge/tlul_device_downsizer.core b/fpga/ip/tlul_width_bridge/tlul_device_downsizer.core new file mode 100644 index 0000000..5cc6203 --- /dev/null +++ b/fpga/ip/tlul_width_bridge/tlul_device_downsizer.core
@@ -0,0 +1,32 @@ +CAPI=2: +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "kelvinv2:ip:tlul_device_downsizer" +description: "TL-UL Device Downsizer" + +filesets: + files_rtl: + depend: + - "lowrisc:prim:all" + - "lowrisc:tlul:headers" + - "kelvinv2:ip:kelvin_tlul:0.1" + files: + - tlul_device_downsizer.sv: { file_type: systemVerilogSource } + file_type: systemVerilogSource + +targets: + default: + filesets: + - files_rtl
diff --git a/fpga/ip/tlul_width_bridge/tlul_device_downsizer.sv b/fpga/ip/tlul_width_bridge/tlul_device_downsizer.sv new file mode 100644 index 0000000..524fb6b --- /dev/null +++ b/fpga/ip/tlul_width_bridge/tlul_device_downsizer.sv
@@ -0,0 +1,145 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +`include "prim_assert.sv" + +module tlul_device_downsizer + #(parameter int AddrWidth = 32) + (input clk_i, + input rst_ni, + + // Slave (Upsizer-facing) TL-UL Interface + input kelvin_tlul_pkg_128::tl_h2d_t s_tl_i, + output kelvin_tlul_pkg_128::tl_d2h_t s_tl_o, + + // Master (Crossbar-facing) TL-UL Interface + output kelvin_tlul_pkg_32::tl_h2d_t m_tl_o, + input kelvin_tlul_pkg_32::tl_d2h_t m_tl_i); + + localparam int SlaveDataWidth = 128; + localparam int MasterDataWidth = 32; + localparam int LaneWidth = MasterDataWidth / 8; + localparam int NumLanes = SlaveDataWidth / MasterDataWidth; + localparam int LaneIndexWidth = $clog2(NumLanes); + + // Response path skid buffer + kelvin_tlul_pkg_128::tl_d2h_t d_skid_reg; + logic d_skid_valid_q, d_skid_valid_d; + logic d_skid_ready; + + // Internal signals + logic [LaneIndexWidth - 1 : 0] lane_idx; + logic [LaneIndexWidth - 1 : 0] lane_idx_reg; + logic [MasterDataWidth - 1 : 0] m_a_data; + logic [MasterDataWidth / 8 - 1 : 0] m_a_mask; + logic [1 : 0] a_size_from_mask; + + // Lane index calculation from mask + always_comb begin + // Priority encode the mask to find the active lane + unique case (1'b1) + |s_tl_i.a_mask[3 : 0]: + lane_idx = 2'b00; + |s_tl_i.a_mask[7 : 4]: + lane_idx = 2'b01; + |s_tl_i.a_mask[11 : 8]: + lane_idx = 2'b10; + |s_tl_i.a_mask[15 : 12]: + lane_idx = 2'b11; + default: + lane_idx = 2'b00; // Should not happen for valid requests + endcase + end + + // Master port data and mask generation + always_comb begin + m_a_data = s_tl_i.a_data >> (lane_idx * MasterDataWidth); + m_a_mask = s_tl_i.a_mask >> (lane_idx * LaneWidth); + end + + // Calculate master port a_size from mask + always_comb begin + case ($countones(m_a_mask)) + 1: + a_size_from_mask = 2'h0; + 2: + a_size_from_mask = 2'h1; + 3, 4: + a_size_from_mask = 2'h2; + default: + a_size_from_mask = 2'h0; + endcase + end + + logic [15 : 0] dbg_s_tl_i_a_size = s_tl_i.a_size; + // Master port connections + assign m_tl_o.a_valid = s_tl_i.a_valid; + assign m_tl_o.a_opcode = s_tl_i.a_opcode; + assign m_tl_o.a_param = s_tl_i.a_param; + assign m_tl_o.a_size = + (s_tl_i.a_opcode == tlul_pkg::Get) ? 2 : a_size_from_mask; + assign m_tl_o.a_address = {s_tl_i.a_address[AddrWidth - 1 : 4], lane_idx, + 2'b00}; + assign m_tl_o.a_source = s_tl_i.a_source; + assign m_tl_o.a_data = m_a_data; + assign m_tl_o.a_mask = m_a_mask; + assign m_tl_o.a_user = s_tl_i.a_user; + assign m_tl_o.d_ready = d_skid_ready; + + // Slave port connections + assign s_tl_o.d_opcode = d_skid_reg.d_opcode; + assign s_tl_o.d_param = d_skid_reg.d_param; + assign s_tl_o.d_sink = d_skid_reg.d_sink; + assign s_tl_o.d_source = d_skid_reg.d_source; + assign s_tl_o.d_data = d_skid_reg.d_data; + assign s_tl_o.d_error = d_skid_reg.d_error; + assign s_tl_o.d_user = d_skid_reg.d_user; + assign s_tl_o.d_size = d_skid_reg.d_size; + assign s_tl_o.d_valid = d_skid_valid_q; + assign s_tl_o.a_ready = m_tl_i.a_ready; + logic d_skid_reg_size = d_skid_reg.d_size; + + // Skid buffer logic + assign d_skid_ready = !d_skid_valid_q || s_tl_i.d_ready; + + always_comb begin + d_skid_valid_d = d_skid_valid_q; + if (d_skid_ready) begin + d_skid_valid_d = m_tl_i.d_valid; + end + end + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + d_skid_valid_q <= 1'b0; + d_skid_reg <= '0; + lane_idx_reg <= '0; + end else begin + d_skid_valid_q <= d_skid_valid_d; + if (d_skid_ready && m_tl_i.d_valid) begin + d_skid_reg.d_opcode <= m_tl_i.d_opcode; + d_skid_reg.d_param <= m_tl_i.d_param; + d_skid_reg.d_sink <= m_tl_i.d_sink; + d_skid_reg.d_source <= m_tl_i.d_source; + d_skid_reg.d_data <= m_tl_i.d_data << (lane_idx_reg * MasterDataWidth); + d_skid_reg.d_error <= m_tl_i.d_error; + d_skid_reg.d_user <= m_tl_i.d_user; + d_skid_reg.d_size <= m_tl_i.d_size; + end + if (s_tl_i.a_valid && s_tl_o.a_ready) begin + lane_idx_reg <= lane_idx; + end + end + end +endmodule
diff --git a/fpga/ip/tlul_width_bridge/tlul_host_upsizer.core b/fpga/ip/tlul_width_bridge/tlul_host_upsizer.core new file mode 100644 index 0000000..3c25112 --- /dev/null +++ b/fpga/ip/tlul_width_bridge/tlul_host_upsizer.core
@@ -0,0 +1,32 @@ +CAPI=2: +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "kelvinv2:ip:tlul_host_upsizer" +description: "TL-UL Host Upsizer" + +filesets: + files_rtl: + depend: + - "lowrisc:prim:all" + - "lowrisc:tlul:headers" + - "kelvinv2:ip:kelvin_tlul:0.1" + files: + - tlul_host_upsizer.sv: { file_type: systemVerilogSource } + file_type: systemVerilogSource + +targets: + default: + filesets: + - files_rtl
diff --git a/fpga/ip/tlul_width_bridge/tlul_host_upsizer.sv b/fpga/ip/tlul_width_bridge/tlul_host_upsizer.sv new file mode 100644 index 0000000..6958dfd --- /dev/null +++ b/fpga/ip/tlul_width_bridge/tlul_host_upsizer.sv
@@ -0,0 +1,116 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +`include "prim_assert.sv" + +module tlul_host_upsizer + #(parameter int AddrWidth = 32) + (input clk_i, + input rst_ni, + + // Slave (Host-facing) TL-UL Interface + input kelvin_tlul_pkg_32::tl_h2d_t s_tl_i, + output kelvin_tlul_pkg_32::tl_d2h_t s_tl_o, + + // Master (Crossbar-facing) TL-UL Interface + output kelvin_tlul_pkg_128::tl_h2d_t m_tl_o, + input kelvin_tlul_pkg_128::tl_d2h_t m_tl_i); + + localparam int SlaveDataWidth = 32; + localparam int MasterDataWidth = 128; + localparam int LaneWidth = SlaveDataWidth / 8; + localparam int NumLanes = MasterDataWidth / SlaveDataWidth; + localparam int LaneIndexWidth = $clog2(NumLanes); + + // Response path skid buffer + kelvin_tlul_pkg_32::tl_d2h_t d_skid_reg; + logic d_skid_valid_q, d_skid_valid_d; + logic d_skid_ready; + + // Internal signals + logic [LaneIndexWidth - 1 : 0] lane_idx; + logic [LaneIndexWidth - 1 : 0] lane_idx_reg; + logic [MasterDataWidth - 1 : 0] m_a_data; + logic [MasterDataWidth / 8 - 1 : 0] m_a_mask; + + // Lane index calculation + assign lane_idx = s_tl_i.a_address[LaneIndexWidth + 1 : 2]; + + // Master port data and mask generation + always_comb begin + m_a_data = '0; + m_a_mask = '0; + m_a_data[lane_idx * SlaveDataWidth +: SlaveDataWidth] = s_tl_i.a_data; + m_a_mask[lane_idx * LaneWidth +: LaneWidth] = s_tl_i.a_mask; + end + + // Master port connections + assign m_tl_o.a_valid = s_tl_i.a_valid; + assign m_tl_o.a_opcode = s_tl_i.a_opcode; + assign m_tl_o.a_param = s_tl_i.a_param; + assign m_tl_o.a_size = s_tl_i.a_size; + assign m_tl_o.a_address = { + s_tl_i.a_address[AddrWidth - 1 : LaneIndexWidth + 2], + {(LaneIndexWidth + 2){1'b0}}}; + assign m_tl_o.a_source = s_tl_i.a_source; + assign m_tl_o.a_data = m_a_data; + assign m_tl_o.a_mask = m_a_mask; + assign m_tl_o.a_user = s_tl_i.a_user; + assign m_tl_o.d_ready = d_skid_ready; + + // Slave port connections + assign s_tl_o.d_opcode = d_skid_reg.d_opcode; + assign s_tl_o.d_param = d_skid_reg.d_param; + assign s_tl_o.d_sink = d_skid_reg.d_sink; + assign s_tl_o.d_source = d_skid_reg.d_source; + assign s_tl_o.d_data = d_skid_reg.d_data; + assign s_tl_o.d_error = d_skid_reg.d_error; + assign s_tl_o.d_user = d_skid_reg.d_user; + assign s_tl_o.d_size = d_skid_reg.d_size; + assign s_tl_o.d_valid = d_skid_valid_q; + assign s_tl_o.a_ready = m_tl_i.a_ready; + + // Skid buffer logic + assign d_skid_ready = !d_skid_valid_q || s_tl_i.d_ready; + + always_comb begin + d_skid_valid_d = d_skid_valid_q; + if (d_skid_ready) begin + d_skid_valid_d = m_tl_i.d_valid; + end + end + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + d_skid_valid_q <= 1'b0; + d_skid_reg <= '0; + lane_idx_reg <= '0; + end else begin + d_skid_valid_q <= d_skid_valid_d; + if (d_skid_ready && m_tl_i.d_valid) begin + d_skid_reg.d_opcode <= m_tl_i.d_opcode; + d_skid_reg.d_param <= m_tl_i.d_param; + d_skid_reg.d_sink <= m_tl_i.d_sink; + d_skid_reg.d_source <= m_tl_i.d_source; + d_skid_reg.d_data <= m_tl_i.d_data >> (lane_idx_reg * SlaveDataWidth); + d_skid_reg.d_error <= m_tl_i.d_error; + d_skid_reg.d_user <= m_tl_i.d_user; + d_skid_reg.d_size <= m_tl_i.d_size; + end + if (s_tl_i.a_valid && s_tl_o.a_ready) begin + lane_idx_reg <= lane_idx; + end + end + end +endmodule \ No newline at end of file