Skip to content
Merged
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
20 changes: 20 additions & 0 deletions VX_types.toml
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,26 @@ VX_CSR_MPM_OCACHE_BANK_ST_H = 0xB8B
VX_CSR_MPM_OCACHE_MSHR_ST = 0xB0C # MSHR stalls
VX_CSR_MPM_OCACHE_MSHR_ST_H = 0xB8C

[csr_mpm_rtu]
# PERF: ray-tracing unit (per-socket; summed across the socket's RTU cores)
VX_CSR_MPM_RTU_RAYS = 0xB03 # rays traced (active lanes of the trace instructions)
VX_CSR_MPM_RTU_RAYS_H = 0xB83
VX_CSR_MPM_RTU_HITS = 0xB04 # rays whose final result is a hit
VX_CSR_MPM_RTU_HITS_H = 0xB84
VX_CSR_MPM_RTU_BOX_TESTS = 0xB05 # ray/box intersection tests
VX_CSR_MPM_RTU_BOX_TESTS_H = 0xB85
VX_CSR_MPM_RTU_TRI_TESTS = 0xB06 # ray/triangle intersection tests
VX_CSR_MPM_RTU_TRI_TESTS_H = 0xB86
VX_CSR_MPM_RTU_CALLBACKS = 0xB07 # any-hit and intersection candidates returned to the shader
VX_CSR_MPM_RTU_CALLBACKS_H = 0xB87
VX_CSR_MPM_RTU_BUSY = 0xB08 # cycles with a ray in flight
VX_CSR_MPM_RTU_BUSY_H = 0xB88
# PERF: ray-tracing cache
VX_CSR_MPM_RTCACHE_READS = 0xB09 # total reads
VX_CSR_MPM_RTCACHE_READS_H = 0xB89
VX_CSR_MPM_RTCACHE_MISS_R = 0xB0A # read misses
VX_CSR_MPM_RTCACHE_MISS_R_H = 0xB8A

# XLEN is an environment-supplied build axis (configure exports it); the
# [memmap]/[vm] exprs above reference it. Declared as a [[builtin]] so the
# dependency is explicit and typed; defaults to 0 (→ 32-bit) when unset.
Expand Down
8 changes: 4 additions & 4 deletions ci/perfetto.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# limitations under the License.

"""
vortex_perfetto.py
perfetto.py

Convert Vortex GPU RTL or SimX log traces into a Perfetto-compatible trace
(Chrome Trace JSON).
Expand All @@ -30,13 +30,13 @@

Usage examples
# SimX (default), compressed:
python3 vortex_perfetto.py run_simx.log -c -o vortex.perfetto.json.gz
python3 ci/perfetto.py run_simx.log -c -o vortex.perfetto.json.gz

# RTL sim:
python3 vortex_perfetto.py run.log -t rtlsim -c -o vortex.perfetto.json.gz
python3 ci/perfetto.py run.log -t rtlsim -c -o vortex.perfetto.json.gz

# Limit export to a cycle window:
python3 vortex_perfetto.py run.log -t rtlsim --cycle-min 10000 --cycle-max 20000 -c -o win.json.gz
python3 ci/perfetto.py run.log -t rtlsim --cycle-min 10000 --cycle-max 20000 -c -o win.json.gz

Open in Perfetto UI
- Web UI: https://ui.perfetto.dev (load the .json or .json.gz)
Expand Down
60 changes: 55 additions & 5 deletions ci/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
instructions must match exactly (both are deterministic ISA-level executions,
so any delta is functional divergence) and cycles must agree within the case's
`tolerance` (SimX is the timing model of the RTL, not just a functional oracle).
A case with `parity_counters` also compares that MPM class's counters.
"""

import os
Expand All @@ -32,7 +33,7 @@


def _run_one(case, xlen, driver):
"""One parity leg: run under `driver`, return (instrs, cycles)."""
"""One parity leg: run under `driver`, return (instrs, cycles, output)."""
argv, env = case.run_command(xlen, driver=driver)
rc, out = tc.execute_capture(argv, env)
assert rc == 0, "{} [{}] failed (exit {}): {}".format(
Expand All @@ -41,12 +42,59 @@ def _run_one(case, xlen, driver):
assert perf, "{} [{}]: no 'PERF: instrs=…, cycles=…' summary in output " \
"(app must call vx_device_dump_perf)".format(case.id, driver)
instrs, cycles = perf[-1]
return int(instrs), int(cycles)
return int(instrs), int(cycles), out


def parse_counters(out, prefix):
"""The raw `name=value` counters of an MPM class's device-total line
(`PERF: <prefix>: ...`; per-unit lines carry a `coreN:` prefix and do not
match). Derived metrics in parentheses are dropped."""
lines = re.findall(r"^PERF: {}: (.*)$".format(re.escape(prefix)), out, re.M)
if not lines:
return None
raw = re.sub(r"\([^)]*\)", "", lines[-1].split(";")[0])
return {k: int(v) for k, v in re.findall(r"(\w+)=(\d+)", raw)}


def rtu_invariants(c, cycles):
"""Violations of the RTU counters' definitions (empty when consistent)."""
errs = []
if c["hits"] > c["rays"]:
errs.append("hits {} > rays {}".format(c["hits"], c["rays"]))
if c["busy"] > cycles:
errs.append("busy {} > cycles {}".format(c["busy"], cycles))
return errs


def _compare_counters(case, simx_out, rtl_out, simx_cycles, rtl_cycles):
pc = case.parity_counters
prefix = pc["prefix"]
sc = parse_counters(simx_out, prefix)
rc = parse_counters(rtl_out, prefix)
assert sc and rc, "{}: no 'PERF: {}: ...' counter line (simx={}, rtlsim={})".format(
case.id, prefix, sc is not None, rc is not None)
if prefix == "rtu":
for drv, c, cyc in (("simx", sc, simx_cycles), ("rtlsim", rc, rtl_cycles)):
errs = rtu_invariants(c, cyc)
assert not errs, "{} [{}]: RTU counter invariants violated: {}".format(
case.id, drv, "; ".join(errs))
fails = []
for name in pc.get("exact", []):
print("PARITY: {}: {} simx={} rtlsim={}".format(case.id, name, sc.get(name), rc.get(name)))
if name not in sc or sc[name] != rc.get(name):
fails.append("{} simx={} rtlsim={}".format(name, sc.get(name), rc.get(name)))
for name in pc.get("tolerance", []):
s, r = sc.get(name), rc.get(name)
gap = abs(r - s) / float(max(r, 1)) if (s is not None and r is not None) else 1.0
print("PARITY: {}: {} simx={} rtlsim={} gap={:.2%}".format(case.id, name, s, r, gap))
if gap > case.tolerance:
fails.append("{} simx={} rtlsim={} gap={:.2%}".format(name, s, r, gap))
assert not fails, "{}: {} counter mismatch: {}".format(case.id, prefix, "; ".join(fails))


def _model_parity(case, xlen):
simx_instrs, simx_cycles = _run_one(case, xlen, "simx")
rtl_instrs, rtl_cycles = _run_one(case, xlen, "rtlsim")
simx_instrs, simx_cycles, simx_out = _run_one(case, xlen, "simx")
rtl_instrs, rtl_cycles, rtl_out = _run_one(case, xlen, "rtlsim")
gap = abs(rtl_cycles - simx_cycles) / float(rtl_cycles)
print("PARITY: {}: instrs simx={} rtlsim={}, cycles simx={} rtlsim={}, "
"gap={:.2%} (tolerance {:.0%})".format(
Expand All @@ -58,11 +106,13 @@ def _model_parity(case, xlen):
assert gap <= case.tolerance, \
"{}: cycle gap {:.2%} exceeds tolerance {:.0%} (simx={}, rtlsim={})".format(
case.id, gap, case.tolerance, simx_cycles, rtl_cycles)
if case.parity_counters:
_compare_counters(case, simx_out, rtl_out, simx_cycles, rtl_cycles)


def _perf_gate(case, xlen, update):
"""Run the benchmark on rtlsim; record or compare its cycles vs baseline."""
instrs, cycles = _run_one(case, xlen, "rtlsim")
instrs, cycles, _ = _run_one(case, xlen, "rtlsim")
if update:
pb.record(case, xlen, cycles, instrs)
print("PERF-BASELINE: {} xlen={} record cycles={} instrs={}".format(
Expand Down
17 changes: 17 additions & 0 deletions ci/testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ def __init__(self, category, entry, driver, defaults):
self.xlens = [32]
self.tolerance = float(entry.get("tolerance",
defaults.get("tolerance", DEFAULT_PARITY_TOLERANCE)))
# model_parity can also compare an MPM class's counters, read from the
# runtime's `PERF: <prefix>: name=value, ...` device-total line (the run
# must pass the class with --perf=N): `exact` counters must match, and
# `tolerance` counters must agree within the case tolerance.
self.parity_counters = dict(entry.get("parity_counters", {}))
self.authored_drivers = ("driver" in entry) or ("drivers" in entry)
# make-run / script fields
self.dir = entry.get("dir", "")
Expand Down Expand Up @@ -585,6 +590,18 @@ def cmd_lint(args):
"(the runner drives simx and rtlsim)".format(c.id))
if not (0.0 < c.tolerance < 1.0):
errors.append("{}: tolerance must be in (0, 1)".format(c.id))
if c.parity_counters:
pc = c.parity_counters
if c.check != "model_parity":
errors.append("{}: parity_counters needs check: model_parity".format(c.id))
if not isinstance(pc.get("prefix"), str) or not pc.get("prefix"):
errors.append("{}: parity_counters needs a 'prefix'".format(c.id))
if set(pc) - {"prefix", "exact", "tolerance"}:
errors.append("{}: parity_counters keys are prefix/exact/tolerance".format(c.id))
if not (pc.get("exact") or pc.get("tolerance")):
errors.append("{}: parity_counters compares nothing".format(c.id))
if "--perf=" not in c.flags:
errors.append("{}: parity_counters needs --perf=<class> in flags".format(c.id))
if errors:
for e in errors:
sys.stderr.write("LINT ERROR: " + e + "\n")
Expand Down
6 changes: 6 additions & 0 deletions ci/testcases/raytracing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ tests:
via: blackbox
app: rt_raycast
configs: -DVX_CFG_EXT_RTU_ENABLE
# The RTU counters too: the traversal work both models must do identically.
# busy is a timing counter and is left out.
flags: --perf=15
parity_counters:
prefix: rtu
exact: [rays, hits, box_tests, tri_tests, callbacks]

# Perf-regression twin of the model_parity-rt_raycast benchmark (same run, cycles-vs-baseline).
- id: perf_gate-rt_raycast
Expand Down
8 changes: 4 additions & 4 deletions docs/designs/dxa_async_copy_multicast.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ There is no software→global path; DXA is GMEM-read / LMEM-write only.
`VX_CFG_NUM_DXA_UNITS = max(1, ceil(NUM_CORES/8))`,
`VX_CFG_DXA_QUEUE_SIZE = 16`, `VX_CFG_DXA_MAX_INFLIGHT = 8`,
`VX_DCR_DXA_DESC_COUNT = 16`.
- **Perf CSRs** `[csr_mpm_dxa]` 0xB03–0xB07 (transfers, gmem_reads,
gmem_dedup, lmem_writes, gmem_latency),
`VX_DCR_MPM_CLASS_DXA = 6`
([`VX_types.toml:557-568`](../../VX_types.toml#L557)).
- **Perf CSRs** `[csr_mpm_dxa]` 0xB03–0xB08 (transfers, gmem_reads,
gmem_dedup, lmem_writes, gmem_latency, noslot_stalls),
`VX_DCR_MPM_CLASS_DXA = 16`
([`VX_types.toml:767-780`](../../VX_types.toml#L767)).
- **Cluster dispatch** (multicast prerequisite):
`VX_CSR_CTA_CLUSTER_SIZE = 0xCE0`,
`VX_DCR_KMU_CLUSTER_DIM_{X,Y,Z}`
Expand Down
3 changes: 2 additions & 1 deletion docs/designs/graphics_hardware_stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ been **retired** in favor of this push/launch path (§4).
rides the aperture address.
DCRs are broadcast to all cluster instances; each raster instance
self-selects its tile stripe by `INSTANCE_IDX`.
- **Perf** MPM classes RASTER=12, TEX=13, OM=14.
- **Perf** MPM classes RASTER=12, TEX=13, OM=14, RTU=15 (ray tracing, see
[ray_tracing_architecture.md §8.1](ray_tracing_architecture.md#81-performance-counters-mpm-class-15)).

### 2.1 The graphics register window (FF↔SIMT handoff)

Expand Down
47 changes: 43 additions & 4 deletions docs/designs/ray_tracing_architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,10 +467,49 @@ oracle**:

SimX additionally models **per-warp multi-trace concurrency** (several traces in
flight from one warp), which the RTL core does not implement — SimX remains the
oracle for that. Perf: MPM class `RTU` plus the SimX `PerfStats` (context/slot
occupancy, front-end busy ticks, box/tri test counts); the RTL core keeps its own
per-slot occupancy counters (busy / write / callback / fill and the two idle
causes: idle-with-a-ray vs starved) under `DBG_RTU_OCC`.
oracle for that. Its exploration counters (context occupancy, fetch merging,
walker cycle accounting) stay in `PerfStats` and the `VX_RTU_STATS=1` teardown
dump; the architectural set below is what both models expose.

### 8.1 Performance counters (MPM class 15)

`--perf=15` (`VORTEX_PROFILING=15`, or `vx_device_mpm_query` with
`VX_DCR_MPM_CLASS_RTU`) reads the RTU counters on every driver. They are
**socket-scoped**: a socket sums its RTU cores and hands the result to each of its
cores' CSR file, so every core of a socket reads the same values. The runtime
queries one core per socket, prints a per-socket line when there is more than one
socket, and always prints the device total.

The set is kept to quantities every BVH ray-tracing unit has, whatever its
internal organization, so the counters stay meaningful as the RTU evolves.
Microarchitecture-specific measures (slots, short-stack restarts, front-end steps)
are left to `PerfStats`; the class's CSR window (B03–B1F) leaves room for
future counters.

| CSR | Counter | Definition |
|---|---|---|
| B03 | `RTU_RAYS` | rays traced: the active lanes of the trace instructions |
| B04 | `RTU_HITS` | rays whose final result is a hit (misses = rays − hits) |
| B05 | `RTU_BOX_TESTS` | ray/box intersection tests (node children and procedural boxes) |
| B06 | `RTU_TRI_TESTS` | ray/triangle intersection tests |
| B07 | `RTU_CALLBACKS` | any-hit and intersection candidates returned to the shader (one per ray per candidate) |
| B08 | `RTU_BUSY` | cycles with at least one ray in flight |
| B09 | `RTCACHE_READS` | RTU memory requests (RTCache reads) |
| B0A | `RTCACHE_MISS_R` | RTCache read misses |

Box and triangle tests include the re-descents of restarted walks: SimX runs the
same `VX_CFG_RTU_STACK_DEPTH` short stack and trail restart as the RTL, so both
models do the same intersection work. Invariants: `RTU_HITS ≤ RTU_RAYS`,
`RTU_BUSY ≤ cycles`.

The `model_parity-rt_raycast` case requires rays, hits, box tests, triangle tests
and callbacks to match SimX exactly. `RTU_BUSY` and the RTCache counters are
timing and memory-system measures and are not gated: SimX keeps a ray's fetched
lines where the RTL refetches per structure access.

The runtime also derives the hit rate, box and triangle tests per ray, memory
requests per ray, RTU utilization and the RTCache hit rate. In RTL the counters
exist only in `PERF_ENABLE` builds.

---

Expand Down
8 changes: 4 additions & 4 deletions docs/perfetto_analysis.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Vortex Perfetto Trace: Performance Analysis Guide

This guide explains how to **generate** and **interpret** a Vortex GPU trace that has been converted into **Perfetto-compatible Chrome Trace JSON** using `vortex_perfetto.py`.
This guide explains how to **generate** and **interpret** a Vortex GPU trace that has been converted into **Perfetto-compatible Chrome Trace JSON** using `ci/perfetto.py`.

It is written for people who are:
- New to Perfetto
Expand Down Expand Up @@ -33,21 +33,21 @@ This guide focuses on **Vortex GPU 1**.
#### SimX (default)

```bash
python3 vortex_perfetto.py run_simx.log -t simx -c -o vortex.perfetto.json.gz
python3 ci/perfetto.py run_simx.log -t simx -c -o vortex.perfetto.json.gz
```

#### RTL simulation

```bash
python3 vortex_perfetto.py run_rtlsim.log -t rtlsim -c -o vortex.perfetto.json.gz
python3 ci/perfetto.py run_rtlsim.log -t rtlsim -c -o vortex.perfetto.json.gz
```

### Common options

- **Limit export to a cycle/tick window** (highly recommended for huge logs):

```bash
python3 vortex_perfetto.py run_rtlsim.log -t rtlsim \
python3 ci/perfetto.py run_rtlsim.log -t rtlsim \
--cycle-min 10000 --cycle-max 20000 -c -o window.json.gz
```

Expand Down
15 changes: 15 additions & 0 deletions hw/rtl/VX_gpu_pkg.sv
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,17 @@ package VX_gpu_pkg;
} om_perf_t;
`endif

`ifdef VX_CFG_EXT_RTU_ENABLE
typedef struct packed {
logic [PERF_CTR_BITS-1:0] rays;
logic [PERF_CTR_BITS-1:0] hits;
logic [PERF_CTR_BITS-1:0] box_tests;
logic [PERF_CTR_BITS-1:0] tri_tests;
logic [PERF_CTR_BITS-1:0] callbacks;
logic [PERF_CTR_BITS-1:0] busy;
} rtu_perf_t;
`endif

typedef struct packed {
logic [PERF_CTR_BITS-1:0] idles;
logic [PERF_CTR_BITS-1:0] active_warps;
Expand Down Expand Up @@ -1361,6 +1372,10 @@ package VX_gpu_pkg;
om_perf_t om;
cache_perf_t ocache;
`endif
`ifdef VX_CFG_EXT_RTU_ENABLE
rtu_perf_t rtu;
cache_perf_t rtcache;
`endif
} sysmem_perf_t;

typedef struct packed {
Expand Down
Loading
Loading