Rust Crate API Reference

Crate: uscope Location: crates/uscope/


1. Overview

The uscope Rust crate provides a complete reader and writer for the µScope trace format. It implements the transport layer (file header, preamble, schema, segments, checkpoints, deltas, string table, section table) and the CPU protocol layer (entity catalog, pipeline stages, typed events).

Dependencies

CratePurpose
byteorderLittle-endian integer read/write
lz4_flexPure-Rust LZ4 compression

No other runtime dependencies.


2. Schema Building

Use SchemaBuilder and DutDescBuilder to define the trace structure before writing.

2.1 SchemaBuilder

#![allow(unused)]
fn main() {
use uscope::schema::{SchemaBuilder, FieldSpec};
use uscope::types::SF_SPARSE;

let mut sb = SchemaBuilder::new();

// Clock domain: 5 GHz (200 ps period)
let clk = sb.clock_domain("core_clk", 200);

// Scope hierarchy
sb.scope("root", None, None, None);
let scope = sb.scope("core0", Some(0), Some("cpu"), Some(clk));

// Enum type
let stage_enum = sb.enum_type(
    "pipeline_stage",
    &["fetch", "decode", "execute", "writeback"],
);

// Storage (entity catalog)
let entities = sb.storage(
    "entities", scope, 512, SF_SPARSE,
    &[
        ("entity_id", FieldSpec::U32),
        ("pc",        FieldSpec::U64),
        ("inst_bits", FieldSpec::U32),
    ],
);

// Event type
let stage_ev = sb.event(
    "stage_transition", scope,
    &[
        ("entity_id", FieldSpec::U32),
        ("stage",     FieldSpec::Enum(stage_enum)),
    ],
);

let schema = sb.build();
}

Methods:

MethodReturnsDescription
clock_domain(name, period_ps)u8Add a clock domain
scope(name, parent, protocol, clock_id)u16Add a scope
enum_type(name, values)u8Add an enum type
storage(name, scope, slots, flags, fields)u16Add a storage definition
event(name, scope, fields)u16Add an event type
summary_field(name, type, scope)Add a summary field
strings_mut()&mut StringPoolBuilderAccess the string pool
build()SchemaConsume builder, produce schema

2.2 DutDescBuilder

#![allow(unused)]
fn main() {
use uscope::schema::DutDescBuilder;

let mut dut = DutDescBuilder::new();
dut.property("dut_name", "boom_core_0")
   .property("cpu.isa", "RV64GC")
   .property("cpu.pipeline_stages", "fetch,decode,execute,writeback");

// Build using the schema's shared string pool
let dut_desc = dut.build(sb.strings_mut());
}

2.3 FieldSpec

VariantWire typeSize
FieldSpec::U8FT_U81
FieldSpec::U16FT_U162
FieldSpec::U32FT_U324
FieldSpec::U64FT_U648
FieldSpec::I8FT_I81
FieldSpec::I16FT_I162
FieldSpec::I32FT_I324
FieldSpec::I64FT_I648
FieldSpec::BoolFT_BOOL1
FieldSpec::StringRefFT_STRING_REF4
FieldSpec::Enum(id)FT_ENUM1

3. Writer

Writer<W> writes µScope trace files in streaming, append-only fashion.

3.1 Creating a Writer

#![allow(unused)]
fn main() {
use uscope::writer::Writer;
use std::fs::File;

let file = File::create("trace.uscope")?;
let mut w = Writer::create(file, &dut_desc, &schema, checkpoint_interval_ps)?;
}

The checkpoint_interval_ps parameter controls how often a full checkpoint is written. Smaller intervals allow faster random-access seeks at the cost of larger files.

3.2 Writing Cycles

All storage mutations and events must occur within a begin_cycle / end_cycle pair. Time must be monotonically non-decreasing.

#![allow(unused)]
fn main() {
w.begin_cycle(time_ps);

// Mutate storage slots
w.slot_set(storage_id, slot, field, value);
w.slot_add(storage_id, slot, field, delta);
w.slot_clear(storage_id, slot);

// Emit events (payload is pre-serialized, fields concatenated LE)
w.event(event_type_id, &payload_bytes);

w.end_cycle()?;
}
MethodDescription
begin_cycle(time_ps)Start a cycle frame at the given time
slot_set(storage, slot, field, value)Set a field value (marks slot valid)
slot_clear(storage, slot)Mark slot invalid (sparse only)
slot_add(storage, slot, field, delta)Add to a field value
event(type_id, payload)Emit an event with raw payload
end_cycle()Finish the cycle frame

3.3 String Table

For STRING_REF fields, insert strings into the writer's string table:

#![allow(unused)]
fn main() {
let text_idx = w.string_table.insert("addi x0, x0, 0");
// Use text_idx as the u32 value for a STRING_REF field in event payloads
}

3.4 Finalization

#![allow(unused)]
fn main() {
let file = w.close()?;  // Writes string table, segment table, section table
}

Calling close() sets F_COMPLETE, writes the section table, and returns the underlying writer. The file is then readable by Reader.


4. Reader

Reader opens µScope trace files for random-access reading.

4.1 Opening a File

#![allow(unused)]
fn main() {
use uscope::reader::Reader;

let mut r = Reader::open("trace.uscope")?;
}

Handles both finalized (F_COMPLETE) and in-progress files. For finalized files, the section table is used for fast segment lookup. For in-progress files, the segment chain is walked from tail_offset.

4.2 Metadata Access

#![allow(unused)]
fn main() {
let header = r.header();           // FileHeader
let schema = r.schema();           // Schema (clock domains, scopes, storages, events)
let dut = r.dut_desc();            // DutDesc (key-value properties)
let config = r.trace_config();     // TraceConfig (checkpoint_interval_ps)
let offsets = r.field_offsets();    // Precomputed field offsets per storage

// Look up a DUT property by key
let isa = r.dut_property("cpu.isa");  // Some("RV64GC")

// String table (for STRING_REF field values)
if let Some(st) = r.string_table() {
    let text = st.get(0);  // Some("addi x0, x0, 0")
}
}

4.3 State Reconstruction

Reconstruct the full storage state at any point in time. The reader finds the appropriate segment, loads its checkpoint, and replays deltas up to the target time.

#![allow(unused)]
fn main() {
let state = r.state_at(time_ps)?;

// Query storage state
let valid = state.slot_valid(storage_id, slot);
let value = state.slot_field(storage_id, slot, field_index, &offsets[storage_id]);
}

4.4 Event Queries

#![allow(unused)]
fn main() {
let events = r.events_in_range(t0_ps, t1_ps)?;
for ev in &events {
    println!("t={} type={} payload={:?}", ev.time_ps, ev.event_type_id, ev.payload);
}
}

4.5 Segment-Level Access

#![allow(unused)]
fn main() {
let n = r.segment_count();
let (storages, events, ops) = r.segment_replay(seg_idx)?;
}

segment_replay returns the checkpoint state after full delta replay, plus all events and storage operations (TimedOp) in the segment.

4.6 Live Tailing

For traces being written concurrently:

#![allow(unused)]
fn main() {
loop {
    if r.poll_new_segments()? {
        // New segments available — re-query events or state
    }
    std::thread::sleep(std::time::Duration::from_millis(100));
}
}

5. CPU Protocol Helpers

The protocols::cpu module provides higher-level APIs that implement the CPU protocol conventions on top of the transport-layer primitives.

5.1 CpuSchemaBuilder

Constructs a complete CPU-protocol schema with all standard enums, storages, and events.

#![allow(unused)]
fn main() {
use uscope::protocols::cpu::CpuSchemaBuilder;
use uscope::schema::FieldSpec;

let (dut_builder, mut schema_builder, ids) = CpuSchemaBuilder::new("core0")
    .isa("RV64GC")
    .pipeline_stages(&["fetch", "decode", "rename", "dispatch",
                        "issue", "execute", "complete", "retire"])
    .fetch_width(4)
    .commit_width(4)
    .entity_slots(512)
    .buffer("rob", 256, &[("completed", FieldSpec::Bool)])
    .buffer("iq_int", 48, &[])
    .counter("committed_insns")
    .counter("bp_misses")
    .build();

let dut = dut_builder.build(schema_builder.strings_mut());
let schema = schema_builder.build();
}

Builder methods:

MethodDescription
isa(name)Set ISA (e.g. "RV64GC")
pipeline_stages(names)Define pipeline stage enum
fetch_width(n)Set fetch width DUT property
commit_width(n)Set commit width DUT property
entity_slots(n)Max in-flight entities (default: 512)
elf_path(path)Set ELF path for disassembly
vendor(name)Set vendor DUT property
buffer(name, slots, fields)Add a hardware buffer storage
counter(name)Add a counter (1-slot dense storage)
stall_reasons(names)Override default stall reason enum

CpuIds — returned by build(), contains all assigned IDs:

FieldTypeDescription
scope_idu16CPU scope ID
entities_storage_idu16Entity catalog storage ID
stage_transition_event_idu16Stage transition event type
annotate_event_idu16Annotation event type
dependency_event_idu16Dependency event type
flush_event_idu16Flush event type
stall_event_idu16Stall event type
field_entity_idu16Field index: entity_id
field_pcu16Field index: pc
field_inst_bitsu16Field index: inst_bits
buffersVec<(String, u16)>Buffer (name, storage_id) pairs
countersVec<(String, u16, u16)>Counter (name, storage_id, field) triples

5.2 CpuWriter

Typed helpers that emit the correct transport-layer operations for CPU protocol semantics.

#![allow(unused)]
fn main() {
use uscope::protocols::cpu::CpuWriter;

let cpu = CpuWriter::new(ids);

w.begin_cycle(time_ps);

// Fetch: allocate entity in catalog
cpu.fetch(&mut w, entity_id, pc, inst_bits);

// Stage transition
cpu.stage_transition(&mut w, entity_id, stage_index);

// Retire: clear entity from catalog
cpu.retire(&mut w, entity_id);

// Flush: emit flush event + clear entity
cpu.flush(&mut w, entity_id, reason);

// Annotation: insert text into string table + emit event
cpu.annotate(&mut w, entity_id, "decoded: addi x1, x0, 1");

// Dependency: record data/structural dependency
cpu.dependency(&mut w, src_entity, dst_entity, dep_type);

// Stall
cpu.stall(&mut w, reason);

// Counter increment
cpu.counter_add(&mut w, "committed_insns", 1);

w.end_cycle()?;
}
MethodTransport opsDescription
fetch(w, id, pc, bits)3 × slot_setAllocate entity
stage_transition(w, id, stage)1 × eventPipeline stage change
retire(w, id)1 × slot_clearNormal retirement
flush(w, id, reason)1 × event + 1 × slot_clearSquash
annotate(w, id, text)1 × string_insert + 1 × eventText annotation
dependency(w, src, dst, type)1 × eventData dependency
stall(w, reason)1 × eventPipeline stall
counter_add(w, name, delta)1 × slot_addIncrement counter

6. Example: Full Write-Read Cycle

#![allow(unused)]
fn main() {
use uscope::protocols::cpu::{CpuSchemaBuilder, CpuWriter};
use uscope::writer::Writer;
use uscope::reader::Reader;
use std::fs::File;

// Build schema
let (dut_builder, mut sb, ids) = CpuSchemaBuilder::new("core0")
    .isa("RV64GC")
    .pipeline_stages(&["fetch", "decode", "execute", "writeback"])
    .entity_slots(16)
    .build();

let dut = dut_builder.build(sb.strings_mut());
let schema = sb.build();

// Write
let file = File::create("trace.uscope").unwrap();
let mut w = Writer::create(file, &dut, &schema, 10_000).unwrap();
let cpu = CpuWriter::new(ids.clone());

w.begin_cycle(0);
cpu.fetch(&mut w, 0, 0x8000_0000, 0x13);
cpu.stage_transition(&mut w, 0, 0);
w.end_cycle().unwrap();

w.begin_cycle(1000);
cpu.stage_transition(&mut w, 0, 1);
w.end_cycle().unwrap();

w.begin_cycle(2000);
cpu.stage_transition(&mut w, 0, 2);
w.end_cycle().unwrap();

w.begin_cycle(3000);
cpu.stage_transition(&mut w, 0, 3);
cpu.retire(&mut w, 0);
w.end_cycle().unwrap();

w.close().unwrap();

// Read
let mut r = Reader::open("trace.uscope").unwrap();
assert_eq!(r.header().total_time_ps, 3000);

let state = r.state_at(1500).unwrap();
assert!(state.slot_valid(ids.entities_storage_id, 0)); // still in-flight

let state = r.state_at(3000).unwrap();
assert!(!state.slot_valid(ids.entities_storage_id, 0)); // retired

let events = r.events_in_range(0, 3000).unwrap();
assert_eq!(events.len(), 4); // 4 stage transitions
}