Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions hw/top_chip/dv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ For more details, see: [sim_sram_axi/README.md](./sim_sram_axi/README.md)

* **Test Status:** The SW writes Pass/Fail status to `SW_DV_TEST_STATUS_ADDR`.
The `sw_test_status_if` detects this and signals the UVM environment to terminate the simulation.
* **Logging:** The SW writes debug strings to `SW_DV_LOG_ADDR`.
The `sw_logger_if` captures these characters and prints them to the simulation log, avoiding the latency of the UART peripheral.
* **Logging:** The SW uses the `DV_LOG_INFO` / `DV_LOG_WARNING` / `DV_LOG_ERROR` / `DV_LOG_FATAL` macros (defined in `sw/device/lib/test_framework/dv_log.h`) to emit log messages.
Each call allocates a static `log_fields_t` struct (severity, file, line, argument count, format string) in the `.logs.fields` ELF section, then writes its address followed by any variadic arguments to `SW_DV_LOG_ADDR`.
At build time, `util/build_sw_collateral_for_sim.py` invokes `extract_sw_logs.py` on the firmware ELF to produce a `<name>.logs.txt` database mapping each struct address to its decoded fields.
At simulation time, `sw_logger_if` intercepts the write, looks up the struct address in the database, substitutes the arguments into the format string, and prints the fully formatted message to the simulation log.
This bypasses the slowness of the UART peripheral and requires no string formatting on the CPU.

## Simulation commands

Expand Down
54 changes: 54 additions & 0 deletions hw/top_chip/dv/env/seq_lib/top_chip_dv_dv_log_smoketest_vseq.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright lowRISC contributors (COSMIC project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

class top_chip_dv_dv_log_smoketest_vseq extends top_chip_dv_base_vseq;
`uvm_object_utils(top_chip_dv_dv_log_smoketest_vseq)

string exp_messages[] = '{
"SW-DV log smoketest starting...",
"a = 10, b = 20, c = 12",
"a + b + c = 42",
"Smoke test for the SW-DV log interface, completed successfully!"
};

typedef bit [1024*8-1:0] arg_t;

extern function new(string name = "");
extern task body();
// Per SV LRM 6.16, a string shorter than the packed type is left-padded with zeros: the last
// character sits at byte[0] (LSB) and the first at byte[N-1], with zeros above. Scan upward
// from byte[0] and prepend each character to reconstruct left-to-right order.
extern function string arg_t_to_string(arg_t val);
endclass : top_chip_dv_dv_log_smoketest_vseq


function top_chip_dv_dv_log_smoketest_vseq::new(string name = "");
super.new(name);
endfunction : new

function string top_chip_dv_dv_log_smoketest_vseq::arg_t_to_string(arg_t val);
string result = "";
for (int i = 0; i < ($bits(val) / 8); i++) begin
logic [7:0] c = val[i*8 +: 8];
if (c == 8'h00) break;
result = {$sformatf("%c", c), result};
end
return result;
endfunction : arg_t_to_string

task top_chip_dv_dv_log_smoketest_vseq::body();
string actual;

if (cfg == null) begin
set_handles();
end

// Check each expected message inline as the event fires. This ensures we sample printed_log
// in the same delta as the event.
for (int i = 0; i < exp_messages.size(); i++) begin
@(cfg.sw_logger_vif.printed_log_event);
actual = arg_t_to_string(arg_t'(cfg.sw_logger_vif.printed_log));
`DV_CHECK_STREQ(actual, exp_messages[i], $sformatf("Log message [%0d] mismatch", i))
end
endtask : body
1 change: 1 addition & 0 deletions hw/top_chip/dv/env/seq_lib/top_chip_dv_vseq_list.sv
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
`include "top_chip_dv_base_vseq.sv"
`include "top_chip_dv_uart_base_vseq.sv"
`include "top_chip_dv_gpio_smoke_vseq.sv"
`include "top_chip_dv_dv_log_smoketest_vseq.sv"
1 change: 1 addition & 0 deletions hw/top_chip/dv/env/top_chip_dv_env.core
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ filesets:
- seq_lib/top_chip_dv_base_vseq.sv: {is_include_file: true}
- seq_lib/top_chip_dv_uart_base_vseq.sv: {is_include_file: true}
- seq_lib/top_chip_dv_gpio_smoke_vseq.sv: {is_include_file: true}
- seq_lib/top_chip_dv_dv_log_smoketest_vseq.sv: {is_include_file: true}
file_type: systemVerilogSource

targets:
Expand Down
6 changes: 4 additions & 2 deletions hw/top_chip/dv/env/top_chip_dv_env_pkg.sv
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ package top_chip_dv_env_pkg;
// 50 MHz Peripheral clock
parameter int unsigned PeriClkFreq = 50_000_000;

// SW DV special write locations for test status and logging will always fit in 32-bits
// SW DV special write locations for test status and logging will always fit in 32-bits.
// Addresses must be 8-byte aligned: AxiDataWidth=64, so a 32-bit store at offset 4
// within a bus word lands in req.w.data[63:32] (upper lane), not req.w.data[31:0].
parameter bit [31:0] SW_DV_START_ADDR = 'h2002_0000;
parameter bit [31:0] SW_DV_SIZE = 'h0000_0100; // 256 bytes reserved for SW DV
parameter bit [31:0] SW_DV_TEST_STATUS_ADDR = SW_DV_START_ADDR + 'h00;
parameter bit [31:0] SW_DV_LOG_ADDR = SW_DV_START_ADDR + 'h04;
parameter bit [31:0] SW_DV_LOG_ADDR = SW_DV_START_ADDR + 'h08;

// File includes
`include "mem_clear_util.sv"
Expand Down
5 changes: 4 additions & 1 deletion hw/top_chip/dv/sim_sram_axi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ These signals are used to generate the `wr_valid` signal, qualifying valid AXI w

In the Testbench Top (`tb.sv`), higher-level verification interfaces are `bind`-ed to this signal and their virtual handle is added into the `uvm_config_db`:
1. **`sw_test_status_if`**: Monitors writes to `SW_DV_TEST_STATUS_ADDR` to detect if the test Passed or Failed.
2. **`sw_logger_if`**: Monitors writes to `SW_DV_LOG_ADDR` to capture `printf` characters and display them in the simulation log.
2. **`sw_logger_if`**: Monitors writes to `SW_DV_LOG_ADDR` to decode SW log messages.
The SW writes the address of a static `log_fields_t` struct (severity, file, line, nargs, format string) plus any variadic arguments.
A pre-generated `<name>.logs.txt` database (produced by `extract_sw_logs.py` at build time) maps each struct address to its fields;
the interface looks up the address, substitutes the arguments, and prints the formatted message to the simulation log.

## Usage

Expand Down
24 changes: 21 additions & 3 deletions hw/top_chip/dv/top_chip_sim_cfg.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@
run_opts: ["+ChipMemSRAM_image_file={run_dir}/test_framework_exception_test_cheri_sram.vmem",
"+ChipMemROM_image_file={run_dir}/bootrom.vmem"]
}
{
name: dv_log_smoketest
uvm_test_seq: top_chip_dv_dv_log_smoketest_vseq
sw_images: ["dv_log_smoketest_vanilla_sram:5" "bootrom:5"]
run_opts: ["+ChipMemSRAM_image_file={run_dir}/dv_log_smoketest_vanilla_sram.vmem",
"+ChipMemROM_image_file={run_dir}/bootrom.vmem"]
}
{
name: dv_log_smoketest_cheri
uvm_test_seq: top_chip_dv_dv_log_smoketest_vseq
sw_images: ["dv_log_smoketest_cheri_sram:5" "bootrom:5"]
run_opts: ["+ChipMemSRAM_image_file={run_dir}/dv_log_smoketest_cheri_sram.vmem",
"+ChipMemROM_image_file={run_dir}/bootrom.vmem"]
}
{
name: spi_device_smoke
uvm_test_seq: top_chip_dv_base_vseq
Expand Down Expand Up @@ -256,15 +270,15 @@
name: rv_dm_access_after_wakeup
uvm_test_seq: top_chip_dv_rv_dm_access_after_wakeup_vseq
sw_images: ["rv_dm_access_after_wakeup_test_vanilla_sram:5" "bootrom:5"]
run_opts: ["+use_jtag_dmi=1",
run_opts: ["+use_jtag_dmi=1",
"+ChipMemSRAM_image_file={run_dir}/rv_dm_access_after_wakeup_test_vanilla_sram.vmem",
"+ChipMemROM_image_file={run_dir}/bootrom.vmem"]
}
{
name: rv_dm_access_after_escalation_reset
uvm_test_seq: "top_chip_dv_rv_dm_access_after_escalation_reset_vseq"
sw_images: ["rv_dm_access_after_escalation_reset_test_vanilla_sram:5" "bootrom:5"]
run_opts: ["+use_jtag_dmi=1",
run_opts: ["+use_jtag_dmi=1",
"+ChipMemSRAM_image_file={run_dir}/rv_dm_access_after_escalation_reset_test_vanilla_sram.vmem",
"+ChipMemROM_image_file={run_dir}/bootrom.vmem"]
}
Expand Down Expand Up @@ -341,6 +355,8 @@
"rv_timer_irq_cheri",
"test_framework_exception_test",
"test_framework_exception_test_cheri",
"dv_log_smoketest",
"dv_log_smoketest_cheri",
"spi_device_smoke",
"spi_device_smoke_cheri",
"spi_host_smoke",
Expand Down Expand Up @@ -390,7 +406,9 @@
name: test_framework
tests: [
"test_framework_exception_test",
"test_framework_exception_test_cheri"
"test_framework_exception_test_cheri",
"dv_log_smoketest",
"dv_log_smoketest_cheri"
]
}
{
Expand Down
2 changes: 1 addition & 1 deletion hw/vendor/lowrisc_ip.vendor.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// Utilities.
{from: "util/basegen", to: "util/basegen"}, // Dependency of top generator.
{from: "util/design", to: "util/design"}, // Dependency of IP generator.
{from: "util/device_sw_utils", to: "util/device_sw_utils"},
{from: "util/device_sw_utils", to: "util/device_sw_utils", patch_dir: "util_device_sw_utils"},
{from: "util/ipgen.py", to: "util/ipgen.py"}, // IP generator.
{from: "util/ipgen", to: "util/ipgen"},
{from: "util/raclgen", to: "util/raclgen"}, // Dependency of top generator.
Expand Down
25 changes: 19 additions & 6 deletions hw/vendor/lowrisc_ip/util/device_sw_utils/extract_sw_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,17 +222,30 @@ def extract_sw_logs(elf_file, logs_fields_section):
logs_fields_section, elf_file))
sys.exit(1)

header_size = 4
logs_offset, = struct.unpack('I', logs_data[0:header_size])
# rv64 ELF: struct layout is 32 bytes with 8-byte pointer fields and a
# QUAD section-address header. rv32 ELF: 20 bytes, all uint32, LONG header.
is_64bit = elf.elfclass == 64
if is_64bit:
header_size = 8
entry_size = 32
header_fmt = '<Q'
entry_fmt = '<I4xQIIQ'
else:
header_size = 4
entry_size = LOGS_FIELDS_SIZE # 20
header_fmt = '<I'
entry_fmt = '<IIIII'

logs_offset, = struct.unpack(header_fmt, logs_data[0:header_size])

# Dump the logs with fields.
result = ""
num_logs = (logs_size - header_size) // LOGS_FIELDS_SIZE
num_logs = (logs_size - header_size) // entry_size
for i in range(num_logs):
start = header_size + i * LOGS_FIELDS_SIZE
end = start + LOGS_FIELDS_SIZE
start = header_size + i * entry_size
end = start + entry_size
severity, file_addr, line, nargs, format_addr = struct.unpack(
'IIIII', logs_data[start:end])
entry_fmt, logs_data[start:end])
result += "addr: {}\n".format(hex(logs_offset + start)[2:])
result += "severity: {}\n".format(severity)
result += "file: {}\n".format(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
diff --git a/extract_sw_logs.py b/extract_sw_logs.py
--- a/extract_sw_logs.py
+++ b/extract_sw_logs.py
@@ -222,17 +222,30 @@ def extract_sw_logs(elf_file, logs_fields_section):
logs_fields_section, elf_file))
sys.exit(1)

- header_size = 4
- logs_offset, = struct.unpack('I', logs_data[0:header_size])
+ # rv64 ELF: struct layout is 32 bytes with 8-byte pointer fields and a
+ # QUAD section-address header. rv32 ELF: 20 bytes, all uint32, LONG header.
+ is_64bit = elf.elfclass == 64
+ if is_64bit:
+ header_size = 8
+ entry_size = 32
+ header_fmt = '<Q'
+ entry_fmt = '<I4xQIIQ'
+ else:
+ header_size = 4
+ entry_size = LOGS_FIELDS_SIZE # 20
+ header_fmt = '<I'
+ entry_fmt = '<IIIII'
+
+ logs_offset, = struct.unpack(header_fmt, logs_data[0:header_size])

# Dump the logs with fields.
result = ""
- num_logs = (logs_size - header_size) // LOGS_FIELDS_SIZE
+ num_logs = (logs_size - header_size) // entry_size
for i in range(num_logs):
- start = header_size + i * LOGS_FIELDS_SIZE
- end = start + LOGS_FIELDS_SIZE
+ start = header_size + i * entry_size
+ end = start + entry_size
severity, file_addr, line, nargs, format_addr = struct.unpack(
- 'IIIII', logs_data[start:end])
+ entry_fmt, logs_data[start:end])
result += "addr: {}\n".format(hex(logs_offset + start)[2:])
result += "severity: {}\n".format(severity)
result += "file: {}\n".format(
11 changes: 11 additions & 0 deletions sw/device/lib/boot/mocha.ld
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ SECTIONS {
. = ALIGN(8);
} > ram

/* DV simulation log database. Each call site puts a log_fields_t entry here.
* extract_sw_logs.py reads the ELF section to produce a .logs.txt database
* consumed by sw_logger_if at simulation start. The first word is the section
* load address (header used by extract_sw_logs.py to map each entry to its
* runtime address). */
.logs.fields : {
QUAD(LOADADDR(.logs.fields));
KEEP(*(.logs.fields))
. = ALIGN(8);
} > ram

/* capability relocation entries */
/* __start___cap_relocs and __stop___cap_relocs are provided by the toolchain */
__cap_relocs : {
Expand Down
10 changes: 10 additions & 0 deletions sw/device/lib/hal/mocha.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
static const uintptr_t rom_base = 0x80000ul;
static const uintptr_t mailbox_base = 0x20010000ul;
static const uintptr_t dv_test_status_base = 0x20020000ul;
static const uintptr_t dv_log_base = 0x20020008ul;
static const uintptr_t ethernet_base = 0x30000000ul;
static const uintptr_t gpio_base = 0x40000000ul;
static const uintptr_t clkmgr_base = 0x40020000ul;
Expand Down Expand Up @@ -201,3 +202,12 @@ void *mocha_system_dv_test_status(void)
return (void *)dv_test_status_base;
#endif /* defined(__riscv_zcherihybrid) */
}

void *mocha_system_dv_log(void)
{
#if defined(__riscv_zcherihybrid)
return create_mmio_capability(dv_log_base, 0x4u);
#else /* !defined(__riscv_zcherihybrid) */
return (void *)dv_log_base;
#endif /* defined(__riscv_zcherihybrid) */
}
1 change: 1 addition & 0 deletions sw/device/lib/hal/mocha.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ plic_t mocha_system_plic(void);
void *mocha_system_dram(void);

void *mocha_system_dv_test_status(void);
void *mocha_system_dv_log(void);
2 changes: 1 addition & 1 deletion sw/device/lib/test_framework/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

set(SRCS main.c)
set(SRCS main.c dv_log.c)
set(LIBS hal startup runtime)

mocha_add_library(NAME test_framework SOURCES ${SRCS} LIBRARIES ${LIBS})
21 changes: 21 additions & 0 deletions sw/device/lib/test_framework/dv_log.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright lowRISC contributors (COSMIC project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include "test_framework/dv_log.h"
#include "hal/mmio.h"
#include "hal/mocha.h"
#include <stdarg.h>

void dv_log_write(const log_fields_t *log, ...)
{
void *port = mocha_system_dv_log();
DEV_WRITE(port, (uint32_t)(uintptr_t)log);

va_list args;
va_start(args, log);
for (uint32_t i = 0; i < log->nargs; i++) {
DEV_WRITE(port, va_arg(args, uint32_t));
}
va_end(args);
}
54 changes: 54 additions & 0 deletions sw/device/lib/test_framework/dv_log.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright lowRISC contributors (COSMIC project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <stdint.h>

// SW-to-DV logging: writes a log_fields_t entry address + arguments to
// SW_DV_LOG_ADDR; sw_logger_if reconstructs the formatted string in simulation.

// const char * for string fields: required for a valid C99 static initialiser
// (pointer-to-integer casts are not constant expressions). extract_sw_logs.py
// detects the rv64 ELF class and uses '<I4xQIIQ' (32 B) instead of '<IIIII'.
typedef struct {
uint32_t severity;
const char *file;
uint32_t line;
uint32_t nargs;
const char *format;
} log_fields_t;

// Writes the log entry address followed by each argument to SW_DV_LOG_ADDR.
void dv_log_write(const log_fields_t *log, ...);

// Count variadic arguments (0–8). DV_LOG_NARGS_SEQ_ is used directly by
// DV_LOG_ rather than via a wrapper; see comment on DV_LOG_ for why.
#define DV_LOG_NARGS_SEQ_(x1, x2, x3, x4, x5, x6, x7, x8, x9, N, ...) N

#define DV_LOG_CONCAT_(a, b) a##b
#define DV_LOG_UNIQUE_(pfx) DV_LOG_CONCAT_(pfx, __LINE__)

// Internal macro shared by all severity levels.
// DV_LOG_NARGS_SEQ_ is used inline (not via DV_LOG_NARGS wrapper) so that
// ##__VA_ARGS__ operates directly on DV_LOG_'s own __VA_ARGS__. Passing
// __VA_ARGS__ through an extra macro call causes Clang to treat the empty
// pack as one empty argument, returning N=1 instead of N=0 for 0-arg calls.
#define DV_LOG_(sev, fmt, ...) \
do { \
static const log_fields_t DV_LOG_UNIQUE_(dv_log_entry_) \
__attribute__((section(".logs.fields"), used)) = { \
.severity = (sev), \
.file = __FILE__, \
.line = (uint32_t)__LINE__, \
.nargs = DV_LOG_NARGS_SEQ_(x, ##__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0), \
.format = (fmt), \
}; \
dv_log_write(&DV_LOG_UNIQUE_(dv_log_entry_), ##__VA_ARGS__); \
} while (0)

#define DV_LOG_INFO(fmt, ...) DV_LOG_(0, fmt, ##__VA_ARGS__)
#define DV_LOG_WARNING(fmt, ...) DV_LOG_(1, fmt, ##__VA_ARGS__)
#define DV_LOG_ERROR(fmt, ...) DV_LOG_(2, fmt, ##__VA_ARGS__)
#define DV_LOG_FATAL(fmt, ...) DV_LOG_(3, fmt, ##__VA_ARGS__)
1 change: 1 addition & 0 deletions sw/device/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ set(LIBS hal runtime startup test_framework)

mocha_add_test(NAME test_framework_test SOURCES test_framework/smoketest.c LIBRARIES ${LIBS} VERILATOR_TIMEOUT 20 FPGA_TIMEOUT 10)
mocha_add_test(NAME test_framework_exception_test SOURCES test_framework/exception.c LIBRARIES ${LIBS})
mocha_add_test(NAME dv_log_smoketest SOURCES test_framework/dv_log_smoketest.c LIBRARIES ${LIBS} SKIP_VERILATOR SKIP_FPGA)
mocha_add_test(NAME axi_sram_smoketest SOURCES axi_sram/smoketest.c LIBRARIES ${LIBS})
mocha_add_test(NAME axi_sram_tag_test SOURCES axi_sram/tag_test.c LIBRARIES ${LIBS})
mocha_add_test(NAME entropy_src_smoketest SOURCES entropy_src/smoketest.c LIBRARIES ${LIBS})
Expand Down
Loading