nMigen – A refreshed Python toolbox for building complex digital hardware
github.com
github.com
I can't speak to the quality of my nmigen code, but earlier in the year I managed to pull together a (basic) bluetooth radio using exclusively nmigen which should demonstrate that you can build "real" things with it: https://github.com/newhouseb/onebitbt
[1] https://github.com/newhouseb/onebitbt/blob/master/research/D...
It is a nice example of nMigen usage, sadly there are not many ultra-cheap FPGA boards with 5GHz SerDes to try it.
Just a random thing I noticed reading through [1]: you might have a bug in how you calculate the magnitudes of the signals? When you assign the output of the low-pass filters on the high frequency to the input of the magnitude approximator, you assign the imaginary LPF to the imaginary input but the real LPF to also the imaginary input, which I think is incorrect? Since I’m on mobile, it’s easiest for me to show sign a screenshot: https://i.imgur.com/tAQe5Sr.jpg (If that is indeed a bug, I’m curious how that might impact the results!)
Since writing this particular notebook I've actually built out a proper CORDIC module, so once I get back to the environment where I was writing all this (I am nomadic right now w/ anemic compute) I can fix this and rerun things (and add in my CORDIC implementation).
However, I would actually make the other claim - nMigen et al don't improve the readability of a hardware block versus Verilog. Verilog does indeed suck for any parametric design though, so the Python metaprogrammability is an obvious win. A dedicated, strongly typed language where the hardware is a first class language feature (or at least appears to be via syntactic sugar/new grammar) would give the best of both worlds in my opinion.
The difficult part is (still) polyglot programs - say, if I have a module written in Silice, which includes a vendor's Verilog block ram model, which is all instantiated by an nMigen harness. LiteX makes this possible (although difficult) to simulate with Verilator, but the quick interactivity of the Jupyter workbench is lost.
Have you seen clash? It sounds like what you want. It really kicks ass - by far the most productivity-boosting and error-reducing HDL strategy I’ve seen. It’s also a subset of Haskell, so you can run your hardware code from a compiled binary or REPL before compiling it to VHDL/Verilog.
I like nmigen, but I have the same preference that hardware should be a first-class component of the language, and strong types are desirable.
Perhaps I'll give Clash a shot - the homepage describes basically all the features I'm looking for. The documentation appears to be excellent.
The learning curve is sort of inherent to the fact that the reason Clash works so well is that semantics of a pure lazy functional language with algebraic data types (very different from what most people learn) almost exactly matches the standard 01XZ semantics of digital circuitry. There’s one very small difference that I’m aware of. The nearly-ideal correspondence is honestly kind of surprising.
If you have the circuit
f :: () -> Int
f () = 3
Vs
g :: () -> Int
g _ = 3
Are the exact same thing in hardware but different in software.
“f undefined” is undefined in software but “g undefined” is not. Neither is undefined in hardware.
This distinction comes up very rarely. Only if you have a parametric circuit that sometimes takes zero-bit inputs (I.e. if the input is unused in some instantiations).
f1 :: (Bit,()) -> Int
f1 (0,()) = 3
f1 (1,()) = 3
-- f1 (0,undefined) is undefined
f2 :: (Bit,(Bit,())) -> Int
-- and so on. Conversely
g2 :: Bit -> Bit -> Int
g1 :: Bit -> Int
g0 :: Int
-- doesn't have this problem but also doesn't work well for busses.
-- The 'obvious' approach
h3 :: (Bit,Bit,Bit) -> Int
h2 :: (Bit,Bit) -> Int
h1 :: (Bit) -> Int
h0 :: () -> Int
-- doesn't work generically; (Bit) isn't a tuple at all,
-- and n-element tuples are totally unrelated to m(!=n)-element tuples
-- (they're literally declared as:
data (a,b) = (a,b)
data (a,b,c) = (a,b,c)
-- in module GHC.Tuple)
You can actually fix this by (ab)using types of kind `TYPE 'UnliftedRep` and unboxed tuples (type `(# Car, Cdr #)`, value `(# b7, b6543210 #)`), it's just generally not worth the effort.[1] https://www.lihaoyi.com/post/SowhatswrongwithSBT.html
[2] http://overwatering.org/blog/2013/12/scala-1-star-would-not-...
My understanding is that generated HDL works, but isn't ideal for highly performant for really latency sensitive applications like HFT or EM warfare. Another mark against Simulink is the relative unreadability of its generated HDL. Very challenging to optimize as a result.
When you use Migen, Chisel, SpinalHDL, you’re still writing RTL: you can define what kind of gates are being generated with the same precision as you can with Verilog. You are 100% control of pipelining and low level details.
The readability of the generated Verilog will obviously be lower but, unlike Simulink or HLS, there is nothing that is making architectural decisions for you.
Is there a tool that does what I'm describing? I assumed that a Python-to-Verilog or VHDL compiler would be useful if you could find ways to map concise Python syntax to slightly more verbose HDL constructs. That'd give you Python's expressiveness, but maintain compatibility with a bunch of vendor toolchains if you could emit something commonly supported like verilog-2001.