Skip to content

Commit a96b43e

Browse files
trace generator tool (#277)
This PR introduces the Trace Generator Tool along with improvements in the documentation at traces/stf_trace_archive/README.md and traces/docker_stf_trace_gen/README.md. The main goal of this tool is to create STF trace and it's metadata file for workload using docker. The trace generator tool comes with three different modes - **Macro** → uses `START_TRACE` and `STOP_TRACE` macros embedded in the workload - **Instruction Count (`insn_count`)** → traces a fixed number of instructions after skipping some - **Program Counter (`pc_count`)** → traces after a specific program counter (PC) is reached Other options included in the tool are: * **`--isa ISA`**. Instruction set architecture (e.g., `rv64imafdc`). * **`--dump`**. Create a trace file dump. * **`--pk`**. Run Spike with **pk (proxy kernel)**. This PR files follows this structure: ```text README.md # Main documentation file README.md # Trace generator documentation file src/ ├── data/ # Core data models and classes ├── converters/ # Converter classes ├── factories/ # Factory classes ├── utils/ # Utility functions and helpers └── generate_trace.py # Main CLI entry point ``` ### Quickstart 1. **Macro mode (SPIKE only)** Trace using `START_TRACE` / `STOP_TRACE` markers inside the workload: ```bash python generate_trace.py --emulator spike macro workload.elf ``` 2. **Instruction Count mode** Skip 1000 instructions, then trace 5000 instructions: ```bash python generate_trace.py --emulator qemu insn_count \ --num-instructions 5000 --start-instruction 1000 workload.elf ``` 3. **Program Counter mode (QEMU only)** Start tracing after PC `0x80000000` is hit 5 times, trace 2000 instructions: ```bash python generate_trace.py --emulator qemu pc_count \ --num-instructions 2000 --start-pc 0x80000000 --pc-threshold 5 workload.elf ``` ### Next Steps: * Integrate `utils/docker_orchestrator.py` with all docker related function created on PR #275 * Add a simpoint option, passing a simpoint file as input and generating traces from it * Validate and compare trace results generated by both Spike and QEMU * Look at including a Macro mode for the qemu plugin * Create test classes
1 parent 03e494a commit a96b43e

File tree

18 files changed

+810
-180
lines changed

18 files changed

+810
-180
lines changed

traces/docker_stf_trace_gen/Dockerfile

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
FROM ubuntu:24.04
22

33
# Set environment variables early
4-
ENV RISCV=/riscv \
5-
QEMU_PLUGINS=/qemu/build/contrib/plugins \
6-
PATH=$RISCV/bin:/opt/riscv/riscv32-elf/bin:/opt/riscv/riscv64-elf/bin:/opt/riscv/riscv32-glibc/bin:/SimPoint/bin:/qemu/build:$PATH \
7-
DEBIAN_FRONTEND=noninteractive \
8-
WORKDIR=/workspace \
9-
WORKLOADS=/workloads \
10-
OUTPUT=/output
4+
ENV RISCV=/riscv
5+
ENV QEMU_DIR=/qemu
6+
ENV QEMU_PLUGINS=/qemu/build/contrib/plugins
7+
ENV PATH=$RISCV/bin:/opt/riscv/riscv32-elf/bin:/opt/riscv/riscv64-elf/bin:/opt/riscv/riscv32-glibc/bin:/SimPoint/bin:/qemu/build:$PATH
8+
ENV DEBIAN_FRONTEND=noninteractive
9+
ENV WORKDIR=/workspace
10+
ENV WORKLOADS=/workloads
11+
ENV OUTPUT=/output
1112

1213
# Install dependencies and clean up in one layer
13-
RUN apt-get update && apt-get install -y \
14+
RUN apt update && apt install -y \
1415
autoconf \
1516
automake \
1617
autotools-dev \
@@ -53,22 +54,22 @@ RUN apt-get update && apt-get install -y \
5354
wget \
5455
zlib1g-dev \
5556
zstd \
56-
&& rm -rf /var/lib/apt/lists/* \
57-
&& apt-get clean
57+
python3-yaml
5858

5959
# Configure git for building
6060
RUN git config --global url."https://github.com/".insteadOf "[email protected]:" && \
6161
git config --global user.email "[email protected]" && \
6262
git config --global user.name "Docker Builder"
6363

6464
# Create directory structure
65-
RUN mkdir -p /workloads /output /workspace $RISCV
65+
RUN mkdir -p /output
6666

6767
# Clone repositories in RISCV directory
6868
WORKDIR $RISCV
69-
RUN git clone https://github.com/condorcomputing/condor.riscv-isa-sim.git --recurse-submodules && \
70-
git clone https://github.com/sparcians/stf_tools && \
71-
git clone https://github.com/riscv-software-src/riscv-pk.git
69+
RUN git clone https://github.com/condorcomputing/condor.riscv-isa-sim.git --recurse-submodules
70+
RUN git clone https://github.com/sparcians/stf_tools
71+
RUN git clone https://github.com/riscv-software-src/riscv-pk.git
72+
RUN git clone https://gitlab.com/ribeiro.v.silva/trace-gen.git
7273

7374
# Clone QEMU and SimPoint
7475
WORKDIR /
@@ -87,15 +88,14 @@ RUN chmod +x $RISCV/get-tool.sh && \
8788
echo "Toolchain version:" && \
8889
riscv64-unknown-linux-gnu-gcc --version 2>/dev/null || echo "Toolchain setup pending"
8990

90-
RUN mkdir -p /qemu/build
9191
# Build QEMU with plugins support
9292
WORKDIR /qemu/build
9393
RUN ../configure \
94-
--target-list=riscv32-linux-user,riscv64-linux-user,riscv32-softmmu,riscv64-softmmu \
95-
--enable-plugins \
96-
--disable-docs \
97-
--disable-gtk \
98-
--disable-sdl
94+
--target-list=riscv32-linux-user,riscv64-linux-user,riscv32-softmmu,riscv64-softmmu \
95+
--enable-plugins \
96+
--disable-docs \
97+
--disable-gtk \
98+
--disable-sdl
9999
RUN make -j$(nproc)
100100
RUN make install
101101

@@ -116,6 +116,7 @@ RUN ../configure --prefix=$RISCV/condor.riscv-isa-sim/install
116116
RUN make -j$(nproc)
117117
RUN make regress
118118
RUN make install
119+
ENV STF_DIR=$RISCV/condor.riscv-isa-sim/stf_lib
119120

120121
# Create mount points for runtime mounting
121122
# Environment and flow scripts will be mounted at runtime
@@ -127,9 +128,18 @@ RUN mkdir -p /workloads/environment /flow /outputs
127128
# - Host outputs -> /outputs
128129

129130
RUN cp $RISCV/condor.riscv-isa-sim/install/bin/spike /usr/bin/
130-
WORKDIR /workspace
131131

132+
WORKDIR $RISCV/trace-gen
133+
RUN make
134+
RUN make install
132135

133-
CMD ["/bin/bash"]
136+
WORKDIR $RISCV/riscv-pk/build
137+
RUN mkdir $RISCV/pk
138+
RUN ../configure --prefix=$RISCV/pk --host=riscv64-unknown-elf
139+
RUN make -j$(nproc)
140+
RUN make install
141+
ENV PATH=$RISCV/pk:$PATH
142+
143+
WORKDIR /workspace
134144

135-
# need to mount Volumes and show it in the documenation when runnignthis
145+
CMD ["/bin/bash"]

traces/docker_stf_trace_gen/README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,9 @@ QEMU advantage: 2.70x faster
298298
More in [doc/emulator-comparison](doc/emulator-comparison)
299299

300300

301-
### Trace Generation
302-
**Important**: QEMU and Spike use different trace formats:
303-
- **Spike**: Detailed STF (System Trace Format) traces for comprehensive analysis
304-
- **QEMU**: Simple assembly traces using `-d in_asm` output
301+
### STF Trace Generation
305302

306-
QEMU cannot generate STF traces, making it useful for running large files and basic traces, while Spike provides detailed tracing capabilities.
303+
Read the [generate trace](generate_trace.md) file for details.
307304

308305
## Documentation
309306

traces/docker_stf_trace_gen/build_workload.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import argparse
44
from pathlib import Path
55
from typing import List
6-
from utils.util import log, LogLevel, run_cmd, clean_dir, file_exists, write_file_lines
6+
from utils.util import Util, LogLevel
77
from utils.config import BoardConfig
88

99
DEFAULT_WORKLOADS = {
@@ -21,7 +21,7 @@ def __init__(self, board: str, arch: str, platform: str, bbv: bool, trace: bool)
2121
self.bbv = bbv
2222
self.trace = trace
2323
self.config = BoardConfig(board)
24-
self.bin_dir = clean_dir(Path(f"/workloads/bin/{board}"))
24+
self.bin_dir = Util.clean_dir(Path(f"/workloads/bin/{board}"))
2525
self.env_dir = Path(f"/workloads/environment/{board}")
2626
self.executables = []
2727

@@ -41,14 +41,14 @@ def _get_flags(self, config: dict, workload_path: Path, workload_type: str, benc
4141
def build_environment(self, workload: str):
4242
"""Compile environment runtime files."""
4343
if self.config.should_skip_environment(self.platform, workload):
44-
log(LogLevel.INFO, f"Skipping environment build for {self.platform}")
44+
Util.log(LogLevel.INFO, f"Skipping environment build for {self.platform}")
4545
return
4646
cc, cflags, _, _ = self._get_flags({}, workload, workload)
4747
for src in self.config.get_environment_files(workload):
4848
src_file = self.env_dir / src
4949
if src_file.exists():
5050
obj = self.env_dir / f"{Path(src).stem}.o"
51-
run_cmd([cc, "-c", *cflags, "-o", str(obj), str(src_file)])
51+
Util.run_cmd([cc, "-c", *cflags, "-o", str(obj), str(src_file)])
5252

5353
def build_common_files(self, workload_path: Path, workload_type: str) -> List[str]:
5454
"""Compile common files for riscv-tests."""
@@ -61,29 +61,29 @@ def build_common_files(self, workload_path: Path, workload_type: str) -> List[st
6161
if c_file.name in skip:
6262
continue
6363
obj = self.bin_dir / f"{c_file.stem}.o"
64-
if run_cmd([cc, "-c", *cflags, "-o", str(obj), str(c_file)]):
64+
if Util.run_cmd([cc, "-c", *cflags, "-o", str(obj), str(c_file)]):
6565
obj_files.append(str(obj))
6666
return obj_files
6767

6868
def build_benchmark(self, bench: str, workload_path: Path, workload_type: str, common_objs: List[str]):
6969
"""Compile and link a single benchmark."""
70-
log(LogLevel.INFO, f"Building {bench}")
70+
Util.log(LogLevel.INFO, f"Building {bench}")
7171
bench_dir = workload_path / ("src" if workload_type == "embench-iot" else "benchmarks") / bench
7272
if not bench_dir.exists():
73-
log(LogLevel.ERROR, f"Benchmark directory not found: {bench_dir}")
73+
Util.log(LogLevel.ERROR, f"Benchmark directory not found: {bench_dir}")
7474

7575
# Find source files
7676
source_exts = ['.c'] if workload_type == "embench-iot" else ['.c', '.S']
7777
sources = [f for ext in source_exts for f in bench_dir.glob(f"*{ext}")]
7878
if not sources:
79-
log(LogLevel.ERROR, f"No sources found for {bench}")
79+
Util.log(LogLevel.ERROR, f"No sources found for {bench}")
8080

8181
# Compile sources
8282
cc, cflags, ldflags, config = self._get_flags({}, workload_path, workload_type, bench)
8383
obj_files = []
8484
for src in sources:
8585
obj = self.bin_dir / f"{src.stem}.o"
86-
if run_cmd([cc, "-c", *cflags, "-o", str(obj), str(src)]):
86+
if Util.run_cmd([cc, "-c", *cflags, "-o", str(obj), str(src)]):
8787
obj_files.append(str(obj))
8888

8989
# Compile additional sources for embench-iot
@@ -92,7 +92,7 @@ def build_benchmark(self, bench: str, workload_path: Path, workload_type: str, c
9292
src_path = Path(src)
9393
if src_path.exists():
9494
obj = self.bin_dir / f"{src_path.stem}_support.o"
95-
if run_cmd([cc, "-c", *cflags, "-o", str(obj), str(src_path)]):
95+
if Util.run_cmd([cc, "-c", *cflags, "-o", str(obj), str(src_path)]):
9696
obj_files.append(str(obj))
9797

9898
# Link executable
@@ -104,7 +104,7 @@ def build_benchmark(self, bench: str, workload_path: Path, workload_type: str, c
104104
link_cmd.extend([f"-T{self.env_dir / config.get('linker_script', 'link.ld')}",
105105
*[str(self.env_dir / f"{Path(f).stem}.o") for f in self.config.get_environment_files(workload_type)]])
106106
link_cmd.extend(config.get('libs', []))
107-
if run_cmd(link_cmd):
107+
if Util.run_cmd(link_cmd):
108108
self.executables.append(str(exe))
109109

110110
def list_benchmarks(self, workload_path: Path, workload_type: str) -> List[str]:
@@ -118,18 +118,18 @@ def build_workload(self, workload: str, benchmark: str = None, custom_path: str
118118
"""Build specified workload or benchmark."""
119119
workload_path = Path(custom_path or DEFAULT_WORKLOADS.get(workload, DEFAULT_WORKLOADS["riscv-tests"]))
120120
workload_type = workload if workload in DEFAULT_WORKLOADS else "custom"
121-
if not file_exists(workload_path):
122-
log(LogLevel.ERROR, f"Workload path not found: {workload_path}")
121+
if not Util.file_exists(workload_path):
122+
Util.log(LogLevel.ERROR, f"Workload path not found: {workload_path}")
123123

124-
log(LogLevel.INFO, f"Building {workload} for {self.arch}/{self.platform}/{self.board}")
124+
Util.log(LogLevel.INFO, f"Building {workload} for {self.arch}/{self.platform}/{self.board}")
125125
self.build_environment(workload_type)
126126
common_objs = self.build_common_files(workload_path, workload_type)
127127
benchmarks = [benchmark] if benchmark else (["dhrystone"] if workload == "dhrystone" else self.list_benchmarks(workload_path, workload_type))
128128

129129
for bench in benchmarks:
130130
self.build_benchmark(bench, workload_path, workload_type, common_objs)
131131

132-
log(LogLevel.INFO, f"Built {len(self.executables)} executables in {self.bin_dir}")
132+
Util.log(LogLevel.INFO, f"Built {len(self.executables)} executables in {self.bin_dir}")
133133

134134
def main():
135135
"""Main entry point for building workloads."""
@@ -147,16 +147,16 @@ def main():
147147

148148
if args.list:
149149
for name, path in DEFAULT_WORKLOADS.items():
150-
if file_exists(path):
151-
log(LogLevel.INFO, f"{name}: {path}")
150+
if Util.file_exists(path):
151+
Util.log(LogLevel.INFO, f"{name}: {path}")
152152
builder = WorkloadBuilder(args.board, args.arch, args.platform, args.bbv, args.trace)
153153
benchmarks = builder.list_benchmarks(Path(path), name)
154154
if benchmarks:
155-
log(LogLevel.INFO, f" Benchmarks: {', '.join(benchmarks[:10])}{'...' if len(benchmarks) > 10 else ''}")
155+
Util.log(LogLevel.INFO, f" Benchmarks: {', '.join(benchmarks[:10])}{'...' if len(benchmarks) > 10 else ''}")
156156
return
157157

158158
if not args.workload:
159-
log(LogLevel.ERROR, "Workload required. Use --list to see available workloads")
159+
Util.log(LogLevel.ERROR, "Workload required. Use --list to see available workloads")
160160
builder = WorkloadBuilder(args.board, args.arch, args.platform, args.bbv, args.trace)
161161
builder.build_workload(args.workload, args.benchmark, args.custom_path)
162162

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from abc import ABC
2+
from typing import Any
3+
4+
5+
class BaseConverter(ABC):
6+
@staticmethod
7+
def convert(self, input: Any) -> Any:
8+
raise NotImplementedError("This method should be overridden by subclasses.")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import os
2+
from converters.base import BaseConverter
3+
from data.consts import Const
4+
5+
6+
class HostToDockerPathConverter(BaseConverter):
7+
@staticmethod
8+
def convert(path: str) -> str:
9+
parts = os.path.abspath(path).strip(os.sep).split(os.sep)
10+
parts.insert(0, Const.DOCKER_TEMP_FOLDER)
11+
return os.path.join(*parts)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass(frozen=True)
5+
class Const():
6+
DOCKER_IMAGE_NAME = "riscv-perf-model:latest"
7+
DOCKER_TEMP_FOLDER = "/host"
8+
LIBSTFMEM = "/usr/lib/libstfmem.so"
9+
STF_TOOLS = "/riscv/stf_tools/release/tools"
10+
SPKIE_PK = "/riscv/riscv-pk/build/pk"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from dataclasses import dataclass
2+
from typing import Literal, Optional, Dict, Union
3+
4+
5+
@dataclass
6+
class Author:
7+
email: str
8+
name: Optional[str] = None
9+
company: Optional[str] = None
10+
11+
12+
@dataclass
13+
class Workload:
14+
filename: str
15+
SHA256: str
16+
execution_command: Optional[str]
17+
elf_sections: Dict[str, str]
18+
19+
20+
@dataclass
21+
class IpModeInterval:
22+
ip: int
23+
ip_count: int
24+
interval_lenght: int
25+
26+
27+
@dataclass
28+
class InstructionCountModeInterval:
29+
start_instruction: int
30+
interval_lenght: int
31+
32+
33+
@dataclass
34+
class Stf:
35+
timestamp: str
36+
stf_trace_info: Dict[str, str]
37+
interval_mode: Literal["ip", "instructionCount", "macro", "fullyTrace"]
38+
interval: Optional[Union[IpModeInterval, InstructionCountModeInterval]] = None
39+
40+
41+
@dataclass
42+
class Metadata:
43+
author: Author
44+
workload: Workload
45+
stf: Stf
46+
description: Optional[str] = None

0 commit comments

Comments
 (0)