The actor framework’s claim of being distributed-by-construction has been an architectural assertion through Chapter 6 and Appendices C, D, and J. This appendix backs it with a working three-process polyglot example — a SystemC publisher running two source actors, a SystemC subscriber running a scoreboard, and a Python subscriber as a polyglot consumer — and then draws the connection the rest of the book’s hardware argument depends on: the transport bridge that crosses a process boundary here is the same TransportBridgeActor actor that crosses the software-to-hardware boundary in emulation. Distributed regression is therefore the shipped, runnable proof of the bridge mechanism Chapter 7 and Appendix G reuse at the emulator seam, and its payoff is a single regression bus that aggregates all three legs of Emergent Verification.
The demonstration runs locally as three OS processes; the same code with a different endpoint runs across a regression farm spanning machines, and — the point this appendix builds toward — across the host/hardware seam of an emulator. ZMQ is not special: ZMQ, NATS, iceoryx, and libfabric all work through the framework’s TransportBridgeActor pattern, and so does a SCE-MI message channel or a FireSim bridge. The verification topology is invariant to where each actor runs and to what kind of seam separates two actors.
appL_distributed_regression/ contains three artifacts:
• publisher.cpp — a C++/SystemC process running two WorkerActor instances and one ZmqPublisherBridge
• subscriber.cpp — a C++/SystemC process running one ZmqSubscriberBridge
• python_subscriber.py — a Python process that connects to the same ZMQ endpoint, subscribes to the same topic, deserializes the same struct layout (32-bit worker ID, 32-bit sequence number, 64-bit timestamp, 32-bit result), and prints each event.
The three processes run concurrently. The publisher fires ten events (five from each worker) over roughly half a second of wall time, separated by 100 ms wall-clock pauses to demonstrate cross-process flow. Both subscribers receive all ten events:
[publisher] starting; ZMQ pub on tcp://*:5555 [publisher] simulation complete at 1 us [scoreboard @ 0 s] worker=1 seq=0 result=1000 trace_id=1 [scoreboard @ 0 s] worker=2 seq=0 result=2000 trace_id=2 ... (8 more) [scoreboard] final counts: worker 1 : 5 events worker 2 : 5 events [python_subscriber] worker=1 seq=0 ts_ns=50 result=1000 [python_subscriber] worker=2 seq=0 ts_ns=50 result=2000 ... (8 more) [python_subscriber] final counts: worker 1 : 5 events worker 2 : 5 events
Both subscribers see exactly the same ten events. The C++ scoreboard runs inside the actor framework and reports through it; the Python subscriber runs outside the framework but consumes the same wire format and reaches the same totals. The actor framework’s distributed transport is one mechanism, polyglot by construction.
The publisher’s actor topology, in pseudocode:
worker_a (WorkerActor) ---wire---> zmq_pub (ZmqPublisherBridge) worker_b (WorkerActor) ---wire ---> zmq_pub
The subscriber’s:
zmq_sub (ZmqSubscriberBridge) ---wire---> scoreboard (Scoreboard)
Logically, the topology spanning both processes is a single graph:
worker_a ---wire---> zmq_pub ===tcp/zmq=== zmq_sub ---wire ---> scoreboard worker_b ---wire ---> zmq_pub \---> scoreboard \---> python_subscriber
The === edge is a transport bridge; the ‘WIRE edges on either side are the same kind they would be in a single-process topology. The transport bridges are themselves Actor subclasses — they participate in the framework’s lifecycle, supervision, and observability the same way any other actor does. There is no special “distributed mode” to enable; choosing a transport is choosing how a particular pair of actors communicate.
The Python subscriber does not participate in the framework; it just consumes the wire format. This is intentional. Polyglot, in this context, means that any process speaking the wire protocol observes the actor topology, regardless of whether it runs the framework natively. A Python regression dashboard, a Rust monitoring service, and a JavaScript real-time visualization all attach in the same way.
actor_pkg_systemc/include/zmq_bridge.h contains both bridge classes. ZmqPublisherBridge
The bridge does one piece of plumbing the rest of the framework does not need to know about: the slow-joiner gap. ZMQ PUB sockets drop messages until a SUB socket connects, so the bridge waits 100 ms wall-clock at construction to give subscribers time to connect. This is a property of ZMQ, not the actor framework; transports with a request-reply handshake (NATS, gRPC) do not need it. The framework absorbs this transport-specific detail inside the bridge so the rest of the topology is unaffected — which is precisely the discipline that lets the same bridge abstraction absorb a SCE-MI message channel or a FireSim token bridge without the verification actors noticing (§L.4).
The TransportBridgeActor actor in this demo crosses a process boundary. The same actor, with a different endpoint, crosses a machine boundary across a regression farm; with a Python peer on the far side, a language boundary; and — this is the connection the emulation chapters depend on — with a synthesizable target on the far side, a software-to-hardware substrate boundary. Appendix G §G.6 emits the same bridge in three realizations from one declaration:
1. The distributed-transport proxy — this appendix’s ZmqPublisherBridge / ZmqSubscriberBridge, the shipped, runnable instance.
2. The SCE-MI transactor — the same bridge against a commercial emulator, with the message channel standardized by Accellera SCE-MI rather than ZMQ.
3. The FireSim bridge — the same bridge against a FireSim FAME target, with the host side a bridge_driver_t over a token channel.
All three are the same Actor subclass at a seam; only the transport differs — a ZMQ socket, a SCE-MI message port, a FireSim token bridge. The actor on either side never learns which kind of seam separates it from its peer, because the framework’s contract is the typed message and the ‘WIRE edge, not the carrier underneath. That ignorance is what lets one verification topology span all of them: the scoreboard that counts WorkerEvents across a ZMQ socket here is, structurally, the scoreboard that counts PwrStateTransition_s across an emulator’s SCE-MI seam in Chapter 7. Distributed regression is thus not a separate capability from the emulation substrate swap; it is the same bridge mechanism, demonstrated on the transport that needs no special hardware, so the mechanism is provable on a workstation before it is deployed against a multi-million-dollar box.
The payoff of a universal seam primitive is a single regression bus. A central dashboard subscribes to a few typed message types — test-pass, coverage-update, error-event — over the same transport, and aggregates results across every source without per-source plumbing, because every source publishes the same typed messages onto the same bus.
• Formal. A bin a formal proof closes (Chapter 3) publishes onto the bus as a coverage-update; the random regression no longer chases it. The formal leg and the simulation leg share one coverage model because both publish typed bins.
• Simulation. A thousand-instance Verilator or commercial-simulator pool streams coverage and result messages as the workers of this appendix’s demo do — each instance a publisher, the dashboard a subscriber.
• Emulation. The emulator is one more backend. Its on-fabric coverage actor aggregates locally and publishes summaries through the bridge (Chapter 7’s continuous-integration-on-emulators flow); a bin left unhit by simulation may be hit by the emulator’s Linux boot the same night and closed in the merge. The bridge to the emulator is realization 2 or 3 of §L.4; the dashboard subscribes to it exactly as it subscribes to a simulation worker.
• Cloud. If the emulator is a remote pool (Chapter 7 §7.9), the bridge talks to it over a wide-area transport the same way it talks to a local box; the dashboard does not know the difference.
This is the closure Chapter 1 promised and Chapter 7’s Sprint Spiral delivered: the three legs of Emergent Verification — formal, simulation, emulation — aggregate onto one bus, one coverage model, one dashboard, with no separate emulation team and no separate emulation dashboard. The cross-leg correlation engine — file a bug when the same RTL signature fails in simulation and on the emulator, the emulator run supplying the larger trace context — is one more subscriber on the same bus. The cost of adding the emulation leg to continuous integration is the emulator capacity and its compile time; the methodology cost is zero, because the substrate that aggregates the results is the same actor bus the simulation leg already publishes onto.
Cross-process is the same as in-process.
Both processes contain identical actor types — WorkerActor, Scoreboard — and the same ‘WIRE composition style. The bridge is one extra actor in each process. The verification logic on the consumer side (the
Scoreboard) does not contain a single line of ZMQ-related code; it consumes Msg
Cross-language is a wire-format question, not a framework question. The Python subscriber matches the wire format (24 bytes of payload after the topic frame: 20 bytes of struct fields plus a 4-byte tail pad for 8-byte alignment) and immediately participates as a consumer. Tomorrow’s Rust dashboard or Go monitoring service does the same. The framework’s contract on the wire is the typed message struct; any language with a struct unpacker is a first-class consumer.
Cross-machine is a configuration question, not a code question.
The publisher already binds all interfaces (tcp://*:5555); point each subscriber at tcp://
The TLA+ neighbor: actor topology as abstract specification. The polyglot pub/sub framing here is the implementation substrate; the abstract-specification methodology that lives one rung above is TLA+ / TLC / Apalache. Chris Newcombe and the AWS DynamoDB team adopted TLA+ to write abstract specifications of distributed protocols and model-check them before any implementation; Microsoft’s P language took a parallel approach. An actor topology with typed messages is a direct candidate for the same treatment — write the protocol contract once in TLA+, model-check the safety and liveness properties at the abstract level, then implement as actors over this transport substrate. The implementation here does not preclude the abstract verification; the two compose, with TLA+ verifying the protocol’s intent and the actor topology realizing it.
ZmqPublisherBridge and ZmqSubscriberBridge are implemented; the demo uses them. The framework’s actor_distributed_pkg.sv (SystemVerilog side) declares DPI signatures for ZMQ, iceoryx, libfabric, and NATS JetStream; the SystemC side gains parallel C++ classes for each. The choice of transport depends on the deployment:
• ZMQ. Simple, fast over local TCP and UDP, mature pub/sub semantics, polyglot bindings everywhere. Default for cross-process and cross-machine within a regression farm.
• NATS / NATS JetStream. Subject-based pub/sub with durable subscriptions and replay. Right when verification engineers want to subscribe historically (“replay all alert events from yesterday’s regression”) or when the farm is geographically distributed.
• Iceoryx. Zero-copy shared-memory IPC. Right for high-bandwidth same-machine bridges, e.g. between a SystemC simulation and an FPGA-emulator host process at hundreds of MB/s message rates.
• Libfabric. RDMA-based transport for HPC-class fan-out. Right for very large regression farms where cross-machine latency matters.
• SCE-MI message channel / FireSim token bridge. The emulation-seam transports (Appendix G §G.4). The same bridge actor, the carrier dictated by the platform rather than chosen.
The actor topology is invariant across all of them. Choosing the transport is an operations decision, not an architecture decision.
• actor_pkg_systemc/include/zmq_bridge.h — templated ZmqPublisherBridge
• appL_distributed_regression/common.h — WorkerEvent struct, the wire format both processes and the Python consumer agree on.
• appL_distributed_regression/publisher.cpp — two source actors plus publisher bridge.
• appL_distributed_regression/subscriber.cpp — subscriber bridge plus scoreboard.
• appL_distributed_regression/python_subscriber.py — polyglot consumer.
• appL_distributed_regression/Makefile — builds publisher and subscriber binaries; make run-pub / make run-sub drive the demo.
• Distributed regression is an integrated capability of the actor framework, not a separate piece of infrastructure. ‘WIRE-based composition continues to work across processes and languages because the transport bridges encapsulate the network plumbing inside an Actor subclass.
• The TransportBridgeActor actor is the universal seam primitive: the bridge that crosses a process boundary here is the same one that crosses a machine, a language, and — in emulation — a software-to-hardware boundary (Appendix G §G.6). Distributed regression is the shipped, workstation-provable instance of the mechanism the emulation substrate swap reuses.
• One regression bus aggregates all three legs of Emergent Verification — formal, simulation, emulation — with one coverage model and one dashboard, no per-source plumbing (§L.5). The cost of adding the emulation leg to CI is capacity and compile time; the methodology cost is zero.
• The choice of transport (ZMQ, NATS, iceoryx, libfabric, or an emulator’s SCE-MI/FireSim seam) is an operations decision; the actor topology and the verification logic are invariant to it.
The methodology roadmap closes here: simulation, RTL DV, synthesizable form, FPGA emulation, AI-driven design, distributed regression, and — as Appendix M develops — modern AI hardware systems including GPUs, AI accelerators, and cluster-scale training and inference. Seven capability areas, one model of computation, one framework, one continuous flow from architecture exploration to silicon and beyond.