Repository: chipsalliance/caliptra-sw
Pull request #418 (2023-06-23 07:49:26)

Implement eTRNG and add TRNG abstraction
As usual, I recommend looking at each commit in this PR separately; context is in the descriptions.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 6c382ca3..ba037658 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -346,6 +346,9 @@ dependencies = [
 [[package]]
 name = "caliptra-hw-model-types"
 version = "0.1.0"
+dependencies = [
+ "rand",
+]
 
 [[package]]
 name = "caliptra-image-app"
diff --git a/drivers/Cargo.toml b/drivers/Cargo.toml
index c1c7f508..efc15196 100644
--- a/drivers/Cargo.toml
+++ b/drivers/Cargo.toml
@@ -20,6 +20,7 @@ caliptra-error = { version = "0.1.0", path = "../error" , default-features=false
 
 [features]
 emu = []
+itrng = ["caliptra-hw-model/itrng"]
 verilator = ["caliptra-hw-model/verilator"]
 
 [dev-dependencies]
diff --git a/drivers/src/lib.rs b/drivers/src/lib.rs
index 4d269c5a..ef0e0266 100644
--- a/drivers/src/lib.rs
+++ b/drivers/src/lib.rs
@@ -38,6 +38,8 @@ mod sha256;
 mod sha384;
 mod sha384acc;
 mod soc_ifc;
+mod trng;
+mod trng_ext;
 
 pub use array::{Array4x12, Array4x4, Array4x5, Array4x8, Array4xN};
 pub use array_concat::array_concat3;
@@ -75,6 +77,8 @@ pub use sha256::{Sha256, Sha256DigestOp};
 pub use sha384::{Sha384, Sha384Digest, Sha384DigestOp};
 pub use sha384acc::{Sha384Acc, Sha384AccOp};
 pub use soc_ifc::{report_boot_status, Lifecycle, MfgFlags, ResetReason, SocIfc};
+pub use trng::Trng;
+
 cfg_if::cfg_if! {
     if #[cfg(feature = "emu")] {
         mod uart;
diff --git a/drivers/src/trng.rs b/drivers/src/trng.rs
new file mode 100644
index 00000000..a2391dfa
--- /dev/null
+++ b/drivers/src/trng.rs
@@ -0,0 +1,40 @@
+// Licensed under the Apache-2.0 license
+
+use caliptra_error::CaliptraResult;
+use caliptra_registers::{
+    csrng::CsrngReg, entropy_src::EntropySrcReg, soc_ifc::SocIfcReg, soc_ifc_trng::SocIfcTrngReg,
+};
+
+use crate::{trng_ext::TrngExt, Array4x12, Csrng};
+
+pub enum Trng {
+    Internal(Csrng),
+    External(TrngExt),
+}
+
+impl Trng {
+    pub fn new(
+        csrng: CsrngReg,
+        entropy_src: EntropySrcReg,
+        soc_ifc_trng: SocIfcTrngReg,
+        soc_ifc: &SocIfcReg,
+    ) -> CaliptraResult<Self> {
+        if soc_ifc.regs().cptra_hw_config().read().i_trng_en() {
+            Ok(Self::Internal(Csrng::new(csrng, entropy_src)?))
+        } else {
+            Ok(Self::External(TrngExt::new(soc_ifc_trng)))
+        }
+    }
+
+    pub fn generate(&mut self) -> CaliptraResult<Array4x12> {
+        match self {
+            Self::Internal(csrng) => {
+                let mut iter = csrng.generate(12.try_into().unwrap())?;
+                Ok(Array4x12::new(core::array::from_fn(|_| {
+                    iter.next().unwrap()
+                })))
+            }
+            Self::External(trng_ext) => trng_ext.generate(),
+        }
+    }
+}
diff --git a/drivers/src/trng_ext.rs b/drivers/src/trng_ext.rs
new file mode 100644
index 00000000..19e4d148
--- /dev/null
+++ b/drivers/src/trng_ext.rs
@@ -0,0 +1,33 @@
+// Licensed under the Apache-2.0 license
+
+use caliptra_error::{CaliptraError, CaliptraResult};
+use caliptra_registers::soc_ifc_trng::SocIfcTrngReg;
+
+use crate::Array4x12;
+
+pub struct TrngExt {
+    soc_ifc_trng: SocIfcTrngReg,
+}
+
+impl TrngExt {
+    pub fn new(soc_ifc_trng: SocIfcTrngReg) -> Self {
+        Self { soc_ifc_trng }
+    }
+
+    pub fn generate(&mut self) -> CaliptraResult<Array4x12> {
+        const MAX_CYCLES_TO_WAIT: u32 = 250000;
+
+        let regs = self.soc_ifc_trng.regs_mut();
+        regs.cptra_trng_status().write(|w| w.data_req(true));
+        let mut cycles = 0;
+        while !regs.cptra_trng_status().read().data_wr_done() {
+            cycles += 1;
+            if cycles >= MAX_CYCLES_TO_WAIT {
+                return Err(CaliptraError::DRIVER_TRNG_EXT_TIMEOUT);
+            }
+        }
+        let result = Array4x12::read_from_reg(regs.cptra_trng_data());
+        regs.cptra_trng_status().write(|w| w.data_req(false));
+        Ok(result)
+    }
+}
diff --git a/error/src/lib.rs b/error/src/lib.rs
index 3cab9e58..b4f17e45 100644
--- a/error/src/lib.rs
+++ b/error/src/lib.rs
@@ -283,6 +283,9 @@ impl CaliptraError {
     pub const FMC_RT_ALIAS_CERT_VERIFY: CaliptraError = CaliptraError::new_const(0x000F0006);
     pub const FMC_RT_ALIAS_TBS_SIZE_EXCEEDED: CaliptraError = CaliptraError::new_const(0x000F0007);
 
+    /// TRNG_EXT Errors
+    pub const DRIVER_TRNG_EXT_TIMEOUT: CaliptraError = CaliptraError::new_const(0x00100001);
+
     /// Initial Device ID Errors
     pub const ROM_IDEVID_CSR_BUILDER_INIT_FAILURE: CaliptraError =
         CaliptraError::new_const(0x01000001);
diff --git a/hw-model/Cargo.toml b/hw-model/Cargo.toml
index 40d92361..09edd23f 100644
--- a/hw-model/Cargo.toml
+++ b/hw-model/Cargo.toml
@@ -10,6 +10,7 @@ edition = "2021"
 [features]
 default = []
 verilator = ["dep:caliptra-verilated"]
+itrng = ["caliptra-verilated?/itrng"]
 
 [dependencies]
 caliptra-emu-bus = { path = "../sw-emulator/lib/bus" }
diff --git a/hw-model/c-binding/Cargo.toml b/hw-model/c-binding/Cargo.toml
index 0dce521d..c7e221b5 100644
--- a/hw-model/c-binding/Cargo.toml
+++ b/hw-model/c-binding/Cargo.toml
@@ -14,6 +14,7 @@ caliptra-emu-bus = { path = "../../sw-emulator/lib/bus" }
 crate-type = ["staticlib"]
 
 [features]
+itrng = ["caliptra-hw-model/itrng"]
 verilator = ["caliptra-hw-model/verilator"]
 
 [build-dependencies]
diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs
index d221abfb..1ac8365f 100644
--- a/hw-model/src/lib.rs
+++ b/hw-model/src/lib.rs
@@ -8,7 +8,7 @@ use std::{
 };
 
 use caliptra_emu_bus::Bus;
-use caliptra_hw_model_types::DEFAULT_CPTRA_OBF_KEY;
+use caliptra_hw_model_types::{EtrngResponse, RandomEtrngResponses, DEFAULT_CPTRA_OBF_KEY};
 use zerocopy::{AsBytes, LayoutVerified, Unalign};
 
 use caliptra_registers::mbox;
@@ -83,6 +83,30 @@ impl<R: RngCore> Iterator for RandomNibbles<R> {
     }
 }
 
+#[derive(Debug, Eq, PartialEq)]
+pub enum TrngMode {
+    // soc_ifc_reg.CPTRA_HW_CONFIG.iTRNG_en will be true.
+    // When running with the verlated hw-model, the itrng compile-time feature
+    // must be enabled or initialization will fail.
+    Internal,
+
+    // soc_ifc_reg.CPTRA_HW_CONFIG.iTRNG_en will be false.
+    // When running with the verlated hw-model, the itrng compile-time feature
+    // must be disabled or initialization will fail.
+    External,
+}
+impl TrngMode {
+    pub fn resolve(mode: Option<Self>) -> Self {
+        if let Some(mode) = mode {
+            mode
+        } else if cfg!(feature = "itrng") {
+            TrngMode::Internal
+        } else {
+            TrngMode::External
+        }
+    }
+}
+
 pub struct InitParams<'a> {
     // The contents of the boot ROM
     pub rom: &'a [u8],
@@ -100,17 +124,33 @@ pub struct InitParams<'a> {
     // The silicon obfuscation key passed to caliptra_top.
     pub cptra_obf_key: [u32; 8],
 
-    pub trng_nibbles: Box<dyn Iterator<Item = u8>>,
+    // 4-bit nibbles of raw entropy to feed into the internal TRNG (ENTROPY_SRC
+    // peripheral).
+    pub itrng_nibbles: Box<dyn Iterator<Item = u8>>,
+
+    // Pre-conditioned TRNG responses to return over the soc_ifc CPTRA_TRNG_DATA
+    // registers in response to requests via CPTRA_TRNG_STATUS
+    pub etrng_responses: Box<dyn Iterator<Item = EtrngResponse>>,
+
+    // When None, use the itrng compile-time feature to decide which mode to use.
+    pub trng_mode: Option<TrngMode>,
 }
 
 impl<'a> Default for InitParams<'a> {
     fn default() -> Self {
-        let rng: Box<dyn Iterator<Item = u8>> =
-            if let Ok(Ok(val)) = std::env::var("CPTRA_TRNG_SEED").map(|s| u64::from_str(&s)) {
-                Box::new(RandomNibbles(StdRng::seed_from_u64(val)))
-            } else {
-                Box::new(RandomNibbles(rand::thread_rng()))
-            };
+        let seed = std::env::var("CPTRA_TRNG_SEED")
+            .ok()
+            .and_then(|s| u64::from_str(&s).ok());
+        let itrng_nibbles: Box<dyn Iterator<Item = u8>> = if let Some(seed) = seed {
+            Box::new(RandomNibbles(StdRng::seed_from_u64(seed)))
+        } else {
+            Box::new(RandomNibbles(rand::thread_rng()))
+        };
+        let etrng_responses: Box<dyn Iterator<Item = EtrngResponse>> = if let Some(seed) = seed {
+            Box::new(RandomEtrngResponses(StdRng::seed_from_u64(seed)))
+        } else {
+            Box::new(RandomEtrngResponses::new_from_thread_rng())
+        };
         Self {
             rom: Default::default(),
             dccm: Default::default(),
@@ -119,7 +159,9 @@ impl<'a> Default for InitParams<'a> {
             security_state: *SecurityState::default()
                 .set_device_lifecycle(DeviceLifecycle::Unprovisioned),
             cptra_obf_key: DEFAULT_CPTRA_OBF_KEY,
-            trng_nibbles: rng,
+            itrng_nibbles,
+            etrng_responses,
+            trng_mode: Default::default(),
         }
     }
 }
@@ -451,6 +493,19 @@ pub trait HwModel {
         }
     }
 
+    /// A register block that can be used to manipulate the soc_ifc peripheral TRNG registers
+    /// over the simulated SoC->Caliptra APB bus.
+    fn soc_ifc_trng(
+        &mut self,
+    ) -> caliptra_registers::soc_ifc_trng::RegisterBlock<BusMmio<Self::TBus<'_>>> {
+        unsafe {
+            caliptra_registers::soc_ifc_trng::RegisterBlock::new_with_mmio(
+                0x3003_0000 as *mut u32,
+                BusMmio::new(self.apb_bus()),
+            )
+        }
+    }
+
     /// A register block that can be used to manipulate the mbox peripheral
     /// over the simulated SoC->Caliptra APB bus.
     fn soc_mbox(&mut self) -> caliptra_registers::mbox::RegisterBlock<BusMmio<Self::TBus<'_>>> {
diff --git a/hw-model/src/model_emulated.rs b/hw-model/src/model_emulated.rs
index 2aaec79f..72d6a812 100644
--- a/hw-model/src/model_emulated.rs
+++ b/hw-model/src/model_emulated.rs
@@ -20,6 +20,7 @@ use crate::bus_logger::LogFile;
 use crate::InitParams;
 use crate::ModelError;
 use crate::Output;
+use crate::TrngMode;
 use caliptra_emu_bus::Bus;
 
 pub struct EmulatedApbBus<'a> {
@@ -88,10 +89,18 @@ impl crate::HwModel for ModelEmulated {
             }),
             security_state: params.security_state,
             cptra_obf_key: params.cptra_obf_key,
+
+            etrng_responses: params.etrng_responses,
             ..CaliptraRootBusArgs::default()
         };
         let mut root_bus = CaliptraRootBus::new(&clock, bus_args);
 
+        let trng_mode = TrngMode::resolve(params.trng_mode);
+        root_bus.soc_reg.set_hw_config(match trng_mode {
+            TrngMode::Internal => 1.into(),
+            TrngMode::External => 0.into(),
+        });
+
         {
             let mut iccm_ram = root_bus.iccm.ram().borrow_mut();
             let Some(iccm_dest) = iccm_ram.data_mut().get_mut(0..params.iccm.len()) else {
diff --git a/hw-model/src/model_verilated.rs b/hw-model/src/model_verilated.rs
index 333aa180..ba896799 100644
--- a/hw-model/src/model_verilated.rs
+++ b/hw-model/src/model_verilated.rs
@@ -1,6 +1,8 @@
 // Licensed under the Apache-2.0 license
 
 use crate::bus_logger::{BusLogger, LogFile, NullBus};
+use crate::EtrngResponse;
+use crate::{HwModel, TrngMode};
 use caliptra_emu_bus::Bus;
 use caliptra_emu_types::{RvAddr, RvData, RvSize};
 use caliptra_verilated::{AhbTxnType, CaliptraVerilated};
@@ -31,6 +33,7 @@ impl<'a> Bus for VerilatedApbBus<'a> {
             .log
             .borrow_mut()
             .log_read("SoC", size, addr, result);
+        self.model.process_trng();
         result
     }
 
@@ -51,18 +54,31 @@ impl<'a> Bus for VerilatedApbBus<'a> {
             .log
             .borrow_mut()
             .log_write("SoC", size, addr, val, Ok(()));
+        self.model.process_trng();
         Ok(())
     }
 }
 
+// Like EtrngResponse, but with an absolute time
+struct AbsoluteEtrngResponse {
+    time: u64,
+    data: [u32; 12],
+}
+
 pub struct ModelVerilated {
     v: CaliptraVerilated,
 
     output: Output,
     trace_enabled: bool,
 
-    trng_nibbles: Box<dyn Iterator<Item = u8>>,
-    trng_delay_remaining: u32,
+    trng_mode: TrngMode,
+
+    itrng_nibbles: Box<dyn Iterator<Item = u8>>,
+    itrng_delay_remaining: u32,
+
+    etrng_responses: Box<dyn Iterator<Item = EtrngResponse>>,
+    etrng_response: Option<AbsoluteEtrngResponse>,
+    etrng_waiting_for_req_to_clear: bool,
 
     log: Rc<RefCell<BusLogger<NullBus>>>,
 }
@@ -138,6 +154,23 @@ impl crate::HwModel for ModelVerilated {
                 }
             },
         );
+        let compiled_trng_mode = if cfg!(feature = "itrng") {
+            TrngMode::Internal
+        } else {
+            TrngMode::External
+        };
+        let desired_trng_mode = TrngMode::resolve(params.trng_mode);
+        if desired_trng_mode != compiled_trng_mode {
+            let msg_suffix = match desired_trng_mode {
+                TrngMode::Internal => "try compiling with --features itrng",
+                TrngMode::External => "try compiling without --features itrng",
+            };
+            return Err(format!(
+                "HwModel InitParams asked for trng_mode={desired_trng_mode:?}, \
+                    but verilog was compiled with trng_mode={compiled_trng_mode:?}; {msg_suffix}"
+            )
+            .into());
+        }
         let mut v = CaliptraVerilated::with_callbacks(
             caliptra_verilated::InitArgs {
                 security_state: u32::from(params.security_state),
@@ -154,8 +187,14 @@ impl crate::HwModel for ModelVerilated {
             output,
             trace_enabled: false,
 
-            trng_nibbles: params.trng_nibbles,
-            trng_delay_remaining: TRNG_DELAY,
+            trng_mode: desired_trng_mode,
+
+            itrng_nibbles: params.itrng_nibbles,
+            itrng_delay_remaining: TRNG_DELAY,
+
+            etrng_responses: params.etrng_responses,
+            etrng_response: None,
+            etrng_waiting_for_req_to_clear: false,
 
             log,
         };
@@ -180,19 +219,9 @@ impl crate::HwModel for ModelVerilated {
     }
 
     fn step(&mut self) {
-        if self.v.output.etrng_req {
-            if self.trng_delay_remaining == 0 {
-                if let Some(val) = self.trng_nibbles.next() {
-                    self.v.input.itrng_valid = true;
-                    self.v.input.itrng_data = val & 0xf;
-                }
-                self.trng_delay_remaining = TRNG_DELAY;
-            } else {
-                self.trng_delay_remaining -= 1;
-            }
-        }
+        self.process_trng_start();
         self.v.next_cycle_high(1);
-        self.v.input.itrng_valid = false;
+        self.process_trng_end();
     }
 
     fn output(&mut self) -> &mut crate::Output {
@@ -229,3 +258,76 @@ impl crate::HwModel for ModelVerilated {
         }
     }
 }
+impl ModelVerilated {
+    fn process_trng(&mut self) {
+        if self.process_trng_start() {
+            self.v.next_cycle_high(1);
+            self.process_trng_end();
+        }
+    }
+    fn process_trng_start(&mut self) -> bool {
+        match self.trng_mode {
+            TrngMode::Internal => self.process_itrng_start(),
+            TrngMode::External => self.process_etrng_start(),
+        }
+    }
+
+    fn process_trng_end(&mut self) {
+        match self.trng_mode {
+            TrngMode::Internal => self.process_itrng_end(),
+            TrngMode::External => {}
+        }
+    }
+
+    // Returns true if process_trng_end must be called after a clock cycle
+    fn process_etrng_start(&mut self) -> bool {
+        if self.etrng_waiting_for_req_to_clear && !self.v.output.etrng_req {
+            self.etrng_waiting_for_req_to_clear = false;
+        }
+        if self.v.output.etrng_req && !self.etrng_waiting_for_req_to_clear {
+            if self.etrng_response.is_none() {
+                if let Some(response) = self.etrng_responses.next() {
+                    self.etrng_response = Some(AbsoluteEtrngResponse {
+                        time: self.v.total_cycles() + u64::from(response.delay),
+                        data: response.data,
+                    });
+                }
+            }
+            if let Some(etrng_response) = &mut self.etrng_response {
+                if self.v.total_cycles().wrapping_sub(etrng_response.time) < 0x8000_0000_0000_0000 {
+                    self.etrng_waiting_for_req_to_clear = true;
+                    let etrng_response = self.etrng_response.take().unwrap();
+                    self.soc_ifc_trng()
+                        .cptra_trng_data()
+                        .write(&etrng_response.data);
+                    self.soc_ifc_trng()
+                        .cptra_trng_status()
+                        .write(|w| w.data_wr_done(true));
+                }
+            }
+        }
+        false
+    }
+    // Returns true if process_trng_end must be called after a clock cycle
+    fn process_itrng_start(&mut self) -> bool {
+        if self.v.output.etrng_req {
+            if self.itrng_delay_remaining == 0 {
+                if let Some(val) = self.itrng_nibbles.next() {
+                    self.v.input.itrng_valid = true;
+                    self.v.input.itrng_data = val & 0xf;
+                }
+                self.itrng_delay_remaining = TRNG_DELAY;
+            } else {
+                self.itrng_delay_remaining -= 1;
+            }
+            self.v.input.itrng_valid
+        } else {
+            false
+        }
+    }
+    fn process_itrng_end(&mut self) {
+        if self.v.input.itrng_valid {
+            self.v.input.itrng_valid = false;
+        }
+    }
+}
diff --git a/hw-model/types/Cargo.toml b/hw-model/types/Cargo.toml
index 343f754f..39c21016 100644
--- a/hw-model/types/Cargo.toml
+++ b/hw-model/types/Cargo.toml
@@ -8,3 +8,4 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+rand = "0.8"
\ No newline at end of file
diff --git a/hw-model/types/src/lib.rs b/hw-model/types/src/lib.rs
index 6cd4b485..43db3aaa 100644
--- a/hw-model/types/src/lib.rs
+++ b/hw-model/types/src/lib.rs
@@ -1,5 +1,9 @@
 // Licensed under the Apache-2.0 license
 
+use std::array;
+
+use rand::{rngs::ThreadRng, RngCore};
+
 // Rationale behind this choice
 //
 // * The constant should be easily recognizable in waveforms and debug logs
@@ -182,6 +186,29 @@ impl Default for Fuses {
     }
 }
 
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct EtrngResponse {
+    pub delay: u32,
+    pub data: [u32; 12],
+}
+
+pub struct RandomEtrngResponses<R: RngCore>(pub R);
+impl RandomEtrngResponses<ThreadRng> {
+    pub fn new_from_thread_rng() -> Self {
+        Self(rand::thread_rng())
+    }
+}
+impl<R: RngCore> Iterator for RandomEtrngResponses<R> {
+    type Item = EtrngResponse;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        Some(EtrngResponse {
+            delay: 0,
+            data: array::from_fn(|_| self.0.next_u32()),
+        })
+    }
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/registers/bin/generator/src/main.rs b/registers/bin/generator/src/main.rs
index 1de88db8..73c85c8e 100644
--- a/registers/bin/generator/src/main.rs
+++ b/registers/bin/generator/src/main.rs
@@ -9,7 +9,7 @@ use std::{error::Error, path::Path, process::Command};
 
 use quote::__private::TokenStream;
 use quote::{format_ident, quote};
-use ureg_schema::{Enum, EnumVariant, Register, RegisterBlock};
+use ureg_schema::{Enum, EnumVariant, Register, RegisterBlock, RegisterBlockInstance};
 
 static HEADER_PREFIX: &str = r"/*
 Licensed under the Apache-2.0 license.
@@ -190,7 +190,29 @@ fn real_main() -> Result<(), Box<dyn Error>> {
                 ("DEVICE_MANUFACTURING", "MANUFACTURING"),
                 ("DEVICE_PRODUCTION", "PRODUCTION"),
             ]);
+            // Move the TRNG retrieval registers into an independent block;
+            // these need to be owned by a separate driver than the rest of
+            // soc_ifc.
+            let mut trng_block = RegisterBlock {
+                name: "soc_ifc_trng".into(),
+                instances: vec![RegisterBlockInstance {
+                    name: "soc_ifc_trng_reg".into(),
+                    address: block.instances[0].address,
+                }],
+                ..Default::default()
+            };
+            block.registers.retain(|field| {
+                if matches!(field.name.as_str(), "CPTRA_TRNG_DATA" | "CPTRA_TRNG_STATUS") {
+                    trng_block.registers.push(field.clone());
+                    false // remove field from soc_ifc
+                } else {
+                    true // keep field
+                }
+            });
+            let trng_block = trng_block.validate_and_dedup()?;
+            validated_blocks.push(trng_block);
         }
+
         let mut block = block.validate_and_dedup()?;
 
         if block.block().name == "ecc" {
@@ -224,6 +246,7 @@ fn real_main() -> Result<(), Box<dyn Error>> {
                 );
             });
         }
+
         let module_ident = format_ident!("{}", block.block().name);
         ureg_codegen::build_extern_types(
             &block,
diff --git a/registers/src/lib.rs b/registers/src/lib.rs
index f5a9f3dd..3b697703 100644
--- a/registers/src/lib.rs
+++ b/registers/src/lib.rs
@@ -868,5 +868,6 @@ pub mod sha256;
 pub mod sha512;
 pub mod sha512_acc;
 pub mod soc_ifc;
+pub mod soc_ifc_trng;
 pub mod spi_host;
 pub mod uart;
diff --git a/registers/src/soc_ifc.rs b/registers/src/soc_ifc.rs
index 93452692..d123af12 100644
--- a/registers/src/soc_ifc.rs
+++ b/registers/src/soc_ifc.rs
@@ -321,34 +321,6 @@ impl<TMmio: ureg::Mmio> RegisterBlock<TMmio> {
             )
         }
     }
-    /// Storage for the requested TRNG Data.
-    /// [br]Caliptra Access: RO
-    /// [br]SOC Access:      RW
-    ///
-    /// Read value: [`u32`]; Write value: [`u32`]
-    #[inline(always)]
-    pub fn cptra_trng_data(
-        &self,
-    ) -> ureg::Array<12, ureg::RegRef<crate::soc_ifc::meta::CptraTrngData, &TMmio>> {
-        unsafe {
-            ureg::Array::new_with_mmio(
-                self.ptr.wrapping_add(0x78 / core::mem::size_of::<u32>()),
-                core::borrow::Borrow::borrow(&self.mmio),
-            )
-        }
-    }
-    /// TRNG Status register to indicate request and done
-    ///
-    /// Read value: [`soc_ifc::regs::CptraTrngStatusReadVal`]; Write value: [`soc_ifc::regs::CptraTrngStatusWriteVal`]
-    #[inline(always)]
-    pub fn cptra_trng_status(&self) -> ureg::RegRef<crate::soc_ifc::meta::CptraTrngStatus, &TMmio> {
-        unsafe {
-            ureg::RegRef::new_with_mmio(
-                self.ptr.wrapping_add(0xa8 / core::mem::size_of::<u32>()),
-                core::borrow::Borrow::borrow(&self.mmio),
-            )
-        }
-    }
     /// Writes to fuse registers are completed. After the done bit is set, any subsequent writes to a fuse register will be dropped unless there is a power cycle or a warm reset or caliptra FW allows a write (negotiated through a mailbox command).
     /// [br]Caliptra Access: RO
     /// [br]SOC Access:      RW1-S
@@ -1816,73 +1788,6 @@ pub mod regs {
         }
     }
     #[derive(Clone, Copy)]
-    pub struct CptraTrngStatusReadVal(u32);
-    impl CptraTrngStatusReadVal {
-        /// Indicates that there is a request for TRNG Data.
-        /// [br]Caliptra Access: RW
-        /// [br]SOC Access:      RO
-        #[inline(always)]
-        pub fn data_req(&self) -> bool {
-            ((self.0 >> 0) & 1) != 0
-        }
-        /// Indicates that the requests TRNG Data is done and stored in the TRNG Data register.
-        /// [br]Caliptra Access: RO
-        /// [br]SOC Access:      RW
-        /// [br]When DATA_REQ is 0 DATA_WR_DONE will also be 0
-        #[inline(always)]
-        pub fn data_wr_done(&self) -> bool {
-            ((self.0 >> 1) & 1) != 0
-        }
-        /// Construct a WriteVal that can be used to modify the contents of this register value.
-        #[inline(always)]
-        pub fn modify(self) -> CptraTrngStatusWriteVal {
-            CptraTrngStatusWriteVal(self.0)
-        }
-    }
-    impl From<u32> for CptraTrngStatusReadVal {
-        #[inline(always)]
-        fn from(val: u32) -> Self {
-            Self(val)
-        }
-    }
-    impl From<CptraTrngStatusReadVal> for u32 {
-        #[inline(always)]
-        fn from(val: CptraTrngStatusReadVal) -> u32 {
-            val.0
-        }
-    }
-    #[derive(Clone, Copy)]
-    pub struct CptraTrngStatusWriteVal(u32);
-    impl CptraTrngStatusWriteVal {
-        /// Indicates that there is a request for TRNG Data.
-        /// [br]Caliptra Access: RW
-        /// [br]SOC Access:      RO
-        #[inline(always)]
-        pub fn data_req(self, val: bool) -> Self {
-            Self((self.0 & !(1 << 0)) | (u32::from(val) << 0))
-        }
-        /// Indicates that the requests TRNG Data is done and stored in the TRNG Data register.
-        /// [br]Caliptra Access: RO
-        /// [br]SOC Access:      RW
-        /// [br]When DATA_REQ is 0 DATA_WR_DONE will also be 0
-        #[inline(always)]
-        pub fn data_wr_done(self, val: bool) -> Self {
-            Self((self.0 & !(1 << 1)) | (u32::from(val) << 1))
-        }
-    }
-    impl From<u32> for CptraTrngStatusWriteVal {
-        #[inline(always)]
-        fn from(val: u32) -> Self {
-            Self(val)
-        }
-    }
-    impl From<CptraTrngStatusWriteVal> for u32 {
-        #[inline(always)]
-        fn from(val: CptraTrngStatusWriteVal) -> u32 {
-            val.0
-        }
-    }
-    #[derive(Clone, Copy)]
     pub struct CptraWdtStatusReadVal(u32);
     impl CptraWdtStatusReadVal {
         /// Timer1 timed out, timer2 enabled
@@ -3127,12 +3032,6 @@ pub mod meta {
         crate::soc_ifc::regs::CptraXxxxPauserLockReadVal,
         crate::soc_ifc::regs::CptraXxxxPauserLockWriteVal,
     >;
-    pub type CptraTrngData = ureg::ReadWriteReg32<0, u32, u32>;
-    pub type CptraTrngStatus = ureg::ReadWriteReg32<
-        0,
-        crate::soc_ifc::regs::CptraTrngStatusReadVal,
-        crate::soc_ifc::regs::CptraTrngStatusWriteVal,
-    >;
     pub type CptraFuseWrDone = ureg::ReadWriteReg32<
         0,
         crate::soc_ifc::regs::CptraFuseWrDoneReadVal,
diff --git a/registers/src/soc_ifc_trng.rs b/registers/src/soc_ifc_trng.rs
new file mode 100644
index 00000000..58c14260
--- /dev/null
+++ b/registers/src/soc_ifc_trng.rs
@@ -0,0 +1,188 @@
+// Licensed under the Apache-2.0 license.
+//
+// generated by caliptra_registers_generator with caliptra-rtl repo at d334c84a0e513a3d66a51078f020a414a2c86a67
+//
+#![allow(clippy::erasing_op)]
+#![allow(clippy::identity_op)]
+/// A zero-sized type that represents ownership of this
+/// peripheral, used to get access to a Register lock. Most
+/// programs create one of these in unsafe code near the top of
+/// main(), and pass it to the driver responsible for managing
+/// all access to the hardware.
+pub struct SocIfcTrngReg {
+    _priv: (),
+}
+impl SocIfcTrngReg {
+    pub const PTR: *mut u32 = 0x30030000 as *mut u32;
+    /// # Safety
+    ///
+    /// Caller must ensure that all concurrent use of this
+    /// peripheral in the firmware is done so in a compatible
+    /// way. The simplest way to enforce this is to only call
+    /// this function once.
+    #[inline(always)]
+    pub unsafe fn new() -> Self {
+        Self { _priv: () }
+    }
+    /// Returns a register block that can be used to read
+    /// registers from this peripheral, but cannot write.
+    #[inline(always)]
+    pub fn regs(&self) -> RegisterBlock<ureg::RealMmio> {
+        RegisterBlock {
+            ptr: Self::PTR,
+            mmio: core::default::Default::default(),
+        }
+    }
+    /// Return a register block that can be used to read and
+    /// write this peripheral's registers.
+    #[inline(always)]
+    pub fn regs_mut(&mut self) -> RegisterBlock<ureg::RealMmioMut> {
+        RegisterBlock {
+            ptr: Self::PTR,
+            mmio: core::default::Default::default(),
+        }
+    }
+}
+#[derive(Clone, Copy)]
+pub struct RegisterBlock<TMmio: ureg::Mmio + core::borrow::Borrow<TMmio>> {
+    ptr: *mut u32,
+    mmio: TMmio,
+}
+impl<TMmio: ureg::Mmio + core::default::Default> RegisterBlock<TMmio> {
+    /// # Safety
+    ///
+    /// The caller is responsible for ensuring that ptr is valid for
+    /// volatile reads and writes at any of the offsets in this register
+    /// block.
+    #[inline(always)]
+    pub unsafe fn new(ptr: *mut u32) -> Self {
+        Self {
+            ptr,
+            mmio: core::default::Default::default(),
+        }
+    }
+}
+impl<TMmio: ureg::Mmio> RegisterBlock<TMmio> {
+    /// # Safety
+    ///
+    /// The caller is responsible for ensuring that ptr is valid for
+    /// volatile reads and writes at any of the offsets in this register
+    /// block.
+    #[inline(always)]
+    pub unsafe fn new_with_mmio(ptr: *mut u32, mmio: TMmio) -> Self {
+        Self { ptr, mmio }
+    }
+    /// Storage for the requested TRNG Data.
+    /// [br]Caliptra Access: RO
+    /// [br]SOC Access:      RW
+    ///
+    /// Read value: [`u32`]; Write value: [`u32`]
+    #[inline(always)]
+    pub fn cptra_trng_data(
+        &self,
+    ) -> ureg::Array<12, ureg::RegRef<crate::soc_ifc_trng::meta::CptraTrngData, &TMmio>> {
+        unsafe {
+            ureg::Array::new_with_mmio(
+                self.ptr.wrapping_add(0x78 / core::mem::size_of::<u32>()),
+                core::borrow::Borrow::borrow(&self.mmio),
+            )
+        }
+    }
+    /// TRNG Status register to indicate request and done
+    ///
+    /// Read value: [`soc_ifc_trng::regs::CptraTrngStatusReadVal`]; Write value: [`soc_ifc_trng::regs::CptraTrngStatusWriteVal`]
+    #[inline(always)]
+    pub fn cptra_trng_status(
+        &self,
+    ) -> ureg::RegRef<crate::soc_ifc_trng::meta::CptraTrngStatus, &TMmio> {
+        unsafe {
+            ureg::RegRef::new_with_mmio(
+                self.ptr.wrapping_add(0xa8 / core::mem::size_of::<u32>()),
+                core::borrow::Borrow::borrow(&self.mmio),
+            )
+        }
+    }
+}
+pub mod regs {
+    //! Types that represent the values held by registers.
+    #[derive(Clone, Copy)]
+    pub struct CptraTrngStatusReadVal(u32);
+    impl CptraTrngStatusReadVal {
+        /// Indicates that there is a request for TRNG Data.
+        /// [br]Caliptra Access: RW
+        /// [br]SOC Access:      RO
+        #[inline(always)]
+        pub fn data_req(&self) -> bool {
+            ((self.0 >> 0) & 1) != 0
+        }
+        /// Indicates that the requests TRNG Data is done and stored in the TRNG Data register.
+        /// [br]Caliptra Access: RO
+        /// [br]SOC Access:      RW
+        /// [br]When DATA_REQ is 0 DATA_WR_DONE will also be 0
+        #[inline(always)]
+        pub fn data_wr_done(&self) -> bool {
+            ((self.0 >> 1) & 1) != 0
+        }
+        /// Construct a WriteVal that can be used to modify the contents of this register value.
+        #[inline(always)]
+        pub fn modify(self) -> CptraTrngStatusWriteVal {
+            CptraTrngStatusWriteVal(self.0)
+        }
+    }
+    impl From<u32> for CptraTrngStatusReadVal {
+        #[inline(always)]
+        fn from(val: u32) -> Self {
+            Self(val)
+        }
+    }
+    impl From<CptraTrngStatusReadVal> for u32 {
+        #[inline(always)]
+        fn from(val: CptraTrngStatusReadVal) -> u32 {
+            val.0
+        }
+    }
+    #[derive(Clone, Copy)]
+    pub struct CptraTrngStatusWriteVal(u32);
+    impl CptraTrngStatusWriteVal {
+        /// Indicates that there is a request for TRNG Data.
+        /// [br]Caliptra Access: RW
+        /// [br]SOC Access:      RO
+        #[inline(always)]
+        pub fn data_req(self, val: bool) -> Self {
+            Self((self.0 & !(1 << 0)) | (u32::from(val) << 0))
+        }
+        /// Indicates that the requests TRNG Data is done and stored in the TRNG Data register.
+        /// [br]Caliptra Access: RO
+        /// [br]SOC Access:      RW
+        /// [br]When DATA_REQ is 0 DATA_WR_DONE will also be 0
+        #[inline(always)]
+        pub fn data_wr_done(self, val: bool) -> Self {
+            Self((self.0 & !(1 << 1)) | (u32::from(val) << 1))
+        }
+    }
+    impl From<u32> for CptraTrngStatusWriteVal {
+        #[inline(always)]
+        fn from(val: u32) -> Self {
+            Self(val)
+        }
+    }
+    impl From<CptraTrngStatusWriteVal> for u32 {
+        #[inline(always)]
+        fn from(val: CptraTrngStatusWriteVal) -> u32 {
+            val.0
+        }
+    }
+}
+pub mod enums {
+    //! Enumerations used by some register fields.
+    pub mod selector {}
+}
+pub mod meta {
+    //! Additional metadata needed by ureg.
+    pub type CptraTrngData = ureg::ReadWriteReg32<0, u32, u32>;
+    pub type CptraTrngStatus = ureg::ReadWriteReg32<
+        0,
+        crate::soc_ifc_trng::regs::CptraTrngStatusReadVal,
+        crate::soc_ifc_trng::regs::CptraTrngStatusWriteVal,
+    >;
+}
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 51eba460..603e4d5e 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -37,4 +37,5 @@ emu = [
 ]
 std = ["ufmt/std", "caliptra_common/std"]
 test_only_commands = []
+itrng = ["caliptra-hw-model/itrng"]
 verilator = ["caliptra-hw-model/verilator"]
diff --git a/sw-emulator/lib/periph/src/root_bus.rs b/sw-emulator/lib/periph/src/root_bus.rs
index f1e2aff2..4d09b818 100644
--- a/sw-emulator/lib/periph/src/root_bus.rs
+++ b/sw-emulator/lib/periph/src/root_bus.rs
@@ -21,7 +21,7 @@ use crate::{
 };
 use caliptra_emu_bus::{Clock, Ram, Rom};
 use caliptra_emu_derive::Bus;
-use caliptra_hw_model_types::SecurityState;
+use caliptra_hw_model_types::{EtrngResponse, RandomEtrngResponses, SecurityState};
 use std::path::PathBuf;
 use tock_registers::registers::InMemoryRegister;
 
@@ -203,7 +203,6 @@ impl From<Box<dyn FnMut() + 'static>> for ActionCb {
 }
 
 /// Caliptra Root Bus Arguments
-#[derive(Debug)]
 pub struct CaliptraRootBusArgs {
     pub rom: Vec<u8>,
     pub log_dir: PathBuf,
@@ -220,6 +219,8 @@ pub struct CaliptraRootBusArgs {
 
     // The obfuscation key, as passed to caliptra-top
     pub cptra_obf_key: [u32; 8],
+
+    pub etrng_responses: Box<dyn Iterator<Item = EtrngResponse>>,
 }
 impl Default for CaliptraRootBusArgs {
     fn default() -> Self {
@@ -233,6 +234,7 @@ impl Default for CaliptraRootBusArgs {
             bootfsm_go_cb: Default::default(),
             download_idevid_csr_cb: Default::default(),
             cptra_obf_key: words_from_bytes_be(&DEFAULT_DOE_KEY),
+            etrng_responses: Box::new(RandomEtrngResponses::new_from_thread_rng()),
         }
     }
 }
diff --git a/sw-emulator/lib/periph/src/soc_reg.rs b/sw-emulator/lib/periph/src/soc_reg.rs
index b7c70f07..7fe2e223 100644
--- a/sw-emulator/lib/periph/src/soc_reg.rs
+++ b/sw-emulator/lib/periph/src/soc_reg.rs
@@ -22,6 +22,9 @@ use caliptra_emu_bus::{
 };
 use caliptra_emu_derive::Bus;
 use caliptra_emu_types::{RvAddr, RvData, RvSize};
+use caliptra_hw_model_types::EtrngResponse;
+use caliptra_registers::soc_ifc::regs::CptraHwConfigReadVal;
+use caliptra_registers::soc_ifc_trng::regs::{CptraTrngStatusReadVal, CptraTrngStatusWriteVal};
 use std::cell::RefCell;
 use std::rc::Rc;
 use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};
@@ -258,6 +261,10 @@ impl SocRegistersInternal {
         self.regs.borrow_mut().clear_secrets();
     }
 
+    pub fn set_hw_config(&mut self, val: CptraHwConfigReadVal) {
+        self.regs.borrow_mut().cptra_hw_config = val.into();
+    }
+
     pub fn external_regs(&self) -> SocRegistersExternal {
         SocRegistersExternal {
             regs: self.regs.clone(),
@@ -400,8 +407,8 @@ struct SocRegistersImpl {
     #[register_array(offset = 0x0078)]
     cptra_trng_data: [u32; CPTRA_TRNG_DATA_SIZE / 4],
 
-    #[register(offset = 0x00a8)]
-    cptra_trng_status: ReadOnlyRegister<u32>,
+    #[register(offset = 0x00a8, write_fn = on_write_trng_status)]
+    cptra_trng_status: u32,
 
     #[register(offset = 0x00ac, write_fn = on_write_fuse_wr_done)]
     cptra_fuse_wr_done: u32,
@@ -424,6 +431,9 @@ struct SocRegistersImpl {
     #[register_array(offset = 0x00c8, write_fn = on_write_generic_output_wires)]
     cptra_generic_output_wires: [u32; CPTRA_GENERIC_OUTPUT_WIRES_SIZE / 4],
 
+    #[register(offset = 0x00dc, write_fn = write_disabled)]
+    cptra_hw_config: u32,
+
     #[register(offset = 0x00e0, write_fn = on_write_wdt_timer1_en)]
     cptra_wdt_timer1_en: ReadWriteRegister<u32, WdtEnable::Register>,
 
@@ -538,6 +548,10 @@ struct SocRegistersImpl {
 
     /// WDT Timer2 Expired action
     op_wdt_timer2_expired_action: Option<ActionHandle>,
+
+    etrng_responses: Box<dyn Iterator<Item = EtrngResponse>>,
+    pending_etrng_response: Option<EtrngResponse>,
+    op_pending_etrng_response_action: Option<ActionHandle>,
 }
 
 impl SocRegistersImpl {
@@ -578,7 +592,7 @@ impl SocRegistersImpl {
             cptra_trng_valid_pauser: ReadWriteRegister::new(0),
             cptra_trng_pauser_lock: ReadWriteRegister::new(0),
             cptra_trng_data: Default::default(),
-            cptra_trng_status: ReadOnlyRegister::new(0),
+            cptra_trng_status: 0,
             cptra_fuse_wr_done: 0,
             cptra_timer_config: ReadWriteRegister::new(0),
             cptra_bootfsm_go: 0,
@@ -586,6 +600,7 @@ impl SocRegistersImpl {
             cptra_clk_gating_en: ReadOnlyRegister::new(0),
             cptra_generic_input_wires: Default::default(),
             cptra_generic_output_wires: Default::default(),
+            cptra_hw_config: 0,
             fuse_uds_seed: words_from_bytes_be(&Self::UDS),
             fuse_field_entropy: [0xffff_ffff; 8],
             fuse_vendor_pk_hash: Default::default(),
@@ -625,6 +640,10 @@ impl SocRegistersImpl {
             cptra_wdt_status: ReadOnlyRegister::new(0),
             op_wdt_timer1_expired_action: None,
             op_wdt_timer2_expired_action: None,
+
+            etrng_responses: args.etrng_responses,
+            pending_etrng_response: None,
+            op_pending_etrng_response_action: None,
         };
 
         regs
@@ -637,6 +656,10 @@ impl SocRegistersImpl {
         self.internal_obf_key = [0u32; 8];
     }
 
+    fn write_disabled(&mut self, _size: RvSize, _val: RvData) -> Result<(), BusError> {
+        Err(BusError::StoreAccessFault)
+    }
+
     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;
@@ -867,6 +890,26 @@ impl SocRegistersImpl {
         Ok(())
     }
 
+    fn on_write_trng_status(&mut self, _size: RvSize, val: RvData) -> Result<(), BusError> {
+        let val = CptraTrngStatusReadVal::from(val);
+        if val.data_req() && self.pending_etrng_response.is_none() {
+            if let Some(next_response) = self.etrng_responses.next() {
+                self.op_pending_etrng_response_action =
+                    Some(self.timer.schedule_poll_in(next_response.delay.into()));
+                self.pending_etrng_response = Some(next_response);
+            }
+        }
+        self.cptra_trng_status = if !val.data_req() {
+            // Clear data_wr_done when data_req is cleared
+            CptraTrngStatusWriteVal::from(u32::from(val))
+                .data_wr_done(false)
+                .into()
+        } else {
+            val.into()
+        };
+        Ok(())
+    }
+
     fn reset_common(&mut self) {
         // Unlock the ICCM.
         self.iccm.unlock();
@@ -907,6 +950,15 @@ impl SocRegistersImpl {
             );
         }
 
+        if self.timer.fired(&mut self.op_pending_etrng_response_action) {
+            if let Some(etrng_response) = self.pending_etrng_response.take() {
+                self.cptra_trng_data = etrng_response.data;
+                self.cptra_trng_status = CptraTrngStatusWriteVal::from(self.cptra_trng_status)
+                    .data_wr_done(true)
+                    .into();
+            }
+        }
+
         if self.timer.fired(&mut self.op_wdt_timer1_expired_action) {
             self.cptra_wdt_status.reg.modify(WdtStatus::T1_TIMEOUT::SET);
 


Test patch:
diff --git a/drivers/test-fw/Cargo.toml b/drivers/test-fw/Cargo.toml
index d77ff1f0..988e272e 100644
--- a/drivers/test-fw/Cargo.toml
+++ b/drivers/test-fw/Cargo.toml
@@ -124,4 +124,9 @@ required-features = ["riscv"]
 [[bin]]
 name = "csrng"
 path = "src/bin/csrng_tests.rs"
+required-features = ["riscv"]
+
+[[bin]]
+name = "trng_driver_responder"
+path = "src/bin/trng_driver_responder.rs"
 required-features = ["riscv"]
\ No newline at end of file
diff --git a/drivers/test-fw/src/bin/trng_driver_responder.rs b/drivers/test-fw/src/bin/trng_driver_responder.rs
new file mode 100644
index 00000000..5435f49b
--- /dev/null
+++ b/drivers/test-fw/src/bin/trng_driver_responder.rs
@@ -0,0 +1,61 @@
+// Licensed under the Apache-2.0 license
+
+//! A very simple program that responds with TRNG data through the mailbox
+
+#![no_main]
+#![no_std]
+
+// Needed to bring in startup code
+#[allow(unused)]
+use caliptra_test_harness::{self, println};
+
+use caliptra_drivers::{self, Trng};
+use caliptra_registers::{
+    csrng::CsrngReg, entropy_src::EntropySrcReg, mbox::MboxCsr, soc_ifc::SocIfcReg,
+    soc_ifc_trng::SocIfcTrngReg,
+};
+use zerocopy::AsBytes;
+
+#[panic_handler]
+pub fn panic(_info: &core::panic::PanicInfo) -> ! {
+    loop {}
+}
+
+#[no_mangle]
+extern "C" fn main() {
+    let csrng_reg = unsafe { CsrngReg::new() };
+    let entropy_src_reg = unsafe { EntropySrcReg::new() };
+    let soc_ifc_trng = unsafe { SocIfcTrngReg::new() };
+    let mut soc_ifc = unsafe { SocIfcReg::new() };
+
+    let mut mbox = unsafe { MboxCsr::new() };
+    let mut trng = Trng::new(csrng_reg, entropy_src_reg, soc_ifc_trng, &soc_ifc).unwrap();
+    loop {
+        if !mbox.regs().status().read().mbox_fsm_ps().mbox_execute_uc() {
+            continue;
+        }
+
+        match trng.generate() {
+            Ok(data) => {
+                mbox.regs_mut()
+                    .dlen()
+                    .write(|_| data.0.as_bytes().len() as u32);
+                for word in data.0 {
+                    mbox.regs_mut().datain().write(|_| word);
+                }
+                mbox.regs_mut()
+                    .status()
+                    .write(|w| w.status(|w| w.data_ready()));
+            }
+            Err(e) => {
+                soc_ifc
+                    .regs_mut()
+                    .cptra_fw_error_non_fatal()
+                    .write(|_| e.into());
+                mbox.regs_mut()
+                    .status()
+                    .write(|w| w.status(|w| w.cmd_failure()));
+            }
+        }
+    }
+}
diff --git a/drivers/tests/integration_tests.rs b/drivers/tests/integration_tests.rs
index d48b0b16..b8b49386 100644
--- a/drivers/tests/integration_tests.rs
+++ b/drivers/tests/integration_tests.rs
@@ -6,7 +6,9 @@ use caliptra_builder::FwId;
 use caliptra_drivers_test_bin::DoeTestResults;
 use caliptra_hw_model::{
     BootParams, DefaultHwModel, DeviceLifecycle, HwModel, InitParams, ModelError, SecurityState,
+    TrngMode,
 };
+use caliptra_hw_model_types::EtrngResponse;
 use caliptra_registers::mbox::enums::MboxStatusE;
 use caliptra_test::derive::{DoeInput, DoeOutput};
 use openssl::{hash::MessageDigest, pkey::PKey};
@@ -571,7 +573,7 @@ fn test_csrng() {
     let mut model = caliptra_hw_model::new(BootParams {
         init_params: InitParams {
             rom: &rom,
-            trng_nibbles: Box::new(trng_nibbles),
+            itrng_nibbles: Box::new(trng_nibbles),
             ..Default::default()
         },
         ..Default::default()
@@ -580,3 +582,99 @@ fn test_csrng() {
 
     model.step_until_exit_success().unwrap();
 }
+
+#[test]
+#[cfg_attr(all(feature = "verilator", not(feature = "itrng")), ignore)]
+fn test_trng_in_itrng_mode() {
+    // To run this test under verilator, use --features=verilator,itrng
+    let rom = build_test_rom("trng_driver_responder");
+
+    let mut model = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            itrng_nibbles: Box::new(
+                [
+                    0x7, 0x4, 0x9, 0xE, 0xD, 0x7, 0xB, 0x4, 0xE, 0x3, 0xD, 0xE, 0x4, 0xE, 0x7, 0x2,
+                    0xD, 0x5, 0xC, 0xF, 0x3, 0x6, 0x7, 0xF, 0xD, 0x9, 0xD, 0x1, 0x3, 0x7, 0x1, 0x1,
+                    0x3, 0x4, 0x9, 0x3, 0xB, 0x8, 0x0, 0xA, 0xA, 0xA, 0x6, 0x5, 0xC, 0xD, 0x1, 0x7,
+                    0xA, 0xB, 0xE, 0xB, 0xC, 0xE, 0x4, 0xF, 0xB, 0x4, 0xE, 0x8, 0x1, 0x5, 0x0, 0x1,
+                    0x0, 0x5, 0xC, 0xC, 0x3, 0x4, 0x7, 0xE, 0x0, 0x6, 0x5, 0x3, 0x9, 0x6, 0x5, 0x6,
+                    0x7, 0x8, 0x6, 0xD, 0xA, 0x7, 0x5, 0xF, 0x5, 0x6, 0xB, 0x3, 0x6, 0xF, 0x3, 0x3,
+                ]
+                .iter()
+                .copied(),
+            ),
+            trng_mode: Some(TrngMode::Internal),
+            ..Default::default()
+        },
+        ..Default::default()
+    })
+    .unwrap();
+
+    let trng_block = model.mailbox_execute(0, &[]).unwrap();
+    assert_eq!(
+        trng_block,
+        Some(vec![
+            0x44, 0x2a, 0xeb, 0x15, 0xdd, 0x51, 0x08, 0x31, 0xab, 0x65, 0x13, 0xba, 0xf4, 0x22,
+            0x73, 0x4c, 0x31, 0x83, 0xb8, 0x6b, 0x50, 0xcc, 0x2e, 0x7a, 0x70, 0x34, 0x67, 0x82,
+            0x24, 0xf6, 0x1e, 0xd9, 0x5d, 0x26, 0xa9, 0xea, 0x4e, 0x9b, 0xbe, 0x60, 0x20, 0x7d,
+            0xf3, 0x7d, 0x2e, 0xa9, 0xee, 0x9d,
+        ])
+    );
+
+    let trng_block = model.mailbox_execute(0, &[]).unwrap();
+    assert_eq!(
+        trng_block,
+        Some(vec![
+            0x34, 0x75, 0x3b, 0xe9, 0xc9, 0xd4, 0x0b, 0x2d, 0xc1, 0x6d, 0x90, 0x9a, 0xb9, 0x5f,
+            0xa2, 0x26, 0x32, 0xdc, 0x93, 0xc1, 0x44, 0xed, 0x65, 0xae, 0xfe, 0x9f, 0xf9, 0xb0,
+            0x79, 0x42, 0xbb, 0xd8, 0xd4, 0xea, 0x3c, 0x4f, 0xfb, 0x8c, 0x32, 0xb5, 0x52, 0x08,
+            0x7f, 0xa6, 0x06, 0x4e, 0x37, 0xdd,
+        ])
+    );
+}
+
+#[test]
+#[cfg_attr(all(feature = "verilator", feature = "itrng"), ignore)]
+fn test_trng_in_etrng_mode() {
+    let block0: [u32; 12] = [
+        0x65b11c74, 0xd4bd4965, 0x5031ec6a, 0x2deaad1e, 0xc0c5508f, 0xe7258dc9, 0xa0af9e7f,
+        0x43e173f0, 0xc614d147, 0x3a31be1b, 0x91227cd7, 0xfe61ed6c,
+    ];
+    let block1: [u32; 12] = [
+        0x6e0780e0, 0x7f7e7385, 0xe43d14bb, 0x07faf8da, 0x0553a88e, 0x4b6bf699, 0x6e09b53a,
+        0x5d9c55c8, 0x0303cff9, 0xb9255124, 0x91f478c5, 0xdd186bc8,
+    ];
+
+    let rom = build_test_rom("trng_driver_responder");
+
+    let mut model = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            itrng_nibbles: Box::new([].iter().copied()),
+            etrng_responses: Box::new(
+                vec![
+                    EtrngResponse {
+                        delay: 10,
+                        data: block0,
+                    },
+                    EtrngResponse {
+                        delay: 20,
+                        data: block1,
+                    },
+                ]
+                .into_iter(),
+            ),
+            trng_mode: Some(TrngMode::External),
+            ..Default::default()
+        },
+        ..Default::default()
+    })
+    .unwrap();
+
+    let trng_block = model.mailbox_execute(0, &[]).unwrap();
+    assert_eq!(trng_block, Some(block0.as_bytes().to_vec()));
+
+    let trng_block = model.mailbox_execute(0, &[]).unwrap();
+    assert_eq!(trng_block, Some(block1.as_bytes().to_vec()));
+}
diff --git a/hw-latest/verilated/Cargo.toml b/hw-latest/verilated/Cargo.toml
index ecc84137..749b7dfb 100644
--- a/hw-latest/verilated/Cargo.toml
+++ b/hw-latest/verilated/Cargo.toml
@@ -15,4 +15,7 @@ edition = "2021"
 # a workspace-wide "cargo build".
 verilator = []
 
+# Whether to build the verilog with CALIPTRA_INTERNAL_TRNG defined
+itrng = []
+
 [dependencies]
diff --git a/hw-latest/verilated/Makefile b/hw-latest/verilated/Makefile
index 4f9bb5d4..b56d8c84 100644
--- a/hw-latest/verilated/Makefile
+++ b/hw-latest/verilated/Makefile
@@ -59,6 +59,7 @@ out/Vcaliptra_verilated.h: ${VERILATED_DIR}/caliptra_verilated.sv out
 	  $(suppress) \
 	  -f $(CALIPTRA_RTL_DIR)/src/integration/config/caliptra_top_tb.vf \
 	  $(VERILATED_DIR)/caliptra_verilated.sv \
+	  $(EXTRA_VERILATOR_FLAGS) \
 	  --top-module caliptra_verilated
 
 out/Vcaliptra_verilated__ALL.a: out/Vcaliptra_verilated.h
diff --git a/hw-latest/verilated/build.rs b/hw-latest/verilated/build.rs
index 91b6b2d6..4ee325c8 100644
--- a/hw-latest/verilated/build.rs
+++ b/hw-latest/verilated/build.rs
@@ -70,7 +70,13 @@ fn main() {
         ),
     };
 
-    run_command(process::Command::new("make").current_dir(&manifest_dir));
+    let mut make_cmd = process::Command::new("make");
+    make_cmd.current_dir(&manifest_dir);
+    if std::env::var_os("CARGO_FEATURE_ITRNG").is_some() {
+        make_cmd.arg("EXTRA_VERILATOR_FLAGS=-DCALIPTRA_INTERNAL_TRNG");
+    }
+
+    run_command(&mut make_cmd);
 
     for p in DEP_FILES {
         println!("cargo:rerun-if-changed={}", manifest_dir.join(p).display());
diff --git a/test/Cargo.toml b/test/Cargo.toml
index 6625f18e..bb9d0f33 100644
--- a/test/Cargo.toml
+++ b/test/Cargo.toml
@@ -19,4 +19,5 @@ caliptra-hw-model = { path = "../hw-model" }
 openssl = { version = "0.10", features = ["vendored"] }
 
 [features]
+itrng = ["caliptra-hw-model/itrng"]
 verilator = ["caliptra-hw-model/verilator"]
\ No newline at end of file