Why hardware development is hard, part 1: Verilog is weird (2013)
danluu.com
danluu.com
Yet if I put on my software cap and think of how things should be, the whole point of a language is to create an abstraction. This allows a designer to think and express their intent at a higher level. If an abstraction is not complete then the designer can never stop thinking in the lower level terms, which nullifies the benefit.
Hardware description languages can never be as abstract as programming languages for the same reason that we can't create a single unifying abstraction for imperative multi and single threaded code: synchronization. Since each algorithm or data structure can have unique memory access/sync patterns, especially once optimized, there is no way to abstract away parallelization so we're forced to work with a large number of low level primitives like queues, locks, etc. In silicon, not only is everything running in parallel but each abstract unit of functionality has many more "degrees of freedom" like synchronization points, clock speeds, and physical location. It's simply impossible to cleanly abstract over all of the little tweaks you have to do in the implementation phase to get your blocks of transistors to synchronize properly.
I'm not sure what it would even mean for HDLs to be "as abstract" as programming languages. They're different paradigms, and they have different abstractions, so you can't really compare them directly.
What I do know is that you can do a lot better than Verilog, because I've experienced it. I worked in Bluespec for a few years and as a language it's so much better than Verilog it's not even funny.
Indeed, using it made me stop thinking of Haskell as a programming language, and start thinking of it as a declarative language for constructing imperative programs in the IO monad.
Yea, the parallels between hardware design and haskell are striking. (Not just haskell, but it is perhaps the easiest to see.) In undergrad, I wrote my basic cpu project (16-bit risc) in haskell, then got it to write the verilog for me. This had the benefit of having an incredibly fast iteration cycle (including testing) paired with the ability to output an actual hardware description at any point. Very powerful. It really hammers home the difference between computation and I/O, a common theme in both CPU design and functional programs.
Also—and this is just my opinion—I much prefer declaring the computation with logical combinators rather than manually specifying how the bits are routed. This is much easier in haskell (or any functional language) than it is in verilog.
http://people.eecs.berkeley.edu/~bora/Conferences/2015/ESSCI...
I'm a fan of state machines, but I don't think they're necessarily more performant. There's nothing about them that keeps you from doing anything crazy that will bog your program down, but I find them easy to reason about. I think they're a stylistic thing, and the only real advantage I can think of is in maybe helping the design of the program. Having to define your states and how you will move between them is usually a good idea.
I'm neutral about state machines. Obviously in things like microcontrollers and HDL, state machines are really necessary. Outside of those two examples, state machines can either make code much more stable or just add unnecessary overhead with all the wasted cycles. They definitely have their place but there are better options available sometimes like threading a process. I had a project recently where we needed to build a custom message broker. A teammate wrote the entire thing in python as one big state machine. Everytime we needed to add another queue, it involved adding so much extra code to handle it. Another teammate suggested just having everything threaded so we reduce our code down to a template queue and can dynamically create them as needed. The state machine might have been more efficient memory-wise if we were doing it all in C on a PIC or more conservative in PLU usage if it was an FPGA but we were using python on a raspberry pi. We ended up with a broker that could handle more bandwidth in terms of how many queues we could publish to and consume from.
Obviously we didn't really shrug off state machines. We just created many smaller ones (two states: wait for message, push message) rather than a huge one. I think the massive state machines we learn about in our freshman classes are unnecessary in many cases. Memory is so cheap that its not always worth it to be so conservative/old school.
We could have just used something like rabbitmq but we didn't want the extra overhead of a service like that when we had so much more resource heavy stuff running on the pi.
Essentially since Bluespec's model is a set of conditional transactions on your state, you can write an imperative sequence of transactions with a few basic flow control primitives (if-else blocks, while loops etc.), and it creates the state logic automatically.
Unfortunately it didn't seem to have received much attention, and I ran into quite a few problems when I tried to use it for anything serious (including one straight up behavioural bug).
Personally I'm a fan of the smaller state machines you describe. You still got to deal with synchronizing them, but it just seems easier to grasp for me.
Sure there are some cases where it might be a good idea, but if it's a requirement to understand the design, then your language isn't doing its job.
You simply can't do that when designing silicon. All of your transistors have to be physically connected on a 2d plane so you can't abstract over the physical location or layout of your "objects" (logical groups of transistors and how they interconnect in this case). This means that the vast majority of possible Verilog designs are impossible to physically implement because there is only a finite amount of space to route traces from one transistor to another. Writing Verilog before drawing a block diagram is like writing an embedded IoT framework in Ruby before checking whether your target microcontrollers have enough ROM/RAM to run the Ruby interpreter.
For example, if you had a stack allocated object containing a queue and you changed it to always heap allocate the queue, it would still do the same thing as before but with a different memory layout. If you did the equivalent in silicon (like move the location of a group of transistors), at best it would break your functionality and at worst it would make the design unsynthesizable. Swapping an interface to point to an implementation with a different size (I.e. switching an IQueue* to an MPSC Queue from a SPSC Queue) is a trivial change with software but when you swap a 100 transistor interface with a 1000 transistor one you have to move the blocks around it, often requiring significant changes to avoid breaking functionality.
Of course, we were in a research group, so performance was less important to us. And we were working with FPGAs, so we didn't need to get it right in one go (and I think the fabric is better able to cope with looser style).
That said, I think there are cases where you can afford to sacrifice a bit of design efficiency to increase development speed. And if you're working with FPGAs, you may even be able to optimise later.
EDIT: Also the impression I got is that while industry does spend a lot of effort on it, complex designs are nowhere near as optimal as they could be. The first clue should be that an Intel chip has clearly defined blocks on it, rather than being a mess of interleaved logic. They divide the design like that to make it tractable for the designers.
> If you did the equivalent in silicon (like move the location of a group of transistors), at best it would break your functionality and at worst it would make the design unsynthesizable.
I don't think this makes much sense unless you're presupposing a prescriptive physical layout. Sure, if you were to move a bunch of synthesized circuitry somewhere else and then try to reconnect it, it's not going to work. But that's not something you'd actually do in an abstract model.
What you might do is move a bunch of HDL logic from somewhere in the module hierarchy to somewhere else, in which case the synthesizer will move everything else around to compensate.
What were you researching/designing? The higher your clock speeds/bandwidth requirements are and the more complex your designs, the more locality matters. The last design I worked on, for example, was a 32x32 crosspoint switch with total bandwidth in the low hundreds of gigabits and we eventually had to tweak cell locations manually on the mask to get the design working to spec. In modern chip design, bandwidth on that scale is pretty common so i don't think we're talking about the extremes of hardware design.
>> I don't think this makes much sense unless you're presupposing a prescriptive physical layout. Sure, if you were to move a bunch of synthesized circuitry somewhere else and then try to reconnect it, it's not going to work. But that's not something you'd actually do in an abstract model.
Again, depends on a variety of factors. Some of the designs I've worked on have had internal clock signals that couldn't travel across a fraction of the chip before attenuating too much to switch a transistor. Sure a low speed design can be synthesized without caring about physical layout but once you're in the RF/high speed digital realm, the relative location of dependent high speed blocks matters much more and the HDL abstractions start to leak or breakdown entirely.
Obviously these are very different fields, to the extent that I wonder if Verilog is even appropriate for what you're talking about, except for legacy reasons. If you're working in a domain where layout is so important, it feels like you should be using a tool that represents it directly, rather than inferring it.
Doing some work with HDL in a university research setting is different than shipping a working product with the associated constraints.
EDIT: clarification
Optimization is important in hardware due to cost, speed, and power implications of hardware design. These tradeoffs are there for software design as well, but to a much less extent.
A littler bartering and I ended up owning display controllers, camera input peripherals, interrupt controllers and other such fun for 65nm and 40nm ASICs - mostly verilog (and, sigh.. TCL for test benches).
I found a singular insight got me all the way from zero to tapeout for all sorts of peripherals and infrastructure without any real support from other RTL peers in the team - design everything around a single clock (inc crossing all async signals into this singular domain as fast as possible) and then explicit pipelining of the peripherals functionality using a judgement of the process/clock speed to guesstimate how much complexity/gate density each pipeline stage could support before starting a new stage.
As I recall, my peripherals were marginally bigger than they needed to be but were extremely easy to push through the back end as the constraints were simple to define and synthesize.
I miss those days - the pressure to tape out a singular piece of art was a great way to focus the brain and the chip bring-up was exhilarating.
It makes no sense
I don't want to design from the bottom up and then if I'm "lucky" the compiler will give me what I wanted.
In the early days of compilers, of course, you were mostly writing C as a macro language for your system's assembly language. If you wanted your program to perform well, you'd have to write C that was, more or less, a translation of assembly that you'd constructed in your head first. If you wrote bizarre C, you'd either get incorrect results, or if you were lucky, you'd get correct but inefficient results.
But that's also Dan's point: Verilog isn't a "high level language". You don't write programs with it, you describe hardware with it. (In fact, that is why it is called a 'hardware description language'!) So if you try to write a program, instead of describing hardware, you'll get something that isn't really either.
Then why doesn't the language let me describe what I want, and if reality disagrees, throws some compiling errors back?
Our current HDLs are lower level than block diagrams. And anything higher level they may claim to provide is iffy and won't work on practice. That's not a problem with the languages, but it is a problem for hardware development.
Maybe in really old compilers
> You don't write programs with it, you describe hardware with it
Which is fair enough, but it seems the "hardware description" pretends to be of a higher-level than it really is.
If you need the user to describe gates and flip-flops and how they connect then make them describe this.
You're talking about RTL, which is exactly what these languages output.
Fundamentally they're not programming languages, unfortunately the initial instinct is to treat them as such and it leads to a ton of confusion.
So why am I wasting time with verilog then if I have to "design" in low-level then translate it to verilog?
If VHDL/Verilog would output RTL, you could easily analyze it just as you analyze assembly output of your favorite compiler. Unluckily the output is some proprietary bitstream for the FPGA.
In the early days of compilers, we were mostly writing FORTRAN.
These are tools meant to scale out designs that you already have a good understanding of. Hardware is implicitly bottom up because you're dealing with physics at the bottom. Very few pieces of hardware are lenient to a single cycle of latency much less 10's of milliseconds.
You mean typing random stuff into the editor without understanding what any of it does and hoping for the best? Because all of the online examples I've seen of "HDL done like programming" involved exactly that. In my experience, HDL design is very similar to functional programming (minus recursion) and the principles that work in one translate well to the other.
From what I see, every company in this sector works off of years of experience. Even if you are god's gift to the world of CPU design, the career options available to your are linked to the years you have worked.
I recently attended a presentation by ARM, where they boasted of this quote "At ARM, we don't really expect our graduates to be able to do anything until they're about 2 years into their careers, at which point they're expected to be decent at something"
And they boasted of it! To a theater full of CS students. And this was a 35 year old line manager making the comment.
If you want a career where you can rise based on merit, and not the numbers of years on your CV, don't pick hardware. I've seen it as an insider, the occasional "kid" comment being thrown around(ARM), designers refusing to work with junior verif staff(imagination tech), people who have written a project from scratch over 2 years not given tech lead roles as the project grew, etc.
Such an enviroment quite often bleeds younger engineers. It saps desire and motivation. Because there are so few startups, good practices don't matter. Here's some conversations I've had:
"We don't need to write tests for our software ISG, its a waste of time" "What is wrong with me writing an extension to the software as a special case? It only executes that code if the username is me" "We don't do training for new people".
I routinely see younger engineers with 3-4 years of experience try and spin out of CPU/Hardware and into Software.
I think this is highly contextual, and how you interpreted that statement depends on how you were primed to interpret it. You can assume they meant that it's an age based hierarchy, or you can assume that it's an extremely complex job that a CS degree doesn't adequately prepare you for, so it takes a couple years of apprenticeship before you are qualified to do something yourself.
There are plenty of industries like this, where it's not just about being smart and using your intellect to put the relatively small amount of parts together into something that works, but instead has a large chunk of learned wisdom that is not quick to impart, the importance of which is sometimes discounted until it's dealt with, and the consequences of messing up are fairly high.
Some examples: electricians, plumbers, architects. Fields that we've decided often require certification and that certification requires an apprenticeship period, because we don't want electrical fires or dead electricians, or water damage to our homes, or leaking sewage, or collapsing buildings.
In a lot of ways, I think the problem with the software engineering industry is that we don't have a system like this in place. Name me a programmer that thinks he was just as good at 22 at not making architectural or security mistakes as he was after 10 years more of programming and I'll name a programmer I never want to work with. Chances are they'll be the same person.
PS: I am a very very fresh hardware guy
Formal proofs and checks for a full unit.
RTL design with another bloke on a block.
Owner of an testing tool (developer).
Owner of a testbench for top level design.
Owner of process for bringing up new pieces of RTL(4 blocks, one after the other) into the top level design.
All of this was caused because the startup I started in was stupidly undermanned, and there wasn't anyone else about. When I left that company, they had gone and hired about 12 people to do all of those tasks, and I saw myself handing over tasks to more senior people.
I had experienced atleast 3-4 different areas of the process, and became really familiar with ARMV8 (what got me my next job).
Yet this guy over here is thinking that its gonna take 2 years for someone to be even able to do 1 of those tasks?
Could argue a really long time about individual cases, but I think the Hardware industry has a very big problem with arrogance. Most companies and senior people carry an air of "no one else can do what I do" kind of bs, that I experienced in SV quite a bit. They look down on junior devs thinking that they are incapable of doing anything.
I asked in my office just now, the reasons for leaving their first jobs:
1. Wasn't allowed to do RTL design work inside the CPU
2. Wasn't given any responsibilities other than manually writing coverage(automating this was not permitted)
3. Ownership of the block that he had written from scratch was given to a more senior person once it came to integrating it to the top level
4. Money
I find it extremely common for people(usually at the 2-3 year mark) to leave for greener pastures where they are taken in as experienced engineers, rather than useless babes. It might be a rite of passage, or it might just be the mistakes of an industry entrenched in the past
Software works differently , so no wonder it's more appealing.
And my impression from our interactions with the industry is that hardware and software are so culturally separated that there are hardly any people who end up experiencing enough of both to truly tackle the problems. Hardware people tend to think abstraction is a synonym for inefficiency, and software people often aren't familiar enough with the complications of hardware to abstract it efficiently.
That's what I've learned reading a lot of articles on age and HW. They've been consistent. The young people are often in verification as it's mostly manual work feeding tools or doing math that doesn't require tons of experience. That's at least the impression I'm getting of people's descriptions of verification vs designing hardware.
However the creation of the verification testbench and tests is as extensive as the design. The verification team has to work in parallel with the design team so they have enough time to get things done. If they make a mistake in verification it can still also cost a fortune.
If the testbench and tests are not well designed then you are screwed in a roundabout way because the ability to quickly find the bugs isn't there. The testbench has to work on both the rtl and the back annotated netlist.
Approximately 75% of the person-months and compute resources for most projects is verification work. I have been verification lead on multiple projects and design lead on multiple projects. In either role I always looked to assign verification resources as top priority because the project is screwed without it.
On the original topic: The problem is that most commenters are not in the verification field. They don't know the amount or type of work done in verification or the commercial tools involved. This is done in SystemVerilog to do UVM - Universal Verification Methodology. It is not just Verilog. People here are concerned with ease of design. The bottleneck in the industry is in ease of verification.
This is why dynamic typing (even if it is static type inference) is a bad idea in the hardware world. Embedded software programmers want C style types because they are often flipping bits or packing data into a specified data structure so that some hardware that expects particular bytes in particular places knows where to get it. In HDL, you are fitting your logic into very limited space, and need to constantly keep in mind what hardware the code you are writing is going to synthesize to.
Later, I realized that HDLs are often not really used like we'd think. Having written the logic in C or maybe Verilog, a lot of shops figure out what the actual desired hardware is and write verilog to specify that. So in verilog, rather than getting the compiler to infer a flipflop you can specify one and use verlilog to wire it up. Professional shops often basically use verilog to describe a circuit design in text, like a C programmer dropping into assembly.
The thing is that, like in the 1980s, you are so much fighting to get the fastest, smallest implementation possible, that like an 80s programmer, many default to assembly - to specifying the logic elements rather than leaving it to a compiler.
That means verilog as written many places is not really used like verilog at all - more like using verilog to express a netlist. Verilog is used to wrap vendor specific code so you specify a flipflop rather than a Xilinx flipflop, to make it easier to go to another vendor or to ASIC, but the clever features of verilog are only used for your test harnesses.
Part of the reason for this is that FPGAs are full of specific purpose hardware like dual port memories, and the compiler won't always infer them for you. That means you need to explicitly tell it to use them - and once you are explicitly specifying some parts of your system, you tend to do it more and more.
You can only use a HLL if you can rely on the fact that the compiler will nearly always put out much better code than you would be able to invent in a reasonable amount of time. This is the reason why even in the late 90th for performance-intensive code (games, multimedia) the critical parts were often still written in assembly instead of C/C++.
The problem is for FPGAs the bitstream format is very closed - so you can hardly dive into the low-level abyss and "look at the generated assembly code/bistream" to rewrite the generated assembly code/bitstream code until you are satisfied with the result; you have to use the HLL (VHDL/Verilog), where you cannot trust the quality of the generated code.
Dan Luu's explanation makes me think they're driven by trauma inflicted by their existing tools. ;)
(but really, I figure they have enough trouble dealing with actual HW concerns that they don't have the bandwidth to deal with SW stuff not immediately relevant to their HW concerns.)
But they're not.
So what's missing from the picture?
The hardware tooling suck.
IMO: Hardware people suck at writing software so they can't make better. And software people don't want to get there so there is noone to do better.
Added: Visio is to draw timing diagram, block diagram, and flow chart. Excel is used for designing FSM next state logic / output.
You need to prove that you can get a certain performance with a matlab algorithm, then you spend a week writing it into verilog.
Oh, and as someone whose bread and butter is ICs, I go out of my way to avoid creating FSMs. They're slow, difficult to debug, and hard to pass on to your successor.
I used that knowledge to write tetris in Verilog that output to VGA which after testing at the wave level in editor worked the first time I actually loaded it onto real hardware.[0]
For programming there is good material for this available, but not for VHDL/Verilog.
What does one lose if they do this? Why isn't this conversion more common?
Yes, it is. C can be converted to Verilog only if you structure the C code as if it were Verilog.
You have to understand that, fundamentally, the CPU is a lie. It is an illusionary abstraction layer fashioned out of bare transistors that pretends to be a von Neumann architecture machine, with a nice set of registers, a instruction pointer, and so forth that chugs dutifully through assembly language instructions, one after another. (Note that nothing about the von Neumann architecture requires binary; for example, the Babbage Analytical Engine computed in decimal.)
At the transistor / gate level, _everything_ is parallel until you impose some order on it and build in clocks and flipflops and so forth to impose some sort of structure and chronological ordering on things. Few, if any, high-level languages are equipped to describe that in any sensible way.
Verilog, VHDL, and so forth are not "weird". These HDLs, while not without their warts, describes the underlying reality of computing. It is assembly language and the high-level languages that are "weird".
(edit: grammar fixes + clarifications)
All abstractions “lie” in the sense that they present a perspective of the world that is slightly different to the reality — function calls “lie” about the operations that are really being performed, the ISA “lies” about the electronics of the CPU, and transistors “lie” about the underlying behaviour of the universe.
C obviously isn't a good fit for a hardware language — it's designed for software! That doesn't mean that there doesn't exist similarly abstract ways of writing hardware though (that express the inherent parallelism, etc.). It is likely that these “higher level languages for hardware” would result in less efficient hardware solutions, but that's always a trade-off that is made through abstractions. Writing a program in properly scheduled assembly code is going to be a lot more efficient than writing the same program in C.
The difference with hardware in terms of language abstractions is not that it behaves differently to software. We could easily define a programming language that expresses parallelism in a way that would map nicely to hardware. The problem, from my perspective at least (please correct me if you think I'm wrong) is that hardware needs to be extremely efficient — particularly because it cannot be easily changed. As a result, hardware languages don't tend to be particularly abstract. But this doesn't mean that hardware languages couldn't be more abstract!
If I can write a working C program, why can't I make working hardware?
Debugging HDL involves looking at timing waveforms in a simulator.
But how do you then explain the button on my FPGA that was not used in my Verilog anywhere that could switch the light off?
I think the answer ended up being that it was switching fast enough that small amounts of leakage current was enough to register as a TRUE value. And that 'undefined behavior' is worse than in C.
Evolution is a strange, strange thing.
Even funnier was that it wouldn't work on another FPGA. It was exploiting weird defects in that particular FPGA.
https://github.com/cdkersey/chdl
Being a fan of C++ myself, the idea of using template metaprogramming to represent hardware designs is something that I think is very cool.
I can say with a fair certainty that 0% of my class uses it now in their jobs. I wouldn't ever want to touch it again with a ten foot pole.
Guess that's a classic case of USA vs EU standards.
> "Vendors pushing high-level synthesis have a decades long track record of overpromising and under-delivering."
Abstraction is important, but in hardware detail matters. In software you can afford to have a less efficient language if it results in faster development. This is absolutely not true of hardware.
High-level synthesis has usually held out misleading promises of portability; that you might be able to get some complex application working written by (cheaper, easier to hire) software developers, then just drop it straight onto a chip. That's simply not true because the constraints are different.
Chisel looks extremely promising, as it addresses a lot of what I'd say were Verilog pain points:
Algebraic construction and wiring
Abstract data types and interfaces
Bulk connections
Hierarchical + object oriented + functional constructionSystemVerilog has all sorts of nice object-oriented features, but you can't use any of them for synthesizable code as you're dependent on the tool vendors supporting useful, yet complex things (e.g., classes, inheritance). Chisel avoids this whole problem by giving you access to extremely useful programming features (e.g., classes, functional programming) while still maintaining compatibility with whatever your ASIC/FPGA toolflow is.
Sure you have to specify things at the gate level, but you have the power to build abstractions on top of that (and indeed most of the ones other HDLs would provide are already there).
But companies won't adopt Chisel because the tools they have now just work. So Chisel is of academic interest, and little else.
Seems that unlike software devs, hardware devs don't have a say, they're merely drones to their managers. In software, lead dev will say that they need this linter, and his superior will have to either provide or argue. But it seems that ib hardware, management will just say "no", end of storg. You're not getting anywhere with this attitude.
This can also be so in software dev - in defunct bigcos.
Software can be rewritten and then can be patched with the new functinoality.
In hardware, typically, your customer is another group, department, company. And there is usually only one person to represent the go-between.
Hardware design is conservative because that one person is responsible for interfacing a project which simply cannot afford to be done incorrectly.
so low bandwidth, and high cost. Leads to unbridled conservatism.
I can live with not having to do 60 hours work weeks for tech companies that didn't even give equity.