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 this profession. 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 live up to. 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 lot of tools that are marketed as "it's easy to".
It's not unusual to develop software by jotting something down, make a test run with the debugger, see how it went, fix something and repeat. Intermittently, code snippets that are found in examples from the Internet might get copy-pasted into the software as well. This iterative work habit can be fairly productive with simple software, such as developing web sites or applications for smartphones: 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 method 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 won't stand in your way.
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 principles of design are followed (see Rule #1). If not, it may very well play dirty tricks for whatever or no apparent reason.
When there is no visible bug, that doesn't prove that there is no problem — 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.
Developing a design 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 techniques that they offer for achieving this exact goal.
It's making proofs to yourself, like mathematical proofs, that the logic design is correct, rather than trying to think in terms of "if this happens, do that". The logic should work in the most bizarre corner cases not because these possibilities 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 working 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. Which is nonsense, of course.
So first and foremost, let's put on the table that unless the Verilog coding style obeys some strict rules, the simulation and the hardware can do completely different things (same with VHDL, of course). See Rule #1.
But simulations cover a limited span of time and scenarios, even when done correctly. 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 the option to do single-step. 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 as a trigger for 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.
This is not just a time saver, 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 is not a programming language when it's used for synthesis. 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, report an error.
Synthesizers, on the other hand, generate logic that is based upon a limited set of logic elements. It's therefore quite easy to write code in Verilog that results in unreliable logic, or even code that is impossible to implement on an FPGA. To make things worse, if there is no way to implement the Verilog code as logic on the FPGA, synthesizers often produce logic that has a different behavior. Quite often, the synthesizer does this without emitting a warning. In other words, the logic on the FPGA does something that is different from the expected (i.e. simulated) behavior, and this happens with no warning.
And to make things even worse, bugs in synthesizers are much more common than in compilers. Creative coding style can definitely expose such bugs.
All said above is true for VHDL as well, of course.
So 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 of people", you're on the safe side.
Sticking to well-established coding style has another advantage: Portability. Like it or not, today you work with one synthesizer, and tomorrow you'll find yourself with a completely different FPGA and different development tools.
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 of the code, but some coding patterns will be seen more than others. Those are the ones to imitate.
Almost needless to say, work according to the RTL paradigm. It's not just a well-established coding style, but this what the FPGA tools expect.
Textbooks and tutorials about Verilog can be misleading, because they usually attempt to be comprehensive. Hence they often cover a lot of possibilities that are syntactically correct, but are rarely used, and sometimes even inadequate for synthesis.
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 clocks are handled correctly.
These are the main topics, more or less:
- Think through how and when the data flows through different stages of pipelines, in particular when the pipelines can get stalled (fully or partially). The same is true for any data flow, actually.
- Make sure that the timing constraints are correct. In particular, read the datasheets of external components and do the math. This page discusses checkup of timing.
- Pay attention to the clocks: Are they stable from the first moment they're used? Is their jitter low enough for their purpose?
- Clock domains crossing: 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 also 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.