Repository: chipsalliance/caliptra-sw
Pull request #142 (2023-04-05 00:15:30)

hw-model: Set fuses via soc_ifc, change HwModel::new() semantics
This PR attempts to unify fuses between HwModelEmulated and HwModelVerilated, by removing all the fuse-related configuration from CaliptraRootBusArgs, and instead using the external soc_ifc bus to set them during early boot.

Also, caliptra_hw_model::create() is replaced by caliptra_hw_model::new(), which has slightly different semantics. This function is intended to be used by most test cases to set the fuses, start the boot FSM, and (optionally) upload the firmware to the mailbox. A bare-bones caliptra_hw_model::new_unbooted() function is provided for test cases that need to manipulate the external SoC->Caliptra APB bus directly during early boot in a non-standard way.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index fe1ed868..7f01465d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -160,10 +160,14 @@ dependencies = [
  "caliptra-emu-cpu",
  "caliptra-emu-periph",
  "caliptra-emu-types",
+ "caliptra-hw-model",
+ "caliptra-hw-model-types",
+ "caliptra-registers",
  "clap",
  "gdbstub",
  "gdbstub_arch",
  "hex",
+ "tock-registers",
 ]
 
 [[package]]
@@ -216,6 +220,7 @@ dependencies = [
  "caliptra-emu-crypto",
  "caliptra-emu-derive",
  "caliptra-emu-types",
+ "caliptra-hw-model-types",
  "lazy_static",
  "smlang",
  "tock-registers",
@@ -261,11 +266,16 @@ dependencies = [
  "caliptra-emu-cpu",
  "caliptra-emu-periph",
  "caliptra-emu-types",
+ "caliptra-hw-model-types",
  "caliptra-registers",
  "caliptra-verilated",
  "ureg",
 ]
 
+[[package]]
+name = "caliptra-hw-model-types"
+version = "0.1.0"
+
 [[package]]
 name = "caliptra-image-app"
 version = "0.5.0"
diff --git a/Cargo.toml b/Cargo.toml
index ef4851b4..e65f0591 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,6 +23,7 @@ members = [
   "fmc/test-fw/test-rt",
   "hw-latest/verilated",
   "hw-model",
+  "hw-model/types",
   "registers",
   "registers/bin/generator",
   "runtime",
diff --git a/hw-model/Cargo.toml b/hw-model/Cargo.toml
index 9670a27d..20699074 100644
--- a/hw-model/Cargo.toml
+++ b/hw-model/Cargo.toml
@@ -16,6 +16,7 @@ caliptra-emu-bus = { path = "../sw-emulator/lib/bus" }
 caliptra-emu-cpu = { path = "../sw-emulator/lib/cpu" }
 caliptra-emu-periph = { path = "../sw-emulator/lib/periph" }
 caliptra-emu-types = { path = "../sw-emulator/lib/types" }
+caliptra-hw-model-types = { path = "types" }
 caliptra-registers = { path = "../registers" }
 ureg = { path = "../ureg" }
-caliptra-verilated = { path = "../hw-latest/verilated", optional = true, features = ["verilator"] }
+caliptra-verilated = { path = "../hw-latest/verilated", optional = true, features = ["verilator"] }
\ No newline at end of file
diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs
index 467c0bb9..8b34a65d 100644
--- a/hw-model/src/lib.rs
+++ b/hw-model/src/lib.rs
@@ -2,7 +2,8 @@
 
 use std::{
     error::Error,
-    io::{stdout, ErrorKind},
+    fmt::Display,
+    io::{stdout, ErrorKind, Write},
 };
 
 use caliptra_emu_bus::Bus;
@@ -15,7 +16,8 @@ mod model_verilated;
 mod output;
 mod rv32_builder;
 
-use mmio::BusMmio;
+use caliptra_hw_model_types::{DeviceLifecycle, Fuses, SecurityState};
+pub use mmio::BusMmio;
 use output::ExitStatus;
 pub use output::Output;
 
@@ -28,7 +30,11 @@ use caliptra_emu_types::RvSize;
 
 /// Constructs an HwModel based on the cargo features and environment
 /// variables. Most test cases that need to construct a HwModel should use this
-/// function.
+/// function over HwModel::new_unbooted().
+///
+/// The model returned by this function does not have any fuses programmed and
+/// is not yet ready to execute code in the microcontroller. Most test cases
+/// should use [`new`] instead.
 ///
 /// Ideally this function would return `Result<impl HwModel, Box<dyn Error>>`
 /// to prevent users from calling functions that weren't available on HwModel
@@ -37,13 +43,29 @@ use caliptra_emu_types::RvSize;
 /// full type until they fix that. Users should treat this return type as if it
 /// were `impl HwModel`.
 #[cfg(not(feature = "verilator"))]
-pub fn create(params: InitParams) -> Result<ModelEmulated, Box<dyn Error>> {
-    ModelEmulated::init(params)
+pub fn new_unbooted(params: InitParams) -> Result<ModelEmulated, Box<dyn Error>> {
+    ModelEmulated::new_unbooted(params)
+}
+
+/// Constructs an HwModel based on the cargo features and environment variables,
+/// and boot it to the point where CPU execution can occur. This includes
+/// programming the fuses, initializing the boot_fsm state machine, and
+/// (optionally) uploading firmware. Most test cases that need to construct a
+/// HwModel should use this function over [`HwModel::new()`] and
+/// [`crate::new_unbooted`].
+#[cfg(not(feature = "verilator"))]
+pub fn new(params: BootParams) -> Result<ModelEmulated, Box<dyn Error>> {
+    ModelEmulated::new(params)
 }
 
 #[cfg(feature = "verilator")]
-pub fn create(params: InitParams) -> Result<ModelVerilated, Box<dyn Error>> {
-    ModelVerilated::init(params)
+pub fn new_unbooted(params: InitParams) -> Result<ModelVerilated, Box<dyn Error>> {
+    ModelVerilated::new_unbooted(params)
+}
+
+#[cfg(feature = "verilator")]
+pub fn new(params: BootParams) -> Result<ModelVerilated, Box<dyn Error>> {
+    ModelVerilated::new(params)
 }
 
 pub struct InitParams<'a> {
@@ -57,6 +79,8 @@ pub struct InitParams<'a> {
     pub iccm: &'a [u8],
 
     pub log_writer: Box<dyn std::io::Write>,
+
+    pub security_state: SecurityState,
 }
 impl<'a> Default for InitParams<'a> {
     fn default() -> Self {
@@ -65,31 +89,92 @@ impl<'a> Default for InitParams<'a> {
             dccm: Default::default(),
             iccm: Default::default(),
             log_writer: Box::new(stdout()),
+            security_state: *SecurityState::default()
+                .set_device_lifecycle(DeviceLifecycle::Unprovisioned),
         }
     }
 }
 
+#[derive(Default)]
+pub struct BootParams<'a> {
+    pub init_params: InitParams<'a>,
+    pub fuses: Fuses,
+    pub fw_image: Option<&'a [u8]>,
+}
+
 #[derive(Debug)]
 pub enum ModelError {
     MailboxErr,
     NotReadyForFwErr,
+    ReadyForFirmwareTimeout { cycles: u32 },
+}
+impl Error for ModelError {}
+impl Display for ModelError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            ModelError::MailboxErr => write!(f, "Mailbox error"),
+            ModelError::NotReadyForFwErr => write!(f, "Not ready for firmware"),
+            ModelError::ReadyForFirmwareTimeout { cycles } => write!(
+                f,
+                "Ready-for-firmware signal not received after {cycles} cycles"
+            ),
+        }
+    }
 }
 
 /// Firmware Load Command Opcode
 const FW_LOAD_CMD_OPCODE: u32 = 0x4657_4C44;
 
 // Represents a emulator or simulation of the caliptra hardware, to be called
-// from tests. Typically, test cases should use `create()` to create a model
+// from tests. Typically, test cases should use [`crate::new()`] to create a model
 // based on the cargo features (and any model-specific environment variables).
 pub trait HwModel {
     type TBus<'a>: Bus
     where
         Self: 'a;
 
-    fn init(params: InitParams) -> Result<Self, Box<dyn Error>>
+    /// Create a model. Most high-level tests should use [`new()`]
+    /// instead.
+    fn new_unbooted(params: InitParams) -> Result<Self, Box<dyn Error>>
     where
         Self: Sized;
 
+    /// Create a model, and boot it to the point where CPU execution can
+    /// occur. This includes programming the fuses, initializing the
+    /// boot_fsm state machine, and (optionally) uploading firmware.
+    fn new(run_params: BootParams) -> Result<Self, Box<dyn Error>>
+    where
+        Self: Sized,
+    {
+        let mut hw: Self = HwModel::new_unbooted(run_params.init_params)?;
+
+        hw.init_fuses(&run_params.fuses);
+
+        hw.soc_ifc().cptra_bootfsm_go().write(|w| w.go(true));
+
+        hw.step();
+
+        if let Some(fw_image) = run_params.fw_image {
+            const MAX_WAIT_CYCLES: u32 = 12_000_000;
+            let mut cycles = 0;
+            while !hw.ready_for_fw() {
+                hw.step();
+                cycles += 1;
+                if cycles > MAX_WAIT_CYCLES {
+                    return Err(ModelError::ReadyForFirmwareTimeout { cycles }.into());
+                }
+            }
+            writeln!(
+                hw.output().logger(),
+                "ready_for_fw high after {cycles} cycles"
+            )
+            .unwrap();
+            hw.upload_firmware(fw_image)?;
+        }
+
+        Ok(hw)
+    }
+
     /// The APB bus from the SoC to Caliptra
     ///
     /// WARNING: Reading or writing to this bus may involve the Caliptra
@@ -109,8 +194,59 @@ pub trait HwModel {
         }
     }
 
+    /// Returns true if the microcontroller has signalled that it is ready for
+    /// firmware to be written to the mailbox. For RTL implementations, this
+    /// should come via a caliptra_top wire rather than an APB register.
     fn ready_for_fw(&self) -> bool;
 
+    /// Initializes the fuse values and locks them in until the next reset. This
+    /// function can only be called during early boot, shortly after the model
+    /// is created with `new_unbooted()`.
+    ///
+    /// # Panics
+    ///
+    /// If the cptra_fuse_wr_done has already been written, or the
+    /// hardware prevents cptra_fuse_wr_done from being set.
+    fn init_fuses(&mut self, fuses: &Fuses) {
+        assert!(
+            !self.soc_ifc().cptra_fuse_wr_done().read().done(),
+            "Fuses are already locked in place (according to cptra_fuse_wr_done)"
+        );
+
+        self.soc_ifc().fuse_uds_seed().write(&fuses.uds_seed);
+        self.soc_ifc()
+            .fuse_field_entropy()
+            .write(&fuses.field_entropy);
+        self.soc_ifc()
+            .fuse_key_manifest_pk_hash()
+            .write(&fuses.key_manifest_pk_hash);
+        self.soc_ifc()
+            .fuse_key_manifest_pk_hash_mask()
+            .write(|w| w.mask(fuses.key_manifest_pk_hash_mask.into()));
+        self.soc_ifc()
+            .fuse_owner_pk_hash()
+            .write(&fuses.owner_pk_hash);
+        self.soc_ifc()
+            .fuse_fmc_key_manifest_svn()
+            .write(|_| fuses.fmc_key_manifest_svn);
+        self.soc_ifc().fuse_runtime_svn().write(&fuses.runtime_svn);
+        self.soc_ifc()
+            .fuse_anti_rollback_disable()
+            .write(|w| w.dis(fuses.anti_rollback_disable));
+        self.soc_ifc()
+            .fuse_idevid_cert_attr()
+            .write(&fuses.idevid_cert_attr);
+        self.soc_ifc()
+            .fuse_idevid_manuf_hsm_id()
+            .write(&fuses.idevid_manuf_hsm_id);
+        self.soc_ifc()
+            .fuse_life_cycle()
+            .write(|w| w.life_cycle(fuses.life_cycle.into()));
+
+        self.soc_ifc().cptra_fuse_wr_done().write(|w| w.done(true));
+        assert!(self.soc_ifc().cptra_fuse_wr_done().read().done());
+    }
+
     fn step_until_exit_success(&mut self) -> std::io::Result<()> {
         self.copy_output_until_exit_success(std::io::Sink::default())
     }
@@ -176,7 +312,7 @@ pub trait HwModel {
     fn tracing_hint(&mut self, enable: bool);
 
     /// Upload firmware to the mailbox.
-    fn upload_firmware(&mut self, firmware: &Vec<u8>) -> Result<(), ModelError> {
+    fn upload_firmware(&mut self, firmware: &[u8]) -> Result<(), ModelError> {
         if self.soc_mbox().lock().read().lock() {
             return Err(ModelError::MailboxErr);
         }
@@ -219,7 +355,7 @@ pub trait HwModel {
 
 #[cfg(test)]
 mod tests {
-    use crate::{mmio::Rv32GenMmio, HwModel, InitParams};
+    use crate::{mmio::Rv32GenMmio, BootParams, HwModel, InitParams};
     use caliptra_emu_bus::Bus;
     use caliptra_emu_types::RvSize;
     use caliptra_registers::soc_ifc;
@@ -230,7 +366,6 @@ mod tests {
     const MBOX_ADDR_LOCK: u32 = MBOX_ADDR_BASE;
     const MBOX_ADDR_CMD: u32 = MBOX_ADDR_BASE + 0x0000_0008;
 
-    #[cfg(feature = "verilator")]
     fn gen_image_fw_ready() -> Vec<u8> {
         let rv32_gen = Rv32GenMmio::new();
         let soc_ifc =
@@ -257,11 +392,14 @@ mod tests {
 
     #[test]
     fn test_apb() {
-        let mut model = caliptra_hw_model::create(InitParams {
+        let mut model = caliptra_hw_model::new_unbooted(InitParams {
             ..Default::default()
         })
         .unwrap();
 
+        model.soc_ifc().cptra_fuse_wr_done().write(|w| w.done(true));
+        model.soc_ifc().cptra_bootfsm_go().write(|w| w.go(true));
+
         assert_eq!(
             model.apb_bus().read(RvSize::Word, MBOX_ADDR_LOCK).unwrap(),
             0
@@ -285,11 +423,14 @@ mod tests {
     #[test]
     fn test_mbox() {
         // Same as test_apb, but uses higher-level register interface
-        let mut model = caliptra_hw_model::create(InitParams {
+        let mut model = caliptra_hw_model::new_unbooted(InitParams {
             ..Default::default()
         })
         .unwrap();
 
+        model.soc_ifc().cptra_fuse_wr_done().write(|w| w.done(true));
+        model.soc_ifc().cptra_bootfsm_go().write(|w| w.go(true));
+
         assert!(!model.soc_mbox().lock().read().lock());
         assert!(model.soc_mbox().lock().read().lock());
 
@@ -299,19 +440,24 @@ mod tests {
 
     #[test]
     fn test_execution() {
-        let mut model = caliptra_hw_model::create(InitParams {
-            rom: &gen_image_hi(),
+        let mut model = caliptra_hw_model::new(BootParams {
+            init_params: InitParams {
+                rom: &gen_image_hi(),
+                ..Default::default()
+            },
             ..Default::default()
         })
         .unwrap();
-
         model.step_until_output("hi").unwrap();
     }
 
     #[test]
     fn test_output_failure() {
-        let mut model = caliptra_hw_model::create(InitParams {
-            rom: &gen_image_hi(),
+        let mut model = caliptra_hw_model::new(BootParams {
+            init_params: InitParams {
+                rom: &gen_image_hi(),
+                ..Default::default()
+            },
             ..Default::default()
         })
         .unwrap();
@@ -331,15 +477,16 @@ mod tests {
         ]
         .into();
 
-        let mut model = caliptra_hw_model::create(InitParams {
-            #[cfg(feature = "verilator")]
-            rom: &gen_image_fw_ready(),
+        let mut model = caliptra_hw_model::new(BootParams {
+            init_params: InitParams {
+                rom: &gen_image_fw_ready(),
+                ..Default::default()
+            },
             ..Default::default()
         })
         .unwrap();
 
         // Wait for ROM to request firmware.
-        #[cfg(feature = "verilator")]
         model.step_until(|m| m.soc_ifc().cptra_flow_status().read().ready_for_fw());
 
         assert!(model.upload_firmware(&firmware).is_ok());
diff --git a/hw-model/src/model_emulated.rs b/hw-model/src/model_emulated.rs
index e5f7a177..81422b75 100644
--- a/hw-model/src/model_emulated.rs
+++ b/hw-model/src/model_emulated.rs
@@ -13,8 +13,9 @@ use std::rc::Rc;
 use caliptra_emu_bus::Clock;
 use caliptra_emu_cpu::Cpu;
 use caliptra_emu_cpu::InstrTracer;
+use caliptra_emu_periph::ActionCb;
 use caliptra_emu_periph::ReadyForFwCb;
-use caliptra_emu_periph::{CaliptraRootBus, CaliptraRootBusArgs, TbServicesCb};
+use caliptra_emu_periph::{CaliptraRootBus, CaliptraRootBusArgs, SocToCaliptraBus, TbServicesCb};
 use caliptra_emu_types::{RvAddr, RvData, RvSize};
 
 use crate::InitParams;
@@ -46,7 +47,7 @@ pub struct EmulatedApbBus<'a> {
 
 impl<'a> Bus for EmulatedApbBus<'a> {
     fn read(&mut self, size: RvSize, addr: RvAddr) -> Result<RvData, caliptra_emu_bus::BusError> {
-        let result = self.model.cpu.bus.bus.read(size, addr);
+        let result = self.model.soc_to_caliptra_bus.read(size, addr);
         self.model.cpu.bus.log_read("SoC", size, addr, result);
         result
     }
@@ -56,7 +57,7 @@ impl<'a> Bus for EmulatedApbBus<'a> {
         addr: RvAddr,
         val: RvData,
     ) -> Result<(), caliptra_emu_bus::BusError> {
-        let result = self.model.cpu.bus.write(size, addr, val);
+        let result = self.model.soc_to_caliptra_bus.write(size, addr, val);
         self.model.cpu.bus.log_write("SoC", size, addr, val, result);
         result
     }
@@ -151,15 +152,17 @@ impl<TBus: Bus> Bus for BusLogger<TBus> {
 /// Emulated model
 pub struct ModelEmulated {
     cpu: Cpu<BusLogger<CaliptraRootBus>>,
+    soc_to_caliptra_bus: SocToCaliptraBus,
     output: Output,
     trace_fn: Option<Box<InstrTracer<'static>>>,
     ready_for_fw: Rc<Cell<bool>>,
+    cpu_enabled: Rc<Cell<bool>>,
 }
 
 impl crate::HwModel for ModelEmulated {
     type TBus<'a> = EmulatedApbBus<'a>;
 
-    fn init(params: InitParams) -> Result<Self, Box<dyn Error>>
+    fn new_unbooted(params: InitParams) -> Result<Self, Box<dyn Error>>
     where
         Self: Sized,
     {
@@ -168,30 +171,38 @@ impl crate::HwModel for ModelEmulated {
         let ready_for_fw = Rc::new(Cell::new(false));
         let ready_for_fw_clone = ready_for_fw.clone();
 
+        let cpu_enabled = Rc::new(Cell::new(false));
+        let cpu_enabled_cloned = cpu_enabled.clone();
+
         let output = Output::new(params.log_writer);
 
         let output_sink = output.sink().clone();
 
         let bus_args = CaliptraRootBusArgs {
             rom: params.rom.into(),
-            tb_services_cb: TbServicesCb(Box::new(move |ch| {
+            tb_services_cb: TbServicesCb::new(move |ch| {
                 output_sink.push_uart_char(ch);
-            })),
-            ready_for_fw_cb: ReadyForFwCb(Box::new(move |_| {
+            }),
+            ready_for_fw_cb: ReadyForFwCb::new(move |_| {
                 ready_for_fw_clone.set(true);
-            })),
+            }),
+            bootfsm_go_cb: ActionCb::new(move || {
+                cpu_enabled_cloned.set(true);
+            }),
+            security_state: params.security_state,
             ..CaliptraRootBusArgs::default()
         };
-        let cpu = Cpu::new(
-            BusLogger::new(CaliptraRootBus::new(&clock, bus_args)),
-            clock,
-        );
+        let root_bus = CaliptraRootBus::new(&clock, bus_args);
+        let soc_to_caliptra_bus = root_bus.soc_to_caliptra_bus();
+        let cpu = Cpu::new(BusLogger::new(root_bus), clock);
 
         let mut m = ModelEmulated {
             output,
             cpu,
+            soc_to_caliptra_bus,
             trace_fn: None,
             ready_for_fw,
+            cpu_enabled,
         };
         // Turn tracing on if CPTRA_TRACE_PATH environment variable is set
         m.tracing_hint(true);
@@ -207,7 +218,9 @@ impl crate::HwModel for ModelEmulated {
     }
 
     fn step(&mut self) {
-        self.cpu.step(self.trace_fn.as_deref_mut());
+        if self.cpu_enabled.get() {
+            self.cpu.step(self.trace_fn.as_deref_mut());
+        }
     }
 
     fn output(&mut self) -> &mut Output {
diff --git a/hw-model/src/model_verilated.rs b/hw-model/src/model_verilated.rs
index 5920a78e..0e8d136d 100644
--- a/hw-model/src/model_verilated.rs
+++ b/hw-model/src/model_verilated.rs
@@ -57,7 +57,7 @@ impl ModelVerilated {
 impl crate::HwModel for ModelVerilated {
     type TBus<'a> = VerilatedApbBus<'a>;
 
-    fn init(params: crate::InitParams) -> Result<Self, Box<dyn std::error::Error>>
+    fn new_unbooted(params: crate::InitParams) -> Result<Self, Box<dyn std::error::Error>>
     where
         Self: Sized,
     {
@@ -67,7 +67,12 @@ impl crate::HwModel for ModelVerilated {
         let generic_load_cb = Box::new(move |ch| {
             output_sink.push_uart_char(ch);
         });
-        let mut v = CaliptraVerilated::with_generic_load_cb(generic_load_cb);
+        let mut v = CaliptraVerilated::with_generic_load_cb(
+            caliptra_verilated::InitArgs {
+                security_state: u32::from(params.security_state),
+            },
+            generic_load_cb,
+        );
 
         v.write_rom_image(params.rom);
 
@@ -88,13 +93,6 @@ impl crate::HwModel for ModelVerilated {
         while !m.v.output.ready_for_fuses {
             m.v.next_cycle_high(1);
         }
-
-        m.soc_ifc().cptra_fuse_wr_done().write(|w| w.done(true));
-        assert!(m.soc_ifc().cptra_fuse_wr_done().read().done());
-        m.soc_ifc().cptra_bootfsm_go().write(|w| w.go(true));
-
-        m.v.next_cycle_high(2);
-
         Ok(m)
     }
 
diff --git a/hw-model/types/Cargo.toml b/hw-model/types/Cargo.toml
new file mode 100644
index 00000000..343f754f
--- /dev/null
+++ b/hw-model/types/Cargo.toml
@@ -0,0 +1,10 @@
+# Licensed under the Apache-2.0 license
+
+[package]
+name = "caliptra-hw-model-types"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/hw-model/types/src/lib.rs b/hw-model/types/src/lib.rs
new file mode 100644
index 00000000..a47160e5
--- /dev/null
+++ b/hw-model/types/src/lib.rs
@@ -0,0 +1,140 @@
+// Licensed under the Apache-2.0 license
+
+// Based on device_lifecycle_e from RTL
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub enum DeviceLifecycle {
+    #[default]
+    Unprovisioned = 0b00,
+    Manufacturing = 0b01,
+    Reserved2 = 0b10,
+    Production = 0b11,
+}
+impl TryFrom<u32> for DeviceLifecycle {
+    type Error = ();
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        match value {
+            0b00 => Ok(Self::Unprovisioned),
+            0b01 => Ok(Self::Manufacturing),
+            0b10 => Ok(Self::Reserved2),
+            0b11 => Ok(Self::Production),
+            _ => Err(()),
+        }
+    }
+}
+impl From<DeviceLifecycle> for u32 {
+    fn from(value: DeviceLifecycle) -> Self {
+        value as u32
+    }
+}
+
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub struct SecurityState(u32);
+impl From<u32> for SecurityState {
+    fn from(value: u32) -> Self {
+        Self(value)
+    }
+}
+impl From<SecurityState> for u32 {
+    fn from(value: SecurityState) -> Self {
+        value.0
+    }
+}
+
+impl SecurityState {
+    pub fn debug_locked(self) -> bool {
+        (self.0 & (1 << 2)) != 0
+    }
+    pub fn set_debug_locked(&mut self, val: bool) -> &mut Self {
+        let mask = 1 << 2;
+        if val {
+            self.0 |= mask;
+        } else {
+            self.0 &= !mask
+        };
+        self
+    }
+    pub fn device_lifecycle(self) -> DeviceLifecycle {
+        DeviceLifecycle::try_from(self.0 & 0x3).unwrap()
+    }
+    pub fn set_device_lifecycle(&mut self, val: DeviceLifecycle) -> &mut Self {
+        self.0 |= (val as u32) & 0x3;
+        self
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
+pub enum U4 {
+    #[default]
+    X0 = 0x0,
+    X1 = 0x1,
+    X2 = 0x2,
+    X3 = 0x3,
+    X4 = 0x4,
+    X5 = 0x5,
+    X6 = 0x6,
+    X7 = 0x7,
+    X8 = 0x8,
+    X9 = 0x9,
+    Xa = 0xa,
+    Xb = 0xb,
+    Xc = 0xc,
+    Xd = 0xd,
+    Xe = 0xe,
+    Xf = 0xf,
+}
+impl U4 {
+    pub const B0000: Self = Self::X0;
+    pub const B0001: Self = Self::X1;
+    pub const B0010: Self = Self::X2;
+    pub const B0011: Self = Self::X3;
+    pub const B0100: Self = Self::X4;
+    pub const B0101: Self = Self::X5;
+    pub const B0110: Self = Self::X6;
+    pub const B0111: Self = Self::X7;
+    pub const B1000: Self = Self::X8;
+    pub const B1001: Self = Self::X9;
+    pub const B1010: Self = Self::Xa;
+    pub const B1011: Self = Self::Xb;
+    pub const B1100: Self = Self::Xc;
+    pub const B1101: Self = Self::Xd;
+    pub const B1110: Self = Self::Xe;
+    pub const B1111: Self = Self::Xf;
+}
+impl From<U4> for u32 {
+    fn from(value: U4) -> Self {
+        value as u32
+    }
+}
+
+#[derive(Default)]
+pub struct Fuses {
+    pub uds_seed: [u32; 12],
+    pub field_entropy: [u32; 8],
+    pub key_manifest_pk_hash: [u32; 12],
+    pub key_manifest_pk_hash_mask: U4,
+    pub owner_pk_hash: [u32; 12],
+    pub fmc_key_manifest_svn: u32,
+    pub runtime_svn: [u32; 4],
+    pub anti_rollback_disable: bool,
+    pub idevid_cert_attr: [u32; 24],
+    pub idevid_manuf_hsm_id: [u32; 4],
+    pub life_cycle: DeviceLifecycle,
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test() {
+        let mut ss = *SecurityState::default()
+            .set_debug_locked(true)
+            .set_device_lifecycle(DeviceLifecycle::Manufacturing);
+        assert_eq!(0x5u32, ss.into());
+        assert!(ss.debug_locked());
+        assert_eq!(ss.device_lifecycle(), DeviceLifecycle::Manufacturing);
+        ss.set_debug_locked(false);
+        assert_eq!(0x1u32, ss.into());
+    }
+}
diff --git a/sw-emulator/app/Cargo.toml b/sw-emulator/app/Cargo.toml
index 2b043c0e..1b540790 100644
--- a/sw-emulator/app/Cargo.toml
+++ b/sw-emulator/app/Cargo.toml
@@ -9,10 +9,14 @@ edition = "2021"
 
 [dependencies]
 clap = { version = "3.2.14", default-features = false, features = ["std"] }
+caliptra-registers = { path = "../../registers" }
 caliptra-emu-bus = { path = "../lib/bus" }
 caliptra-emu-cpu = { path = "../lib/cpu" }
 caliptra-emu-periph = { path = "../lib/periph" }
 caliptra-emu-types = { path = "../lib/types" }
+caliptra-hw-model = { path = "../../hw-model" }
+caliptra-hw-model-types = { path = "../../hw-model/types" }
 gdbstub = "0.6.3"
 gdbstub_arch = "0.2.4"
 hex = "0.4.3"
+tock-registers = { git = "https://github.com/tock/tock.git"}
\ No newline at end of file
diff --git a/sw-emulator/app/src/main.rs b/sw-emulator/app/src/main.rs
index f68d279f..0d99694c 100644
--- a/sw-emulator/app/src/main.rs
+++ b/sw-emulator/app/src/main.rs
@@ -17,6 +17,8 @@ use caliptra_emu_cpu::{Cpu, RvInstr, StepAction};
 use caliptra_emu_periph::{
     CaliptraRootBus, CaliptraRootBusArgs, Mailbox, ReadyForFwCb, TbServicesCb, UploadUpdateFwCb,
 };
+use caliptra_hw_model::BusMmio;
+use caliptra_hw_model_types::{DeviceLifecycle, SecurityState};
 use clap::{arg, value_parser, ArgAction};
 use std::fs::File;
 use std::io;
@@ -24,10 +26,14 @@ use std::io::{Read, Write};
 use std::path::{Path, PathBuf};
 use std::process::exit;
 use std::rc::Rc;
+use tock_registers::interfaces::{Readable, Writeable};
+use tock_registers::registers::InMemoryRegister;
 mod gdb;
 use crate::gdb::gdb_target::GdbTarget;
 use gdb::gdb_state;
 
+use tock_registers::register_bitfields;
+
 /// Firmware Load Command Opcode
 const FW_LOAD_CMD_OPCODE: u32 = 0x4657_4C44;
 
@@ -57,6 +63,14 @@ fn free_run(mut cpu: Cpu<CaliptraRootBus>, trace_path: Option<PathBuf>) {
     };
 }
 
+fn words_from_bytes_le(arr: &[u8; 48]) -> [u32; 12] {
+    let mut result = [0u32; 12];
+    for i in 0..result.len() {
+        result[i] = u32::from_le_bytes(arr[i * 4..][..4].try_into().unwrap())
+    }
+    result
+}
+
 fn main() -> io::Result<()> {
     let args = clap::Command::new("caliptra-emu")
         .about("Caliptra emulator")
@@ -205,13 +219,26 @@ fn main() -> io::Result<()> {
     let update_fw_buf = Rc::new(update_fw_buf);
 
     let clock = Clock::new();
+
+    let req_idevid_csr = args.get_flag("req-idevid-csr");
+    let req_ldevid_cert = args.get_flag("req-ldevid-cert");
+
+    let mut security_state = SecurityState::default();
+    security_state.set_device_lifecycle(
+        match args_device_lifecycle.to_ascii_lowercase().as_str() {
+            "manufacturing" => DeviceLifecycle::Manufacturing,
+            "production" => DeviceLifecycle::Production,
+            "unprovisioned" | "" => DeviceLifecycle::Unprovisioned,
+            other => {
+                println!("Unknown device lifecycle {:?}", other);
+                exit(-1);
+            }
+        },
+    );
+
     let bus_args = CaliptraRootBusArgs {
         rom: rom_buffer,
         log_dir: args_log_dir.clone(),
-        ueid: *args_ueid,
-        idev_key_id_algo: args_idevid_key_id_algo.clone(),
-        req_idevid_csr: args.get_flag("req-idevid-csr"),
-        req_ldevid_cert: args.get_flag("req-ldevid-cert"),
         tb_services_cb: TbServicesCb::new(move |val| match val {
             0x01 => exit(0xFF),
             0xFF => exit(0x00),
@@ -226,15 +253,93 @@ fn main() -> io::Result<()> {
                 upload_fw_to_mailbox(mailbox, firmware_buffer);
             });
         }),
-        mfg_pk_hash,
-        owner_pk_hash,
-        device_lifecycle: args_device_lifecycle.clone(),
+        security_state,
         upload_update_fw: UploadUpdateFwCb::new(move |mailbox: &mut Mailbox| {
             while !mailbox.try_acquire_lock() {}
             upload_fw_to_mailbox(mailbox, update_fw_buf.clone());
         }),
+        ..Default::default()
     };
-    let cpu = Cpu::new(CaliptraRootBus::new(&clock, bus_args), clock);
+
+    let root_bus = CaliptraRootBus::new(&clock, bus_args);
+    let soc_ifc = unsafe {
+        caliptra_registers::soc_ifc::RegisterBlock::new_with_mmio(
+            0x3003_0000 as *mut u32,
+            BusMmio::new(root_bus.soc_to_caliptra_bus()),
+        )
+    };
+
+    if !mfg_pk_hash.is_empty() {
+        let mfg_pk_hash = words_from_bytes_le(
+            &mfg_pk_hash
+                .try_into()
+                .expect("mfg_pk_hash must be 48 bytes"),
+        );
+        soc_ifc.fuse_key_manifest_pk_hash().write(&mfg_pk_hash);
+    }
+
+    if !owner_pk_hash.is_empty() {
+        let owner_pk_hash = words_from_bytes_le(
+            &owner_pk_hash
+                .try_into()
+                .expect("owner_pk_hash must be 48 bytes"),
+        );
+        soc_ifc.fuse_owner_pk_hash().write(&owner_pk_hash);
+    }
+
+    // Populate DBG_MANUF_SERVICE_REG
+    {
+        const GEN_IDEVID_CSR_FLAG: u32 = 1 << 0;
+        const GEN_LDEVID_CSR_FLAG: u32 = 1 << 1;
+
+        let mut val = 0;
+        if req_idevid_csr {
+            val |= GEN_IDEVID_CSR_FLAG;
+        }
+        if req_ldevid_cert {
+            val |= GEN_LDEVID_CSR_FLAG;
+        }
+        soc_ifc.cptra_dbg_manuf_service_reg().write(|_| val);
+    }
+
+    // Populate fuse_idevid_cert_attr
+    {
+        register_bitfields! [
+            u32,
+            IDevIdCertAttrFlags [
+                KEY_ID_ALGO OFFSET(0) NUMBITS(2) [
+                    SHA1 = 0b00,
+                    SHA256 = 0b01,
+                    SHA384 = 0b10,
+                    FUSE = 0b11,
+                ],
+                RESERVED OFFSET(2) NUMBITS(30) [],
+            ],
+        ];
+
+        // Determine the Algorithm used for IDEVID Certificate Subject Key Identifier
+        let algo = match args_idevid_key_id_algo.to_ascii_lowercase().as_str() {
+            "" | "sha1" => IDevIdCertAttrFlags::KEY_ID_ALGO::SHA1,
+            "sha256" => IDevIdCertAttrFlags::KEY_ID_ALGO::SHA256,
+            "sha384" => IDevIdCertAttrFlags::KEY_ID_ALGO::SHA384,
+            "fuse" => IDevIdCertAttrFlags::KEY_ID_ALGO::FUSE,
+            _ => panic!("Unknown idev_key_id_algo {:?}", args_idevid_key_id_algo),
+        };
+
+        let flags: InMemoryRegister<u32, IDevIdCertAttrFlags::Register> = InMemoryRegister::new(0);
+        flags.write(algo);
+        let mut cert = [0u32; 24];
+        // DWORD 00 - Flags
+        cert[0] = flags.get();
+        // DWORD 01 - 05 - IDEVID Subject Key Identifier (all zeroes)
+        // DWORD 06 - 07 - UEID / Manufacturer Serial Number
+        cert[6] = *args_ueid as u32;
+        cert[7] = (*args_ueid >> 32) as u32;
+
+        soc_ifc.fuse_idevid_cert_attr().write(&cert);
+    }
+
+    let cpu = Cpu::new(root_bus, clock);
 
     // Check if Optional GDB Port is passed
     match args.get_one::<String>("gdb-port") {
diff --git a/sw-emulator/lib/periph/Cargo.toml b/sw-emulator/lib/periph/Cargo.toml
index e955a0d8..669c95b9 100644
--- a/sw-emulator/lib/periph/Cargo.toml
+++ b/sw-emulator/lib/periph/Cargo.toml
@@ -13,6 +13,7 @@ caliptra-emu-cpu = { path = "../cpu"}
 caliptra-emu-crypto = { path = "../crypto"}
 caliptra-emu-derive = { path = "../derive" }
 caliptra-emu-types = { path = "../types" }
+caliptra-hw-model-types = { path = "../../../hw-model/types" }
 tock-registers = { git = "https://github.com/tock/tock.git"}
 smlang="0.6.0"
 lazy_static = "1.4.0"
diff --git a/sw-emulator/lib/periph/src/asym_ecc384.rs b/sw-emulator/lib/periph/src/asym_ecc384.rs
index cbced5a7..861ca37f 100644
--- a/sw-emulator/lib/periph/src/asym_ecc384.rs
+++ b/sw-emulator/lib/periph/src/asym_ecc384.rs
@@ -531,7 +531,9 @@ impl AsymEcc384 {
         };
 
         if let Some(key) = key {
-            self.priv_key = words_from_bytes_le(key[..ECC384_COORD_SIZE].try_into().unwrap());
+            self.priv_key = words_from_bytes_le(
+                &<[u8; ECC384_COORD_SIZE]>::try_from(&key[..ECC384_COORD_SIZE]).unwrap(),
+            );
         }
 
         self.key_read_status.reg.modify(
@@ -562,7 +564,9 @@ impl AsymEcc384 {
         };
 
         if let Some(seed) = seed {
-            self.seed = words_from_bytes_le(seed[..ECC384_SEED_SIZE].try_into().unwrap());
+            self.seed = words_from_bytes_le(
+                &<[u8; ECC384_SEED_SIZE]>::try_from(&seed[..ECC384_SEED_SIZE]).unwrap(),
+            );
         }
 
         self.seed_read_status.reg.modify(
@@ -593,7 +597,9 @@ impl AsymEcc384 {
         };
 
         if let Some(msg) = msg {
-            self.hash = words_from_bytes_le(msg[..ECC384_SEED_SIZE].try_into().unwrap());
+            self.hash = words_from_bytes_le(
+                &<[u8; ECC384_SEED_SIZE]>::try_from(&msg[..ECC384_SEED_SIZE]).unwrap(),
+            );
         }
 
         self.msg_read_status.reg.modify(
diff --git a/sw-emulator/lib/periph/src/doe.rs b/sw-emulator/lib/periph/src/doe.rs
index 7e67a447..72a37f4d 100644
--- a/sw-emulator/lib/periph/src/doe.rs
+++ b/sw-emulator/lib/periph/src/doe.rs
@@ -12,7 +12,7 @@ Abstract:
 
 --*/
 
-use crate::{KeyVault, SocRegisters};
+use crate::{KeyVault, SocRegistersInternal};
 use caliptra_emu_bus::{
     BusError, Clock, ReadOnlyRegister, ReadWriteMemory, ReadWriteRegister, Timer, TimerAction,
 };
@@ -79,7 +79,7 @@ pub struct Doe {
     key_vault: KeyVault,
 
     /// SOC Registers
-    soc_reg: SocRegisters,
+    soc_reg: SocRegistersInternal,
 
     /// Operation Complete Action
     op_complete_action: Option<TimerAction>,
@@ -97,7 +97,7 @@ impl Doe {
     /// # Returns
     ///
     /// * `Self` - Instance of deobfuscation engine
-    pub fn new(clock: &Clock, key_vault: KeyVault, soc_reg: SocRegisters) -> Self {
+    pub fn new(clock: &Clock, key_vault: KeyVault, soc_reg: SocRegistersInternal) -> Self {
         Self {
             iv: ReadWriteMemory::new(),
             control: ReadWriteRegister::new(0),
@@ -246,7 +246,7 @@ mod tests {
 
         let clock = Clock::new();
         let key_vault = KeyVault::new();
-        let soc_reg = SocRegisters::new(
+        let soc_reg = SocRegistersInternal::new(
             &clock,
             Mailbox::new(MailboxRam::new()),
             Iccm::new(),
@@ -305,7 +305,7 @@ mod tests {
 
         let clock = Clock::new();
         let key_vault = KeyVault::new();
-        let soc_reg = SocRegisters::new(
+        let soc_reg = SocRegistersInternal::new(
             &clock,
             Mailbox::new(MailboxRam::new()),
             Iccm::new(),
@@ -362,7 +362,7 @@ mod tests {
         let expected_fe = [0u8; 32];
         let clock = Clock::new();
         let key_vault = KeyVault::new();
-        let soc_reg = SocRegisters::new(
+        let soc_reg = SocRegistersInternal::new(
             &clock,
             Mailbox::new(MailboxRam::new()),
             Iccm::new(),
diff --git a/sw-emulator/lib/periph/src/hash_sha512.rs b/sw-emulator/lib/periph/src/hash_sha512.rs
index d6bc6215..2fac71f7 100644
--- a/sw-emulator/lib/periph/src/hash_sha512.rs
+++ b/sw-emulator/lib/periph/src/hash_sha512.rs
@@ -481,7 +481,7 @@ impl HashSha512 {
             if pcr_hash_extend != 0 {
                 // Copy the PCR (48 bytes) to the block registers.
                 self.block[..KeyVault::KEY_SIZE / 4].copy_from_slice(&words_from_bytes_le(
-                    &data[..KeyVault::KEY_SIZE].try_into().unwrap(),
+                    &<[u8; KeyVault::KEY_SIZE]>::try_from(&data[..KeyVault::KEY_SIZE]).unwrap(),
                 ));
             } else {
                 self.format_block(&data);
diff --git a/sw-emulator/lib/periph/src/helpers.rs b/sw-emulator/lib/periph/src/helpers.rs
index 729d33b6..7095bb5e 100644
--- a/sw-emulator/lib/periph/src/helpers.rs
+++ b/sw-emulator/lib/periph/src/helpers.rs
@@ -13,18 +13,122 @@ Abstract:
 --*/
 #![allow(unused)]
 
-pub fn words_from_bytes_le(arr: &[u8; 48]) -> [u32; 12] {
-    let mut result = [0u32; 12];
-    for i in 0..result.len() {
-        result[i] = u32::from_le_bytes(arr[i * 4..][..4].try_into().unwrap())
+use std::ops::{Index, IndexMut, RangeFrom};
+
+pub trait U32Array: AsRef<[u32]> {
+    const BYTE_LEN: usize;
+    type ByteArray: AsMut<[u8]>;
+    // Required because Default isn't implemented for [u8; 64]
+    fn default_result() -> Self::ByteArray;
+}
+pub trait U32ArrayBytes: AsRef<[u8]> {
+    const WORD_LEN: usize;
+    type WordArray: AsMut<[u32]> + Default;
+}
+macro_rules! u32_array_impl {
+    ($word_len:literal) => {
+        impl U32Array for [u32; $word_len] {
+            const BYTE_LEN: usize = $word_len * 4;
+            type ByteArray = [u8; $word_len * 4];
+            fn default_result() -> Self::ByteArray {
+                [0u8; $word_len * 4]
+            }
+        }
+        impl U32ArrayBytes for [u8; $word_len * 4] {
+            const WORD_LEN: usize = $word_len;
+            type WordArray = [u32; $word_len];
+        }
+    };
+}
+u32_array_impl!(0);
+u32_array_impl!(1);
+u32_array_impl!(2);
+u32_array_impl!(3);
+u32_array_impl!(4);
+u32_array_impl!(5);
+u32_array_impl!(6);
+u32_array_impl!(7);
+u32_array_impl!(8);
+u32_array_impl!(9);
+u32_array_impl!(10);
+u32_array_impl!(11);
+u32_array_impl!(12);
+u32_array_impl!(13);
+u32_array_impl!(14);
+u32_array_impl!(15);
+u32_array_impl!(16);
+
+pub fn words_from_bytes_le<A: U32ArrayBytes>(arr: &A) -> A::WordArray {
+    let mut result = A::WordArray::default();
+    for i in 0..result.as_mut().len() {
+        result.as_mut()[i] = u32::from_le_bytes(arr.as_ref()[i * 4..][..4].try_into().unwrap())
     }
     result
 }
 
-pub fn bytes_from_words_le(arr: &[u32; 12]) -> [u8; 48] {
-    let mut result = [0u8; 48];
-    for i in 0..arr.len() {
-        result[i * 4..][..4].copy_from_slice(&arr[i].to_le_bytes());
+pub fn bytes_from_words_le<A: U32Array>(arr: &A) -> A::ByteArray {
+    let mut result = A::default_result();
+    for i in 0..arr.as_ref().len() {
+        result.as_mut()[i * 4..][..4].copy_from_slice(&arr.as_ref()[i].to_le_bytes());
     }
     result
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    pub fn test_words_from_bytes_le() {
+        assert_eq!(
+            [
+                0xeb457996, 0x70cdc537, 0x95684f6f, 0xde320a3e, 0xbe5a8d48, 0xd25c3fe1, 0x47051dda,
+                0x2fcb1d61, 0x5abaabff, 0xd041c30e, 0x1a18b955, 0x117495d4,
+            ],
+            words_from_bytes_le(&[
+                0x96, 0x79, 0x45, 0xeb, 0x37, 0xc5, 0xcd, 0x70, 0x6f, 0x4f, 0x68, 0x95, 0x3e, 0x0a,
+                0x32, 0xde, 0x48, 0x8d, 0x5a, 0xbe, 0xe1, 0x3f, 0x5c, 0xd2, 0xda, 0x1d, 0x05, 0x47,
+                0x61, 0x1d, 0xcb, 0x2f, 0xff, 0xab, 0xba, 0x5a, 0x0e, 0xc3, 0x41, 0xd0, 0x55, 0xb9,
+                0x18, 0x1a, 0xd4, 0x95, 0x74, 0x11
+            ])
+        );
+        assert_eq!(
+            [
+                0x6d533aa5, 0xb8066955, 0xb1d55284, 0xe44aade8, 0x279ad4d2, 0xde9de661, 0x13fba077,
+                0x76a6efd,
+            ],
+            words_from_bytes_le(&[
+                0xa5, 0x3a, 0x53, 0x6d, 0x55, 0x69, 0x06, 0xb8, 0x84, 0x52, 0xd5, 0xb1, 0xe8, 0xad,
+                0x4a, 0xe4, 0xd2, 0xd4, 0x9a, 0x27, 0x61, 0xe6, 0x9d, 0xde, 0x77, 0xa0, 0xfb, 0x13,
+                0xfd, 0x6e, 0x6a, 0x7
+            ])
+        );
+    }
+
+    #[test]
+    pub fn test_bytes_from_words_le() {
+        assert_eq!(
+            [
+                0x96, 0x79, 0x45, 0xeb, 0x37, 0xc5, 0xcd, 0x70, 0x6f, 0x4f, 0x68, 0x95, 0x3e, 0x0a,
+                0x32, 0xde, 0x48, 0x8d, 0x5a, 0xbe, 0xe1, 0x3f, 0x5c, 0xd2, 0xda, 0x1d, 0x05, 0x47,
+                0x61, 0x1d, 0xcb, 0x2f, 0xff, 0xab, 0xba, 0x5a, 0x0e, 0xc3, 0x41, 0xd0, 0x55, 0xb9,
+                0x18, 0x1a, 0xd4, 0x95, 0x74, 0x11
+            ],
+            bytes_from_words_le(&[
+                0xeb457996, 0x70cdc537, 0x95684f6f, 0xde320a3e, 0xbe5a8d48, 0xd25c3fe1, 0x47051dda,
+                0x2fcb1d61, 0x5abaabff, 0xd041c30e, 0x1a18b955, 0x117495d4,
+            ])
+        );
+        assert_eq!(
+            [
+                0xa5, 0x3a, 0x53, 0x6d, 0x55, 0x69, 0x06, 0xb8, 0x84, 0x52, 0xd5, 0xb1, 0xe8, 0xad,
+                0x4a, 0xe4, 0xd2, 0xd4, 0x9a, 0x27, 0x61, 0xe6, 0x9d, 0xde, 0x77, 0xa0, 0xfb, 0x13,
+                0xfd, 0x6e, 0x6a, 0x7
+            ],
+            bytes_from_words_le(&[
+                0x6d533aa5, 0xb8066955, 0xb1d55284, 0xe44aade8, 0x279ad4d2, 0xde9de661, 0x13fba077,
+                0x76a6efd,
+            ])
+        );
+    }
+}
diff --git a/sw-emulator/lib/periph/src/lib.rs b/sw-emulator/lib/periph/src/lib.rs
index 2f37d63a..36df0ea2 100644
--- a/sw-emulator/lib/periph/src/lib.rs
+++ b/sw-emulator/lib/periph/src/lib.rs
@@ -40,8 +40,9 @@ pub use key_vault::KeyUsage;
 pub use key_vault::KeyVault;
 pub use mailbox::{Mailbox, MailboxRam};
 pub use root_bus::{
-    CaliptraRootBus, CaliptraRootBusArgs, ReadyForFwCb, TbServicesCb, UploadUpdateFwCb,
+    ActionCb, CaliptraRootBus, CaliptraRootBusArgs, ReadyForFwCb, SocToCaliptraBus, TbServicesCb,
+    UploadUpdateFwCb,
 };
 pub use sha512_acc::Sha512Accelerator;
-pub use soc_reg::SocRegisters;
+pub use soc_reg::SocRegistersInternal;
 pub use uart::Uart;
diff --git a/sw-emulator/lib/periph/src/root_bus.rs b/sw-emulator/lib/periph/src/root_bus.rs
index c0a87351..f2982ce9 100644
--- a/sw-emulator/lib/periph/src/root_bus.rs
+++ b/sw-emulator/lib/periph/src/root_bus.rs
@@ -13,11 +13,12 @@ Abstract:
 --*/
 
 use crate::{
-    iccm::Iccm, AsymEcc384, Doe, EmuCtrl, HashSha256, HashSha512, HmacSha384, KeyVault, Mailbox,
-    MailboxRam, Sha512Accelerator, SocRegisters, Uart,
+    iccm::Iccm, soc_reg::SocRegistersExternal, AsymEcc384, Doe, EmuCtrl, HashSha256, HashSha512,
+    HmacSha384, KeyVault, Mailbox, MailboxRam, Sha512Accelerator, SocRegistersInternal, Uart,
 };
 use caliptra_emu_bus::{Clock, Ram, Rom};
 use caliptra_emu_derive::Bus;
+use caliptra_hw_model_types::SecurityState;
 use std::path::PathBuf;
 
 pub struct TbServicesCb(pub Box<dyn FnMut(u8)>);
@@ -114,23 +115,47 @@ impl From<Box<dyn FnMut(&mut Mailbox) + 'static>> for UploadUpdateFwCb {
     }
 }
 
+pub struct ActionCb(Box<dyn FnMut()>);
+impl ActionCb {
+    pub fn new(f: impl FnMut() + 'static) -> Self {
+        Self(Box::new(f))
+    }
+    pub(crate) fn take(&mut self) -> Box<dyn FnMut()> {
+        std::mem::take(self).0
+    }
+}
+impl Default for ActionCb {
+    fn default() -> Self {
+        Self(Box::new(|| {}))
+    }
+}
+impl std::fmt::Debug for ActionCb {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_tuple("ActionCb")
+            .field(&"<unknown closure>")
+            .finish()
+    }
+}
+impl From<Box<dyn FnMut() + 'static>> for ActionCb {
+    fn from(value: Box<dyn FnMut()>) -> Self {
+        Self(value)
+    }
+}
+
 /// Caliptra Root Bus Arguments
 #[derive(Default, Debug)]
 pub struct CaliptraRootBusArgs {
     pub rom: Vec<u8>,
     pub log_dir: PathBuf,
-    pub ueid: u64,
-    pub idev_key_id_algo: String,
-    pub req_idevid_csr: bool,
-    pub req_ldevid_cert: bool,
+    // The security state wires provided to caliptra_top
+    pub security_state: SecurityState,
+
     /// Callback to customize application behavior when
     /// a write to the tb-services register write is performed.
     pub tb_services_cb: TbServicesCb,
     pub ready_for_fw_cb: ReadyForFwCb,
-    pub mfg_pk_hash: Vec<u8>,
-    pub owner_pk_hash: Vec<u8>,
-    pub device_lifecycle: String,
     pub upload_update_fw: UploadUpdateFwCb,
+    pub bootfsm_go_cb: ActionCb,
 }
 
 #[derive(Bus)]
@@ -175,7 +200,7 @@ pub struct CaliptraRootBus {
     pub sha512_acc: Sha512Accelerator,
 
     #[peripheral(offset = 0x3003_0000, mask = 0x0000_ffff)]
-    pub soc_reg: SocRegisters,
+    pub soc_reg: SocRegistersInternal,
 
     #[peripheral(offset = 0x5000_0000, mask = 0x0fff_ffff)]
     pub dccm: Ram,
@@ -192,7 +217,7 @@ impl CaliptraRootBus {
         let mailbox = Mailbox::new(mailbox_ram.clone());
         let rom = Rom::new(std::mem::take(&mut args.rom));
         let iccm = Iccm::new();
-        let soc_reg = SocRegisters::new(clock, mailbox.clone(), iccm.clone(), args);
+        let soc_reg = SocRegistersInternal::new(clock, mailbox.clone(), iccm.clone(), args);
 
         Self {
             rom,
@@ -212,4 +237,22 @@ impl CaliptraRootBus {
             sha512_acc: Sha512Accelerator::new(clock, mailbox_ram),
         }
     }
+
+    pub fn soc_to_caliptra_bus(&self) -> SocToCaliptraBus {
+        SocToCaliptraBus {
+            // TODO: This should not be the same mailbox bus as the one used
+            // internaly
+            mailbox: self.mailbox.clone(),
+            soc_ifc: self.soc_reg.external_regs(),
+        }
+    }
+}
+
+#[derive(Bus)]
+pub struct SocToCaliptraBus {
+    #[peripheral(offset = 0x3002_0000, mask = 0x0000_0fff)]
+    mailbox: Mailbox,
+
+    #[peripheral(offset = 0x3003_0000, mask = 0x0000_ffff)]
+    soc_ifc: SocRegistersExternal,
 }
diff --git a/sw-emulator/lib/periph/src/soc_reg.rs b/sw-emulator/lib/periph/src/soc_reg.rs
index dbbdff5f..d9680d20 100644
--- a/sw-emulator/lib/periph/src/soc_reg.rs
+++ b/sw-emulator/lib/periph/src/soc_reg.rs
@@ -12,13 +12,15 @@ Abstract:
 
 --*/
 
+use crate::helpers::{bytes_from_words_le, words_from_bytes_le};
 use crate::root_bus::ReadyForFwCbArgs;
 use crate::{CaliptraRootBusArgs, Iccm, Mailbox};
 use caliptra_emu_bus::BusError::{LoadAccessFault, StoreAccessFault};
 use caliptra_emu_bus::{
-    Bus, BusError, Clock, ReadOnlyMemory, ReadOnlyRegister, ReadWriteMemory, ReadWriteRegister,
-    Register, Timer, TimerAction, TimerActionType,
+    Bus, BusError, Clock, ReadOnlyMemory, ReadOnlyRegister, ReadWriteRegister, Register, Timer,
+    TimerAction, TimerActionType,
 };
+use caliptra_emu_derive::Bus;
 use caliptra_emu_types::{RvAddr, RvData, RvSize};
 use std::cell::RefCell;
 use std::io::Write;
@@ -32,276 +34,64 @@ use tock_registers::registers::InMemoryRegister;
 // Mailbox)>), which is called to schedule firmware writing in the future
 type ReadyForFwCallback = Box<dyn FnMut(ReadyForFwCbArgs)>;
 type UploadUpdateFwCallback = Box<dyn FnMut(&mut Mailbox)>;
-
-/// CPTRA_HW_ERROR_FATAL Register Start Address
-const CPTRA_HW_ERROR_FATAL_START: u32 = 0x0;
-
-/// CPTRA_HW_ERROR_FATAL Register End Address
-const CPTRA_HW_ERROR_FATAL_END: u32 = 0x3;
-
-/// CPTRA_HW_ERROR_NON_FATAL Register Start Address
-const CPTRA_HW_ERROR_NON_FATAL_START: u32 = 0x4;
-
-/// CPTRA_HW_ERROR_NON_FATAL Register End Address
-const CPTRA_HW_ERROR_NON_FATAL_END: u32 = 0x7;
-
-/// CPTRA_FW_ERROR_FATAL Register Start Address
-const CPTRA_FW_ERROR_FATAL_START: u32 = 0x8;
-
-/// CPTRA_FW_ERROR_FATAL Register End Address
-const CPTRA_FW_ERROR_FATAL_END: u32 = 0xb;
-
-/// CPTRA_FW_ERROR_NON_FATAL Register Start Address
-const CPTRA_FW_ERROR_NON_FATAL_START: u32 = 0xc;
-
-/// CPTRA_FW_ERROR_NON_FATAL Register End Address
-const CPTRA_FW_ERROR_NON_FATAL_END: u32 = 0xf;
-
-/// CPTRA_HW_ERROR_ENC Register Start Address
-const CPTRA_HW_ERROR_ENC_START: u32 = 0x10;
-
-/// CPTRA_HW_ERROR_ENC Register End Address
-const CPTRA_HW_ERROR_ENC_END: u32 = 0x13;
-
-/// CPTRA_FW_ERROR_ENC Register Start Address
-const CPTRA_FW_ERROR_ENC_START: u32 = 0x14;
-
-/// CPTRA_FW_ERROR_ENC Register End Address
-const CPTRA_FW_ERROR_ENC_END: u32 = 0x17;
-
-/// CPTRA_FW_EXTENDED_ERROR_INFO Register Start Address
-const CPTRA_FW_EXTENDED_ERROR_INFO_START: u32 = 0x18;
-
-/// CPTRA_FW_EXTENDED_ERROR_INFO Register End Address
-const CPTRA_FW_EXTENDED_ERROR_INFO_END: u32 = 0x37;
-
-/// CPTRA_FW_EXTENDED_ERROR_INFO Register Size
-const CPTRA_FW_EXTENDED_ERROR_INFO_SIZE: usize = 32;
-
-/// CPTRA_BOOT_STATUS Register Start Address
-const CPTRA_BOOT_STATUS_START: u32 = 0x38;
-
-/// CPTRA_BOOT_STATUS Register End Address
-const CPTRA_BOOT_STATUS_END: u32 = 0x3b;
-
-/// CPTRA_FLOW_STATUS Register Start Address
-const CPTRA_FLOW_STATUS_START: u32 = 0x3c;
-
-/// CPTRA_FLOW_STATUS Register End Address
-const CPTRA_FLOW_STATUS_END: u32 = 0x3f;
-
-/// CPTRA_RESET_REASON Register Start Address
-const CPTRA_RESET_REASON_START: u32 = 0x40;
-
-/// CPTRA_RESET_REASON Register End Address
-const CPTRA_RESET_REASON_END: u32 = 0x43;
-
-/// CPTRA_SECURITY_STATE Register Start Address
-const CPTRA_SECURITY_STATE_START: u32 = 0x44;
-
-/// CPTRA_SECURITY_STATE Register End Address
-const CPTRA_SECURITY_STATE_END: u32 = 0x47;
-
-/// CPTRA_VALID_PAUSER Register Start Address
-const CPTRA_VALID_PAUSER_START: u32 = 0x48;
-
-/// CPTRA_VALID_PAUSER Register End Address
-const CPTRA_VALID_PAUSER_END: u32 = 0x5b;
-
-/// CPTRA_VALID_PAUSER Register Size
-const CPTRA_VALID_PAUSER_SIZE: usize = 20;
-
-/// CPTRA_PAUSER_LOCK Register Start Address
-const CPTRA_PAUSER_LOCK_START: u32 = 0x5c;
-
-/// CPTRA_PAUSER_LOCK Register End Address
-const CPTRA_PAUSER_LOCK_END: u32 = 0x6f;
-
-/// CPTRA_PAUSER_LOCK Register Size
-const CPTRA_PAUSER_LOCK_SIZE: usize = 20;
-
-/// CPTRA_TRNG_VALID_PAUSER Register Start Address
-const CPTRA_TRNG_VALID_PAUSER_START: u32 = 0x70;
-
-/// CPTRA_TRNG_VALID_PAUSER Register End Address
-const CPTRA_TRNG_VALID_PAUSER_END: u32 = 0x73;
-
-/// CPTRA_TRNG_PAUSER_LOCK Register Start Address
-const CPTRA_TRNG_PAUSER_LOCK_START: u32 = 0x74;
-
-/// CPTRA_TRNG_PAUSER_LOCK Register End Address
-const CPTRA_TRNG_PAUSER_LOCK_END: u32 = 0x77;
-
-/// CPTRA_TRNG_DATA Register Start Address
-const CPTRA_TRNG_DATA_START: u32 = 0x78;
-
-/// CPTRA_TRNG_DATA Register End Address
-const CPTRA_TRNG_DATA_END: u32 = 0xa7;
-
-/// CPTRA_TRNG_DATA Register Size
-const CPTRA_TRNG_DATA_SIZE: usize = 48;
-
-/// CPTRA_TRNG_STATUS Register Start Address
-const CPTRA_TRNG_STATUS_START: u32 = 0xa8;
-
-/// CPTRA_TRNG_STATUS Register End Address
-const CPTRA_TRNG_STATUS_END: u32 = 0xab;
-
-/// CPTRA_FUSE_WR_DONE Register Start Address
-const CPTRA_FUSE_WR_DONE_START: u32 = 0xac;
-
-/// CPTRA_FUSE_WR_DONE Register End Address
-const CPTRA_FUSE_WR_DONE_END: u32 = 0xaf;
-
-/// CPTRA_TIMER_CONFIG Register Start Address
-const CPTRA_TIMER_CONFIG_START: u32 = 0xb0;
-
-/// CPTRA_TIMER_CONFIG Register End Address
-const CPTRA_TIMER_CONFIG_END: u32 = 0xb3;
-
-/// CPTRA_BOOTFSM_GO Register Start Address
-const CPTRA_BOOTFSM_GO_START: u32 = 0xb4;
-
-/// CPTRA_BOOTFSM_GO Register End Address
-const CPTRA_BOOTFSM_GO_END: u32 = 0xb7;
-
-/// CPTRA_DBG_MANUF_SERVICE_REG Register Start Address
-const CPTRA_DBG_MANUF_SERVICE_REG_START: u32 = 0xb8;
-
-/// CPTRA_DBG_MANUF_SERVICE_REG Register End Address
-const CPTRA_DBG_MANUF_SERVICE_REG_END: u32 = 0xbb;
-
-/// CPTRA_CLK_GATING_EN Register Start Address
-const CPTRA_CLK_GATING_EN_START: u32 = 0xbc;
-
-/// CPTRA_CLK_GATING_EN Register End Address
-const CPTRA_CLK_GATING_EN_END: u32 = 0xbf;
-
-/// CPTRA_GENERIC_INPUT_WIRES Register Start Address
-const CPTRA_GENERIC_INPUT_WIRES_START: u32 = 0xc0;
-
-/// CPTRA_GENERIC_INPUT_WIRES Register End Address
-const CPTRA_GENERIC_INPUT_WIRES_END: u32 = 0xc7;
-
-/// CPTRA_GENERIC_INPUT_WIRES Register Size
-const CPTRA_GENERIC_INPUT_WIRES_SIZE: usize = 8;
-
-/// CPTRA_GENERIC_OUTPUT_WIRES Register Start Address
-const CPTRA_GENERIC_OUTPUT_WIRES_START: u32 = 0xc8;
-
-/// CPTRA_GENERIC_OUTPUT_WIRES Register 0 Address
-const CPTRA_GENERIC_OUTPUT_WIRES_0: u32 = CPTRA_GENERIC_OUTPUT_WIRES_START;
-
-/// CPTRA_GENERIC_OUTPUT_WIRES Register 1 Address
-const CPTRA_GENERIC_OUTPUT_WIRES_1: u32 = 0xcc;
-
-/// CPTRA_GENERIC_OUTPUT_WIRES Register End Address
-const CPTRA_GENERIC_OUTPUT_WIRES_END: u32 = 0xcf;
-
-/// CPTRA_GENERIC_OUTPUT_WIRES Register Size
-const CPTRA_GENERIC_OUTPUT_WIRES_SIZE: usize = 8;
-
-/// FUSE_UDS_SEED Register Size
-const FUSE_UDS_SEED_SIZE: usize = 48;
-
-/// FUSE_FIELD_ENTROPY Register Size
-const FUSE_FIELD_ENTROPY_SIZE: usize = 32;
-
-/// FUSE_VENDOR_PK_HASH Register Start Address
-const FUSE_VENDOR_PK_HASH_START: u32 = 0x250;
-
-/// FUSE_VENDOR_PK_HASH Register End Address
-const FUSE_VENDOR_PK_HASH_END: u32 = 0x27f;
-
-/// FUSE_VENDOR_PK_HASH Register Size
-const FUSE_VENDOR_PK_HASH_SIZE: usize = 48;
-
-/// FUSE_VENDOR_PK_MASK Register Start Address
-const FUSE_VENDOR_PK_MASK_START: u32 = 0x280;
-
-/// FUSE_VENDOR_PK_MASK Register End Address
-const FUSE_VENDOR_PK_MASK_END: u32 = 0x283;
-
-/// FUSE_OWNER_PK_HASH Register Start Address
-const FUSE_OWNER_PK_HASH_START: u32 = 0x284;
-
-/// FUSE_OWNER_PK_HASH Register End Address
-const FUSE_OWNER_PK_HASH_END: u32 = 0x2b3;
-
-/// FUSE_OWNER_PK_HASH Register Size
-const FUSE_OWNER_PK_HASH_SIZE: usize = 48;
-
-/// FUSE_FMC_SVN Register Start Address
-const FUSE_FMC_SVN_START: u32 = 0x2b4;
-
-/// FUSE_FMC_SVN Register End Address
-const FUSE_FMC_SVN_END: u32 = 0x2b7;
-
-/// FUSE_RUNTIME_SVN Register Start Address
-const FUSE_RUNTIME_SVN_START: u32 = 0x2b8;
-
-/// FUSE_RUNTIME_SVN Register End Address
-const FUSE_RUNTIME_SVN_END: u32 = 0x2c7;
-
-/// FUSE_RUNTIME_SVN Register Size
-const FUSE_RUNTIME_SVN_SIZE: usize = 16;
-
-/// FUSE_ANTI_ROLLBACK_DISABLE Register Start Address
-const FUSE_ANTI_ROLLBACK_DISABLE_START: u32 = 0x2c8;
-
-/// FUSE_ANTI_ROLLBACK_DISABLE Register End Address
-const FUSE_ANTI_ROLLBACK_DISABLE_END: u32 = 0x2cb;
-
-/// FUSE_IDEVID_CERT_ATTR Register Start Address
-const FUSE_IDEVID_CERT_ATTR_START: u32 = 0x2cc;
-
-/// FUSE_IDEVID_CERT_ATTR Register End Address
-const FUSE_IDEVID_CERT_ATTR_END: u32 = 0x32b;
-
-/// FUSE_IDEVID_CERT_ATTR Register Size
-const FUSE_IDEVID_CERT_ATTR_SIZE: usize = 96;
-
-/// FUSE_IDEVID_MANUF_HSM_ID Register Start Address
-const FUSE_IDEVID_MANUF_HSM_ID_START: u32 = 0x32c;
-
-/// FUSE_IDEVID_MANUF_HSM_ID Register End Address
-const FUSE_IDEVID_MANUF_HSM_ID_END: u32 = 0x33b;
-
-/// FUSE_IDEVID_MANUF_HSM_ID Register Size
-const FUSE_IDEVID_MANUF_HSM_ID_SIZE: usize = 16;
-
-/// FUSE_LIFE_CYCLE Register Start Address
-const FUSE_LIFE_CYCLE_START: u32 = 0x33c;
-
-/// FUSE_LIFE_CYCLE Register End Address
-const FUSE_LIFE_CYCLE_END: u32 = 0x33f;
-
-/// INTERNAL_OBF_KEY Register Size
-const INTERNAL_OBF_KEY_SIZE: usize = 32;
-
-/// INTERNAL_ICCM_LOCK Register Start Address
-const INTERNAL_ICCM_LOCK_START: u32 = 0x620;
-
-/// INTERNAL_ICCM_LOCK Register End Address
-const INTERNAL_ICCM_LOCK_END: u32 = 0x623;
-
-/// INTERNAL_FW_UPDATE_RESET Register Start Address
-const INTERNAL_FW_UPDATE_RESET_START: u32 = 0x624;
-
-/// INTERNAL_FW_UPDATE_RESET Register End Address
-const INTERNAL_FW_UPDATE_RESET_END: u32 = 0x627;
-
-/// INTERNAL_FW_UPDATE_RESET_WAIT_CYCLES Register Start Address
-const INTERNAL_FW_UPDATE_RESET_WAIT_CYCLES_START: u32 = 0x628;
-
-/// INTERNAL_FW_UPDATE_RESET_WAIT_CYCLES Register End Address
-const INTERNAL_FW_UPDATE_RESET_WAIT_CYCLES_END: u32 = 0x62b;
-
-/// INTERNAL_NMI_VECTOR Register Start Address
-const INTERNAL_NMI_VECTOR_START: u32 = 0x62c;
-
-/// INTERNAL_NMI_VECTOR Register End Address
-const INTERNAL_NMI_VECTOR_END: u32 = 0x62f;
+type BootFsmGoCallback = Box<dyn FnMut()>;
+
+mod constants {
+    #![allow(unused)]
+
+    pub const CPTRA_HW_ERROR_FATAL_START: u32 = 0x0;
+    pub const CPTRA_HW_ERROR_NON_FATAL_START: u32 = 0x4;
+    pub const CPTRA_FW_ERROR_FATAL_START: u32 = 0x8;
+    pub const CPTRA_FW_ERROR_NON_FATAL_START: u32 = 0xc;
+    pub const CPTRA_HW_ERROR_ENC_START: u32 = 0x10;
+    pub const CPTRA_FW_ERROR_ENC_START: u32 = 0x14;
+    pub const CPTRA_FW_EXTENDED_ERROR_INFO_START: u32 = 0x18;
+    pub const CPTRA_FW_EXTENDED_ERROR_INFO_SIZE: usize = 32;
+    pub const CPTRA_BOOT_STATUS_START: u32 = 0x38;
+    pub const CPTRA_FLOW_STATUS_START: u32 = 0x3c;
+    pub const CPTRA_RESET_REASON_START: u32 = 0x40;
+    pub const CPTRA_SECURITY_STATE_START: u32 = 0x44;
+    pub const CPTRA_VALID_PAUSER_START: u32 = 0x48;
+    pub const CPTRA_VALID_PAUSER_SIZE: usize = 20;
+    pub const CPTRA_PAUSER_LOCK_START: u32 = 0x5c;
+    pub const CPTRA_PAUSER_LOCK_SIZE: usize = 20;
+    pub const CPTRA_TRNG_VALID_PAUSER_START: u32 = 0x70;
+    pub const CPTRA_TRNG_PAUSER_LOCK_START: u32 = 0x74;
+    pub const CPTRA_TRNG_DATA_START: u32 = 0x78;
+    pub const CPTRA_TRNG_DATA_SIZE: usize = 48;
+    pub const CPTRA_TRNG_STATUS_START: u32 = 0xa8;
+    pub const CPTRA_FUSE_WR_DONE_START: u32 = 0xac;
+    pub const CPTRA_TIMER_CONFIG_START: u32 = 0xb0;
+    pub const CPTRA_BOOTFSM_GO_START: u32 = 0xb4;
+    pub const CPTRA_DBG_MANUF_SERVICE_REG_START: u32 = 0xb8;
+    pub const CPTRA_CLK_GATING_EN_START: u32 = 0xbc;
+    pub const CPTRA_GENERIC_INPUT_WIRES_START: u32 = 0xc0;
+    pub const CPTRA_GENERIC_INPUT_WIRES_SIZE: usize = 8;
+    pub const CPTRA_GENERIC_OUTPUT_WIRES_START: u32 = 0xc8;
+    pub const CPTRA_GENERIC_OUTPUT_WIRES_SIZE: usize = 8;
+    pub const FUSE_UDS_SEED_SIZE: usize = 48;
+    pub const FUSE_FIELD_ENTROPY_SIZE: usize = 32;
+    pub const FUSE_VENDOR_PK_HASH_START: u32 = 0x250;
+    pub const FUSE_VENDOR_PK_HASH_SIZE: usize = 48;
+    pub const FUSE_VENDOR_PK_MASK_START: u32 = 0x280;
+    pub const FUSE_OWNER_PK_HASH_START: u32 = 0x284;
+    pub const FUSE_OWNER_PK_HASH_SIZE: usize = 48;
+    pub const FUSE_FMC_SVN_START: u32 = 0x2b4;
+    pub const FUSE_RUNTIME_SVN_START: u32 = 0x2b8;
+    pub const FUSE_RUNTIME_SVN_SIZE: usize = 16;
+    pub const FUSE_ANTI_ROLLBACK_DISABLE_START: u32 = 0x2c8;
+    pub const FUSE_IDEVID_CERT_ATTR_START: u32 = 0x2cc;
+    pub const FUSE_IDEVID_CERT_ATTR_SIZE: usize = 96;
+    pub const FUSE_IDEVID_MANUF_HSM_ID_START: u32 = 0x32c;
+    pub const FUSE_IDEVID_MANUF_HSM_ID_SIZE: usize = 16;
+    pub const FUSE_LIFE_CYCLE_START: u32 = 0x33c;
+    pub const INTERNAL_OBF_KEY_SIZE: usize = 32;
+    pub const INTERNAL_ICCM_LOCK_START: u32 = 0x620;
+    pub const INTERNAL_FW_UPDATE_RESET_START: u32 = 0x624;
+    pub const INTERNAL_FW_UPDATE_RESET_WAIT_CYCLES_START: u32 = 0x628;
+    pub const INTERNAL_NMI_VECTOR_START: u32 = 0x62c;
+}
+use constants::*;
 
 register_bitfields! [
     u32,
@@ -376,17 +166,22 @@ register_bitfields! [
 
 /// SOC Register peripheral
 #[derive(Clone)]
-pub struct SocRegisters {
+pub struct SocRegistersInternal {
     regs: Rc<RefCell<SocRegistersImpl>>,
 }
 
-impl SocRegisters {
-    /// Caliptra Register Start Address
-    const CALIPTRA_REG_START_ADDR: u32 = 0x00;
+/// Caliptra Register Start Address
+const CALIPTRA_REG_START_ADDR: u32 = 0x00;
 
-    /// Caliptra Register End Address
-    const CALIPTRA_REG_END_ADDR: u32 = 0x62C;
+/// Caliptra Register End Address
+const CALIPTRA_REG_END_ADDR: u32 = 0x62C;
 
+/// Caliptra Fuse start address
+const FUSE_START_ADDR: u32 = 0x200;
+/// Caliptra Fuse end address
+const FUSE_END_ADDR: u32 = 0x340;
+
+impl SocRegistersInternal {
     /// Create an instance of SOC register peripheral
     pub fn new(clock: &Clock, mailbox: Mailbox, iccm: Iccm, args: CaliptraRootBusArgs) -> Self {
         Self {
@@ -398,12 +193,12 @@ impl SocRegisters {
 
     /// Get Unique device secret
     pub fn uds(&self) -> [u8; FUSE_UDS_SEED_SIZE] {
-        *self.regs.borrow().fuse_uds_seed.data()
+        bytes_from_words_le(&self.regs.borrow().fuse_uds_seed)
     }
 
     // Get field entropy
     pub fn field_entropy(&self) -> [u8; FUSE_FIELD_ENTROPY_SIZE] {
-        *self.regs.borrow().fuse_field_entropy.data()
+        bytes_from_words_le(&self.regs.borrow().fuse_field_entropy)
     }
 
     /// Get deobfuscation engine key
@@ -415,13 +210,19 @@ impl SocRegisters {
     pub fn clear_secrets(&mut self) {
         self.regs.borrow_mut().clear_secrets();
     }
+
+    pub fn external_regs(&self) -> SocRegistersExternal {
+        SocRegistersExternal {
+            regs: self.regs.clone(),
+        }
+    }
 }
 
-impl Bus for SocRegisters {
+impl Bus for SocRegistersInternal {
     /// Read data of specified size from given address
     fn read(&mut self, size: RvSize, addr: RvAddr) -> Result<RvData, BusError> {
         match addr {
-            Self::CALIPTRA_REG_START_ADDR..=Self::CALIPTRA_REG_END_ADDR => {
+            CALIPTRA_REG_START_ADDR..=CALIPTRA_REG_END_ADDR => {
                 self.regs.borrow_mut().read(size, addr)
             }
             _ => Err(LoadAccessFault),
@@ -431,7 +232,11 @@ impl Bus for SocRegisters {
     /// Write data of specified size to given address
     fn write(&mut self, size: RvSize, addr: RvAddr, val: RvData) -> Result<(), BusError> {
         match addr {
-            Self::CALIPTRA_REG_START_ADDR..=Self::CALIPTRA_REG_END_ADDR => {
+            FUSE_START_ADDR..=FUSE_END_ADDR => {
+                // Microcontroller can't ever write to fuse registers
+                Err(StoreAccessFault)
+            }
+            CALIPTRA_REG_START_ADDR..=CALIPTRA_REG_END_ADDR => {
                 self.regs.borrow_mut().write(size, addr, val)
             }
             _ => Err(StoreAccessFault),
@@ -443,135 +248,185 @@ impl Bus for SocRegisters {
     }
 
     fn warm_reset(&mut self) {
-        self.regs.borrow_mut().warm_reset();
+        self.regs.borrow_mut().bus_warm_reset();
+    }
+
+    fn update_reset(&mut self) {
+        self.regs.borrow_mut().bus_update_reset();
+    }
+}
+
+pub struct SocRegistersExternal {
+    regs: Rc<RefCell<SocRegistersImpl>>,
+}
+impl Bus for SocRegistersExternal {
+    /// Read data of specified size from given address
+    fn read(&mut self, size: RvSize, addr: RvAddr) -> Result<RvData, BusError> {
+        match addr {
+            CALIPTRA_REG_START_ADDR..=CALIPTRA_REG_END_ADDR => {
+                self.regs.borrow_mut().read(size, addr)
+            }
+            _ => Err(LoadAccessFault),
+        }
+    }
+
+    /// Write data of specified size to given address
+    fn write(&mut self, size: RvSize, addr: RvAddr, val: RvData) -> Result<(), BusError> {
+        match addr {
+            FUSE_START_ADDR..=FUSE_END_ADDR => {
+                if self.regs.borrow_mut().fuses_can_be_written {
+                    self.regs.borrow_mut().write(size, addr, val)
+                } else {
+                    Err(StoreAccessFault)
+                }
+            }
+            CALIPTRA_REG_START_ADDR..=CALIPTRA_REG_END_ADDR => {
+                self.regs.borrow_mut().write(size, addr, val)
+            }
+            _ => Err(StoreAccessFault),
+        }
+    }
+
+    fn poll(&mut self) {
+        // Do nothing; external interface can't control time
+    }
+
+    fn warm_reset(&mut self) {
+        // Do nothing; external interface can't control reset
     }
 
     fn update_reset(&mut self) {
-        self.regs.borrow_mut().update_reset();
+        // Do nothing; external interface can't control reset
     }
 }
 
 /// SOC Register implementation
 
+#[derive(Bus)]
+#[poll_fn(bus_poll)]
 struct SocRegistersImpl {
-    /// CPTRA_HW_ERROR_FATAL Register
+    #[register(offset = 0x0000)]
     cptra_hw_error_fatal: ReadWriteRegister<u32>,
 
-    /// CPTRA_HW_ERROR_NON_FATAL Register
+    #[register(offset = 0x0004)]
     cptra_hw_error_non_fatal: ReadWriteRegister<u32>,
 
-    /// CPTRA_FW_ERROR_FATAL Register
+    #[register(offset = 0x0008)]
     cptra_fw_error_fatal: ReadWriteRegister<u32>,
 
-    /// CPTRA_FW_ERROR_NON_FATAL Register
+    #[register(offset = 0x000c)]
     cptra_fw_error_non_fatal: ReadWriteRegister<u32>,
 
-    /// CPTRA_HW_ERROR_ENC Register
+    #[register(offset = 0x0010)]
     cptra_hw_error_enc: ReadWriteRegister<u32>,
 
-    /// CPTRA_FW_ERROR_ENC Register
+    #[register(offset = 0x0014)]
     cptra_fw_error_enc: ReadWriteRegister<u32>,
 
-    /// CPTRA_FW_EXTENDED_ERROR_INFO Register
-    cptra_fw_extended_error_info: ReadWriteMemory<CPTRA_FW_EXTENDED_ERROR_INFO_SIZE>,
+    #[register_array(offset = 0x0018)]
+    cptra_fw_extended_error_info: [u32; CPTRA_FW_EXTENDED_ERROR_INFO_SIZE / 4],
 
-    /// CPTRA_BOOT_STATUS Register
+    #[register(offset = 0x0038)]
     cptra_boot_status: ReadWriteRegister<u32>,
 
-    /// CPTRA_FLOW_STATUS Register
+    #[register(offset = 0x003c, write_fn = on_write_flow_status)]
     cptra_flow_status: ReadWriteRegister<u32, FlowStatus::Register>,
 
-    /// CPTRA_RESET_REASON Register
+    #[register(offset = 0x0040)]
     cptra_reset_reason: ReadOnlyRegister<u32, ResetReason::Register>,
 
-    /// CPTRA_SECURITY_STATE Register
+    #[register(offset = 0x0044)]
     cptra_security_state: ReadOnlyRegister<u32, SecurityState::Register>,
 
-    /// CPTRA_VALID_PAUSER Register
-    cptra_valid_pauser: ReadWriteMemory<CPTRA_VALID_PAUSER_SIZE>,
+    #[register_array(offset = 0x0048)]
+    cptra_valid_pauser: [u32; CPTRA_VALID_PAUSER_SIZE / 4],
 
-    /// CPTRA_PAUSER_LOCK Register
-    cptra_pauser_lock: ReadWriteMemory<CPTRA_PAUSER_LOCK_SIZE>,
+    #[register_array(offset = 0x005c)]
+    cptra_pauser_lock: [u32; CPTRA_PAUSER_LOCK_SIZE / 4],
 
-    /// CPTRA_TRNG_VALID_PAUSER Register
+    #[register(offset = 0x0070)]
     cptra_trng_valid_pauser: ReadWriteRegister<u32>,
 
-    /// CPTRA_TRNG_PAUSER_LOCK Register
+    #[register(offset = 0x0074)]
     cptra_trng_pauser_lock: ReadWriteRegister<u32>,
 
-    /// CPTRA_TRNG_DATA Register
-    cptra_trng_data: ReadOnlyMemory<CPTRA_TRNG_DATA_SIZE>,
+    #[register_array(offset = 0x0078)]
+    cptra_trng_data: [u32; CPTRA_TRNG_DATA_SIZE / 4],
 
-    /// CPTRA_TRNG_STATUS Register
+    #[register(offset = 0x00a8)]
     cptra_trng_status: ReadOnlyRegister<u32>,
 
-    /// CPTRA_FUSE_WR_DONE Register
-    cptra_fuse_wr_done: ReadOnlyRegister<u32>,
+    #[register(offset = 0x00ac, write_fn = on_write_fuse_wr_done)]
+    cptra_fuse_wr_done: u32,
 
-    /// CPTRA_TIMER_CONFIG Register
+    #[register(offset = 0x00b0)]
     cptra_timer_config: ReadWriteRegister<u32>,
 
-    /// CPTRA_BOOTFSM_GO Register
-    cptra_bootfsm_go: ReadOnlyRegister<u32>,
+    #[register(offset = 0x00b4, write_fn = on_write_bootfsm_go)]
+    cptra_bootfsm_go: u32,
 
-    /// CPTRA_DBG_MANUF_SERVICE_REG Register
+    #[register(offset = 0x00b8)]
     cptra_dbg_manuf_service_reg: ReadWriteRegister<u32, DebugManufService::Register>,
 
-    /// CPTRA_CLK_GATING_EN Register
+    #[register(offset = 0x00bc)]
     cptra_clk_gating_en: ReadOnlyRegister<u32>,
 
-    /// CPTRA_GENERIC_INPUT_WIRES Register
-    cptra_generic_input_wires: ReadOnlyMemory<CPTRA_GENERIC_INPUT_WIRES_SIZE>,
+    #[register_array(offset = 0x00c0)]
+    cptra_generic_input_wires: [u32; CPTRA_GENERIC_INPUT_WIRES_SIZE / 4],
 
-    /// CPTRA_GENERIC_OUTPUT_WIRES Register
-    cptra_generic_output_wires: ReadWriteMemory<CPTRA_GENERIC_OUTPUT_WIRES_SIZE>,
+    #[register_array(offset = 0x00c8, write_fn = on_write_generic_output_wires)]
+    cptra_generic_output_wires: [u32; CPTRA_GENERIC_OUTPUT_WIRES_SIZE / 4],
 
-    /// FUSE_UDS_SEED Register
-    fuse_uds_seed: ReadOnlyMemory<FUSE_UDS_SEED_SIZE>,
+    #[register_array(offset = 0x0200)]
+    fuse_uds_seed: [u32; FUSE_UDS_SEED_SIZE / 4],
 
-    /// FUSE_FIELD_ENTROPY Register
-    fuse_field_entropy: ReadOnlyMemory<FUSE_FIELD_ENTROPY_SIZE>,
+    #[register_array(offset = 0x0230)]
+    fuse_field_entropy: [u32; FUSE_FIELD_ENTROPY_SIZE / 4],
 
-    /// FUSE_VENDOR_PK_HASH Register
-    fuse_vendor_pk_hash: ReadOnlyMemory<FUSE_VENDOR_PK_HASH_SIZE>,
+    #[register_array(offset = 0x0250)]
+    fuse_vendor_pk_hash: [u32; FUSE_VENDOR_PK_HASH_SIZE / 4],
 
-    /// FUSE_VENDOR_PK_MASK Register
-    fuse_vendor_pk_hash_mask: ReadOnlyRegister<u32, VendorPubKeyMask::Register>,
+    #[register(offset = 0x0280)]
+    fuse_vendor_pk_hash_mask: ReadWriteRegister<u32, VendorPubKeyMask::Register>,
 
-    /// FUSE_OWNER_PK_HASH Register
-    fuse_owner_pk_hash: ReadOnlyMemory<FUSE_OWNER_PK_HASH_SIZE>,
+    #[register_array(offset = 0x0284)]
+    fuse_owner_pk_hash: [u32; FUSE_OWNER_PK_HASH_SIZE / 4],
 
-    /// FUSE_FMC_SVN Register
-    fuse_fmc_svn: ReadOnlyRegister<u32>,
+    #[register(offset = 0x02b4)]
+    fuse_fmc_svn: u32,
 
-    /// FUSE_RUNTIME_SVN Register
-    fuse_runtime_svn: ReadOnlyMemory<FUSE_RUNTIME_SVN_SIZE>,
+    #[register_array(offset = 0x02b8)]
+    fuse_runtime_svn: [u32; FUSE_RUNTIME_SVN_SIZE / 4],
 
-    /// FUSE_ANTI_ROLLBACK_DISABLE Register
-    fuse_anti_rollback_disable: ReadOnlyRegister<u32>,
+    #[register(offset = 0x02c8)]
+    fuse_anti_rollback_disable: u32,
 
-    /// FUSE_IDEVID_CERT_ATTR Register
-    fuse_idevid_cert_attr: ReadOnlyMemory<FUSE_IDEVID_CERT_ATTR_SIZE>,
+    #[register_array(offset = 0x02cc)]
+    fuse_idevid_cert_attr: [u32; FUSE_IDEVID_CERT_ATTR_SIZE / 4],
 
-    /// FUSE_IDEVID_MANUF_HSM_ID Register
-    fuse_idevid_manuf_hsm_id: ReadOnlyMemory<FUSE_IDEVID_MANUF_HSM_ID_SIZE>,
+    #[register_array(offset = 0x032c)]
+    fuse_idevid_manuf_hsm_id: [u32; FUSE_IDEVID_MANUF_HSM_ID_SIZE / 4],
 
-    /// FUSE_LIFE_CYCLE Register
-    fuse_life_cycle: ReadOnlyRegister<u32>,
+    #[register(offset = 0x033c)]
+    fuse_life_cycle: u32,
 
     /// INTERNAL_OBF_KEY Register
     internal_obf_key: ReadOnlyMemory<INTERNAL_OBF_KEY_SIZE>,
 
     /// INTERNAL_ICCM_LOCK Register
+    #[register(offset = 0x0620, write_fn = on_write_iccm_lock)]
     internal_iccm_lock: ReadWriteRegister<u32, IccmLock::Register>,
 
     /// INTERNAL_FW_UPDATE_RESET Register
+    #[register(offset = 0x0624, write_fn = on_write_internal_fw_update_reset)]
     internal_fw_update_reset: ReadWriteRegister<u32, FwUpdateReset::Register>,
 
     /// INTERNAL_FW_UPDATE_RESET_WAIT_CYCLES Register
+    #[register(offset = 0x0628)]
     internal_fw_update_reset_wait_cycles: ReadWriteRegister<u32, FwUpdateResetWaitCycles::Register>,
 
     /// INTERNAL_NMI_VECTOR Register
+    #[register(offset = 0x062c)]
     internal_nmi_vector: ReadWriteRegister<u32>,
 
     /// Mailbox
@@ -609,6 +464,10 @@ struct SocRegistersImpl {
     ready_for_fw_cb: ReadyForFwCallback,
 
     upload_update_fw: UploadUpdateFwCallback,
+
+    bootfsm_go_cb: BootFsmGoCallback,
+
+    fuses_can_be_written: bool,
 }
 
 impl SocRegistersImpl {
@@ -637,42 +496,42 @@ impl SocRegistersImpl {
     const LDEVID_CERT_READ_TICKS: u64 = 300;
 
     pub fn new(clock: &Clock, mailbox: Mailbox, iccm: Iccm, mut args: CaliptraRootBusArgs) -> Self {
-        let mut regs = Self {
+        let regs = Self {
             cptra_hw_error_fatal: ReadWriteRegister::new(0),
             cptra_hw_error_non_fatal: ReadWriteRegister::new(0),
             cptra_fw_error_fatal: ReadWriteRegister::new(0),
             cptra_fw_error_non_fatal: ReadWriteRegister::new(0),
             cptra_hw_error_enc: ReadWriteRegister::new(0),
             cptra_fw_error_enc: ReadWriteRegister::new(0),
-            cptra_fw_extended_error_info: ReadWriteMemory::new(),
+            cptra_fw_extended_error_info: Default::default(),
             cptra_boot_status: ReadWriteRegister::new(0),
             cptra_flow_status: ReadWriteRegister::new(0),
             cptra_reset_reason: ReadOnlyRegister::new(0),
-            cptra_security_state: ReadOnlyRegister::new(0),
-            cptra_valid_pauser: ReadWriteMemory::new(),
-            cptra_pauser_lock: ReadWriteMemory::new(),
+            cptra_security_state: ReadOnlyRegister::new(args.security_state.into()),
+            cptra_valid_pauser: Default::default(),
+            cptra_pauser_lock: Default::default(),
             cptra_trng_valid_pauser: ReadWriteRegister::new(0),
             cptra_trng_pauser_lock: ReadWriteRegister::new(0),
-            cptra_trng_data: ReadOnlyMemory::new(),
+            cptra_trng_data: Default::default(),
             cptra_trng_status: ReadOnlyRegister::new(0),
-            cptra_fuse_wr_done: ReadOnlyRegister::new(0),
+            cptra_fuse_wr_done: 0,
             cptra_timer_config: ReadWriteRegister::new(0),
-            cptra_bootfsm_go: ReadOnlyRegister::new(0),
+            cptra_bootfsm_go: 0,
             cptra_dbg_manuf_service_reg: ReadWriteRegister::new(0),
             cptra_clk_gating_en: ReadOnlyRegister::new(0),
-            cptra_generic_input_wires: ReadOnlyMemory::new(),
-            cptra_generic_output_wires: ReadWriteMemory::new(),
-            fuse_uds_seed: ReadOnlyMemory::new_with_data(Self::UDS),
-            fuse_field_entropy: ReadOnlyMemory::new_with_data([0xFF; 32]),
-            fuse_vendor_pk_hash: ReadOnlyMemory::new(),
-            fuse_vendor_pk_hash_mask: ReadOnlyRegister::new(0),
-            fuse_owner_pk_hash: ReadOnlyMemory::new(),
-            fuse_fmc_svn: ReadOnlyRegister::new(0),
-            fuse_runtime_svn: ReadOnlyMemory::new(),
-            fuse_anti_rollback_disable: ReadOnlyRegister::new(0),
-            fuse_idevid_cert_attr: ReadOnlyMemory::new(),
-            fuse_idevid_manuf_hsm_id: ReadOnlyMemory::new(),
-            fuse_life_cycle: ReadOnlyRegister::new(0),
+            cptra_generic_input_wires: Default::default(),
+            cptra_generic_output_wires: Default::default(),
+            fuse_uds_seed: words_from_bytes_le(&Self::UDS),
+            fuse_field_entropy: [0xffff_ffff; 8],
+            fuse_vendor_pk_hash: Default::default(),
+            fuse_vendor_pk_hash_mask: ReadWriteRegister::new(0),
+            fuse_owner_pk_hash: Default::default(),
+            fuse_fmc_svn: Default::default(),
+            fuse_runtime_svn: Default::default(),
+            fuse_anti_rollback_disable: Default::default(),
+            fuse_idevid_cert_attr: Default::default(),
+            fuse_idevid_manuf_hsm_id: Default::default(),
+            fuse_life_cycle: Default::default(),
             internal_obf_key: ReadOnlyMemory::new_with_data(Self::DOE_KEY),
             internal_iccm_lock: ReadWriteRegister::new(0),
             internal_fw_update_reset: ReadWriteRegister::new(0),
@@ -691,114 +550,28 @@ impl SocRegistersImpl {
             tb_services_cb: args.tb_services_cb.take(),
             ready_for_fw_cb: args.ready_for_fw_cb.take(),
             upload_update_fw: args.upload_update_fw.take(),
+            fuses_can_be_written: true,
+            bootfsm_go_cb: args.bootfsm_go_cb.take(),
         };
 
-        regs.set_cptra_dbg_manuf_service_reg(&args);
-        regs.set_idevid_cert_attr(&args);
-        regs.set_fuse_vendor_pk_hash(&args);
-        regs.set_fuse_owner_pk_hash(&args);
-        regs.set_cptra_security_state_device_lifecycle(&args);
-
         regs
     }
 
-    fn set_fuse_vendor_pk_hash(&mut self, args: &CaliptraRootBusArgs) {
-        if args.mfg_pk_hash.len() == FUSE_VENDOR_PK_HASH_SIZE {
-            self.fuse_vendor_pk_hash
-                .data_mut()
-                .copy_from_slice(array_ref![args.mfg_pk_hash, 0, FUSE_VENDOR_PK_HASH_SIZE]);
-        }
-    }
-
-    fn set_fuse_owner_pk_hash(&mut self, args: &CaliptraRootBusArgs) {
-        if args.owner_pk_hash.len() == FUSE_OWNER_PK_HASH_SIZE {
-            self.fuse_owner_pk_hash
-                .data_mut()
-                .copy_from_slice(array_ref![args.owner_pk_hash, 0, FUSE_OWNER_PK_HASH_SIZE]);
-        }
-    }
-
-    fn set_cptra_security_state_device_lifecycle(&mut self, args: &CaliptraRootBusArgs) {
-        let mut value = SecurityState::LIFE_CYCLE::UNPROVISIONED;
-        if args.device_lifecycle.eq_ignore_ascii_case("manufacturing") {
-            value = SecurityState::LIFE_CYCLE::MANUFACTURING;
-        } else if args.device_lifecycle.eq_ignore_ascii_case("production") {
-            value = SecurityState::LIFE_CYCLE::PRODUCTION;
-        }
-        self.cptra_security_state
-            .reg
-            .modify(SecurityState::LIFE_CYCLE.val(value.read(SecurityState::LIFE_CYCLE)));
-    }
-
-    fn set_cptra_dbg_manuf_service_reg(&mut self, args: &CaliptraRootBusArgs) {
-        register_bitfields! [
-            u32,
-            DebugManufService [
-                GEN_IDEVID_CSR OFFSET(0) NUMBITS(1) [],
-                GEN_LDEVID_CERT OFFSET(1) NUMBITS(1) [],
-                RESERVED OFFSET(2) NUMBITS(30) [],
-            ],
-        ];
-        let reg: InMemoryRegister<u32, DebugManufService::Register> = InMemoryRegister::new(0);
-
-        if args.req_idevid_csr {
-            reg.modify(DebugManufService::GEN_IDEVID_CSR::SET);
-        }
-
-        if args.req_ldevid_cert {
-            reg.modify(DebugManufService::GEN_LDEVID_CERT::SET);
-        }
-
-        self.cptra_dbg_manuf_service_reg.reg.set(reg.get());
-    }
-
-    fn set_idevid_cert_attr(&mut self, args: &CaliptraRootBusArgs) {
-        register_bitfields! [
-            u32,
-            IDevIdCertAttrFlags [
-                KEY_ID_ALGO OFFSET(0) NUMBITS(2) [
-                    SHA1 = 0b00,
-                    SHA256 = 0b01,
-                    SHA384 = 0b10,
-                    FUSE = 0b11,
-                ],
-                RESERVED OFFSET(2) NUMBITS(30) [],
-            ],
-        ];
-
-        // Determine the Algorithm used for IDEVID Certificate Subject Key Identifier
-        let reg: InMemoryRegister<u32, IDevIdCertAttrFlags::Register> = InMemoryRegister::new(0);
-        if args.idev_key_id_algo.eq_ignore_ascii_case("sha1") {
-            reg.write(IDevIdCertAttrFlags::KEY_ID_ALGO::SHA1)
-        } else if args.idev_key_id_algo.eq_ignore_ascii_case("sha256") {
-            reg.write(IDevIdCertAttrFlags::KEY_ID_ALGO::SHA256)
-        } else if args.idev_key_id_algo.eq_ignore_ascii_case("sha384") {
-            reg.write(IDevIdCertAttrFlags::KEY_ID_ALGO::SHA384)
-        } else if args.idev_key_id_algo.eq_ignore_ascii_case("fuse") {
-            reg.write(IDevIdCertAttrFlags::KEY_ID_ALGO::FUSE)
-        } else {
-            reg.write(IDevIdCertAttrFlags::KEY_ID_ALGO::SHA1)
-        }
-
-        // DWORD 00      - Flags
-        self.fuse_idevid_cert_attr.data_mut()[0..4].copy_from_slice(&reg.get().to_le_bytes());
-
-        // DWORD 01 - 05 - IDEVID Subject Key Identifier
-        self.fuse_idevid_cert_attr.data_mut()[4..24].copy_from_slice(&[0x00; 20]);
-
-        // DWORD 06 - 07 - UEID / Manufacturer Serial Number
-        let ueid = args.ueid.to_le_bytes();
-        self.fuse_idevid_cert_attr.data_mut()[24..28].copy_from_slice(&ueid[..4]);
-        self.fuse_idevid_cert_attr.data_mut()[28..32].copy_from_slice(&ueid[4..]);
-    }
-
     /// Clear secrets
     fn clear_secrets(&mut self) {
-        self.fuse_uds_seed.data_mut().fill(0);
-        self.fuse_field_entropy.data_mut().fill(0);
+        self.fuse_uds_seed = [0u32; 12];
+        self.fuse_field_entropy = [0u32; 8];
         self.internal_obf_key.data_mut().fill(0);
     }
 
+    fn on_write_bootfsm_go(&mut self, _size: RvSize, val: RvData) -> Result<(), BusError> {
+        if (val & 1) != (self.cptra_bootfsm_go & 1) && (val & 1) != 0 {
+            self.cptra_bootfsm_go = 1;
+            (self.bootfsm_go_cb)();
+        }
+        Ok(())
+    }
+
     /// On Write callback for `stdout` register
     ///
     /// # Arguments
@@ -896,6 +669,52 @@ impl SocRegistersImpl {
         Ok(())
     }
 
+    fn on_write_fuse_wr_done(&mut self, _size: RvSize, val: RvData) -> Result<(), BusError> {
+        if (val & 1) != 0 {
+            self.fuses_can_be_written = false;
+            self.cptra_fuse_wr_done |= 1;
+        }
+        Ok(())
+    }
+
+    fn on_write_generic_output_wires(
+        &mut self,
+        size: RvSize,
+        index: usize,
+        val: RvData,
+    ) -> Result<(), BusError> {
+        match index {
+            0 => self.on_write_tb_services(size, val),
+            1 => self.on_write_warm_reset(size, val),
+            _ => Err(StoreAccessFault),
+        }
+    }
+
+    fn on_write_iccm_lock(&mut self, size: RvSize, val: RvData) -> Result<(), BusError> {
+        let iccm_lock_reg = InMemoryRegister::<u32, IccmLock::Register>::new(val);
+        if iccm_lock_reg.is_set(IccmLock::LOCK) {
+            self.iccm.lock();
+        } else {
+            self.iccm.unlock();
+        }
+        self.internal_iccm_lock.write(size, val)
+    }
+
+    fn on_write_internal_fw_update_reset(
+        &mut self,
+        size: RvSize,
+        val: RvData,
+    ) -> Result<(), BusError> {
+        self.internal_fw_update_reset.write(size, val)?;
+
+        // Schedule a firmware update reset timer action.
+        self.op_reset_trigger_action = Some(
+            self.timer
+                .schedule_reset_in(0, TimerActionType::UpdateReset),
+        );
+        Ok(())
+    }
+
     fn download_idev_id_csr(&mut self) {
         if !self.mailbox.is_command_exec_requested() {
             return;
@@ -948,274 +767,9 @@ impl SocRegistersImpl {
         // Unlock the ICCM.
         self.iccm.unlock();
     }
-}
-
-impl Bus for SocRegistersImpl {
-    fn read(&mut self, size: RvSize, addr: RvAddr) -> Result<RvData, BusError> {
-        match addr {
-            CPTRA_HW_ERROR_FATAL_START..=CPTRA_HW_ERROR_FATAL_END => {
-                self.cptra_hw_error_fatal.read(size)
-            }
-
-            CPTRA_HW_ERROR_NON_FATAL_START..=CPTRA_HW_ERROR_NON_FATAL_END => {
-                self.cptra_hw_error_non_fatal.read(size)
-            }
-
-            CPTRA_FW_ERROR_FATAL_START..=CPTRA_FW_ERROR_FATAL_END => {
-                self.cptra_fw_error_fatal.read(size)
-            }
-
-            CPTRA_FW_ERROR_NON_FATAL_START..=CPTRA_FW_ERROR_NON_FATAL_END => {
-                self.cptra_fw_error_non_fatal.read(size)
-            }
-
-            CPTRA_HW_ERROR_ENC_START..=CPTRA_HW_ERROR_ENC_END => self.cptra_hw_error_enc.read(size),
-
-            CPTRA_FW_ERROR_ENC_START..=CPTRA_FW_ERROR_ENC_END => self.cptra_fw_error_enc.read(size),
-
-            CPTRA_FW_EXTENDED_ERROR_INFO_START..=CPTRA_FW_EXTENDED_ERROR_INFO_END => self
-                .cptra_fw_extended_error_info
-                .read(size, addr - CPTRA_FW_EXTENDED_ERROR_INFO_START),
-
-            CPTRA_BOOT_STATUS_START..=CPTRA_BOOT_STATUS_END => self.cptra_boot_status.read(size),
-
-            CPTRA_FLOW_STATUS_START..=CPTRA_FLOW_STATUS_END => self.cptra_flow_status.read(size),
-
-            CPTRA_RESET_REASON_START..=CPTRA_RESET_REASON_END => self.cptra_reset_reason.read(size),
-
-            CPTRA_SECURITY_STATE_START..=CPTRA_SECURITY_STATE_END => {
-                self.cptra_security_state.read(size)
-            }
-
-            CPTRA_VALID_PAUSER_START..=CPTRA_VALID_PAUSER_END => self
-                .cptra_valid_pauser
-                .read(size, addr - CPTRA_VALID_PAUSER_START),
-
-            CPTRA_PAUSER_LOCK_START..=CPTRA_PAUSER_LOCK_END => self
-                .cptra_pauser_lock
-                .read(size, addr - CPTRA_PAUSER_LOCK_START),
-
-            CPTRA_TRNG_VALID_PAUSER_START..=CPTRA_TRNG_VALID_PAUSER_END => {
-                self.cptra_trng_valid_pauser.read(size)
-            }
-
-            CPTRA_TRNG_PAUSER_LOCK_START..=CPTRA_TRNG_PAUSER_LOCK_END => {
-                self.cptra_trng_pauser_lock.read(size)
-            }
-
-            CPTRA_TRNG_DATA_START..=CPTRA_TRNG_DATA_END => self
-                .cptra_trng_data
-                .read(size, addr - CPTRA_TRNG_DATA_START),
-
-            CPTRA_TRNG_STATUS_START..=CPTRA_TRNG_STATUS_END => self.cptra_trng_status.read(size),
-
-            CPTRA_FUSE_WR_DONE_START..=CPTRA_FUSE_WR_DONE_END => self.cptra_fuse_wr_done.read(size),
-
-            CPTRA_TIMER_CONFIG_START..=CPTRA_TIMER_CONFIG_END => self.cptra_timer_config.read(size),
-
-            CPTRA_BOOTFSM_GO_START..=CPTRA_BOOTFSM_GO_END => self.cptra_bootfsm_go.read(size),
-
-            CPTRA_DBG_MANUF_SERVICE_REG_START..=CPTRA_DBG_MANUF_SERVICE_REG_END => {
-                self.cptra_dbg_manuf_service_reg.read(size)
-            }
-
-            CPTRA_CLK_GATING_EN_START..=CPTRA_CLK_GATING_EN_END => {
-                self.cptra_clk_gating_en.read(size)
-            }
-
-            CPTRA_GENERIC_INPUT_WIRES_START..=CPTRA_GENERIC_INPUT_WIRES_END => self
-                .cptra_generic_input_wires
-                .read(size, addr - CPTRA_GENERIC_INPUT_WIRES_START),
-
-            CPTRA_GENERIC_OUTPUT_WIRES_START..=CPTRA_GENERIC_OUTPUT_WIRES_END => self
-                .cptra_generic_output_wires
-                .read(size, addr - CPTRA_GENERIC_OUTPUT_WIRES_START),
-
-            FUSE_VENDOR_PK_HASH_START..=FUSE_VENDOR_PK_HASH_END => self
-                .fuse_vendor_pk_hash
-                .read(size, addr - FUSE_VENDOR_PK_HASH_START),
-
-            FUSE_VENDOR_PK_MASK_START..=FUSE_VENDOR_PK_MASK_END => {
-                self.fuse_vendor_pk_hash_mask.read(size)
-            }
-
-            FUSE_OWNER_PK_HASH_START..=FUSE_OWNER_PK_HASH_END => self
-                .fuse_owner_pk_hash
-                .read(size, addr - FUSE_OWNER_PK_HASH_START),
-
-            FUSE_FMC_SVN_START..=FUSE_FMC_SVN_END => self.fuse_fmc_svn.read(size),
-
-            FUSE_RUNTIME_SVN_START..=FUSE_RUNTIME_SVN_END => self
-                .fuse_runtime_svn
-                .read(size, addr - FUSE_RUNTIME_SVN_START),
-
-            FUSE_ANTI_ROLLBACK_DISABLE_START..=FUSE_ANTI_ROLLBACK_DISABLE_END => {
-                self.fuse_anti_rollback_disable.read(size)
-            }
-
-            FUSE_IDEVID_CERT_ATTR_START..=FUSE_IDEVID_CERT_ATTR_END => self
-                .fuse_idevid_cert_attr
-                .read(size, addr - FUSE_IDEVID_CERT_ATTR_START),
-
-            FUSE_IDEVID_MANUF_HSM_ID_START..=FUSE_IDEVID_MANUF_HSM_ID_END => self
-                .fuse_idevid_manuf_hsm_id
-                .read(size, addr - FUSE_IDEVID_MANUF_HSM_ID_START),
-
-            FUSE_LIFE_CYCLE_START..=FUSE_LIFE_CYCLE_END => self.fuse_life_cycle.read(size),
-
-            INTERNAL_ICCM_LOCK_START..=INTERNAL_ICCM_LOCK_END => self.internal_iccm_lock.read(size),
-
-            INTERNAL_FW_UPDATE_RESET_START..=INTERNAL_FW_UPDATE_RESET_END => {
-                self.internal_fw_update_reset.read(size)
-            }
-
-            INTERNAL_FW_UPDATE_RESET_WAIT_CYCLES_START
-                ..=INTERNAL_FW_UPDATE_RESET_WAIT_CYCLES_END => {
-                self.internal_fw_update_reset_wait_cycles.read(size)
-            }
-
-            INTERNAL_NMI_VECTOR_START..=INTERNAL_NMI_VECTOR_END => {
-                self.internal_nmi_vector.read(size)
-            }
-            _ => Err(BusError::LoadAccessFault),
-        }
-    }
-
-    fn write(&mut self, size: RvSize, addr: RvAddr, val: RvData) -> Result<(), BusError> {
-        match addr {
-            CPTRA_HW_ERROR_FATAL_START..=CPTRA_HW_ERROR_FATAL_END => {
-                self.cptra_hw_error_fatal.write(size, val)
-            }
-
-            CPTRA_HW_ERROR_NON_FATAL_START..=CPTRA_HW_ERROR_NON_FATAL_END => {
-                self.cptra_hw_error_non_fatal.write(size, val)
-            }
-
-            CPTRA_FW_ERROR_FATAL_START..=CPTRA_FW_ERROR_FATAL_END => {
-                self.cptra_fw_error_fatal.write(size, val)
-            }
-
-            CPTRA_FW_ERROR_NON_FATAL_START..=CPTRA_FW_ERROR_NON_FATAL_END => {
-                self.cptra_fw_error_non_fatal.write(size, val)
-            }
-
-            CPTRA_HW_ERROR_ENC_START..=CPTRA_HW_ERROR_ENC_END => {
-                self.cptra_hw_error_enc.write(size, val)
-            }
-
-            CPTRA_FW_ERROR_ENC_START..=CPTRA_FW_ERROR_ENC_END => {
-                self.cptra_fw_error_enc.write(size, val)
-            }
-
-            CPTRA_FW_EXTENDED_ERROR_INFO_START..=CPTRA_FW_EXTENDED_ERROR_INFO_END => self
-                .cptra_fw_extended_error_info
-                .write(size, addr - CPTRA_FW_EXTENDED_ERROR_INFO_START, val),
-
-            CPTRA_BOOT_STATUS_START..=CPTRA_BOOT_STATUS_END => {
-                self.cptra_boot_status.write(size, val)
-            }
-
-            CPTRA_FLOW_STATUS_START..=CPTRA_FLOW_STATUS_END => self.on_write_flow_status(size, val),
-
-            CPTRA_VALID_PAUSER_START..=CPTRA_VALID_PAUSER_END => {
-                self.cptra_valid_pauser
-                    .write(size, addr - CPTRA_VALID_PAUSER_START, val)
-            }
-
-            CPTRA_PAUSER_LOCK_START..=CPTRA_PAUSER_LOCK_END => {
-                self.cptra_pauser_lock
-                    .write(size, addr - CPTRA_PAUSER_LOCK_START, val)
-            }
-
-            CPTRA_TRNG_VALID_PAUSER_START..=CPTRA_TRNG_VALID_PAUSER_END => {
-                self.cptra_trng_valid_pauser.write(size, val)
-            }
-
-            CPTRA_TRNG_PAUSER_LOCK_START..=CPTRA_TRNG_PAUSER_LOCK_END => {
-                self.cptra_trng_pauser_lock.write(size, val)
-            }
-
-            CPTRA_TRNG_DATA_START..=CPTRA_TRNG_DATA_END => {
-                self.cptra_trng_data
-                    .write(size, addr - CPTRA_TRNG_DATA_START, val)
-            }
-
-            CPTRA_TRNG_STATUS_START..=CPTRA_TRNG_STATUS_END => {
-                self.cptra_trng_status.write(size, val)
-            }
-
-            CPTRA_FUSE_WR_DONE_START..=CPTRA_FUSE_WR_DONE_END => {
-                self.cptra_fuse_wr_done.write(size, val)
-            }
-
-            CPTRA_TIMER_CONFIG_START..=CPTRA_TIMER_CONFIG_END => {
-                self.cptra_timer_config.write(size, val)
-            }
-
-            CPTRA_BOOTFSM_GO_START..=CPTRA_BOOTFSM_GO_END => self.cptra_bootfsm_go.write(size, val),
-
-            CPTRA_DBG_MANUF_SERVICE_REG_START..=CPTRA_DBG_MANUF_SERVICE_REG_END => {
-                self.cptra_dbg_manuf_service_reg.write(size, val)
-            }
-
-            CPTRA_CLK_GATING_EN_START..=CPTRA_CLK_GATING_EN_END => {
-                self.cptra_clk_gating_en.write(size, val)
-            }
-
-            CPTRA_GENERIC_INPUT_WIRES_START..=CPTRA_GENERIC_INPUT_WIRES_END => self
-                .cptra_generic_input_wires
-                .write(size, addr - CPTRA_GENERIC_INPUT_WIRES_START, val),
-
-            CPTRA_GENERIC_OUTPUT_WIRES_START..=CPTRA_GENERIC_OUTPUT_WIRES_END => {
-                if addr == CPTRA_GENERIC_OUTPUT_WIRES_0 {
-                    self.on_write_tb_services(size, val)
-                } else if addr == CPTRA_GENERIC_OUTPUT_WIRES_1 {
-                    self.on_write_warm_reset(size, val)
-                } else {
-                    self.cptra_generic_output_wires.write(
-                        size,
-                        addr - CPTRA_GENERIC_OUTPUT_WIRES_START,
-                        val,
-                    )
-                }
-            }
-
-            INTERNAL_ICCM_LOCK_START..=INTERNAL_ICCM_LOCK_END => {
-                let iccm_lock_reg = InMemoryRegister::<u32, IccmLock::Register>::new(val);
-                if iccm_lock_reg.is_set(IccmLock::LOCK) {
-                    self.iccm.lock();
-                } else {
-                    self.iccm.unlock();
-                }
-                self.internal_iccm_lock.write(size, val)
-            }
-
-            INTERNAL_FW_UPDATE_RESET_START..=INTERNAL_FW_UPDATE_RESET_END => {
-                self.internal_fw_update_reset.write(size, val)?;
-
-                // Schedule a firmware update reset timer action.
-                self.op_reset_trigger_action = Some(
-                    self.timer
-                        .schedule_reset_in(0, TimerActionType::UpdateReset),
-                );
-
-                Ok(())
-            }
-
-            INTERNAL_FW_UPDATE_RESET_WAIT_CYCLES_START
-                ..=INTERNAL_FW_UPDATE_RESET_WAIT_CYCLES_END => {
-                self.internal_fw_update_reset_wait_cycles.write(size, val)
-            }
-
-            INTERNAL_NMI_VECTOR_START..=INTERNAL_NMI_VECTOR_END => {
-                self.internal_nmi_vector.write(size, val)
-            }
-            _ => Err(BusError::LoadAccessFault),
-        }
-    }
 
     /// Called by Bus::poll() to indicate that time has passed
-    fn poll(&mut self) {
+    fn bus_poll(&mut self) {
         if self.timer.fired(&mut self.op_fw_write_complete_action) {
             if let Some(cb) = self.op_fw_write_complete_cb.take() {
                 (cb)(&mut self.mailbox);
@@ -1252,7 +806,7 @@ impl Bus for SocRegistersImpl {
     }
 
     /// Called by Bus::warm_reset() to indicate a warm reset
-    fn warm_reset(&mut self) {
+    fn bus_warm_reset(&mut self) {
         // Set the reaset reason to 'WARM_RESET'
         self.cptra_reset_reason
             .reg
@@ -1262,7 +816,7 @@ impl Bus for SocRegistersImpl {
     }
 
     /// Called by Bus::update_reset() to indicate an update reset
-    fn update_reset(&mut self) {
+    fn bus_update_reset(&mut self) {
         // Upload the update firmware in the mailbox.
         (self.upload_update_fw)(&mut self.mailbox);
 
@@ -1322,17 +876,16 @@ mod tests {
         let clock = Clock::new();
         let mailbox_ram = MailboxRam::new();
         let mut mailbox = Mailbox::new(mailbox_ram);
-        let req_idevid_csr = true;
         let mut log_dir = PathBuf::new();
         log_dir.push("/tmp");
         let args = CaliptraRootBusArgs::default();
-        let args = CaliptraRootBusArgs {
-            req_idevid_csr,
-            log_dir,
-            ..args
-        };
-        let mut soc_reg: SocRegisters =
-            SocRegisters::new(&clock, mailbox.clone(), Iccm::new(), args);
+        let args = CaliptraRootBusArgs { log_dir, ..args };
+        let mut soc_reg: SocRegistersInternal =
+            SocRegistersInternal::new(&clock, mailbox.clone(), Iccm::new(), args);
+
+        soc_reg
+            .write(RvSize::Word, CPTRA_DBG_MANUF_SERVICE_REG_START, 1)
+            .unwrap();
 
         //
         // [Sender Side]
@@ -1390,17 +943,15 @@ mod tests {
         let clock = Clock::new();
         let mailbox_ram = MailboxRam::new();
         let mut mailbox = Mailbox::new(mailbox_ram);
-        let req_ldevid_cert = true;
         let mut log_dir = PathBuf::new();
         log_dir.push("/tmp");
         let args = CaliptraRootBusArgs::default();
-        let args = CaliptraRootBusArgs {
-            req_ldevid_cert,
-            log_dir,
-            ..args
-        };
-        let mut soc_reg: SocRegisters =
-            SocRegisters::new(&clock, mailbox.clone(), Iccm::new(), args);
+        let args = CaliptraRootBusArgs { log_dir, ..args };
+        let mut soc_reg: SocRegistersInternal =
+            SocRegistersInternal::new(&clock, mailbox.clone(), Iccm::new(), args);
+        soc_reg
+            .write(RvSize::Word, CPTRA_DBG_MANUF_SERVICE_REG_START, 2)
+            .unwrap();
 
         //
         // [Sender Side]
@@ -1461,7 +1012,8 @@ mod tests {
             tb_services_cb: TbServicesCb::new(move |ch| output2.borrow_mut().push(ch)),
             ..Default::default()
         };
-        let mut soc_reg: SocRegisters = SocRegisters::new(&clock, mailbox, Iccm::new(), args);
+        let mut soc_reg: SocRegistersInternal =
+            SocRegistersInternal::new(&clock, mailbox, Iccm::new(), args);
 
         let _ = soc_reg.write(RvSize::Word, CPTRA_GENERIC_OUTPUT_WIRES_START, b'h'.into());
 


Test patch:
diff --git a/drivers/tests/integration_tests.rs b/drivers/tests/integration_tests.rs
index 75796c1a..6c8df97c 100644
--- a/drivers/tests/integration_tests.rs
+++ b/drivers/tests/integration_tests.rs
@@ -1,7 +1,7 @@
 // Licensed under the Apache-2.0 license
 
 use caliptra_builder::FwId;
-use caliptra_hw_model::{HwModel, InitParams};
+use caliptra_hw_model::{BootParams, HwModel, InitParams};
 
 fn run_driver_test(test_bin_name: &str) {
     let rom = caliptra_builder::build_firmware_rom(&FwId {
@@ -10,8 +10,11 @@ fn run_driver_test(test_bin_name: &str) {
         features: &["emu"],
     })
     .unwrap();
-    let mut model = caliptra_hw_model::create(InitParams {
-        rom: &rom,
+    let mut model = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            ..Default::default()
+        },
         ..Default::default()
     })
     .unwrap();
diff --git a/fmc/tests/test_hand_off.rs b/fmc/tests/test_hand_off.rs
index a627eb7b..a3219729 100644
--- a/fmc/tests/test_hand_off.rs
+++ b/fmc/tests/test_hand_off.rs
@@ -1,6 +1,6 @@
 // Licensed under the Apache-2.0 license
 use caliptra_builder::{FwId, ImageOptions, FMC_WITH_UART, ROM_WITH_UART};
-use caliptra_hw_model::{HwModel, InitParams};
+use caliptra_hw_model::{BootParams, HwModel, InitParams};
 
 #[test]
 fn test_hand_off() {
@@ -19,12 +19,16 @@ fn test_hand_off() {
     )
     .unwrap();
 
-    let mut hw = caliptra_hw_model::create(InitParams {
-        rom: &rom,
+    let mut hw = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            ..Default::default()
+        },
+        fw_image: Some(&image),
         ..Default::default()
     })
     .unwrap();
-    hw.upload_firmware(&image).unwrap();
+
     let mut output = vec![];
     hw.copy_output_until_exit_success(&mut output).unwrap();
 }
diff --git a/hw-latest/verilated/caliptra_verilated.cpp b/hw-latest/verilated/caliptra_verilated.cpp
index bdc9d8b7..3132e632 100644
--- a/hw-latest/verilated/caliptra_verilated.cpp
+++ b/hw-latest/verilated/caliptra_verilated.cpp
@@ -12,8 +12,10 @@ struct caliptra_verilated {
   uint64_t sim_time = 0;
 };
 
-struct caliptra_verilated* caliptra_verilated_new(void) {
-  return new caliptra_verilated();
+struct caliptra_verilated* caliptra_verilated_new(struct caliptra_verilated_init_args* init_args) {
+  auto result = new caliptra_verilated();
+  result->v.security_state = init_args->security_state;
+  return result;
 }
 void caliptra_verilated_destroy(struct caliptra_verilated* model) {
   if (model->tfp.get()) {
diff --git a/hw-latest/verilated/caliptra_verilated.h b/hw-latest/verilated/caliptra_verilated.h
index a8bca569..402feb45 100644
--- a/hw-latest/verilated/caliptra_verilated.h
+++ b/hw-latest/verilated/caliptra_verilated.h
@@ -40,9 +40,14 @@ struct caliptra_verilated_sig_out {
   uint32_t generic_load_data;
 };
 
+struct caliptra_verilated_init_args {
+  uint32_t security_state;
+};
+
 // Constructs a new model. Model must eventually be destroyed with
 // caliptra_verilated_destroy.
-struct caliptra_verilated* caliptra_verilated_new(void);
+struct caliptra_verilated* caliptra_verilated_new(
+    struct caliptra_verilated_init_args* args);
 
 // Destroys the model.
 void caliptra_verilated_destroy(struct caliptra_verilated* model);
diff --git a/hw-latest/verilated/caliptra_verilated.sv b/hw-latest/verilated/caliptra_verilated.sv
index 8e121926..930b3438 100644
--- a/hw-latest/verilated/caliptra_verilated.sv
+++ b/hw-latest/verilated/caliptra_verilated.sv
@@ -39,6 +39,8 @@ module caliptra_verilated (
 
     input bit [7:0][31:0]           cptra_obf_key,
 
+    input bit [3:0] security_state,
+
     output bit ready_for_fuses,
     output bit ready_for_fw_push,
 
@@ -78,10 +80,6 @@ module caliptra_verilated (
     logic [MBOX_DATA_AND_ECC_W-1:0] mbox_sram_wdata_bitflip;
     logic [MBOX_DATA_AND_ECC_W-1:0] mbox_sram_rdata;
 
-
-    //TIE-OFF device lifecycle
-    security_state_t security_state = '{device_lifecycle: DEVICE_PRODUCTION, debug_locked: 1'b1};
-
     el2_mem_if el2_mem_export ();
 
 
@@ -151,7 +149,7 @@ caliptra_top caliptra_top_dut (
     .itrng_data(),
     .itrng_valid(),
 
-    .security_state(security_state) //FIXME TIE-OFF
+    .security_state(security_state)
 );
 
 assign generic_load_en = caliptra_top_dut.soc_ifc_top1.i_soc_ifc_reg.field_combo.CPTRA_GENERIC_OUTPUT_WIRES[0].generic_wires.load_next;
diff --git a/hw-latest/verilated/src/bindings/mod.rs b/hw-latest/verilated/src/bindings/mod.rs
index a209cd7c..b3e54a33 100644
--- a/hw-latest/verilated/src/bindings/mod.rs
+++ b/hw-latest/verilated/src/bindings/mod.rs
@@ -5,7 +5,10 @@ Licensed under the Apache-2.0 license.
 #[allow(dead_code)]
 mod real;
 
-pub use real::{caliptra_verilated, caliptra_verilated_sig_in, caliptra_verilated_sig_out};
+pub use real::{
+    caliptra_verilated, caliptra_verilated_init_args, caliptra_verilated_sig_in,
+    caliptra_verilated_sig_out,
+};
 
 #[cfg(not(feature = "verilator"))]
 mod disabled {
@@ -13,7 +16,9 @@ mod disabled {
 
     const MSG: &str = "Built without verilator support; use --features=verilator to enable";
 
-    pub unsafe fn caliptra_verilated_new() -> *mut caliptra_verilated {
+    pub unsafe fn caliptra_verilated_new(
+        _args: *mut caliptra_verilated_init_args,
+    ) -> *mut caliptra_verilated {
         panic!("{}", MSG);
     }
     pub unsafe fn caliptra_verilated_destroy(_model: *mut caliptra_verilated) {
diff --git a/hw-latest/verilated/src/bindings/real.rs b/hw-latest/verilated/src/bindings/real.rs
index 049df06d..9be5c81e 100644
--- a/hw-latest/verilated/src/bindings/real.rs
+++ b/hw-latest/verilated/src/bindings/real.rs
@@ -37,8 +37,15 @@ pub struct caliptra_verilated_sig_out {
     pub generic_load_en: bool,
     pub generic_load_data: u32,
 }
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct caliptra_verilated_init_args {
+    pub security_state: u32,
+}
 extern "C" {
-    pub fn caliptra_verilated_new() -> *mut caliptra_verilated;
+    pub fn caliptra_verilated_new(
+        args: *mut caliptra_verilated_init_args,
+    ) -> *mut caliptra_verilated;
 }
 extern "C" {
     pub fn caliptra_verilated_destroy(model: *mut caliptra_verilated);
diff --git a/hw-latest/verilated/src/lib.rs b/hw-latest/verilated/src/lib.rs
index 51bb5684..808409fc 100644
--- a/hw-latest/verilated/src/lib.rs
+++ b/hw-latest/verilated/src/lib.rs
@@ -6,6 +6,7 @@ use std::ffi::CString;
 use std::ffi::NulError;
 use std::ptr::null;
 
+pub use bindings::caliptra_verilated_init_args as InitArgs;
 pub use bindings::caliptra_verilated_sig_in as SigIn;
 pub use bindings::caliptra_verilated_sig_out as SigOut;
 
@@ -18,16 +19,16 @@ pub struct CaliptraVerilated {
 
 impl CaliptraVerilated {
     /// Constructs a new model.
-    pub fn new() -> Self {
-        Self::with_generic_load_cb(Box::new(|_| {}))
+    pub fn new(args: InitArgs) -> Self {
+        Self::with_generic_load_cb(args, Box::new(|_| {}))
     }
 
     /// Creates a model that calls `generic_load_cb` whenever the
     /// microcontroller CPU does a load to the generic wires.
-    pub fn with_generic_load_cb(generic_load_cb: Box<dyn FnMut(u8)>) -> Self {
+    pub fn with_generic_load_cb(mut args: InitArgs, generic_load_cb: Box<dyn FnMut(u8)>) -> Self {
         unsafe {
             Self {
-                v: bindings::caliptra_verilated_new(),
+                v: bindings::caliptra_verilated_new(&mut args),
                 input: Default::default(),
                 output: Default::default(),
                 generic_load_cb,
@@ -147,11 +148,6 @@ impl CaliptraVerilated {
         self.input.imem_we = false;
     }
 }
-impl Default for CaliptraVerilated {
-    fn default() -> Self {
-        Self::new()
-    }
-}
 impl Drop for CaliptraVerilated {
     fn drop(&mut self) {
         unsafe { bindings::caliptra_verilated_destroy(self.v) }
@@ -171,7 +167,7 @@ mod tests {
         if !cfg!(feature = "verilator") {
             return;
         }
-        let mut v = CaliptraVerilated::new();
+        let mut v = CaliptraVerilated::new(InitArgs { security_state: 0 });
 
         std::fs::remove_file("/tmp/caliptra_verilated_test.vcd").ok();
         std::fs::remove_file("/tmp/caliptra_verilated_test2.vcd").ok();
diff --git a/test/tests/smoke_test.rs b/test/tests/smoke_test.rs
index a9d3b41e..79e887ce 100644
--- a/test/tests/smoke_test.rs
+++ b/test/tests/smoke_test.rs
@@ -1,7 +1,7 @@
 // Licensed under the Apache-2.0 license
 
 use caliptra_builder::{ImageOptions, APP_WITH_UART, FMC_WITH_UART, ROM_WITH_UART};
-use caliptra_hw_model::{HwModel, InitParams};
+use caliptra_hw_model::{BootParams, HwModel, InitParams};
 
 #[track_caller]
 fn assert_output_contains(haystack: &str, needle: &str) {
@@ -20,12 +20,15 @@ fn smoke_test() {
         ImageOptions::default(),
     )
     .unwrap();
-    let mut hw = caliptra_hw_model::create(InitParams {
-        rom: &rom,
+    let mut hw = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            ..Default::default()
+        },
+        fw_image: Some(&image),
         ..Default::default()
     })
     .unwrap();
-    hw.upload_firmware(&image).unwrap();
     let mut output = vec![];
     hw.copy_output_until_exit_success(&mut output).unwrap();
     let output = String::from_utf8_lossy(&output);