Learn HardCaml in Half an Hour
inspired by fasterthanlime article on "Learn Rust in Half an Hour"
HardCaml is a domain-specific language embedded in OCaml for designing digital circuits. You know, the kind that eventually become chips. But unlike traditional hardware description languages like Verilog or VHDL, HardCaml lets you leverage OCaml's powerful type system and functional programming features to build hardware designs that are both correct and composable.
Ready? Let's dive in.
Variables and Mutability
Actually, let's start with something more fundamental. In HardCaml, everything is about signals.
let my_signal = Signal.of_int ~width:8 42This creates an 8-bit signal with the constant value 42. The ~width:8 part is OCaml's labeled argument syntax - we're explicitly saying this signal should be 8 bits wide.
Unlike variables in imperative languages, signals in HardCaml represent wires in your circuit. Once you create a signal, it doesn't change - it represents a fixed connection in your hardware.
let a = Signal.of_int ~width:4 5
let b = Signal.of_int ~width:4 3
let sum = Signal.(a +: b)The +: operator adds two signals. Notice the .() syntax - that's OCaml's way of opening a module locally. It's like saying "use the operators from the Signal module here."
Basic Operations
HardCaml has different operators for different kinds of operations:
let a = Signal.of_int ~width:8 10
let b = Signal.of_int ~width:8 5
(* Arithmetic *)
let sum = Signal.(a +: b)
let diff = Signal.(a -: b)
let product = Signal.(a *: b)
(* Logic *)
let and_result = Signal.(a &: b)
let or_result = Signal.(a |: b)
let xor_result = Signal.(a ^: b)
(* Comparison *)
let equal = Signal.(a ==: b)
let less_than = Signal.(a <: b)The : at the end of each operator is HardCaml's convention. It helps distinguish hardware operators from regular OCaml operators.
Bit Manipulation
Working with individual bits or ranges of bits:
The select function takes the signal, the high bit index, and the low bit index. It reads a bit backwards from what you might expect, but that's the hardware convention.
Multiplexers (the if-then-else of Hardware)
mux2 is like a conditional: if condition is true (high), choose a, otherwise choose b.
For multiple choices, use mux:
Sequential Logic (Memory!)
So far everything has been combinational - the output immediately depends on the input. But real hardware needs memory. Enter registers:
Whoa, what's happening here? We're creating a counter that increments every clock cycle. But there's this weird wire and assign business. The problem is that we have a cycle: the counter's next value depends on its current value. HardCaml solves this with wires - you create a placeholder signal, use it in your logic, then assign the actual value later.
A More Practical Example
Let's build a simple traffic light controller:
This creates a state machine that cycles through Red β Green β Yellow β Red based on timer expiration.
Modules and Interfaces
HardCaml uses OCaml's module system to organize designs. Here's how you typically structure a module:
The [@@deriving hardcaml] part is PPX magic that generates helper functions for working with these interfaces. It creates functions to convert between records and signal lists, which is super useful for connecting modules together.
Simulation
Writing hardware is only half the fun - you need to test it! HardCaml includes a built-in simulator:
Notice that during simulation, we work with Bits.t values instead of Signal.t. Signals represent the structure of your circuit, while Bits represent actual values flowing through that circuit during simulation.
Expect Tests and Waveforms
HardCaml integrates beautifully with Jane Street's expect test framework:
And you can visualize what's happening with waveforms:
Always Blocks (State Machines Made Easy)
For complex sequential logic, HardCaml provides an Always DSL that looks like imperative code but generates proper hardware:
This creates a state machine with three states that transitions based on conditions. The Always module compiles this into proper register assignments.
Hierarchical Design
Real designs are built by composing smaller modules:
The [@bits n] annotations tell HardCaml how wide each signal should be.
Memory
HardCaml can generate different kinds of memory:
This creates a 32-entry memory with one read port and one write port.
Instantiating Verilog
Sometimes you need to use existing Verilog IP. HardCaml makes this straightforward:
This instantiates a Verilog module called ddr_controller and connects its ports to HardCaml signals.
Generating Verilog
Once you've built your design, you can generate Verilog:
This generates a cpu.v file that you can synthesize with standard FPGA tools.
Testing and Formal Verification
HardCaml supports formal verification through integration with external tools:
Types and Width Inference
One of HardCaml's superpowers is its type system. It catches width mismatches at compile time:
HardCaml will complain that you're trying to add an 8-bit signal to a 4-bit signal. You need to explicitly resize:
Pattern Matching on Signals
You can pattern match on signal values during simulation:
Useful Libraries
HardCaml comes with several useful libraries:
Hardcaml_waveterm: Terminal-based waveform viewerHardcaml_xilinx: Xilinx-specific IP integrationHardcaml_circuits: Common circuit patternsHardcaml_step_testbench: Advanced testing framework
Error Messages
HardCaml's error messages are generally quite helpful:
Much better than the cryptic errors you get from traditional HDLs!
Performance Considerations
HardCaml designs can be highly optimized:
Where to Go From Here
This covers the core of HardCaml! You should now be able to read most HardCaml code you encounter. Key concepts to remember:
Everything is a signal representing wires in your circuit
Combinational logic uses operators like
+:,&:,mux2Sequential logic uses registers and the
AlwaysDSLModules organize your design hierarchically
Simulation lets you test your designs before synthesis
The type system catches many errors at compile time
For more advanced topics, check out:
Jane Street's tech talks on using HardCaml in production
The examples repository for real designs
Last updated