Modern hardware description languages (HDL)#
I curated a list of modern (newer than SystemVerilog or VHDL) open-source HDL tools. Click on the project name to jump to see further information.
Project |
#Stars |
#Commits last year |
---|---|---|
With less than 10 commits last year:
Warning
Code examples shown below are not mine. I copied them from the corresponding repositories/guides and pasted them for an easier overview.
Chisel#
maintained by the CHIPS Alliance which many companies and universities are members of.
an open-source book: Digital Design with Chisel by M. Schoeberl
Scala-based:
import chisel3._
class Count100 extends Module {
val io = IO(new Bundle {
val cnt = Output(UInt(8.W))
})
//- start counter
val cntReg = RegInit(0.U(8.W))
cntReg := Mux(cntReg === 9.U, 0.U, cntReg + 1.U)
//- end
io.cnt := cntReg
}
cocotb#
not for describing hardware but testbenches
An excerpt from the introductory example which exercises dut
’s clock:
import cocotb
from cocotb.triggers import Timer
@cocotb.test()
async def my_first_test(dut):
"""Try accessing the design."""
for cycle in range(10):
dut.clk.value = 0
await Timer(1, units="ns")
dut.clk.value = 1
await Timer(1, units="ns")
dut._log.info("my_signal_1 is %s", dut.my_signal_1.value)
assert dut.my_signal_2.value[0] == 0, "my_signal_2[0] is not 0!"
SpinalHDL#
the ecosystem includes a plugin-based FPGA-friendly RISC-V-based processor VexRiscv
standard library includes FIFOs, clock domain crossing bridges, buses like AHB, peripherals UART & USB
forked from Chisel decade ago
Scala-based:
package spinaldoc.examples.simple
import spinal.core._
import scala.language.postfixOps
case class Counter(width: Int) extends Component {
val io = new Bundle {
val clear = in Bool()
val value = out UInt(width bits)
}
val register = Reg(UInt(width bits)) init 0
register := register + 1
when(io.clear) {
register := 0
}
io.value := register
}
// end case class Counter
object Counter extends App {
SpinalVerilog(Counter(8))
}
Amaranth#
has an extensive ecosystem including processor generator, board files and request-for-comments infrastructure
standard library includes memory arrays, interfaces, clock domain crossing elements, FIFOs, etc
Python-based:
from amaranth import *
class LEDBlinker(Elaboratable):
def elaborate(self, platform):
m = Module()
led = platform.request("led")
half_freq = int(platform.default_clk_period.hertz // 2)
timer = Signal(range(half_freq + 1))
with m.If(timer == half_freq):
m.d.sync += led.o.eq(~led.o)
m.d.sync += timer.eq(0)
with m.Else():
m.d.sync += timer.eq(timer + 1)
return m
Clash#
compared to Chisel, Spinal, Amaranth, Hardcaml: Clash has its own synthesizing compiler. So if you write Haskell code, you get a functional simulation for free
standard library includes RAM, Mealy/Moore FSMs
maintained by QBayLogic
Haskell-based:
{-# LANGUAGE CPP #-}
module MatrixVect where
import Clash.Prelude
import Clash.Explicit.Testbench
import qualified Data.List as L
row1 = 1 :> 2 :> 3 :> Nil
row2 = 4 :> 5 :> 6 :> Nil
row3 = 7 :> 8 :> 9 :> Nil
matrix = row1 :> row2 :> row3 :> Nil
vector = 2 :> 3 :> 4 :> Nil
dotProduct xs ys = foldr (+) 0 (zipWith (*) xs ys)
matrixVector m v = map (`dotProduct` v) m
topEntity :: Vec 3 (Signed 16) -> Vec 3 (Signed 16)
topEntity = matrixVector matrix
-- See: https://github.com/clash-lang/clash-compiler/pull/2511
{-# CLASH_OPAQUE topEntity #-}
testBench :: Signal System Bool
testBench = done
where
testInput = stimuliGenerator clk rst ((2 :> 3 :> 4 :> Nil) :> Nil)
expectedOutput = outputVerifier' clk rst ((20 :> 47 :> 74 :> Nil) :> Nil)
done = expectedOutput (topEntity <$> testInput)
clk = tbSystemClockGen (not <$> done)
rst = systemResetGen
XLS: Accelerated HW Synthesis#
by Google
Rust-inspired domain-specific language (DSL) called DSLX:
fn adder8_with_carry(a: u8, b: u8) -> (u8, u1) {
let sum_with_carry: u9 = a as u9 + b as u9;
let sum = sum_with_carry[0:8];
let carry_bit = sum_with_carry[8+:u1];
(sum, carry_bit)
}
#[test]
fn adders_test() {
assert_eq(adder8_with_carry(u8:41, u8:1), (u8:42, u1:0));
assert_eq(adder8_with_carry(u8:255, u8:1), (u8:0 , u1:1));
}
MyHDL#
uses generators to model concurrency and decorators to annotate event information (e.g., triggering on a positive edge)
seems to focus on register transfer level abstraction
supports also high-level abstraction provided by Python, e.g.:
modeling a RS232 bus transmission using a function,
object-oriented modeling
memory modeling using
list
,dict
Python-based:
from myhdl import *
ACTIVE = 0
DirType = enum('RIGHT', 'LEFT')
@block
def jc2(goLeft, goRight, stop, clk, q):
dir = Signal(DirType.LEFT)
run = Signal(False)
@always(clk.posedge)
def logic():
# direction
if goRight == ACTIVE:
dir.next = DirType.RIGHT
run.next = True
elif goLeft == ACTIVE:
dir.next = DirType.LEFT
run.next = True
# stop
if stop == ACTIVE:
run.next = False
# counter action
if run:
if dir == DirType.LEFT:
q.next[4:1] = q[3:]
q.next[0] = not q[3]
else:
q.next[3:] = q[4:1]
q.next[3] = not q[0]
return logic
Bluespec HDL#
actually decades old, but still worth looking into
developed by Bluespec Inc. and open-sourced in 2020
supports writing in two languages that are similar to SystemVerilog (BSV) and Haskell (BH)
standard library includes FIFOs, vectors, lists, LFSR, clock synchronizers
Hardcaml#
embedded language inside OCaml
example from their repository:
open Hardcaml
open Hardcaml.Signal
open Hardcaml_waveterm
module I = struct
type 'a t =
{ clock : 'a
; clear : 'a
; incr : 'a
}
[@@deriving hardcaml]
end
module O = struct
type 'a t =
{ dout : 'a[@bits 8]
}
[@@deriving hardcaml]
end
# let create (i : _ I.t) =
{ O.dout =
reg_fb
(Reg_spec.create ~clock:i.clock ~clear:i.clear ())
~enable:i.incr
~width:8
~f:(fun d -> d +:. 1)
}
val create : t I.t -> t O.t = <fun>
DFiant HDL (DFHDL)#
supports three different abstraction levels
dataflow (functional simulation)
focuses on the sequence of operations based on data dependencies
does not mandate in which clock cycle each operation should start or finish
gives the compiler the freedom for pipelining
register transfer (cycle-accurate simulation)
anchors the design to specific timing requirements
different register types that we abstract away when we use higher level modeling: pipelining, CDC synchronization, clock division, state
event driven (event-based simulation)
known from VHDL/Verilog
compiles to VHDL/Verilog
Scala-based:
import dfhdl.*
@top class Counter8 extends RTDesign:
val cnt = UInt(8) <> OUT.REG init 0
cnt.din := cnt + 1
given options.CompilerOptions.PrintBackendCode = true
PipelineC#
compiler driven pipelining: a pure function (returns the same output for the same input) is autopipelined
only part of PipelineC can be compiled by C compilers
produces synthesizable VHDL/Verilog
C-based:
#include "uintN_t.h" // uintN_t types for any N
#pragma MAIN_MHZ blink 200.0
uint1_t blink()
{
static uint28_t counter;
static uint1_t led;
if(counter==(200000000-1)) {
led = !led;
counter = 0;
}
else {
counter += 1; // one 5ns increment
}
return led;
}
PyMTL 3 (Mamba)#
focuses on teaching and research
Counter example copied from here
import pyrtl
def one_bit_add(a, b, carry_in):
assert len(a) == len(b) == 1 # len returns the bitwidth
sum = a ^ b ^ carry_in
carry_out = a & b | a & carry_in | b & carry_in
return sum, carry_out
def ripple_add(a, b, carry_in=0):
a, b = pyrtl.match_bitwidth(a, b)
# This function is a function that allows us to match the bitwidth of multiple
# different wires. By default, it zero extends the shorter bits
if len(a) == 1:
sumbits, carry_out = one_bit_add(a, b, carry_in)
else:
lsbit, ripplecarry = one_bit_add(a[0], b[0], carry_in)
msbits, carry_out = ripple_add(a[1:], b[1:], ripplecarry)
sumbits = pyrtl.concat(msbits, lsbit)
return sumbits, carry_out
counter = pyrtl.Register(bitwidth=3, name='counter')
sum, carry_out = ripple_add(counter, pyrtl.Const("1'b1"))
counter.next <<= sum
sim_trace = pyrtl.SimulationTrace()
sim = pyrtl.Simulation(tracer=sim_trace)
for cycle in range(15):
sim.step()
assert sim.value[counter] == cycle % 8
sim_trace.render_trace()
PyMTL 3 (Mamba)#
I could not find a documentation in five minutes 😐
Blarney#
can leverage functional programming properties of Haskell
Haskell-based. An example: Counter.hs