There is a common assumption that serious RTL development eventually has to live behind proprietary tools. For some parts of the ASIC/FPGA flow, commercial tools are still part of the world we tape out into. But that does not mean the day-to-day design loop has to start there.
For a large part of the work, we can model, generate, lint, simulate, synthesize, and get early timing feedback with open source tools. More importantly, we can do it in a way that is repeatable, composable, and close enough to implementation reality to make good engineering decisions early.
This is the workflow we try to use at HardMatrix: start with a model, write maintainable RTL, verify it with lean Python tests, run fast simulation, check synthesis and timing, and keep the whole thing described in a way that can move between tool backends.
Golden model in Python
We usually start by modeling the behavior before writing RTL. The model gives us an independent reference for what the block should do, and it keeps algorithm work separate from clocks, resets, and handshakes.
We covered this workflow in more detail in Lightweight RTL Verification Flow with cocotb, so the point here is just where it sits in the stack: the golden model is the first executable artifact, and the later verification phases reuse it instead of rebuilding expected behavior by hand.
RTL development with Chisel
For some blocks, handwritten SystemVerilog is still the right tool. For other blocks, the structure is easier to express as a generator. That is where Chisel is useful.
Chisel lets us describe parameterized hardware in a host language with real abstraction tools. Repeated structures, generated pipelines, width-dependent logic, and families of related blocks are easier to write and easier to maintain when the repetition lives in the generator instead of being copied through the RTL.
The generated Verilog still has to be normal backend-friendly RTL. That is the key point. We do not want a design flow where the only thing that understands the hardware is the generator. The output should be readable enough to inspect, friendly enough for simulators and synthesis tools, and structured enough to pass through the rest of the flow.
Generated RTL should land in the same place as handwritten RTL: linted, simulated, synthesized, timed, and wrapped in the same project structure. Whether the source was Chisel or SystemVerilog, the resulting block has to meet the same interface and implementation expectations.
Cocotb for verification
Cocotb gives us a lean way to verify RTL from Python. We can drive transactions, wait on clocks, apply backpressure, randomize packet boundaries, and build expected output from the same Python models we used while designing the block.
This keeps tests close to the behavior we care about. Instead of writing a heavy verification environment for every block, we can write focused tests around standard interfaces and independent expected results.
cocotb-bus helps with the repetitive parts. Reusable drivers, monitors, and scoreboards for interfaces such as AXI4-Stream keep the tests focused on behavior instead of rewriting handshake code. A lot of hardware bugs are interface bugs, so having reliable monitors at the boundary matters.
This is also why we prefer standard interfaces where possible. A block with clean streaming ports can be tested alone, dropped into a larger pipeline, and checked with the same kind of monitor at each boundary.
Verilator and Verible for fast feedback
Verilator is the simulator we want in the normal edit-test loop. It is fast, easy to run locally, and works well in CI. That matters because slow feedback changes how people write hardware. If a test takes too long to run, it becomes a nightly check instead of a design tool.
We use Verilator for cocotb simulation and as one source of lint feedback. Verible can sit next to it as a focused SystemVerilog linting and formatting tool. Together they catch a different set of issues from behavioral tests: width mistakes, unused logic, unsupported constructs, style drift, and code that is likely to become painful later. Simulation then checks whether the block behaves correctly under realistic interface timing.
The goal is not to make Verilator the only simulator we ever use. The goal is to make the cheap path good enough that we run it constantly. Fast local simulation makes design iteration much less speculative.
Register maps with SystemRDL
Control and status registers deserve the same treatment as datapath RTL: one source of truth, generated views where possible, and tests that interact with the design through the same interface the real system will use.
For register maps we like SystemRDL because it is focused on describing registers rather than forcing the register map to be implied by handwritten RTL. A register description can drive the hardware-facing view, software-facing view, documentation, and verification hooks without letting those copies drift apart.
Sometimes the register map is not ours to define. If a design has to plug into an external register interface or an existing SystemVerilog package, pyslang is a useful escape hatch. We can parse packed SystemVerilog structs from the package that defines the register-facing interface, expose those structs as Python classes, and pack or unpack them directly in cocotb tests.
That keeps the testbench aligned with the actual RTL-facing register layout even when the register map source is outside our control.
Yosys, OpenSTA, and OpenROAD
Simulation is necessary, but it is not enough. RTL that simulates correctly can still be hard to synthesize, too large, or shaped poorly for timing.
Yosys gives us an open synthesis step. With yosys-slang, we can handle practical SystemVerilog rather than reducing examples to a narrow language subset. Synthesis catches structural problems that simulation will not always expose, and it gives early area and logic-shape feedback.
OpenSTA adds timing feedback. Paired with an open cell library such as ASAP7, it gives us a way to ask useful implementation questions early: does this block map cleanly, where is the critical path, and is a design choice too expensive?
If we need a fuller open backend, OpenROAD is the natural next piece. It extends the flow beyond synthesis and static timing into physical design steps such as floorplanning, placement, clock tree synthesis, routing, and post-route analysis.
None of this is a replacement for a final signoff flow. It is a way to keep implementation feedback close to the RTL while the design is still cheap to change.
FuseSoC puts it together
The stack only works if it is easy to run. FuseSoC is the layer that keeps the flow from turning into a pile of scripts.
A FuseSoC core can describe RTL files, generated files, interface dependencies, testbench files, parameters, and targets. One target can run lint with Verilator or Verible. Another can run Verilator plus cocotb. Another can run Yosys synthesis. Another can run ASAP7/OpenSTA timing.
The same idea is in our hardmatrix-examples repository. The AXI4-Stream Ethernet FCS core already shows the basic shape with lint and simulation targets:
$ fusesoc run --target lint hardmatrix:examples:axis_eth_fcs_insert:0.1.0
$ fusesoc run --target test hardmatrix:examples:axis_eth_fcs_insert:0.1.0
The same core structure can be expanded with synthesis, timing, or backend-specific targets as the example grows.
FuseSoC also keeps the open flow from becoming a dead end. It uses Edalize to generate tool-specific build directories, so the same core structure can point at different backends. In our projects that usually means Verilator, Yosys, and OpenSTA for the open loop, but the same description can also target commercial tools such as Cadence Xcelium, Xilinx Vivado, or a customer ASIC flow when the project needs it.
That separation is valuable. The design description stays close to the RTL. The target chooses the tool. Moving from an open simulator to a commercial simulator should not require rediscovering the file list by hand.
Closing thoughts
An open flow lets us share examples, reproduce bugs, run tests without license servers, and bring up new blocks quickly. More importantly, it gives a small team a practical full-stack RTL development loop, from the first executable model through implementation feedback, without depending on a proprietary environment for every step.
That is enough to ship competitive RTL. Not because the tools solve the hard problems by themselves, but because they make the right loop cheap: write a block, test it against a model, compose it with other blocks, synthesize it, inspect timing, and repeat.
The more of that loop we can keep open, the easier it is to build hardware that other people can understand, run, and trust.