verilog-polycall
v0.2.0
Published
SystemVerilog DPI-C binding for the Polycall C library (binding ABI v1).
Maintainers
Readme
verilog-polycall
SystemVerilog DPI-C binding for the Polycall
C library, binding ABI v1 (polycall.h, library >= 1.1.0). Tested with
Verilator 5 (Debian 13 verilator 5.032).
Verilog itself has no foreign-function interface; the package
verilog_polycall (src/verilog_polycall.sv) imports DPI-C functions that
the thin C adapter (src/verilog_polycall.c, src/verilog_polycall_peer.c)
forwards to <polycall.h>. Configuration parsing, RPC framing and the
polycall-peer/1 wire protocol stay in the core.
API (package verilog_polycall)
| Area | Functions |
| --- | --- |
| Library | check_abi(), verilog_polycall_abi_version(), verilog_polycall_version(), strerror(status), last_error() |
| Configuration | run_config(path) (= polycall_ffi_run_config(path, 1)), run_config_or_fatal(path), verilog_polycall_run_config_mode(path, run), verilog_polycall_describe(path, json) |
| RPC | verilog_polycall_call(endpoint, service, operation, input_json, timeout_ms, out) |
| Peer nodes | verilog_polycall_peer_open / close / endpoint / node_id / register / unregister / list / health / ping / send_text / send / recv / cancel |
| Large payloads | verilog_polycall_buffer_new / free / size / get / set, verilog_polycall_peer_send_buffer, verilog_polycall_peer_recv_buffer (chandle buffers, up to 1 MiB) |
Every function returns the core's status unchanged (POLYCALL_OK = 0,
POLYCALL_E_* < 0, as localparams in the package); strerror(status)
names it and last_error() returns the core's detail of the latest failure.
Optional string arguments use "" for "not given" (no bind = send-only
node, no token, generated message id). peer_send / peer_recv take
byte unsigned open arrays (binary-safe); for payloads larger than a few
KiB use the chandle buffers, because a simulator copies an unpacked array
element by element at every DPI call.
import verilog_polycall::*;
int h, rc, len; string ep, from, id; byte unsigned data[256];
run_config_or_fatal("verilog-polycallrc");
rc = verilog_polycall_peer_open("hdl-node", "127.0.0.1:0", token, h);
rc = verilog_polycall_peer_send_text(h, "127.0.0.1:9001", "hello", "m-1", 5000);
rc = verilog_polycall_peer_recv(h, 5000, from, id, data, len);Build and test
The core is found with pkg-config (PKG_CONFIG_PATH=<prefix>/lib/pkgconfig,
LD_LIBRARY_PATH=<prefix>/lib, polycall CLI on PATH).
make # compile the adapter (C11, -Werror)
make test # tests/run-real.sh: real-core testbench (exit 77 = SKIP if a tool is missing)
make test-loader # loader behaviour: real library, no library, ABI-2 / 1.0 fixture libraries
make test-mock # ADAPTER UNIT TESTS against a mock core (clearly not the core)
make example # examples/basic.sv against the installed library
npm test # package, thin-adapter check, mock and real-core tests, each PASS / FAIL / SKIPtests/run-real.sh starts polycall start, polycall daemon start (private
state directory) and a polycall peer serve C node on ephemeral loopback
ports, builds tests/polycall_real_tb.sv with verilator --binary -Wall
and runs it (optionally under SIM_WRAPPER="valgrind ..."; TB_CFLAGS /
TB_LDFLAGS add sanitizer flags). The testbench covers: version + ABI;
run_config valid / missing / invalid / strict / unsupported TLS, also on a
non-ASCII (UTF-8) path; describe; polycall_call against polycall start
and polycall daemon (success, unknown operation, remote error, deadline,
invalid input, timeout_ms 600000 accepted and 600001 / 0 / -1 rejected);
two nodes in both directions with sender and message id verified; empty,
UTF-8, binary-with-NUL, exactly 1 MiB and 1 MiB + 1 payloads; 63/64-byte
ids, array and buffer length limits; registry ownership; duplicate id
delivered once; auth failure; dead peer; receive timeout; too-small buffer
keeps the message; cancel and close waking a receive blocked forever;
concurrent senders and calls; double close, calls after close, invalid
handles; and interop with the C node in both directions (verified at the C
side with polycall peer recv).
Threads. A Verilator model makes every DPI call on its simulation
thread, so SystemVerilog code cannot itself cancel a receive it is blocked
in. The adapter is thread-safe (per-thread result storage, released when a
thread exits): the testbench proves cancel/close waking a blocked receive and
concurrent senders/callers with test-only helper OS threads
(tests/tb_support.c) that call the binding's own DPI-C functions while the
simulation thread is blocked in another binding call. Tested on Linux
(Debian 13, Verilator 5.032; valgrind and ASan+UBSan clean; TSan clean
apart from one suppressed race inside Verilator's own thread-pool start-up). Windows
and other simulators (Questa, Xcelium, VCS) are untested: none is available
to QA.
Loading the library
The Verilated model links libpolycall at build time, so the loader checks
it first: with no library the simulation does not start (exit 127,
libpolycall.so.1: cannot open shared object file), and a 1.0 library
without the ABI v1 symbols is refused (exit 127, undefined symbol:
polycall_...). A library reporting another binding ABI makes check_abi()
and run_config() return POLYCALL_E_UNSUPPORTED. make test-loader
checks all four cases.
npm
verilog-polycall (not yet published) ships these sources;
require() returns absolute paths for build tooling and loads nothing.
License: MIT
