Appendix D argued that the model of computation is the methodology contribution and the host language is an engineering choice. This appendix backs that argument with a working SystemC implementation of the actor framework, parallel to the SystemVerilog implementation Chapter 6 documents, and makes a sharper claim besides: SystemC is the host on which the spec-to-silicon continuum closes most cleanly. One C++/SystemC ecosystem spans every step the other hosts split across tools — architecture exploration, high-level synthesis to RTL, the FIRRTL/Golden Gate path to FireSim, and distributed regression — and the verification actors are native C++ objects that cross substrates unchanged. The port lives at actor_pkg_systemc/: the same actor topology, the same ‘WIRE-based composition, the same typed-message envelope, expressed in C++ on top of SystemC’s discrete-event kernel.
This is not an alternative framework. It is the same framework on a different substrate, demonstrating that the actor methodology is host-language-agnostic. A team that picks SystemC inherits all of Chapter 6’s claims — low line counts, ‘WIRE-based composition, RAL by symbolic name, distributed transport for free — plus three advantages SystemC brings natively: timing without simulator-specific overhead, a synthesizable path to RTL through HLS, and free interoperability with the C++ ecosystem. The first removes the Verilator --timing boundary (Appendix D §D.10); the second and third are what close the synthesis and emulation steps of the continuum (§J.5).
The SystemVerilog implementation in actor_pkg/ runs inside a SystemVerilog simulator alongside the RTL DUT, which is exactly what the verification engineer needs day-to-day: one process, one log, no inter-language bridges. That implementation is the right deployment for verification engineers working in the existing UVM-adjacent ecosystem, and it is what the OpenTitan example uses end-to-end.
A SystemC implementation answers a different question: what does the actor methodology look like where SystemVerilog is not the natural — or the fastest — host? That is the architecture and synthesis stages, the emulation back-ends, and, increasingly, high-throughput verification at subsystem and SoC scale. Five concrete cases:
• Architecture exploration before RTL exists. Pre-RTL, there is no DUT for SystemVerilog to drive; the team is making blocks-and-arrows decisions and measuring performance/power claims against an executable model. SystemC has historically been the language of choice here (SystemC TLM, virtual platforms, Synopsys Platform Architect). An actor framework in SystemC fits the existing tool flow without forcing the team into SystemVerilog before it has any reason to be there.
• HLS to RTL. Catapult and Stratus accept synthesizable SystemC and emit RTL. A SystemC actor specification synthesizes through the existing tool chain without waiting for an actor-specific synthesis path; the actor’s structural shape (state, message handler, FIFO interfaces, fan-out) maps cleanly onto what HLS tools already accept (§J.5).
• Native C++ ecosystem. ZMQ, NATS, gRPC, Apache Arrow, machine-learning libraries, regression-management tools, web dashboards — all are first-class C++ libraries. A SystemC actor publishes typed messages that these libraries consume natively, without DPI, without IPC bridges. The actor_distributed_pkg bridges, which on the SystemVerilog side require DPI to call into the ZMQ/NATS C libraries, become normal function calls.
• High-throughput verification at subsystem and SoC scale. The architecture-versus-DV split is not as clean as it once was. As designs grow, event-driven SystemVerilog simulation running a full UVM environment becomes the throughput limiter, and teams increasingly carry C++/SystemC reference models, scoreboards, and checkers — run standalone or co-simulated against the RTL — to recover cycles the SystemVerilog simulator cannot deliver. The framework’s SystemC deployment serves this directly: the same actor topology, hosted where it runs faster.
• The emulation back-ends. A commercial emulator and FireSim both want synthesizable RTL and a host-side driver in C++ (Appendix G). A SystemC actor graph is one HLS pass from the RTL and is already C++ for the host-side seam — so the SystemC host is the one from which an actor reaches both hardware targets through existing tool chains, with no actor-DSL in between (§J.5).
These are not arguments against the SystemVerilog implementation; they are arguments for both to exist as two deployments of one topology. A useful first approximation is SystemC during architecture, synthesis, and emulation, SystemVerilog during RTL DV — but that boundary is softening rather than fixed, and because the two share the actor topology, work moves across it without a rewrite.
Every SystemVerilog primitive in actor_pkg.sv has a direct SystemC equivalent. The mapping is not approximate; the constructs match because both languages have native support for the same underlying concepts.
| Concept | SystemVerilog (actor_pkg.sv) | SystemC (actor_pkg_systemc/include/actor.h) |
| Actor base | class Actor | class Actor : public sc_module |
| Universal envelope | class MsgBase | struct MsgBase |
| Typed payload | class Msg#(T) | template |
| Mailbox | mailbox #(MsgBase) mbox | sc_fifo |
| Run loop | forever begin mbox.get(...) end | SC_THREAD(run_loop) reading from mbox |
| Spawn | fork begin run() end join_none | SC_THREAD declared in constructor |
| Topology wiring | ‘WIRE(prod, T, sub) | wire |
| Fan-out publish | publish(msg) -> try_put each sub | publish(msg) -> nb_write each sub |
| Backpressure-aware publish | try_publish(msg) -> bit | try_publish(msg) -> bool |
| Lineage stamping | stamp(this.id) | stamp(actor_id) |
| Trace ID allocation | lazy on first publish | lazy on first publish (atomic counter) |
| Lifecycle flag | bit is_alive | std::atomic |
| Cleanup hook | on_terminate() | on_terminate() |
| ‘PUBLISH macro | wraps payload, stamps, fans out | actor::make_msg |
| ‘PUBLISH_TRACED | preserves parent ancestry | actor::make_traced_msg |
The mappings are not idiomatic translations; each SystemC construct is the natural way to express the corresponding concept in C++ + SystemC. sc_fifo is what SystemC ships as a typed buffered channel with blocking get and non-blocking put. SC_THREAD is what SystemC ships as a kernel-scheduled coroutine. The actor framework is a discipline of how to use these primitives consistently — the same discipline the SystemVerilog framework codifies on top of mailbox and fork. The same table read right to left is the porting guide for any host with typed message-passing concurrency: Bluespec, Chisel, Amaranth, or a future actor-DSL.
actor_pkg_systemc/include/actor.h is a single header, roughly 200 lines, containing:
• MsgBase struct with id, trace_id, parent_span, timestamp, sender_id, plus a virtual type_name() for runtime type identification and a stamp(sender) method for lineage propagation.
• Msg
• Actor class derived from sc_module, holding a typed sc_fifo mailbox, a type-keyed subscriber map (subscribers_by_type — one subscriber vector per message type, so publish fans out only to consumers wired for that exact type), a name, an actor ID, and an is_alive flag. The constructor declares SC_THREAD(run_loop), which is the SystemC kernel’s equivalent of fork begin run(); end join_none.
• publish, try_publish, wire
• Helper functions make_msg
The header has no external dependencies beyond
actor_pkg_systemc/examples/hello_actor.cpp is the parallel of ch6_actor_examples/01_hello_actor/: two actors, one publishes, one consumes, wire
struct TickEvent { int seq; double time_ns; };
class TickProducer : public actor::Actor {
public:
explicit TickProducer(sc_module_name name) : Actor(name) {
SC_THREAD(generate);
}
private:
void generate() {
for (int i = 0; i < 5; ++i) {
wait(10, SC_NS);
TickEvent ev{i, sc_time_stamp().to_seconds() * 1e9};
publish(actor::make_msg(ev));
}
}
};
class TickConsumer : public actor::Actor {
public:
using Actor::Actor;
void act(std::shared_ptr m) override {
auto* typed = dynamic_cast*>(m.get());
if (typed != nullptr) {
const auto& ev = typed->payload;
std::cout << "[" << sc_time_stamp() << "] TickConsumer got seq="
<< ev.seq << " time_ns=" << ev.time_ns
<< " trace_id=" << m->trace_id << "\n";
}
}
};
int sc_main(int, char*[]) {
TickProducer prod("producer");
TickConsumer cons("consumer");
actor::wire(&prod, &cons);
sc_start(100, SC_NS);
return 0;
}
Run output:
SystemC 3.0.2-Accellera --- Oct 31 2025 16:51:39
[10 ns] TickConsumer got seq=0 time_ns=10 trace_id=1
[20 ns] TickConsumer got seq=1 time_ns=20 trace_id=2
[30 ns] TickConsumer got seq=2 time_ns=30 trace_id=3
[40 ns] TickConsumer got seq=3 time_ns=40 trace_id=4
[50 ns] TickConsumer got seq=4 time_ns=50 trace_id=5
[hello_actor] simulation complete at 100 ns
The same observable behavior as the SystemVerilog hello-world: five timed events, lineage trace IDs allocated lazily on first publish, fan-out from one source to one subscriber. The kernel handles concurrency, timing, and scheduling natively. The producer’s wait(10, SC_NS) and the framework’s blocking mbox.read() on the consumer’s behalf (its run_loop pulling the next message) both go through SystemC’s discrete-event scheduler — which is what --timing mode in Verilator is trying to emulate, and what commercial SystemVerilog simulators implement natively. The build is one C++17 compile-and-link (sub-second); the binary is small; verification scoreboards, coverage actors, RAL actors, and supervisors all map to additional Actor subclasses with the same shape.
The continuum of Appendix D has two steps — synthesis (Step 4) and FPGA/emulator execution (Step 5) — that other hosts reach only through a tool the language does not natively own. In SystemC both close through existing C++/SystemC tool chains, and they close onto both hardware targets Chapter 7 names.
Synthesis (Step 4) through HLS, no actor-DSL needed.
A synthesizable SystemC actor maps onto what Catapult and Stratus already accept: act() becomes a synthesizable C++ function with a fixed-cardinality sequential body, the mailbox becomes a hardware FIFO, wire
Emulation (Step 5) onto both targets. The RTL an HLS pass emits from a SystemC actor is ordinary synthesizable RTL, so it takes either back-end of Appendix G: a commercial emulator (the vendor compiler maps it; the genuine seams become generated SCE-MI transactors) or an FPGA. The second path is more interesting. A SystemC/C++ actor is a primitive latency-insensitive bounded dataflow network node (Appendix E §E.5), and the FAME-1 transform that FireSim’s Golden Gate compiler applies (Appendix G §G.2) renders such a node to a cycle-exact FireSim target. The module-level formal partial-implementation guarantee is Golden Gate’s for FIRRTL-native front-ends (Chisel); HLS-emitted Verilog reaches FireSim as a wrapped target, without inheriting that per-module proof. So one SystemC-authored actor reaches both hardware targets — commercial emulator and FireSim — through existing, published tool chains. And the host side of each seam (the SCE-MI proxy, the FireSim bridge_driver_t) is C++, which is what a SystemC actor already is: the bridge is not a language boundary, it is a normal C++ object (§below).
Why this is the clean closure. On the SystemVerilog host the continuum crosses two language/tool seams: SystemVerilog class to synthesizable RTL (an actor-DSL, not yet built), and host-side driver to the emulator (DPI). On the SystemC host neither seam is a language boundary: HLS takes the same C++; the host driver is the same C++. SystemC is therefore the host on which “one authored artifact crosses every boundary as a re-rendering, never a rewrite” (Appendix D) holds with the fewest moving parts.
The host-language-agnostic claim has a runnable artifact, and it is the one Chapter 7’s contribution turns on: the verification actors are C++ objects, and the same C++ objects check a software DUT and a synthesized-RTL DUT.
appG_firesim_substrate_swap authors its stimulus, scoreboard (an independent golden model, an expected-value FIFO, a comparator), and coverage actors once, in the pure-C++ actor tier (Appendix K — SystemC’s sibling, the same actor API without the discrete-event kernel). It then runs them against two device-under-test renderings: the accumulator as a
C++ actor, and the same accumulator as synthesizable RTL hosted by Verilator (the crossing Appendix K §K.5 develops from the C++ side). The verification actors do not change between the two runs — same wire
A SystemC rendering of those same actors would add exactly what the pure-C++ tier lacks: native timing (the wait/SC_THREAD kernel) and the HLS path to RTL (§J.5). That is the upgrade the SystemC port offers a team that wants the verification actors not only reused across substrates but synthesized through the existing SystemC HLS flow. The host-language-agnostic claim is thus concrete at two levels: the pure-C++ actors are demonstrated crossing substrates today (the substrate-swap example), and the SystemC actors are the path that carries the same code through HLS to the emulator.
The SystemC implementation enables three capabilities the SystemVerilog implementation cannot reach naturally.
Native timing without simulator-specific overhead. The SystemC kernel handles wait(), SC_THREAD, and sc_event natively. There is no --timing flag because there is no alternate non-timing mode; concurrent thread scheduling is what SystemC was designed for. Chip-scale verification with class-based actor testbenches runs at full speed against Verilator-translated RTL when the SystemC kernel drives the simulation (Verilator’s --sc flag emits SystemC modules the same kernel drives); the throughput limit Appendix D §D.10 documents disappears.
Synthesis through existing HLS tooling. As §J.5 develops: a synthesizable subset of the SystemC actor framework maps to the inputs Catapult and Stratus already accept, so the SystemC actor reaches RTL — and from there both emulation back-ends — without a new tool. Appendix E gives the synthesizable rules; they transfer to SystemC unchanged.
Polyglot ecosystem and distributed transport without DPI. A C++ SystemC binary links directly against ZMQ, NATS, libfabric, gRPC, Arrow, RocksDB, Prometheus, and the rest of the C++ ecosystem. actor_distributed_pkg’s transport bridges, which on the SystemVerilog side require DPI declarations and a separate C-side compilation, become normal C++ classes that subclass Actor and forward messages through the chosen transport. A ZmqPublisherBridge is roughly thirty lines of C++ in the SystemC port; the equivalent in the SystemVerilog port is a DPI declaration, a C-side implementation, a build-time link step, and per-platform path concerns at run time. The same property makes the host side of an emulator seam (Appendix G) a plain C++ object rather than a language crossing — the same universal seam primitive that crosses a process or machine boundary in distributed regression (Appendix L §L.4): one TransportBridgeActor actor, with the carrier (ZMQ, a SCE-MI transactor, a FireSim token channel) the only thing that changes behind it.
The next package on the methodology roadmap was actor_supervision_pkg — the Erlang/OTP-style supervisor with restart strategies (ONE_FOR_ONE, ONE_FOR_ALL, REST_FOR_ONE), restart-budget enforcement, the DeathWatcher for one-way termination notification, and the LinkRegistry for bidirectional fate sharing. actor_pkg_systemc/include/supervision.h ports all four to C++/SystemC; examples/supervision_demo.cpp runs end-to-end.
The mapping inherits the same shape. Strategy and Directive become enum class; Supervisor becomes an Actor subclass wired for ChildFailureMsg; DeathWatcher and LinkRegistry become plain C++ classes with the same maps and notification logic. One concession is the restart mechanism: rather than tearing down and respawning the SC_THREAD, the C++ port adds a virtual void reset() method to Actor; RESTART calls the child’s reset() to roll back state without thread-lifecycle gymnastics. This is simpler in C++ and equivalent in observable behavior.
The demo wires a TickGenerator -> FlakyActor -> Supervisor chain, with the flaky actor publishing a ChildFailureMsg every third tick. A separate WatcherActor monitors an UnrelatedActor via the DeathWatcher. Output:
[10 ns] flaky tick: count=1 [20 ns] flaky tick: count=2 [30 ns] flaky tick: count=3 [30 ns] flaky FAILING (publishing ChildFailureMsg up) [30 ns] flaky reset(): count 3 -> 0 [40 ns] flaky tick: count=1 [50 ns] flaky tick: count=2 [60 ns] flaky tick: count=3 [60 ns] flaky FAILING (publishing ChildFailureMsg up) [60 ns] flaky reset(): count 3 -> 0 [70 ns] watcher saw death of 'monitored' (id=5) ... (the flaky/reset cycle repeats through 180 ns) ... [supervision_demo] simulation complete at 300 ns
Six failures, six restarts, the count rolls back each time, the watcher sees the unrelated actor’s death at exactly 70 ns. The supervision pattern Erlang/OTP introduced and actor_supervision_pkg.sv ports to SystemVerilog now ports to SystemC with the same behavior — typed messages over ‘WIRE, no host-specific machinery, no special-case scheduling.
The SystemC implementation as committed is the substrate (Actor, MsgBase, Msg
The first concrete next step is the remaining actor_distributed_pkg transports — NATS, iceoryx, and libfabric as C++ bridge classes alongside the shipped ZMQ one, on the order of 50–200 lines each. After that, a synthesizable-form SystemC build driven through an HLS preset would close Steps 4–5 of the continuum on the SystemC host end to end; the appG_firesim_substrate_swap verification actors (§J.6) are the natural first workload to carry through it, since they already cross the software/RTL boundary in the pure-C++ tier.
The SystemC port is roughly 200 lines of header, 40 lines of build glue, and 70 lines of hello-world example. It is a small artifact relative to the methodology claim it supports: the actor framework is host-language-agnostic, and a team picks the host based on where it is in the flow — SystemC for architecture, synthesis, emulation, and high-throughput subsystem/SoC verification; SystemVerilog for RTL DV in the UVM-adjacent ecosystem — and the topology and verification work transfer across that increasingly blurred boundary without rewrite.
The deeper implication is that the methodology is not specific to either language, and the book now ships three renderings of the same actor as evidence: a SystemVerilog actor synthesized to iCE40 silicon (Appendix E), pure-C++ verification actors that cross the software/RTL substrate boundary unchanged (the substrate-swap example, §J.6), and this SystemC port that adds native timing and the HLS path to RTL. A team with a Bluespec, Chisel, or Amaranth background could add a fourth using the same mapping table. The framework is an architectural pattern over typed message-passing concurrency; any language with that primitive can host it, and SystemC is the host on which the whole spec-to-silicon continuum closes with one ecosystem.