i am trying out something new here with footnotes that look like this: (*foo)
vhdl is a hardware description language. that is what the ‘HDL’ stands for (’V’ stands for ‘very high speed integrated circuit’). it is a language, seemingly very similar to a programming language, that is used to model computer hardware in some capacity. it is not a programming language, like C is
you are almost certainly reading this post on a device that incorporates a few billion transistors. each one of those transistors performs a unique, simplistic duty and was placed, printed, or whatever according to some grand scheme. obviously, the task of laying out each individual transistor is insurmountable by even a large group of dedicated human beings(*xsistors). instead we use very unambiguous, verbose, and well-defined languages to describe the functionality we wish to achieve with our physical circuits: HDLs
this is what VHDL looks like:
-------------------------------------------------------- -- Simple Microprocessor Design -- -- Program Counter -- PC.vhd -------------------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all; use work.constant_lib.all; entity PC is port( PCld: in std_logic; PCinc: in std_logic; PCclr: in std_logic; PCin: in std_logic_vector(15 downto 0); PCout: out std_logic_vector(15 downto 0) ); end PC; architecture behv of PC is signal tmp_PC: std_logic_vector(15 downto 0); begin process(PCclr, PCinc, PCld, PCin) begin if PCclr='1' then tmp_PC <= ZERO; elsif (PCld'event and PCld = '1') then tmp_PC <= PCin; elsif (PCinc'event and PCinc = '1') then tmp_PC <= tmp_PC + 1; end if; end process; PCout <= tmp_PC; end behv;
this looks weird if you’ve only ever written software. what we have here is a program counter, the number (stored in a very fancy register) that perpetually increments every time the CPU executes an instruction. this is to handle the CPU control flow, the CPU looks at it to determine which instruction to run next (the next one in sequence, usually, unless there was a jump or branch)
looking at the top 5 lines we see a bunch of library includes, just like software. VHDL libraries, uh, kind of warrant some explanation(*hdl-lib). the next contiguous block of text is the entity declaration. this is where hardware/software begin to diverge:
everything in the entity block (lines from ‘entity PC is’ to ‘end PC’ inclusive) is just a list of inputs and outputs tied to lexical names(*lex). everything is either an input or an output(*direction) and fundamentally represents a single metal pin that either caries a single high or low (1 or 0) voltage into the entity or out of the entity. the ‘1′ and ‘0′ in this case is represented by the type std_logic. an std_logic_vector is simply an “array” of 2 or more std_logics (like a bus). std_logic is almost certainly the only type you’ll ever use and you can reasonably think of it as you would a ‘bit’ in a computer although we’ve decided to make even bits more complicated(*std_logic)
to build a large-scale piece of hardware, you must have everything explicitly and verbosely outlined like this or you won’t get very far. you write thousands of little components like the one above and link them together, pairing an output of one component to an input of another in order to create a model of something useful like a CPU or DAC or whatever. since you don’t really have ‘types’ like you do with software, everything fits really well together in massive quantities which is a necessary quality when trying to tackle the super hard problem of designing computer hardware
at this point we know how many data bits we have incoming and know how many data bits we are responsible for emitting. how we exactly go from point A to point B is defined in the lines after the entity block: the behavioral architecture
this is where it starts to look and feel a whole lot like programming. you got your ifs and your elses and your stupid shitty temporary variables and stuff. it looks easy enough at first glance but of course it isn’t. unlike software, where it no longer really matters whether you use a gross long chain of if/elseifs or a switch statement, your approach to solving the logic of your digital machine is scrutinized fanatically, almost superfluously, by whatever software is interpreting your VHDL code. a big long chain of if/elseifs indicate sequential logic in your circuit (first check for this condition, and if that doesn’t work check for this next one, then this next one etc etc). this is a lot of discrete, nuanced steps meaning you’ll end up with an inefficient and overly complex outcome if you’re not careful. using a SELECT statement would instead immediately match an input to its corresponding result in a single step resulting in cheaper, less complicated, less demanding and more performant hardware (with less keys hit on your end to boot!). this gets nuts(*flow)
anyway, this behavioral block is incredibly simple. your little digital machine is given a 16-bit-long list of 1s and 0s that describe a number and must emit a 16-bit-number in return. you’re also given 3 other one-bit signals that control how this works; one of these will be ‘1′ and the rest ‘0′. if the PCInc (PC increment) is set, as usually would be the case, then the number is simply incremented by one and then emitted like we discussed. if the PCclr bit is set (PC clear) the input is ignored and sixteen zeroes are outputted and you computer blows up. finally, PCld (PC load) just replaces the current PC counter value with what is given as input and outputs it untouched, which is what happens when a branch is taken and you must jump to a totally new place in memory and begin executing
now we have a very strictly and robustly defined port mapping (the entity block, inputs and outputs) and the information that describes absolute output values for any given combination of input values. we have all we need here, but what next?
in the software world, we would take this human-readable text file and pass it to a compiler to create objects/executable/binary code that can be executed by the processor. in this scenario we have described a lot less with our software syntax and rely on the compiler to make guesses and heuristic optimizations to produce a (hopefully) good functional object from our textual source code
with HDLs, on the other hand, we have very arduously described a much more exacting and complete model of functionality that precludes the aforementioned bullshit optimizing compilers do. there is still a lot of guesswork that is done with HDL processing, but it’s of an entire different nature. let’s not get ahead of ourselves, though
like i said, software source code has a very specific endpoint: a compiled object or executable. HDLs do not. the amount of information you’re forced to very clearly annunciate in HDL source opens the door to all sorts of possible endpoints. you can use your HDL code to come up with a high-level schematic of a machine to be manufactured, and then the low-level combination and layout of transistors the can be passed off to a manufacturer and actually be made in real life. you can also use it to configure a generic programmable logic chip like an FPGA or CPLD. hell, you could use it to generate a rube goldberg machine with dominos and seesaws and pins that pop a balloon to scare a dog that runs into a lever that..etc etc
anyway, with software you have a pretty unchanging compilation data flow. using C as an example, your source code first runs through a preprocessor, then a compiler, then an assembler and finally a linker.
with HDLs, all sorts of things can happen but let me describe a pretty general scenario, taking VHDL and using it to program an FPGA. the first step is ‘analysis’ which is similar to the preprocessor in that it checks to see if you fucked up your syntax, and if not, creates a software graph of all the entities you’ve described and how they link up and if those link-ups make sense and so on
next, is synthesis. this is by far the most interesting stage. whereas analysis dealt heavily with the entity declaration, synthesis deals mostly with the behavioral models. this is where the sythesis program takes the functional model you’ve described, all the conditionals, control flow statements, operators and internal/external signals, and tries to construct an optimal configuration of [something] in order to achieve the desired functionality. in this case, [something] is the programming of the individual logic cells in an FPGA each of which occupy distinct positions in a two-dimensional grid. i’ll write a post explaining how FPGAs work later, but you can think of them as “very smart transistors” for now. if we were trying to build an ASIC as opposed to programming an FPGA, we would come up with networks of actual transistors & friends that would do the same thing in some sense. don’t get confused though, the ‘network’ we’re talking about here is a network in the graph theory sense and even though everything is interconnected properly we still don’t have an actual plan describing where everything physically goes. it’s like we’re working at a shipping warehouse & we have fetched all the proper things we need to ship to a customer, but still need to figure out a way to efficiently place them in a box
the next stage is fitting. fitting is done by the fitter. the fitter is similarly complicated & interesting and the fitter is my favorite, uh, ‘stage’. what it does is take the complicated graph model generated by the synthesizer and comes up with an efficient placement and routing of each individual component. this is mind-bogglingly complicated. using our shipping example, let’s say you have a bunch of items, each of different size and shape, and a selection of boxes of different sizes. you need to play tetris in your head to figure out how to fit all these items in the smallest box possible. this is what the fitter does, but fitting and routing digital subcomponents turns out to be a much harder problem. it’s like the shipping scenario except if you place item A too close to item C then item B goes into state μ wherein if not within x nanometers of item D, explodes
this is where the heuristical & computational heavy lifting ends. the next stage is assembly, where this huge software model is condensed into a file suitable for whatever your end goal is. with an FPGA, this is simply a binary file that can be serially tapped-out over USB or whatever to some microcontroller on the fpga board that sets the logic cells properly. for an ASIC, this might be a geometry file that describes the size, shape, and chemical composition of layers to be lithographically etched on to a silicon die. this stage is not very interesting
this is where software might end, but with hardware we have only seen half the battle. since there are no mistakes™ allowed when it comes to hardware (you can’t fuck up a design of an ASIC you plan on spending tens of millions of dollars manufacturing en masse) you need to have an obnoxiously intricate system of verification to make sure your end result will actually function correctly. you can absolutely write valid VHDL syntax that passes all previously described stages without so much as a hiccup that doesn’t even come close to working when actually manufactured. verification checks this. and verification is important (*verif)
one of the biggest things verification deals with is timing schemes. the synth & router/fitter have no concept of time, no concept of voltage or current or electron propagation or gate delay and will happily generate a translation lookaside buffer that can, at most, pass values from memory to the CPU once per second when it actually should be many hundreds of millions of times faster. now, your otherwise functional computer takes from now until the heat death of the universe to boot up!
verification stages can heuristically detect things like this and for the most part are very good. they’ll also check to make sure the FPGA programming file doesn’t require, like, a ten billion-by-ten billion array of logic cells or that the ASIC you’re trying to make won’t be the size of a dinner plate
and with that, we have pretty much covered it. to summarize, software programming languages and HDLs are decevingly similar at first glance, but in the end are apples and oranges. yes, you’re still writing if statements and switch statements and maybe loops but this shared sentiment is like comparing someone who paints lines on the street to someone who paints nude dudes on the ceiling of the sistine chapel
go buy a cheap $60 FPGA (look up terasic’s DE0-nano), download the free version of quartus II and go learn you some hardware, son
(*xsistors) we don’t place each transistor, but we do very intricately design standard models of fundamental electrical components like transistors & inverters on a geometrical/chemical level and reuse those en masse to create “very large scale” electronics.
(*hdl-lib) VHDL libraries are strange. i almost think it’s a poor idea to call them libraries because of how greatly they differ from software libraries. in VHDL, you literally almost always only ever use one “library” and its subcomponents. this is partly due to the fact that it’s promulgated by the IEEE, a venerable and authoritative professional body of engineers and partly due to the fact you really don’t/can’t/shouldn’t just tack on crummy pre-configured routines and declarations and whatnot when it comes to hardware. your end goal is usually to create a hunk of fiberglass and aluminum and silicon that has to function according to the laws electromagnetism and not some virtual beep-boop world of imaginary 1s and 0s that don’t get all screwed up by thinks like clock skew and electromigration and etc. another interesting thing about “the” VHDL library is that it is actually what defines most of the functionality and types you end up using. VHDL sans libraries actually has relatively few and quite esoteric keywords. the library is what defines what an std_logic is, what operators like ‘+’ or ‘*’ do, how values are assigned, etc
(*lex) just kidding. you actually don’t need to assign lexical names to ins/outs and can properly externally interact with an entity by providing your port mappings in the same order and you typed them in the entity declaration. hah!
(*direction) the pin directionality fundamentally limits you in some respects: you can never check the value of an output, nor can you write a value to an input. people didn’t like this, so there are actually two other directions data can go: ‘inout’ and ‘buffer’. you can both read and write to these. don’t ask me how they work or what the difference is between them
(*std_logic) the high school student sees data bits as things that are absolutely 0 or absolutely 1, no in-between, no other possible states. he goes on to university & keeps with the college student tradition by incorporating a third state, “don’t care”, to refer to bits that contextually do not affect the outcome of whatever subjective task they’re modeled in. time passes, bits become less and less like binary digits cascading matrix-style across his monitor and more like physical pins and balls and pads of metal that conduct electrical current, he recognizes a forth state, “floating” to refer to data bits that are not properly connected and randomly and wildly fluctuate according to environmental conditions (mistakes). this cycle continues and eventually we end up with the std_logic that has something like nine possible states. what started out as a glorified light switch has now become some kind of puzzle box that can be twisted and contorted into all sorts of wild and numerous permutations because nobody is competent or good at what they do
(*flow) i cannot stress to you how deceptive the familiar syntax in the behavioral block is. i know you see if statements and all that, and i know you want to believe it’s the same as it is in software, but it isn’t. the sequential/concurrent example i posed is one ‘gotcha’ a software-minded person might encounter writing in VHDL, but it isn’t even just the ‘gotchas’. in our case with the PC counter, it might seem better if we just matched the three possible control pins (PCclr/PCinc/PCld) with the corresponding counterpart in a switch statement, as that would result in a circuit that only ever needs a maximum of one step to complete as opposed to the sequential if/elseif which could possible take three steps. however! think about this: in a CPU, the PC counter will be incrementing nearly 100% of the time. if this is the first pin checked in our if/elseif block, then it will usually only take 1 step just like the concurrent/switch model. BUT! this is not like software! we are not locked into discrete and uniform cycles of a CPU. the ‘one’ step that the if/elseif model has to take nearly 100% of the time might be just minisculely quicker than the ‘one’ step the concurrent/switch model has to make. that difference, multiplied by the billions of cycles your CPU runs per second, quickly becomes not-so-miniscule. pretend there is a paragraph break here, i only want one footnote per break. another thing liable to trip you up coming from the software slums is the idea of locally-scoped variables and assignment. a careful reader might find i eschewed describing what the line beginning with ‘signal’ does in the PC counter VHDL code. a ‘signal’ in VHDL is a very distant cousin of a locally-scoped variable in software. with our PC counter, we needed some place to to store a temporary value so we could add 1 to it to get the output we needed. we can’t add 1 to the input, because we cannot modify the values of inputs, so we create a signal. this is dangerous. signals complicated the synthesis model that already required partial differential equations and fucking graph theory and shit because it now has to decide how to handle internal signals that are described in a limited functional scope and have no bearing on the actual output. where there was once nothing but simple inputs and outputs and immutable paths from one to the other, the HDL processor must now account for some weird space that only really matters internally. another paragraph break. assignment is also weird. software has all sorts of esoteric approaches to handling assignment, un-initialized variables, comparison and the like. you usually have just ‘=‘ to denote whatever is left of the equals sign is now the same as what’s on the right (whatever ‘same’ means, anything from duplicating the data in memory to simply providing another lexical identifier for the same bits in memory). with VHDL, you have left and right assigns. a left assign is a less-than symbol followed by an equals sign (something i can’t type because this shitty fucking website will interpret it as the start of an HTML tag and break my post). left and right assigns mean different things and are appropriate in different cases. they also work to define concurrent logic flow, like this:
process(a) begin case a is when "00" => b <= "1000"; when "01" => b <= "0100"; when "10" => b <= "0010"; when "11" => b <= "0001"; when others => report "unreachable" severity failure; end case; end process;
(*verif) there is a company, ARM, that occupies one out-of-the-way facility in the lovely city of cambridge in england. it’s their only real physical establishment and is filled with nothing more than engineers, computers, and office supplies. they rake in nearly a billion dollars every year and they don’t even sell anything you can touch and feel with your hands. they design CPUs and come up with practical design files with which they sell licenses for, to big companies like apple and samsung and qualcomm (qualcomm is the worst). these licenses allow such companies to access these design files in incredibly strict and prescribed ways such that they can go out to a manufacturer and have the chips actually madenow, these licenses can cost anywhere from zero dollars to probably something like fifty million dollars a year (they don’t disclose). the zero dollar one is for universities and fledgling idiot companies to experiment and play around with either for educational purposes or feasibility tests respectivelynow, it would be a very bad idea to hand over design files you sell for millions for free! ARM solves this in a hilarious way: the design files you get model real-world ARM CPUs. they pass all of ARM’s litany of tests and checks that determine whether a CPU conforms to ARM’s architecture and would pass all the HDL “compilation” stages described up to this point with flying colorshoweverwere you to be an incredibly daring and handsome individual and attempt to break the contract/license you made with ARM by actually manufacturing (much less commercializing) hardware with the design files they gave you for nothing, you wouldn’t get very far. you’d send off your shit to a fabrication plant, and the fab plant would call you back, very angrily, informing you that your supposed CPU would be 40 meters in length/width, would need a megawatt of power to run and would cost one hundred thousand dollars in components to manufacture a single chip (whereas usually this amount is in pennies)they’ve sabotaged the files! incredible. they’ve stopped any potential breach of contract without substantially impeding you in your pursuit of the lawful endeavors your license/contract defines. oh, and by the way, you’d have better luck trying to burglarize the white house than you would trying to cheat ARM and manufacture/commercialize products with their intellectual property illicitly