Welcome Back to the Underground
Round two: FIGHT! ๐ฅ Last time, I let you in on hardware's dirty little secret - that you can write hardware with actual readable code instead of VHDL's syntax nightmare. Now it's time to get our hands dirty. If you missed part one, go back. Trust me, you'll want the full initiation before we start bending hardware to our will.
Today's mission: We're going from "What is this Haskell witchcraft?" to "Heavens, I just made hardware that counts!" Buckle up.
Haskell: The Quick and Dirty Guide
Look, if you're coming from C++ or Python, Haskell is going to feel like driving on the wrong side of the road. Instead of barking orders at your computer, you're having a philosophical conversation with it.
In imperative languages, you tell the computer HOW to do things, step by step. In Haskell? You just describe WHAT things are. Want Fibonacci numbers? Don't tell it how to calculate each one - just tell it that any Fibonacci number is the sum of the two before it.
Mind. Blown. ๐คฏ
Fire Up the Playground
Time to launch GHCI - that's our playground. Type this in your terminal:
ghci
If it takes a moment to load, that's normal. Even Haskell needs coffee sometimes...
You'll see something like this (version numbers may vary, we don't judge):![]()
Pro tip: Create a file called prog.hs so we're not typing everything directly into the void:
ghci> :load path_to_file/prog.hs
ghci> :reload
Types: Haskell's Superpower
Haskell has a type system so strict it makes your high school math teacher look lenient. But here's the secret - it's actually your best friend. The compiler knows the type of EVERYTHING at compile time. No more "undefined is not a function" at 3 AM.
Let's see what Haskell thinks about the number 1:
ghci> :type 1
1 :: Num a => a
Translation: "1 can be any type 'a', as long as 'a' knows how to act like a number."
The compiler just told us it's flexible but not stupid. Beautiful.
Your First Functions (They're Friendlier Than They Look)
Functions in Haskell are refreshingly simple. No parentheses, no commas, just spaces:
successor :: Num a => a -> a
successor val = val + 1
ghci> successor 5
6
That :: means "has the type of". The arrow -> is just showing data flow: takes a number, returns a number.
But here's where it gets fun - pattern matching. Let's run a sketchy car rental business:
data CarBrand = Ferrari | Bugatti | Toyota | Ford
rentCost :: CarBrand -> Int
rentCost Ferrari = 150 -- "That'll be $150, sir"
rentCost Bugatti = 800 -- "Hope you've got deep pockets"
rentCost anyOtherCar = 30 -- "Meh, whatever"
The function checks patterns top to bottom. First Ferrari it sees? $150. Toyota? Falls through to the $30 "peasant tier".
Recursion: When Functions Call Themselves (And It's Not Weird)
In Haskell, loops are so last century. We use recursion. Here's summing a list the Haskell way:
sumList :: Num a => [a] -> a
sumList [] = 0 -- Empty list? Sum is 0. Duh.
sumList (x:xs) = x + sumList xs -- First element + sum of the rest
But wait, there's a sexier way using higher-order functions:
sumListHigherOrder x = foldl (+) 0 x -- One line. Boom.
Why should you care? Because Clash HATES recursion but LOVES higher-order functions. Remember that - it'll save your sanity later.
Time to Clash! (The Main Event)
Enough foreplay. Let's make some hardware.
Fire up clashi:
clashi
You'll see this (ignore the warning, it's just being dramatic):
If it takes forever to load, that's normal. It's thinking about all the hardware it's about to help you create.
Your First Circuit: The Mighty AND Gate
Time to play Dr. Frankenstein. We're building an AND gate - the "hello world" of hardware. Create a file called ANDGate.hs:
module ANDGate where
import Clash.Prelude
andBit :: Bit -> Bit -> Bit
andBit x y = x .&. y -- That's it. You're a hardware engineer now.
Test this bad boy:
ghci> :load path_to_file/ANDGate.hs
ghci> andBit 1 0
0 -- Returns: 0 (sorry, AND gates are picky)
ghci> andBit 1 1
1 -- Returns: 1 (both inputs true = happy gate)
Anticlimactic? Just wait until we see this in silicon...
Seeing Is Believing (Time to Fire Up the Beast)
Remember that 18GB monster we installed? Vivado? Time to wake it up and watch our code become actual circuit diagrams. This is where things get real.
First, tell Clash what to compile:
module ANDGate where
import Clash.Prelude
andBit :: Bit -> Bit -> Bit
andBit x y = x .&. y
topEntity :: Bit -> Bit -> Bit
topEntity = andBit -- "Hey Clash, compile THIS one!"
Now compile to Verilog (exit clashi first):
clash --verilog path_to_file/ANDGate.hs
Check out the new file verilog/ANDGate/topEntity.v. That's YOUR hardware in Verilog. You just wrote 4 lines of Haskell and got valid HDL. Your VHDL-writing colleagues are crying right now.
The Vivado Dance
Fair warning: Vivado's UI looks like it was designed by someone who hates joy. But seeing your code transform into gates and wires? Worth it.
Step 1 : Create a new project
Step 2 : Proceed
Step 3 : Give your project a recognizable name, the default location is fine
Step 4 : Select the type to be RTL Project
Step 5 : From the dropdown select 'Add Files...'
Step 6 : Locate the 'topEntity.v' file
Step 7 : Project summary, proceed
Step 8 : Select the FPGA development board. This tutorial doesn't assume you have a physical board, we will be only simulating our designs. So, in this case the selected board choice does not really matter, but the board we opt for is 'Kria KV260 Vision AI Starter Kit SOM'.
Step 9 : Click on 'Open Elaborated Design' and wait for the process to finish
Step 10 : New 'Schematic' window appears that contains our design in RTL (Register-transfer level)
Step 11 : (Optional) Run Synthesis
Step 12 : (Optional) Press 'OK'. After finished, choose 'Schematic' under 'Open Synthesized Design' dropdown
Step 13 : New 'Schematic' window appears that contains our synthesized design
Plot twist: After synthesis, notice something? NO AND GATES! Remember from part 1 - FPGAs fake everything with lookup tables. Sneaky bastards.
Level 2: Hardware with Memory (Things Get Spicy)
Combinational circuits are like goldfish - no memory. But what if your hardware could actually remember things? Enter sequential design, where circuits get a brain upgrade.
We're building a counter that can count up AND down. Because why settle for one direction?
Create Counter.hs:
module Counter where
import Clash.Prelude
type Val = Unsigned 3 -- 3 bits = counts 0 to 7
incrementer :: Val -> Val
incrementer v = v + 1 -- Advanced mathematics here
decrementer :: Val -> Val
decrementer v = v - 1 -- PhD-level stuff
counter :: (HiddenClockResetEnable dom) => Signal dom Bool -> Signal dom Val
counter incr = state
where
state = register 0 (mux incr (incrementer <$> state) (decrementer <$> state))
"Holy smoke, what is all that?" - You, probably.
Don't panic. Here's the decoder ring:
-
HiddenClockResetEnable= Clash handles the boring clock stuff -
register 0= Our memory starts at zero (humble beginnings) -
mux= The bouncer that decides: count up or down? -
<$>= Applying functions to signals (it's justfmapbeing fancy)
Take It for a Spin
Add this test to your file:
simulateCounter = simulate @System counter [True, True, True, False, False, False]
Run it:
ghci> :load path_to_file/Counter.hs
ghci> simulateCounter
[0,1,2,3,2,1,0,*** Exception: X: finite list
CallStack (from HasCallStack):
...
IT COUNTS! Up three times, down three times. Your code just learned to count! ๐
(Ignore that initial 0 and the exception - Clash is just being dramatic)
Making It Real
For Vivado visualization, add this magic:
topEntity :: Clock System -> Reset System -> Signal System Bool -> Signal System Val
topEntity clk rst incr = withClockResetEnable clk rst enableGen counter incr
Compile it:
clash --verilog path_to_file/Counter.hs
A new folder is created 'verilog/Counter' containing ' topEntity.v' that contains the translated code.
Load it into Vivado (same deal as before):
Step 1 : Remove the existing 'topEntity.v' file from sources
Step 2 : Choose option to add new sources
Step 3 : Choose 'Add Files' option
Step 4 : Select the 'topEntity.v' source file for the counter circuit design
Step 5 : Click on 'Open Elaborated Design' and wait for the process to finish. New 'Schematic' window appears that contains our design in RTL (Register-transfer level).
And BOOM - you'll see:
- Clock and reset inputs (the heartbeat and panic button)
- Your increment control signal
- A multiplexer (the decision maker)
- A register (the memory)
You just built stateful hardware. In like 15 lines. While VHDL developers are still writing their entity declarations.
The Uncomfortable Truth
Here's what just happened: You learned enough Haskell to be dangerous, wrote hardware that actually works, and visualized it in industry-standard tools.
Your counter? That's the building block for:
- Timers that control your coffee machine
- Frequency dividers in communication systems
- State machines that run traffic lights
- The foundations of a CPU
And you did it without touching a single line of traditional HDL.
You're One of Us Now
Stone the crows, you did it! You went from zero to building actual circuits that count, remember things, and respond to inputs. While your colleagues are still fighting with VHDL syntax errors, you're already watching RTL schematics materialize from functional code.
Got an FPGA board? Generate that bitstream and watch your code literally reconfigure silicon. No board? No problem - keep simulating and learning.
The hardware underground is growing. You're part of it now.
Welcome to hardware development that doesn't suck. ๐
Want the deep dive? Check out the full technical analysis on my blog
Top comments (0)