Being an FPGA designer requires a variety of skills, which I surely can't summarize in one page. But there are a few general guidelines that are relevant in particular in the FPGA field. I've tried to sum them up as the five Golden Rules for FPGA design.
Rule #1: Know what you're doing
This is the first rule, and the most difficult one to follow. It means understanding what each line of code, constraint or configuration command means, and what implications it has. It's understanding the underlying theory of logic design, and how the design tools respond to what we feed them with.
It's the opposite of working by trial and error, something that vendors of all hi-tec development tools implicitly encourage, by offering debuggers, simulators and a whole host of tools that are marketed as "it's easy to".
It's not unusual to develop software by jotting something down, give it a test run with the debugger, see how it went, fix something and repeat. Intermittently, code snippets found in examples from the Internet might get pasted into the software as well. This iterative work practice can be fairly productive with simple software, such as developing web sites or mobile applications: Small bugs aren't all that serious and can be fixed as they appear, or so is the common attitude.
But the more sophisticated the software is, the faster does this practice lead to a deadlock. And when it comes to FPGA design, iterative development practices tend to bite back quickly and hard. FPGA design tools are by far not helpful in preventing or warning against serious mistakes. You're absolutely welcome to mess up completely, the tools will just play along.
Rule #2: Guarantee that it works
Or: "It works" is not good enough.
An FPGA is a piece of electronics, not a computer. It's nevertheless as repeatable and dependable if certain design principles are followed (see Rule #1). If not, it may very well play dirty tricks for whatever or no apparent reason.
The absence of a visible bug proves very little — that holds true in any field. For example, if your plumber finishes working and there's no water leaking from the pipe, it doesn't mean that the work was done correctly. The pipe may begin leaking later on because the water pressure has risen, or because someone touched it accidentally.
And yet, we all fall in the trap of making a quick test, or a more extensive one, and consider it done when everything seems fine. That's fairly acceptable for a leaking water pipe, when developing a simple web site or even software that isn't part of a critical application. But with FPGAs, that's simply not good enough.
Designing for an FPGA means guaranteeing that the design will work. It's forcing the design tools into producing a bitstream that is failproof by using the means that they offer for meeting this exact goal.
It's making mathematical-like proofs to yourself that the logic design is correct, rather than trying to think through, and possibly test, certain scenarios. The logic should work in the most bizarre corner cases not because they were taken into account separately, but for the same reason that a correct mathematical expression remains true no matter what.
The fact that it works in the end is no reason to celebrate. It's the natural outcome of designing correctly.
Rule #3: Simulate wisely (or not at all)
One of the most common complaints made by FPGA newbies is that their simulation works perfectly, therefore their design is fine, and now there must be some problem elsewhere. Like, please.
So first and foremost, let's put on the table that unless the Verilog / VHDL coding style obeys some strict rules, the simulation and the hardware can do completely different things. This is the way it is, see Rule #1.
But even when done correctly, simulations cover a limited span of time and scenarios. Even with a medium-sized design, it takes forever to simulate what happens inside the FPGA during, say 100 ms. Not only that, running the design for real on hardware often exposes to the logic to scenarios that the designer didn't imagine, and therefore didn't simulate. So a design that went through simulation perfectly might turn out a completely fiasco on hardware, just because the FPGA runs much longer than the simulation covers, or unexpected things happened.
To make things worse, bugs are hard to find on hardware, since everything happens in parallel. Unlike software debugging, there's rarely a sequence of events to follow, let alone single-step through with a debugger. There are of course tools for tracing signals inside the FPGA, but it's not always easy to figure out which signals to trace, and what condition to use to trigger the collection of trace data.
So make it a goal to use simulations as little as possible. If you're using simulations to fix your design back-and-forth in order to "make it work", odds are that you'll run into trouble when you'll try it on hardware.
Rather, attempt to write the FPGA design to work perfectly from the first run. This requires some thinking through before writing the first row of code, as well as knowing what you're doing (Rule #1 above). The simulation should just confirm that you got it right, or possibly detect some silly typo. Reaching this goal is a process of learning and improving, but it pays off. In the end, simulations become a waste of time, as it never finds anything to fix.
Not only the work on running the simulations is saved this way, but the bugs to fix on hardware become fewer and easier to spot.
I'm very aware that the common first lesson on FPGA design is "first we simulate, then we run on hardware", but that's good for the first lesson.
Rule #4: Don't push your luck
Or: Stick to well-established coding style.
Verilog, when used for synthesis, is not a programming language (and neither is VHDL). The main difference is that if you write something that is syntactically correct in any programming language, the compiler (or interpreter) is guaranteed to execute exactly what the syntax means. Or at worst, throw an error.
Synthesizers, on the other hand, generate logic based upon a limited set of basic logic elements. It's therefore quite easy to write code in Verilog that is impossible to implement on an FPGA, or that results in logic whose reliability can't be assured. To make things worse, when faced with code that can't be implemented as logic, synthesizers often implement logic that doesn't match the expected (i.e. simulated) behavior, sometimes without even issuing a warning.
And to make things even worse, bugs in synthesizers are much more common than in compilers. Creative coding style can definitely expose them.
The only way to stay away from trouble of this sort is to adopt a coding style that is widely used by others. The question to keep in mind is "if the synthesizer gets confused by this code snippet, how many will get angry except for me?". If the answer is "a whole lot", you're on the safe side.
Another important aspect of sticking to well-established coding style is portability. Today you work with one synthesizer, tomorrow you'll find yourself with another FPGA vendor, like it or not.
To get an idea what well-established coding style looks like, look at the Verilog code that is generated by the tools on behalf of the tools' built-in IPs. There's some diversity between different authors, but some coding patterns will be seen more than others. Those are the ones to follow.
Verilog textbooks and tutorials can be misleading, because they usually attempt to be comprehensive. Hence they often cover a lot of possibilities that are syntactically correct, but rarely used, and sometimes even not synthesizable.
Last but not least, the RTL paradigm. Stick to it, or push your luck big time. I didn't mention it until now, because following well-established coding style has this covered.
Rule #5: Timing is Everything
Logic design isn't software programming. It's not enough to get the correct value in Verilog wires and registers, but it's no less important that it appears in the right place at the right time. Also, that the elements that set the timing — the clocks — are up to the job.
It boils done to this, more or less:
- Thinking through how and when data flows through different stages of pipelines, in particular when they can get stalled (fully or partially). The same is true for any data flow, actually.
- Getting the timing constraints right. In particular, reading the datasheets of external components and doing the math. This page discusses timing checkups.
- Minding the clocks: Are they stable from the first moment they're used? Is their jitter low enough for their purpose?
- Crossing clock domains: Is there need for resynchronization logic? If so, does it guarantee a failproof transmission of the signal from one domain to another? More on this here.
In short, unlike computer programs, logic design is not just about what will happen, but when it happens.
Nobody said FPGA design is easy, and none of these five rules is easy to follow. Each of them requires both knowledge and a level of self discipline. And still, it's definitely worth the effort to stick to these rules. Doing so saves a lot of frustration, in particular at that last phase of the project, when everything is expected to just work.