An open-source high-level synthesis tool that accepts idiomatic Python

Recently I’ve been working on a certain embedded system that has an FPGA running EKF and some controls. Coding that in RTL is inefficient at best so I turned to HLS and looked around to see what the industry has to offer. I have a pretty extensive simulation and verification scaffold in Python, so ideally I wanted something that can accept Python directly, preferably with minimal adaptation, so that I could feed relevant parts of my Python models to the HLS tool and immediately get a working RTL out. Also I wanted floating point. I should add that my FPGA here is Lattice ECP5 which means that Vitis et al are not an option, so I was focusing on vendor-agnostic tools. There are some that can ingest Python but they are frankly not really usable in practice aside from perhaps a very narrow set of applications – I evaluated Polyphony, PyLog, Allo+XLS, Allo+Vitis (not really an option because Lattice), and Veriloggen. They work in the sense that they translate some Python into some RTL, but not in the sense that you can get something immediately useful out of them if you’re trying to build, say, a Kalman filter or even a basic PID controller. Then there are highly capable tools like the aforementioned XLS, Bambu, etc. that unfortunately don’t support Python and there are a few shortcomings (especially when it comes to ECP5) that I might cover someday in a later post.

So, behold Holoso:

To me personally this is a big deal because it has already enabled dramatic acceleration of my work. The feature set is currently mostly defined by my immediate needs but it is extensible and contributions are welcome.

I will spare a detailed explanation of how it works (follow the link for that) but the basic idea is that it parses Python, constructs a CFG, identifies which operators are needed, constructs a minimal specialized VLIW core, schedules microcode (fully statically to keep the core simple), and emits Verilog along with some extra outputs like Cocotb and reports. There are examples included that will give you a better feel of what it’s like; there are even some exotic ones like UART rx/tx, which I would not call a sensible use of this tool but it’s mostly there to outline its limits more clearly – it’s an HLS, not an RTL, so keep that in mind.

I have already benchmarked it side by side with Bambu, XLS, Dynamatic, and Vitis, and the results seem decent; I am going to post about this later if there’s interest (still working on this).

An online demo is available at holoso.digital (runs in your browser along with Yosys compiled into webassembly).

Holoso is available under Apache-2 with explicit legal waivers for generated RTL.

Hopefully someone will find it useful as well!

Nice to see different approaches, Python as a HLS does indeed a good job.

Unfortunately, your demo stalled at loading numpy, scipy, but that might just be my oldish development machine or webassembly restrictions. Just some inspiration:

  • Pack demos into jupyter notebooks and runnable containers that play with mybinder.org
  • Explore Co-Simulation options using CXXRTL for grainier control and elegant co-routines in Python

The verification part of such new language or generator concepts is quite crucial, so it might be interesting to see how that’s handled.

Thanks for checking out the demo!

…sorry it failed though. I don’t think this problem is reproducible so either it was transient or it’s something specific to your environment. We’re going to look into mybinder, thanks for the pointer.

Indeed it is crucial. At the high level the problem is quite tractable: feed inputs to the original Python and to the generated RTL in simulation (Icarus+Verilator), ensure they match, or stay within some small error bounds if floats are used. In practice it gets a bit nuanced though.

Fully end-to-end tests are impractical to construct automatically for stateful modules or for modules involving floats, so those are hard-coded for a few dozen of representative kernels (including the examples). They are hard to build automatically because recurrences and floats often reveal degenerate cases where results may differ significantly while this remains immaterial to the application (e.g., functions with large local gain – exp/log for large/small arguments, tan near quadrant boundaries, IIR filters, rounding, etc).

Automatic testing is possible once you go a few IR levels down, to the MIR level, where the hardware operators are already resolved and the CFG is fully defined, such that any precision loss caused by narrower float formats or by non-commutative/associative math transforms (fastmath-style optimizations) is already factored in. This lets you simply inject random test vectors and obtain bit-exact results across MIR and the final simulated RTL. This MIR–RTL cosimulation straddles most of the compiler transforms, including by far the largest and the most complex part – the LIR with its scheduling/regalloc/etc, that I spent the most time building and am least confident in.

Then there is the simplest low-level cosimulation part that can be 100% automated away cheaply, and which many HLS suites I’ve seen do – emit a cosim test bench alongside the generated RTL that can be run in a black-box fashion without any knowledge of the semantics of the generated module. While cheap and cycle-accurate, it is of limited utility because it is blind to defects occurring anywhere at or above the LIR.

This is still unstable at the moment and eventually I am considering extending the current low-level cosim with a wider-reach alternative that compares the generated RTL against the original MIR. There is no solution in sight for automatic tests covering the frontend-to-MIR transforms, so that one remains the riskiest part.

Thanks for the insights, I guess I recognize some of those issues, but went for fixpoint arithmetics in the first place to avoid simulation mismatches.

The part I’m most interested in is the formal verification of the constructed pipeline. So I’ve been dabbling with Python notation of an intermediate, explicit pipeline description, but already got consumed by more complicated topics where the pipe would stall (inserting bubbles), or rotate data lanes in order to obtain best balance, let aside VLIW-microcode and start/stop conditions. But this is getting off topic WRT yosys, I guess. I did not use yosys formal verification framework yet, might also be interesting to explore in order to raise the confidence level.

WRT the yosys infrastructure, I found CXXRTL the most flexible option so far as it closely reflects the generated RTL structure and can be lock-stepped elegantly against a working reference. Likewise, confidence level turned out to be high compared against “golden” HDL simulators. Unfortunately, there seems no official Python binding for it, I had to hack one in Cython, pybind would probably be the better way to go nowadays.

Handling the IR translation was found tricky here as well, I ended up with an executable python IR notation for the RTL generation part. That at least translates into yosys RTL without too much maintenance overhead. But enough OT :slight_smile:

1 Like