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