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.
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.