SystemC emitter (Catapult, ASIC)¶
target="systemc" turns an Allo @df.region into synthesizable SystemC: one
SC_MODULE per @df.kernel, MatchLib Connections for the links between them, and a
self-contained sc_main testbench. Siemens Catapult then synthesizes that SystemC to
Verilog, and Cadence Xcelium re-runs the emitted testbench against the synthesized RTL for a
bit-exact check. Emission needs nothing but Allo; everything past it needs Catapult
(MGC_HOME) and, for cosim, Xcelium.
This backend came from the choonsik1/allo fork’s SystemC-emitter branch and was merged
with its history, so git log on mlir/lib/Translation/EmitSystemC.cpp attributes it
correctly.
Quick start¶
Emission runs anywhere the allo env runs:
# the env sets neither of these
source $(conda info --base)/etc/profile.d/conda.sh && conda activate allo
export LLVM_BUILD_DIR=/home/sk3463/llvm-allo-6b09f739/build OMP_NUM_THREADS=8
cd tests/systemc
python pc_channel.py systemc # print the generated SystemC
python pc_channel.py mlir # the MLIR it was emitted from
In Python the whole surface is one call:
import allo.dataflow as df
from allo.ir.types import int32, Stream
@df.region()
def top(A: int32[8], B: int32[8]):
fifo: Stream[int32, 4][1]
@df.kernel(mapping=[1], args=[A])
def producer(a: int32[8]):
for i in range(8):
fifo[0].put(a[i])
@df.kernel(mapping=[1], args=[B])
def consumer(b: int32[8]):
for i in range(8):
b[i] = fifo[0].get() + 1
print(df.build(top, target="systemc").hls_code) # emit only
mod = df.build(top, target="systemc", mode="csim", # compile + run
project="pc_project") # (needs Catapult)
Emitting a design of this size takes under a second. csim compiles the emitted
kernel.cpp with g++ against Catapult’s bundled libsystemc and runs it; csyn
and cosim invoke Catapult.
The larger worked example is EVA (examples/eva/), a mesh of fused router + PE nodes
emitted at 1x1 as 22 SC_MODULEs and 32 FIFOs. examples/eva/README.md has its
environment and the two commands.
How it works¶
Three emitters form a subclass chain, each overriding only what differs:
VhlsModuleEmitter ap_int, #pragma HLS ... mlir/lib/Translation/EmitVivadoHLS.cpp
└── CatapultModuleEmitter ac_int, #pragma hls_... mlir/lib/Translation/EmitCatapultHLS.cpp
└── SystemCModuleEmitter SC_MODULE + Connections mlir/lib/Translation/EmitSystemC.cpp
So arithmetic, control flow and most of a kernel body are the Vitis emitter’s; Catapult swaps
the vendor types and pragmas; SystemC replaces the structure. Emission proceeds in four
stages: the region hierarchy is flattened (an SC_THREAD cannot structurally instantiate a
sub-region), each kernel becomes an SC_MODULE with a clocked SC_THREAD run(), the
region top becomes a wiring module that declares one channel per link and binds every port,
and finally the device header and testbench are emitted.
The three modes exercise the same emitted source three ways:
mode |
what runs |
what it proves |
|---|---|---|
|
|
functional correctness, in seconds |
|
Catapult synthesizes the SystemC to Verilog |
that it synthesizes; area and Fmax |
|
the synthesized RTL runs in Xcelium against the csim golden |
the RTL is bit-exact with the C simulation |
csim and synthesis do not compile the same code – the emitter uses several
#ifdef __SYNTHESIS__ splits, the largest being that a kernel’s outermost repeat loop
becomes while (1) under synthesis so Catapult pipelines it rather than treating the body
as reset-action setup. A green csim therefore does not prove the RTL is right. Run
cosim.
Reference¶
Link primitives¶
The link type is a design decision, not a label: each maps to different RTL. Wire and
Channel are SystemC-only – every other target raises NotImplementedError.
Allo link |
RTL |
handshake |
buffered |
|---|---|---|---|
|
|
none |
no |
|
|
full valid/ready |
no |
|
|
valid only |
no |
|
|
credit/handshake |
yes |
A Wire has zero storage and zero alignment: it reads garbage unless the two kernels are
cycle-locked. valid_only drops a datum if the consumer is not looking that cycle. Both are
deliberate performance points – take them only when you own the timing.
Boundary arrays are not all alike either. A 1-D array scanned strictly in order becomes a
stream port (a[i] lowers to .Pop()); anything 2-D, strided, random or re-read becomes an
addressable memory port behind a request/response channel.
Configuration¶
key / variable |
meaning |
|---|---|
|
|
|
the Catapult standard-cell library, e.g. |
|
clock period in ns. Takes precedence over |
|
synthesize one |
|
Catapult install. Emission does not need it; |
|
SystemC headers and |
|
synchronous instead of asynchronous reset (smaller flops on the ASIC path) |
Where things live¶
path |
what |
|---|---|
|
the emitter; |
|
the |
|
the TCL generator both Catapult flows share |
|
the EVA design and its emitted project |
|
the emitter demonstrations, in Allo, with |
|
what was measured: |
|
the suite; emit cases run anywhere, csim/cosim cases skip without |
|
the emitter author’s working notes, kept whole and not rewritten |
Limits and known failures¶
Neither Catapult nor Xcelium is installed on this fork’s development host (
dev/toolchains.rst). Everything past emission is therefore unverified here: the emit-only tests pass, and everycsim/csyn/cosimcase skips. The evidence that they pass elsewhere is indev/records/systemc/reports/andtests/dataflow/COSIM_REGRESSION.md.A ``Wire`` is not a cheap ``Channel``. With no storage and no alignment it reads garbage unless the producer and consumer are cycle-locked;
tests/systemc/rtlsim/reproduces that on two simulators, and 22. The SystemC fork’s Wire is semantically incomplete – and wrong in RTL, not just in simulation records it.Bit-slicing a packed value wider than 64 bits works in csim and can fail at
csyn: Catapult rejects subclassing its builtinac_int(CIN-15), and the emittedap_intshim is such a subclass.``csyn`` needs a build subdirectory. Running Catapult in the directory holding
kernel.cppdegradesConnections::In/Outports to rawsc_signals (CIN-124, SCHD-30).tests/systemc/csyn_subdir.pyworks around it.``cosim`` does not work against a ``synth_top`` submodule. SCVerify wraps the design top, and the stimulus path into the region’s memories disappears. Measure and verify separately.
The timed dataflow simulator is not here.
SystemC-emitteralso carries a per-PE simulated-clock simulator with aget_cycles()read-out; it was not merged, because it rewrites the same code this fork rewrote for OpenMP team sizing.dev/systemc/SIMULATOR.mddescribes it.