A Common Lisp toolchain for digital hardware design. Describe hardware in a language-neutral IR, emit portable Verilog-2001.
# Generate Verilog from a design
fab usb-keyboard.lisp
# Generate + compile to FPGA bitstream
fab --board tangnano9k usb-keyboard.lispDownload or build the fab binary:
# Build from source (requires SBCL + Quicklisp)
git clone git@github.com:turtle-bazon/fab.git
cd fab
make build # produces build/fab
cp build/fab /usr/local/bin/# Yosys (synthesis)
sudo apt install yosys
# nextpnr-himbaechel + gowin_pack (place & route + bitstream)
pip install yowasp-nextpnr-himbaechel-gowin apyculafab [options] <DESIGN>
Options:
-o, --output-dir DIR Output directory (default: build)
-b, --board-dir DIR Board search directory (may be repeated)
--board NAME Compile with FPGA toolchain after generation
fab examples/03-usb-keyboard/usb-keyboard.lisp
fab -o rtl/ examples/03-usb-keyboard/usb-keyboard.lispfab --board tangnano9k examples/03-usb-keyboard/usb-keyboard.lispThis runs: fab (generate) → yosys (synthesize) → nextpnr (place & route) → gowin_pack (bitstream).
fab -b /usr/share/fab/boards --board tangnano9k design.lispA design is a .lisp file with (fab) forms. Design files are board-agnostic — pin mappings go in a separate binding file.
;; my-design.lisp — board-agnostic design
(in-package :fab)
(fab
(module my-design
:ports ((clk :input)
(led :output))
:assigns ((led clk))
:body ()));; tangnano9k.lisp — board binding (in same directory)
(in-package :fab)
(fab (board-target my-design :board :tangnano9k))(fab
(module name
:depends (dep1 dep2) ;; auto-load dependency modules
:ports ((port-name direction width :reg) ...)
:signals ((sig-name :wire width) ...)
:params ((name value) ...)
:localparams ((name value) ...)
:assigns ((lhs rhs) ...)
:body ((statement ...) ...)))Maps design ports to physical FPGA pins. Lives in a separate file (e.g., tangnano9k.lisp) next to the design:
(fab (board-target my-design :board :tangnano9k))The fab binary auto-discovers <board>.lisp in the design's directory when --board is specified.
:input, :output, :inout
(sig :wire) ;; 1-bit wire
(sig :wire 8) ;; 8-bit wire [7:0]
(sig :reg) ;; 1-bit reg
(sig :reg 8) ;; 8-bit reg
(sig :reg 8 :init 0) ;; 8-bit reg with initial value:assigns ((led clk) ;; assign LED = CLK;
(out (logand a b))) ;; assign OUT = A & B;;; Combinational
(always-comb sensitivity-list (statement ...))
;; Sequential
((always (posedge clk) (statement ...))) ;; with reset
((always (posedge clk (negedge rstn)) ...)) ;; async reset(= lhs rhs) ;; blocking assignment
(<= lhs rhs) ;; non-blocking assignment
(incf x) ;; x = x + 1
(decf x) ;; x = x - 1
(if condition (then ...) (else ...))
(case expr (val1 ...) (val2 ...) (otherwise ...))
(for ((i 0) (< i 8) (incf i)) (body ...))
(begin (stmt1) (stmt2) ...) ;; block(logand a b c) ;; a & b & c
(logor a b) ;; a | b
(logxor a b) ;; a ^ b
(lognot a) ;; ~a
(slice expr hi lo) ;; expr[hi:lo]
(concat a b c) ;; {a, b, c}
(zero-extend expr width) ;; {W'h0, expr}
(sign-extend expr width) ;; {{W{expr[W-1]}}, expr}(instance module-name (inst-name)
((param val) ...) ;; parameters (or nil)
((port expr) ...)) ;; port connectionsString module names preserve case (for Gowin primitives like "rPLL").
:tasks ((name ((param kind width) ...) (body ...)))
:functions ((name ((param kind width) ...) :returns (:reg width) :body (...)))
;; Call by name:
(task-name arg1 arg2)
(func-name arg1)#\U ;; → 85
#\Return ;; → 13
#\Newline ;; → 10Boards live at boards/<name>/<name>.lisp:
(in-package :fab)
(fab
(board tangnano9k
:device "GW1NR-LV9QN88PC6/I5"
:family "GW1N-9C"
:clock 52
:pins ((led 10)
(btn 3)
(uart-tx 17)
(uart-rx 18))))Search order:
--board-dirpaths (in order)- Project
boards/directory (development mode)
| Example | Description | Files |
|---|---|---|
01-uart-sender |
UART character sender | 1 module + 1 testbench |
02-z80 |
Z80 CPU core | 5 modules (ALU, regfile, uart, top) |
03-usb-keyboard |
USB HID keyboard (soft USB) | 11 modules |
# Generate only
fab examples/03-usb-keyboard/usb-keyboard.lisp
# Generate + compile for Tang Nano 9K
fab --board tangnano9k examples/03-usb-keyboard/usb-keyboard.lisp
# Flash to board
openFPGALoader -b tangnano9k build/usb-keyboard.fs -ffab/
src/
packages.lisp Package definition
ir.lisp IR struct definitions
emit-verilog.lisp Verilog emitter
fab-macro.lisp DSL parser + fab macro
main.lisp CLI entry point
boards/
tangnano9k/
tangnano9k.lisp Board definition
examples/
01-uart-sender/ Simple UART
02-z80/ Z80 CPU
03-usb-keyboard/ USB HID keyboard
build/ Generated output (gitignored)
fab.asd ASDF system definition
Makefile Top-level build
GPL-3.0-or-later. See LICENSE.