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