Repository: chipsalliance/caliptra-sw
Pull request #173 (2023-04-13 01:10:13)

Integrate mailbox into runtime firmware
Handle mailbox commands in runtime and add test which can run against
sw and verilator.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index b21117d2..047adb4c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -465,6 +465,7 @@ dependencies = [
  "caliptra-image-openssl",
  "caliptra-image-serde",
  "caliptra-image-types",
+ "caliptra-registers",
  "caliptra_common",
  "cfg-if",
  "ufmt",
@@ -477,6 +478,7 @@ version = "0.1.0"
 dependencies = [
  "caliptra-cpu",
  "caliptra-drivers",
+ "caliptra-runtime",
  "caliptra-test-harness",
  "cfg-if",
 ]
diff --git a/drivers/src/error.rs b/drivers/src/error.rs
index a7d532ba..8ec52279 100644
--- a/drivers/src/error.rs
+++ b/drivers/src/error.rs
@@ -53,6 +53,11 @@ pub enum CaliptraComponent {
 
     /// CSRNG
     Csrng = 13,
+
+    /// Runtime firmware
+    /// TODO: Once https://github.com/chipsalliance/caliptra-sw/pull/220 is
+    /// merged remove this and use RT error mechanism instead.
+    Runtime = 14,
 }
 
 #[macro_export]
diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs
index 0f8b5678..a75914a5 100644
--- a/hw-model/src/lib.rs
+++ b/hw-model/src/lib.rs
@@ -315,6 +315,12 @@ pub trait HwModel {
         Ok(())
     }
 
+    fn step_until_output_contains(&mut self, substr: &str) -> Result<(), Box<dyn Error>> {
+        self.output().set_search_term(substr);
+        self.step_until(|m| m.output().search_matched());
+        Ok(())
+    }
+
     /// A register block that can be used to manipulate the soc_ifc peripheral
     /// over the simulated SoC->Caliptra APB bus.
     fn soc_ifc(&mut self) -> caliptra_registers::soc_ifc::RegisterBlock<BusMmio<Self::TBus<'_>>> {
diff --git a/hw-model/src/output.rs b/hw-model/src/output.rs
index 52417ab0..df044fd7 100644
--- a/hw-model/src/output.rs
+++ b/hw-model/src/output.rs
@@ -156,6 +156,10 @@ impl std::io::Write for &OutputSink {
 pub struct Output {
     output: String,
     sink: OutputSink,
+
+    search_term: Option<String>,
+    unsearched: usize, // Number of characters that have not been searched yet
+    search_matched: bool,
 }
 impl Output {
     pub fn new(log_writer: impl std::io::Write + 'static) -> Self {
@@ -172,6 +176,9 @@ impl Output {
                 now: Cell::new(0),
                 next_write_needs_time_prefix: Cell::new(true),
             })),
+            search_term: None,
+            unsearched: 0,
+            search_matched: false,
         }
     }
     pub fn sink(&self) -> &OutputSink {
@@ -202,11 +209,37 @@ impl Output {
 
     fn process_new_data(&mut self) {
         let new_data = self.sink.0.new_uart_output.take();
+        let new_data_len = new_data.len();
+
         if self.output.is_empty() {
             self.output = new_data;
         } else {
             self.output.push_str(&new_data);
         }
+
+        if let Some(term) = &self.search_term {
+            if !self.search_matched {
+                let to_search = term.len() + self.unsearched;
+                if self.output.len() >= to_search {
+                    self.search_matched =
+                        self.output[self.output.len() - to_search..].contains(term);
+                    self.unsearched = 0;
+                } else {
+                    self.unsearched += new_data_len;
+                }
+            }
+        }
+    }
+
+    pub(crate) fn set_search_term(&mut self, search_term: &str) {
+        self.search_term = Some(search_term.to_string());
+        self.unsearched = 0;
+        self.search_matched = false;
+    }
+
+    pub(crate) fn search_matched(&mut self) -> bool {
+        self.process_new_data();
+        self.search_matched
     }
 
     /// Returns true if the caliptra microcontroller has signalled that it wants to exit
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 75202ac9..c81acdd7 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -8,6 +8,7 @@ edition = "2021"
 [dependencies]
 caliptra-cpu = { version = "0.1.0", path = "../cpu" }
 caliptra-drivers = { path = "../drivers" }
+caliptra-registers = { path = "../registers" }
 caliptra_common = { path = "../common", default-features = false }
 ufmt = "0.2.0"
 zerocopy = "0.6.1"
@@ -33,3 +34,4 @@ emu = [
     "caliptra-drivers/emu"
 ]
 std = ["ufmt/std", "caliptra_common/std"]
+verilator = ["caliptra-hw-model/verilator"]
diff --git a/runtime/build.rs b/runtime/build.rs
index 0068d644..c5173e05 100644
--- a/runtime/build.rs
+++ b/runtime/build.rs
@@ -17,6 +17,7 @@ fn main() {
             println!("cargo:rustc-link-arg=-Tmemory.x");
 
             println!("cargo:rustc-link-arg=-Tlink.x");
+
             println!("cargo:rerun-if-changed=build.rs");
         }
     }
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
new file mode 100644
index 00000000..63445f46
--- /dev/null
+++ b/runtime/src/lib.rs
@@ -0,0 +1,61 @@
+// Licensed under the Apache-2.0 license
+
+#![no_std]
+
+mod mailbox;
+
+use mailbox::Mailbox;
+
+use caliptra_common::cprintln;
+use caliptra_drivers::{caliptra_err_def, CaliptraResult};
+use caliptra_registers::mbox::enums::MboxStatusE;
+
+use core::mem::size_of;
+
+caliptra_err_def! {
+    Runtime,
+    RuntimeErr
+    {
+        // Internal
+        InternalErr = 0x1,
+    }
+}
+
+fn wait_for_cmd() {
+    // TODO: Enable interrupts?
+    //#[cfg(feature = "riscv")]
+    //unsafe {
+    //core::arch::asm!("wfi");
+    //}
+}
+
+fn handle_command() -> CaliptraResult<()> {
+    let cmd_id = Mailbox::cmd();
+    let dlen_words = Mailbox::dlen_words() as usize;
+    let mut buf = [0u32; 1024];
+    Mailbox::copy_from_mbox(buf.get_mut(..dlen_words).ok_or(err_u32!(InternalErr))?);
+
+    // TODO: Actually handle command
+    cprintln!("[rt] Received command={}, len={}", cmd_id, Mailbox::dlen());
+
+    // Write response
+    let out_buf = [0xFFFFFFFFu32; 4];
+    Mailbox::set_dlen((out_buf.len() * size_of::<u32>()) as u32);
+    Mailbox::copy_to_mbox(&out_buf);
+
+    Ok(())
+}
+
+pub fn handle_mailbox_commands() {
+    loop {
+        wait_for_cmd();
+
+        if Mailbox::is_cmd_ready() {
+            if handle_command().is_ok() {
+                Mailbox::set_status(MboxStatusE::DataReady);
+            } else {
+                Mailbox::set_status(MboxStatusE::CmdFailure);
+            }
+        }
+    }
+}
diff --git a/runtime/src/mailbox.rs b/runtime/src/mailbox.rs
new file mode 100644
index 00000000..2a7fa344
--- /dev/null
+++ b/runtime/src/mailbox.rs
@@ -0,0 +1,50 @@
+// Licensed under the Apache-2.0 license
+
+use caliptra_registers::mbox::{self, enums::MboxStatusE};
+
+pub struct Mailbox {}
+
+impl Mailbox {
+    /// Check if there is a new command to be executed
+    pub fn is_cmd_ready() -> bool {
+        let mbox = mbox::RegisterBlock::mbox_csr();
+        mbox.status().read().mbox_fsm_ps().mbox_execute_uc()
+    }
+
+    // Get the length of the current mailbox data in bytes
+    pub fn dlen() -> u32 {
+        mbox::RegisterBlock::mbox_csr().dlen().read()
+    }
+
+    // Set the length of the current mailbox data in bytes
+    pub fn set_dlen(len: u32) {
+        mbox::RegisterBlock::mbox_csr().dlen().write(|_| len);
+    }
+
+    // Get the length of the current mailbox data in words
+    pub fn dlen_words() -> u32 {
+        (Self::dlen() + 7) / 8
+    }
+
+    pub fn cmd() -> u32 {
+        mbox::RegisterBlock::mbox_csr().cmd().read()
+    }
+
+    pub fn copy_from_mbox(buf: &mut [u32]) {
+        for word in buf {
+            *word = mbox::RegisterBlock::mbox_csr().dataout().read();
+        }
+    }
+
+    pub fn copy_to_mbox(buf: &[u32]) {
+        for word in buf {
+            mbox::RegisterBlock::mbox_csr().datain().write(|_| *word);
+        }
+    }
+
+    pub fn set_status(status: MboxStatusE) {
+        mbox::RegisterBlock::mbox_csr()
+            .status()
+            .write(|w| w.status(|_| status));
+    }
+}
diff --git a/runtime/src/main.rs b/runtime/src/main.rs
index bf03cdb3..eb57f911 100644
--- a/runtime/src/main.rs
+++ b/runtime/src/main.rs
@@ -36,6 +36,9 @@ pub extern "C" fn entry_point() -> ! {
     cprintln!("{}", BANNER);
 
     if let Some(_fht) = caliptra_common::FirmwareHandoffTable::try_load() {
+        cprintln!("Caliptra RT listening for mailbox commands...");
+        caliptra_runtime::handle_mailbox_commands();
+
         caliptra_drivers::ExitCtrl::exit(0)
     } else {
         caliptra_drivers::ExitCtrl::exit(0xff)
@@ -47,7 +50,7 @@ pub extern "C" fn entry_point() -> ! {
 #[allow(clippy::empty_loop)]
 extern "C" fn exception_handler(trap_record: &TrapRecord) {
     cprintln!(
-        "FMC EXCEPTION mcause=0x{:08X} mscause=0x{:08X} mepc=0x{:08X}",
+        "RT EXCEPTION mcause=0x{:08X} mscause=0x{:08X} mepc=0x{:08X}",
         trap_record.mcause,
         trap_record.mscause,
         trap_record.mepc
@@ -62,7 +65,7 @@ extern "C" fn exception_handler(trap_record: &TrapRecord) {
 #[allow(clippy::empty_loop)]
 extern "C" fn nmi_handler(trap_record: &TrapRecord) {
     cprintln!(
-        "FMC NMI mcause=0x{:08X} mscause=0x{:08X} mepc=0x{:08X}",
+        "RT NMI mcause=0x{:08X} mscause=0x{:08X} mepc=0x{:08X}",
         trap_record.mcause,
         trap_record.mscause,
         trap_record.mepc
diff --git a/sw-emulator/lib/cpu/src/cpu.rs b/sw-emulator/lib/cpu/src/cpu.rs
index 55cdbf6c..c1caab5a 100644
--- a/sw-emulator/lib/cpu/src/cpu.rs
+++ b/sw-emulator/lib/cpu/src/cpu.rs
@@ -383,7 +383,10 @@ impl<TBus: Bus> Cpu<TBus> {
         self.write_csr(Csr::MSTATUS, status.0)?;
 
         self.write_pc(next_pc);
-        println!("{:x}", next_pc);
+        println!(
+            "handle_trap: cause={:x}, mtval={:x}, next_pc={:x}",
+            cause, info, next_pc
+        );
         Ok(())
     }
 


Test patch:
diff --git a/rom/dev/tests/test_idevid_derivation.rs b/rom/dev/tests/test_idevid_derivation.rs
index 21ec6d9b..ba7820b0 100644
--- a/rom/dev/tests/test_idevid_derivation.rs
+++ b/rom/dev/tests/test_idevid_derivation.rs
@@ -3,6 +3,7 @@
 use caliptra_builder::ImageOptions;
 use caliptra_drivers::{state::MfgFlags, IdevidCertAttr, X509KeyIdAlgo};
 use caliptra_hw_model::{Fuses, HwModel};
+use std::io::Write;
 
 pub mod helpers;
 
@@ -26,9 +27,12 @@ fn test_generate_csr() {
     hw.upload_firmware(&image_bundle.to_bytes().unwrap())
         .unwrap();
 
-    let result = hw.copy_output_until_exit_success(&mut output);
-    assert!(result.is_ok());
+    hw.step_until_output_contains("Caliptra RT listening for mailbox commands...")
+        .unwrap();
 
+    output
+        .write_all(hw.output().take(usize::MAX).as_bytes())
+        .unwrap();
     let output = String::from_utf8_lossy(&output);
     let csr_str = helpers::get_data("[idev] CSR = ", &output);
     let csr_uploaded = hex::decode(csr_str).unwrap();
@@ -37,7 +41,6 @@ fn test_generate_csr() {
 
 #[test]
 fn test_idev_subj_key_id_algo() {
-    let mut output = vec![];
     for algo in 0..(X509KeyIdAlgo::Fuse as u32 + 1) {
         let mut fuses = Fuses::default();
         fuses.idevid_cert_attr[IdevidCertAttr::Flags as usize] = algo;
@@ -47,7 +50,7 @@ fn test_idev_subj_key_id_algo() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap();
 
-        let result = hw.copy_output_until_exit_success(&mut output);
-        assert!(result.is_ok());
+        hw.step_until_output_contains("Caliptra RT listening for mailbox commands...")
+            .unwrap();
     }
 }
diff --git a/rom/dev/tests/test_image_validation.rs b/rom/dev/tests/test_image_validation.rs
index 7551d4d0..3c7c937d 100644
--- a/rom/dev/tests/test_image_validation.rs
+++ b/rom/dev/tests/test_image_validation.rs
@@ -188,7 +188,6 @@ fn test_preamble_owner_pubkey_digest_mismatch() {
 
 #[test]
 fn test_preamble_vendor_pubkey_revocation() {
-    let mut output = vec![];
     let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART).unwrap();
     const LAST_KEY_IDX: u32 = VENDOR_ECC_KEY_COUNT - 1;
     const VENDOR_CONFIG_LIST: [ImageGeneratorVendorConfig; VENDOR_ECC_KEY_COUNT as usize] = [
@@ -229,7 +228,8 @@ fn test_preamble_vendor_pubkey_revocation() {
             // Last key is never revoked.
             hw.upload_firmware(&image_bundle.to_bytes().unwrap())
                 .unwrap();
-            hw.copy_output_until_exit_success(&mut output).unwrap();
+            hw.step_until_output_contains("Caliptra RT listening for mailbox commands...")
+                .unwrap();
         } else {
             assert_eq!(
                 ModelError::MailboxCmdFailed,
diff --git a/runtime/test-fw/Cargo.toml b/runtime/test-fw/Cargo.toml
index fe0f84aa..2cfbed48 100644
--- a/runtime/test-fw/Cargo.toml
+++ b/runtime/test-fw/Cargo.toml
@@ -7,9 +7,15 @@ edition = "2021"
 
 [features]
 runtime = ["caliptra-test-harness/runtime"]
-riscv = ["caliptra-cpu/riscv", "runtime"]
+riscv = [
+    "caliptra-cpu/riscv",
+    "caliptra-runtime/riscv",
+    "caliptra-test-harness/riscv",
+]
 emu = [
-    "caliptra-drivers/emu"
+    "caliptra-drivers/emu",
+    "caliptra-runtime/emu",
+    "caliptra-test-harness/emu",
 ]
 
 [[bin]]
@@ -17,11 +23,17 @@ name = "boot"
 path = "src/boot_tests.rs"
 required-features = ["riscv"]
 
+[[bin]]
+name = "mbox"
+path = "src/mbox_tests.rs"
+required-features = ["riscv"]
+
 [build-dependencies]
 cfg-if = "1.0.0"
 
 [dependencies]
 caliptra-test-harness = { path = "../../test-harness" }
 caliptra-cpu = { version = "0.1.0", path = "../../cpu" }
+caliptra-runtime = { path = "..", default-features = false }
 caliptra-drivers = { path = "../../drivers" }
 cfg-if = "1.0.0"
diff --git a/runtime/test-fw/build.rs b/runtime/test-fw/build.rs
index 740b4e08..0cc8a621 100644
--- a/runtime/test-fw/build.rs
+++ b/runtime/test-fw/build.rs
@@ -3,21 +3,26 @@
 fn main() {
     cfg_if::cfg_if! {
         if #[cfg(not(feature = "std"))] {
-            use std::env;
-            use std::fs;
-            use std::path::PathBuf;
+            cfg_if::cfg_if! {
+                if #[cfg(feature = "runtime")] {
+                    use std::env;
+                    use std::fs;
+                    use std::path::PathBuf;
 
-            let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
+                    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
+                    // Put the linker script somewhere the linker can find it.
+                    fs::write(out_dir.join("memory.x"), include_bytes!("../memory.x")).unwrap();
+                    println!("cargo:rustc-link-search={}", out_dir.display());
 
-            // Put the linker script somewhere the linker can find it.
-            fs::write(out_dir.join("memory.x"), include_bytes!("../memory.x")).unwrap();
-            println!("cargo:rustc-link-search={}", out_dir.display());
+                    println!("cargo:rerun-if-changed=memory.x");
+                    println!("cargo:rustc-link-arg=-Tmemory.x");
 
-            println!("cargo:rerun-if-changed=memory.x");
-            println!("cargo:rustc-link-arg=-Tmemory.x");
-
-            println!("cargo:rustc-link-arg=-Tlink.x");
-            println!("cargo:rerun-if-changed=build.rs");
+                    println!("cargo:rustc-link-arg=-Tlink.x");
+                } else {
+                    println!("cargo:rerun-if-changed=../../test-harness/scripts/rom.ld");
+                    println!("cargo:rustc-link-arg=-Ttest-harness/scripts/rom.ld");
+                }
+            }
         }
     }
 }
diff --git a/runtime/test-fw/src/mbox_tests.rs b/runtime/test-fw/src/mbox_tests.rs
new file mode 100644
index 00000000..16e81705
--- /dev/null
+++ b/runtime/test-fw/src/mbox_tests.rs
@@ -0,0 +1,26 @@
+/*++
+
+Licensed under the Apache-2.0 license.
+
+File Name:
+
+    boot_tests.rs
+
+Abstract:
+
+    File contains test cases for booting runtime firmware
+
+--*/
+
+#![no_std]
+#![no_main]
+
+use caliptra_test_harness::test_suite;
+
+fn test_mbox_cmd() {
+    caliptra_runtime::handle_mailbox_commands();
+}
+
+test_suite! {
+    test_mbox_cmd,
+}
diff --git a/runtime/tests/integration_tests.rs b/runtime/tests/integration_tests.rs
index 4f031f34..476204ee 100644
--- a/runtime/tests/integration_tests.rs
+++ b/runtime/tests/integration_tests.rs
@@ -1,9 +1,32 @@
 // Licensed under the Apache-2.0 license.
 
 use caliptra_builder::{FwId, ImageOptions, APP_WITH_UART, FMC_WITH_UART, ROM_WITH_UART};
-use caliptra_hw_model::{BootParams, HwModel, InitParams};
+use caliptra_hw_model::{BootParams, DefaultHwModel, HwModel, InitParams};
 
-fn run_rt_test(test_bin_name: Option<&str>) {
+// Run test_bin as a ROM image. The is used for faster tests that can run
+// against verilator
+fn run_rom_test(test_bin_name: &str) -> DefaultHwModel {
+    let runtime_fwid = FwId {
+        crate_name: "caliptra-runtime-test-bin",
+        bin_name: test_bin_name,
+        features: &["emu", "riscv"],
+    };
+
+    let rom = caliptra_builder::build_firmware_rom(&runtime_fwid).unwrap();
+
+    caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            ..Default::default()
+        },
+        ..Default::default()
+    })
+    .unwrap()
+}
+
+// Run a test which boots ROM -> FMC -> test_bin. If test_bin_name is None,
+// run the production runtime image.
+fn run_rt_test(test_bin_name: Option<&str>) -> DefaultHwModel {
     let runtime_fwid = match test_bin_name {
         Some(bin) => FwId {
             crate_name: "caliptra-runtime-test-bin",
@@ -14,7 +37,8 @@ fn run_rt_test(test_bin_name: Option<&str>) {
     };
 
     let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART).unwrap();
-    let image_bundle = caliptra_builder::build_and_sign_image(
+
+    let image = caliptra_builder::build_and_sign_image(
         &FMC_WITH_UART,
         &runtime_fwid,
         ImageOptions::default(),
@@ -26,13 +50,49 @@ fn run_rt_test(test_bin_name: Option<&str>) {
             rom: &rom,
             ..Default::default()
         },
+        fw_image: Some(&image.to_bytes().unwrap()),
         ..Default::default()
     })
     .unwrap();
 
+    model.step_until(|m| m.soc_ifc().cptra_flow_status().read().ready_for_fw());
+
     model
-        .upload_firmware(&image_bundle.to_bytes().unwrap())
-        .unwrap();
+}
+
+fn send_mailbox_command(model: &mut DefaultHwModel, cmd_id: u32, arg_buf: &[u32]) -> Vec<u32> {
+    model.soc_mbox().lock().read().lock();
+    model.soc_mbox().cmd().write(|_| cmd_id);
+    model
+        .soc_mbox()
+        .dlen()
+        .write(|_| u32::try_from(arg_buf.len()).unwrap() * 4);
+
+    for word in arg_buf {
+        model.soc_mbox().datain().write(|_| *word);
+    }
+    model
+        .soc_mbox()
+        .execute()
+        .write(|exec_reg| exec_reg.execute(true));
+
+    model.step_until(|m| !m.soc_mbox().status().read().status().cmd_busy());
+
+    let dlen_bytes = model.soc_mbox().dlen().read() as usize;
+    let dlen_words = (dlen_bytes + 3) / 4;
+    let mut resp = vec![0u32; dlen_words];
+
+    for dest_word in resp.iter_mut() {
+        *dest_word = model.soc_mbox().dataout().read();
+    }
+
+    model
+        .soc_mbox()
+        .execute()
+        .write(|exec_reg| exec_reg.execute(false));
+    model.step(); // Step once to get mailbox to idel state
+
+    resp
 }
 
 #[test]
@@ -40,10 +100,30 @@ fn test_standard() {
     // Test that the normal runtime firmware boots.
     // Ultimately, this will be useful for exercising Caliptra end-to-end
     // via the mailbox.
-    run_rt_test(None);
+    let mut model = run_rt_test(None);
+
+    model
+        .step_until_output_contains("Caliptra RT listening for mailbox commands...")
+        .unwrap();
 }
 
 #[test]
 fn test_boot() {
-    run_rt_test(Some("boot"));
+    let mut model = run_rt_test(Some("boot"));
+
+    model.step_until_exit_success().unwrap();
+}
+
+#[test]
+fn test_mbox() {
+    let mut model = run_rom_test("mbox");
+
+    model.step_until(|m| m.soc_mbox().status().read().mbox_fsm_ps().mbox_idle());
+
+    let cmd_buf = [0u32; 4];
+    let resp = send_mailbox_command(&mut model, 0x0, &cmd_buf);
+
+    assert_eq!(resp, vec![0xFFFFFFFFu32; 4]);
+
+    assert!(model.soc_mbox().status().read().mbox_fsm_ps().mbox_idle());
 }
diff --git a/test/tests/smoke_test.rs b/test/tests/smoke_test.rs
index 16cd44e1..4883a532 100644
--- a/test/tests/smoke_test.rs
+++ b/test/tests/smoke_test.rs
@@ -2,6 +2,7 @@
 
 use caliptra_builder::{ImageOptions, APP_WITH_UART, FMC_WITH_UART, ROM_WITH_UART};
 use caliptra_hw_model::{BootParams, HwModel, InitParams};
+use std::io::Write;
 
 #[track_caller]
 fn assert_output_contains(haystack: &str, needle: &str) {
@@ -30,7 +31,13 @@ fn smoke_test() {
     })
     .unwrap();
     let mut output = vec![];
-    hw.copy_output_until_exit_success(&mut output).unwrap();
+
+    hw.step_until_output_contains("Caliptra RT listening for mailbox commands...")
+        .unwrap();
+    output
+        .write_all(hw.output().take(usize::MAX).as_bytes())
+        .unwrap();
+
     let output = String::from_utf8_lossy(&output);
     assert_output_contains(&output, "Running Caliptra ROM");
     assert_output_contains(&output, "[cold-reset]");