# APB transaction trace
This example performs 128 deterministic APB writes and 128 matching reads
against a 64-word RAM. One passive `ApbMonitor` reconstructs all 256 completed
operations. Its typed observation stream fans out to both an in-order
scoreboard and a transaction recorder.
The example is intentionally larger than a single smoke transaction but small
enough to read in one sitting. It exercises transaction order, payload fields,
begin/end simulation time, completion disposition, and per-stream sequence
numbers without mixing in a larger register model or protocol stack.
## Connect checking and recording
cpptb-vc (C++ DPI)
```cpp
Master master{bus, ApbConfig{.sample_delay = 1_ps}};
ApbMonitor monitor{test, bus, 1_ps};
AnalysisPort expected;
InOrderScoreboard scoreboard{test, "APB trace transaction"};
TransactionRecorder recorder;
InMemoryTransactionSink trace;
auto trace_sink = recorder.connect(trace);
auto& stream = recorder.stream("apb0.observed");
auto expected_connection = expected.connect(scoreboard.expected());
auto checking = monitor.observed().connect(scoreboard.actual());
auto recording = monitor.observed().connect(stream);
co_await Join{trace_sequence(master, test, expected),
monitor.run(kTransactionCount)};
```
Pure SystemVerilog
```systemverilog
transaction_t expected_transactions[$];
transaction_t actual_transactions[$];
string recorded_streams[$];
string recorded_types[$];
longint unsigned recorded_sequences[$];
time recorded_begin_times[$];
time recorded_end_times[$];
int unsigned recorded_dispositions[$];
string recorded_json[$];
task automatic publish_observed(
input transaction_t transaction,
input time begin_time,
input time end_time
);
actual_transactions.push_back(transaction);
recorded_streams.push_back("apb0.observed");
recorded_types.push_back("memory_transaction");
recorded_sequences.push_back(recorded_sequences.size());
recorded_begin_times.push_back(begin_time);
recorded_end_times.push_back(end_time);
recorded_dispositions.push_back(0); // completed
recorded_json.push_back(format_transaction(transaction));
compare_available();
endtask
fork
trace_sequence();
monitor();
join
```
Both versions decode the APB setup and access phases once, compare the same
256 transaction values, retain the same seven record fields, and exercise 128
single-cycle wait states. They each report 649 checks and 898 simulated cycles.
The C++ monitor owns `observed()`, so adding or removing recording does not
change the driver, scoreboard, or protocol decoder.
## Inspect records
`InMemoryTransactionSink::records()` returns records in publication order:
```cpp
const auto& first = trace.records().front();
test.expect_eq("first sequence", first.sequence, uint64_t{0});
test.expect("completed after setup", first.end_time > first.begin_time);
test.expect("captured write",
first.value_json.find("\"operation\":\"write\"") !=
std::string::npos);
```
Use JSON Lines when the trace should survive the process:
```cpp
JsonLinesTransactionSink json{"transactions.jsonl"};
auto json_output = recorder.connect(json);
// Run the monitor and stimulus while json_output remains alive.
co_await Join{monitor.run(kTransactionCount), stimulus(dut, test)};
json.finalize();
```
Each output line contains the stream, sequence, static transaction type,
begin/end time in femtoseconds, disposition, and structured payload:
```json
{"stream":"apb0.observed","sequence":0,"type":"memory_transaction","begin_time_fs":35001000,"end_time_fs":45001000,"disposition":"completed","value":{"operation":"write","address":0,"data":1489221554,"byte_enable":18446744073709551615,"status":"okay","wait_cycles":0}}
```
The sink must outlive its connection. `finalize()` flushes and closes JSON
output and throws an actionable error if writing fails. The destructor only
performs best-effort cleanup.
## Run both versions
```sh
make cpp-dpi-apb-trace-run
make cpp-dpi-apb-trace-sv-run
make feature-test FEATURE=dpi_apb_trace
make feature-test FEATURE=transaction_recording
make feature-benchmark FEATURE=transaction_recording
```
The complete authored sources are under `examples/apb_trace/`. Generated DUT
bindings and simulator transport stay under `build/cpptb/apb_trace/`.
`dpi_apb_trace` is the readable 256-transfer equivalence example.
`transaction_recording` is its 100,000-iteration authoring-core counterpart:
it retains 200,000 records in both C++ and pure SV and applies the standard
`1.10x` hard performance guard. A timing result is published only after the
host passes the repository's low-load admission checks.
See [Transaction recording](../verification-components/transaction-recording.md)
for custom descriptors, stream naming, disabled behavior, and deferred
semantics.