The Simulator JIT: Model to .so
The Hybrid Simulator gives the user view of the
JIT: buildSimMode = gcr, startGen.sh, and where the files land. This page
is the internals view — what ProxyBuildMng
(src/sim/modelSimEngine/base/proxyBuildMng.h) actually writes, in what
order, and how the compiled object is wired back into the process. The
Architecture Overview already placed the
simulator proxies in the mirror rule; here we follow them into generated code.
The proxy layer: build face and retrieve face
Section titled “The proxy layer: build face and retrieve face”Every simulator proxy implements two small interfaces from
src/sim/modelSimEngine/base/modelProxy.h:
ModelProxyBuildis the code-generation face:proxyBuildInit(),getDep(), aValRname, a clock mode, and thecreate*family —createGlobalVariable,createLocalVariable,createOp,createOpEndCycle,createOpEndCycle2,createUserMarkValue— each printing C++ into aCbBaseCxxbuilder.ModelProxyRetrieveis the read-back face: after the.sois loaded,proxyRetInit(ProxySimEventBase*)binds aValRepBaseview onto the live variable inside the shared object.
LogicSimEngine
(src/sim/modelSimEngine/hwComponent/abstract/logicSimEngine.h) implements
both for every value-carrying component; its subclasses are the mirror tree:
RegSimEngine, WireSimEngine, expressionSimEngine (lower-case, matching
the model’s expression), NestSimEngine, ValSimEngine, PmValSimEngine,
and MemEleHolderSimEngine for memory ports. MemSimEngine
(.../hwComponent/memBlk/memSim.h) derives from the two interfaces directly.
ModuleSimEngine (.../hwComponent/module/moduleSim.h) is not a proxy but
the recruiter: recruitForCreateVar, recruitForRegisVar,
recruitForMainOpVolatile, recruitForMainOpNonVolatile,
recruitForFinalizeOp, recruitForVcdVar, and recruitPerf walk the module
hierarchy and hand ProxyBuildMng flat lists of proxies per purpose.
Values in generated code
Section titled “Values in generated code”Proxies do not build syntax trees — they build C++ expression strings.
ValR (src/sim/modelSimEngine/base/simValType.h) carries a string plus a
SIM_VALREP_TYPE_ALL, and overloads the full operator set so composing
proxies composes source text. getMatchSVT picks the storage type from the
bit width: up to 8/16/32/64 bits become uint8_t/uint16_t/uint32_t/
uint64_t; anything wider becomes SVT_U64M, emitted as UintX<N> with
N = getArrSize(size) 64-bit words — UintX
(src/sim/logicRep/valRep.h) supplies the arithmetic, shifts, comparisons,
divmod, and the toBiStr() the VCD collector uses for wide signals. Each
register variable also gets a _TEMP shadow (the TEMP_VAR_SUFFIX) so
edge-triggered updates can commit at end of cycle:
// modelCompile/generated/<name>.cpp — real emitted globalsuint32_t REG10018_USER_ijImem0 = 0; uint32_t REG10018_USER_ijImem0_TEMP = 0;On the host side the type is erased: ValRepBase is a {_byteSize, void*}
view with _continLength set for UintX values, read through
getVal()/getLargeVal().
One translation unit: the write phases
Section titled “One translation unit: the write phases”SimInterface::createModelSimEvent (src/sim/interface/simInterface.cpp)
gates the three stages on the SPB_GEN/SPB_COMPILE/SPB_RUN flags decoded
from buildSimMode by getSPBM
(src/sim/modelSimEngine/base/proxyBuildMode.cpp). Under SPB_GEN it calls
startReadOldModelSim() and then startWriteModelSim(), which writes
modelCompile/generated/<TEST_NAME>.cpp — a single file defining
ProxySimEvent, the subclass of ProxySimEventBase statically declared in
modelCompile/proxyEvent.h. The phases run in this verified order:
- Preamble —
#include "../proxyEvent.h", the preservedincluderegion, and thekathrynnamespace. - Globals —
startWriteCallBackVarInit(the trigger bookkeeping array),startWriteVcdDecWriter,startWriteCreateVariable(every recruited proxy’screateGlobalVariable),startWritePerfDec(ZEP counters), and the preservedglobalVarregion. - Callbacks —
startWriteInitInternalWarmUp(theintCodeWarmUpbody),startWriteRegisterCallback(oneregisterToCallBack/registerToCallBackPerfline per variable), then thestartWriteCallBack*trio, whose generatedcheckCallBack()tests eachtrig()condition installed on the testbench and records which fired. - Collectors —
startWriteVcdDecVar/startWriteVcdColSke/startWriteVcdColfor the user and internal variants (dummy bodies when the recording policy disables one), andstartWritePerfColSke/startWritePerfColfor the profiler. - Per-clock logic —
startWriteAllLogicSim(CM_NEGEDGE)then(CM_POSEDGE). Each expands tostartWriteMainEleSimSke/...SimandstartFinalizeEleSimSke/...Sim: local_TEMPdeclarations, the volatile (combinational) proxies ordered bydoTopologySort— a DFS that aborts oncycle dep detect— then the clock-screened non-volatile proxies (screenClockMode), and finally the two commit passescreateOpEndCycle/createOpEndCycle2. A negative edge with no negedge-clocked logic collapses to an empty function. - User hook and driver —
startWriteUserDefinedFunctionemitsuserDefUserSkecontaining themarkSVreference aliases (createUserMarkValue) and the preservedmanualDesignerregion;startWriteMainSimSkeemits the long-rangedo { ... } whileloop that runs user code, both edges, the collectors, andcheckCallBack()against the cycle budget;startWriteMainSimwraps it asmainSim(); andstartWriteCreateFunccloses with theextern "C"factoryProxySimEventBase* create().
The hot functions are emitted as free ...Ske (skeleton) helpers marked with
INLINE_ATTR — __attribute__((always_inline)) inline unless the
SimInterface constructor’s reqInline argument disabled it. All statement
printing goes through the CbBaseCxx/CbIfCxx/CbSwitchCxx combinators in
src/util/fileWriter/codeWriter/cppWriter.h (how a sorted UpdatePool turns
into those statements is the subject of
UpdateEvents and the UpdatePool).
Regeneration is not destructive everywhere: startReadOldModelSim runs a
UserDefRepo (src/sim/modelSimEngine/base/userDefRepo.h) over the previous
generated file and harvests the three regions bracketed by //KDMD_<key> …
//KDMD_END comments (include, globalVar, manualDesigner). Anything a
designer hand-writes between those markers is re-emitted verbatim into the
next generation — the Verilator-style escape hatch that markSV names exist
to serve.
flowchart TB
PROX["component proxies<br/>RegSimEngine, WireSimEngine, ..."] --> MNG["ProxyBuildMng::startWriteModelSim<br/>globals, callbacks, collectors, per-edge logic"]
MNG --> CPP["modelCompile/generated/name.cpp<br/>defines class ProxySimEvent"]
CPP --> GXX["startCompile runs startGen.sh<br/>g++ -fPIC -shared -O3"]
GXX --> SO["modelCompile/build/name.so"]
SO --> DL["loadAndGetProxy<br/>dlopen and dlsym of create"]
DL --> CB["startRegisterCallBack<br/>name-to-pointer maps inside the .so"]
CB --> RET["startRetrieveSimVal<br/>ValRepBase views for the testbench"]
Compile, dlopen, retrieve
Section titled “Compile, dlopen, retrieve”Under SPB_COMPILE, startCompile() shells out with system() to
modelCompile/startGen.sh, passing the test name, the project directory, and
the OP_FLAG (-O plus the constructor’s opLevel, default -O3). The
script compiles the generated file plus three support sources fresh into
every .so — proxyEventBase.cpp, fileWriterBase.cpp, and
simResWriter.cpp — with g++ -fPIC -shared -I ../src (the full command is
quoted on the user page).
Loading is literal dlopen. loadAndGetProxy() in proxyBuildMng.cpp:
_handle = dlopen(srcDynLoadPath.c_str(), RTLD_LAZY);// ... dlerror check, then:typedef ProxySimEventBase* (*SeCreator)();SeCreator create = (SeCreator)dlsym(_handle, "create");Any dlerror prints and exits the process; the ProxyBuildMng destructor
dlcloses the handle via unloadProxy(). Under SPB_RUN,
createModelSimEvent then calls the factory, installs the VcdWriter and
recording policy, runs eventWarmUp()/intCodeWarmUp(), and calls
startRetrieveSimVal. That last step is the bridge back: the generated
startRegisterCallBack() filled the typed name-to-pointer maps in
ProxySimEventBase (callBack8 … callBack64M), and each proxy’s
proxyRetInit looks its own name up with getVal(...), sizes the resulting
ValRepBase, and caches it in its model Operable — which is exactly what
testAndPrint and the sim{ ... } blocks read and poke during the run.
Finally the loaded object joins the event queue as an ordinary event; how
SimController drives it each cycle is covered in
The simulator runtime.
Where next
Section titled “Where next”- The Hybrid Simulator — the user view: params keys, testbench hooks, VCD and ZEP outputs.
- The simulator runtime — the event queue
and cycle loop that call into the loaded
.so. - UpdateEvents and the UpdatePool — how
each CCO’s event record becomes the statements
createOpprints.