.pccx File Format Specification

Version: 0.2 (format major: 0x01, minor: 0x01)
Status: Active
Source of truth: src/core/src/pccx_format.rs

Overview

The .pccx binary format is the official container for storing NPU profiling traces, hardware configurations, and session metadata produced by pccx-lab.

Design goals:

  • Zero-copy IPC: The payload is a raw byte blob that can be memory-mapped directly into a WebGL ArrayBuffer without re-encoding.

  • Self-describing: The JSON header contains all metadata needed to decode the payload without out-of-band schema files.

  • Versioned: Major/minor version bytes allow backward-compatible evolution.

  • Integrity-checked: An optional FNV-1a 64-bit checksum enables fast tamper detection.


Binary Layout

All multi-byte integers are little-endian unless otherwise noted.

Offset

Size

Field

Description

0

4

Magic

PCCX = 0x50 0x43 0x43 0x58

4

1

Major version

Breaking-change counter (currently 0x01)

5

1

Minor version

Additive-change counter (currently 0x01)

6

2

Reserved

0x00 0x00 — must be zeroed by writers

8

8

Header length

u64 — byte length of the JSON header

16

N

JSON header

UTF-8 JSON object (see below)

16 + N

M

Binary payload

Encoding declared in payload.encoding

Compatibility rule: Parsers MUST reject files whose major version does not match the expected value. Parsers SHOULD accept any minor version, treating unknown fields as additive extensions.


JSON Header Fields

{
  "pccx_lab_version": "v0.4.0-contention-aware",   // string
  "format_minor": 1,                                 // u8

  "arch": {
    "mac_dims": [32, 32],    // [rows, cols] of the systolic MAC array
    "isa_version": "1.1",    // string
    "peak_tops": 2.05        // f64, theoretical peak (informational)
  },

  "trace": {
    "cycles":    12345678,   // u64 — total simulation cycles
    "cores":     32,         // u32 — number of active NPU cores
    "clock_mhz": 1000        // u32 — clock frequency used to generate the trace
  },

  "payload": {
    "encoding":       "bincode",           // "bincode" | "flatbuf" | "raw"
    "byte_length":    4096000,             // u64 — exact payload byte count
    "checksum_fnv64": "0xcbf29ce4842223" // hex string | null — FNV-1a 64-bit
  }
}

Payload Encodings

Value

Description

"bincode"

Rust bincode v1 serialisation of NpuTrace struct

"flatbuf"

24-byte packed struct array (see Flat Buffer Layout below)

"raw"

Architecture-specific raw bytes (not standardised)


Flat Buffer Layout ("flatbuf" encoding)

Each event is 24 bytes, all fields little-endian:

Offset

Size

Type

Field

0

4

u32

core_id

4

8

u64

start_cycle

12

8

u64

duration

20

4

u32

event_type_id

Event Type IDs

ID

Name

0

UNKNOWN

1

MAC_COMPUTE

2

DMA_READ

3

DMA_WRITE

4

SYSTOLIC_STALL

5

BARRIER_SYNC


Pseudocode (Rust)

// Reading
let mut magic = [0u8; 4];
reader.read_exact(&mut magic)?;
assert_eq!(&magic, b"PCCX");

let mut version = [0u8; 2];
reader.read_exact(&mut version)?;
let major = version[0]; // must equal MAJOR_VERSION (0x01)
let minor = version[1]; // any value accepted

let mut reserved = [0u8; 2];
reader.read_exact(&mut reserved)?;

let mut hlen_buf = [0u8; 8];
reader.read_exact(&mut hlen_buf)?;
let header_len = u64::from_le_bytes(hlen_buf);

let mut json_bytes = vec![0u8; header_len as usize];
reader.read_exact(&mut json_bytes)?;
let header: PccxHeader = serde_json::from_slice(&json_bytes)?;

let mut payload = vec![0u8; header.payload.byte_length as usize];
reader.read_exact(&mut payload)?;

Versioning Policy

  • Major version increments on incompatible layout changes (e.g. changing the reserved field size, removing header fields). Parsers must reject mismatches.

  • Minor version increments on additive changes (new optional header fields, new event type IDs). Parsers must ignore unknown fields gracefully.

  • The pccx_lab_version string is informational only and does not affect parsing.


Integrity Check

The optional checksum_fnv64 field stores the FNV-1a 64-bit hash of the raw payload bytes. Parsers may use it to detect accidental corruption:

fn fnv1a_64(data: &[u8]) -> u64 {
    const BASIS: u64 = 0xcbf29ce484222325;
    const PRIME: u64 = 0x00000100000001b3;
    data.iter().fold(BASIS, |h, &b| (h ^ b as u64).wrapping_mul(PRIME))
}

A mismatch produces a warning but is not fatal by default (configurable).