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
38 changes: 29 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
all: workloads

MULTIHART ?= 0
PLATFORM ?= $(if $(filter 1,$(MULTIHART)),qemu,nemu)
HARTS ?= 2
LINUX_DEFAULT_DTB := $(if $(DEFAULT_DTB),$(DEFAULT_DTB),)
QEMU_DEFAULT_DTB ?= xiangshan-qemu-nemu-mem2g

ifeq ($(filter $(PLATFORM),nemu qemu),)
$(error PLATFORM must be either nemu or qemu)
endif
ifeq ($(filter 1,$(MULTIHART)),1)
ifneq ($(PLATFORM),qemu)
$(error MULTIHART=1 requires PLATFORM=qemu)
endif
endif

LINUX_DEFAULT_DTB := $(if $(DEFAULT_DTB),$(DEFAULT_DTB),$(if $(filter 1,$(MULTIHART)),,$(if $(filter qemu,$(PLATFORM)),$(QEMU_DEFAULT_DTB),)))
LINUX_FIRMWARE_FILENAME := $(if $(filter qemu,$(PLATFORM)),fw_payload.qemu.bin,fw_payload.bin)
LINUX_ROOTFS_BUILD_VARS_HASH := $(shell printf '%s\n' \
'multihart=$(if $(filter 1,$(MULTIHART)),1,0)' \
'harts=$(if $(filter 1,$(MULTIHART)),$(HARTS),1)' | sha256sum | cut -d ' ' -f 1)
LINUX_FIRMWARE_BUILD_VARS_HASH := $(shell printf '%s\n' \
'multihart=$(if $(filter 1,$(MULTIHART)),1,0)' \
'harts=$(if $(filter 1,$(MULTIHART)),$(HARTS),1)' \
'default_dtb=$(if $(DEFAULT_DTB),$(DEFAULT_DTB),xiangshan)' | sha256sum | cut -d ' ' -f 1)
'default_dtb=$(if $(LINUX_DEFAULT_DTB),$(LINUX_DEFAULT_DTB),xiangshan)' | sha256sum | cut -d ' ' -f 1)

MULTIHART_SUPPORTED_HARTS = $(shell seq 2 128)
ifeq ($(filter 1,$(MULTIHART)),1)
Expand Down Expand Up @@ -51,11 +64,12 @@ GCPT_IMPLEMENTATION := $(if $(filter 1,$(MULTIHART)),libcheckpoint,alpha)
GCPT_SOURCE_DIR := $(if $(filter 1,$(MULTIHART)),bootloader/LibCheckpoint,bootloader/LibCheckpointAlpha)
GCPT_BUILD_DIR := $(if $(filter 1,$(MULTIHART)),build/LibCheckpoint,build/LibCheckpointAlpha)
GCPT_BIN := $(GCPT_BUILD_DIR)/build/gcpt.bin
GCPT_DEFAULT_DTB ?= $(if $(DEFAULT_DTB),$(DEFAULT_DTB),xiangshan)
GCPT_DEFAULT_DTB ?= $(if $(DEFAULT_DTB),$(DEFAULT_DTB),$(if $(filter qemu,$(PLATFORM)),$(QEMU_DEFAULT_DTB),xiangshan))
GCPT_DEFAULT_DTS := dts/$(GCPT_DEFAULT_DTB).dts.in
GCPT_CONFIGURE_MODE := $(if $(filter 1,$(MULTIHART)),dual_core,normal)
GCPT_SERIAL_PORT ?= $(if $(filter 1,$(MULTIHART)),0x310b0000,)
GCPT_DTB_CONFIG_HASH := $(shell printf '%s\n' "$(GCPT_DEFAULT_DTB)" | sha256sum | cut -d ' ' -f 1)
GCPT_CONFIG_STAMP := $(if $(filter 1,$(MULTIHART)),build/LibCheckpoint-config/mode.$(GCPT_CONFIGURE_MODE),build/LibCheckpointAlpha-config/dtb.$(GCPT_DTB_CONFIG_HASH))
GCPT_CONFIG_STAMP := $(if $(filter 1,$(MULTIHART)),build/LibCheckpoint-config/mode.$(GCPT_CONFIGURE_MODE).serial-port.$(GCPT_SERIAL_PORT),build/LibCheckpointAlpha-config/dtb.$(GCPT_DTB_CONFIG_HASH))
GCPT_SOURCES := $(if $(filter 1,$(MULTIHART)),$(shell find $(GCPT_SOURCE_DIR) -path '*/.git' -prune -o -path '*/tests' -prune -o -type f -print 2>/dev/null),$(shell find $(GCPT_SOURCE_DIR) -path '*/.git' -prune -o -type f -print 2>/dev/null))
GCPT_DTS_SOURCES := $(if $(filter 1,$(MULTIHART)),,$(GCPT_DEFAULT_DTS))
$(GCPT_CONFIG_STAMP):
Expand All @@ -67,6 +81,7 @@ $(GCPT_BIN): scripts/build-gcpt.sh scripts/dts-config.sh $(TOOLCHAIN_WRAPPER) $(
GCPT_IMPLEMENTATION="$(GCPT_IMPLEMENTATION)" \
GCPT_CONFIGURE_MODE="$(GCPT_CONFIGURE_MODE)" \
GCPT_PAYLOAD_PATH="$(if $(filter 1,$(MULTIHART)),$(abspath $(SBI_BIN)),)" \
GCPT_SERIAL_PORT="$(GCPT_SERIAL_PORT)" \
DEFAULT_DTB="$(GCPT_DEFAULT_DTB)" \
DTS_TEMPLATE_DIR="$(abspath dts)" \
bash scripts/build-gcpt.sh $(GCPT_SOURCE_DIR) $(GCPT_BUILD_DIR)
Expand Down Expand Up @@ -110,19 +125,20 @@ build/linux-workloads/$(1)/firmware-vars.$(LINUX_FIRMWARE_BUILD_VARS_HASH).stamp
rm -f "$$(@D)"/firmware-vars.*.stamp
touch "$$@"

build/linux-workloads/$(1)/fw_payload.bin: $$(shell find $$(abspath dts)) $(GCPT_BIN) dts/xiangshan.dts.in scripts/build-sbi.sh scripts/dts-config.sh scripts/build-firmware-linux.sh build/linux-workloads/$(1)/rootfs.cpio $(LINUX_IMAGE) $(SBI_BIN) build/linux-workloads/$(1)/firmware-vars.$(LINUX_FIRMWARE_BUILD_VARS_HASH).stamp
build/linux-workloads/$(1)/$(LINUX_FIRMWARE_FILENAME): $$(shell find $$(abspath dts)) $(GCPT_BIN) $(GCPT_DEFAULT_DTS) scripts/build-sbi.sh scripts/dts-config.sh scripts/build-firmware-linux.sh build/linux-workloads/$(1)/rootfs.cpio $(LINUX_IMAGE) $(SBI_BIN) build/linux-workloads/$(1)/firmware-vars.$(LINUX_FIRMWARE_BUILD_VARS_HASH).stamp
CROSS_COMPILE="$$(abspath $(BUILDROOT_DIR)/output/host/bin)/riscv64-linux-" \
DTC="$$(abspath $(BUILDROOT_DIR)/output/host/bin)/dtc" \
DEFAULT_DTB="$(LINUX_DEFAULT_DTB)" \
FIRMWARE_OUTPUT="$$(abspath $$@)" \
MULTIHART="$$(MULTIHART)" \
HARTS="$$(HARTS)" \
bash scripts/build-firmware-linux.sh $(GCPT_BIN) $(SBI_BUILD_DIR) dts $(LINUX_IMAGE) build/linux-workloads/$(1)

linux/$(1): build/linux-workloads/$(1)/fw_payload.bin
linux/$(1): build/linux-workloads/$(1)/$(LINUX_FIRMWARE_FILENAME)

WORKLOAD_PHONY_TARGETS += linux/$(1)
WORKLOAD_DIRS += build/linux-workloads/$(1)
WORKLOADS_LINUX += build/linux-workloads/$(1)/fw_payload.bin
WORKLOADS_LINUX += build/linux-workloads/$(1)/$(LINUX_FIRMWARE_FILENAME)
ROOTFS += build/linux-workloads/$(1)/rootfs.cpio
DT_DIRS += build/linux-workloads/$(1)/dt
TARFLAGS += --transform='s|^build/linux-workloads/$(1)|workloads/linux/$(1)|'
Expand Down Expand Up @@ -159,13 +175,17 @@ LINUX_WORKLOAD_DIRS := $(patsubst workloads/linux/%/build.sh,%,$(wildcard worklo
AM_WORKLOAD_DIRS := $(patsubst workloads/am/%/build.sh,%,$(wildcard workloads/am/*/build.sh))
LINUX_GENERIC_WORKLOADS := $(sort $(filter-out $(LINUX_WORKLOAD_RULE_DIRS),$(LINUX_WORKLOAD_DIRS)))
AM_GENERIC_WORKLOADS := $(sort $(filter-out $(AM_WORKLOAD_RULE_DIRS),$(AM_WORKLOAD_DIRS)))
# QEMU support covers the Linux workloads with QEMU-compatible firmware flows.
QEMU_SUPPORTED_LINUX_WORKLOADS := coremark spec2006 spec2017
LINUX_ENABLED_GENERIC_WORKLOADS := $(if $(filter qemu,$(PLATFORM)),$(filter $(QEMU_SUPPORTED_LINUX_WORKLOADS),$(LINUX_GENERIC_WORKLOADS)),$(LINUX_GENERIC_WORKLOADS))
LINUX_WORKLOAD_RULE_FILES := $(if $(filter qemu,$(PLATFORM)),$(wildcard $(foreach workload,$(QEMU_SUPPORTED_LINUX_WORKLOADS),workloads/linux/$(workload)/rules.mk)),$(wildcard workloads/linux/*/rules.mk))

$(foreach workload,$(LINUX_GENERIC_WORKLOADS),$(eval $(call add_workload_linux,$(workload))))
$(foreach workload,$(LINUX_ENABLED_GENERIC_WORKLOADS),$(eval $(call add_workload_linux,$(workload))))
$(foreach workload,$(AM_GENERIC_WORKLOADS),$(eval $(call add_workload_am,$(workload))))

# Include workload-specific make rules. A workload can add its own targets by
# placing rules.mk under its workload directory.
-include $(wildcard workloads/linux/*/rules.mk)
-include $(LINUX_WORKLOAD_RULE_FILES)
-include $(wildcard workloads/am/*/rules.mk)

# Pack all workloads
Expand Down
90 changes: 71 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# NEMU Workload Builder
# NEMU/QEMU Workload Builder

This repository serves as an automated build system to build the workloads of NEMU for testing purpose. It supports two types of workloads:
This repository serves as an automated build system to build workloads for
NEMU and the QEMU NEMU machine. It supports two types of workloads:

1. **Linux workloads**: Traditional Linux-based workloads that run on top of the Linux kernel with initramfs
2. **AM workloads**: Bare-metal applications that run directly on the Abstract Machine (AM) abstraction layer
Expand All @@ -11,7 +12,7 @@ Simply run `make` under the repository. The Linux kernel, the workloads and the

For Linux workloads:

- `build/linux-workloads/workload_name/fw_payload.bin`: The all-in-one image that can be directly loaded by NEMU.
- `build/linux-workloads/workload_name/fw_payload.bin` (NEMU) or `fw_payload.qemu.bin` (QEMU): The all-in-one image that can be directly loaded by the selected machine.
- `build/linux-workloads/workload_name/rootfs.cpio`: The initramfs overlay of the workload.
- `build/linux-workloads/workload_name/dt/`: The selected device tree source and binary.

Expand All @@ -24,35 +25,71 @@ You can also build a single workload with:
- `make linux/workload_name` for a Linux workload.
- `make am/workload_name` for an AM workload.

SPEC Linux workloads can also build multi-hart rootfs images:
Single-core workloads default to NEMU. QEMU support covers CoreMark, SPEC
CPU2006, and SPEC CPU2017, which can be assembled with `PLATFORM=qemu`:

```shell
make linux/coremark PLATFORM=qemu -jN
make linux/spec2006 PLATFORM=qemu BENCH=astar INPUT=biglakes \
SPEC2006_ISO=/path/to/cpu2006.iso -jN
```

QEMU firmware is written beside the NEMU image as `fw_payload.qemu.bin`, so
switching platforms does not overwrite `fw_payload.bin` or rebuild the shared
benchmark/rootfs unnecessarily. Run the completed workload with:

```shell
QEMU_BIN=/path/to/qemu-system-riscv64 \
bash scripts/run-qemu.sh \
build/linux-workloads/coremark/fw_payload.qemu.bin
QEMU_BIN=/path/to/qemu-system-riscv64 \
bash scripts/run-qemu.sh \
build/linux-workloads/spec2006/astar_biglakes/fw_payload.qemu.bin
```

Building does not require a QEMU installation. The verified baseline is
OpenXiangShan/qemu commit `d6ef1ba720` from `fix/nemu-uart16550`; the runner
requires `QEMU_BIN` and checks for the equivalent `nemu` machine device contract
before booting. It uses TCG and treats only `Hit GOOD TRAP` as success. It
defaults to 2 GiB of RAM; set `QEMU_MEMORY` to match the selected multi-hart DTS
when needed.

CoreMark, SPEC CPU2006, and SPEC CPU2017 also support multi-hart QEMU rootfs images:

```shell
make linux/coremark MULTIHART=1 HARTS=2 \
DEFAULT_DTB=xiangshan-fpga-noAIA-2hart-mem8g-novec -jN

make linux/spec2006 BENCH=astar INPUT=biglakes \
SPEC2006_ISO=/path/to/cpu2006.iso \
MULTIHART=1 HARTS=2 \
DEFAULT_DTB=xiangshan-fpga-noAIA-2hart-mem8g -jN

make linux/spec2017 BENCH=mcf MODE=rate INPUT=ref \
SPEC2017_ISO=/path/to/cpu2017.iso \
MULTIHART=1 HARTS=2 \
DEFAULT_DTB=xiangshan-fpga-noAIA-2hart-mem8g -jN
DEFAULT_DTB=xiangshan-fpga-noAIA-2hart-mem8g-novec -jN
```

`MULTIHART=1` creates per-hart workload directories, uses `/bin/nemu-trap` to
send codes 256 and 257 before each benchmark copy and code 258 after it
returns, and requires `DEFAULT_DTB` to be set to the complete DTS basename.
Its matching template must exist in `dts/`; the build fails rather than
guessing a memory profile or generating a missing multi-hart DTS.
`MULTIHART=1` automatically selects `PLATFORM=qemu`; explicitly combining it
with `PLATFORM=nemu` is rejected. It creates per-hart workload directories,
uses `/bin/nemu-trap` to send codes 256 and 257 before each benchmark copy and
code 258 after it returns, and requires `DEFAULT_DTB` to be set to the complete
DTS basename. Its matching template must exist in `dts/`; the build fails
rather than guessing a memory profile or generating a missing multi-hart DTS.
Other Linux workload targets remain NEMU-only.

Single-core firmware uses LibCheckpointAlpha. Multi-core firmware uses
LibCheckpoint to restore QEMU multi-hart checkpoints. Set `HARTS` to match
the checkpoint and the selected device-tree template; supported multi-hart
counts are 2 through 128. All multi-hart images reserve the 131 MiB checkpoint
window `[0x80300000, 0x88600000)` and load Linux at `0x88600000`.
window `[0x80300000, 0x88600000)` and load Linux at `0x88600000`. Multi-hart
builds default `GCPT_SERIAL_PORT` to the QEMU 16550A TX register at
`0x310b0000`; the build passes it to LibCheckpoint as `CONFIG_SERIAL_PORT`.

## Workload Compatibility

Not all workloads can run on all NEMU configurations. The only workload supported by `riscv64-nutshell_defconfig` is `linux/hello`, since all other workloads require hardware floating point, which is not supported by nutshell. RVV related workloads require the vector ISA extension, and hypervisor related workloads require the hypervisor ISA extension.
Not all workloads can run on all NEMU or QEMU configurations. QEMU support is
limited to `linux/coremark`, `linux/spec2006`, and `linux/spec2017`; the only workload supported by
`riscv64-nutshell_defconfig` is `linux/hello`, since all other workloads require
hardware floating point, which is not supported by nutshell. RVV related workloads
require the vector ISA extension, and hypervisor related workloads require the
hypervisor ISA extension.

## Build Requirements

Expand All @@ -79,10 +116,16 @@ A single-core image contains:
|---------|-------------------------------|
| 0.0 MiB | LibCheckpointAlpha |
| 1.0 MiB | OpenSBI |
| 1.5 MiB | device tree |
| >=2.0 MiB | Linux kernel at the next 2 MiB-aligned address |
| 1.75 MiB | device tree |
| 2.0 MiB | Linux kernel |
| -- | initramfs containing workload |

Single-core images place the DTB at 1.75 MiB (`0x801c0000`), providing 768 KiB
for OpenSBI while leaving 256 KiB for the DTB before Linux at 2 MiB. This is an
image-packing choice, not a fixed machine address. The assembler checks both
component sizes. Multi-hart images keep their separate DTB address at
`0x80200000`.

A multi-hart image uses the fixed QEMU checkpoint layout:

| Physical address range | Size | Content |
Expand All @@ -105,6 +148,15 @@ the workload's `dt` directory and the DTB is embedded in the firmware image.

For DTS files used with gcpt, the beginning of RAM must be reserved with a `reserved-memory` node so Linux does not allocate or map the gcpt checkpoint buffer. The XiangShan FPGA DTS templates reserve the first 1 MiB of their declared DRAM for this purpose.

```shell
dd conv=notrunc bs=1024 seek=1792 if=dt/some_device.dtb of=fw_payload.bin
```

Use `fw_payload.qemu.bin` as the output file for a QEMU image. The supported
QEMU `nemu` machine exposes a no-IRQ 16550A UART at `0x310b0000`. Multi-hart
images use their separate fixed DTB offset of 2048 KiB and must retain a
matching multi-hart device tree.

OpenSBI is patched (see `bootloader/opensbi.patch`) to load the device tree from a fixed offset relative to the DRAM base. The initramfs is placed after the Linux kernel and aligned to 1 MiB.

### AM Workloads
Expand Down
2 changes: 1 addition & 1 deletion bootloader/LibCheckpoint
2 changes: 1 addition & 1 deletion bootloader/LibCheckpointAlpha
49 changes: 26 additions & 23 deletions dts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ These parameters are replaced with the corresponding values when building the wo
- `xiangshan-fpga-noAIA-mem8g-novec.dts.in`: XiangShan FPGA DTS without vector extensions, with an 8 GiB memory profile.
- `xiangshan-fpga-noAIA-mem24g-novec.dts.in`: XiangShan FPGA DTS without vector extensions, with a 24 GiB memory profile.
- `xiangshan-fpga-noAIA-mem64g-novec.dts.in`: XiangShan FPGA DTS without vector extensions, with a 64 GiB memory profile.
- `xiangshan-qemu-nemu-mem2g.dts.in`: Single-hart DTS for QEMU's `nemu`
machine, with 2 GiB of memory and a no-IRQ 16550A UART at `0x310b0000`.
- `xiangshan-qemu-nemu-mem8g.dts.in`: The same single-hart QEMU profile with
8 GiB of memory, used by SPEC2017 rate workloads.
- `xiangshan-qemu-nemu-mem24g.dts.in`: The same single-hart QEMU profile with
24 GiB of memory, used by SPEC2017 speed workloads.

Multi-hart XiangShan builds require the user to select a complete DTS basename
with `DEFAULT_DTB`; the build no longer assumes a `mem8g` suffix. For example,
Expand All @@ -38,12 +44,11 @@ and the `0x88600000` kernel address do not apply. This table shows the default
|-------------------------|----------------|------------|
| `0x80000000–0x800fffff` | 1 MiB | LibCheckpointAlpha checkpoint-recovery program; reserved as `no-map` in the DTS |
| `0x80100000` | +1 MiB | OpenSBI firmware starts here |
| `0x80180000` | +1.5 MiB | Device tree placed here by firmware assembly |
| `0x80200000` and above | >=+2 MiB | Linux kernel at the first 2 MiB-aligned address, then the MiB-aligned initramfs |
| `0x801c0000` | +1.75 MiB | Device tree placed here by firmware assembly |
| `0x80200000` and above | +2 MiB | Linux kernel image, then the MiB-aligned initramfs |

The single-core firmware packer uses `DTB_OFFSET_KB=1536`,
`SBI_OFFSET_KB=1024`, and a minimum kernel offset of 2 MiB. The kernel's
physical address is rounded up to a 2 MiB boundary. The initramfs address is
The single-core firmware packer uses `DTB_OFFSET_KB=1792`,
`SBI_OFFSET_KB=1024`, and `KERNEL_OFFSET_MB=2`. The initramfs address is
computed from the actual kernel size and starts at the next MiB boundary. The
selected single-core DTS supplies the DRAM base and capacity; no fixed 8 GiB or
64 GiB profile is imposed by this layout.
Expand Down Expand Up @@ -82,8 +87,8 @@ standard templates retain these profiles:

| Template | DRAM |
|----------|------|
| `xiangshan-fpga-noAIA-2hart-mem8g` | 8 GiB |
| `xiangshan-fpga-noAIA-2hart-mem16g` | 16 GiB |
| `xiangshan-fpga-noAIA-2hart-mem8g-novec` | 8 GiB |
| `xiangshan-fpga-noAIA-2hart-mem16g-novec` | 16 GiB |
| `xiangshan-fpga-noAIA-32hart-mem64g` | 64 GiB |

`xiangshan-fpga-noAIA-mem16g-novec` is the single-hart Host profile for
Expand All @@ -95,8 +100,8 @@ least 16 GiB, and structured ISA extension `h`.
Select the complete template basename explicitly when building, for example:

```sh
make linux/hello MULTIHART=1 HARTS=2 \
DEFAULT_DTB=xiangshan-fpga-noAIA-2hart-mem8g
make linux/coremark MULTIHART=1 HARTS=2 \
DEFAULT_DTB=xiangshan-fpga-noAIA-2hart-mem8g-novec
```

## Generate Multi-Hart XiangShan DTS
Expand All @@ -107,35 +112,33 @@ XiangShan FPGA baseline with:

```shell
python3 scripts/generate-xiangshan-multihart-dts.py \
--base dts/xiangshan-fpga-noAIA-mem8g.dts.in \
--base dts/xiangshan-fpga-noAIA-mem8g-novec.dts.in \
--harts 2 \
--output dts/xiangshan-fpga-noAIA-2hart-mem8g.dts.in
--output dts/xiangshan-fpga-noAIA-2hart-mem8g-novec.dts.in
```

Use `--memory-gib` to override the copied DRAM capacity. The checked-in 16 GiB
two-hart profile is generated with:
two-hart profile can be regenerated with:

```shell
python3 scripts/generate-xiangshan-multihart-dts.py \
--base dts/xiangshan-fpga-noAIA-mem8g.dts.in \
--base dts/xiangshan-fpga-noAIA-mem8g-novec.dts.in \
--harts 2 \
--memory-gib 16 \
--output dts/xiangshan-fpga-noAIA-2hart-mem16g.dts.in
--output dts/xiangshan-fpga-noAIA-2hart-mem16g-novec.dts.in
```

`--harts` must be in the range 2 through 128, and `--memory-gib` must be a
positive integer when specified. The generator copies the CPU node for each
hart, extends the CLINT, PLIC, and debug interrupt contexts, and applies the
NEMU UARTLITE and PLIC settings. Generated multi-hart templates reserve the
fixed 131 MiB checkpoint window `[0x80300000, 0x88600000)`.
hart, extends the CLINT, PLIC, and debug interrupt contexts, sets
`riscv,ndev = <66>`, and emits the no-IRQ 16550A console at `0x310b0000` used
by the supported QEMU `nemu` machine. Generated multi-hart templates reserve
the fixed 131 MiB checkpoint window `[0x80300000, 0x88600000)`.

The full capability block describes XiangShan hardware and is not fully
emulated by the current generic OpenSBI/QEMU `nemu` path. In particular,
DT-advertised `smrnmi` requires an OpenSBI platform
`smrnmi_handlers_init` callback, so all generic-platform DTS templates omit
it. The `nemu` timer path likewise cannot execute the DT-advertised `sstc` CSR
sequence; multi-core diagnostic runs may need to omit `sstc` and use
`sstc=false`.
emulated by the current QEMU `nemu` path. In particular, the timer path cannot
execute the DT-advertised `sstc` CSR sequence, so generated multi-hart CPU nodes
omit `sstc`.

The build does not invoke this generator automatically. Run it and review the
result before building with the corresponding `HARTS` value.
Expand Down
Loading
Loading