Show HN: SHDL – A minimal hardware description language built from logic gates

44 pointsposted 2 days ago
by rafa_rrayes

21 Comments

weebull

2 days ago

My biggest complaint is there's no way to name a signal because a wire isn't a thing. You instance gates and give those names, but wires are anonymous connections between gate pins.

I think this is backwards. Knowing that a signal is the clock, reset, data valid, adder result is far more important than the gate that drove it. The gates barely need names. Sadly, I think starting with that concept leads to a rather different language.

pjc50

a day ago

Yes. HDLs are way behind structured programming in not having "wire-orientation", which would be the equivalent of structured programming's "object orientation". Users should be able to define primitive bidirectional interfaces like "AXI bus" and wire up components accordingly. Bonus is that's where the vitally important constraints get defined anyway.

As you say, the communication interface is far more important than the gates. A true HDL can synthesize the gates for you, and indeed in an FPGA the gates don't really exist (LUTs instead). Optimization tools will further swirl the gates around once you start dealing with place and route - it may be more optimal to factor out common subexpressions, or "push bubbles" (invert OR/AND, De Morgan), or it may not.

The state of the art in Python HDLs is Chisel, btw.

OP says "understanding: what digital systems actually look like when abstractions are removed", which is a reasonable teaching step, and they themselves are probably learning a lot in the process. But it's not all that useful for getting stuff done. It's like learning assembly language, useful for unlocking understanding in your head, useful to read occasionally, but tiring to actually write anything substantial.

RetroTechie

a day ago

Both are important. Signals represent the data being worked on, and thus naming them can be useful.

But gates apply operations/functions to those signals, and naming that logic clarifies its purpose.

lioeters

a day ago

The project looks great! I browsed the codebase and enjoyed how much documentation there is about the user-facing and internal workings. I'm not familiar with the subject matter but I do love me a DSL, so the language design aspect was interesting to learn about.

I was curious how the language compiles to C, what the resulting code does, and how one interacts with it. It took a while of reading to find it, so maybe this could be linked from places where compilation is mentioned. This part is my favorite, it's cool how it works. Especially since you mention "anti-abstraction", I like seeing how the DSL maps to C.

https://github.com/rafa-rrayes/SHDL/blob/master/docs/docs/ar...

> Compiles circuits to C so that they can run anywhere

Input (Base SHDL):

  component Buffer(A) -> (B) { 
      n1: NOT; 
      n2: NOT;

      connect { 
          A -> n1.A; 
          n1.O -> n2.A; 
          n2.O -> B; 
      } 
  }
Output (C code):

  #include <stdint.h>
  #include <string.h>

  typedef struct {
      uint64_t NOT_O_0;
  } State;

  static inline State tick(State s, uint64_t A) {
      State n = s;
      
      // NOT gate inputs
      uint64_t NOT_0_A = 0ull;
      NOT_0_A |= ((uint64_t)-( (A & 1u) )) & 0x1ull;
      NOT_0_A |= ((uint64_t)-( ((s.NOT_O_0 >> 0) & 1u) )) & 0x2ull;
      
      // Evaluate NOT gates
      n.NOT_O_0 = (~NOT_0_A) & 0x3ull;  // 2 active lanes
      
      return n;
  }

  static inline uint64_t extract_B(const State *s) {
      return (s->NOT_O_0 >> 1) & 1ull;  // B from lane 1
  }

  ...

rafa_rrayes

a day ago

Thank you so much for taking the time to dig into the code and docs, it means so much to me!

Here’s the core idea behind how SHDL compiles to C.

At compile time, SHDL groups all gates of the same typea together and packs them into uint64_t bitfields. Each individual gate occupies exactly one bit. If there are more than 64 gates of a given type, multiple uint64_t's are used.

So for example, if a circuit contains: 36 XOR gates - 82 AND gates - 1 NOT gate

The compiler will generate: 1 uint64_t for XOR (36 bits used, rest unused) - 2 uint64_t's for AND (64 + 18 bits) - 1 uint64_t for NOT

Each of these integers represents the state of all gates of that type at once.

The generated C code then works lane-wise: during `tick()`, it computes the inputs for all gates of a given type simultaneously using bitwise operations, and then evaluates them in parallel. Because everything is packed, a single ~, &, |, or ^ operates on up to 64 gates at once.

So instead of iterating gate-by-gate, the simulation step becomes something like: build input bitmasks - apply one bitwise operation per gate type - write the result back into the packed state

In other words, a full simulation step can advance dozens or hundreds of gates using just a handful of native CPU instructions. That’s the main reason the generated C code is both simple and fast.

This also ties directly into the “anti-abstraction” idea sunce there’s no hidden scheduler, no opaque simulator loop, and no dynamic dispatch. The DSL maps very explicitly to bit-level operations in C, and you can see exactly how a logical structure becomes executable code.

The final result is a compiled C shared library, which we can interact from using python (or anything else if you want to build it)

I really appreciate you calling this out. Do you think I should make it clearer in the docs? Thanks again for the comment!

lioeters

15 hours ago

Very cool, I enjoyed the explanation about how bitwise operations are built up to work on a set of gates all at once. I was browsing more of the codebase, it's really well-organized and commented code. I like how SHDL is readable for beginners, the examples are all intuitive and self-explanatory.

As another commenter mentioned, integrating visual diagrams would add an interesting dimension. I saw some manually written ASCII diagrams on the documentation site, and could imagine a way to convert SHDL into diagrams. And a step further, a code playground to write and run SHDL circuits, to be able to see the circuits work visually.

For educational purposes, the docs could use more visual descriptions, like starting with the simplest circuits and show step by step what happens.

Lramseyer

2 days ago

Kind of a wild idea, but have you considered using this as a markup language for logic diagrams? I'm thinking something like mermaid - https://mermaid.js.org/ While this might not be super useful for chip design, it is a fully functional HDL, and since it is gate level, it would map nicely to diagrams.

rafa_rrayes

2 days ago

That is a very interesting idea! Tbh, I have been thinking about something along those lines. I was messing around with gemini 3.0 back when it came out and made this program called Logic Lab (https://logiclab-227111532364.us-west1.run.app/). I was thinking of exporting/importing the components as SHDL, but as of rn they are just exported in some generic format gemini made.

knowitnone3

2 days ago

I thought the opposite. Use mermaid as the HDL language. Draw the diagram, then synthesis.

vhantz

2 days ago

Real nice project!

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
  }

fspeech

2 days ago

If you are just interested in a structural description (so-called netlist) the standard is EDIF.

bigbadfeline

2 days ago

Looks cool and can be useful for its stated purpose. You mention simulation, is there a way to specify and simulate time delays?

rafa_rrayes

2 days ago

Not as a native functionality, not yet but it is something I have been wanting to do for a while. I want to make a complete simulation platform with useful tools such as that. For now you can make the python code call the step() function on your desired timing

huragok

a day ago

Dumb question: how do I load this onto an ICE40?

pjc50

a day ago

I think you'd have to translate it to Verilog then feed it to the existing toolchain.

notherhack

2 days ago

[flagged]

dang

2 days ago

I'm sure you didn't mean to, but this comes across as a shallow dismissal, which is against the site guidelines (https://news.ycombinator.com/newsguidelines.html): "Please don't post shallow dismissals, especially of other people's work. A good critical comment teaches us something.", as well as the Show HN guidelines (https://news.ycombinator.com/showhn.html).

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.

jacquesm

a day ago

Funny, I read it as a very high level compliment.