[dv] Add ROM-only chip-level test example

A few chip-level tests (e.g., `chip_sw_flash_init`) must run and
complete entirely in the ROM stage. This means that the conventional
mechanism for running chip-level tests at the flash (ROM_EXT) stage is
not suitable for such tests.

This commit overcomes this by adding a mechanism to build a chip-level
test to be run in ROM using the meson build system. (Conveniently, the
`opentitan_functest` rule already provides such a mechanism using the
`test_in_rom` rule option.) Additionally, this commit updates the DV
testbench to skip loading of a flash image (either via bootstrap or via
the backdoor mem util mechanism) if no such flash image is provided at
runtime. Lastly, an example ROM-stage chip-level test is added to verify
the correctness of the added infrastructure, and to provide a reference
point for future chip-level test development.

Signed-off-by: Timothy Trippel <ttrippel@google.com>
diff --git a/hw/top_earlgrey/data/chip_testplan.hjson b/hw/top_earlgrey/data/chip_testplan.hjson
index 2c78574..d8268cd 100644
--- a/hw/top_earlgrey/data/chip_testplan.hjson
+++ b/hw/top_earlgrey/data/chip_testplan.hjson
@@ -2652,6 +2652,20 @@
     // System level scenarios //
     ////////////////////////////
     {
+      name: chip_sw_example_test_from_rom_or_flash
+      desc: '''Run examples tests developed for each boot stage.
+
+            Our test infrastructure defaults to running tests out flash, at the
+            ROM_EXT stage, but also supports running tests in the ROM stage. We
+            develop example tests to demonstrate these capabilities, and need to
+            run them in DV to ensure the integrity of our infrastructure.
+            '''
+      milestone: V1
+      tests: ["chip_sw_example_flash",
+              "chip_sw_example_rom",
+              ]
+    }
+    {
       name: chip_sw_smoketest
       desc: '''Run smoke tests developed for each IP.
 
diff --git a/hw/top_earlgrey/dv/chip_sim_cfg.hjson b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
index 47a62661..8f3fef6 100644
--- a/hw/top_earlgrey/dv/chip_sim_cfg.hjson
+++ b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
@@ -206,6 +206,18 @@
   // This allows an arbitrary number of SW images to be supplied to the TB.
   tests: [
     {
+      name: chip_sw_example_flash
+      uvm_test_seq: chip_sw_base_vseq
+      sw_images: ["sw/device/tests/example_test_from_flash:1"]
+      en_run_modes: ["sw_test_mode_test_rom"]
+    }
+    {
+      name: chip_sw_example_rom
+      uvm_test_seq: chip_sw_base_vseq
+      sw_images: ["sw/device/tests/example_test_from_rom:0"]
+      en_run_modes: ["sw_test_mode_common"]
+    }
+    {
       name: chip_sw_sleep_pwm_pulses
       uvm_test_seq: chip_sw_pwm_pulses_vseq
       sw_images: ["sw/device/tests/sleep_pwm_pulses_test:1"]
@@ -303,7 +315,7 @@
       uvm_test_seq: chip_sw_base_vseq
       sw_images: ["sw/device/tests/flash_ctrl_idle_low_power_test:1"]
       en_run_modes: ["sw_test_mode_test_rom"]
-    }    
+    }
     {
       name: chip_sw_lc_ctrl_otp_hw_cfg
       uvm_test_seq: chip_sw_base_vseq
diff --git a/hw/top_earlgrey/dv/env/chip_env_cfg.sv b/hw/top_earlgrey/dv/env/chip_env_cfg.sv
index 7553b24..6e26460 100644
--- a/hw/top_earlgrey/dv/env/chip_env_cfg.sv
+++ b/hw/top_earlgrey/dv/env/chip_env_cfg.sv
@@ -128,11 +128,6 @@
       m_pwm_monitor_cfg[i].is_active = 0;
     end
 
-    // By default, assume SW images in PWD with these generic names.
-    sw_images[SwTypeRom] = "./rom";
-    sw_images[SwTypeTest] = "./sw";
-    sw_images[SwTypeOtbn] = "./otbn";
-
     // By default, assume these OTP image paths.
     otp_images[lc_ctrl_state_pkg::LcStRaw] = "otp_ctrl_img_raw.vmem";
     otp_images[lc_ctrl_state_pkg::LcStDev] = "otp_ctrl_img_dev.vmem";
diff --git a/hw/top_earlgrey/dv/env/seq_lib/chip_sw_base_vseq.sv b/hw/top_earlgrey/dv/env/seq_lib/chip_sw_base_vseq.sv
index eeead99..70aeaba 100644
--- a/hw/top_earlgrey/dv/env/seq_lib/chip_sw_base_vseq.sv
+++ b/hw/top_earlgrey/dv/env/seq_lib/chip_sw_base_vseq.sv
@@ -66,12 +66,14 @@
     cfg.mem_bkdr_util_h[Rom].load_mem_from_file({cfg.sw_images[SwTypeRom], ".scr.39.vmem"});
 
     // TODO: the location of the main execution image should be randomized to either bank in future.
-    if (cfg.use_spi_load_bootstrap) begin
-      `uvm_info(`gfn, "Initializing SPI flash bootstrap", UVM_MEDIUM)
-      spi_device_load_bootstrap({cfg.sw_images[SwTypeTest], ".frames.vmem"});
-    end else begin
-      cfg.mem_bkdr_util_h[FlashBank0Data].load_mem_from_file(
-          {cfg.sw_images[SwTypeTest], ".64.scr.vmem"});
+    if (cfg.sw_images.exists(SwTypeTest)) begin
+      if (cfg.use_spi_load_bootstrap) begin
+        `uvm_info(`gfn, "Initializing SPI flash bootstrap", UVM_MEDIUM)
+        spi_device_load_bootstrap({cfg.sw_images[SwTypeTest], ".frames.vmem"});
+      end else begin
+        cfg.mem_bkdr_util_h[FlashBank0Data].load_mem_from_file(
+            {cfg.sw_images[SwTypeTest], ".64.scr.vmem"});
+      end
     end
     cfg.sw_test_status_vif.sw_test_status = SwTestStatusBooted;
 
diff --git a/sw/device/tests/example_chip_level_test.c b/sw/device/tests/example_test_from_flash.c
similarity index 100%
rename from sw/device/tests/example_chip_level_test.c
rename to sw/device/tests/example_test_from_flash.c
diff --git a/sw/device/tests/example_test_from_rom.c b/sw/device/tests/example_test_from_rom.c
new file mode 100644
index 0000000..3a540f4
--- /dev/null
+++ b/sw/device/tests/example_test_from_rom.c
@@ -0,0 +1,56 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/dif/dif_uart.h"
+#include "sw/device/lib/pinmux.h"
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/runtime/print.h"
+#include "sw/device/lib/testing/check.h"
+#include "sw/device/lib/testing/test_framework/test_status.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"  // Generated.
+
+static dif_uart_t uart0;
+
+void _boot_start(void) {
+  // We need to set the test status as "in test" to indicate to the test code
+  // has been reached, even though this test is also in the "boot ROM".
+  test_status_set(kTestStatusInTest);
+  pinmux_init();
+
+  // We need to initialize the UART regardless if we LOG any messages, since
+  // Verilator and FPGA platforms use the UART to communicate the test results.
+  if (kDeviceType != kDeviceSimDV) {
+    CHECK_DIF_OK(dif_uart_init(
+        mmio_region_from_addr(TOP_EARLGREY_UART0_BASE_ADDR), &uart0));
+    CHECK_DIF_OK(
+        dif_uart_configure(&uart0, (dif_uart_config_t){
+                                       .baudrate = kUartBaudrate,
+                                       .clk_freq_hz = kClockFreqPeripheralHz,
+                                       .parity_enable = kDifToggleDisabled,
+                                       .parity = kDifUartParityEven,
+                                   }));
+    base_uart_stdout(&uart0);
+  }
+
+  bool result = false;
+
+  /**
+   * Place test code here.
+   */
+
+  /**
+   * Set test result to true if the test succeeds. Otherwise, set to false.
+   */
+  result = true;
+
+  // Report test status.
+  test_status_set(result ? kTestStatusPassed : kTestStatusFailed);
+
+  // Unreachable.
+  abort();
+}
diff --git a/sw/device/tests/meson.build b/sw/device/tests/meson.build
index cd5613b..99e05a8 100644
--- a/sw/device/tests/meson.build
+++ b/sw/device/tests/meson.build
@@ -19,35 +19,63 @@
 #    after the OTTF initialization assembly (`ottf_start.S`) runs.
 sw_tests = {
   # 'test_name': {
-  #   'library':         test_lib,
-  #   'dv_frames':       true/false, # (can be omitted, defaults to `false`)
-  #   'sign':            true/false, # (can be omitted, defaults to `false`)
-  #   'ottf_start_only': true/false, # (can be omitted, defaults to `false`)
+  #   'library':          test_lib,
+  #   'dv_frames':        true/false, # (can be omitted, defaults to `false`)
+  #   'sign':             true/false, # (can be omitted, defaults to `false`)
+  #   'ottf_start_only':  true/false, # (can be omitted, defaults to `false`)
+  #   'run_at_rom_stage': true/false, # (can be omitted, defaults to `false`)
   # },
 }
 
 ###############################################################################
-# Example Chip-Level Test
+# Example Chip-Level Tests
 ###############################################################################
-example_chip_level_test_lib = declare_dependency(
+# Most chip-level tests run at the flash stage. These can rely on the OTTF to
+# provide common HW initialization (e.g., pinmux, UART, flash, IRQ vector).
+example_test_from_flash_lib = declare_dependency(
   link_with: static_library(
-    'example_chip_level_test_lib',
-    sources: ['example_chip_level_test.c'],
+    'example_test_from_flash_lib ',
+    sources: ['example_test_from_flash.c'],
     dependencies: [
       # Add dependencies here.
     ],
   ),
 )
 sw_tests += {
-  'example_chip_level_test': {
-    'library': example_chip_level_test_lib,
+  'example_test_from_flash': {
+    'library': example_test_from_flash_lib,
+  }
+}
+
+# A few chip-level tests need to run at the ROM stage. These may not rely on the
+# OTTF, and therefore must do all required initializations within the test.
+example_test_from_rom_lib = declare_dependency(
+  link_with: static_library(
+    'example_test_from_rom_lib',
+    sources: [
+      'example_test_from_rom.c',
+    ],
+    dependencies: [
+      sw_lib_dif_uart,
+      sw_lib_mmio,
+      sw_lib_pinmux,
+      sw_lib_runtime_hart,
+      sw_lib_runtime_print,
+      sw_lib_runtime_log,
+      sw_lib_testing_test_status,
+    ],
+  ),
+)
+sw_tests += {
+  'example_test_from_rom': {
+    'library': example_test_from_rom_lib,
+    'run_at_rom_stage': true,
   }
 }
 
 ###############################################################################
 # Cryptolib Tests
 ###############################################################################
-
 subdir('crypto')
 
 ###############################################################################
@@ -1066,49 +1094,76 @@
     targets_to_export = []
     shared_test_deps = [device_lib]
 
-    # unsigned programs loaded with test ROM
-    if 'ottf_start_only' in sw_test_info and sw_test_info['ottf_start_only']
-      # Explicitly ONLY pull in the OTTF startup library since these tests need
-      # to run right after ottf_start.S is done executing. Additionally, the
-      # startup library contains default OTTF ISRs. While these tests may not
-      # override any of the default ISR symbols, they should be linked in since
-      # the `mtvec` is set to point to these in the `ottf_start.S`
-      # initialization assembly (contained in the ottf_start_lib target below).
-      shared_test_deps += [
-        ottf_start_lib,
-      ]
-    else
-      shared_test_deps += [
-        ottf_lib,
-      ]
-    endif
-       
-    if (get_option('closed_source_dir') != '') and \
-      'link_with_closed_configs' in sw_test_info and \
-      sw_test_info['link_with_closed_configs']
+    if 'run_at_rom_stage' in sw_test_info and sw_test_info['run_at_rom_stage']
+
+      # Build the test ELF for ROM.
       sw_test_elf = executable(
         sw_test_name + '_' + device_name,
         name_suffix: 'elf',
-        # Need to force the linker to examine (strong) symbols that may have a
-        # weak implementation in the (static) test library.
-        link_whole: closed_source_config_hooks_lib,
+        link_args: test_rom_link_args,
+        link_depends: test_rom_link_deps,
+        sources: [
+          hw_ip_ast_reg_h,
+          hw_ip_clkmgr_reg_h,
+          hw_ip_csrng_reg_h,
+          hw_ip_edn_reg_h,
+          hw_ip_entropy_src_reg_h,
+          hw_ip_otp_ctrl_reg_h,
+          hw_ip_sram_ctrl_reg_h,
+          hw_ip_sensor_ctrl_reg_h,
+          meson.project_source_root() / 'sw/device/lib/testing/test_rom/test_rom_start.S',
+        ],
         dependencies: [
           shared_test_deps,
+          sw_lib_crt,
           sw_test_info['library'],
-          closed_source_config_hooks_dep,
         ],
       )
     else
-      sw_test_elf = executable(
-        sw_test_name + '_' + device_name,
-        name_suffix: 'elf',
-        dependencies: [
-          shared_test_deps,
-          sw_test_info['library'],
-        ],
-      )
+      if 'ottf_start_only' in sw_test_info and sw_test_info['ottf_start_only']
+        # Explicitly ONLY pull in the OTTF startup library since these tests need
+        # to run right after ottf_start.S is done executing. Additionally, the
+        # startup library contains default OTTF ISRs. While these tests may not
+        # override any of the default ISR symbols, they should be linked in since
+        # the `mtvec` is set to point to these in the `ottf_start.S`
+        # initialization assembly (contained in the ottf_start_lib target below).
+        shared_test_deps += [
+          ottf_start_lib,
+        ]
+      else
+        shared_test_deps += [
+          ottf_lib,
+        ]
+      endif
+
+      # Build the test ELF for flash.
+      if (get_option('closed_source_dir') != '') and \
+        'link_with_closed_configs' in sw_test_info and \
+        sw_test_info['link_with_closed_configs']
+        sw_test_elf = executable(
+          sw_test_name + '_' + device_name,
+          name_suffix: 'elf',
+          # Need to force the linker to examine (strong) symbols that may have a
+          # weak implementation in the (static) test library.
+          link_whole: closed_source_config_hooks_lib,
+          dependencies: [
+            shared_test_deps,
+            sw_test_info['library'],
+            closed_source_config_hooks_dep,
+          ],
+        )
+      else
+        sw_test_elf = executable(
+          sw_test_name + '_' + device_name,
+          name_suffix: 'elf',
+          dependencies: [
+            shared_test_deps,
+            sw_test_info['library'],
+          ],
+        )
+      endif
     endif
-    
+
     target_name = sw_test_name + '_@0@_' + device_name
 
     sw_test_dis = custom_target(
@@ -1123,116 +1178,136 @@
       kwargs: elf_to_bin_custom_target_args,
     )
 
-    sw_test_vmem64 = custom_target(
-      target_name.format('vmem64'),
-      input: sw_test_bin,
-      kwargs: bin_to_vmem64_custom_target_args,
-    )
-
-    sw_test_scr_vmem64 = custom_target(
-      target_name.format('scrambled'),
-      input: sw_test_vmem64,
-      output: flash_image_outputs,
-      command: flash_image_command,
-      depend_files: flash_image_depend_files,
-      build_by_default: true,
-    )
-
     targets_to_export += [
       sw_test_elf,
       sw_test_dis,
       sw_test_bin,
-      sw_test_vmem64,
-      sw_test_scr_vmem64,
     ]
 
-    # signed programs loaded with mask ROM
-    if 'sign' in sw_test_info and sw_test_info['sign']
-      foreach key_name, key_info : signing_keys
-        signed_target_name = '_'.join([
-          'signed',
-          sw_test_name,
-          key_name,
-          '@0@',
-          device_name,
-        ])
-
-        sw_test_signed_bin = custom_target(
-          signed_target_name.format('bin'),
-          input: sw_test_bin,
-          output: '@BASENAME@.@0@.signed.bin'.format(key_name),
-          command: [
-            rom_ext_signer_export.full_path(),
-            'rom_ext',
-            '@INPUT@',
-            key_info['path'],
-            sw_test_elf.full_path(),
-            '@OUTPUT@',
-          ],
-          depends: rom_ext_signer_export,
-          build_by_default: true,
-        )
-
-        sw_test_signed_vmem64 = custom_target(
-          signed_target_name.format('vmem64'),
-          input: sw_test_signed_bin,
-          kwargs: bin_to_vmem64_custom_target_args,
-        )
-
-        sw_test_signed_scr_vmem64 = custom_target(
-          signed_target_name.format('scrambled'),
-          input: sw_test_signed_vmem64,
-          output: flash_image_outputs,
-          command: flash_image_command,
-          depend_files: flash_image_depend_files,
-          build_by_default: true,
-        )
-
-        targets_to_export += [
-          sw_test_signed_bin,
-          sw_test_signed_vmem64,
-          sw_test_signed_scr_vmem64,
-        ]
-      endforeach
-    endif
-
-    sw_test_sim_dv_frames = []
-    if device_name == 'sim_dv' and \
-        sw_test_info.has_key('dv_frames') and sw_test_info['dv_frames']
-      sw_test_sim_dv_frames_bin = custom_target(
-        sw_test_name + '_sim_dv_frames_bin',
-        command: [
-          spiflash_bin,
-          '--input=@INPUT@',
-          '--dump-frames=@OUTPUT@',
-        ],
-        input: sw_test_bin,
-        output: '@BASENAME@.frames.bin',
-      )
-
-      sw_test_sim_dv_frames_vmem = custom_target(
-        sw_test_name + '_sim_dv_frames_vmem',
-        command: [
-          prog_srec_cat,
-          '@INPUT@',
-          '--binary',
-          '--offset', '0x0',
-          '--byte-swap', '4',
-          '--fill', '0xff',
-          '-within', '@INPUT@',
-          '-binary',
-          '-range-pad', '4',
-          '--output', '@OUTPUT@',
-          '--vmem',
-        ],
-        input: sw_test_sim_dv_frames_bin,
-        output: '@BASENAME@.vmem',
+    if 'run_at_rom_stage' in sw_test_info and sw_test_info['run_at_rom_stage']
+      # Test image is destined for ROM.
+      sw_test_scr_vmem32 = custom_target(
+        target_name.format('scr_vmem32'),
+        command: scramble_image_command,
+        depend_files: scramble_image_depend_files,
+        input: sw_test_elf,
+        output: scramble_image_outputs,
+        build_by_default: true,
       )
 
       targets_to_export += [
-        sw_test_sim_dv_frames_bin,
-        sw_test_sim_dv_frames_vmem,
+        sw_test_scr_vmem32,
       ]
+    else
+      # Unsigned test image destined for flash, loaded with the test ROM.
+      sw_test_vmem64 = custom_target(
+        target_name.format('vmem64'),
+        input: sw_test_bin,
+        kwargs: bin_to_vmem64_custom_target_args,
+      )
+
+      sw_test_scr_vmem64 = custom_target(
+        target_name.format('scr_vmem64'),
+        input: sw_test_vmem64,
+        output: flash_image_outputs,
+        command: flash_image_command,
+        depend_files: flash_image_depend_files,
+        build_by_default: true,
+      )
+
+      targets_to_export += [
+        sw_test_vmem64,
+        sw_test_scr_vmem64,
+      ]
+
+      # Signed test image destined for flash, loaded with the mask ROM.
+      if 'sign' in sw_test_info and sw_test_info['sign']
+        foreach key_name, key_info : signing_keys
+          signed_target_name = '_'.join([
+            'signed',
+            sw_test_name,
+            key_name,
+            '@0@',
+            device_name,
+          ])
+
+          sw_test_signed_bin = custom_target(
+            signed_target_name.format('bin'),
+            input: sw_test_bin,
+            output: '@BASENAME@.@0@.signed.bin'.format(key_name),
+            command: [
+              rom_ext_signer_export.full_path(),
+              'rom_ext',
+              '@INPUT@',
+              key_info['path'],
+              sw_test_elf.full_path(),
+              '@OUTPUT@',
+            ],
+            depends: rom_ext_signer_export,
+            build_by_default: true,
+          )
+
+          sw_test_signed_vmem64 = custom_target(
+            signed_target_name.format('vmem64'),
+            input: sw_test_signed_bin,
+            kwargs: bin_to_vmem64_custom_target_args,
+          )
+
+          sw_test_signed_scr_vmem64 = custom_target(
+            signed_target_name.format('scrambled'),
+            input: sw_test_signed_vmem64,
+            output: flash_image_outputs,
+            command: flash_image_command,
+            depend_files: flash_image_depend_files,
+            build_by_default: true,
+          )
+
+          targets_to_export += [
+            sw_test_signed_bin,
+            sw_test_signed_vmem64,
+            sw_test_signed_scr_vmem64,
+          ]
+        endforeach
+      endif
+
+      # DV sim bootstrap image (for loading flash images via bootstrap).
+      if device_name == 'sim_dv' and \
+          sw_test_info.has_key('dv_frames') and sw_test_info['dv_frames']
+        sw_test_sim_dv_frames_bin = custom_target(
+          sw_test_name + '_sim_dv_frames_bin',
+          command: [
+            spiflash_bin,
+            '--input=@INPUT@',
+            '--dump-frames=@OUTPUT@',
+          ],
+          input: sw_test_bin,
+          output: '@BASENAME@.frames.bin',
+        )
+
+        sw_test_sim_dv_frames_vmem = custom_target(
+          sw_test_name + '_sim_dv_frames_vmem',
+          command: [
+            prog_srec_cat,
+            '@INPUT@',
+            '--binary',
+            '--offset', '0x0',
+            '--byte-swap', '4',
+            '--fill', '0xff',
+            '-within', '@INPUT@',
+            '-binary',
+            '-range-pad', '4',
+            '--output', '@OUTPUT@',
+            '--vmem',
+          ],
+          input: sw_test_sim_dv_frames_bin,
+          output: '@BASENAME@.vmem',
+        )
+
+        targets_to_export += [
+          sw_test_sim_dv_frames_bin,
+          sw_test_sim_dv_frames_vmem,
+        ]
+      endif
     endif
 
     if device_name == 'sim_dv'