C DPI Library API Reference

Header: uscope_dpi.h Location: dpi/


1. Overview

The C DPI library is a standalone, write-only µScope trace library designed for integration with hardware simulators via DPI (Direct Programming Interface). It produces trace files that are binary-compatible with the Rust reader.

Design Principles

  • Single .c + .h (plus vendored LZ4) — easy to integrate
  • C99 — compiles with any standard C compiler
  • No dynamic allocation during per-cycle operations — pre-allocated buffers
  • Write-only — no reader (use the Rust crate for reading)
  • Zero Rust dependency — fully self-contained

Building

make -C dpi            # builds libuscope_dpi.a
make -C dpi test       # builds and runs the test program

Link with -luscope_dpi (or include uscope_dpi.c and lz4.c directly).


2. Schema Building

Before opening a writer, define the trace schema.

2.1 Create / Free

uscope_schema_def_t *schema = uscope_schema_new();
// ... add clocks, scopes, enums, storages, events ...
// Schema is consumed by uscope_writer_open() — do not free after open.
// If not opening a writer, free with:
uscope_schema_free(schema);

2.2 Clock Domains

uint8_t clk = uscope_schema_add_clock(schema, "core_clk", 1000); // 1 GHz
ParameterTypeDescription
nameconst char *Clock name
period_psuint32_tPeriod in picoseconds
Returnsuint8_tClock domain ID

2.3 Scopes

uscope_schema_add_scope(schema, "root", 0xFFFF, NULL, 0xFF);
uint16_t scope = uscope_schema_add_scope(schema, "core0", 0, "cpu", clk);
ParameterTypeDescription
nameconst char *Scope name
parentuint16_tParent scope ID (0xFFFF = root)
protocolconst char *Protocol name (NULL = none)
clock_iduint8_tClock domain (0xFF = inherit)
Returnsuint16_tScope ID

2.4 Enums

const char *stages[] = {"fetch", "decode", "execute", "writeback"};
uint8_t stage_enum = uscope_schema_add_enum(schema, "pipeline_stage", stages, 4);

2.5 Storages

Fields are passed as parallel arrays of names, types, and enum IDs.

const char  *fields[]    = {"entity_id", "pc",          "inst_bits"};
uint8_t      types[]     = {USCOPE_FT_U32, USCOPE_FT_U64, USCOPE_FT_U32};
uint8_t      enum_ids[]  = {0,             0,              0};

uint16_t entities = uscope_schema_add_storage(
    schema, "entities", scope, /*num_slots=*/512, USCOPE_SF_SPARSE,
    /*num_fields=*/3, fields, types, enum_ids);
ParameterTypeDescription
nameconst char *Storage name
scope_iduint16_tOwning scope
num_slotsuint16_tNumber of slots
flagsuint16_tUSCOPE_SF_SPARSE or 0 (dense)
num_fieldsuint16_tNumber of fields
field_namesconst char **Field name array
field_typesconst uint8_t *Field type array
field_enum_idsconst uint8_t *Enum ID array (or NULL)
Returnsuint16_tStorage ID

2.6 Events

const char  *st_fields[] = {"entity_id",    "stage"};
uint8_t      st_types[]  = {USCOPE_FT_U32,  USCOPE_FT_ENUM};
uint8_t      st_enums[]  = {0,              stage_enum};

uint16_t st_event = uscope_schema_add_event(
    schema, "stage_transition", scope,
    /*num_fields=*/2, st_fields, st_types, st_enums);

3. Field Type Constants

ConstantValueSizeDescription
USCOPE_FT_U80x011Unsigned 8-bit
USCOPE_FT_U160x022Unsigned 16-bit
USCOPE_FT_U320x034Unsigned 32-bit
USCOPE_FT_U640x048Unsigned 64-bit
USCOPE_FT_I80x051Signed 8-bit
USCOPE_FT_I160x062Signed 16-bit
USCOPE_FT_I320x074Signed 32-bit
USCOPE_FT_I640x088Signed 64-bit
USCOPE_FT_BOOL0x091Boolean
USCOPE_FT_STRING_REF0x0A4String table index
USCOPE_FT_ENUM0x0B1Enum value

4. Writer

4.1 Open / Close

uscope_dut_property_t props[] = {
    {"dut_name", "boom_core_0"},
    {"cpu.isa",  "RV64GC"},
};

uscope_writer_t *w = uscope_writer_open(
    "trace.uscope",
    props, /*num_props=*/2,
    schema,                    // consumed — do not free
    /*checkpoint_interval_ps=*/1000000);

// ... write cycles ...

uscope_writer_close(w);  // finalizes and frees

uscope_writer_open takes ownership of the schema. Do not call uscope_schema_free after opening.

uscope_writer_close writes the string table, segment table, section table, sets F_COMPLETE, and frees all resources.

4.2 Per-Cycle Operations

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

uscope_begin_cycle(w, time_ps);

uscope_slot_set(w, storage_id, slot, field, value);
uscope_slot_clear(w, storage_id, slot);
uscope_slot_add(w, storage_id, slot, field, delta);
uscope_event(w, event_type_id, payload, payload_size);

uscope_end_cycle(w);
FunctionDescription
uscope_begin_cycle(w, time_ps)Start a cycle at the given time
uscope_slot_set(w, stor, slot, field, val)Set field value (marks slot valid)
uscope_slot_clear(w, stor, slot)Mark slot invalid
uscope_slot_add(w, stor, slot, field, val)Add to field value
uscope_event(w, type_id, payload, size)Emit event with raw payload
uscope_end_cycle(w)End cycle, flush segment if needed

4.3 Event Payloads

Event payloads are the field values concatenated in schema-definition order, little-endian, with no padding. Build them manually:

// stage_transition: entity_id (U32) + stage (ENUM/U8)
uint8_t payload[5];
uint32_t entity_id = 42;
memcpy(payload, &entity_id, 4);  // little-endian on LE platforms
payload[4] = 2;                  // stage index
uscope_event(w, st_event, payload, 5);

4.4 String Table

For STRING_REF fields in event payloads:

uint32_t idx = uscope_string_insert(w, "addi x0, x0, 0");
// Use idx as the 4-byte value in a STRING_REF field

5. Limits

ResourceMaximum
String pool (schema)64 KB
Clock domains16
Scopes256
Enum types64
Enum values per type256
Storages256
Event types256
Fields per storage/event32
Ops per cycle4096
Events per cycle1024
Event payload size256 bytes
Segments65536
Delta buffer4 MB (auto-grows)

6. Example: CPU Pipeline Trace

#include "uscope_dpi.h"
#include <string.h>

int main(void) {
    // Schema
    uscope_schema_def_t *s = uscope_schema_new();
    uint8_t clk = uscope_schema_add_clock(s, "clk", 1000);
    uscope_schema_add_scope(s, "root", 0xFFFF, NULL, 0xFF);
    uint16_t scope = uscope_schema_add_scope(s, "core0", 0, "cpu", clk);

    const char *stages[] = {"fetch", "decode", "execute", "writeback"};
    uint8_t se = uscope_schema_add_enum(s, "pipeline_stage", stages, 4);

    const char *ef[] = {"entity_id", "pc", "inst_bits"};
    uint8_t et[] = {USCOPE_FT_U32, USCOPE_FT_U64, USCOPE_FT_U32};
    uint16_t ent = uscope_schema_add_storage(s, "entities", scope,
                                              256, USCOPE_SF_SPARSE,
                                              3, ef, et, NULL);

    const char *sf[] = {"entity_id", "stage"};
    uint8_t st[] = {USCOPE_FT_U32, USCOPE_FT_ENUM};
    uint8_t sen[] = {0, se};
    uint16_t sev = uscope_schema_add_event(s, "stage_transition", scope,
                                            2, sf, st, sen);

    // DUT properties
    uscope_dut_property_t props[] = {
        {"dut_name", "core0"},
        {"cpu.isa", "RV64GC"},
        {"cpu.pipeline_stages", "fetch,decode,execute,writeback"},
    };

    // Open
    uscope_writer_t *w = uscope_writer_open("trace.uscope",
                                             props, 3, s, 100000);

    // Fetch instruction 0
    uscope_begin_cycle(w, 0);
    uscope_slot_set(w, ent, 0, 0, 0);          // entity_id
    uscope_slot_set(w, ent, 0, 1, 0x80000000); // pc
    uscope_slot_set(w, ent, 0, 2, 0x13);       // inst_bits
    uint8_t payload[5];
    uint32_t eid = 0;
    memcpy(payload, &eid, 4);
    payload[4] = 0; // fetch stage
    uscope_event(w, sev, payload, 5);
    uscope_end_cycle(w);

    // Decode
    uscope_begin_cycle(w, 1000);
    payload[4] = 1;
    uscope_event(w, sev, payload, 5);
    uscope_end_cycle(w);

    // Execute
    uscope_begin_cycle(w, 2000);
    payload[4] = 2;
    uscope_event(w, sev, payload, 5);
    uscope_end_cycle(w);

    // Writeback + retire
    uscope_begin_cycle(w, 3000);
    payload[4] = 3;
    uscope_event(w, sev, payload, 5);
    uscope_slot_clear(w, ent, 0);
    uscope_end_cycle(w);

    uscope_writer_close(w);
    return 0;
}

7. Integration with Simulators

SystemVerilog DPI

import "DPI-C" function chandle uscope_writer_open(
    input string path,
    /* ... */
);
import "DPI-C" function void uscope_begin_cycle(
    input chandle w, input longint unsigned time_ps
);
// ... etc

Verilator

Include uscope_dpi.c and lz4.c in the Verilator build:

verilator --cc top.sv --exe sim_main.cpp uscope_dpi.c lz4.c

Call the C API from sim_main.cpp or from DPI-exported functions in the SystemVerilog testbench.