Skip to content

Commit b498cab

Browse files
committed
[utils] Add rv64 ELF support to extract_sw_logs.py
The upstream script assumes a 32-bit struct layout (5×uint32, 20 B, LONG header). Mocha's rv64 log_fields_t uses 8-byte const char * pointer fields, giving a 32-byte struct and an 8-byte QUAD section-address header. Add a patch that detects the ELF class at runtime and selects the correct header format, entry size, and unpack string accordingly. Wire the script into build_sw_collateral_for_sim.py so each CMake install step produces the .logs.txt and .rodata.txt files that sw_logger_if needs at simulation start. Signed-off-by: martin-velay <mvelay@lowrisc.org>
1 parent d46088d commit b498cab

4 files changed

Lines changed: 104 additions & 7 deletions

File tree

hw/vendor/lowrisc_ip.vendor.hjson

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
// Utilities.
1717
{from: "util/basegen", to: "util/basegen"}, // Dependency of top generator.
1818
{from: "util/design", to: "util/design"}, // Dependency of IP generator.
19-
{from: "util/device_sw_utils", to: "util/device_sw_utils"},
19+
{from: "util/device_sw_utils", to: "util/device_sw_utils", patch_dir: "util_device_sw_utils"},
2020
{from: "util/ipgen.py", to: "util/ipgen.py"}, // IP generator.
2121
{from: "util/ipgen", to: "util/ipgen"},
2222
{from: "util/raclgen", to: "util/raclgen"}, // Dependency of top generator.

hw/vendor/lowrisc_ip/util/device_sw_utils/extract_sw_logs.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,17 +222,30 @@ def extract_sw_logs(elf_file, logs_fields_section):
222222
logs_fields_section, elf_file))
223223
sys.exit(1)
224224

225-
header_size = 4
226-
logs_offset, = struct.unpack('I', logs_data[0:header_size])
225+
# rv64 ELF: struct layout is 32 bytes with 8-byte pointer fields and a
226+
# QUAD section-address header. rv32 ELF: 20 bytes, all uint32, LONG header.
227+
is_64bit = elf.elfclass == 64
228+
if is_64bit:
229+
header_size = 8
230+
entry_size = 32
231+
header_fmt = '<Q'
232+
entry_fmt = '<I4xQIIQ'
233+
else:
234+
header_size = 4
235+
entry_size = LOGS_FIELDS_SIZE # 20
236+
header_fmt = '<I'
237+
entry_fmt = '<IIIII'
238+
239+
logs_offset, = struct.unpack(header_fmt, logs_data[0:header_size])
227240

228241
# Dump the logs with fields.
229242
result = ""
230-
num_logs = (logs_size - header_size) // LOGS_FIELDS_SIZE
243+
num_logs = (logs_size - header_size) // entry_size
231244
for i in range(num_logs):
232-
start = header_size + i * LOGS_FIELDS_SIZE
233-
end = start + LOGS_FIELDS_SIZE
245+
start = header_size + i * entry_size
246+
end = start + entry_size
234247
severity, file_addr, line, nargs, format_addr = struct.unpack(
235-
'IIIII', logs_data[start:end])
248+
entry_fmt, logs_data[start:end])
236249
result += "addr: {}\n".format(hex(logs_offset + start)[2:])
237250
result += "severity: {}\n".format(severity)
238251
result += "file: {}\n".format(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
diff --git a/extract_sw_logs.py b/extract_sw_logs.py
2+
--- a/extract_sw_logs.py
3+
+++ b/extract_sw_logs.py
4+
@@ -222,17 +222,30 @@ def extract_sw_logs(elf_file, logs_fields_section):
5+
logs_fields_section, elf_file))
6+
sys.exit(1)
7+
8+
- header_size = 4
9+
- logs_offset, = struct.unpack('I', logs_data[0:header_size])
10+
+ # rv64 ELF: struct layout is 32 bytes with 8-byte pointer fields and a
11+
+ # QUAD section-address header. rv32 ELF: 20 bytes, all uint32, LONG header.
12+
+ is_64bit = elf.elfclass == 64
13+
+ if is_64bit:
14+
+ header_size = 8
15+
+ entry_size = 32
16+
+ header_fmt = '<Q'
17+
+ entry_fmt = '<I4xQIIQ'
18+
+ else:
19+
+ header_size = 4
20+
+ entry_size = LOGS_FIELDS_SIZE # 20
21+
+ header_fmt = '<I'
22+
+ entry_fmt = '<IIIII'
23+
+
24+
+ logs_offset, = struct.unpack(header_fmt, logs_data[0:header_size])
25+
26+
# Dump the logs with fields.
27+
result = ""
28+
- num_logs = (logs_size - header_size) // LOGS_FIELDS_SIZE
29+
+ num_logs = (logs_size - header_size) // entry_size
30+
for i in range(num_logs):
31+
- start = header_size + i * LOGS_FIELDS_SIZE
32+
- end = start + LOGS_FIELDS_SIZE
33+
+ start = header_size + i * entry_size
34+
+ end = start + entry_size
35+
severity, file_addr, line, nargs, format_addr = struct.unpack(
36+
- 'IIIII', logs_data[start:end])
37+
+ entry_fmt, logs_data[start:end])
38+
result += "addr: {}\n".format(hex(logs_offset + start)[2:])
39+
result += "severity: {}\n".format(severity)
40+
result += "file: {}\n".format(

util/build_sw_collateral_for_sim.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,46 @@
55

66
import argparse
77
import subprocess
8+
import sys
89
from pathlib import Path
910

11+
# Path to the OpenTitan-derived script that generates the .logs.txt and
12+
# .rodata.txt database files consumed by the DV sw_logger_if monitor.
13+
_EXTRACT_SW_LOGS = (
14+
Path(__file__).parent.parent /
15+
"hw" / "vendor" / "lowrisc_ip" / "util" / "device_sw_utils" /
16+
"extract_sw_logs.py"
17+
)
18+
19+
20+
def _extract_logs(elf_file: Path, out_dir: Path) -> None:
21+
"""Run extract_sw_logs.py to generate the DV logger database files.
22+
23+
Generates <target>.logs.txt and <target>.rodata.txt in out_dir.
24+
The sw_logger_if monitor reads these at simulation start to decode
25+
printf-style log messages written by firmware to SW_DV_LOG_ADDR.
26+
27+
A missing .logs.fields ELF section is non-fatal: it means the SW image
28+
was built without logging support, so we emit a warning and continue.
29+
"""
30+
if not _EXTRACT_SW_LOGS.exists():
31+
print(f"Warning: extract_sw_logs.py not found at {_EXTRACT_SW_LOGS}, "
32+
"skipping DV log database generation.")
33+
return
34+
35+
name = elf_file.stem if elf_file.suffix else elf_file.name
36+
cmd = [
37+
sys.executable, str(_EXTRACT_SW_LOGS),
38+
"--elf-file", str(elf_file),
39+
"--name", name,
40+
"--outdir", str(out_dir),
41+
]
42+
result = subprocess.run(cmd, capture_output=True)
43+
if result.returncode != 0:
44+
stderr = result.stderr.decode(errors="replace").strip()
45+
print(f"Warning: DV log extraction skipped for '{name}' "
46+
f"(no .logs.fields section or parse error): {stderr}")
47+
1048

1149
def generate(args) -> None:
1250
"""Build with cmake"""
@@ -26,6 +64,12 @@ def generate(args) -> None:
2664
cmd = [*install_cmd, target]
2765
subprocess.run(cmd, capture_output=False, check=True)
2866

67+
# Generate .logs.txt / .rodata.txt for the sw_logger_if DV monitor.
68+
# The ELF is installed without an extension (CMake default).
69+
elf_file = out_dir / target
70+
if elf_file.exists():
71+
_extract_logs(elf_file, out_dir)
72+
2973
print("Finished")
3074

3175

0 commit comments

Comments
 (0)