feat(fpga): Add Kelvin SoC top-level and build infrastructure
Change-Id: I93885002bc8675f17f62d75440fa39ece7ddc3e0
diff --git a/fpga/BUILD b/fpga/BUILD
index 36a1e8d..f221189 100644
--- a/fpga/BUILD
+++ b/fpga/BUILD
@@ -12,3 +12,227 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+load("@bazel_skylib//rules:common_settings.bzl", "string_list_flag")
+load("@lowrisc_opentitan_gh//rules:fusesoc.bzl", "fusesoc_build")
+load("//rules:kelvin_v2.bzl", "kelvin_v2_binary")
+load("//rules:utils.bzl", "cc_embed_data")
+load(":rules.bzl", "tlgen_rule")
+
+package(default_visibility = ["//visibility:public"])
+
+_CLOCK_FREQUENCY_MHZ = "10"
+
+# This is the tlgen.py script itself
+py_binary(
+ name = "tlgen_tool",
+ srcs = ["@lowrisc_opentitan_gh//util:tlgen.py"],
+ main = "@lowrisc_opentitan_gh//util:tlgen.py",
+ deps = [
+ "@kelvin_pip_deps_hjson//:pkg",
+ "@lowrisc_opentitan_gh//util/tlgen",
+ ],
+)
+
+tlgen_rule(
+ name = "tl_crossbar_generated",
+ topcfg = "tl_config.hjson",
+)
+
+py_binary(
+ name = "post_process_xbar",
+ srcs = ["post_process_xbar.py"],
+)
+
+genrule(
+ name = "tl_crossbar_processed",
+ srcs = [":tl_crossbar_generated"],
+ outs = [
+ "tl_crossbar_processed_out",
+ ],
+ cmd = "$(execpath :post_process_xbar) " +
+ "--input-dir $(location :tl_crossbar_generated) " +
+ "--output-dir $(location tl_crossbar_processed_out) " +
+ "--cores kelvinv2:ip:kelvin_tlul:0.1",
+ tools = [":post_process_xbar"],
+)
+
+filegroup(
+ name = "tl_crossbar",
+ srcs = [
+ ":tl_crossbar_processed_out",
+ ],
+)
+
+filegroup(
+ name = "tl_crossbar_core",
+ srcs = [":tl_crossbar"],
+)
+
+filegroup(
+ name = "rtl_files",
+ srcs = glob(["**/*.sv"]) + glob(["**/*.core"]) + [
+ "//fpga/ip/kelvin_tlul:rtl_files",
+ "//fpga/ip/rv_core_ibex:rtl_files",
+ "//fpga/ip/rvv_core_mini_tlul:rtl_files",
+ "//fpga/ip/sram:rtl_files",
+ "//fpga/ip/tlul_width_bridge:rtl_files",
+ "//fpga/rtl:rtl_files",
+ ],
+)
+
+string_list_flag(
+ name = "verilator_options",
+ build_setting_default = [
+ "-Wno-ALWCOMBORDER",
+ "-Wno-WIDTHEXPAND",
+ "-Wno-WIDTHTRUNC",
+ "-Wno-UNUSEDSIGNAL",
+ "-Wno-UNUSEDPARAM",
+ "-Wno-VARHIDDEN",
+ # TODO: The MULTIDRIVEN warnings are caused by the `prim_arbiter_fixed`
+ # module and the `tlul_fifo_sync` module. We should investigate these
+ # modules and fix the underlying issues.
+ # "-Wno-MULTIDRIVEN",
+ "-Wno-UNOPTTHREADS",
+ "-Wno-GENUNNAMED",
+ "-DRVFI",
+ "-DUSE_GENERIC",
+ ],
+)
+
+string_list_flag(
+ name = "make_options",
+ build_setting_default = [
+ "-j16",
+ ],
+)
+
+cc_embed_data(
+ name = "add_uint32_m1_bin_header",
+ srcs = [":add_uint32_m1_bin"],
+ var_name = "add_uint32_m1_bin",
+)
+
+kelvin_v2_binary(
+ name = "ibex_boot_rom",
+ srcs = [
+ "sw/ibex_boot_rom.S",
+ "sw/main.cc",
+ ],
+ hdrs = [":add_uint32_m1_bin_header"],
+ copts = ["-DCLOCK_FREQUENCY_MHZ=" + _CLOCK_FREQUENCY_MHZ],
+ linker_script = "sw/ibex_boot_rom.ld",
+)
+
+kelvin_v2_binary(
+ name = "add_uint32_m1",
+ srcs = ["sw/add_uint32_m1.cc"],
+ copts = ["-DCLOCK_FREQUENCY_MHZ=" + _CLOCK_FREQUENCY_MHZ],
+)
+
+filegroup(
+ name = "ibex_boot_rom_bin",
+ srcs = [":ibex_boot_rom"],
+ output_group = "bin_file",
+)
+
+filegroup(
+ name = "add_uint32_m1_bin",
+ srcs = [":add_uint32_m1"],
+ output_group = "bin_file",
+)
+
+KELVIN_SOC_CORES = [
+ "//fpga/ip/rv_core_ibex:rv_core_ibex.core",
+ ":kelvin_soc.core",
+ ":kelvin_soc_pkg.core",
+ ":racl_pkg.core",
+ ":tl_crossbar_core",
+ "@lowrisc_opentitan_gh//hw:check_tool_requirements.core",
+]
+
+KELVIN_SOC_SRCS = [
+ ":rtl_files",
+ "@lowrisc_opentitan_gh//hw/dv/sv:dv_macros",
+ "@lowrisc_opentitan_gh//hw/dv:verilator_files",
+ "@lowrisc_opentitan_gh//hw:check_tool_requirements.py",
+ "@lowrisc_opentitan_gh//hw:lint/tools/verilator/common.vlt",
+ "@lowrisc_opentitan_gh//hw:lint/tools/verilator/comportable.vlt",
+ "@lowrisc_opentitan_gh//hw:rtl_files",
+ "@lowrisc_opentitan_gh//hw:tool_requirements.py",
+ "@lowrisc_opentitan_gh//hw:vendor/lint/pulp_riscv_dbg.vlt",
+ "@lowrisc_opentitan_gh//hw:vendor/pulp_riscv_dbg/debug_rom/debug_rom.sv",
+ "@lowrisc_opentitan_gh//hw:vendor/pulp_riscv_dbg/debug_rom/debug_rom_one_scratch.sv",
+ "@lowrisc_opentitan_gh//hw:vendor/pulp_riscv_dbg/src/dm_csrs.sv",
+ "@lowrisc_opentitan_gh//hw:vendor/pulp_riscv_dbg/src/dm_mem.sv",
+ "@lowrisc_opentitan_gh//hw:vendor/pulp_riscv_dbg/src/dm_pkg.sv",
+ "@lowrisc_opentitan_gh//hw:vendor/pulp_riscv_dbg/src/dm_sba.sv",
+ "@lowrisc_opentitan_gh//hw:vendor/pulp_riscv_dbg/src/dm_top.sv",
+ "@lowrisc_opentitan_gh//hw:vendor/pulp_riscv_dbg/src/dmi_cdc.sv",
+ "@lowrisc_opentitan_gh//hw:vendor/pulp_riscv_dbg/src/dmi_jtag.sv",
+ "@lowrisc_opentitan_gh//hw:vendor/pulp_riscv_dbg/src/dmi_jtag_tap.sv",
+ "@lowrisc_opentitan_gh//hw:verilator_files",
+ "main.cc",
+]
+
+fusesoc_build(
+ name = "build_chip_verilator",
+ srcs = KELVIN_SOC_SRCS + [
+ ":ibex_boot_rom.vmem",
+ "@lowrisc_opentitan_gh//hw:dpi_files",
+ ],
+ cores = KELVIN_SOC_CORES + [
+ ":chip_verilator.core",
+ "@lowrisc_opentitan_gh//hw/dv:dpi/uartdpi/uartdpi.core",
+ "@lowrisc_opentitan_gh//hw/dv:dpi/uartdpi/uartdpi_sv.core",
+ ],
+ flags = [
+ "--MemInitFile=$(location :ibex_boot_rom.vmem)",
+ "--ClockFrequencyMhz=" + _CLOCK_FREQUENCY_MHZ,
+ ],
+ make_options = ":make_options",
+ output_groups = {
+ "binary": ["com.google.kelvin_fpga_chip_verilator_0.1/sim-verilator/Vchip_verilator"],
+ },
+ systems = ["com.google.kelvin:fpga:chip_verilator:0.1"],
+ target = "sim",
+ verilator_options = ":verilator_options",
+ tags = ["manual"],
+)
+
+_PREFIX = "../../../../../../../../.."
+
+# "../../../../../../../"bazel-out/k8-fastbuild-ST-be77d280135c/bin/fpga
+# "../../../../../../../"bazel-out/k8-fastbuild-ST-be77d280135c/bin/fpga/wfi.vmem
+_IBEX_BOOT_ROM_VMEM = ":ibex_boot_rom.vmem"
+
+IBEX_BOOT_ROM_VMEM_PATH = "{}/$(location {})".format(_PREFIX, _IBEX_BOOT_ROM_VMEM)
+
+fusesoc_build(
+ name = "build_chip_nexus_bitstream",
+ srcs = KELVIN_SOC_SRCS + [
+ "pins.xdc",
+ "vivado_setup_hooks.tcl",
+ ":ibex_boot_rom.vmem",
+ ],
+ cores = KELVIN_SOC_CORES + [":chip_nexus.core"],
+ flags = [
+ "--MemInitFile=" + IBEX_BOOT_ROM_VMEM_PATH,
+ "--ClockFrequencyMhz=" + _CLOCK_FREQUENCY_MHZ,
+ ],
+ output_groups = {
+ # /home/atv/.cache/bazel/_bazel_atv/cb485e413f28624e05d5dee28237de6d/sandbox/
+ # linux-sandbox/34/execroot/kelvin_hw/bazel-out/k8-fastbuild/bin/fpga/
+ # build.build_chip_nexus_bitstream/
+ # com.google.kelvin_fpga_chip_nexus_0.1/synth-vivado/com.google.kelvin_fpga_chip_nexus_0.1.runs/impl_1/chip_nexus.bit
+ "bitstream": ["com.google.kelvin_fpga_chip_nexus_0.1/synth-vivado/com.google.kelvin_fpga_chip_nexus_0.1.runs/impl_1/chip_nexus.bit"],
+ # /home/atv/.cache/bazel/_bazel_atv/cb485e413f28624e05d5dee28237de6d/sandbox/
+ # linux-sandbox/34/execroot/kelvin_hw/bazel-out/k8-fastbuild/bin/fpga/
+ # build.build_chip_nexus_bitstream/
+ # com.google.kelvin_fpga_chip_nexus_0.1/synth-vivado/com.google.kelvin_fpga_chip_nexus_0.1.runs/
+ "logs": ["com.google.kelvin_fpga_chip_nexus_0.1/synth-vivado/com.google.kelvin_fpga_chip_nexus_0.1.runs/"],
+ },
+ systems = ["com.google.kelvin:fpga:chip_nexus:0.1"],
+ target = "synth",
+ tags = ["manual"],
+)
diff --git a/fpga/chip_nexus.core b/fpga/chip_nexus.core
new file mode 100644
index 0000000..912ddaf
--- /dev/null
+++ b/fpga/chip_nexus.core
@@ -0,0 +1,63 @@
+CAPI=2:
+name: "com.google.kelvin:fpga:chip_nexus:0.1"
+description: "Nexus-specific top-level for Kelvin."
+
+filesets:
+ files_rtl:
+ depend:
+ - com.google.kelvin:fpga:kelvin_soc
+ files:
+ - rtl/chip_nexus.sv
+ - rtl/clkgen_wrapper.sv
+ - rtl/clkgen_xilultrascaleplus.sv
+ file_type: systemVerilogSource
+
+ files_constraints:
+ files:
+ - pins.xdc
+ file_type: xdc
+
+ files_tcl:
+ files:
+ - vivado_setup_hooks.tcl: { file_type: tclSource }
+
+parameters:
+ ClockFrequencyMhz:
+ datatype: int
+ description: "Target clock frequency in MHz."
+ default: 10
+ paramtype: vlogparam
+ MemInitFile:
+ datatype: str
+ description: Path to ROM
+ default: "fpga/wfi.bin"
+ paramtype: vlogparam
+ USE_GENERIC:
+ datatype: bool
+ description: "Use generic primitives"
+ default: false
+ paramtype: vlogdefine
+ FPGA_XILINX:
+ datatype: bool
+ description: "Use Xilinx FPGA primitives"
+ default: false
+ paramtype: vlogdefine
+
+targets:
+ default: &default_target
+ filesets:
+ - files_rtl
+ - files_constraints
+ - files_tcl
+ synth:
+ <<: *default_target
+ toplevel: chip_nexus
+ default_tool: vivado
+ parameters:
+ - ClockFrequencyMhz
+ - MemInitFile
+ - USE_GENERIC=true
+ - FPGA_XILINX=true
+ tools:
+ vivado:
+ part: "xcvu13p-fhga2104-2-e"
diff --git a/fpga/chip_verilator.core b/fpga/chip_verilator.core
new file mode 100644
index 0000000..0971b5a
--- /dev/null
+++ b/fpga/chip_verilator.core
@@ -0,0 +1,60 @@
+CAPI=2:
+name: "com.google.kelvin:fpga:chip_verilator:0.1"
+description: "Verilator-specific top-level for Kelvin."
+
+filesets:
+ files_rtl:
+ depend:
+ - com.google.kelvin:fpga:kelvin_soc:0.2
+ - kelvinv2:ip:sram:0.1
+ - kelvinv2:ip:kelvin_tlul:0.1
+ - lowrisc:dv_dpi_c:uartdpi:0.1
+ - lowrisc:dv_dpi_sv:uartdpi:0.1
+ files:
+ - rtl/chip_verilator.sv
+ file_type: systemVerilogSource
+ sim_src:
+ depend:
+ - lowrisc:dv_verilator:memutil_verilator
+ - lowrisc:dv_verilator:simutil_verilator
+ files:
+ - main.cc
+ file_type: cppSource
+
+parameters:
+ ClockFrequencyMhz:
+ datatype: int
+ description: "Target clock frequency in MHz."
+ default: 10
+ paramtype: vlogparam
+ MemInitFile:
+ datatype: str
+ description: Path to ROM
+ default: "fpga/wfi.bin"
+ paramtype: vlogparam
+
+targets:
+ default: &default_target
+ filesets:
+ - files_rtl
+ sim:
+ <<: *default_target
+ filesets:
+ - files_rtl
+ - sim_src
+ toplevel: chip_verilator
+ default_tool: verilator
+ parameters:
+ - ClockFrequencyMhz
+ - MemInitFile
+ tools:
+ verilator:
+ mode: cc
+ verilator_options:
+ - '--trace'
+ - '--trace-fst'
+ - '-CFLAGS "-DTOPLEVEL_NAME=chip_verilator -std=c++17 -Wall -DVM_TRACE_FMT_FST"'
+ - '-LDFLAGS "-lelf -lutil"'
+ - '-Wall'
+ - '--threads 16'
+ - '-Wno-fatal'
diff --git a/fpga/kelvin_soc.core b/fpga/kelvin_soc.core
new file mode 100644
index 0000000..3db97b4
--- /dev/null
+++ b/fpga/kelvin_soc.core
@@ -0,0 +1,99 @@
+CAPI=2:
+name: "com.google.kelvin:fpga:kelvin_soc:0.2"
+description: "The Kelvin SoC for FPGA."
+
+filesets:
+ rtl:
+ depend:
+ - lowrisc:ip:xbar_kelvin_soc_xbar:0.1
+ - com.google.kelvin:fpga:kelvin_soc_pkg:0.1
+ - com.google.kelvin:fpga:racl_pkg:0.1
+ - lowrisc:ip:uart:0.1
+ - lowrisc:prim:rom_adv
+ - lowrisc:prim_generic:rom
+ - lowrisc:tlul:adapter_sram
+ - lowrisc:ip:spi_device:0.1
+ - lowrisc:kelvin_ip:rv_core_ibex:0.1
+ - google:kelvin:rvv_core_mini_tlul
+ - kelvinv2:ip:tlul_host_upsizer
+ - kelvinv2:ip:tlul_device_downsizer
+ - lowrisc:ibex:ibex_tracer
+ - kelvinv2:ip:sram:0.1
+ - kelvinv2:ip:kelvin_tlul:0.1
+ files:
+ - rtl/kelvin_soc.sv
+ file_type: systemVerilogSource
+
+ sim_src:
+ depend:
+ - lowrisc:dv_verilator:memutil_verilator
+ - lowrisc:dv_verilator:simutil_verilator
+ files:
+ - main.cc
+ file_type: cppSource
+
+mapping:
+ "lowrisc:virtual_constants:top_racl_pkg": "com.google.kelvin:fpga:racl_pkg:0.1"
+
+parameters:
+ ClockFrequencyMhz:
+ datatype: int
+ description: "Target clock frequency in MHz."
+ default: 10
+ paramtype: vlogparam
+ MemInitFile:
+ datatype: str
+ description: Path to ROM
+ default: "fpga/wfi.bin"
+ paramtype: vlogparam
+ USE_GENERIC:
+ datatype: bool
+ description: "Use generic primitives"
+ default: false
+ paramtype: vlogdefine
+ FPGA_XILINX:
+ datatype: bool
+ description: "Use Xilinx FPGA primitives"
+ default: false
+ paramtype: vlogdefine
+
+targets:
+ default: &default
+ filesets:
+ - rtl
+
+ sim:
+ <<: *default
+ filesets:
+ - rtl
+ - sim_src
+ default_tool: verilator
+ description: "Simulation target for the Kelvin SoC"
+ parameters:
+ - MemInitFile
+ tools:
+ verilator:
+ mode: cc
+ verilator_options:
+ - '--trace'
+ - '--trace-fst'
+ - '-CFLAGS "-DTOPLEVEL_NAME=kelvin_soc -std=c++17 -Wall -DVM_TRACE_FMT_FST"'
+ - '-LDFLAGS "-lelf"'
+ - '-Wall'
+ - '--threads 16'
+ - '-Wno-fatal'
+
+ synth:
+ <<: *default
+ default_tool: vivado
+ filesets:
+ - rtl
+ parameters:
+ - MemInitFile
+ - USE_GENERIC=true
+ - FPGA_XILINX=true
+ tools:
+ vivado:
+ part: "xcvu13p-fhga2104-2-e"
+ STEPS.SYNTH_DESIGN.TCL.PRE:
+ - vivado_pre_synth.tcl
\ No newline at end of file
diff --git a/fpga/kelvin_soc_pkg.core b/fpga/kelvin_soc_pkg.core
new file mode 100644
index 0000000..937c982
--- /dev/null
+++ b/fpga/kelvin_soc_pkg.core
@@ -0,0 +1,30 @@
+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: "com.google.kelvin:fpga:kelvin_soc_pkg:0.1"
+description: "Toplevel-wide constants for the Kelvin SoC"
+virtual:
+ - lowrisc:virtual_constants:top_pkg
+
+filesets:
+ files_rtl:
+ files:
+ - rtl/top_pkg.sv
+ file_type: systemVerilogSource
+
+targets:
+ default:
+ filesets:
+ - files_rtl
diff --git a/fpga/main.cc b/fpga/main.cc
new file mode 100644
index 0000000..3e1d352
--- /dev/null
+++ b/fpga/main.cc
@@ -0,0 +1,56 @@
+// 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 <iostream>
+
+#include "verilated_toplevel.h"
+#include "verilator_memutil.h"
+#include "verilator_sim_ctrl.h"
+
+int main(int argc, char **argv) {
+ chip_verilator top;
+ VerilatorMemUtil memutil;
+ VerilatorSimCtrl &simctrl = VerilatorSimCtrl::GetInstance();
+ simctrl.SetTop(&top, &top.clk_i, &top.rst_ni,
+ VerilatorSimCtrlFlags::ResetPolarityNegative);
+
+ // NB: Final parameter here is "width" of your memory, penultimate parameter
+ // is "depth".
+ MemArea rom("TOP.chip_verilator.i_kelvin_soc.i_rom.u_prim_rom", 0x8000 / 4,
+ 4);
+ MemArea sram("TOP.chip_verilator.i_kelvin_soc.i_sram", 0x400000 / 4, 4);
+ MemArea itcm(
+ "TOP.chip_verilator.i_kelvin_soc.i_kelvin_core.coreAxi.itcm.sram."
+ "sramModules_0",
+ 0x2000 / 16, 16);
+ MemArea dtcm(
+ "TOP.chip_verilator.i_kelvin_soc.i_kelvin_core.coreAxi.dtcm.sram."
+ "sramModules_0",
+ 0x8000 / 16, 16);
+
+ memutil.RegisterMemoryArea("rom", 0x10000000, &rom);
+ memutil.RegisterMemoryArea("sram", 0x20000000, &sram);
+ memutil.RegisterMemoryArea("itcm", 0x00000000, &itcm);
+ memutil.RegisterMemoryArea("dtcm", 0x00010000, &dtcm);
+ simctrl.RegisterExtension(&memutil);
+
+ simctrl.SetInitialResetDelay(20000);
+ simctrl.SetResetDuration(10);
+
+ std::cout << "Simulation of Kelvin SoC" << std::endl
+ << "======================" << std::endl
+ << std::endl;
+
+ return simctrl.Exec(argc, argv).first;
+}
diff --git a/fpga/pins.xdc b/fpga/pins.xdc
new file mode 100644
index 0000000..1f881ac
--- /dev/null
+++ b/fpga/pins.xdc
@@ -0,0 +1,35 @@
+# Clock Signal
+create_clock -period 10.00 -name sys_clk_pin -waveform {0 5} [get_ports clk_p_i]
+set_property -dict { PACKAGE_PIN U13 IOSTANDARD DIFF_SSTL18_I } [get_ports { clk_p_i }];
+set_property -dict { PACKAGE_PIN T13 IOSTANDARD DIFF_SSTL18_I } [get_ports { clk_n_i }];
+
+# Generated Clocks
+create_generated_clock -name clk_main [get_pin i_clkgen/i_clkgen/pll/CLKOUT0]
+create_generated_clock -name clk_48MHz [get_pin i_clkgen/i_clkgen/pll/CLKOUT1]
+create_generated_clock -name clk_aon [get_pin i_clkgen/i_clkgen/pll/CLKOUT4]
+
+# Reset
+set_property -dict { PACKAGE_PIN AR19 IOSTANDARD LVCMOS18 } [get_ports { rst_ni }];
+
+# SPI
+create_clock -period 83.333 -name spi_clk_i -waveform {0 41.667} [get_ports spi_clk_i]
+set_property -dict { PACKAGE_PIN AV19 IOSTANDARD LVCMOS18 } [get_ports { spi_clk_i }];
+
+# UART0
+set_property -dict { PACKAGE_PIN BF20 IOSTANDARD LVCMOS18 } [get_ports { uart_tx_o[0] }];
+set_property -dict { PACKAGE_PIN BD20 IOSTANDARD LVCMOS18 } [get_ports { uart_rx_i[0] }];
+
+# UART1
+set_property -dict { PACKAGE_PIN R23 IOSTANDARD LVCMOS18 } [get_ports { uart_tx_o[1] }];
+set_property -dict { PACKAGE_PIN T23 IOSTANDARD LVCMOS18 } [get_ports { uart_rx_i[1] }];
+
+# LEDs
+set_property -dict { PACKAGE_PIN T31 DRIVE 8 IOSTANDARD LVCMOS12 } [get_ports { io_halted }];
+set_property -dict { PACKAGE_PIN P31 DRIVE 8 IOSTANDARD LVCMOS12 } [get_ports { io_fault }];
+set_property -dict { PACKAGE_PIN N37 DRIVE 8 IOSTANDARD LVCMOS12 } [get_ports { io_halted_n }];
+set_property -dict { PACKAGE_PIN M38 DRIVE 8 IOSTANDARD LVCMOS12 } [get_ports { io_fault_n }];
+
+# Asynchronous Clock Groups
+set_clock_groups -asynchronous \
+ -group {clk_main clk_48MHz clk_aon} \
+ -group {spi_clk_i}
diff --git a/fpga/post_process_xbar.py b/fpga/post_process_xbar.py
new file mode 100644
index 0000000..20179ab
--- /dev/null
+++ b/fpga/post_process_xbar.py
@@ -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.
+"""A script to post-process the output of tlgen.
+
+This script takes an input directory and an output directory. It copies the
+contents of the input directory to the output directory, and then modifies the
+generated SystemVerilog file to use the correct TileLink types.
+"""
+
+import argparse
+import os
+import re
+import shutil
+import stat
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument(
+ "--input-dir",
+ required=True,
+ help="The input directory.",
+ )
+ parser.add_argument(
+ "--output-dir",
+ required=True,
+ help="The output directory.",
+ )
+ parser.add_argument(
+ "--cores",
+ nargs="+",
+ required=True,
+ help="The cores to add as dependencies.",
+ )
+ args = parser.parse_args()
+
+ # Copy the contents of the input directory to the output directory.
+ if os.path.exists(args.output_dir):
+ shutil.rmtree(args.output_dir)
+ shutil.copytree(args.input_dir, args.output_dir)
+
+ # Find the generated SystemVerilog file.
+ sv_file = None
+ for root, _, files in os.walk(args.output_dir):
+ for f in files:
+ if f == "xbar_kelvin_soc_xbar.sv":
+ sv_file = os.path.join(root, f)
+ break
+ if sv_file:
+ break
+
+ if sv_file is None:
+ raise RuntimeError("Could not find generated SystemVerilog file.")
+
+ # Make the file writable.
+ os.chmod(sv_file, stat.S_IWRITE | stat.S_IREAD)
+
+ # Read the file and perform the replacements.
+ with open(sv_file, "r") as f:
+ content = f.read()
+ original_content = content
+ content = content.replace("tlul_pkg::tl_h2d_t",
+ "kelvin_tlul_pkg_128::tl_h2d_t")
+ content = content.replace("tlul_pkg::tl_d2h_t",
+ "kelvin_tlul_pkg_128::tl_d2h_t")
+ content = content.replace("import tlul_pkg::*",
+ "import kelvin_tlul_pkg_128::*")
+ content = content.replace("tlul_socket_1n #", "tlul_socket_1n_128 #")
+ content = content.replace("tlul_socket_m1 #", "tlul_socket_m1_128 #")
+ content = content.replace("tlul_fifo_async #", "tlul_fifo_async_128 #")
+
+ if original_content == content:
+ print("Warning: No replacements made.")
+ else:
+ print("Success: Replacements made.")
+
+ with open(sv_file, "w") as f:
+ f.write(content)
+
+ core_file = None
+ for root, _, files in os.walk(args.output_dir):
+ for f in files:
+ if f == "xbar_kelvin_soc_xbar.core":
+ core_file = os.path.join(root, f)
+ break
+ if core_file:
+ break
+ if core_file is None:
+ raise RuntimeError("Could not find generated core file.")
+ os.chmod(core_file, stat.S_IWRITE | stat.S_IREAD)
+ with open(core_file, "r") as f:
+ content = f.read()
+ original_content = content
+ for core in args.cores:
+ content = re.sub(r"(\s+)depend:", r"\1depend:\n\1 - " + core, content)
+ if original_content == content:
+ print("Warning: No replacements made (core).")
+ else:
+ print("Success: replacesments made (core).")
+ with open(core_file, "w") as f:
+ f.write(content)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/fpga/racl_pkg.core b/fpga/racl_pkg.core
new file mode 100644
index 0000000..26f2249
--- /dev/null
+++ b/fpga/racl_pkg.core
@@ -0,0 +1,34 @@
+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: "com.google.kelvin:fpga:racl_pkg:0.1"
+description: "Toplevel-wide RAC-L constants for the Kelvin SoC"
+virtual:
+ - lowrisc:virtual_constants:top_racl_pkg
+
+filesets:
+ files_rtl:
+ depend:
+ - com.google.kelvin:fpga:kelvin_soc_pkg:0.1
+ - lowrisc:tlul:headers
+ - lowrisc:prim:util
+ files:
+ - rtl/top_racl_pkg.sv
+ file_type: systemVerilogSource
+
+targets:
+ default:
+ filesets:
+ - files_rtl
diff --git a/fpga/rtl/BUILD b/fpga/rtl/BUILD
new file mode 100644
index 0000000..d78f918
--- /dev/null
+++ b/fpga/rtl/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/rtl/chip_nexus.sv b/fpga/rtl/chip_nexus.sv
new file mode 100644
index 0000000..30f8dff
--- /dev/null
+++ b/fpga/rtl/chip_nexus.sv
@@ -0,0 +1,65 @@
+// 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.
+
+module chip_nexus
+ #(parameter MemInitFile = "",
+ parameter int ClockFrequencyMhz = 10)
+ (input clk_p_i,
+ input clk_n_i,
+ input rst_ni,
+ input spi_clk_i,
+ output [1 : 0] uart_tx_o,
+ input [1 : 0] uart_rx_i,
+ output logic io_halted,
+ output logic io_fault,
+ output logic io_halted_n,
+ output logic io_fault_n);
+
+ logic clk;
+ logic rst_n;
+ logic clk_48MHz;
+ logic clk_aon;
+
+ top_pkg::uart_sideband_i_t[1 : 0] uart_sideband_i;
+ top_pkg::uart_sideband_o_t[1 : 0] uart_sideband_o;
+
+ assign uart_sideband_i[0].cio_rx = uart_rx_i[0];
+ assign uart_sideband_i[1].cio_rx = uart_rx_i[1];
+ assign uart_tx_o[0] = uart_sideband_o[0].cio_tx;
+ assign uart_tx_o[1] = uart_sideband_o[1].cio_tx;
+
+ assign io_halted_n = ~io_halted;
+ assign io_fault_n = ~io_fault;
+
+ clkgen_wrapper #(.ClockFrequencyMhz(ClockFrequencyMhz))
+ i_clkgen(.clk_p_i(clk_p_i),
+ .clk_n_i(clk_n_i),
+ .rst_ni(rst_ni),
+ .srst_ni(rst_ni),
+ .clk_main_o(clk),
+ .clk_48MHz_o(clk_48MHz),
+ .clk_aon_o(clk_aon),
+ .rst_no(rst_n));
+
+ kelvin_soc #(.MemInitFile(MemInitFile),
+ .ClockFrequencyMhz(ClockFrequencyMhz))
+ i_kelvin_soc(.clk_i(clk),
+ .rst_ni(rst_n),
+ .spi_clk_i(spi_clk_i),
+ .scanmode_i(prim_mubi_pkg::MuBi4False),
+ .uart_sideband_i(uart_sideband_i),
+ .uart_sideband_o(uart_sideband_o),
+ .io_halted(io_halted),
+ .io_fault(io_fault));
+endmodule
diff --git a/fpga/rtl/chip_verilator.sv b/fpga/rtl/chip_verilator.sv
new file mode 100644
index 0000000..b94ffcb
--- /dev/null
+++ b/fpga/rtl/chip_verilator.sv
@@ -0,0 +1,65 @@
+// 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.
+
+module chip_verilator
+ #(parameter MemInitFile = "",
+ parameter int ClockFrequencyMhz = 10)
+ (input clk_i,
+ input rst_ni,
+ input spi_clk_i,
+ input prim_mubi_pkg::mubi4_t scanmode_i,
+ input top_pkg::uart_sideband_i_t[1 : 0] uart_sideband_i,
+ output top_pkg::uart_sideband_o_t[1 : 0] uart_sideband_o);
+
+ logic uart0_rx;
+ logic uart0_tx;
+
+ uartdpi #(.BAUD(115200),
+ .FREQ(ClockFrequencyMhz * 1_000_000),
+ .NAME("uart0"),
+ .EXIT_STRING("EXIT"))
+ i_uartdpi0(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .active(1'b1),
+ .tx_o(uart0_rx),
+ .rx_i(uart0_tx));
+
+ logic uart1_rx;
+ logic uart1_tx;
+
+ uartdpi #(.BAUD(115200),
+ .FREQ(ClockFrequencyMhz * 1_000_000),
+ .NAME("uart1"),
+ .EXIT_STRING("EXIT"))
+ i_uartdpi1(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .active(1'b1),
+ .tx_o(uart1_rx),
+ .rx_i(uart1_tx));
+
+ kelvin_soc #(.MemInitFile(MemInitFile),
+ .ClockFrequencyMhz(ClockFrequencyMhz))
+ i_kelvin_soc(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .spi_clk_i(spi_clk_i),
+ .scanmode_i(scanmode_i),
+ .uart_sideband_i(
+ '{'{cio_rx: uart0_rx}, '{cio_rx: uart1_rx}}),
+ .uart_sideband_o(uart_sideband_o),
+ .io_halted(),
+ .io_fault());
+
+ assign uart0_tx = uart_sideband_o[0].cio_tx;
+ assign uart1_tx = uart_sideband_o[1].cio_tx;
+endmodule
diff --git a/fpga/rtl/clkgen_wrapper.sv b/fpga/rtl/clkgen_wrapper.sv
new file mode 100644
index 0000000..55339ef
--- /dev/null
+++ b/fpga/rtl/clkgen_wrapper.sv
@@ -0,0 +1,25 @@
+// Copyright lowRISC contributors (OpenTitan project).
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+module clkgen_wrapper
+ #(parameter int ClockFrequencyMhz = 10)
+ (input clk_p_i,
+ input clk_n_i,
+ input rst_ni,
+ input srst_ni,
+ output clk_main_o,
+ output clk_48MHz_o,
+ output clk_aon_o,
+ output rst_no);
+
+ clkgen_xilultrascaleplus #(.ClockFrequencyMhz(ClockFrequencyMhz))
+ i_clkgen(.clk_i(clk_p_i),
+ .clk_n_i(clk_n_i),
+ .rst_ni(rst_ni),
+ .srst_ni(srst_ni),
+ .clk_main_o(clk_main_o),
+ .clk_48MHz_o(clk_48MHz_o),
+ .clk_aon_o(clk_aon_o),
+ .rst_no(rst_no));
+endmodule
\ No newline at end of file
diff --git a/fpga/rtl/clkgen_xilultrascaleplus.sv b/fpga/rtl/clkgen_xilultrascaleplus.sv
new file mode 100644
index 0000000..9817265
--- /dev/null
+++ b/fpga/rtl/clkgen_xilultrascaleplus.sv
@@ -0,0 +1,137 @@
+// Copyright 2023 Google LLC
+// Copyright lowRISC contributors
+//
+// 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.
+
+module clkgen_xilultrascaleplus
+ #(parameter int ClockFrequencyMhz = 10,
+ // Add BUFG if not done by downstream logic
+ parameter bit AddClkBuf = 1)
+ (input clk_i,
+ input clk_n_i,
+ input rst_ni,
+ input srst_ni,
+ output clk_main_o,
+ output clk_48MHz_o,
+ output clk_aon_o,
+ output rst_no);
+ logic locked_pll;
+ logic io_clk_buf;
+ logic io_rst_buf_n;
+ logic clk_10_buf;
+ logic clk_10_unbuf;
+ logic clk_fb_buf;
+ logic clk_fb_unbuf;
+ logic clk_48_buf;
+ logic clk_48_unbuf;
+ logic clk_aon_buf;
+ logic clk_aon_unbuf;
+ logic clk_ibufds_o;
+
+ // Input IBUFDS conver diff-pair to single-end
+ IBUFDS clk_ibufds(.I(clk_i),
+ .IB(clk_n_i),
+ .O(clk_ibufds_o));
+
+ localparam real CLKOUT0_DIVIDE_F_CALC = 1200.0 / ClockFrequencyMhz;
+
+ MMCME2_ADV #(
+ .BANDWIDTH("OPTIMIZED"),
+ .COMPENSATION("ZHOLD"),
+ .STARTUP_WAIT("FALSE"),
+ .DIVCLK_DIVIDE(1),
+ .CLKFBOUT_MULT_F(12.000),
+ .CLKFBOUT_PHASE(0.000),
+ .CLKOUT0_DIVIDE_F(CLKOUT0_DIVIDE_F_CALC),
+ .CLKOUT0_PHASE(0.000),
+ .CLKOUT0_DUTY_CYCLE(0.500),
+ .CLKOUT1_DIVIDE(25),
+ .CLKOUT1_PHASE(0.000),
+ .CLKOUT1_DUTY_CYCLE(0.500),
+ // With CLKOUT4_CASCADE, CLKOUT6's divider is an input to CLKOUT4's
+ // divider. The effective ratio is a multiplication of the two.
+ .CLKOUT4_DIVIDE(40),
+ .CLKOUT4_PHASE(0.000),
+ .CLKOUT4_DUTY_CYCLE(0.500),
+ .CLKOUT4_CASCADE("TRUE"),
+ .CLKOUT6_DIVIDE(120),
+ .CLKIN1_PERIOD(10.000))
+ pll(.CLKFBOUT(clk_fb_unbuf),
+ .CLKFBOUTB(),
+ .CLKOUT0(clk_10_unbuf),
+ .CLKOUT0B(),
+ .CLKOUT1(clk_48_unbuf),
+ .CLKOUT1B(),
+ .CLKOUT2(),
+ .CLKOUT2B(),
+ .CLKOUT3(),
+ .CLKOUT3B(),
+ .CLKOUT4(clk_aon_unbuf),
+ .CLKOUT5(),
+ .CLKOUT6(),
+ // Input clock control
+ .CLKFBIN(clk_fb_buf),
+ .CLKIN1(clk_ibufds_o),
+ .CLKIN2(1'b0),
+ // Tied to always select the primary input clock
+ .CLKINSEL(1'b1),
+ // Ports for dynamic reconfiguration
+ .DADDR(7'h0),
+ .DCLK(1'b0),
+ .DEN(1'b0),
+ .DI(16'h0),
+ .DO(),
+ .DRDY(),
+ .DWE(1'b0),
+ // Phase shift signals
+ .PSCLK(1'b0),
+ .PSEN(1'b0),
+ .PSINCDEC(1'b0),
+ .PSDONE(),
+ // Other control and status signals
+ .CLKFBSTOPPED(),
+ .CLKINSTOPPED(),
+ .LOCKED(locked_pll),
+ .PWRDWN(1'b0),
+ // Do not reset MMCM on external reset, otherwise ILA disconnects at a
+ // reset
+ .RST(1'b0));
+
+ // output buffering
+ BUFGCE clk_fb_bufgce(.I(clk_fb_unbuf),
+ .O(clk_fb_buf));
+
+ BUFGCE clk_aon_bufgce(.I(clk_aon_unbuf),
+ .O(clk_aon_buf));
+
+ if (AddClkBuf == 1) begin : gen_clk_bufs
+ BUFGCE clk_10_bufgce(.I(clk_10_unbuf),
+ .O(clk_10_buf));
+
+ BUFGCE clk_48_bufgce(.I(clk_48_unbuf),
+ .O(clk_48_buf));
+ end else begin : gen_no_clk_bufs
+ // BUFGs added by downstream modules, no need to add here
+ assign clk_10_buf = clk_10_unbuf;
+ assign clk_48_buf = clk_48_unbuf;
+ end
+
+ // outputs
+ // clock
+ assign clk_main_o = clk_10_buf;
+ assign clk_48MHz_o = clk_48_buf;
+ assign clk_aon_o = clk_aon_buf;
+
+ // reset
+ assign rst_no = locked_pll & rst_ni & srst_ni;
+endmodule
diff --git a/fpga/rtl/kelvin_soc.sv b/fpga/rtl/kelvin_soc.sv
new file mode 100644
index 0000000..1c23def
--- /dev/null
+++ b/fpga/rtl/kelvin_soc.sv
@@ -0,0 +1,639 @@
+// 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.
+
+module kelvin_soc
+ #(parameter MemInitFile = "",
+ parameter int ClockFrequencyMhz = 10)
+ (input clk_i,
+ input rst_ni,
+ input spi_clk_i,
+ input prim_mubi_pkg::mubi4_t scanmode_i,
+ input top_pkg::uart_sideband_i_t[1 : 0] uart_sideband_i,
+ output top_pkg::uart_sideband_o_t[1 : 0] uart_sideband_o,
+ output logic io_halted,
+ output logic io_fault);
+
+ import tlul_pkg::*;
+ import top_pkg::*;
+
+ kelvin_tlul_pkg_128::tl_h2d_t tl_kelvin_core_i;
+ kelvin_tlul_pkg_128::tl_d2h_t tl_kelvin_core_o;
+ kelvin_tlul_pkg_128::tl_h2d_t tl_kelvin_device_o;
+ kelvin_tlul_pkg_128::tl_d2h_t tl_kelvin_device_i;
+
+ kelvin_tlul_pkg_32::tl_h2d_t tl_ibex_core_i_o_32;
+ kelvin_tlul_pkg_32::tl_d2h_t tl_ibex_core_i_i_32;
+ kelvin_tlul_pkg_128::tl_h2d_t tl_ibex_core_i_o_xbar;
+ kelvin_tlul_pkg_128::tl_d2h_t tl_ibex_core_i_i_xbar;
+
+ tlul_host_upsizer i_ibex_core_i_upsizer(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .s_tl_i(tl_ibex_core_i_o_32),
+ .s_tl_o(tl_ibex_core_i_i_32),
+ .m_tl_o(tl_ibex_core_i_o_xbar),
+ .m_tl_i(tl_ibex_core_i_i_xbar));
+
+ kelvin_tlul_pkg_32::tl_h2d_t tl_rom_o_32;
+ kelvin_tlul_pkg_32::tl_d2h_t tl_rom_i_32;
+ kelvin_tlul_pkg_128::tl_h2d_t tl_rom_o_xbar;
+ kelvin_tlul_pkg_128::tl_d2h_t tl_rom_i_xbar;
+ tlul_device_downsizer i_rom_downsizer(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .s_tl_i(tl_rom_o_xbar),
+ .s_tl_o(tl_rom_i_xbar),
+ .m_tl_o(tl_rom_o_32),
+ .m_tl_i(tl_rom_i_32));
+
+ kelvin_tlul_pkg_32::tl_h2d_t tl_ibex_core_d_o_32;
+ kelvin_tlul_pkg_32::tl_d2h_t tl_ibex_core_d_i_32;
+
+ kelvin_tlul_pkg_128::tl_h2d_t tl_ibex_core_d_o_xbar;
+ kelvin_tlul_pkg_128::tl_d2h_t tl_ibex_core_d_i_xbar;
+ tlul_host_upsizer i_ibex_core_d_upsizer(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .s_tl_i(tl_ibex_core_d_o_32),
+ .s_tl_o(tl_ibex_core_d_i_32),
+ .m_tl_o(tl_ibex_core_d_o_xbar),
+ .m_tl_i(tl_ibex_core_d_i_xbar));
+
+ kelvin_tlul_pkg_128::tl_h2d_t tl_sram_o_xbar;
+ kelvin_tlul_pkg_128::tl_d2h_t tl_sram_i_xbar;
+ tl_h2d_t tl_sram_o;
+ tl_d2h_t tl_sram_i;
+ tlul_device_downsizer i_sram_downsizer(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .s_tl_i(tl_sram_o_xbar),
+ .s_tl_o(tl_sram_i_xbar),
+ .m_tl_o(tl_sram_o),
+ .m_tl_i(tl_sram_i));
+ kelvin_tlul_pkg_128::tl_h2d_t tl_uart0_o_xbar;
+ kelvin_tlul_pkg_128::tl_d2h_t tl_uart0_i_xbar;
+ tl_h2d_t tl_uart0_o;
+ tl_d2h_t tl_uart0_i;
+ tlul_device_downsizer i_uart0_downsizer(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .s_tl_i(tl_uart0_o_xbar),
+ .s_tl_o(tl_uart0_i_xbar),
+ .m_tl_o(tl_uart0_o),
+ .m_tl_i(tl_uart0_i));
+ kelvin_tlul_pkg_128::tl_h2d_t tl_uart1_o_xbar;
+ kelvin_tlul_pkg_128::tl_d2h_t tl_uart1_i_xbar;
+ tl_h2d_t tl_uart1_o;
+ tl_d2h_t tl_uart1_i;
+ tlul_device_downsizer i_uart1_downsizer(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .s_tl_i(tl_uart1_o_xbar),
+ .s_tl_o(tl_uart1_i_xbar),
+ .m_tl_o(tl_uart1_o),
+ .m_tl_i(tl_uart1_i));
+ kelvin_tlul_pkg_128::tl_h2d_t tl_spi0_o_xbar;
+ kelvin_tlul_pkg_128::tl_d2h_t tl_spi0_i_xbar;
+ tl_h2d_t tl_spi0_o;
+ tl_d2h_t tl_spi0_i;
+ tlul_device_downsizer i_spi0_downsizer(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .s_tl_i(tl_spi0_o_xbar),
+ .s_tl_o(tl_spi0_i_xbar),
+ .m_tl_o(tl_spi0_o),
+ .m_tl_i(tl_spi0_i));
+
+ xbar_kelvin_soc_xbar i_xbar(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .spi_clk_i(spi_clk_i),
+ .scanmode_i(scanmode_i),
+ .tl_kelvin_core_i(tl_kelvin_core_i),
+ .tl_kelvin_core_o(tl_kelvin_core_o),
+ .tl_ibex_core_i_o(tl_ibex_core_i_i_xbar),
+ .tl_ibex_core_i_i(tl_ibex_core_i_o_xbar),
+ .tl_ibex_core_d_o(tl_ibex_core_d_i_xbar),
+ .tl_ibex_core_d_i(tl_ibex_core_d_o_xbar),
+ .tl_kelvin_device_o(tl_kelvin_device_o),
+ .tl_kelvin_device_i(tl_kelvin_device_i),
+ .tl_rom_o(tl_rom_o_xbar),
+ .tl_rom_i(tl_rom_i_xbar),
+ .tl_sram_o(tl_sram_o_xbar),
+ .tl_sram_i(tl_sram_i_xbar),
+ .tl_uart0_o(tl_uart0_o_xbar),
+ .tl_uart0_i(tl_uart0_i_xbar),
+ .tl_uart1_o(tl_uart1_o_xbar),
+ .tl_uart1_i(tl_uart1_i_xbar),
+ .tl_spi0_o(tl_spi0_o_xbar),
+ .tl_spi0_i(tl_spi0_i_xbar));
+
+ uart i_uart0(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .tl_i(tl_uart0_o),
+ .tl_o(tl_uart0_i),
+ .alert_rx_i(1'b0),
+ .alert_tx_o(),
+ .racl_policies_i(1'b0),
+ .racl_error_o(),
+ .cio_rx_i(uart_sideband_i[0].cio_rx),
+ .cio_tx_o(uart_sideband_o[0].cio_tx),
+ .cio_tx_en_o(uart_sideband_o[0].cio_tx_en),
+ .intr_tx_watermark_o(uart_sideband_o[0].intr_tx_watermark),
+ .intr_tx_empty_o(uart_sideband_o[0].intr_tx_empty),
+ .intr_rx_watermark_o(uart_sideband_o[0].intr_rx_watermark),
+ .intr_tx_done_o(uart_sideband_o[0].intr_tx_done),
+ .intr_rx_overflow_o(uart_sideband_o[0].intr_rx_overflow),
+ .intr_rx_frame_err_o(uart_sideband_o[0].intr_rx_frame_err),
+ .intr_rx_break_err_o(uart_sideband_o[0].intr_rx_break_err),
+ .intr_rx_timeout_o(uart_sideband_o[0].intr_rx_timeout),
+ .intr_rx_parity_err_o(uart_sideband_o[0].intr_rx_parity_err),
+ .lsio_trigger_o(uart_sideband_o[0].lsio_trigger));
+
+ uart i_uart1(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .tl_i(tl_uart1_o),
+ .tl_o(tl_uart1_i),
+ .alert_rx_i(1'b0),
+ .alert_tx_o(),
+ .racl_policies_i(1'b0),
+ .racl_error_o(),
+ .cio_rx_i(uart_sideband_i[1].cio_rx),
+ .cio_tx_o(uart_sideband_o[1].cio_tx),
+ .cio_tx_en_o(uart_sideband_o[1].cio_tx_en),
+ .intr_tx_watermark_o(uart_sideband_o[1].intr_tx_watermark),
+ .intr_tx_empty_o(uart_sideband_o[1].intr_tx_empty),
+ .intr_rx_watermark_o(uart_sideband_o[1].intr_rx_watermark),
+ .intr_tx_done_o(uart_sideband_o[1].intr_tx_done),
+ .intr_rx_overflow_o(uart_sideband_o[1].intr_rx_overflow),
+ .intr_rx_frame_err_o(uart_sideband_o[1].intr_rx_frame_err),
+ .intr_rx_break_err_o(uart_sideband_o[1].intr_rx_break_err),
+ .intr_rx_timeout_o(uart_sideband_o[1].intr_rx_timeout),
+ .intr_rx_parity_err_o(uart_sideband_o[1].intr_rx_parity_err),
+ .lsio_trigger_o(uart_sideband_o[1].lsio_trigger));
+
+ logic rom_req;
+ logic [10 : 0] rom_addr;
+ logic [31 : 0] rom_rdata;
+ logic rom_we;
+ logic [31 : 0] rom_wdata;
+ logic [3 : 0] rom_wmask;
+ logic rom_rvalid;
+
+ tlul_adapter_sram #(.SramAw(11),
+ .SramDw(32),
+ .ErrOnWrite(1),
+ .CmdIntgCheck(1'b1),
+ .EnableRspIntgGen(1'b1),
+ .EnableDataIntgGen(1'b1))
+ i_rom_adapter(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .tl_i(tl_rom_o_32),
+ .tl_o(tl_rom_i_32),
+ .req_o(rom_req),
+ .we_o(rom_we),
+ .addr_o(rom_addr),
+ .wdata_o(rom_wdata),
+ .wmask_o(rom_wmask),
+ .rdata_i(rom_rdata),
+ .gnt_i(1'b1),
+ .rvalid_i(rom_rvalid),
+ .en_ifetch_i(prim_mubi_pkg::MuBi4True),
+ .req_type_o(),
+ .intg_error_o(),
+ .user_rsvd_o(),
+ .rerror_i(2'b0),
+ .compound_txn_in_progress_o(),
+ .readback_en_i(4'b0),
+ .readback_error_o(),
+ .wr_collision_i(1'b0),
+ .write_pending_i(1'b0));
+
+ prim_rom_adv #(.Width(32),
+ .Depth(2048),
+ .MemInitFile(MemInitFile))
+ i_rom(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .req_i(rom_req),
+ .addr_i(rom_addr),
+ .rvalid_o(rom_rvalid),
+ .rdata_o(rom_rdata),
+ .cfg_i('0));
+
+ logic sram_req;
+ logic sram_we;
+ logic [11 : 0] sram_addr;
+ logic [31 : 0] sram_wdata;
+ logic [3 : 0] sram_wmask;
+ logic [31 : 0] sram_rdata;
+ logic sram_rvalid;
+
+ tlul_adapter_sram #(.SramAw(12),
+ .SramDw(32),
+ .CmdIntgCheck(1'b1),
+ .EnableRspIntgGen(1'b1),
+ .EnableDataIntgGen(1'b1))
+ i_sram_adapter(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .tl_i(tl_sram_o),
+ .tl_o(tl_sram_i),
+ .req_o(sram_req),
+ .we_o(sram_we),
+ .addr_o(sram_addr),
+ .wdata_o(sram_wdata),
+ .wmask_o(sram_wmask),
+ .rdata_i(sram_rdata),
+ .gnt_i(1'b1),
+ .rvalid_i(sram_rvalid),
+ .en_ifetch_i(prim_mubi_pkg::MuBi4True),
+ .req_type_o(),
+ .intg_error_o(),
+ .user_rsvd_o(),
+ .rerror_i(2'b0),
+ .compound_txn_in_progress_o(),
+ .readback_en_i(4'b0),
+ .readback_error_o(),
+ .wr_collision_i(1'b0),
+ .write_pending_i(1'b0));
+
+ Sram #(.Width(32),
+ .Depth(4096))
+ i_sram(.clk_i(clk_i),
+ .req_i(sram_req),
+ .we_i(sram_we),
+ .addr_i(sram_addr),
+ .wdata_i(sram_wdata),
+ .wmask_i(sram_wmask),
+ .rdata_o(sram_rdata),
+ .rvalid_o(sram_rvalid));
+
+ // SPI Device Instantiation
+ spi_device i_spi_device(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .tl_i(tl_spi0_o),
+ .tl_o(tl_spi0_i),
+ .cio_sck_i(spi_clk_i),
+ .cio_csb_i(1'b1),
+ .cio_sd_o(),
+ .cio_sd_en_o(),
+ .cio_sd_i(4'b0),
+ // Tie off unused ports
+ .alert_rx_i('{default: '0}),
+ .alert_tx_o(),
+ .racl_policies_i('0),
+ .racl_error_o(),
+ .cio_tpm_csb_i(1'b1),
+ .passthrough_o(),
+ .passthrough_i('0),
+ .intr_upload_cmdfifo_not_empty_o(),
+ .intr_upload_payload_not_empty_o(),
+ .intr_upload_payload_overflow_o(),
+ .intr_readbuf_watermark_o(),
+ .intr_readbuf_flip_o(),
+ .intr_tpm_header_not_empty_o(),
+ .intr_tpm_rdfifo_cmd_end_o(),
+ .intr_tpm_rdfifo_drop_o(),
+ .ram_cfg_sys2spi_i('0),
+ .ram_cfg_rsp_sys2spi_o(),
+ .ram_cfg_spi2sys_i('0),
+ .ram_cfg_rsp_spi2sys_o(),
+ .sck_monitor_o(),
+ .mbist_en_i(1'b0),
+ .scan_clk_i(1'b0),
+ .scan_rst_ni(1'b1),
+ .scanmode_i(4'b0));
+
+ logic rst_cpu_n;
+
+ // Data and Response integrity generation for Kelvin Device Port
+ localparam int XbarSourceWidth = kelvin_tlul_pkg_128::TL_AIW;
+ localparam int XbarSourceCount = 1 << XbarSourceWidth;
+ logic [1 : 0] host_lane_reg[XbarSourceCount - 1 : 0];
+
+ logic [38 : 0] dev_ecc_full_0, dev_ecc_full_1, dev_ecc_full_2, dev_ecc_full_3;
+ logic [6 : 0] dev_ecc_0, dev_ecc_1, dev_ecc_2, dev_ecc_3;
+ logic [6 : 0] dev_selected_ecc;
+ tl_d2h_rsp_intg_t dev_rsp_metadata;
+ logic [63 : 0] dev_rsp_ecc_full;
+ logic [6 : 0] dev_rsp_ecc;
+
+ assign dev_ecc_0 = dev_ecc_full_0[38 : 32];
+ assign dev_ecc_1 = dev_ecc_full_1[38 : 32];
+ assign dev_ecc_2 = dev_ecc_full_2[38 : 32];
+ assign dev_ecc_3 = dev_ecc_full_3[38 : 32];
+ assign dev_rsp_ecc = dev_rsp_ecc_full[63 : 57];
+
+ prim_secded_inv_39_32_enc dev_enc0(.data_i(tl_kelvin_device_i.d_data[31 : 0]),
+ .data_o(dev_ecc_full_0));
+ prim_secded_inv_39_32_enc dev_enc1(.data_i(
+ tl_kelvin_device_i.d_data[63 : 32]),
+ .data_o(dev_ecc_full_1));
+ prim_secded_inv_39_32_enc dev_enc2(.data_i(
+ tl_kelvin_device_i.d_data[95 : 64]),
+ .data_o(dev_ecc_full_2));
+ prim_secded_inv_39_32_enc dev_enc3(.data_i(
+ tl_kelvin_device_i.d_data[127 : 96]),
+ .data_o(dev_ecc_full_3));
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin
+ if (!rst_ni) begin
+ for (int i = 0; i < XbarSourceCount; i++) begin
+ host_lane_reg[i] <= 2'b0;
+ end
+ end else begin
+ // Capture lane index from Ibex data core requests
+ if (tl_ibex_core_d_o_xbar.a_valid && tl_ibex_core_d_i_xbar.a_ready) begin
+ unique case (4'hF)
+ tl_ibex_core_d_o_xbar.a_mask[3 : 0]:
+ host_lane_reg[tl_ibex_core_d_o_xbar.a_source] <= 2'b00;
+ tl_ibex_core_d_o_xbar.a_mask[7 : 4]:
+ host_lane_reg[tl_ibex_core_d_o_xbar.a_source] <= 2'b01;
+ tl_ibex_core_d_o_xbar.a_mask[11 : 8]:
+ host_lane_reg[tl_ibex_core_d_o_xbar.a_source] <= 2'b10;
+ tl_ibex_core_d_o_xbar.a_mask[15 : 12]:
+ host_lane_reg[tl_ibex_core_d_o_xbar.a_source] <= 2'b11;
+ endcase
+ end
+
+ // Capture lane index from Ibex instruction core requests
+ if (tl_ibex_core_i_o_xbar.a_valid && tl_ibex_core_i_i_xbar.a_ready) begin
+ unique case (4'hF)
+ tl_ibex_core_i_o_xbar.a_mask[3 : 0]:
+ host_lane_reg[tl_ibex_core_i_o_xbar.a_source] <= 2'b00;
+ tl_ibex_core_i_o_xbar.a_mask[7 : 4]:
+ host_lane_reg[tl_ibex_core_i_o_xbar.a_source] <= 2'b01;
+ tl_ibex_core_i_o_xbar.a_mask[11 : 8]:
+ host_lane_reg[tl_ibex_core_i_o_xbar.a_source] <= 2'b10;
+ tl_ibex_core_i_o_xbar.a_mask[15 : 12]:
+ host_lane_reg[tl_ibex_core_i_o_xbar.a_source] <= 2'b11;
+ endcase
+ end
+
+ // Capture lane index from Kelvin core requests
+ if (tl_kelvin_core_i.a_valid && tl_kelvin_core_o.a_ready) begin
+ unique case (4'hF)
+ tl_kelvin_core_i.a_mask[3 : 0]:
+ host_lane_reg[tl_kelvin_core_i.a_source] <= 2'b00;
+ tl_kelvin_core_i.a_mask[7 : 4]:
+ host_lane_reg[tl_kelvin_core_i.a_source] <= 2'b01;
+ tl_kelvin_core_i.a_mask[11 : 8]:
+ host_lane_reg[tl_kelvin_core_i.a_source] <= 2'b10;
+ tl_kelvin_core_i.a_mask[15 : 12]:
+ host_lane_reg[tl_kelvin_core_i.a_source] <= 2'b11;
+ endcase
+ end
+ end
+ end
+
+ always_comb begin
+ logic [1 : 0] lane_idx;
+ lane_idx = host_lane_reg[tl_from_kelvin_core.d_source];
+ case (lane_idx)
+ 2'b00:
+ dev_selected_ecc = dev_ecc_0;
+ 2'b01:
+ dev_selected_ecc = dev_ecc_1;
+ 2'b10:
+ dev_selected_ecc = dev_ecc_2;
+ 2'b11:
+ dev_selected_ecc = dev_ecc_3;
+ default:
+ dev_selected_ecc = dev_ecc_0;
+ endcase
+ end
+
+ assign dev_rsp_metadata = '{
+ opcode: tl_from_kelvin_core.d_opcode,
+ size: 2'b10,
+ error: tl_from_kelvin_core.d_error
+ };
+
+ prim_secded_inv_64_57_enc dev_enc_rsp(.data_i(
+ D2HRspMaxWidth'(dev_rsp_metadata)),
+ .data_o(dev_rsp_ecc_full));
+
+ // Kelvin Core Instantiation
+ logic kelvin_halted, kelvin_fault, kelvin_wfi;
+ kelvin_tlul_pkg_128::tl_d2h_t tl_from_kelvin_core;
+
+ assign io_halted = kelvin_halted;
+ assign io_fault = kelvin_fault;
+
+ // Assign all fields for the device D-channel from the Kelvin core's output,
+ // except for the user integrity bits, which we override with our generated
+ // ECC.
+ assign tl_kelvin_device_i.d_valid = tl_from_kelvin_core.d_valid;
+ assign tl_kelvin_device_i.d_opcode = tl_from_kelvin_core.d_opcode;
+ assign tl_kelvin_device_i.d_param = tl_from_kelvin_core.d_param;
+ assign tl_kelvin_device_i.d_size = tl_from_kelvin_core.d_size;
+ assign tl_kelvin_device_i.d_source = tl_from_kelvin_core.d_source;
+ assign tl_kelvin_device_i.d_sink = tl_from_kelvin_core.d_sink;
+ assign tl_kelvin_device_i.d_data = tl_from_kelvin_core.d_data;
+ assign tl_kelvin_device_i.d_error = tl_from_kelvin_core.d_error;
+ assign tl_kelvin_device_i.a_ready = tl_from_kelvin_core.a_ready;
+ assign tl_kelvin_device_i.d_user.rsp_intg = dev_rsp_ecc;
+ assign tl_kelvin_device_i.d_user.data_intg = dev_selected_ecc;
+
+ // Command and Data integrity generation for Kelvin Host Port
+ logic [38 : 0] host_a_data_ecc_full_0, host_a_data_ecc_full_1,
+ host_a_data_ecc_full_2, host_a_data_ecc_full_3;
+ logic [6 : 0] host_a_data_ecc_0, host_a_data_ecc_1, host_a_data_ecc_2,
+ host_a_data_ecc_3;
+ logic [6 : 0] host_a_data_selected_ecc;
+ tl_h2d_cmd_intg_t host_a_cmd_metadata;
+ logic [63 : 0] host_a_cmd_ecc_full;
+ logic [6 : 0] host_a_cmd_ecc;
+
+ assign host_a_data_ecc_0 = host_a_data_ecc_full_0[38 : 32];
+ assign host_a_data_ecc_1 = host_a_data_ecc_full_1[38 : 32];
+ assign host_a_data_ecc_2 = host_a_data_ecc_full_2[38 : 32];
+ assign host_a_data_ecc_3 = host_a_data_ecc_full_3[38 : 32];
+ assign host_a_cmd_ecc = host_a_cmd_ecc_full[63 : 57];
+
+ prim_secded_inv_39_32_enc host_a_data_enc0(
+ .data_i(tl_kelvin_core_i.a_data[31 : 0]),
+ .data_o(host_a_data_ecc_full_0));
+ prim_secded_inv_39_32_enc host_a_data_enc1(
+ .data_i(tl_kelvin_core_i.a_data[63 : 32]),
+ .data_o(host_a_data_ecc_full_1));
+ prim_secded_inv_39_32_enc host_a_data_enc2(
+ .data_i(tl_kelvin_core_i.a_data[95 : 64]),
+ .data_o(host_a_data_ecc_full_2));
+ prim_secded_inv_39_32_enc host_a_data_enc3(
+ .data_i(tl_kelvin_core_i.a_data[127 : 96]),
+ .data_o(host_a_data_ecc_full_3));
+
+ logic [top_pkg::TL_DBW - 1 : 0] host_a_cmd_mask;
+
+ localparam logic [top_pkg::TL_AW - 1 : 0] Uart1BaseAddr = 32'h40010000;
+ logic [15 : 0] computed_mask;
+ logic [3 : 0] host_a_cmd_mask_4b;
+ logic [1 : 0] host_a_cmd_lane;
+ tl_h2d_cmd_intg_t host_a_cmd_payload;
+ logic [15 : 0] kelvin_core_i_a_mask;
+
+ always_comb begin
+ if (tl_kelvin_core_i.a_opcode == tlul_pkg::Get) begin
+ computed_mask = ((1 << (1 << tl_kelvin_core_i.a_size)) - 1)
+ << (tl_kelvin_core_i.a_address[3 : 0]);
+ end else begin
+ computed_mask = kelvin_core_i_a_mask;
+ end
+ host_a_data_selected_ecc = 7'b0;
+ host_a_cmd_mask_4b = '0;
+ host_a_cmd_lane = '0;
+ // This is a priority mux, which is what we want.
+ if (|computed_mask[3 : 0]) begin
+ host_a_data_selected_ecc = host_a_data_ecc_0;
+ host_a_cmd_mask_4b = computed_mask[3 : 0];
+ host_a_cmd_lane = 2'b00;
+ end else if (|computed_mask[7 : 4]) begin
+ host_a_data_selected_ecc = host_a_data_ecc_1;
+ host_a_cmd_mask_4b = computed_mask[7 : 4];
+ host_a_cmd_lane = 2'b01;
+ end else if (|computed_mask[11 : 8]) begin
+ host_a_data_selected_ecc = host_a_data_ecc_2;
+ host_a_cmd_mask_4b = computed_mask[11 : 8];
+ host_a_cmd_lane = 2'b10;
+ end else if (|computed_mask[15 : 12]) begin
+ host_a_data_selected_ecc = host_a_data_ecc_3;
+ host_a_cmd_mask_4b = computed_mask[15 : 12];
+ host_a_cmd_lane = 2'b11;
+ end
+ end
+
+ // Manually pack the command integrity payload to match the 32-bit
+ // peripheral's view. The packing order is derived from the tl_h2d_cmd_intg_t
+ // struct definition.
+ assign host_a_cmd_payload = '{
+ instr_type: prim_mubi_pkg::MuBi4False, // instr_type (4 bits)
+ addr: tl_kelvin_core_i.a_address, // addr (32 bits)
+ opcode: tl_kelvin_core_i.a_opcode, // opcode (3 bits)
+ mask: host_a_cmd_mask_4b // mask (4 bits)
+ };
+ logic [31 : 0] dbg_uart1_addr = host_a_cmd_payload.addr;
+ logic [2 : 0] dbg_uart1_opcode = host_a_cmd_payload.opcode;
+ logic [3 : 0] dbg_uart1_mask = host_a_cmd_payload.mask;
+ logic [3 : 0] dbg_uart1_instr_type = host_a_cmd_payload.instr_type;
+
+ prim_secded_inv_64_57_enc host_a_cmd_enc(.data_i(H2DCmdMaxWidth'(
+ host_a_cmd_payload)),
+ .data_o(host_a_cmd_ecc_full));
+
+ assign tl_kelvin_core_i.a_user.cmd_intg = host_a_cmd_ecc;
+ assign tl_kelvin_core_i.a_user.data_intg = host_a_data_selected_ecc;
+ assign tl_kelvin_core_i.a_user.instr_type = prim_mubi_pkg::MuBi4False;
+ assign tl_kelvin_core_i.a_mask = computed_mask;
+
+ RvvCoreMiniTlul
+ i_kelvin_core(
+ .io_clk(clk_i),
+ .io_rst_ni(rst_ni),
+ .io_tl_host_a_ready(tl_kelvin_core_o.a_ready),
+ .io_tl_host_a_valid(tl_kelvin_core_i.a_valid),
+ .io_tl_host_a_bits_opcode(tl_kelvin_core_i.a_opcode),
+ .io_tl_host_a_bits_param(tl_kelvin_core_i.a_param),
+ .io_tl_host_a_bits_size(tl_kelvin_core_i.a_size),
+ .io_tl_host_a_bits_source(tl_kelvin_core_i.a_source),
+ .io_tl_host_a_bits_address(tl_kelvin_core_i.a_address),
+ .io_tl_host_a_bits_mask(kelvin_core_i_a_mask),
+ .io_tl_host_a_bits_data(tl_kelvin_core_i.a_data),
+ .io_tl_host_a_bits_user_rsvd(tl_kelvin_core_i.a_user.rsvd),
+ .io_tl_host_a_bits_user_instr_type(),
+ .io_tl_host_a_bits_user_cmd_intg(),
+ .io_tl_host_a_bits_user_data_intg(),
+ .io_tl_host_d_ready(tl_kelvin_core_i.d_ready),
+ .io_tl_host_d_valid(tl_kelvin_core_o.d_valid),
+ .io_tl_host_d_bits_opcode(tl_kelvin_core_o.d_opcode),
+ .io_tl_host_d_bits_param(tl_kelvin_core_o.d_param),
+ .io_tl_host_d_bits_size(tl_kelvin_core_o.d_size),
+ .io_tl_host_d_bits_source(tl_kelvin_core_o.d_source),
+ .io_tl_host_d_bits_sink(tl_kelvin_core_o.d_sink),
+ .io_tl_host_d_bits_data(tl_kelvin_core_o.d_data),
+ .io_tl_host_d_bits_error(tl_kelvin_core_o.d_error),
+ .io_tl_host_d_bits_user_rsp_intg(
+ tl_kelvin_core_o.d_user.rsp_intg),
+ .io_tl_host_d_bits_user_data_intg(
+ tl_kelvin_core_o.d_user.data_intg),
+ .io_tl_device_a_valid(tl_kelvin_device_o.a_valid),
+ .io_tl_device_a_bits_opcode(tl_kelvin_device_o.a_opcode),
+ .io_tl_device_a_bits_param(tl_kelvin_device_o.a_param),
+ .io_tl_device_a_bits_size(tl_kelvin_device_o.a_size),
+ .io_tl_device_a_bits_source(tl_kelvin_device_o.a_source),
+ .io_tl_device_a_bits_address(tl_kelvin_device_o.a_address),
+ .io_tl_device_a_bits_mask(tl_kelvin_device_o.a_mask),
+ .io_tl_device_a_bits_data(tl_kelvin_device_o.a_data),
+ .io_tl_device_a_bits_user_rsvd(tl_kelvin_device_o.a_user.rsvd),
+ .io_tl_device_a_bits_user_instr_type(
+ tl_kelvin_device_o.a_user.instr_type),
+ .io_tl_device_a_bits_user_cmd_intg(
+ tl_kelvin_device_o.a_user.cmd_intg),
+ .io_tl_device_a_bits_user_data_intg(
+ tl_kelvin_device_o.a_user.data_intg),
+ .io_tl_device_d_ready(tl_kelvin_device_o.d_ready),
+ .io_tl_device_a_ready(tl_from_kelvin_core.a_ready),
+ .io_tl_device_d_valid(tl_from_kelvin_core.d_valid),
+ .io_tl_device_d_bits_opcode(tl_from_kelvin_core.d_opcode),
+ .io_tl_device_d_bits_param(tl_from_kelvin_core.d_param),
+ .io_tl_device_d_bits_size(tl_from_kelvin_core.d_size),
+ .io_tl_device_d_bits_source(tl_from_kelvin_core.d_source),
+ .io_tl_device_d_bits_sink(tl_from_kelvin_core.d_sink),
+ .io_tl_device_d_bits_data(tl_from_kelvin_core.d_data),
+ .io_tl_device_d_bits_error(tl_from_kelvin_core.d_error),
+ .io_tl_device_d_bits_user_rsp_intg(),
+ .io_tl_device_d_bits_user_data_intg(),
+ .io_halted(kelvin_halted),
+ .io_fault(kelvin_fault),
+ .io_wfi(kelvin_wfi),
+ .io_irq(1'b0),
+ .io_te(1'b0));
+
+ // Ibex Core Instantiation
+ rv_core_ibex #(.PipeLine(1'b1),
+ .PMPEnable(1'b0))
+ i_ibex_core(.clk_i(clk_i),
+ .rst_ni(rst_ni),
+ .corei_tl_h_o(tl_ibex_core_i_o_32),
+ .corei_tl_h_i(tl_ibex_core_i_i_32),
+ .cored_tl_h_o(tl_ibex_core_d_o_32),
+ .cored_tl_h_i(tl_ibex_core_d_i_32),
+ // Tie off unused ports
+ .clk_edn_i(1'b0),
+ .rst_edn_ni(1'b1),
+ .clk_esc_i(1'b0),
+ .rst_esc_ni(1'b1),
+ .rst_cpu_n_o(rst_cpu_n),
+ .ram_cfg_icache_tag_i('0),
+ .ram_cfg_rsp_icache_tag_o(),
+ .ram_cfg_icache_data_i('0),
+ .ram_cfg_rsp_icache_data_o(),
+ .hart_id_i(32'b0),
+ .boot_addr_i(32'h10000000),
+ .irq_software_i(1'b0),
+ .irq_timer_i(1'b0),
+ .irq_external_i(1'b0),
+ .esc_tx_i('0),
+ .esc_rx_o(),
+ .nmi_wdog_i(1'b0),
+ .debug_req_i(1'b0),
+ .crash_dump_o(),
+ .lc_cpu_en_i(lc_ctrl_pkg::On),
+ .pwrmgr_cpu_en_i(lc_ctrl_pkg::On),
+ .pwrmgr_o(),
+ .scan_rst_ni(1'b1),
+ .scanmode_i(4'b0),
+ .cfg_tl_d_i('0),
+ .cfg_tl_d_o(),
+ .edn_o(),
+ .edn_i('0),
+ .clk_otp_i(1'b0),
+ .rst_otp_ni(1'b1),
+ .icache_otp_key_o(),
+ .icache_otp_key_i('0),
+ .fpga_info_i(32'b0),
+ .alert_rx_i('{default: '0}),
+ .alert_tx_o());
+endmodule
diff --git a/fpga/rtl/top_pkg.sv b/fpga/rtl/top_pkg.sv
new file mode 100644
index 0000000..25013ab
--- /dev/null
+++ b/fpga/rtl/top_pkg.sv
@@ -0,0 +1,49 @@
+// 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 top_pkg;
+
+ // This is a placeholder file.
+ // Toplevel constants for the Kelvin SoC will be added here.
+ localparam int TL_AW = 32;
+ localparam int TL_DW = 32;
+ localparam int TL_AIW = 8;
+ localparam int TL_DIW = 1;
+ localparam int TL_AUW = 23;
+ localparam int TL_DUW = 14;
+ localparam int TL_DBW = (TL_DW >> 3);
+ localparam int TL_SZW = $clog2($clog2(TL_DBW) + 1);
+ localparam int NrRaclBits = 1;
+
+ typedef logic[NrRaclBits - 1 : 0] ctn_uid_t;
+
+ typedef struct packed {
+ logic cio_rx;
+ } uart_sideband_i_t;
+
+ typedef struct packed {
+ logic cio_tx;
+ logic cio_tx_en;
+ logic intr_tx_watermark;
+ logic intr_tx_empty;
+ logic intr_rx_watermark;
+ logic intr_tx_done;
+ logic intr_rx_overflow;
+ logic intr_rx_frame_err;
+ logic intr_rx_break_err;
+ logic intr_rx_timeout;
+ logic intr_rx_parity_err;
+ logic lsio_trigger;
+ } uart_sideband_o_t;
+endpackage
diff --git a/fpga/rtl/top_racl_pkg.sv b/fpga/rtl/top_racl_pkg.sv
new file mode 100644
index 0000000..50a0f10
--- /dev/null
+++ b/fpga/rtl/top_racl_pkg.sv
@@ -0,0 +1,89 @@
+// 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 top_racl_pkg;
+ import tlul_pkg::*;
+
+ // This is a placeholder file.
+ // Toplevel RAC-L constants for the Kelvin SoC will be added here.
+ parameter int unsigned NrRaclPolicies = 1;
+
+ // RACL Policy selector bits
+ parameter int unsigned RaclPolicySelLen =
+ prim_util_pkg::vbits(NrRaclPolicies);
+
+ // Number of RACL bits transferred
+ parameter int unsigned NrRaclBits = 1;
+
+ // Number of CTN UID bits transferred
+ parameter int unsigned NrCtnUidBits = 1;
+ // CTN UID assigned the bus originator
+ typedef logic[NrCtnUidBits - 1 : 0] ctn_uid_t;
+
+ // RACL Policy selector type
+ typedef logic[RaclPolicySelLen - 1 : 0] racl_policy_sel_t;
+
+ // RACL role type binary encoded
+ typedef logic[NrRaclBits - 1 : 0] racl_role_t;
+ // RACL permission: A one-hot encoded role vector
+ typedef logic[(2 ** NrRaclBits) - 1 : 0] racl_role_vec_t;
+
+ // RACL policy containing a read and write permission
+ typedef struct packed {
+ racl_role_vec_t write_perm; // Write permission (upper bits)
+ racl_role_vec_t read_perm; // Read permission (lower bits)
+ } racl_policy_t;
+ typedef racl_policy_t[NrRaclPolicies - 1 : 0] racl_policy_vec_t;
+
+ // RACL information logged in case of a denial
+ typedef struct packed {
+ logic valid; // Error information is valid
+ logic overflow; // Error overflow, More than 1 RACL error at a time
+ racl_role_t racl_role;
+ ctn_uid_t ctn_uid;
+ logic read_access; // 0: Write access, 1: Read access
+ logic [top_pkg::TL_AW - 1 : 0] request_address;
+ } racl_error_log_t;
+
+ // RACL range used to protect a range of addresses with a RACL policy (e.g.,
+ // for sram).
+ typedef struct packed {
+ logic [top_pkg::TL_AW - 1 : 0] base; // Start address of range
+ logic [top_pkg::TL_AW - 1 : 0] limit; // End address of range (inclusive)
+ racl_policy_sel_t policy_sel; // Policy selector
+ logic enable; // 0: Range is disabled, 1: Range is enabled
+ } racl_range_t;
+
+ function automatic racl_role_t tlul_extract_racl_role_bits
+ (logic [tlul_pkg::RsvdWidth - 1 : 0] rsvd);
+ // Waive unused bits
+ logic unused_rsvd_bits;
+ unused_rsvd_bits = ^{
+ rsvd
+ };
+
+ return racl_role_t'(rsvd[0 : 0]);
+ endfunction
+
+ function automatic ctn_uid_t tlul_extract_ctn_uid_bits
+ (logic [tlul_pkg::RsvdWidth - 1 : 0] rsvd);
+ // Waive unused bits
+ logic unused_rsvd_bits;
+ unused_rsvd_bits = ^{
+ rsvd
+ };
+
+ return ctn_uid_t'(rsvd[0 : 0]);
+ endfunction
+endpackage
diff --git a/fpga/rules.bzl b/fpga/rules.bzl
new file mode 100644
index 0000000..df307ad
--- /dev/null
+++ b/fpga/rules.bzl
@@ -0,0 +1,43 @@
+"""Starlark rules for FPGA development."""
+
+def _tlgen_impl(ctx):
+ """Implementation of the tlgen_rule."""
+ topcfg = ctx.file.topcfg
+ out_dir = ctx.actions.declare_directory(ctx.label.name + "_out")
+ core_file = ctx.actions.declare_file(ctx.label.name + "_out/" + "xbar_kelvin_soc_xbar.core")
+
+ ctx.actions.run(
+ outputs = [out_dir, core_file],
+ inputs = [topcfg],
+ executable = ctx.executable._tool,
+ arguments = [
+ "--topcfg",
+ topcfg.path,
+ "--outdir",
+ out_dir.path,
+ ],
+ progress_message = "Running tlgen and extracting core for %s" % topcfg.short_path,
+ )
+
+ return [
+ DefaultInfo(files = depset([out_dir])),
+ OutputGroupInfo(
+ core_file_output = depset([core_file, out_dir]),
+ ),
+ ]
+
+tlgen_rule = rule(
+ implementation = _tlgen_impl,
+ attrs = {
+ "topcfg": attr.label(
+ allow_single_file = True,
+ mandatory = True,
+ doc = "HJSON top-level configuration file.",
+ ),
+ "_tool": attr.label(
+ default = Label("//fpga:tlgen_tool"),
+ executable = True,
+ cfg = "exec",
+ ),
+ },
+)
diff --git a/fpga/sw/.gitignore b/fpga/sw/.gitignore
new file mode 100644
index 0000000..dbfe247
--- /dev/null
+++ b/fpga/sw/.gitignore
@@ -0,0 +1,4 @@
+# Ignore build artifacts
+*.o
+*.bin
+*.elf
diff --git a/fpga/sw/add_uint32_m1.cc b/fpga/sw/add_uint32_m1.cc
new file mode 100644
index 0000000..fb2c2dd
--- /dev/null
+++ b/fpga/sw/add_uint32_m1.cc
@@ -0,0 +1,61 @@
+/*
+ * 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 <riscv_vector.h>
+
+uint32_t in_buf_1[16] __attribute__((aligned(16)));
+uint32_t in_buf_2[16] __attribute__((aligned(16)));
+uint32_t out_buf[16] __attribute__((aligned(16)));
+
+void add_u32_m1(const uint32_t *in_buf_1, const uint32_t *in_buf_2,
+ uint32_t *out_buf) {
+ vuint32m1_t input_v1 = __riscv_vle32_v_u32m1(in_buf_1, 4);
+ vuint32m1_t input_v2 = __riscv_vle32_v_u32m1(in_buf_2, 4);
+ vuint32m1_t add_result = __riscv_vadd_vv_u32m1(input_v1, input_v2, 4);
+ __riscv_vse32_v_u32m1(out_buf, add_result, 4);
+}
+
+int main(int argc, char **argv) {
+ add_u32_m1(in_buf_1, in_buf_2, out_buf);
+
+ // Configure UART1.
+ // The NCO is calculated as: (baud_rate * 2^20) / clock_frequency
+ // In our case: (115200 * 2^20) / (CLOCK_FREQUENCY_MHZ * 1000000)
+ volatile unsigned int *uart_ctrl =
+ reinterpret_cast<volatile unsigned int *>(0x40010010);
+ const uint64_t uart_ctrl_nco =
+ ((uint64_t)115200 << 20) / (CLOCK_FREQUENCY_MHZ * 1000000);
+ // Enable TX and RX, and set the NCO value.
+ *uart_ctrl = (uart_ctrl_nco << 16) | 3;
+
+ auto uart_print = [](const char *str) {
+ volatile char *uart_wdata = reinterpret_cast<volatile char *>(0x4001001c);
+ volatile unsigned int *uart_status =
+ reinterpret_cast<volatile unsigned int *>(0x40010014);
+
+ while (*str) {
+ // Wait until TX FIFO is not full.
+ while (*uart_status & 1) {
+ asm volatile("nop");
+ }
+ *uart_wdata = *str++;
+ }
+ };
+
+ uart_print("Hello from Kelvin!\n");
+
+ return 0;
+}
diff --git a/fpga/sw/ibex_boot_rom.S b/fpga/sw/ibex_boot_rom.S
new file mode 100644
index 0000000..1c2bfe8
--- /dev/null
+++ b/fpga/sw/ibex_boot_rom.S
@@ -0,0 +1,45 @@
+// A simple program that sets up a stack and calls main.
+.section .text
+.globl _start
+.org 0x80
+_start:
+ // Set up the stack pointer
+ la sp, _stack_start
+
+ // Clear registers
+ mv tp, zero
+ mv t1, zero
+ mv t2, zero
+ mv s0, zero
+ mv s1, zero
+ mv a1, zero
+ mv a2, zero
+ mv a3, zero
+ mv a4, zero
+ mv a5, zero
+ mv a6, zero
+ mv a7, zero
+ mv s2, zero
+ mv s3, zero
+ mv s4, zero
+ mv s5, zero
+ mv s6, zero
+ mv s7, zero
+ mv s8, zero
+ mv s9, zero
+ mv s10, zero
+ mv s11, zero
+ mv t3, zero
+ mv t4, zero
+ mv t5, zero
+ mv t6, zero
+
+ // Call main
+ call main
+
+ // Wait for interrupt
+ wfi
+
+// Infinite loop
+_hang:
+ j _hang
diff --git a/fpga/sw/ibex_boot_rom.ld b/fpga/sw/ibex_boot_rom.ld
new file mode 100644
index 0000000..95ab232
--- /dev/null
+++ b/fpga/sw/ibex_boot_rom.ld
@@ -0,0 +1,20 @@
+MEMORY {
+ rom (rx) : ORIGIN = 0x10000000, LENGTH = 0x8000
+ sram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x400000
+}
+
+ENTRY(_start)
+
+SECTIONS {
+ . = 0x10000080;
+ .text : {
+ *(.text)
+ *(.text.*)
+ } > rom
+
+ .stack (NOLOAD) : {
+ . = ALIGN(16);
+ . = . + 4K;
+ _stack_start = .;
+ } > sram
+}
diff --git a/fpga/sw/main.cc b/fpga/sw/main.cc
new file mode 100644
index 0000000..3ff561f
--- /dev/null
+++ b/fpga/sw/main.cc
@@ -0,0 +1,69 @@
+#include <stdint.h>
+
+#include <cstring>
+
+#include "fpga/add_uint32_m1_bin_header.h"
+
+extern "C" int main() {
+ // Copy the embedded binary to Kelvin's ITCM at 0x0.
+ void *itcm_base = reinterpret_cast<void *>(static_cast<uintptr_t>(0x0));
+ memcpy(itcm_base, add_uint32_m1_bin, add_uint32_m1_bin_len);
+
+ // Kelvin run sequence
+ volatile unsigned int *kelvin_reset_csr =
+ reinterpret_cast<volatile unsigned int *>(
+ static_cast<uintptr_t>(0x00030000));
+
+ // Release clock gate
+ *kelvin_reset_csr = 1;
+
+ // Wait one cycle
+ __asm__ volatile("nop");
+
+ // Release reset
+ *kelvin_reset_csr = 0;
+
+ volatile unsigned int *kelvin_status_csr =
+ reinterpret_cast<volatile unsigned int *>(
+ static_cast<uintptr_t>(0x00030008));
+ // Wait for Kelvin to halt
+ while (!(*kelvin_status_csr & 1)) {
+ for (int i = 0; i < 1000; ++i) {
+ __asm__ volatile("nop");
+ }
+ }
+
+ // Configure UART0.
+ // The NCO is calculated as: (baud_rate * 2^20) / clock_frequency
+ // In our case: (115200 * 2^20) / (CLOCK_FREQUENCY_MHZ * 1000000)
+ volatile unsigned int *uart_ctrl =
+ reinterpret_cast<volatile unsigned int *>(0x40000010);
+ const uint64_t uart_ctrl_nco =
+ ((uint64_t)115200 << 20) / (CLOCK_FREQUENCY_MHZ * 1000000);
+ // Enable TX and RX, and set the NCO value.
+ *uart_ctrl = (uart_ctrl_nco << 16) | 3;
+
+ auto uart_print = [](const char *str) {
+ volatile char *uart_wdata = reinterpret_cast<volatile char *>(0x4000001c);
+ volatile unsigned int *uart_status =
+ reinterpret_cast<volatile unsigned int *>(0x40000014);
+
+ while (*str) {
+ // Wait until TX FIFO is not full.
+ while (*uart_status & 1) {
+ asm volatile("nop");
+ }
+ *uart_wdata = *str++;
+ }
+ };
+
+ uart_print("Kelvin halted, as expected.\n");
+
+ volatile unsigned int *sram = (volatile unsigned int *)0x20000000;
+ *sram = 0xdeadbeef;
+ while (*sram != 0xdeadbeef) {
+ asm volatile("nop");
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/fpga/tl_config.hjson b/fpga/tl_config.hjson
new file mode 100644
index 0000000..82f5a83
--- /dev/null
+++ b/fpga/tl_config.hjson
@@ -0,0 +1,95 @@
+{
+ // Top-level configuration for the Kelvin SoC crossbar.
+ name: "kelvin_soc_xbar",
+ clock: "clk_i",
+ reset: "rst_ni",
+
+ // Define all the hosts (masters) and devices (slaves) in the system.
+ nodes: [
+ // Hosts (CPU Cores)
+ { name: "kelvin_core", type: "host", clock: "clk_i", reset: "rst_ni", addr_space: "asid0" },
+ { name: "ibex_core_i", type: "host", clock: "clk_i", reset: "rst_ni", addr_space: "asid0" },
+ { name: "ibex_core_d", type: "host", clock: "clk_i", reset: "rst_ni", addr_space: "asid0" },
+
+ // Devices (Peripherals and Memory)
+ {
+ name: "kelvin_device",
+ type: "device",
+ clock: "clk_i",
+ reset: "rst_ni",
+ xbar: false,
+ addr_range: [
+ { base_addrs: {"asid0": "0x00000000"}, size_byte: "0x2000" }, // 8kB
+ { base_addrs: {"asid0": "0x00010000"}, size_byte: "0x8000" }, // 32kB
+ { base_addrs: {"asid0": "0x00030000"}, size_byte: "0x1000" } // 4kB
+ ]
+ },
+ {
+ name: "rom",
+ type: "device",
+ clock: "clk_i",
+ reset: "rst_ni",
+ xbar: false,
+ addr_range: [{ base_addrs: {"asid0": "0x10000000"}, size_byte: "0x8000" }] // 32kB
+ },
+ {
+ name: "sram",
+ type: "device",
+ clock: "clk_i",
+ reset: "rst_ni",
+ xbar: false,
+ addr_range: [{ base_addrs: {"asid0": "0x20000000"}, size_byte: "0x400000" }] // 4MB
+ },
+ {
+ name: "uart0",
+ type: "device",
+ clock: "clk_i",
+ reset: "rst_ni",
+ xbar: false,
+ addr_range: [{ base_addrs: {"asid0": "0x40000000"}, size_byte: "0x1000" }]
+ },
+ {
+ name: "uart1",
+ type: "device",
+ clock: "clk_i",
+ reset: "rst_ni",
+ xbar: false,
+ addr_range: [{ base_addrs: {"asid0": "0x40010000"}, size_byte: "0x1000" }]
+ },
+ {
+ name: "spi0",
+ type: "device",
+ clock: "spi_clk_i", // Using a separate clock for the SPI peripheral
+ reset: "rst_ni",
+ xbar: false,
+ addr_range: [{ base_addrs: {"asid0": "0x40020000"}, size_byte: "0x1000" }]
+ }
+ ],
+
+ // Define which hosts can access which devices.
+ connections: {
+ kelvin_core: [ "sram", "uart1", "spi0" ],
+ ibex_core_i: [ "rom", "sram" ],
+ ibex_core_d: [ "rom", "sram", "uart0", "kelvin_device" ]
+ },
+
+ // Define clock connections for all components.
+ clock_connections: {
+ clk_i: [
+ "kelvin_core",
+ "ibex_core_i",
+ "ibex_core_d",
+ "kelvin_device",
+ "rom",
+ "sram",
+ "uart0",
+ "uart1"
+ ],
+ spi_clk_i: [
+ "spi0"
+ ]
+ },
+ reset_connections: {
+ rst_ni: "rst_ni"
+ }
+}
diff --git a/fpga/vivado_setup_hooks.tcl b/fpga/vivado_setup_hooks.tcl
new file mode 100644
index 0000000..28182d2
--- /dev/null
+++ b/fpga/vivado_setup_hooks.tcl
@@ -0,0 +1,2 @@
+## Elevate warning for not finding file for readmemh to ERROR.
+set_msg_config -id {[Synth 8-4445]} -new_severity ERROR
\ No newline at end of file