[dv, sw] "Backdoor" C->SV logging

- This PR enables C logging that bypasses UART and uses a "backdoor"
mechanism.
- The log strings are initialized in a dummy section that is not
actually loaded into the memory. When the elf file is generated, the log
strings (with their addresses) are extracted and converted into a SV
friendly database. The C code on the Ibex does not process the log
strings at all, it just writes the address of the log string to a
reserved memory location in the RAM that the SV side is monitoring. Any
parameters passed to the format string are also subsequently written to
that same address. THe SV looks up the format log string and its
parameter values as it sees them on the bus and process the log into the
final string message. The format string includes information such as the
severity, file name and the line number. Based on the severity, the log
is then printed with the corresponding `uvm_*` facilities.

Signed-off-by: Srikrishna Iyer <sriyer@google.com>
diff --git a/hw/dv/data/sim.mk b/hw/dv/data/sim.mk
index e2bba2c..8e2974b 100644
--- a/hw/dv/data/sim.mk
+++ b/hw/dv/data/sim.mk
@@ -49,9 +49,19 @@
 	# Compile boot rom code and generate the image.
 	ninja -C ${sw_build_dir}/build-out \
 		sw/device/boot_rom/boot_rom_export_${sw_build_device}
+	# Extract the rom logs.
+	${proj_root}/util/device_sw_utils/extract_sw_logs.py \
+		-e "${sw_build_dir}/build-out/sw/device/boot_rom/boot_rom_${sw_build_device}.elf" \
+		-f .logs.fields -r .rodata .logs.strings .chip_info \
+		-n "rom" -o "${run_dir}"
 	# Compile the test sw code and generate the image.
 	ninja -C ${sw_build_dir}/build-out \
 		sw/device/${sw_dir}/${sw_name}_export_${sw_build_device}
+	# Extract the sw logs.
+	${proj_root}/util/device_sw_utils/extract_sw_logs.py \
+		-e "${sw_build_dir}/build-out/sw/device/${sw_dir}/${sw_name}_${sw_build_device}.elf" \
+		-f .logs.fields -r .rodata .logs.strings \
+		-n "sw" -o "${run_dir}"
 	# Copy over the images to the run_dir.
 	cp ${sw_build_dir}/build-out/sw/device/boot_rom/boot_rom_${sw_build_device}.vmem \
 		${run_dir}/rom.vmem
diff --git a/hw/dv/sv/sw_msg_monitor_if/sw_msg_monitor_if.core b/hw/dv/sv/sw_logger_if/sw_logger_if.core
similarity index 83%
rename from hw/dv/sv/sw_msg_monitor_if/sw_msg_monitor_if.core
rename to hw/dv/sv/sw_logger_if/sw_logger_if.core
index 462ef46..912d096 100644
--- a/hw/dv/sv/sw_msg_monitor_if/sw_msg_monitor_if.core
+++ b/hw/dv/sv/sw_logger_if/sw_logger_if.core
@@ -2,13 +2,13 @@
 # Copyright lowRISC contributors.
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
-name: "lowrisc:dv:sw_msg_monitor_if"
+name: "lowrisc:dv:sw_logger_if"
 description: "SW msg monitor interface (convert SW msg prints into SV)"
 
 filesets:
   files_dv:
     files:
-      - sw_msg_monitor_if.sv
+      - sw_logger_if.sv
     file_type: systemVerilogSource
 
 targets:
diff --git a/hw/dv/sv/sw_logger_if/sw_logger_if.sv b/hw/dv/sv/sw_logger_if/sw_logger_if.sv
new file mode 100644
index 0000000..c6d8bbc
--- /dev/null
+++ b/hw/dv/sv/sw_logger_if/sw_logger_if.sv
@@ -0,0 +1,428 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// shortcuts for use in switching # of args to insert in formatted string
+`define _0_ARGS(a)
+`define _1_ARGS(a)                  , a[0]
+`define _2_ARGS(a)                  `_1_ARGS(a), a[1]
+`define _3_ARGS(a)                  `_2_ARGS(a), a[2]
+`define _4_ARGS(a)                  `_3_ARGS(a), a[3]
+`define _5_ARGS(a)                  `_4_ARGS(a), a[4]
+`define _6_ARGS(a)                  `_5_ARGS(a), a[5]
+`define _7_ARGS(a)                  `_6_ARGS(a), a[6]
+`define _8_ARGS(a)                  `_7_ARGS(a), a[7]
+`define _9_ARGS(a)                  `_8_ARGS(a), a[8]
+`define _10_ARGS(a)                 `_9_ARGS(a), a[9]
+`define _11_ARGS(a)                 `_10_ARGS(a), a[10]
+`define _12_ARGS(a)                 `_11_ARGS(a), a[11]
+`define _13_ARGS(a)                 `_12_ARGS(a), a[12]
+`define _14_ARGS(a)                 `_13_ARGS(a), a[13]
+`define _15_ARGS(a)                 `_14_ARGS(a), a[14]
+`define _16_ARGS(a)                 `_15_ARGS(a), a[15]
+`define _17_ARGS(a)                 `_16_ARGS(a), a[16]
+`define _18_ARGS(a)                 `_17_ARGS(a), a[17]
+`define _19_ARGS(a)                 `_18_ARGS(a), a[18]
+`define _20_ARGS(a)                 `_19_ARGS(a), a[19]
+`define _21_ARGS(a)                 `_20_ARGS(a), a[20]
+`define _22_ARGS(a)                 `_21_ARGS(a), a[21]
+`define _23_ARGS(a)                 `_22_ARGS(a), a[22]
+`define _24_ARGS(a)                 `_23_ARGS(a), a[23]
+`define _25_ARGS(a)                 `_24_ARGS(a), a[24]
+`define _26_ARGS(a)                 `_25_ARGS(a), a[25]
+`define _27_ARGS(a)                 `_26_ARGS(a), a[26]
+`define _28_ARGS(a)                 `_27_ARGS(a), a[27]
+`define _29_ARGS(a)                 `_28_ARGS(a), a[28]
+`define _30_ARGS(a)                 `_29_ARGS(a), a[29]
+`define _31_ARGS(a)                 `_30_ARGS(a), a[30]
+`define _32_ARGS(a)                 `_31_ARGS(a), a[31]
+`define _ADD_ARGS(a, n)             `_``n``_ARGS(a)
+`define FORMATTED_LOG_WITH_NARGS(n) $sformatf(log `_ADD_ARGS(sw_log.arg, n))
+
+interface sw_logger_if #(
+  // width of the data bus
+  parameter int unsigned DATA_WIDTH = 32
+) (
+  input logic                   clk,        // clock
+  input logic                   rst_n,      // active low reset
+  input logic                   valid,      // qualification for addr_data
+  input logic [DATA_WIDTH-1:0]  addr_data,  // addr/data written to sw_log_addr
+  output logic [DATA_WIDTH-1:0] sw_log_addr // used by external logic to qualify valid
+);
+
+`ifdef UVM
+  import uvm_pkg::*;
+  `include "uvm_macros.svh"
+`endif
+
+  // Enable signal to turn on/off logging at runtime.
+  bit enable = 1'b1;
+
+  // types
+  // image name and log file for parsing
+  string sw_name;
+  string sw_log_file;
+
+  // typedef addr / data values
+  typedef bit [DATA_WIDTH-1:0] addr_data_t;
+
+  typedef enum {
+    LogSeverityInfo,
+    LogSeverityWarning,
+    LogSeverityError,
+    LogSeverityFatal
+  } log_severity_e;
+
+  typedef enum {
+    LogVerbosityNone,
+    LogVerbosityLow,
+    LogVerbosityMedium,
+    LogVerbosityHigh,
+    LogVerbosityFull,
+    LogVerbosityDebug
+  } log_verbosity_e;
+
+  // struct to hold the complete log data
+  typedef struct {
+    string          name;       // Name of the SW image.
+    log_severity_e  severity;
+    log_verbosity_e verbosity;
+    string          file;       // Name of the C file invoking the log.
+    int             line;       // Line no in the C file from where the log originated.
+    int             nargs;      // Number of args provided to the format string.
+    addr_data_t     arg[];      // actual arg values
+    string          format;     // formatted string
+  } sw_log_t;
+
+  // signal indicating all initializations are done (this is set by calling ready() function)
+  bit _ready;
+
+  // hash of log with addr key
+  sw_log_t sw_logs[addr_data_t];
+
+  // q of values obtained from the bus
+  addr_data_t addr_data_q[$];
+
+  // function to add the log dat files
+  function automatic void set_sw_log_file(string _sw_name, string _sw_log_file);
+    if (_ready) log_fatal(.log("this function cannot be called after calling ready()"));
+    sw_name = _sw_name;
+    sw_log_file = _sw_log_file;
+  endfunction
+
+  // signal to indicate all monitor is good to go - all initializations are done
+  function automatic void ready();
+    _ready = 1'b1;
+  endfunction
+
+  /********************/
+  /* Monitoring logic */
+  /********************/
+  initial begin
+    wait(_ready);
+    if (parse_sw_log_file()) begin
+      fork
+        get_addr_data_from_bus();
+        construct_log_and_print();
+      join_none
+    end
+  end
+
+  /******************/
+  /* helper methods */
+  /******************/
+  // function that parses the log data file
+  // returns 1 if log data is avaiable, else false
+  function automatic bit parse_sw_log_file();
+    int fd;
+    fd = $fopen(sw_log_file, "r");
+    if (!fd) return 0;
+
+    while (!$feof(fd)) begin
+      string        field;
+      addr_data_t   addr;
+      sw_log_t      sw_log;
+      bit           status;
+
+      sw_log.name = sw_name;
+      status = get_sw_log_field(fd, "addr", field);
+      // We proceed to retrieving other fields only if we get the addr.
+      if (!status) break;
+      addr = field.atohex();
+      void'(get_sw_log_field(fd, "severity", field));
+      sw_log.severity = log_severity_e'(field.atoi());
+      void'(get_sw_log_field(fd, "file", field));
+      sw_log.file = field;
+      void'(get_sw_log_field(fd, "line", field));
+      sw_log.line = field.atoi();
+      void'(get_sw_log_field(fd, "nargs", field));
+      sw_log.nargs = field.atoi();
+      sw_log.arg = new[sw_log.nargs];
+      void'(get_sw_log_field(fd, "format", field));
+      sw_log.format = field;
+
+      if (sw_logs.exists(addr)) begin
+        log_warning($sformatf("Log entry for addr %0x already exists:\nOld: %p\nNew: %p",
+                              addr, sw_logs[addr], sw_log));
+      end
+      sw_logs[addr] = sw_log;
+    end
+    $fclose(fd);
+
+    // cleanup the format of all logs
+    cleanup_format();
+
+    // print parsed logs
+    foreach (sw_logs[addr]) begin
+      log_info(.verbosity(LogVerbosityHigh),
+               .log($sformatf("sw_logs[%0h] = %p", addr, sw_logs[addr])));
+    end
+
+    return (sw_logs.size() > 0);
+  endfunction
+
+  // Get the sw log fields by parsing line-by-line.
+  // The ref arg value is used to return the retrived field.
+  // Function returns the successfulness of the operation.
+  function automatic bit get_sw_log_field(int fd, string field, ref string value);
+    byte   lf    = 8'ha;
+    byte   cr    = 8'hd;
+    string match = {field, ": *"};
+
+    while (!$feof(fd)) begin
+      string line;
+      void'($fgets(line, fd));
+      // Chomp the trailing newlines.
+      while (byte'(line.getc(line.len() - 1)) inside {lf, cr}) begin
+        line = line.substr(0, line.len() - 2);
+      end
+      if (line == "") continue;
+      // check if field exists
+      if (!uvm_re_match(match, line)) begin
+        value = line.substr(match.len() - 1, line.len() - 1);
+        return 1'b1;
+      end
+      else begin
+        return 1'b0;
+      end
+    end
+    return 1'b0;
+  endfunction
+
+  // function to cleanup the string formatting
+  function automatic void cleanup_format();
+    byte cr = 8'hd;
+    byte lf = 8'ha;
+    foreach (sw_logs[addr]) begin
+      string str = sw_logs[addr].format;
+      if (str.len() >= 2) begin
+        for (int i = 0; i < str.len() - 1; i++) begin
+          // replace carriage return in the middle of the string with newline.
+          if (byte'(str.getc(i)) == cr) begin
+            str.putc(i, lf);
+          end
+        end
+      end
+      sw_logs[addr].format = str;
+    end
+  endfunction
+
+    // retrieve addr or data from the bus
+  task automatic get_addr_data_from_bus();
+    forever begin
+      @(posedge clk);
+      if (enable && valid === 1'b1 && rst_n !== 0) begin
+        addr_data_q.push_back(addr_data);
+      end
+    end
+  endtask
+
+  //construct log and print when complete data is available
+  task automatic construct_log_and_print();
+    forever begin
+      addr_data_t addr;
+      // get addr
+      wait(addr_data_q.size() > 0);
+      addr = addr_data_q.pop_front();
+
+      // lookup addr in sw_logs
+      if (sw_logs.exists(addr)) begin
+        bit rst_occurred;
+        fork
+          begin
+            fork
+              // get args
+              for (int i = 0; i < sw_logs[addr].nargs; i++) begin
+                wait(addr_data_q.size() > 0);
+                sw_logs[addr].arg[i] = addr_data_q.pop_front();
+              end
+              begin
+                // check if rst_n occurred - in that case discard and start over
+                wait(rst_n === 1'b0);
+                rst_occurred = 1'b1;
+              end
+            join_any
+            disable fork;
+          end
+        join
+        if (rst_occurred) continue;
+        print_sw_log(sw_logs[addr]);
+      end
+    end
+  endtask
+
+  // print the log captured from the SW.
+  function automatic void print_sw_log(sw_log_t sw_log);
+    string log = sw_log.format;
+
+    // construct formatted string based on args
+    case (sw_log.nargs)
+       0: log = `FORMATTED_LOG_WITH_NARGS(0);
+       1: log = `FORMATTED_LOG_WITH_NARGS(1);
+       2: log = `FORMATTED_LOG_WITH_NARGS(2);
+       3: log = `FORMATTED_LOG_WITH_NARGS(3);
+       4: log = `FORMATTED_LOG_WITH_NARGS(4);
+       5: log = `FORMATTED_LOG_WITH_NARGS(5);
+       6: log = `FORMATTED_LOG_WITH_NARGS(6);
+       7: log = `FORMATTED_LOG_WITH_NARGS(7);
+       8: log = `FORMATTED_LOG_WITH_NARGS(8);
+       9: log = `FORMATTED_LOG_WITH_NARGS(9);
+      10: log = `FORMATTED_LOG_WITH_NARGS(10);
+      11: log = `FORMATTED_LOG_WITH_NARGS(11);
+      12: log = `FORMATTED_LOG_WITH_NARGS(12);
+      13: log = `FORMATTED_LOG_WITH_NARGS(13);
+      14: log = `FORMATTED_LOG_WITH_NARGS(14);
+      15: log = `FORMATTED_LOG_WITH_NARGS(15);
+      16: log = `FORMATTED_LOG_WITH_NARGS(16);
+      17: log = `FORMATTED_LOG_WITH_NARGS(17);
+      18: log = `FORMATTED_LOG_WITH_NARGS(18);
+      19: log = `FORMATTED_LOG_WITH_NARGS(19);
+      20: log = `FORMATTED_LOG_WITH_NARGS(20);
+      21: log = `FORMATTED_LOG_WITH_NARGS(21);
+      22: log = `FORMATTED_LOG_WITH_NARGS(22);
+      23: log = `FORMATTED_LOG_WITH_NARGS(23);
+      24: log = `FORMATTED_LOG_WITH_NARGS(24);
+      25: log = `FORMATTED_LOG_WITH_NARGS(25);
+      26: log = `FORMATTED_LOG_WITH_NARGS(26);
+      27: log = `FORMATTED_LOG_WITH_NARGS(27);
+      28: log = `FORMATTED_LOG_WITH_NARGS(28);
+      29: log = `FORMATTED_LOG_WITH_NARGS(29);
+      30: log = `FORMATTED_LOG_WITH_NARGS(30);
+      31: log = `FORMATTED_LOG_WITH_NARGS(31);
+      32: log = `FORMATTED_LOG_WITH_NARGS(32);
+    default: log_fatal($sformatf("UNSUPPORTED: nargs = %0d (only 0:32 allowed)", sw_log.nargs));
+    endcase
+    sw_log.format = log;
+    print_log(sw_log);
+  endfunction
+
+  // print logs from this file.
+  function automatic void print_self_log(log_severity_e severity,
+                                         log_verbosity_e verbosity = LogVerbosityLow,
+                                         string log);
+    sw_log_t self_log;
+    self_log.name = $sformatf("sw_logger_if[%0s]", sw_name);
+    self_log.severity = severity;
+    self_log.verbosity = verbosity;
+    self_log.file = "";
+    self_log.format = log;
+    print_log(self_log);
+  endfunction
+
+  // print an info message from this file
+  function automatic void log_info(log_verbosity_e verbosity = LogVerbosityLow, string log);
+    print_self_log(.severity(LogSeverityInfo), .verbosity(verbosity), .log(log));
+  endfunction
+
+  // print a warning message from this file
+  function automatic void log_warning(string log);
+    print_self_log(.severity(LogSeverityWarning), .log(log));
+  endfunction
+
+  // print an error message from this file
+  function automatic void log_error(string log);
+    print_self_log(.severity(LogSeverityError), .log(log));
+  endfunction
+
+  // print a fatal message from this file
+  function automatic void log_fatal(string log);
+    print_self_log(.severity(LogSeverityFatal), .log(log));
+  endfunction
+
+  // UVM-agnostic print_log api that switches between system call and UVM call
+  function automatic void print_log(sw_log_t sw_log);
+    string log_header = sw_log.name;
+    if (sw_log.file != "") begin
+      log_header = {log_header, "(", sw_log.file, ":",
+                    $sformatf("%0d", sw_log.line), ")"};
+    end
+`ifdef UVM
+    begin
+      uvm_verbosity level;
+      case (sw_log.verbosity)
+        LogVerbosityNone:   level = UVM_NONE;
+        LogVerbosityLow:    level = UVM_LOW;
+        LogVerbosityMedium: level = UVM_MEDIUM;
+        LogVerbosityHigh:   level = UVM_HIGH;
+        LogVerbosityFull:   level = UVM_FULL;
+        LogVerbosityDebug:  level = UVM_DEBUG;
+        default:            level = UVM_LOW;
+      endcase
+
+      case (sw_log.severity)
+        LogSeverityInfo:    `uvm_info(log_header, sw_log.format, level)
+        LogSeverityWarning: `uvm_error(log_header, sw_log.format)
+        LogSeverityError:   `uvm_error(log_header, sw_log.format)
+        LogSeverityFatal:   `uvm_fatal(log_header, sw_log.format)
+        default:            `uvm_info(log_header, sw_log.format, level)
+      endcase
+    end
+`else
+    case (sw_log.severity)
+      LogSeverityInfo:    $info("%0t: [%0s] %0s", $time, log_header, sw_log.format);
+      LogSeverityWarning: $warning("%0t: [%0s] %0s", $time, log_header, sw_log.format);
+      LogSeverityError:   $error("%0t: [%0s] %0s", $time, log_header, sw_log.format);
+      LogSeverityFatal:   $fatal("%0t: [%0s] %0s", $time, log_header, sw_log.format);
+      default:            $info("%0t: [%0s] %0s", $time, log_header, sw_log.format);
+    endcase
+`endif
+  endfunction
+
+endinterface
+
+// undefine previously defined macros
+`undef _0_ARGS
+`undef _1_ARGS
+`undef _2_ARGS
+`undef _3_ARGS
+`undef _4_ARGS
+`undef _5_ARGS
+`undef _6_ARGS
+`undef _7_ARGS
+`undef _8_ARGS
+`undef _9_ARGS
+`undef _10_ARGS
+`undef _11_ARGS
+`undef _12_ARGS
+`undef _13_ARGS
+`undef _14_ARGS
+`undef _15_ARGS
+`undef _16_ARGS
+`undef _17_ARGS
+`undef _18_ARGS
+`undef _19_ARGS
+`undef _20_ARGS
+`undef _21_ARGS
+`undef _22_ARGS
+`undef _23_ARGS
+`undef _24_ARGS
+`undef _25_ARGS
+`undef _26_ARGS
+`undef _27_ARGS
+`undef _28_ARGS
+`undef _29_ARGS
+`undef _30_ARGS
+`undef _31_ARGS
+`undef _32_ARGS
+`undef _ADD_ARGS
+`undef FORMATTED_LOG_WITH_NARGS
diff --git a/hw/dv/sv/sw_msg_monitor_if/sw_msg_monitor_if.sv b/hw/dv/sv/sw_msg_monitor_if/sw_msg_monitor_if.sv
deleted file mode 100644
index 336f5bf..0000000
--- a/hw/dv/sv/sw_msg_monitor_if/sw_msg_monitor_if.sv
+++ /dev/null
@@ -1,511 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-// shortcuts for use in switching # of args to insert in formatted string
-`define _0_ARGS(a)
-`define _1_ARGS(a)                  , a[0]
-`define _2_ARGS(a)                  `_1_ARGS(a), a[1]
-`define _3_ARGS(a)                  `_2_ARGS(a), a[2]
-`define _4_ARGS(a)                  `_3_ARGS(a), a[3]
-`define _5_ARGS(a)                  `_4_ARGS(a), a[4]
-`define _6_ARGS(a)                  `_5_ARGS(a), a[5]
-`define _7_ARGS(a)                  `_6_ARGS(a), a[6]
-`define _8_ARGS(a)                  `_7_ARGS(a), a[7]
-`define _9_ARGS(a)                  `_8_ARGS(a), a[8]
-`define _10_ARGS(a)                 `_9_ARGS(a), a[9]
-`define _11_ARGS(a)                 `_10_ARGS(a), a[10]
-`define _12_ARGS(a)                 `_11_ARGS(a), a[11]
-`define _13_ARGS(a)                 `_12_ARGS(a), a[12]
-`define _14_ARGS(a)                 `_13_ARGS(a), a[13]
-`define _15_ARGS(a)                 `_14_ARGS(a), a[14]
-`define _16_ARGS(a)                 `_15_ARGS(a), a[15]
-`define _17_ARGS(a)                 `_16_ARGS(a), a[16]
-`define _18_ARGS(a)                 `_17_ARGS(a), a[17]
-`define _19_ARGS(a)                 `_18_ARGS(a), a[18]
-`define _20_ARGS(a)                 `_19_ARGS(a), a[19]
-`define _21_ARGS(a)                 `_20_ARGS(a), a[20]
-`define _22_ARGS(a)                 `_21_ARGS(a), a[21]
-`define _23_ARGS(a)                 `_22_ARGS(a), a[22]
-`define _24_ARGS(a)                 `_23_ARGS(a), a[23]
-`define _25_ARGS(a)                 `_24_ARGS(a), a[24]
-`define _26_ARGS(a)                 `_25_ARGS(a), a[25]
-`define _27_ARGS(a)                 `_26_ARGS(a), a[26]
-`define _28_ARGS(a)                 `_27_ARGS(a), a[27]
-`define _29_ARGS(a)                 `_28_ARGS(a), a[28]
-`define _30_ARGS(a)                 `_29_ARGS(a), a[29]
-`define _31_ARGS(a)                 `_30_ARGS(a), a[30]
-`define _32_ARGS(a)                 `_31_ARGS(a), a[31]
-`define _ADD_ARGS(a, n)             `_``n``_ARGS(a)
-`define FORMATTED_MSG_WITH_NARGS(n) $sformatf(msg `_ADD_ARGS(sw_msg.arg, n))
-
-interface sw_msg_monitor_if #(
-  // width of the data bus
-  parameter int unsigned DATA_WIDTH = 32
-) (
-  input logic                   clk,        // clock
-  input logic                   rst_n,      // active low reset
-  input logic                   valid,      // qualification for addr_data
-  input logic [DATA_WIDTH-1:0]  addr_data,  // addr/data written to sw_msg_addr
-  output logic [DATA_WIDTH-1:0] sw_msg_addr // used by external logic to qualify valid
-);
-
-  // types
-  // struct to hold sw msg data file and name
-  typedef struct {
-    string name;
-    string file;
-  } sw_msg_data_file_t;
-
-  // typedef addr / data values
-  typedef bit [DATA_WIDTH-1:0] addr_data_t;
-
-  // struct to hold the complete msg data
-  typedef struct {
-    string      msg_type;   // info, warning, error or fatal
-    string      verbosity;  // none, low, medium, high, full, debug
-    string      name;       // sw name
-    string      file;       // name of C file
-    int         line;       // line no
-    string      header;     // custom header (else its [name](file:line))
-    int         nargs;      // no of args
-    addr_data_t arg[];      // actual arg values
-    string      format;     // formatted string
-  } sw_msg_t;
-
-  // index of elements parsed from each line of msg data file
-  typedef enum {
-    AddrIndex,
-    MsgTypeIndex,
-    VerbosityIndex,
-    FileIndex,
-    LineIndex,
-    NArgsIndex,
-    FormatIndex,
-    ArgsIndex
-  } sw_msg_fields_index_e;
-
-  // msg scheme
-  typedef enum {
-    MsgSchemeNone,        // addr\msg (msg might contain additional metadata)
-    MsgSchmeCustomHeader, // addr\header\nargs\format
-    MsgSchemeDv           // addr\msg_type\verbosity\file\line\nargs\format
-  } msg_scheme_e;
-
-  // signal indicating all initializations are done (this is set by calling ready() function)
-  bit _ready;
-
-  // single char string delimiter used to segregate the msg data fields
-  // by default, this is set to '\' which is 5c in hex
-  byte delimiter = 8'h5c;
-  string delimiter_str = "";
-
-  // q of input file sources
-  sw_msg_data_file_t sw_msg_data_files[$];
-
-  // hash of msg with addr key
-  sw_msg_t sw_msgs[addr_data_t];
-
-  // q of values obtained from the bus
-  addr_data_t addr_data_q[$];
-
-  /****************************/
-  /* Initialization functions */
-  /****************************/
-  // function to set the delimiter value
-  function automatic void set_delimiter(byte value);
-    if (_ready) msg_fatal(.msg("this function cannot be called after calling ready()"));
-    delimiter = value;
-    // update the string version
-    // if delimiter is '\' (8'h5c) then the string version
-    // needs to be empty since SV treats '\' as null
-    delimiter_str.putc(0, delimiter);
-    delimiter_str = (delimiter == 8'h5c) ? "" : delimiter_str;
-  endfunction
-
-  // function to add the msg dat files
-  function automatic void add_sw_msg_data_files(string img_name, string img_file);
-    sw_msg_data_file_t sw_msg_data_file;
-    if (_ready) msg_fatal(.msg("this function cannot be called after calling ready()"));
-    sw_msg_data_file.name = img_name;
-    sw_msg_data_file.file = img_file;
-    sw_msg_data_files.push_back(sw_msg_data_file);
-  endfunction
-
-  // signal to indicate all monitor is good to go - all initializations are done
-  function automatic void ready();
-    _ready = 1'b1;
-  endfunction
-
-  /********************/
-  /* Monitoring logic */
-  /********************/
-  initial begin
-    wait(_ready);
-    if (parse_sw_msg_data_files()) begin
-      fork
-        get_addr_data_from_bus();
-        construct_msg_and_print();
-      join_none
-    end
-  end
-
-  /******************/
-  /* helper methods */
-  /******************/
-  // function that parses the msg data file
-  // returns 1 if msg data is avaiable, else false
-  function automatic bit parse_sw_msg_data_files();
-    foreach (sw_msg_data_files[i]) begin
-      int fd;
-      fd = $fopen(sw_msg_data_files[i].file, "r");
-      if (!fd) continue;
-      else begin
-        addr_data_t addr;
-
-        while (!$feof(fd)) begin
-          string       line;
-          string       fields[$];
-          int          merge_fields_idx_start = 0;
-          int          merge_fields_idx_end = 0;
-          msg_scheme_e scheme;
-
-          // read line and split into fields based on delimiter
-          void'($fgets(line, fd));
-          if (line inside {"", "\n", "\r"}) continue;
-          split_msg_data_line_to_fields(line, fields);
-
-          // print fields for debug
-          foreach (fields[i]) begin
-            msg_info(.verbosity("d"), .msg($sformatf("fields[%0d] = %0s", i, fields[i])));
-          end
-
-          // get addr (first field)
-          addr = fields[AddrIndex].atohex();
-          if (sw_msgs.exists(addr)) begin
-            msg_warning(.msg($sformatf("msg entry for addr %0x already exists:\n%p",
-                                       addr, sw_msgs[addr])));
-          end
-
-          // detect msg scheme based on fields size and values
-          // if > 7 fields AND fields[MsgTypeIndex] is either "i", "w", "e" or "f"
-          // then its DV scheme, otherwise it is something else
-          if (fields.size() >= 7) scheme = MsgSchemeDv;
-          // Note: If user adds null termination in the msg that is not supported
-          if (fields[MsgTypeIndex].tolower() inside {"i", "info"}) begin
-            sw_msgs[addr].msg_type = "i";
-          end else if (fields[MsgTypeIndex].tolower() inside {"w", "warn", "warning"}) begin
-            sw_msgs[addr].msg_type = "w";
-          end else if (fields[MsgTypeIndex].tolower() inside {"e", "err", "error"}) begin
-            sw_msgs[addr].msg_type = "e";
-          end else if (fields[MsgTypeIndex].tolower() inside {"f", "fatal"}) begin
-            sw_msgs[addr].msg_type = "f";
-          end else begin
-            // if fields size >= 4, its a msg with custom header
-            // if fields size < 4, there no scheme detected
-            if (fields.size() >= 4) scheme = MsgSchmeCustomHeader;
-            else                    scheme = MsgSchemeNone;
-          end
-
-          case (scheme)
-            MsgSchemeNone: begin
-              sw_msgs[addr].msg_type = "i";
-              sw_msgs[addr].verbosity = "n";
-              sw_msgs[addr].file = "";
-              sw_msgs[addr].line = 0;
-              sw_msgs[addr].nargs = 0;
-              sw_msgs[addr].format =  fields[1];
-              sw_msgs[addr].name = sw_msg_data_files[i].name;
-              merge_fields_idx_start = 2;
-            end
-            MsgSchmeCustomHeader: begin
-              sw_msgs[addr].verbosity = "n";
-              sw_msgs[addr].file = "";
-              sw_msgs[addr].line = 0;
-              sw_msgs[addr].header = fields[1];
-              sw_msgs[addr].nargs = fields[2].atoi();
-              sw_msgs[addr].arg = new[sw_msgs[addr].nargs];
-              sw_msgs[addr].format =  fields[3];
-              sw_msgs[addr].name = sw_msg_data_files[i].name;
-              merge_fields_idx_start = 4;
-            end
-            MsgSchemeDv: begin
-              // 7 entries: addr, type, verbosity, file, line, nargs, format
-              sw_msgs[addr].verbosity = fields[VerbosityIndex];
-              sw_msgs[addr].file = fields[FileIndex];
-              sw_msgs[addr].line = fields[LineIndex].atoi();
-              sw_msgs[addr].nargs = fields[NArgsIndex].atoi();
-              sw_msgs[addr].arg = new[sw_msgs[addr].nargs];
-              sw_msgs[addr].format = fields[FormatIndex];
-              sw_msgs[addr].name = sw_msg_data_files[i].name;
-              merge_fields_idx_start = 7;
-            end
-          endcase
-          // its possible that user might have added the delimiter in the msg itself
-          // in that case append the remaining ones back into formatted string
-          for (int i = merge_fields_idx_start; i < fields.size(); i++) begin
-            sw_msgs[addr].format = {sw_msgs[addr].format, delimiter_str, fields[i]};
-          end
-        end
-
-        // cleanup the format of all msgs
-        cleanup_format();
-
-        // print parsed msgs
-        foreach (sw_msgs[addr]) begin
-          msg_info(.verbosity("h"), .msg($sformatf("sw_msgs[%0h] = %p", addr, sw_msgs[addr])));
-        end
-      end
-      $fclose(fd);
-    end
-    return (sw_msgs.size() > 0);
-  endfunction
-
-    // split string using single character delimiter (as byte)
-  function automatic void split_msg_data_line_to_fields(input string  str,
-                                                        output string split[$]);
-    int start = 0;
-    for (int i = 0; i < str.len(); i++) begin
-      if (str.getc(i) == delimiter) begin
-        split.push_back(str.substr(start, i - 1));
-        start = i + 1;
-      end
-    end
-    // last one
-    split.push_back(str.substr(start, str.len() - 1));
-  endfunction
-
-  // function to cleanup the string formatting
-  function automatic void cleanup_format();
-    foreach (sw_msgs[addr]) begin
-      string str = sw_msgs[addr].format;
-      if (str.len() >= 2) begin
-        // replace ^M with \n and ^J also with \n (CR is not supported in SV)
-        for (int i = 0; i < str.len() - 1; i++) begin
-          if (str.getc(i) == "^" && str.getc(i + 1) inside {"M", "J"}) begin
-            str = {str.substr(0, i - 1), "\n", str.substr(i + 2, str.len() - 1)};
-            i++;
-          end
-          // replace %x, %d, %h with %0x, %0d and %0h
-          if (str.getc(i) == "%" && str.getc(i + 1) inside {"x", "h", "d"}) begin
-            str = {str.substr(0, i), "0", str.substr(i + 1, str.len() - 1)};
-            i += 2;
-          end
-        end
-      end
-      // remove the last \n added by $fgets
-      if (str.getc(str.len() - 1) == "\n") begin
-        str = str.substr(0, str.len() - 2);
-      end
-      // remove the last \n added in the print msg
-      if (str.getc(str.len() - 1) == "\n") begin
-        str = str.substr(0, str.len() - 2);
-      end
-      sw_msgs[addr].format = str;
-    end
-  endfunction
-
-    // retrieve addr or data from the bus
-  task automatic get_addr_data_from_bus();
-    forever begin
-      @(posedge clk);
-      if (valid === 1'b1 && rst_n !== 0) begin
-        addr_data_q.push_back(addr_data);
-      end
-    end
-  endtask
-
-  //construct msg and print when complete data is available
-  task automatic construct_msg_and_print();
-    forever begin
-      addr_data_t addr;
-      // get addr
-      wait(addr_data_q.size() > 0);
-      addr = addr_data_q.pop_front();
-
-      // lookup addr in sw_msgs
-      if (sw_msgs.exists(addr)) begin
-        bit rst_occurred;
-        fork
-          begin
-            fork
-              // get args
-              for (int i = 0; i < sw_msgs[addr].nargs; i++) begin
-                wait(addr_data_q.size() > 0);
-                sw_msgs[addr].arg[i] = addr_data_q.pop_front();
-              end
-              begin
-                // check if rst_n occurred - in that case discard and start over
-                wait(rst_n === 1'b0);
-                rst_occurred = 1'b1;
-              end
-            join_any
-            disable fork;
-          end
-        join
-        if (rst_occurred) continue;
-        sw_msg_print(sw_msgs[addr]);
-      end
-    end
-  endtask
-
-  // print the msg
-  function automatic void sw_msg_print(sw_msg_t sw_msg);
-    string msg_header = sw_msg.header;
-    string msg = sw_msg.format;
-
-    // if header not available, then construct from other fields: name(file:line)
-    if (msg_header == "") begin
-      msg_header = sw_msg.name;
-      if (sw_msg.file != "") begin
-        msg_header = {msg_header, "(", sw_msg.file, ":", $sformatf("%0d", sw_msg.line), ")"};
-      end
-    end
-
-    // construct formatted string based on args
-    case (sw_msg.nargs)
-       0: msg = `FORMATTED_MSG_WITH_NARGS(0);
-       1: msg = `FORMATTED_MSG_WITH_NARGS(1);
-       2: msg = `FORMATTED_MSG_WITH_NARGS(2);
-       3: msg = `FORMATTED_MSG_WITH_NARGS(3);
-       4: msg = `FORMATTED_MSG_WITH_NARGS(4);
-       5: msg = `FORMATTED_MSG_WITH_NARGS(5);
-       6: msg = `FORMATTED_MSG_WITH_NARGS(6);
-       7: msg = `FORMATTED_MSG_WITH_NARGS(7);
-       8: msg = `FORMATTED_MSG_WITH_NARGS(8);
-       9: msg = `FORMATTED_MSG_WITH_NARGS(9);
-      10: msg = `FORMATTED_MSG_WITH_NARGS(10);
-      11: msg = `FORMATTED_MSG_WITH_NARGS(11);
-      12: msg = `FORMATTED_MSG_WITH_NARGS(12);
-      13: msg = `FORMATTED_MSG_WITH_NARGS(13);
-      14: msg = `FORMATTED_MSG_WITH_NARGS(14);
-      15: msg = `FORMATTED_MSG_WITH_NARGS(15);
-      16: msg = `FORMATTED_MSG_WITH_NARGS(16);
-      17: msg = `FORMATTED_MSG_WITH_NARGS(17);
-      18: msg = `FORMATTED_MSG_WITH_NARGS(18);
-      19: msg = `FORMATTED_MSG_WITH_NARGS(19);
-      20: msg = `FORMATTED_MSG_WITH_NARGS(20);
-      21: msg = `FORMATTED_MSG_WITH_NARGS(21);
-      22: msg = `FORMATTED_MSG_WITH_NARGS(22);
-      23: msg = `FORMATTED_MSG_WITH_NARGS(23);
-      24: msg = `FORMATTED_MSG_WITH_NARGS(24);
-      25: msg = `FORMATTED_MSG_WITH_NARGS(25);
-      26: msg = `FORMATTED_MSG_WITH_NARGS(26);
-      27: msg = `FORMATTED_MSG_WITH_NARGS(27);
-      28: msg = `FORMATTED_MSG_WITH_NARGS(28);
-      29: msg = `FORMATTED_MSG_WITH_NARGS(29);
-      30: msg = `FORMATTED_MSG_WITH_NARGS(30);
-      31: msg = `FORMATTED_MSG_WITH_NARGS(31);
-      32: msg = `FORMATTED_MSG_WITH_NARGS(32);
-      default: msg_fatal("UNSUPPORTED", $sformatf("nargs = %0d (only 0:32 allowed)", sw_msg.nargs));
-    endcase
-
-    // print the msg
-    msg_print(sw_msg.msg_type, sw_msg.verbosity, msg_header, msg);
-  endfunction
-
-  // methods
-  // msg_print api that switches between system call and UVM call
-  function automatic void msg_print(string msg_type = "i",
-                                    string verbosity = "n",
-                                    string msg_header = "",
-                                    string msg);
-`ifdef UVM
-    import uvm_pkg::*;
-    `include "uvm_macros.svh"
-
-    uvm_verbosity level;
-    case (verbosity)
-      "n", "none":          level = UVM_NONE;
-      "l", "lo", "low":     level = UVM_LOW;
-      "m", "med", "medium": level = UVM_MEDIUM;
-      "h", "hi", "high":    level = UVM_HIGH;
-      "f", "full":          level = UVM_FULL;
-      "d", "dbg", "debug":  level = UVM_DEBUG;
-      default:              level = UVM_NONE;
-    endcase
-
-    // additional cleanup: if msg_header is already enclosed in [],
-    // then remove it, since uvm macros also add them
-    if (msg_header.len() >= 2) begin
-      if (msg_header[0] == "[" && msg_header[msg_header.len() - 1] == "]") begin
-        msg_header = msg_header.substr(1, msg_header.len() - 2);
-      end
-    end
-
-    case (msg_type.tolower())
-      "i", "info":            `uvm_info(msg_header, msg, level)
-      "w", "warn", "warning": `uvm_error(msg_header, msg)
-      "e", "err", "error":    `uvm_error(msg_header, msg)
-      "f", "fatal":           `uvm_fatal(msg_header, msg)
-      default:                `uvm_info(msg_header, msg, level)
-    endcase
-`else
-    case (msg_type.tolower())
-      "i", "info":            $info("%0t: %0s %0s", $time, msg_header, msg);
-      "w", "warn", "warning": $warning("%0t: %0s %0s", $time, msg_header, msg);
-      "e", "err", "error":    $error("%0t: %0s %0s", $time, msg_header, msg);
-      "f", "fatal":           $fatal("%0t: %0s %0s", $time, msg_header, msg);
-      default:                $info("%0t: %0s %0s", $time, msg_header, msg);
-    endcase
-`endif
-  endfunction
-
-  // print info msg
-  function automatic void msg_info(string verbosity = "l", string msg_header = "", string msg);
-    msg_print(.verbosity(verbosity), .msg_header(msg_header), .msg(msg));
-  endfunction
-
-  // print warning msg
-  function automatic void msg_warning(string msg_header = "", string msg);
-    msg_print(.msg_type("w"), .msg_header(msg_header), .msg(msg));
-  endfunction
-
-  // print error msg
-  function automatic void msg_error(string msg_header = "", string msg);
-    msg_print(.msg_type("e"), .msg_header(msg_header), .msg(msg));
-  endfunction
-
-  // print fatal msg
-  function automatic void msg_fatal(string msg_header = "", string msg);
-    msg_print(.msg_type("f"), .msg_header(msg_header), .msg(msg));
-  endfunction
-
-endinterface
-
-// undefine previously defined macros
-`undef _0_ARGS
-`undef _1_ARGS
-`undef _2_ARGS
-`undef _3_ARGS
-`undef _4_ARGS
-`undef _5_ARGS
-`undef _6_ARGS
-`undef _7_ARGS
-`undef _8_ARGS
-`undef _9_ARGS
-`undef _10_ARGS
-`undef _11_ARGS
-`undef _12_ARGS
-`undef _13_ARGS
-`undef _14_ARGS
-`undef _15_ARGS
-`undef _16_ARGS
-`undef _17_ARGS
-`undef _18_ARGS
-`undef _19_ARGS
-`undef _20_ARGS
-`undef _21_ARGS
-`undef _22_ARGS
-`undef _23_ARGS
-`undef _24_ARGS
-`undef _25_ARGS
-`undef _26_ARGS
-`undef _27_ARGS
-`undef _28_ARGS
-`undef _29_ARGS
-`undef _30_ARGS
-`undef _31_ARGS
-`undef _32_ARGS
-`undef _ADD_ARGS
-`undef FORMATTED_MSG_WITH_NARGS
diff --git a/hw/top_earlgrey/dv/env/chip_env.core b/hw/top_earlgrey/dv/env/chip_env.core
index dec660e..cdcbcb8 100644
--- a/hw/top_earlgrey/dv/env/chip_env.core
+++ b/hw/top_earlgrey/dv/env/chip_env.core
@@ -13,7 +13,7 @@
       - lowrisc:dv:jtag_agent
       - lowrisc:dv:spi_agent
       - lowrisc:dv:mem_bkdr_if
-      - lowrisc:dv:sw_msg_monitor_if
+      - lowrisc:dv:sw_logger_if
     files:
       - chip_env_pkg.sv
       - chip_tl_seq_item.sv: {is_include_file: true}
diff --git a/hw/top_earlgrey/dv/env/chip_env.sv b/hw/top_earlgrey/dv/env/chip_env.sv
index e9dea27..96867a1 100644
--- a/hw/top_earlgrey/dv/env/chip_env.sv
+++ b/hw/top_earlgrey/dv/env/chip_env.sv
@@ -63,9 +63,14 @@
       end
     end
 
-    if (!uvm_config_db#(sw_msg_monitor_vif)::get(this, "", "sw_msg_monitor_vif",
-                                                 cfg.sw_msg_monitor_vif)) begin
-      `uvm_fatal(`gfn, "failed to get sw_msg_monitor_vif from uvm_config_db")
+    // get the handle to the sw log monitor for available sw_types
+    foreach (cfg.sw_types[i]) begin
+      if (!uvm_config_db#(sw_logger_vif)::get(this, "",
+                                              $sformatf("sw_logger_vif[%0s]", cfg.sw_types[i]),
+                                              cfg.sw_logger_vif[cfg.sw_types[i]])) begin
+          `uvm_fatal(`gfn, $sformatf("failed to get sw_logger_vif[%0s] from uvm_config_db",
+                                     cfg.sw_types[i]))
+      end
     end
 
     // create components
diff --git a/hw/top_earlgrey/dv/env/chip_env_cfg.sv b/hw/top_earlgrey/dv/env/chip_env_cfg.sv
index bd17c65..00af76c 100644
--- a/hw/top_earlgrey/dv/env/chip_env_cfg.sv
+++ b/hw/top_earlgrey/dv/env/chip_env_cfg.sv
@@ -17,14 +17,11 @@
   virtual pins_if#(1) jtag_spi_n_vif;
   virtual pins_if#(1) bootstrap_vif;
 
-  // sw msg monitor related
-  sw_msg_monitor_vif  sw_msg_monitor_vif;
-  // below values are constants, but made variables in case some test has different requirements
-  string              rom_image         = "rom.vmem";
-  string              rom_msg_data_file = "msg_data.txt";
-  string              sw_image          = "sw.vmem";
-  string              sw_msg_data_file  = "msg_data.txt";
-  bit [TL_AW-1:0]     sw_msg_addr       = 32'h1000fff4;
+  // sw logger related
+  string sw_types[]   = '{"rom", "sw"};
+  sw_logger_vif       sw_logger_vif[string];
+  string              sw_images[string];
+  string              sw_log_files[string];
 
   // ext component cfgs
   rand uart_agent_cfg m_uart_agent_cfg;
@@ -68,9 +65,15 @@
     m_cpu_d_tl_agent_cfg = tl_agent_cfg::type_id::create("m_cpu_d_tl_agent_cfg");
     m_cpu_d_tl_agent_cfg.if_mode = dv_utils_pkg::Host;
     // initialize the mem_bkdr_if vifs we want for this chip
-    foreach(mems[mem]) begin
+    foreach (mems[mem]) begin
       mem_bkdr_vifs[mems[mem]] = null;
     end
+
+    // initialize the sw_image names and log file names
+    foreach (sw_types[i]) begin
+      sw_images[sw_types[i]] = {sw_types[i], ".vmem"};
+      sw_log_files[sw_types[i]] = {sw_types[i], "_logs.txt"};
+    end
   endfunction
 
   // ral flow is limited in terms of setting correct field access policies and reset values
diff --git a/hw/top_earlgrey/dv/env/chip_env_pkg.sv b/hw/top_earlgrey/dv/env/chip_env_pkg.sv
index 207d7d2..faf9cc9 100644
--- a/hw/top_earlgrey/dv/env/chip_env_pkg.sv
+++ b/hw/top_earlgrey/dv/env/chip_env_pkg.sv
@@ -21,11 +21,15 @@
   `include "dv_macros.svh"
 
   // local parameters and types
-  parameter         NUM_GPIOS   = 16;
+  parameter NUM_GPIOS = 16;
+
+  // SW constants
+  parameter bit [TL_AW-1:0] SW_LOG_DV_ADDR = 32'h1000fffc;
+  parameter bit [TL_AW-1:0] SW_TEST_STATUS_ADDR = 32'h1000fff8;
 
   typedef virtual pins_if #(NUM_GPIOS)  gpio_vif;
   typedef virtual mem_bkdr_if           mem_bkdr_vif;
-  typedef virtual sw_msg_monitor_if     sw_msg_monitor_vif;
+  typedef virtual sw_logger_if          sw_logger_vif;
 
   // enum to indicate cpu test pass / fail status
   typedef enum bit [15:0] {
diff --git a/hw/top_earlgrey/dv/env/seq_lib/chip_base_vseq.sv b/hw/top_earlgrey/dv/env/seq_lib/chip_base_vseq.sv
index a156585..bc9fec2 100644
--- a/hw/top_earlgrey/dv/env/seq_lib/chip_base_vseq.sv
+++ b/hw/top_earlgrey/dv/env/seq_lib/chip_base_vseq.sv
@@ -73,19 +73,20 @@
     cfg.m_uart_agent_cfg.set_baud_rate(BaudRate2Mbps);
 
     // Backdoor load memories.
-    cfg.mem_bkdr_vifs[Rom].load_mem_from_file(cfg.rom_image);
+    cfg.mem_bkdr_vifs[Rom].load_mem_from_file(cfg.sw_images["rom"]);
     cfg.mem_bkdr_vifs[FlashBank0].set_mem();
     cfg.mem_bkdr_vifs[FlashBank1].set_mem();
-
     // TODO: the location of the main execution image should be randomized for either bank in future
-    cfg.mem_bkdr_vifs[FlashBank0].load_mem_from_file(cfg.sw_image);
+    cfg.mem_bkdr_vifs[FlashBank0].load_mem_from_file(cfg.sw_images["sw"]);
     cpu_test_state = CpuTestRunning;
 
     // initialize the sw msg monitor
-    cfg.sw_msg_monitor_vif.sw_msg_addr = cfg.sw_msg_addr;
-    cfg.sw_msg_monitor_vif.add_sw_msg_data_files("rom", cfg.rom_msg_data_file);
-    cfg.sw_msg_monitor_vif.add_sw_msg_data_files("sw", cfg.sw_msg_data_file);
-    cfg.sw_msg_monitor_vif.ready();
+    foreach (cfg.sw_types[i]) begin
+      cfg.sw_logger_vif[cfg.sw_types[i]].sw_log_addr = SW_LOG_DV_ADDR;
+      cfg.sw_logger_vif[cfg.sw_types[i]].set_sw_log_file(cfg.sw_types[i],
+                                                         cfg.sw_log_files[cfg.sw_types[i]]);
+      cfg.sw_logger_vif[cfg.sw_types[i]].ready();
+    end
   endtask
 
   virtual task dut_shutdown();
diff --git a/hw/top_earlgrey/dv/tb/tb.sv b/hw/top_earlgrey/dv/tb/tb.sv
index a3ffd6c..fcb0262 100644
--- a/hw/top_earlgrey/dv/tb/tb.sv
+++ b/hw/top_earlgrey/dv/tb/tb.sv
@@ -103,19 +103,30 @@
     .IO_GP15          (gpio_pins[15])
   );
 
-  // connect sw_msg_monitor
-  bit                 sw_msg_monitor_valid;
-  bit [TL_AW-1:0]     sw_msg_monitor_sw_msg_addr;
+  // connect sw_logger_if
+  parameter string SwTypes[] = '{"rom", "sw"};
+  generate
+    for (genvar i = 0; i < 2; i++) begin: sw_logger_if_i
+      bit             sw_log_valid;
+      bit [TL_AW-1:0] sw_log_addr;
 
-  sw_msg_monitor_if sw_msg_monitor_if (
-    .clk              (`RAM_MAIN_HIER.clk_i),
-    .rst_n            (`RAM_MAIN_HIER.rst_ni),
-    .valid            (sw_msg_monitor_valid),
-    .addr_data        (`RAM_MAIN_HIER.wdata_i),
-    .sw_msg_addr      (sw_msg_monitor_sw_msg_addr)
-  );
-  assign sw_msg_monitor_valid = `RAM_MAIN_HIER.req_i && `RAM_MAIN_HIER.write_i &&
-                                (`RAM_MAIN_HIER.addr_i == sw_msg_monitor_sw_msg_addr);
+      sw_logger_if sw_logger_if (
+        .clk          (`RAM_MAIN_HIER.clk_i),
+        .rst_n        (`RAM_MAIN_HIER.rst_ni),
+        .valid        (sw_log_valid),
+        .addr_data    (`RAM_MAIN_HIER.wdata_i),
+        .sw_log_addr  (sw_log_addr)
+      );
+      // TODO: RAM only looks at addr[15:2] - need to find a better way to capture it.
+      assign sw_log_valid = `RAM_MAIN_HIER.req_i && `RAM_MAIN_HIER.write_i &&
+                            (`RAM_MAIN_HIER.addr_i == sw_log_addr[15:2]);
+
+      initial begin
+        uvm_config_db#(virtual sw_logger_if)::set(
+            null, "*.env", $sformatf("sw_logger_vif[%0s]", SwTypes[i]), sw_logger_if);
+      end
+    end
+  endgenerate
 
   // connect signals
   assign io_dps[0]  = jtag_spi_n ? jtag_tck : spi_device_sck;
@@ -175,8 +186,6 @@
         null, "*.env", "mem_bkdr_vifs[FlashBank0]", `FLASH0_MEM_HIER.flash0_mem_bkdr_if);
     uvm_config_db#(virtual mem_bkdr_if)::set(
         null, "*.env", "mem_bkdr_vifs[FlashBank1]", `FLASH1_MEM_HIER.flash1_mem_bkdr_if);
-    uvm_config_db#(virtual sw_msg_monitor_if)::set(
-        null, "*.env", "sw_msg_monitor_vif", sw_msg_monitor_if);
 
     $timeformat(-12, 0, " ps", 12);
     run_test();
diff --git a/python-requirements.txt b/python-requirements.txt
index 8d3bc1f..4407df1 100644
--- a/python-requirements.txt
+++ b/python-requirements.txt
@@ -14,6 +14,7 @@
 # Ubuntu 16.04.
 meson >= 0.51.0, != 0.53.0, <0.54.0 # minimum matches version in meson.build
 mistletoe>=0.7.2
+pyelftools
 pyftdi
 pygments
 pytest
diff --git a/sw/device/boot_rom/boot_rom.c b/sw/device/boot_rom/boot_rom.c
index fa80a72..9b98d29 100644
--- a/sw/device/boot_rom/boot_rom.c
+++ b/sw/device/boot_rom/boot_rom.c
@@ -30,7 +30,7 @@
   uart_init(kUartBaudrate);
   base_set_stdout(uart_stdout);
 
-  base_printf(chip_info);
+  LOG_INFO("%s", chip_info);
 
   int bootstrap_err = bootstrap();
   if (bootstrap_err != 0) {
diff --git a/sw/device/boot_rom/bootstrap.c b/sw/device/boot_rom/bootstrap.c
index c9c7e6e..6c488a6 100644
--- a/sw/device/boot_rom/bootstrap.c
+++ b/sw/device/boot_rom/bootstrap.c
@@ -5,6 +5,7 @@
 #include "sw/device/boot_rom/bootstrap.h"
 
 #include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/base/log.h"
 #include "sw/device/lib/common.h"
 #include "sw/device/lib/dif/dif_gpio.h"
 #include "sw/device/lib/flash_ctrl.h"
@@ -70,17 +71,12 @@
   for (;;) {
     if (spid_bytes_available() >= sizeof(f)) {
       spid_read_nb(&f, sizeof(f));
-      uart_send_str("Processing frame no: ");
-      uart_send_uint(f.hdr.frame_num, 32);
-      uart_send_str(" exp no: ");
-      uart_send_uint(expected_frame_no, 32);
-      uart_send_str("\r\n");
+      LOG_INFO("Processing frame no: %d exp no: %d", f.hdr.frame_num,
+               expected_frame_no);
 
       if (FRAME_NO(f.hdr.frame_num) == expected_frame_no) {
         if (check_frame_hash(&f)) {
-          uart_send_str("Error: detected hash mismatch on frame: ");
-          uart_send_uint(f.hdr.frame_num, 32);
-          uart_send_str("\r\n");
+          LOG_ERROR("Detected hash mismatch on frame: %d", f.hdr.frame_num);
           spid_send(ack, sizeof(ack));
           continue;
         }
@@ -110,7 +106,7 @@
       }
     }
   }
-  uart_send_str("bootstrap: DONE!\r\n");
+  LOG_INFO("bootstrap: DONE!");
   return 0;
 }
 
@@ -119,11 +115,11 @@
     return 0;
   }
   // SPI device is only initialized in bootstrap mode.
-  uart_send_str("Bootstrap requested, initialising HW...\n");
+  LOG_INFO("Bootstrap requested, initialising HW...");
   spid_init();
   flash_init_block();
 
-  uart_send_str("HW initialisation completed, waiting for SPI input...\n");
+  LOG_INFO("HW initialisation completed, waiting for SPI input...");
   int rv = bootstrap_flash();
   if (rv) {
     rv |= erase_flash();
diff --git a/sw/device/boot_rom/rom_link.ld b/sw/device/boot_rom/rom_link.ld
index 95aabdd..d0fd987 100644
--- a/sw/device/boot_rom/rom_link.ld
+++ b/sw/device/boot_rom/rom_link.ld
@@ -28,8 +28,10 @@
  */
 _boot_address = ORIGIN(rom);
 
+/* Reserving 32 bytes at the top of the RAM for test utils. */
+_test_reserved_size = 0x20;
 _heap_size = 0xe000;
-_stack_size = 0x2000;
+_stack_size = 0x2000 - _test_reserved_size;
 _stack_start = ORIGIN(ram) + _heap_size + _stack_size;
 _stack_end   = ORIGIN(ram) + _heap_size;
 _flash_start = ORIGIN(flash);
@@ -156,4 +158,33 @@
   .stabstr 0x0 (NOLOAD): {
     [.stabstr]
   }
+
+  /**
+   * The following sections are used by DV to implement logging in an
+   * alternate way, which enables simulation speed up by completely avoiding
+   * any string format processing or even the actual transmission of log data
+   * to a real peripheral.
+   *
+   * These sections are marked as dummy so that they can still be extracted
+   * using readelf or similar utilities. As such, the content in these sections
+   * is not relevant for the actual SW code and can be safely discarded.
+   */
+
+  /**
+   * The following section contains strings. Examples include file names where
+   * the log is invoked (using the __FILE__ preprocessor macro). To prevent
+   * address collisions, this must not coincide with the rodata section.
+   */
+  .logs.strings 0x80000000 (DSECT): {
+    *(.logs.strings)
+  }
+
+  /**
+   * The following section contains log fields constructed from the logs using
+   * the log_fields_t struct defined in sw/device/lib/base/log.h. The size of
+   * each log field is fixed - 20 bytes, which is used as the delimiter.
+   */
+  .logs.fields 0x0 (DSECT): {
+    *(.logs.fields)
+  }
 }
diff --git a/sw/device/exts/common/flash_link.ld b/sw/device/exts/common/flash_link.ld
index 42ec2ec..3c5c8c9 100644
--- a/sw/device/exts/common/flash_link.ld
+++ b/sw/device/exts/common/flash_link.ld
@@ -21,22 +21,25 @@
   flash(rx) : ORIGIN = 0x20000000, LENGTH = 0x100000
 }
 
+/* Reserving 32 bytes at the top of the RAM for test utils. */
+_test_reserved_size = 0x20;
+
 /**
- * The stack starts at the end of RAM and grows down. It is zeroed out so a size
- * must be provided.
+ * The stack starts at the end of RAM right after the 32 reserved bytes and grows down.
+ * It is zeroed out so a size must be provided.
  */
-_stack_size = 0x2000; /* 8kb stack */
-_stack_start = ORIGIN(ram) + LENGTH(ram);
+_stack_size = 0x2000 - _test_reserved_size; /* ~8kb stack */
+_stack_start = ORIGIN(ram) + LENGTH(ram) - _test_reserved_size;
 _stack_end = _stack_start - _stack_size;
 
 SECTIONS {
   /**
-   * The flash header. This will eventually contain other stuff, like a 
+   * The flash header. This will eventually contain other stuff, like a
    * signature, but for now it's just the entry point at offset zero.
    */
   .flash_header ORIGIN(flash): ALIGN(4) {
-    /** 
-     * NOTE:ld scripts do not provide a mechanism to refer to ENTRY, 
+    /**
+     * NOTE:ld scripts do not provide a mechanism to refer to ENTRY,
      * so we just harcode the _start symbol.
      */
     LONG(_start)
@@ -86,17 +89,17 @@
   } > flash
 
   /**
-   * "Intitial data" section, the initial values of the mutable data section 
+   * "Intitial data" section, the initial values of the mutable data section
    * initialized at runtime.
-   */ 
+   */
   .idata : ALIGN(4) {
     _data_init_start = .;
   } > flash
 
-  /** 
+  /**
    * Standard mutable data section, at the bottom of RAM. This will be
    * initialized from the .idata section at runtime by the CRT.
-   */ 
+   */
   .data ORIGIN(ram): AT(_data_init_start) ALIGN(4) {
     _data_start = .;
     __global_pointer$ = . + 2048;
@@ -143,4 +146,33 @@
   .stabstr 0x0 (NOLOAD): {
     *(.stabstr)
   }
+
+  /**
+   * The following sections are used by DV to implement logging in an
+   * alternate way, which enables simulation speed up by completely avoiding
+   * any string format processing or even the actual transmission of log data
+   * to a real peripheral.
+   *
+   * These sections are marked as dummy so that they can still be extracted
+   * using readelf or similar utilities. As such, the content in these sections
+   * is not relevant for the actual SW code and can be safely discarded.
+   */
+
+  /**
+   * The following section contains strings. Examples include file names where
+   * the log is invoked (using the __FILE__ preprocessor macro). To prevent
+   * address collisions, this must not coincide with the rodata section.
+   */
+  .logs.strings 0x80000000 (DSECT): {
+    *(.logs.strings)
+  }
+
+  /**
+   * The following section contains log fields constructed from the logs using
+   * the log_fields_t struct defined in sw/device/lib/base/log.h. The size of
+   * each log field is fixed - 20 bytes, which is used as the delimiter.
+   */
+  .logs.fields 0x10000 (DSECT): {
+    *(.logs.fields)
+  }
 }
diff --git a/sw/device/lib/base/log.c b/sw/device/lib/base/log.c
index 2c0417f..caf2573 100644
--- a/sw/device/lib/base/log.c
+++ b/sw/device/lib/base/log.c
@@ -4,10 +4,21 @@
 
 #include "sw/device/lib/base/log.h"
 
+#include "sw/device/lib/arch/device.h"
 #include "sw/device/lib/base/memory.h"
+#include "sw/device/lib/base/mmio.h"
 #include "sw/device/lib/base/print.h"
 
 /**
+ * Ensure that log_fields_t is always 20 bytes.
+ *
+ * The assertion below helps prevent inadvertant changes to the struct.
+ * Please see the description of log_fields_t in log.h for more details.
+ */
+_Static_assert(sizeof(log_fields_t) == 20,
+               "log_fields_t must always be 20 bytes.");
+
+/**
  * Converts a severity to a static string.
  */
 static const char *stringify_severity(log_severity_t severity) {
@@ -18,6 +29,8 @@
       return "W";
     case kLogSeverityError:
       return "E";
+    case kLogSeverityFatal:
+      return "F";
     default:
       return "?";
   }
@@ -66,11 +79,26 @@
  * Logs `format` and the values that following in an efficient, DV-testbench
  * specific way.
  *
- * @param severity the log severity.
- * @param format a format string, as described in print.h. This must be a string
- * literal.
+ * @param log the log_fields_t struct that holds the log fields.
+ * @param nargs the number of arguments passed to the format string.
  * @param ... format parameters matching the format string.
  */
-void base_log_internal_dv(log_severity_t severity, const char *format, ...) {
-  // Do nothing, for now.
+
+/**
+ * Indicates the fixed location in RAM for SW logging for DV.
+ * TODO: Figure aout a better place to put this.
+ */
+static const uintptr_t kSwLogDvAddr = 0x1000fffc;
+
+void base_log_internal_dv(const log_fields_t *log, int nargs, ...) {
+  mmio_region_t sw_log_dv_addr = mmio_region_from_addr(kSwLogDvAddr);
+  mmio_region_write32(sw_log_dv_addr, 0x0, (uintptr_t)log);
+
+  va_list args;
+  va_start(args, nargs);
+  for (int i = 0; i < nargs; ++i) {
+    uint32_t value = va_arg(args, uint32_t);
+    mmio_region_write32(sw_log_dv_addr, 0x0, value);
+  }
+  va_end(args);
 }
diff --git a/sw/device/lib/base/log.h b/sw/device/lib/base/log.h
index 8b2f1f0..6d12dac 100644
--- a/sw/device/lib/base/log.h
+++ b/sw/device/lib/base/log.h
@@ -9,6 +9,7 @@
 #include <stdint.h>
 
 #include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/base/macros.h"
 
 /**
  * Generic logging APIs
@@ -56,8 +57,44 @@
   kLogSeverityInfo,
   kLogSeverityWarn,
   kLogSeverityError,
+  kLogSeverityFatal,
 } log_severity_t;
 
+/**
+ * Provides additional information (metadata) pertaining to the log to aid
+ * debug.
+ *
+ * Any modification to this struct must be made with caution due to external
+ * assumptions. A post-processing script parses the ELF file and extracts the
+ * log fields. The said script uses 20-byte size as the delimiter to collect the
+ * log fields. Any changes to this struct must be accompanied with the updates
+ * to the script, located here:
+ * util/device_sw_utils/extract_sw_logs.py.
+ */
+typedef struct log_fields {
+  /**
+   * Indicates the severity of the log.
+   */
+  log_severity_t severity;
+  /**
+   * Pointer to the file name that is invoking the LOG_* methods.
+   */
+  const char *file_name;
+  /**
+   * Indicates the line number of the log.
+   */
+  uint32_t line;
+  /**
+   * Indicates the number of arguments passed to the format string. This is
+   * used only in the SW logging method for DV.
+   */
+  uint32_t nargs;
+  /**
+   * Indicates the format string.
+   */
+  const char *format;
+} log_fields_t;
+
 // Internal functions exposed only for access by macros. Their
 // real doxygen can be found in log.c.
 /**
@@ -68,16 +105,16 @@
 /**
  * Implementation detail.
  */
-void base_log_internal_dv(log_severity_t severity, const char *format, ...);
+void base_log_internal_dv(const log_fields_t *log, int nargs, ...);
 
 /**
  * Basic logging macro that all other logging macros delegate to.
  *
  * Prefer to use a LOG function with a specified severity, instead.
  *
- * @param severity a severity of type `log_severity_t`.
- * @param format a format string, as described in print.h. This must be a string
- * literal.
+ * @param _severity a severity of type `log_severity_t`.
+ * @param _format a format string, as described in print.h. This must be a
+ * string literal.
  * @param ... format parameters matching the format string.
  */
 // Currently, this simply prints directly to printf. In the future, when
@@ -87,17 +124,28 @@
 //
 // NOTE: the ##__VA_ARGS__ syntax below is a GCC/Clang extension, while
 // "" foo "" is a common C idiom to assert that a macro parameter is a string.
-#define LOG(severity, format, ...)                                       \
-  do {                                                                   \
-    /* The `false` below will eventually be replaced with a device.h     \
-       function `device_is_dv()` or similar, which determines if the     \
-       current device is a DV testbench. */                              \
-    if (false) {                                                         \
-      base_log_internal_dv(severity, "" format "", ##__VA_ARGS__);       \
-    } else {                                                             \
-      base_log_internal_core(severity, __FILE__, __LINE__, "" format "", \
-                             format, ##__VA_ARGS__);                     \
-    }                                                                    \
+#define LOG(_severity, _format, ...)                                          \
+  do {                                                                        \
+    if (kDeviceType == kDeviceSimDV) {                                        \
+      /* Put the file name in the throw away .logs.strings section. */        \
+      __attribute__(                                                          \
+          (section(".logs.strings"))) static const char kFileNameStr[] =      \
+          "" __FILE__ "";                                                     \
+      /* Construct the log fields in the throw away .logs.fields section. */  \
+      __attribute__(                                                          \
+          (section(".logs.fields"))) static const log_fields_t kLogFields = { \
+          .severity = _severity,                                              \
+          .file_name = kFileNameStr,                                          \
+          .line = __LINE__,                                                   \
+          .nargs = GET_NUM_VARIABLE_ARGS(_format, ##__VA_ARGS__),             \
+          .format = "" _format ""};                                           \
+      base_log_internal_dv(&kLogFields,                                       \
+                           GET_NUM_VARIABLE_ARGS(_format, ##__VA_ARGS__),     \
+                           ##__VA_ARGS__);                                    \
+    } else {                                                                  \
+      base_log_internal_core(_severity, __FILE__, __LINE__, "" _format "",    \
+                             _format, ##__VA_ARGS__);                         \
+    }                                                                         \
   } while (false)
 
 /**
@@ -130,4 +178,17 @@
  */
 #define LOG_ERROR(...) LOG(kLogSeverityError, __VA_ARGS__)
 
+/**
+ * Log a fatal error.
+ *
+ * @param severity a severity of type `log_severity_t`.
+ * @param format a format string, as described in print.h. This must be a string
+ * literal.
+ * @param ... format parameters matching the format string.
+ *
+ * It is the user's responsibility to follow this up with a call to `abort()` to
+ * immediately stop the execution.
+ */
+#define LOG_FATAL(...) LOG(kLogSeverityFatal, __VA_ARGS__)
+
 #endif  // OPENTITAN_SW_DEVICE_LIB_BASE_LOG_H_
diff --git a/sw/device/lib/base/macros.h b/sw/device/lib/base/macros.h
new file mode 100644
index 0000000..f96447e
--- /dev/null
+++ b/sw/device/lib/base/macros.h
@@ -0,0 +1,28 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_LIB_MACROS_H_
+#define OPENTITAN_SW_DEVICE_LIB_MACROS_H_
+
+/**
+ * Computes the number of variable args at compile-time.
+ *
+ * Currently, there is no way of knowing the number of var args at compile time.
+ * This information is needed for the 'backdoor' logging to work in DV. The
+ * following meta-programming enables us to get that information as long as the
+ * number of var args passed is <=32.
+ */
+#define GET_NTH_VARIABLE_ARG(xf, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10,  \
+                             x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, \
+                             x21, x22, x23, x24, x25, x26, x27, x28, x29, x30, \
+                             x31, n, ...)                                      \
+  n
+#define SHIFT_N_VARIABLE_ARGS(...) GET_NTH_VARIABLE_ARG(__VA_ARGS__)
+#define PASS_N_VARIABLE_ARGS(...)                                             \
+  SHIFT_N_VARIABLE_ARGS(__VA_ARGS__, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22,  \
+                        21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, \
+                        7, 6, 5, 4, 3, 2, 1, 0)
+#define GET_NUM_VARIABLE_ARGS(...) PASS_N_VARIABLE_ARGS(0, ##__VA_ARGS__)
+
+#endif  // OPENTITAN_SW_DEVICE_LIB_MACROS_H_
diff --git a/sw/device/lib/base/print.h b/sw/device/lib/base/print.h
index 94563b1..9bdca90 100644
--- a/sw/device/lib/base/print.h
+++ b/sw/device/lib/base/print.h
@@ -74,6 +74,11 @@
  * Of course, providing arguments for formatting which are incompatible with a
  * given format specifier is Undefined Behavior.
  *
+ * Note that for logging in DV, the following script updates the format
+ * specifiers supported in C above and changes them to match the SystemVerilog
+ * language semantics: util/device_sw_utils/extract_sw_logs.py
+ * It also makes fixes as needed for custom speficiers such as %z.
+ *
  * @param format the format spec.
  * @param ... values to interpolate in the format spec.
  */
diff --git a/sw/device/tests/consecutive_irqs/consecutive_irqs_test.c b/sw/device/tests/consecutive_irqs/consecutive_irqs_test.c
index 26dc0b5..98ccef4 100644
--- a/sw/device/tests/consecutive_irqs/consecutive_irqs_test.c
+++ b/sw/device/tests/consecutive_irqs/consecutive_irqs_test.c
@@ -43,11 +43,10 @@
     .data = NULL, .sink = &polled_uart_sink_func,
 };
 
-#define LOG_FATAL(...)        \
-  do {                        \
-    LOG_ERROR(__VA_ARGS__);   \
-    base_printf("FAIL!\r\n"); \
-    abort();                  \
+#define LOG_FATAL_AND_ABORT(...) \
+  do {                           \
+    LOG_FATAL(__VA_ARGS__);      \
+    abort();                     \
   } while (false)
 
 /**
@@ -82,11 +81,11 @@
       }
       break;
     default:
-      LOG_FATAL("ISR is not implemented!");
+      LOG_FATAL_AND_ABORT("ISR is not implemented!");
   }
 
   if (!dif_uart_irq_state_clear(uart, uart_irq)) {
-    LOG_FATAL("ISR failed to clear IRQ!");
+    LOG_FATAL_AND_ABORT("ISR failed to clear IRQ!");
   }
 }
 
@@ -102,19 +101,19 @@
   // Claim the IRQ by reading the Ibex specific CC register.
   dif_irq_claim_data_t claim_data;
   if (!dif_plic_irq_claim(&plic0, PLIC_TARGET, &claim_data)) {
-    LOG_FATAL("ISR is not implemented!");
+    LOG_FATAL_AND_ABORT("ISR is not implemented!");
   }
 
   // Check if the interrupted peripheral is UART.
   if (claim_data.peripheral != kDifPlicPeripheralUart) {
-    LOG_FATAL("ISR interrupted peripheral is not UART!");
+    LOG_FATAL_AND_ABORT("ISR interrupted peripheral is not UART!");
   }
   handler_uart_isr(&claim_data);
 
   // Complete the IRQ by writing the IRQ source to the Ibex specific CC
   // register.
   if (!dif_plic_irq_complete(&plic0, &claim_data)) {
-    LOG_FATAL("Unable to complete the IRQ request!");
+    LOG_FATAL_AND_ABORT("Unable to complete the IRQ request!");
   }
 }
 
diff --git a/util/device_sw_utils/__init__.py b/util/device_sw_utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/util/device_sw_utils/__init__.py
diff --git a/util/device_sw_utils/extract_sw_logs.py b/util/device_sw_utils/extract_sw_logs.py
new file mode 100755
index 0000000..fa3e11b
--- /dev/null
+++ b/util/device_sw_utils/extract_sw_logs.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python3
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+"""Script to convert logs placed in given sections into SystemVerilog-friendly
+database.
+
+The tool uses the pyelftools utility to extract the log fields from a given
+section and the strings from read only sections. It processes the log fields
+& the strings and converts them into a database. The script produces 2 outputs:
+- <name_logs.txt, which is the log database
+- <name>_rodata.txt which contains {addr: string} pairs.
+"""
+
+import argparse
+import os
+import re
+import struct
+import sys
+
+from elftools.elf import elffile
+
+# A printf statement in C code is converted into a single write to a reserved
+# address in the RAM. The value written is the address of the log_fields_t
+# struct constucted from the log. It has the following fields:
+# severity (int), 4 bytes:        0 (I), 1 (W), 2 (E), 3 (F)
+# file_name (int, ptr), 4 bytes:  Pointer to file_name string.
+# Line no (int), 4 bytes:         Line number of the log message.
+# Nargs (int), 4 bytes:           Number of arguments the format string takes.
+# format (int, ptr), 4 bytes:     Log format string.
+#
+# Total size of log_fields_t: 20 bytes.
+LOGS_FIELDS_SECTION = '.logs.fields'
+LOGS_FIELDS_SIZE = 20
+RODATA_SECTION = '.rodata'
+
+
+def cleanup_newlines(string):
+    '''Replaces newlines with a carriage return.
+
+    The reason for doing so if a neline is encountered in the middle of a
+    string, it ends up adding that newline in the output files this script
+    generates. The output of this script is consumed by a monitor written in
+    SystemVerilog (hw/dv/sv/sw_logger_if), a language with limited parsing
+    / processing support.'''
+    return re.sub(r"[\n\r]+", "\r", string).strip()
+
+
+def cleanup_format(_format):
+    '''Converts C style format specifiers to SV style.
+
+    It makes the folllowing substitutions:
+    - Change %[N]?i, %[N]?u --> %[N]?d
+    - Change %[N]?x, %[N]?p --> %[N]?h
+    - Change %[N]?X         --> %[N]?H
+
+    The below is a non-standard format specifier added in OpenTitan
+    (see sw/device/lib/base/print.c for more details). A single %z specifier
+    consumes 2 arguments instead of 1 and hence has to converted as such to
+    prevent the log monitor in SystemVerilog from throwing an error at runtime.
+    - Change %[N]?z         --> %[N]?s[%d].'''
+    _format = re.sub(r"(%-?\d*)[iu]", r"\1d", _format)
+    _format = re.sub(r"(%-?\d*)[xp]", r"\1h", _format)
+    _format = re.sub(r"(%-?\d*)X", r"\1H", _format)
+    _format = re.sub(r"(%-?\d*)z", r"\1s[%d]", _format)
+    _format = re.sub(r"%([bcodhHs])", r"%0\1", _format)
+    return cleanup_newlines(_format)
+
+
+def prune_filename(filename):
+    'This function prunes the filename to only display the hierarchy under sw/'
+    hier = "sw/device"
+    index = filename.find(hier)
+    return (filename if index == -1 else filename[index:])
+
+
+def get_addr_strings(ro_contents):
+    '''Construct {addr: string} dict from all read-only sections.
+
+    This function processes the read-only sections of the elf supplied as
+    a list of ro_content tuples comprising of base addr, size and data in bytes
+    and converts it into an {addr: string} dict which is returned.'''
+    result = {}
+    for ro_content in ro_contents:
+        str_start = 0
+        base_addr, size, data = ro_content
+        while (str_start < size):
+            str_end = data.find(b'\0', str_start)
+            # Skip if start and end is the same
+            if str_start == str_end:
+                str_start += 1
+                continue
+            # Get full string address by adding base addr to the start.
+            addr = base_addr + str_start
+            string = cleanup_newlines(data[str_start:str_end].decode(
+                'utf-8', errors='replace'))
+            if addr in result:
+                exc_msg = "Error: duplicate {addr: string} pair encountered\n"
+                exc_msg += "addr: {} string: {}\n".format(addr, result[addr])
+                exc_msg += "addr: {} string: {}\n".format(addr, string)
+                raise IndexError(exc_msg)
+            result[addr] = string
+            str_start = str_end + 1
+    return result
+
+
+def get_str_at_addr(str_addr, addr_strings):
+    '''Returns the string at the provided addr.
+
+    It may be possible that the input addr is an offset within the string.
+    If true, then it returns remainder of the string starting at the offset.'''
+    for addr in addr_strings.keys():
+        if addr <= str_addr < addr + len(addr_strings[addr]):
+            return addr_strings[addr][str_addr - addr:].strip()
+    raise KeyError("string at addr {} not found".format(str_addr))
+
+
+def extract_sw_logs(elf_file, logs_fields_section, ro_sections):
+    '''This function extracts contents from the logs fields section, and the
+    read only sections, processes them and generates a tuple of (results) -
+    log with fields and (rodata) - constant strings with their addresses.
+    '''
+    # Open the elf file.
+    with open(elf_file, 'rb') as f:
+        elf = elffile.ELFFile(f)
+        # Parse the ro sections to get {addr: string} pairs.
+        ro_contents = []
+        for ro_section in ro_sections:
+            section = elf.get_section_by_name(name=ro_section)
+            if section:
+                base_addr = int(section.header['sh_addr'])
+                size = int(section.header['sh_size'])
+                data = section.data()
+                ro_contents.append((base_addr, size, data))
+            else:
+                print("Error: {} section not found in {}".format(
+                    ro_section, elf_file))
+                sys.exit(1)
+        addr_strings = get_addr_strings(ro_contents)
+
+        # Dump the {addr: string} data.
+        rodata = ""
+        for addr in addr_strings.keys():
+            rodata += "addr: {}\n".format(hex(addr)[2:])
+            string = cleanup_newlines(addr_strings[addr])
+            rodata += "string: {}\n".format(string)
+
+        # Parse the logs fields section to extract the logs.
+        section = elf.get_section_by_name(name=logs_fields_section)
+        if section:
+            logs_base_addr = int(section.header['sh_addr'])
+            logs_size = int(section.header['sh_size'])
+            logs_data = section.data()
+        else:
+            print("Error: {} section not found in {}".format(
+                logs_fields_section, elf_file))
+            sys.exit(1)
+
+        # Dump the logs with fields.
+        result = ""
+        num_logs = logs_size // LOGS_FIELDS_SIZE
+        for i in range(num_logs):
+            start = i * LOGS_FIELDS_SIZE
+            end = start + LOGS_FIELDS_SIZE
+            severity, file_addr, line, nargs, format_addr = struct.unpack(
+                'IIIII', logs_data[start:end])
+            result += "addr: {}\n".format(hex(logs_base_addr + start)[2:])
+            result += "severity: {}\n".format(severity)
+            result += "file: {}\n".format(
+                prune_filename(get_str_at_addr(file_addr, addr_strings)))
+            result += "line: {}\n".format(line)
+            result += "nargs: {}\n".format(nargs)
+            result += "format: {}\n".format(
+                cleanup_format(get_str_at_addr(format_addr, addr_strings)))
+
+        return rodata, result
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--elf-file', '-e', required=True, help="Elf file")
+    parser.add_argument('--logs-fields-section',
+                        '-f',
+                        default=LOGS_FIELDS_SECTION,
+                        help="Elf section where log fields are written.")
+    parser.add_argument('--rodata-sections',
+                        '-r',
+                        default=[RODATA_SECTION],
+                        nargs="+",
+                        help="Elf sections with rodata.")
+    parser.add_argument('--name',
+                        '-n',
+                        required=True,
+                        help="Type of the SW elf being processed.")
+    parser.add_argument('--outdir',
+                        '-o',
+                        required=True,
+                        help="Output directory.")
+    args = parser.parse_args()
+
+    os.makedirs(args.outdir, exist_ok=True)
+    rodata, result = extract_sw_logs(args.elf_file, args.logs_fields_section,
+                                     args.rodata_sections)
+
+    outfile = os.path.join(args.outdir, args.name + "_rodata.txt")
+    with open(outfile, "w", encoding='utf-8') as f:
+        f.write(rodata.strip())
+
+    outfile = os.path.join(args.outdir, args.name + "_logs.txt")
+    with open(outfile, "w", encoding='utf-8') as f:
+        f.write(result.strip())
+
+
+if __name__ == "__main__":
+    main()