Show HN: SHDL – A minimal hardware description language built from logic gates
Posted by rafa_rrayes 11 hours ago
Hi, everyone!
I built SHDL (Simple Hardware Description Language) as an experiment in stripping hardware description down to its absolute fundamentals.
In SHDL, there are no arithmetic operators, no implicit bit widths, and no high-level constructs. You build everything explicitly from logic gates and wires, and then compose larger components hierarchically. The goal is not synthesis or performance, but understanding: what digital systems actually look like when abstractions are removed.
SHDL is accompanied by PySHDL, a Python interface that lets you load circuits, poke inputs, step the simulation, and observe outputs. Under the hood, SHDL compiles circuits to C for fast execution, but the language itself remains intentionally small and transparent.
This is not meant to replace Verilog or VHDL. It’s aimed at: - learning digital logic from first principles - experimenting with HDL and language design - teaching or visualizing how complex hardware emerges from simple gates.
I would especially appreciate feedback on: - the language design choices - what feels unnecessarily restrictive vs. educationally valuable - whether this kind of “anti-abstraction” HDL is useful to you.
Repo: https://github.com/rafa-rrayes/SHDL
Python package: PySHDL on PyPI
To make this concrete, here are a few small working examples written in SHDL:
1. Full Adder
component FullAdder(A, B, Cin) -> (Sum, Cout) {
x1: XOR; a1: AND;
x2: XOR; a2: AND;
o1: OR;
connect {
A -> x1.A; B -> x1.B;
A -> a1.A; B -> a1.B;
x1.O -> x2.A; Cin -> x2.B;
x1.O -> a2.A; Cin -> a2.B;
a1.O -> o1.A; a2.O -> o1.B;
x2.O -> Sum; o1.O -> Cout;
}
}2. 16 bit register
# clk must be high for two cycles to store a value
component Register16(In[16], clk) -> (Out[16]) {
>i[16]{
a1{i}: AND;
a2{i}: AND;
not1{i}: NOT;
nor1{i}: NOR;
nor2{i}: NOR;
}
connect {
>i[16]{
# Capture on clk
In[{i}] -> a1{i}.A;
In[{i}] -> not1{i}.A;
not1{i}.O -> a2{i}.A;
clk -> a1{i}.B;
clk -> a2{i}.B;
a1{i}.O -> nor1{i}.A;
a2{i}.O -> nor2{i}.A;
nor1{i}.O -> nor2{i}.B;
nor2{i}.O -> nor1{i}.B;
nor2{i}.O -> Out[{i}];
}
}
}3. 16-bit Ripple-Carry Adder
use fullAdder::{FullAdder};
component Adder16(A[16], B[16], Cin) -> (Sum[16], Cout) {
>i[16]{ fa{i}: FullAdder; }
connect {
A[1] -> fa1.A;
B[1] -> fa1.B;
Cin -> fa1.Cin;
fa1.Sum -> Sum[1];
>i[2,16]{
A[{i}] -> fa{i}.A;
B[{i}] -> fa{i}.B;
fa{i-1}.Cout -> fa{i}.Cin;
fa{i}.Sum -> Sum[{i}];
}
fa16.Cout -> Cout;
}
}Comments
Comment by vhantz 5 minutes ago
If you removed the explicit declaration of every gate in a preamble and then their wiring as a separate step, you could reduce the boilerplate a lot. This example could look like this:
component FullAdder(A, B, Cin) -> (Sum, Cout)
{
A XOR B -> AxB
A AND B -> AB
AxB XOR Cin -> S
(AxB AND Cin) OR AB -> C
Sum: S
Cout: C
}Comment by Lramseyer 3 hours ago
Comment by rafa_rrayes 1 hour ago
Comment by knowitnone3 52 minutes ago
Comment by bigbadfeline 1 hour ago
Comment by rafa_rrayes 1 hour ago
Comment by fspeech 2 hours ago
Comment by oleumbudget 1 hour ago
Comment by notherhack 3 hours ago
Comment by dang 1 hour ago
A comment like this could turn from a bad one to a good one if it were written more in the key of curiosity: what are the similarities or differences? what are some pointers for further development? and so on. If you know more than someone else does, that's great, but then please share some of what you know so we can all learn.
Telling somebody that their project which they've been pouring their passion and creativity into is merely reinventing some well-known thing that's been around for years is going to come across as a putdown even when it isn't intended that way. The effect is to shut down creativity and exploration, which is the opposite of what this place is supposed to be for.