feat(fpga): Integrate Chisel Subsystem into FPGA build

This commit integrates the newly created `KelvinChiselSubsystem` into the top-level FPGA design for both Verilator simulation and the Nexus hardware target.

Key changes:
- **`kelvin_soc.sv`**: The top-level SoC module is updated to instantiate the `KelvinChiselSubsystem` instead of the individual Chisel-generated modules (core, crossbar). This greatly simplifies the top-level Verilog.
- **FPGA Build System**: The `fpga/BUILD` file is updated to build and use the `KelvinChiselSubsystem`. The dependencies on the old, individual modules (`rvv_core_mini_tlul`, `xbar_kelvin_soc_chisel`, `rv_core_ibex`) have been removed.
- **Verilator Simulation**: The `chip_verilator.sv` wrapper now includes the `spi_dpi_master`, allowing host-driven SPI communication with the simulated SoC.
- **Ibex Boot ROM Removal**: The Ibex core and its associated software boot ROM have been removed from the FPGA build.
- **Clocking**: The clock generator has been simplified to remove the dedicated clock for the now-removed standalone Ibex core.

This commit completes the transition to the unified Chisel subsystem architecture at the FPGA level.

Change-Id: I8de2a29e3f59ec834647f644c13bd9f6b9b4bb6c
diff --git a/fpga/BUILD b/fpga/BUILD
index 4d8dc81..57a5b3b 100644
--- a/fpga/BUILD
+++ b/fpga/BUILD
@@ -20,15 +20,15 @@
 package(default_visibility = ["//visibility:public"])
 
 _CLOCK_FREQUENCY_MHZ = "80"
+_NEXUS_CLOCK_FREQUENCY_MHZ = "80"
 
 filegroup(
     name = "rtl_files",
     srcs = glob(["**/*.sv"]) + glob(["**/*.core"]) + [
+        "//fpga/ip/spi_dpi_master:rtl_files",
         "//fpga/ip/kelvin_tlul:rtl_files",
-        "//fpga/ip/rv_core_ibex:rtl_files",
-        "//fpga/ip/rvv_core_mini_tlul:rtl_files",
+        "//fpga/ip/kelvin_chisel_subsystem:rtl_files",
         "//fpga/ip/sram:rtl_files",
-        "//fpga/ip/xbar_kelvin_soc_chisel:rtl_files",
         "//fpga/rtl:rtl_files",
     ],
 )
@@ -48,6 +48,8 @@
         # "-Wno-MULTIDRIVEN",
         "-Wno-UNOPTTHREADS",
         "-Wno-GENUNNAMED",
+        "-Wno-DECLFILENAME",
+        "-Wno-ASCRANGE",
         "-DRVFI",
         "-DUSE_GENERIC",
     ],
@@ -67,38 +69,19 @@
 )
 
 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",
-    "//fpga/ip/rvv_core_mini_tlul:rvv_core_mini_tlul.core",
-    "//fpga/ip/xbar_kelvin_soc_chisel:xbar_kelvin_soc_chisel.core",
+    "//fpga/ip/kelvin_chisel_subsystem:kelvin_chisel_subsystem.core",
     ":kelvin_soc.core",
     ":kelvin_soc_pkg.core",
     ":racl_pkg.core",
@@ -132,16 +115,16 @@
 fusesoc_build(
     name = "build_chip_verilator",
     srcs = KELVIN_SOC_SRCS + [
-        ":ibex_boot_rom.vmem",
+        "//fpga/ip/spi_dpi_master:dpi_files",
         "@lowrisc_opentitan_gh//hw:dpi_files",
     ],
     cores = KELVIN_SOC_CORES + [
         ":chip_verilator.core",
+        "//fpga/ip/spi_dpi_master:spi_dpi_master.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",
@@ -156,21 +139,15 @@
 
 _PREFIX = "../../../../../../../../.."
 
-_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,
+        "--ClockFrequencyMhz=" + _NEXUS_CLOCK_FREQUENCY_MHZ,
     ],
     output_groups = {
         "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"],
@@ -180,3 +157,18 @@
     target = "synth",
     tags = ["manual"],
 )
+
+filegroup(
+    name = "chip_verilator_binary",
+    srcs = [":build_chip_verilator"],
+    output_group = "binary",
+    tags = ["manual"],
+)
+
+genrule(
+    name = "copy_chip_verilator_binary",
+    srcs = [":chip_verilator_binary"],
+    outs = ["Vchip_verilator"],
+    cmd = "cp $< $@",
+    tags = ["manual"],
+)
diff --git a/fpga/chip_nexus.core b/fpga/chip_nexus.core
index 4d930dd..7a85b64 100644
--- a/fpga/chip_nexus.core
+++ b/fpga/chip_nexus.core
@@ -30,7 +30,7 @@
   MemInitFile:
     datatype: str
     description: Path to ROM
-    default: "fpga/wfi.bin"
+    default: ""
     paramtype: vlogparam
   USE_GENERIC:
     datatype: bool
diff --git a/fpga/chip_verilator.core b/fpga/chip_verilator.core
index 343866e..3e52a5a 100644
--- a/fpga/chip_verilator.core
+++ b/fpga/chip_verilator.core
@@ -9,6 +9,7 @@
       - kelvinv2:ip:kelvin_tlul:0.1
       - lowrisc:dv_dpi_c:uartdpi:0.1
       - lowrisc:dv_dpi_sv:uartdpi:0.1
+      - com.google.kelvin:fpga:spi_dpi_master:0.1
     files:
       - rtl/chip_verilator.sv
     file_type: systemVerilogSource
@@ -29,7 +30,7 @@
   MemInitFile:
     datatype: str
     description: Path to ROM
-    default: "fpga/wfi.bin"
+    default: ""
     paramtype: vlogparam
 
 targets:
diff --git a/fpga/ip/xbar_kelvin_soc_chisel/BUILD b/fpga/ip/kelvin_chisel_subsystem/BUILD
similarity index 65%
rename from fpga/ip/xbar_kelvin_soc_chisel/BUILD
rename to fpga/ip/kelvin_chisel_subsystem/BUILD
index fe7f6ab..f21f668 100644
--- a/fpga/ip/xbar_kelvin_soc_chisel/BUILD
+++ b/fpga/ip/kelvin_chisel_subsystem/BUILD
@@ -15,20 +15,20 @@
 package(default_visibility = ["//visibility:public"])
 
 genrule(
-    name = "kelvin_xbar",
-    srcs = ["//hdl/chisel/src/soc:KelvinXbar.sv"],
-    outs = ["KelvinXbar.sv"],
+    name = "kelvin_chisel_subsystem_verilog",
+    srcs = ["//hdl/chisel/src/soc:KelvinChiselSubsystem.sv"],
+    outs = ["KelvinChiselSubsystem.sv"],
     cmd = "cp $< $@",
 )
 
 genrule(
     name = "generate_core_file",
     srcs = [
-        "xbar_kelvin_soc_chisel.core.tpl",
-        ":kelvin_xbar",
+        "kelvin_chisel_subsystem.core.tpl",
+        ":kelvin_chisel_subsystem_verilog",
     ],
-    outs = ["xbar_kelvin_soc_chisel.core"],
-    cmd = "sed 's|__VERILOG_FILE__|KelvinXbar.sv|' $(location xbar_kelvin_soc_chisel.core.tpl) > $@",
+    outs = ["kelvin_chisel_subsystem.core"],
+    cmd = "sed 's|__VERILOG_FILE__|KelvinChiselSubsystem.sv|' $(location kelvin_chisel_subsystem.core.tpl) > $@",
 )
 
 filegroup(
@@ -37,6 +37,6 @@
         "*.sv",
         "*.core",
     ]) + [
-        ":KelvinXbar.sv",
+        ":KelvinChiselSubsystem.sv",
     ],
 )
diff --git a/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core.tpl b/fpga/ip/kelvin_chisel_subsystem/kelvin_chisel_subsystem.core
similarity index 72%
rename from fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core.tpl
rename to fpga/ip/kelvin_chisel_subsystem/kelvin_chisel_subsystem.core
index b48b939..caa82c0 100644
--- a/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core.tpl
+++ b/fpga/ip/kelvin_chisel_subsystem/kelvin_chisel_subsystem.core
@@ -13,19 +13,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-name: "google:kelvin:rvv_core_mini_tlul"
-description: "RvvCoreMini with TileLink interface"
+name: "kelvinv2:ip:kelvin_chisel_subsystem:0.1"
+description: "Kelvin SoC Unified Chisel Subsystem"
 
 filesets:
-  files_rtl:
+  rtl:
     depend:
-      - "lowrisc:prim:all"
-      - "lowrisc:prim_generic:all"
+      - lowrisc:prim:all
+      - lowrisc:prim_generic:all
     files:
-      - __VERILOG_FILE__: { file_type: systemVerilogSource }
+      - KelvinChiselSubsystem.sv
     file_type: systemVerilogSource
 
 targets:
   default:
     filesets:
-      - files_rtl
+      - rtl
diff --git a/fpga/ip/kelvin_chisel_subsystem/kelvin_chisel_subsystem.core.tpl b/fpga/ip/kelvin_chisel_subsystem/kelvin_chisel_subsystem.core.tpl
new file mode 100644
index 0000000..c7c85d2
--- /dev/null
+++ b/fpga/ip/kelvin_chisel_subsystem/kelvin_chisel_subsystem.core.tpl
@@ -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 of an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "kelvinv2:ip:kelvin_chisel_subsystem:0.1"
+description: "Kelvin SoC Unified Chisel Subsystem"
+
+filesets:
+  rtl:
+    depend:
+      - lowrisc:prim:all
+      - lowrisc:prim_generic:all
+    files:
+      - __VERILOG_FILE__
+    file_type: systemVerilogSource
+
+targets:
+  default:
+    filesets:
+      - rtl
diff --git a/fpga/ip/rvv_core_mini_tlul/BUILD b/fpga/ip/rvv_core_mini_tlul/BUILD
deleted file mode 100644
index e8edeb8..0000000
--- a/fpga/ip/rvv_core_mini_tlul/BUILD
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-package(default_visibility = ["//visibility:public"])
-
-genrule(
-    name = "rvv_core_mini_tlul_verilog",
-    srcs = ["//hdl/chisel/src/kelvin:RvvCoreMiniTlul.sv"],
-    outs = ["RvvCoreMiniTlul.sv"],
-    cmd = "cp $< $@",
-)
-
-genrule(
-    name = "rvv_core_mini_tlul_core",
-    srcs = [
-        "rvv_core_mini_tlul.core.tpl",
-        ":rvv_core_mini_tlul_verilog",
-    ],
-    outs = ["rvv_core_mini_tlul.core"],
-    cmd = "sed 's|__VERILOG_FILE__|RvvCoreMiniTlul.sv|' $(location rvv_core_mini_tlul.core.tpl) > $@",
-)
-
-filegroup(
-    name = "rtl_files",
-    srcs = glob([
-        "*.sv",
-        "*.core",
-    ]) + [
-        ":RvvCoreMiniTlul.sv",
-    ],
-)
diff --git a/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core b/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core
deleted file mode 100644
index 0b58a34..0000000
--- a/fpga/ip/rvv_core_mini_tlul/rvv_core_mini_tlul.core
+++ /dev/null
@@ -1,31 +0,0 @@
-CAPI=2:
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-name: "google:kelvin:rvv_core_mini_tlul"
-description: "RvvCoreMini with TileLink interface"
-
-filesets:
-  files_rtl:
-    depend:
-      - "lowrisc:prim:all"
-      - "lowrisc:prim_generic:all"
-    files:
-      - RvvCoreMiniTlul.sv: { file_type: systemVerilogSource }
-    file_type: systemVerilogSource
-
-targets:
-  default:
-    filesets:
-      - files_rtl
diff --git a/fpga/ip/sram/Sram.sv b/fpga/ip/sram/Sram.sv
index 1cd1f3d..6221bf3 100644
--- a/fpga/ip/sram/Sram.sv
+++ b/fpga/ip/sram/Sram.sv
@@ -32,7 +32,7 @@
 
   // The rvalid signal is simply a delayed version of req_i.
   always_ff @(posedge clk_i) begin
-    rvalid_o <= req_i;
+    rvalid_o <= req_i & ~we_i;
   end
 
   localparam MemInitFile = "";
diff --git a/fpga/ip/xbar_kelvin_soc_chisel/xbar_kelvin_soc_chisel.core b/fpga/ip/xbar_kelvin_soc_chisel/xbar_kelvin_soc_chisel.core
deleted file mode 100644
index b469a7a..0000000
--- a/fpga/ip/xbar_kelvin_soc_chisel/xbar_kelvin_soc_chisel.core
+++ /dev/null
@@ -1,14 +0,0 @@
-CAPI=2:
-name: "kelvinv2:ip:xbar_kelvin_soc_chisel:0.1"
-description: "Kelvin SoC Chisel-Generated Crossbar"
-
-filesets:
-  rtl:
-    files:
-      - KelvinXbar.sv
-    file_type: systemVerilogSource
-
-targets:
-  default:
-    filesets:
-      - rtl
diff --git a/fpga/ip/xbar_kelvin_soc_chisel/xbar_kelvin_soc_chisel.core.tpl b/fpga/ip/xbar_kelvin_soc_chisel/xbar_kelvin_soc_chisel.core.tpl
deleted file mode 100644
index 88044d9..0000000
--- a/fpga/ip/xbar_kelvin_soc_chisel/xbar_kelvin_soc_chisel.core.tpl
+++ /dev/null
@@ -1,14 +0,0 @@
-CAPI=2:
-name: "kelvinv2:ip:xbar_kelvin_soc_chisel:0.1"
-description: "Kelvin SoC Chisel-Generated Crossbar"
-
-filesets:
-  rtl:
-    files:
-      - __VERILOG_FILE__
-    file_type: systemVerilogSource
-
-targets:
-  default:
-    filesets:
-      - rtl
diff --git a/fpga/kelvin_soc.core b/fpga/kelvin_soc.core
index 7cfb9e2..fbf4749 100644
--- a/fpga/kelvin_soc.core
+++ b/fpga/kelvin_soc.core
@@ -5,17 +5,13 @@
 filesets:
   rtl:
     depend:
-      - kelvinv2:ip:xbar_kelvin_soc_chisel:0.1
+      - kelvinv2:ip:kelvin_chisel_subsystem: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
-      - lowrisc:ibex:ibex_tracer
       - kelvinv2:ip:sram:0.1
       - kelvinv2:ip:kelvin_tlul:0.1
     files:
@@ -37,12 +33,12 @@
   ClockFrequencyMhz:
     datatype: int
     description: "Target clock frequency in MHz."
-    default: 10
+    default: 80
     paramtype: vlogparam
   MemInitFile:
     datatype: str
     description: Path to ROM
-    default: "fpga/wfi.bin"
+    default: ""
     paramtype: vlogparam
   USE_GENERIC:
     datatype: bool
diff --git a/fpga/pins.xdc b/fpga/pins.xdc
index 1f881ac..ebb1fe6 100644
--- a/fpga/pins.xdc
+++ b/fpga/pins.xdc
@@ -14,6 +14,9 @@
 # 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 }];
+set_property -dict { PACKAGE_PIN AW20 IOSTANDARD LVCMOS18 } [get_ports { spi_csb_i }];
+set_property -dict { PACKAGE_PIN AV20 IOSTANDARD LVCMOS18 } [get_ports { spi_mosi_i }];
+set_property -dict { PACKAGE_PIN AV18 IOSTANDARD LVCMOS18 } [get_ports { spi_miso_o }];
 
 # UART0
 set_property -dict { PACKAGE_PIN BF20 IOSTANDARD LVCMOS18 } [get_ports { uart_tx_o[0] }];
@@ -33,3 +36,9 @@
 set_clock_groups -asynchronous \
   -group {clk_main clk_48MHz clk_aon} \
   -group {spi_clk_i}
+
+# SPI Probe Outputs (PMOD3)
+set_property -dict { PACKAGE_PIN AU40 IOSTANDARD LVCMOS18 } [get_ports { spi_clk_probe_o }];
+set_property -dict { PACKAGE_PIN AV40 IOSTANDARD LVCMOS18 } [get_ports { spi_csb_probe_o }];
+set_property -dict { PACKAGE_PIN AW40 IOSTANDARD LVCMOS18 } [get_ports { spi_mosi_probe_o }];
+set_property -dict { PACKAGE_PIN AY39 IOSTANDARD LVCMOS18 } [get_ports { spi_miso_probe_o }];
diff --git a/fpga/rtl/chip_nexus.sv b/fpga/rtl/chip_nexus.sv
index 50dd228..588dfc3 100644
--- a/fpga/rtl/chip_nexus.sv
+++ b/fpga/rtl/chip_nexus.sv
@@ -19,20 +19,30 @@
      input clk_n_i,
      input rst_ni,
      input spi_clk_i,
+     input spi_csb_i,
+     input spi_mosi_i,
+     output logic spi_miso_o,
      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);
+     output logic io_fault_n,
+     output logic spi_clk_probe_o,
+     output logic spi_csb_probe_o,
+     output logic spi_mosi_probe_o,
+     output logic spi_miso_probe_o);
 
   logic clk;
   logic rst_n;
-  logic clk_ibex;
-  logic rst_ibex_n;
   logic clk_48MHz;
   logic clk_aon;
 
+  assign spi_clk_probe_o = spi_clk_i;
+  assign spi_csb_probe_o = spi_csb_i;
+  assign spi_mosi_probe_o = spi_mosi_i;
+  assign spi_miso_probe_o = spi_miso_o;
+
   top_pkg::uart_sideband_i_t[1 : 0] uart_sideband_i;
   top_pkg::uart_sideband_o_t[1 : 0] uart_sideband_o;
 
@@ -52,31 +62,20 @@
                .clk_main_o(clk),
                .clk_48MHz_o(clk_48MHz),
                .clk_aon_o(clk_aon),
-               .clk_ibex_o(clk_ibex),
                .rst_no(rst_n));
 
-  // Reset synchronizer for Ibex reset.
-  logic rst_n_sync;
-  always_ff @(posedge clk_ibex or negedge rst_n) begin
-    if (!rst_n) begin
-      rst_n_sync <= 1'b0;
-      rst_ibex_n <= 1'b0;
-    end else begin
-      rst_n_sync <= 1'b1;
-      rst_ibex_n <= rst_n_sync;
-    end
-  end
+  kelvin_soc i_kelvin_soc (
+    .clk_i(clk),
+    .rst_ni(rst_n),
+    .spi_clk_i(spi_clk_i),
+    .spi_csb_i(spi_csb_i),
+    .spi_mosi_i(spi_mosi_i),
+    .spi_miso_o(spi_miso_o),
+    .scanmode_i('0),
+    .uart_sideband_i(uart_sideband_i),
+    .uart_sideband_o(uart_sideband_o),
+    .io_halted(io_halted),
+    .io_fault(io_fault)
+  );
 
-  kelvin_soc #(.MemInitFile(MemInitFile),
-               .ClockFrequencyMhz(ClockFrequencyMhz))
-      i_kelvin_soc(.clk_i(clk),
-                   .rst_ni(rst_n),
-                   .ibex_clk_i(clk_ibex),
-                   .ibex_rst_ni(rst_ibex_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
index ce495ed..b83d8dc 100644
--- a/fpga/rtl/chip_verilator.sv
+++ b/fpga/rtl/chip_verilator.sv
@@ -17,11 +17,22 @@
       parameter int ClockFrequencyMhz = 80)
     (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 sck, csb, mosi, miso;
+
+  spi_dpi_master i_spi_dpi_master (
+    .clk_i(clk_i),
+    .rst_ni(rst_ni),
+    .sck_o(sck),
+    .csb_o(csb),
+    .mosi_o(mosi),
+    .miso_i(miso)
+  );
+
+
   logic uart0_rx;
   logic uart0_tx;
 
@@ -48,45 +59,22 @@
                  .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),
-                   .ibex_clk_i(ibex_clk_i),
-                   .ibex_rst_ni(ibex_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;
 
-  // Clock divider for Ibex clock.
-  logic [1:0] clk_divider;
-  always_ff @(posedge clk_i or negedge rst_ni) begin
-    if (!rst_ni) begin
-      clk_divider <= 2'b0;
-    end else begin
-      clk_divider <= clk_divider + 1;
-    end
-  end
-  logic ibex_clk_i;
-  assign ibex_clk_i = clk_divider[1];
-
-  // Reset synchronizer for Ibex reset.
-  logic ibex_rst_ni;
-  logic rst_n_sync;
-  always_ff @(posedge ibex_clk_i or negedge rst_ni) begin
-    if (!rst_ni) begin
-      rst_n_sync <= 1'b0;
-      ibex_rst_ni <= 1'b0;
-    end else begin
-      rst_n_sync <= 1'b1;
-      ibex_rst_ni <= rst_n_sync;
-    end
-  end
+  kelvin_soc #(.MemInitFile(MemInitFile),
+               .ClockFrequencyMhz(ClockFrequencyMhz))
+    i_kelvin_soc (
+      .clk_i(clk_i),
+      .rst_ni(rst_ni),
+      .spi_clk_i(sck),
+      .spi_csb_i(csb),
+      .spi_mosi_i(mosi),
+      .spi_miso_o(miso),
+      .scanmode_i('0),
+      .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/clkgen_wrapper.sv b/fpga/rtl/clkgen_wrapper.sv
index bcc8bcc..9ea066c 100644
--- a/fpga/rtl/clkgen_wrapper.sv
+++ b/fpga/rtl/clkgen_wrapper.sv
@@ -11,7 +11,6 @@
      output clk_main_o,
      output clk_48MHz_o,
      output clk_aon_o,
-     output clk_ibex_o,
      output rst_no);
 
   clkgen_xilultrascaleplus #(.ClockFrequencyMhz(ClockFrequencyMhz))
@@ -22,6 +21,5 @@
                .clk_main_o(clk_main_o),
                .clk_48MHz_o(clk_48MHz_o),
                .clk_aon_o(clk_aon_o),
-               .clk_ibex_o(clk_ibex_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
index bedc4a5..7c3de4c 100644
--- a/fpga/rtl/clkgen_xilultrascaleplus.sv
+++ b/fpga/rtl/clkgen_xilultrascaleplus.sv
@@ -24,7 +24,6 @@
      output clk_main_o,
      output clk_48MHz_o,
      output clk_aon_o,
-     output clk_ibex_o,
      output rst_no);
   logic locked_pll;
   logic io_clk_buf;
@@ -37,8 +36,6 @@
   logic clk_48_unbuf;
   logic clk_aon_buf;
   logic clk_aon_unbuf;
-  logic clk_ibex_buf;
-  logic clk_ibex_unbuf;
   logic clk_ibufds_o;
 
   // Input IBUFDS conver diff-pair to single-end
@@ -47,7 +44,6 @@
                     .O(clk_ibufds_o));
 
   localparam real CLKOUT0_DIVIDE_F_CALC = 1200.0 / ClockFrequencyMhz;
-  localparam int CLKOUT2_DIVIDE_CALC = CLKOUT0_DIVIDE_F_CALC * 4;
 
   MMCME2_ADV #(
           .BANDWIDTH("OPTIMIZED"),
@@ -62,9 +58,6 @@
           .CLKOUT1_DIVIDE(25),
           .CLKOUT1_PHASE(0.000),
           .CLKOUT1_DUTY_CYCLE(0.500),
-          .CLKOUT2_DIVIDE(CLKOUT2_DIVIDE_CALC),
-          .CLKOUT2_PHASE(0.000),
-          .CLKOUT2_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),
@@ -79,7 +72,7 @@
           .CLKOUT0B(),
           .CLKOUT1(clk_48_unbuf),
           .CLKOUT1B(),
-          .CLKOUT2(clk_ibex_unbuf),
+          .CLKOUT2(),
           .CLKOUT2B(),
           .CLKOUT3(),
           .CLKOUT3B(),
@@ -128,13 +121,10 @@
     BUFGCE clk_48_bufgce(.I(clk_48_unbuf),
                          .O(clk_48_buf));
 
-    BUFGCE clk_ibex_bufgce(.I(clk_ibex_unbuf),
-                         .O(clk_ibex_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;
-    assign clk_ibex_buf = clk_ibex_unbuf;
   end
 
   // outputs
@@ -142,7 +132,6 @@
   assign clk_main_o = clk_10_buf;
   assign clk_48MHz_o = clk_48_buf;
   assign clk_aon_o = clk_aon_buf;
-  assign clk_ibex_o = clk_ibex_buf;
 
   // reset
   assign rst_no = locked_pll & rst_ni & srst_ni;
diff --git a/fpga/rtl/kelvin_soc.sv b/fpga/rtl/kelvin_soc.sv
index 37de707..3f61b7c 100644
--- a/fpga/rtl/kelvin_soc.sv
+++ b/fpga/rtl/kelvin_soc.sv
@@ -17,9 +17,10 @@
       parameter int ClockFrequencyMhz = 80)
     (input clk_i,
      input rst_ni,
-     input ibex_clk_i,
-     input ibex_rst_ni,
      input spi_clk_i,
+     input spi_csb_i,
+     input spi_mosi_i,
+     output logic spi_miso_o,
      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,
@@ -34,15 +35,9 @@
   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_32::tl_h2d_t tl_rom_o_32;
   kelvin_tlul_pkg_32::tl_d2h_t 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;
-
   tl_h2d_t tl_sram_o;
   tl_d2h_t tl_sram_i;
 
@@ -52,168 +47,6 @@
   tl_h2d_t tl_uart1_o;
   tl_d2h_t tl_uart1_i;
 
-  tl_h2d_t tl_spi0_o;
-  tl_d2h_t tl_spi0_i;
-
-  KelvinXbar i_xbar(
-    .io_clk_i(clk_i),
-    .io_rst_ni(rst_ni),
-
-    // Host connections
-    .io_hosts_0_a_valid(tl_kelvin_core_i.a_valid),
-    .io_hosts_0_a_bits_opcode(tl_kelvin_core_i.a_opcode),
-    .io_hosts_0_a_bits_param(tl_kelvin_core_i.a_param),
-    .io_hosts_0_a_bits_size(tl_kelvin_core_i.a_size),
-    .io_hosts_0_a_bits_source(tl_kelvin_core_i.a_source),
-    .io_hosts_0_a_bits_address(tl_kelvin_core_i.a_address),
-    .io_hosts_0_a_bits_mask(tl_kelvin_core_i.a_mask),
-    .io_hosts_0_a_bits_data(tl_kelvin_core_i.a_data),
-    .io_hosts_0_a_bits_user_rsvd(tl_kelvin_core_i.a_user.rsvd),
-    .io_hosts_0_a_bits_user_instr_type(tl_kelvin_core_i.a_user.instr_type),
-    .io_hosts_0_a_bits_user_cmd_intg(tl_kelvin_core_i.a_user.cmd_intg),
-    .io_hosts_0_a_bits_user_data_intg(tl_kelvin_core_i.a_user.data_intg),
-    .io_hosts_0_d_ready(tl_kelvin_core_i.d_ready),
-    .io_hosts_1_a_valid(1'b0),
-
-    // Host response connections
-    .io_hosts_0_a_ready(tl_kelvin_core_o.a_ready),
-    .io_hosts_0_d_valid(tl_kelvin_core_o.d_valid),
-    .io_hosts_0_d_bits_opcode(tl_kelvin_core_o.d_opcode),
-    .io_hosts_0_d_bits_param(tl_kelvin_core_o.d_param),
-    .io_hosts_0_d_bits_size(tl_kelvin_core_o.d_size),
-    .io_hosts_0_d_bits_source(tl_kelvin_core_o.d_source),
-    .io_hosts_0_d_bits_sink(tl_kelvin_core_o.d_sink),
-    .io_hosts_0_d_bits_data(tl_kelvin_core_o.d_data),
-    .io_hosts_0_d_bits_error(tl_kelvin_core_o.d_error),
-    .io_hosts_0_d_bits_user_rsp_intg(tl_kelvin_core_o.d_user.rsp_intg),
-    .io_hosts_0_d_bits_user_data_intg(tl_kelvin_core_o.d_user.data_intg),
-    .io_hosts_1_d_ready(1'b0),
-
-    // Device connections
-    .io_devices_0_a_ready(tl_kelvin_device_i.a_ready),
-    .io_devices_0_d_valid(tl_kelvin_device_i.d_valid),
-    .io_devices_0_d_bits_opcode(tl_kelvin_device_i.d_opcode),
-    .io_devices_0_d_bits_param(tl_kelvin_device_i.d_param),
-    .io_devices_0_d_bits_size(tl_kelvin_device_i.d_size),
-    .io_devices_0_d_bits_source(tl_kelvin_device_i.d_source),
-    .io_devices_0_d_bits_sink(tl_kelvin_device_i.d_sink),
-    .io_devices_0_d_bits_data(tl_kelvin_device_i.d_data),
-    .io_devices_0_d_bits_error(tl_kelvin_device_i.d_error),
-    .io_devices_0_d_bits_user_rsp_intg(tl_kelvin_device_i.d_user.rsp_intg),
-    .io_devices_0_d_bits_user_data_intg(tl_kelvin_device_i.d_user.data_intg),
-    .io_devices_1_a_ready(tl_rom_i_32.a_ready),
-    .io_devices_1_d_valid(tl_rom_i_32.d_valid),
-    .io_devices_1_d_bits_opcode(tl_rom_i_32.d_opcode),
-    .io_devices_1_d_bits_param(tl_rom_i_32.d_param),
-    .io_devices_1_d_bits_size(tl_rom_i_32.d_size),
-    .io_devices_1_d_bits_source(tl_rom_i_32.d_source),
-    .io_devices_1_d_bits_sink(tl_rom_i_32.d_sink),
-    .io_devices_1_d_bits_data(tl_rom_i_32.d_data),
-    .io_devices_1_d_bits_error(tl_rom_i_32.d_error),
-    .io_devices_1_d_bits_user_rsp_intg(tl_rom_i_32.d_user.rsp_intg),
-    .io_devices_1_d_bits_user_data_intg(tl_rom_i_32.d_user.data_intg),
-    .io_devices_2_a_ready(tl_sram_i.a_ready),
-    .io_devices_2_d_valid(tl_sram_i.d_valid),
-    .io_devices_2_d_bits_opcode(tl_sram_i.d_opcode),
-    .io_devices_2_d_bits_param(tl_sram_i.d_param),
-    .io_devices_2_d_bits_size(tl_sram_i.d_size),
-    .io_devices_2_d_bits_source(tl_sram_i.d_source),
-    .io_devices_2_d_bits_sink(tl_sram_i.d_sink),
-    .io_devices_2_d_bits_data(tl_sram_i.d_data),
-    .io_devices_2_d_bits_error(tl_sram_i.d_error),
-    .io_devices_2_d_bits_user_rsp_intg(tl_sram_i.d_user.rsp_intg),
-    .io_devices_2_d_bits_user_data_intg(tl_sram_i.d_user.data_intg),
-    .io_devices_3_a_ready(tl_uart0_i.a_ready),
-    .io_devices_3_d_valid(tl_uart0_i.d_valid),
-    .io_devices_3_d_bits_opcode(tl_uart0_i.d_opcode),
-    .io_devices_3_d_bits_param(tl_uart0_i.d_param),
-    .io_devices_3_d_bits_size(tl_uart0_i.d_size),
-    .io_devices_3_d_bits_source(tl_uart0_i.d_source),
-    .io_devices_3_d_bits_sink(tl_uart0_i.d_sink),
-    .io_devices_3_d_bits_data(tl_uart0_i.d_data),
-    .io_devices_3_d_bits_error(tl_uart0_i.d_error),
-    .io_devices_3_d_bits_user_rsp_intg(tl_uart0_i.d_user.rsp_intg),
-    .io_devices_3_d_bits_user_data_intg(tl_uart0_i.d_user.data_intg),
-    .io_devices_4_a_ready(tl_uart1_i.a_ready),
-    .io_devices_4_d_valid(tl_uart1_i.d_valid),
-    .io_devices_4_d_bits_opcode(tl_uart1_i.d_opcode),
-    .io_devices_4_d_bits_param(tl_uart1_i.d_param),
-    .io_devices_4_d_bits_size(tl_uart1_i.d_size),
-    .io_devices_4_d_bits_source(tl_uart1_i.d_source),
-    .io_devices_4_d_bits_sink(tl_uart1_i.d_sink),
-    .io_devices_4_d_bits_data(tl_uart1_i.d_data),
-    .io_devices_4_d_bits_error(tl_uart1_i.d_error),
-    .io_devices_4_d_bits_user_rsp_intg(tl_uart1_i.d_user.rsp_intg),
-    .io_devices_4_d_bits_user_data_intg(tl_uart1_i.d_user.data_intg),
-
-    // Device response connections
-    .io_devices_0_a_valid(tl_kelvin_device_o.a_valid),
-    .io_devices_0_a_bits_opcode(tl_kelvin_device_o.a_opcode),
-    .io_devices_0_a_bits_param(tl_kelvin_device_o.a_param),
-    .io_devices_0_a_bits_size(tl_kelvin_device_o.a_size),
-    .io_devices_0_a_bits_source(tl_kelvin_device_o.a_source),
-    .io_devices_0_a_bits_address(tl_kelvin_device_o.a_address),
-    .io_devices_0_a_bits_mask(tl_kelvin_device_o.a_mask),
-    .io_devices_0_a_bits_data(tl_kelvin_device_o.a_data),
-    .io_devices_0_a_bits_user_rsvd(tl_kelvin_device_o.a_user.rsvd),
-    .io_devices_0_a_bits_user_instr_type(tl_kelvin_device_o.a_user.instr_type),
-    .io_devices_0_a_bits_user_cmd_intg(tl_kelvin_device_o.a_user.cmd_intg),
-    .io_devices_0_a_bits_user_data_intg(tl_kelvin_device_o.a_user.data_intg),
-    .io_devices_0_d_ready(tl_kelvin_device_o.d_ready),
-    .io_devices_1_a_valid(tl_rom_o_32.a_valid),
-    .io_devices_1_a_bits_opcode(tl_rom_o_32.a_opcode),
-    .io_devices_1_a_bits_param(tl_rom_o_32.a_param),
-    .io_devices_1_a_bits_size(tl_rom_o_32.a_size),
-    .io_devices_1_a_bits_source(tl_rom_o_32.a_source),
-    .io_devices_1_a_bits_address(tl_rom_o_32.a_address),
-    .io_devices_1_a_bits_mask(tl_rom_o_32.a_mask),
-    .io_devices_1_a_bits_data(tl_rom_o_32.a_data),
-    .io_devices_1_a_bits_user_rsvd(tl_rom_o_32.a_user.rsvd),
-    .io_devices_1_a_bits_user_instr_type(tl_rom_o_32.a_user.instr_type),
-    .io_devices_1_a_bits_user_cmd_intg(tl_rom_o_32.a_user.cmd_intg),
-    .io_devices_1_a_bits_user_data_intg(tl_rom_o_32.a_user.data_intg),
-    .io_devices_1_d_ready(tl_rom_o_32.d_ready),
-    .io_devices_2_a_valid(tl_sram_o.a_valid),
-    .io_devices_2_a_bits_opcode(tl_sram_o.a_opcode),
-    .io_devices_2_a_bits_param(tl_sram_o.a_param),
-    .io_devices_2_a_bits_size(tl_sram_o.a_size),
-    .io_devices_2_a_bits_source(tl_sram_o.a_source),
-    .io_devices_2_a_bits_address(tl_sram_o.a_address),
-    .io_devices_2_a_bits_mask(tl_sram_o.a_mask),
-    .io_devices_2_a_bits_data(tl_sram_o.a_data),
-    .io_devices_2_a_bits_user_rsvd(tl_sram_o.a_user.rsvd),
-    .io_devices_2_a_bits_user_instr_type(tl_sram_o.a_user.instr_type),
-    .io_devices_2_a_bits_user_cmd_intg(tl_sram_o.a_user.cmd_intg),
-    .io_devices_2_a_bits_user_data_intg(tl_sram_o.a_user.data_intg),
-    .io_devices_2_d_ready(tl_sram_o.d_ready),
-    .io_devices_3_a_valid(tl_uart0_o.a_valid),
-    .io_devices_3_a_bits_opcode(tl_uart0_o.a_opcode),
-    .io_devices_3_a_bits_param(tl_uart0_o.a_param),
-    .io_devices_3_a_bits_size(tl_uart0_o.a_size),
-    .io_devices_3_a_bits_source(tl_uart0_o.a_source),
-    .io_devices_3_a_bits_address(tl_uart0_o.a_address),
-    .io_devices_3_a_bits_mask(tl_uart0_o.a_mask),
-    .io_devices_3_a_bits_data(tl_uart0_o.a_data),
-    .io_devices_3_a_bits_user_rsvd(tl_uart0_o.a_user.rsvd),
-    .io_devices_3_a_bits_user_instr_type(tl_uart0_o.a_user.instr_type),
-    .io_devices_3_a_bits_user_cmd_intg(tl_uart0_o.a_user.cmd_intg),
-    .io_devices_3_a_bits_user_data_intg(tl_uart0_o.a_user.data_intg),
-    .io_devices_3_d_ready(tl_uart0_o.d_ready),
-    .io_devices_4_a_valid(tl_uart1_o.a_valid),
-    .io_devices_4_a_bits_opcode(tl_uart1_o.a_opcode),
-    .io_devices_4_a_bits_param(tl_uart1_o.a_param),
-    .io_devices_4_a_bits_size(tl_uart1_o.a_size),
-    .io_devices_4_a_bits_source(tl_uart1_o.a_source),
-    .io_devices_4_a_bits_address(tl_uart1_o.a_address),
-    .io_devices_4_a_bits_mask(tl_uart1_o.a_mask),
-    .io_devices_4_a_bits_data(tl_uart1_o.a_data),
-    .io_devices_4_a_bits_user_rsvd(tl_uart1_o.a_user.rsvd),
-    .io_devices_4_a_bits_user_instr_type(tl_uart1_o.a_user.instr_type),
-    .io_devices_4_a_bits_user_cmd_intg(tl_uart1_o.a_user.cmd_intg),
-    .io_devices_4_a_bits_user_data_intg(tl_uart1_o.a_user.data_intg),
-    .io_devices_4_d_ready(tl_uart1_o.d_ready)
-  );
-
   uart i_uart0(.clk_i(clk_i),
                .rst_ni(rst_ni),
                .tl_i(tl_uart0_o),
@@ -353,155 +186,123 @@
              .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));
+  KelvinChiselSubsystem i_chisel_subsystem (
+    .io_clk_i(clk_i),
+    .io_rst_ni(rst_ni),
 
-  logic rst_cpu_n;
+    // External Device Port 0: rom
+    .io_external_devices_ports_0_a_valid(tl_rom_o_32.a_valid),
+    .io_external_devices_ports_0_a_bits_opcode(tl_rom_o_32.a_opcode),
+    .io_external_devices_ports_0_a_bits_param(tl_rom_o_32.a_param),
+    .io_external_devices_ports_0_a_bits_size(tl_rom_o_32.a_size),
+    .io_external_devices_ports_0_a_bits_source(tl_rom_o_32.a_source),
+    .io_external_devices_ports_0_a_bits_address(tl_rom_o_32.a_address),
+    .io_external_devices_ports_0_a_bits_mask(tl_rom_o_32.a_mask),
+    .io_external_devices_ports_0_a_bits_data(tl_rom_o_32.a_data),
+    .io_external_devices_ports_0_a_bits_user_rsvd(tl_rom_o_32.a_user.rsvd),
+    .io_external_devices_ports_0_a_bits_user_instr_type(tl_rom_o_32.a_user.instr_type),
+    .io_external_devices_ports_0_a_bits_user_cmd_intg(tl_rom_o_32.a_user.cmd_intg),
+    .io_external_devices_ports_0_a_bits_user_data_intg(tl_rom_o_32.a_user.data_intg),
+    .io_external_devices_ports_0_d_ready(tl_rom_o_32.d_ready),
+    .io_external_devices_ports_0_a_ready(tl_rom_i_32.a_ready),
+    .io_external_devices_ports_0_d_valid(tl_rom_i_32.d_valid),
+    .io_external_devices_ports_0_d_bits_opcode(tl_rom_i_32.d_opcode),
+    .io_external_devices_ports_0_d_bits_param(tl_rom_i_32.d_param),
+    .io_external_devices_ports_0_d_bits_size(tl_rom_i_32.d_size),
+    .io_external_devices_ports_0_d_bits_source(tl_rom_i_32.d_source),
+    .io_external_devices_ports_0_d_bits_sink(tl_rom_i_32.d_sink),
+    .io_external_devices_ports_0_d_bits_data(tl_rom_i_32.d_data),
+    .io_external_devices_ports_0_d_bits_error(tl_rom_i_32.d_error),
+    .io_external_devices_ports_0_d_bits_user_rsp_intg(tl_rom_i_32.d_user.rsp_intg),
+    .io_external_devices_ports_0_d_bits_user_data_intg(tl_rom_i_32.d_user.data_intg),
 
-  // Kelvin Core Instantiation
-  logic kelvin_halted, kelvin_fault, kelvin_wfi;
-  assign io_halted = kelvin_halted;
-  assign io_fault = kelvin_fault;
+    // External Device Port 1: sram
+    .io_external_devices_ports_1_a_valid(tl_sram_o.a_valid),
+    .io_external_devices_ports_1_a_bits_opcode(tl_sram_o.a_opcode),
+    .io_external_devices_ports_1_a_bits_param(tl_sram_o.a_param),
+    .io_external_devices_ports_1_a_bits_size(tl_sram_o.a_size),
+    .io_external_devices_ports_1_a_bits_source(tl_sram_o.a_source),
+    .io_external_devices_ports_1_a_bits_address(tl_sram_o.a_address),
+    .io_external_devices_ports_1_a_bits_mask(tl_sram_o.a_mask),
+    .io_external_devices_ports_1_a_bits_data(tl_sram_o.a_data),
+    .io_external_devices_ports_1_a_bits_user_rsvd(tl_sram_o.a_user.rsvd),
+    .io_external_devices_ports_1_a_bits_user_instr_type(tl_sram_o.a_user.instr_type),
+    .io_external_devices_ports_1_a_bits_user_cmd_intg(tl_sram_o.a_user.cmd_intg),
+    .io_external_devices_ports_1_a_bits_user_data_intg(tl_sram_o.a_user.data_intg),
+    .io_external_devices_ports_1_d_ready(tl_sram_o.d_ready),
+    .io_external_devices_ports_1_a_ready(tl_sram_i.a_ready),
+    .io_external_devices_ports_1_d_valid(tl_sram_i.d_valid),
+    .io_external_devices_ports_1_d_bits_opcode(tl_sram_i.d_opcode),
+    .io_external_devices_ports_1_d_bits_param(tl_sram_i.d_param),
+    .io_external_devices_ports_1_d_bits_size(tl_sram_i.d_size),
+    .io_external_devices_ports_1_d_bits_source(tl_sram_i.d_source),
+    .io_external_devices_ports_1_d_bits_sink(tl_sram_i.d_sink),
+    .io_external_devices_ports_1_d_bits_data(tl_sram_i.d_data),
+    .io_external_devices_ports_1_d_bits_error(tl_sram_i.d_error),
+    .io_external_devices_ports_1_d_bits_user_rsp_intg(tl_sram_i.d_user.rsp_intg),
+    .io_external_devices_ports_1_d_bits_user_data_intg(tl_sram_i.d_user.data_intg),
 
-  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(tl_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(tl_kelvin_core_i.a_user.instr_type),
-              .io_tl_host_a_bits_user_cmd_intg(tl_kelvin_core_i.a_user.cmd_intg),
-              .io_tl_host_a_bits_user_data_intg(tl_kelvin_core_i.a_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_kelvin_device_i.a_ready),
-              .io_tl_device_d_valid(tl_kelvin_device_i.d_valid),
-              .io_tl_device_d_bits_opcode(tl_kelvin_device_i.d_opcode),
-              .io_tl_device_d_bits_param(tl_kelvin_device_i.d_param),
-              .io_tl_device_d_bits_size(tl_kelvin_device_i.d_size),
-              .io_tl_device_d_bits_source(tl_kelvin_device_i.d_source),
-              .io_tl_device_d_bits_sink(tl_kelvin_device_i.d_sink),
-              .io_tl_device_d_bits_data(tl_kelvin_device_i.d_data),
-              .io_tl_device_d_bits_error(tl_kelvin_device_i.d_error),
-              .io_tl_device_d_bits_user_rsp_intg(tl_kelvin_device_i.d_user.rsp_intg),
-              .io_tl_device_d_bits_user_data_intg(tl_kelvin_device_i.d_user.data_intg),
-              .io_halted(kelvin_halted),
-              .io_fault(kelvin_fault),
-              .io_wfi(kelvin_wfi),
-              .io_irq(1'b0),
-              .io_te(1'b0));
+    // External Device Port 2: uart0
+    .io_external_devices_ports_2_a_valid(tl_uart0_o.a_valid),
+    .io_external_devices_ports_2_a_bits_opcode(tl_uart0_o.a_opcode),
+    .io_external_devices_ports_2_a_bits_param(tl_uart0_o.a_param),
+    .io_external_devices_ports_2_a_bits_size(tl_uart0_o.a_size),
+    .io_external_devices_ports_2_a_bits_source(tl_uart0_o.a_source),
+    .io_external_devices_ports_2_a_bits_address(tl_uart0_o.a_address),
+    .io_external_devices_ports_2_a_bits_mask(tl_uart0_o.a_mask),
+    .io_external_devices_ports_2_a_bits_data(tl_uart0_o.a_data),
+    .io_external_devices_ports_2_a_bits_user_rsvd(tl_uart0_o.a_user.rsvd),
+    .io_external_devices_ports_2_a_bits_user_instr_type(tl_uart0_o.a_user.instr_type),
+    .io_external_devices_ports_2_a_bits_user_cmd_intg(tl_uart0_o.a_user.cmd_intg),
+    .io_external_devices_ports_2_a_bits_user_data_intg(tl_uart0_o.a_user.data_intg),
+    .io_external_devices_ports_2_d_ready(tl_uart0_o.d_ready),
+    .io_external_devices_ports_2_a_ready(tl_uart0_i.a_ready),
+    .io_external_devices_ports_2_d_valid(tl_uart0_i.d_valid),
+    .io_external_devices_ports_2_d_bits_opcode(tl_uart0_i.d_opcode),
+    .io_external_devices_ports_2_d_bits_param(tl_uart0_i.d_param),
+    .io_external_devices_ports_2_d_bits_size(tl_uart0_i.d_size),
+    .io_external_devices_ports_2_d_bits_source(tl_uart0_i.d_source),
+    .io_external_devices_ports_2_d_bits_sink(tl_uart0_i.d_sink),
+    .io_external_devices_ports_2_d_bits_data(tl_uart0_i.d_data),
+    .io_external_devices_ports_2_d_bits_error(tl_uart0_i.d_error),
+    .io_external_devices_ports_2_d_bits_user_rsp_intg(tl_uart0_i.d_user.rsp_intg),
+    .io_external_devices_ports_2_d_bits_user_data_intg(tl_uart0_i.d_user.data_intg),
 
-  // Ibex Core Instantiation
-  rv_core_ibex #(.PipeLine(1'b1),
-                 .PMPEnable(1'b0))
-      i_ibex_core(.clk_i(ibex_clk_i),
-                  .rst_ni(ibex_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());
+    // External Device Port 3: uart1
+    .io_external_devices_ports_3_a_valid(tl_uart1_o.a_valid),
+    .io_external_devices_ports_3_a_bits_opcode(tl_uart1_o.a_opcode),
+    .io_external_devices_ports_3_a_bits_param(tl_uart1_o.a_param),
+    .io_external_devices_ports_3_a_bits_size(tl_uart1_o.a_size),
+    .io_external_devices_ports_3_a_bits_source(tl_uart1_o.a_source),
+    .io_external_devices_ports_3_a_bits_address(tl_uart1_o.a_address),
+    .io_external_devices_ports_3_a_bits_mask(tl_uart1_o.a_mask),
+    .io_external_devices_ports_3_a_bits_data(tl_uart1_o.a_data),
+    .io_external_devices_ports_3_a_bits_user_rsvd(tl_uart1_o.a_user.rsvd),
+    .io_external_devices_ports_3_a_bits_user_instr_type(tl_uart1_o.a_user.instr_type),
+    .io_external_devices_ports_3_a_bits_user_cmd_intg(tl_uart1_o.a_user.cmd_intg),
+    .io_external_devices_ports_3_a_bits_user_data_intg(tl_uart1_o.a_user.data_intg),
+    .io_external_devices_ports_3_d_ready(tl_uart1_o.d_ready),
+    .io_external_devices_ports_3_a_ready(tl_uart1_i.a_ready),
+    .io_external_devices_ports_3_d_valid(tl_uart1_i.d_valid),
+    .io_external_devices_ports_3_d_bits_opcode(tl_uart1_i.d_opcode),
+    .io_external_devices_ports_3_d_bits_param(tl_uart1_i.d_param),
+    .io_external_devices_ports_3_d_bits_size(tl_uart1_i.d_size),
+    .io_external_devices_ports_3_d_bits_source(tl_uart1_i.d_source),
+    .io_external_devices_ports_3_d_bits_sink(tl_uart1_i.d_sink),
+    .io_external_devices_ports_3_d_bits_data(tl_uart1_i.d_data),
+    .io_external_devices_ports_3_d_bits_error(tl_uart1_i.d_error),
+    .io_external_devices_ports_3_d_bits_user_rsp_intg(tl_uart1_i.d_user.rsp_intg),
+    .io_external_devices_ports_3_d_bits_user_data_intg(tl_uart1_i.d_user.data_intg),
+
+    // Peripheral Ports (indexed based on SoCChiselConfig order)
+    .io_external_ports_0(io_halted),      // halted
+    .io_external_ports_1(io_fault),       // fault
+    .io_external_ports_2(),               // wfi (unused)
+    .io_external_ports_3(1'b0),           // irq (tied off)
+    .io_external_ports_4(1'b0),           // te (tied off)
+    .io_external_ports_5(spi_clk_i),      // spi_clk
+    .io_external_ports_6(spi_csb_i),      // spi_csb
+    .io_external_ports_7(spi_mosi_i),     // spi_mosi
+    .io_external_ports_8(spi_miso_o)      // spi_miso
+  );
 endmodule
diff --git a/fpga/sw/ibex_boot_rom.S b/fpga/sw/ibex_boot_rom.S
deleted file mode 100644
index 1c2bfe8..0000000
--- a/fpga/sw/ibex_boot_rom.S
+++ /dev/null
@@ -1,45 +0,0 @@
-// 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
deleted file mode 100644
index 95ab232..0000000
--- a/fpga/sw/ibex_boot_rom.ld
+++ /dev/null
@@ -1,20 +0,0 @@
-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
deleted file mode 100644
index f9c7b8e..0000000
--- a/fpga/sw/main.cc
+++ /dev/null
@@ -1,76 +0,0 @@
-#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.
-  // NB: Use this copy loop instead of memcpy to get word writes
-  // instead of byte writes.
-  uint32_t* itcm_base =
-      reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(0x0));
-  const uint32_t* add_uint32_m1_bin_u32 =
-      reinterpret_cast<const uint32_t*>(add_uint32_m1_bin);
-  for (unsigned int i = 0; i < add_uint32_m1_bin_len / 4; i++) {
-    *(itcm_base + i) = add_uint32_m1_bin_u32[i];
-  }
-
-  // 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