Architecture Overview
Kathryn’s C++ implementation is one executable built from three deliberately
separated layers: the model (src/model/), which is the embedded language
itself and does nothing but elaborate an in-memory graph of hardware; the
Hybrid Simulator (src/sim/); and the Verilog generator (src/gen/).
The two backends consume the same finished model — neither one redesigns it.
This page is the map for the Developer Guide: where each layer lives, how a run
travels through them, and where the artifacts land. It is the C++ sibling of
the Rust devbook’s Architecture Overview.
flowchart TB
P["params file<br/>key = value pairs"] --> RD["readParamKathryn<br/>src/frontEnd/cmd/paramReader.cpp"]
RD --> D["start dispatches on testType<br/>src/frontEnd/cmd/cfe.cpp"]
D --> MGR["scenario manager<br/>constructs the top Module"]
MGR --> MODEL["startModelKathryn<br/>ModelController finalizes the model"]
subgraph SIM["Hybrid Simulator — src/sim"]
SI["SimInterface testbench"] --> PBM["ProxyBuildMng<br/>generate C++ · compile · load .so"]
end
subgraph GEN["Verilog generator — src/gen"]
GC["GenController<br/>initEle · routeIo · generateEveryModule"]
end
MODEL --> SIM
MODEL --> GEN
SIM --> SOUT["KOut — VCD waveforms and ZEP report"]
GEN --> VOUT["KOut — synthesizable Verilog"]
Three layers, one model
Section titled “Three layers, one model”- The model layer (
src/model/) is what the designer’s C++ actually drives: the component macros (mReg,mWire,mMod, … insrc/model/hwComponent/abstract/makeComponent.h), the hardware primitives undersrc/model/hwComponent/, the Hybrid Design Blocks undersrc/model/flowBlock/, and the aggregators undersrc/model/hwCollection/. Elaboration is orchestrated by theModelController(src/model/controller/controller.h): it keeps a module stack, a box stack, and per-type flow-block stacks, and every construct reports into it throughon_*callbacks (on_reg_init,on_wire_update,on_attach_flowBlock, …). Building a model is pure elaboration — no simulation, no file output. - The Hybrid Simulator (
src/sim/) executes the model cycle-accurately. TheSimController(src/sim/controller/simController.h) owns the event queue and cycle counter; the user-facing testbench base class isSimInterface(src/sim/interface/simInterface.h), covered in The Hybrid Simulator. - The Verilog generator (
src/gen/) lowers the same model to synthesizable Verilog under theGenController(src/gen/controller/genController.h), covered in Verilog generation.
Because both backends read one model, the design you simulate is exactly the design you generate — the property the Kride verification leans on.
The mirror rule
Section titled “The mirror rule”The backends never store their state inside model objects’ logic: each backend
wraps every hardware primitive in its own proxy class. Simulator proxies
live under src/sim/modelSimEngine/ (deriving from LogicSimEngine);
generator proxies live under src/gen/proxyHwComp/ (deriving from
LogicGenBase or AssignGenBase). The three trees mirror each other
component-for-component:
| Model primitive | Simulator proxy | Generator proxy |
|---|---|---|
Reg — src/model/hwComponent/register/register.h | RegSimEngine — src/sim/modelSimEngine/hwComponent/register/registerSim.h | RegGen — src/gen/proxyHwComp/register/regGen.h |
Wire — src/model/hwComponent/wire/wire.h | WireSimEngine — .../hwComponent/wire/wireSim.h | WireGen — .../proxyHwComp/wire/wireGen.h |
expression — src/model/hwComponent/expression/expression.h | expressionSimEngine — .../hwComponent/expression/expressionSim.h | ExprGen — .../proxyHwComp/expression/exprGen.h |
MemBlock — src/model/hwComponent/memBlock/MemBlock.h | MemSimEngine — .../hwComponent/memBlk/memSim.h | MemGen — .../proxyHwComp/memBlock/memGen.h |
Module — src/model/hwComponent/module/module.h | ModuleSimEngine — .../hwComponent/module/moduleSim.h | ModuleGen — .../proxyHwComp/module/moduleGen.h |
flowchart LR
REG["Reg<br/>src/model/hwComponent/register"]
REG --> RS["RegSimEngine<br/>src/sim/modelSimEngine/..."]
REG --> RG["RegGen<br/>src/gen/proxyHwComp/..."]
The practical consequence for contributors: changing a primitive or an HDB
means touching all three trees. Adding a feature in
src/model/hwComponent/... without its *Sim and *Gen counterparts
silently breaks simulation or generation.
Program lifecycle
Section titled “Program lifecycle”main.cpp is tiny: it reads the single command-line argument (a params file
path) with readParamKathryn(argv[1]) and hands the resulting PARAM map (a
std::map<std::string, std::string> parsed by
src/frontEnd/cmd/paramReader.cpp) to start() in
src/frontEnd/cmd/cfe.cpp. start() dispatches on the testType key to a
scenario manager — testSimple runs the src/test/autoSim/ regression suite,
testO3Sim/testKrideSim run the Kride out-of-order CPU simulations,
testGenO3 generates its Verilog, testRiscv/testGenRiscv drive the
in-order RISC-V example, and so on (see
Building and running for the full
table).
Every manager then follows the same shape, built from the lifecycle trio
declared in src/kathryn.h: startModelKathryn(), startGenKathryn(), and
resetKathryn(). The Kride generation manager
(src/example/o3/generation/O3_gen.cpp) is the canonical example:
void O3_GEN_MNG::startGen(PARAM& params){
mMod(o3GenTop, Core, 0); startModelKathryn(); GenController* genCtrl = getGenController(); assert(genCtrl != nullptr); genCtrl->initEnv(params); genCtrl->start(); //genCtrl->startSynthesis(); resetKathryn();}- Build —
mMod(...)constructs the top module; its constructor andflow()elaborate the whole design through theModelController. - Finalize —
startModelKathryn()(src/kathryn.cpp) callsgetControllerPtr()->start(), which closes out the global module’s component and design-flow phases. The model is now complete and read-only as far as the backends are concerned. - Consume — for generation,
GenController::start()runsinitEle()→routeIo()→generateEveryModule()(proxy creation, hierarchical IO routing, then file emission);startSynthesis()can optionally hand the result tosynthesisRunner/launchVivado.sh. For simulation, aSimInterfacesubclass callssimStart(), and itsProxyBuildMng(src/sim/modelSimEngine/base/proxyBuildMng.h) generates one optimized C++ translation unit from the proxies, compiles it viamodelCompile/startGen.sh, and dynamically loads the resulting.so. - Reset —
resetKathryn()clears the global IO pool and resets the model, sim, and gen controllers, so the auto-test manager (src/test/autoSim/simMng.cpp) can run many designs in one process.
Where artifacts land
Section titled “Where artifacts land”KOut/<scenario>/— all generated output, one subdirectory per scenario, addressed from the params file: simulation runs pointprefixat it (VCD waveforms, ZEP profiler report), generation runs pointgenFolderat it (<topFileName>.v). Example:params/o3GenParamssetsgenFolder = .../KOut/o3Gen.modelCompile/— the simulator’s scratch build area: generated C++ inmodelCompile/generated/, the compiled shared object inmodelCompile/build/, driven bymodelCompile/startGen.sh. ThebuildSimModeparams key (e.g.gcr) selects which of the generate / compile / run steps execute, mapping to theSPB_GEN/SPB_COMPILE/SPB_RUNflags insrc/sim/modelSimEngine/base/proxyBuildMode.h.
Both path roots are anchored on the KATHRYN_PROJECT_DIR build definition, so
runs work from the build/ directory. Never hand-edit either tree — they are
regenerated on every run. Params keys are cataloged in
Parameter files.
Where this guide goes next
Section titled “Where this guide goes next”- The model controller — the
elaboration stacks and
on_*callback protocol in detail. - The simulator JIT — how
ProxyBuildMngturns proxies into a compiled.so. - Generator passes —
initEle, IO routing, and module writing insidesrc/gen/.