FlexSoC is an open-source digital-design orchestration framework for speeding
up ASIC IP and small-SoC development from one command-line interface: fx.
The value of FlexSoC is not one individual generator or EDA command. It is the layer that keeps specifications, RTL, generated collateral, verification, filelists, constraints, logs, and external tools connected as the design changes.
A real hardware project rarely moves in a straight line. A register map changes, a port is added, a block becomes multi-clock, a test exposes a bug, or an IP is reused inside a larger SoC. Each change can invalidate several downstream artifacts. FlexSoC gives those dependencies an explicit workflow so the designer can regenerate what is derived, preserve what is authored, and rerun the right checks without rebuilding the project flow by hand.
Digital-design work spans several interfaces at once:
specification / HJSON
β
register RTL + model CSR API + automatic CSR formal
β
RTL hierarchy βββ Slang filelists βββ lint / structural analysis
β
Design Verification
βββ functional: SV + cocotb + Verilator coverage
βββ formal: BMC / PROVE / COVER with SymbiYosys
βββ CDC / RDC structural analysis [planned]
β
Implementation
βββ syn/<pdk>
βββ pnr_openroad/<pdk>
β
Design Signoff
βββ equivalence/<pdk>: EQY RTL β post-synthesis netlist
βββ post-synthesis / post-PnR GLS
βββ SDF / STA
βββ power
β
validated IP βββ reusable source block βββ larger SoC/system
Without an orchestration layer, the interfaces between those stages become project-specific shell scripts, copied filelists, duplicated register offsets, stale generated files, and different assumptions in each tool. FlexSoC turns those interfaces into named targets and explicit source/generated ownership.
The goal is simple: spend time on architecture, RTL, verification, and design trade-offs instead of repeatedly wiring tools and collateral together.
π‘ FlexSoC is the glue layer: open-source EDA tools stay visible and replaceable, while the project structure, generated collateral, and hand-authored design intent stay synchronized.
A design run is isolated by RUN_TOP and RUN_ID, with a predictable tree for
register data, RTL, models, tests, simulation, synthesis, signoff, and logs.
The workspace can live in the repository or anywhere else with --workdir.
HJSON is the source of truth for software-visible registers. FlexSoC connects it to:
- generated register RTL;
- register documentation;
- model-side CSR objects and field encoding;
- software driver generation where used;
- single- or multi-domain register maps.
A register change therefore does not require copying offsets and masks into the model or tests. Regenerate the derived collateral instead.
FlexSoC can bootstrap editable RTL cores and wrappers for new IPs, refresh a generated wrapper after port changes, and preserve deliberately custom RTL for existing IPs.
The canonical fx flist flow uses Slang to elaborate downward from the
selected top, trim unreachable RTL, and topologically order the reachable
sources. FlexSoC then preserves the useful project split:
rtl_common.f # shared packages, primitives and TL-UL infrastructure
rtl_ip.f # reachable IP/run hierarchy
Both are real command files, including the include paths required by downstream tools.
Run the complete suite:
fx lint_suiteor choose the frontend without changing the project flow:
fx lint_suite --set LINT_TOOL=verilator
fx lint_suite --set LINT_TOOL=slangFocused latch, width, unconnected, undriven, and unused diagnostics remain
available as separate targets. Tool output is retained under logs/lint/ while
the terminal stays compact.
Single- and multi-clock model workspaces use the same ownership contract:
dv/functional/model/
βββ <top>_model.py # editable behavioral/reference model
βββ <top>_regmap.py # generated from HJSON; do not hand-edit
βββ <top>_tests.py # editable scenarios + vector generation
This matters during development: tests can grow without changing the reference model, and an HJSON-only change can refresh the CSR API without overwriting behavioral code.
Every generated scenario becomes explicit files:
dv/functional/tests/<TEST_NAME>/
βββ config.regs
βββ data_in.vec
βββ data_out.vec
The same vectors are consumed by the generated SystemVerilog and cocotb infrastructure. Tests can mix direct pins with CSR writes/reads. Single-clock flows can check outputs at a fixed model latency or on a named output-valid event; multi-clock flows can use event-driven completion across asynchronous domains.
CLOCK_MODE=single and CLOCK_MODE=multi keep the high-level command vocabulary
consistent while selecting the appropriate register, RTL, model, testbench,
formal, and constraint behavior underneath.
Multi-clock closure will also grow explicit CDC and RDC structural analysis. These are intentionally separate from functional coverage and formal proof percentages.
FlexSoC keeps the DV branches explicit:
functional DV scenarios + RTL simulation + code coverage
formal DV CSR/design properties: BMC / PROVE / COVER
CDC / RDC structural domain verification [planned]
signoff equivalence RTL β mapped netlist proof with EQY
fx regression runs the generated catalogue on SystemVerilog and cocotb and
reports Verilator coverage as a scope Γ type matrix (line, toggle, expr,
branch, fsm, user, total). fx formal runs CSR and authored-property
BMC/PROVE/COVER. After synthesis, fx eqy runs the sign-off equivalence branch
and reports EQY partition closure separately from code coverage. Failed EQY partitions are diagnosed with the unified fx eqy_debug command; waveform and raw artifacts are available as options of the same debugger.
See Design verification for RTL/property verification and Design sign-off for PDK-scoped synthesis handoff, EQY, GLS, STA, SDF, power, and counterexample debug.
The same run can continue into:
- Yosys synthesis;
- SDF generation;
- OpenSTA timing analysis and explicit global-activity power estimates;
- corner-oriented STA plus explicit
power_estimatetargets; - OpenROAD-oriented PnR collateral and runs.
Logs are separated by responsibility:
logs/
βββ dv/functional/
βββ lint/
βββ synthesis/
βββ signoff/
hw/ips/<top>/ is the versioned library of reusable IP source collateral.
Existing IP support is not a separate end goal: it lets a validated block keep
its custom RTL/model/tests, be regression-tested standalone, and then be staged
unchanged into a larger SoC or system.
This is the bridge from IP development to IP reuse.
SoC runs stage reusable IPs, generate top-level integration collateral, build a Slang-resolved SoC filelist, and own their own top-level verification, constraints, software, and signoff assumptions.
Slang-backed analysis targets can inspect an arbitrary hierarchy from a top file and search root:
fx slang_hier --set SLANG_TOP_FILE=/path/top.sv --set SLANG_ROOT=/path/rtl
fx slang_ast --set SLANG_TOP_FILE=/path/top.sv --set SLANG_ROOT=/path/rtl
fx slang_flist --set SLANG_TOP_FILE=/path/top.sv --set SLANG_ROOT=/path/rtlThese complement the canonical flow without replacing its rtl_common.f /
rtl_ip.f outputs.
FlexSoC is designed for iteration, not only first-time generation. For example, if a project changes its register map:
edit <top>.hjson
β
βββ fx reg doc --force
β βββ register RTL
β βββ register documentation
β
βββ fx regmap_py --force
β βββ <top>_regmap.py
β
βββ adapt RTL/model/tests only if semantics changed
β
βββ fx tests_gen --force
β βββ regenerated vector tests
β
βββ fx flist --force
βββ fx lint_suite
βββ fx regression
βββ fx coverage_detail
βββ fx formal
βββ if hardware changed:
fx syn --force
fx eqy --force
fx sdf sta power_estimate --force
The important distinction is between authored source and derived collateral. FlexSoC makes that boundary explicit so a local change does not require blindly regenerating everything or accidentally overwriting custom work.
See Project lifecycle and change propagation for concrete change scenarios such as register-map edits, port changes, behavioral changes, and IP-to-SoC reuse.
FlexSoC orchestrates open-source tools rather than hiding them behind a closed flow. Depending on the target, the current backend integrates tools including:
- Slang for hierarchy elaboration, dependency/filelist generation, AST and hierarchy analysis, and optionally lint;
- Verilator for lint and SystemVerilog simulation;
- cocotb for Python-driven verification over the same generated vectors;
- lowRISC reggen for HJSON register collateral;
- Yosys and its Slang frontend for synthesis;
- SymbiYosys for CSR and authored-property BMC/PROVE/COVER;
- EQY for RTL-to-post-synthesis equivalence partitioning and proof;
- sv2v where Verilog conversion is useful;
- OpenSTA for static timing and global-activity power estimates;
- OpenROAD for physical-design-oriented flows.
FlexSoC's job is to keep the project representation and assumptions consistent between those tools.
uv sync
source .venv/bin/activate
fx --help
fx commandsfx settings TOP=test RUN_TOP=test RUN_ID=dev HOST=uart CLOCK_MODE=single
fx setup --force
fx hjson --force
fx reg doc --force
fx rtl_stub --force
fx top_from_core --force
fx flist --force
fx lint_suite
fx setup_model --force
fx tests_gen --force
fx setup_tb setup_cocotb --force
fx regression
fx coverage_detail
fx formal
fx syn --force
fx eqy --force
fx sdf sta power_estimate --force
fx metrics check --forcesetup_model --force is a bootstrap/reset operation. Once <top>_model.py and
<top>_tests.py contain authored work, use selective regeneration such as
regmap_py --force for HJSON-only changes instead of resetting the whole model
workspace.
A reusable IP lives under:
hw/ips/<top>/
For standalone regression it is copied into an isolated run with fx ip_load.
The standard existing-IP flow intentionally preserves source-owned HJSON, custom
RTL/top, behavioral model, and tests while regenerating explicitly derived
collateral such as register RTL/docs, filelists, and <top>_regmap.py.
When RUN_TOP is a larger SoC, the same source IP can be staged under that SoC
run instead of replacing the run itself. See
Existing IP development and
SoC development.
A run normally looks like:
<WORKDIR>/runs/<RUN_TOP>/<RUN_ID>/
βββ data/ # HJSON register specifications
βββ doc/ # generated register documentation
βββ dv/ # functional DV and PDK-independent property formal
βββ analysis/ # Slang analysis; CDC/RDC next-step structural closure
βββ rtl/ # RTL implementation and Slang-ordered filelists
βββ logs/ # lint / DV / synthesis / signoff
βββ syn/ # synthesis collateral/results
βββ signoff/ # equivalence / SDF / STA / power, each scoped by PDK
βββ pnr_openroad/ # physical-design collateral
See Folder structure and ownership for the full source-versus-generated contract.
Read the docs by intent:
- Quickstart β shortest runnable paths.
- Project lifecycle and change propagation β how changes propagate and which targets to rerun.
- Single-clock IP development β detailed generated-IP workflow.
- Existing IP development β preserve and regression-test reusable source IPs.
- Multi-clock IP development β domain-specific register, verification, and timing behavior.
- SoC development β reuse validated IPs in larger systems.
- Folder structure and ownership β where source, generated collateral, logs, and run outputs belong.
Run the real command/tool regression:
pytest -s tests/test_e2e_fx.pySkip synthesis/signoff while iterating on frontend or verification changes:
pytest -s tests/test_e2e_fx.py --no-signoffUse another E2E workspace base when needed:
pytest -s tests/test_e2e_fx.py --e2e-root ~/flexsoc-e2eor:
FLEXSOC_E2E_ROOT=~/flexsoc-e2e pytest -s tests/test_e2e_fx.pySuccessful temporary workspaces are removed automatically. Failed workspaces are retained for debugging.
