🏡 home Bottom of the Stack
2023 Nov 17

> Bottom of the Stack

A not-exactly-lucid introduction to HDL (specifically verilog) as a software programmer.

Full-Stack

Many programmers are familiar with the term "software stack" which refers to the collection of software which makes up your product. We usually think of this software as building one on top of each other. For instance, a budgeting app has a website, which displays info from a budgeting server, which analyzes data from your credit card company's or bank's servers, so the "stack" might be

.------------.
| DontBePoor |     // "frontend" budgeting website
|    .com    |
|            |
'------------'
  /_______\ \_\
      ^  |
- - - - - - - - - -
      |  v
 .------------.
 | DontBePoor |    // "backend" server
 '------------'
  ^|        ^|
- - - - - - - - - -
  |v        |v
.----.   .------.  // other servers out of our control
|bank|   |credit|
'----'   '------'

As with life, UI accessibility, and __ everyone seems to strong and wildly varying opinions about what "full-stack" means for them, but unlike the first three, the platonic ideal of "full-stack" seems to lie somewhere near "frontend and backend software".

I usually work on "backend" services, but I'm interested in the moment at the stack below the software stack. Studying computer science in university, I was taught plenty about what computer hardware "was", but not really why it is designed the way it is, or how hardware is designed at all. I'm interested in that now, so I'm trying to piece together a freshman understanding of computer engineering from online resources.

I started by just looking up job descriptions in hardware and auto companies because they are interesting enough to me, and keeping a list of some of the requirements, then going through and looking them up. There was a bunch of wire protocols mentioned like I2C, SPI, USB, Ethernet, as well as software like SPICE, AUTOSAR, ADAS, HSM, and FreeRTOS. A bunch of the FPGA engineer positions required knowledge of some "HDL" like verilog or similar. Not really sure what that meant (I think I had heard the name "verilog" before, but never looked into it), I started reading wikipedia pages.

(not) Physics

I got some introduction to analog circuits in my high school Physics course. I remember helping my mom with electric in our house, and trying to calculate P=IV. Beyond that, not much. I guess one of the first things I learned is that digital circuit design is not drawing pictures of resistors and capacitors and calculating the voltage across them. I haven't really seen any mention at all of the components I learned in Physics class. Rather more common in digital circuit design is logic gates (OR,AND,etc), multiplexers ("muxes"), and clocks. Honestly designing these is a lot like programming software.

Maybe the reason it's like programming software is I'm learning verilog which is like a programming language but it is for describing some hardware. It's part of a class of languages known as "Hardware Description Languages" (HDL). Verilog is sort of like C but for describing logical circuits. Both are old, seem to be used everywhere, and every PLT person that's used them is trying to replace them with something better.

I found a nice set of online exercises at 01xz.net to get better at describing some fundamental digital circuits using verilog. That site also has a REPL to allow simulating and testing your designs against the expected results. I think there's three novel concepts I learned while writing verilog programs:

  1. types for bit arrays
  2. 4-value logic
  3. behavioural vs structural descriptions

Bit arrays

C and many other programming languages have a concept of an "array". You may call it by a different name, but it looks like a[4] and it's a way to work with a collection of values, all of the same type without having to name each one individually. Verilog also has arrays, but you don't need them for any of the early circuits I was working with. Much more important is a bit, which can have two types in verilog wire or reg, and a packed collection of bits called a "bus", for which you define a starting and ending index, like a[8:1]. In a lot of ways, it's a lot like an array of bits, it just gets a different name because it behaves a little differently from "arrays" in verilog, and they need to distinguish.

Like arrays, you can select a single bit from a bus with a[1] or a sub-bus like a[1:4]. It's pretty important to keep track of the width of a bus, and not to lose a bit here or there. One could argue that's also important in normal programming languages, but it's a lot easier to mess up when you only have eg 3 bits to work with instead of 64. I appreciate the economy of it. Anyway, the tools for handling verilog will warn you whenever there's even a chance your calculation could overflow the available bits.

4-Value Logic

Verilog, as far as I understand, was originally intended for circuit simulation, and not necessarily circuit synthesis. So a bit in Verilog can be not just 1 or 0, but also x or z. 'x' indicates that you don't care what the value is (allows for similar optimizations as MaybeUninit in Rust, let the implementation choose which value works best). There's also 'z' which indicates "high-impedance", and kind of means "no value". I haven't used or seen 'z' at all, but I understand it is handy for describing some circuits, especially when you're lower than just the logic level.

There's logical tables you can make for common operations like AND and OR 4-value logic:

AND|0 1 x z  OR|0 1 x z
---+-------  --+-------
  0|0 0 0 0   0|0 1 x z
  1|0 1 x z   1|1 1 1 1
  x|0 x x 0   x|x 1 x 1
  z|0 z 0 z   z|z 1 1 z

Honestly, not sure if that's right for 'x' and 'z' values, I stole it from wikipedia which doesn't seem specific to verilog, and logicians love moving things around in these tables and asking whether the resulting logic still make sense.

Behavioural vs Structural

I think of these like "procedural" vs "declarative" programming. I don't think there's an official definition of what "behavioural" or "structural" designs are, but it seems like "behavioural" means always blocks with if/else inside while "structural" means assign statements with a wire on the left and some logical equation on the right.

wire v, reset, load, data;

// behavioural
always @(posedge clk) 
    if (reset)
      v = 0;
    else if (load)
      v = data;
    else
      v = v;


// structural
assign v = reset & (data ? load : v);

Some more Terms

In addition to the above, here's some other vocabulary that gets thrown around:

Register-Transfer Level (RTL)
Something between software programming and describing logical circuits. Implies you can load and store values to registers between clocks. If you're writing behavioural verilog code, you are working in RTL.
netlist
a description of a physical circuit, but as a connected graph where nodes are electrical components and edges are wires. It's not a file format or anything, it's just a stage of hardware synthesis like how object files are an intermediate stage for software compilation. This might be an input for SPICE or related tools, but I don't know I haven't gotten that deep yet.
testbench
unit test, but in HDL-ese. By convention, if your main file is `foo.v` then your testbench is `foo_tb.v`.
waveforms
the output of a simulation or measurement

(an example of a waveform rendered as ascii)

   ______ _______          ______
v  __x___/   1   \____0___/
          _______ ________ ______
x  ______/___3___X___7____X___1__

Parting words

Like many programmers, I started with video game programming. I don't do much game programming in my day job, instead I work on servers and APIs and "the cloud". In terms of philosophy, the two areas seem to be opposites. Indie game development favours creativity. Game engine development encourages a vertical understanding of how every pixel on the machine starts from some data file, to main memory, to GPU and ultimately to the screen.

In contrast, writing "cloud" apps for a large company encourages homogeneity, and the performance bottlenecks at least at my past two positions have rarely if every been due to the machines, but rather simply how many network hops are required to get some simple request done. Instead of encouraging a knowledge of the request from start to finish, there's more interest in factoring everything into pieces so a single department or group can work on each part. It's a useful for software development, but I miss the machine. So it's been nice to dive into the world of HDLs and I think there's room here to grow.

-