Translated from The Golden Rules for proper FPGA design

Introduction

As an FPGA designer, one needs to possess a multitude of skills, which I cannot summarize on one page. However, there are some general guidelines that are particularly relevant in this profession. I have attempted to summarize them as the five golden rules for FPGA design.

Rule #1:

Know what you are doing

This is the first rule and also the most difficult to follow. It means understanding the meaning and implications of every line of code, constraint, or configuration command, as well as the underlying theory of logical design and how design tools respond to what we input into them.

This is in contrast to trial and error, which is subtly encouraged by vendors of high-tech development tools, who provide debuggers, simulators, and numerous tools marketed as "easy to use".

It is not uncommon to develop software by scribbling down something, testing it with a debugger, seeing how it works, fixing some issues, and repeating the process. Occasionally, code snippets from the internet may also be copied and pasted into the software. This iterative approach may be quite efficient for simple software development, such as developing websites or smartphone apps, where small errors are not critical and can be fixed as they appear. This is a common attitude.

However, the more complex the software, the faster this approach leads to a dead end. And for FPGA design, iterative development practices will quickly backfire, and backfire badly. FPGA design tools are far from able to prevent or warn against serious errors. You can completely mess things up, and the tools will not stop you.

Rule #2:

Ensure it works, or rather, "it works" is not good enough

An FPGA is an electronic device, not a computer. If certain design principles are followed (see Rule #1), it can be just as repeatable and reliable. If not, it is likely to play tricks on you for no apparent reason.

Just because there are no visible bugs does not mean there are no problems - this applies to any field. For example, if a plumber finishes a job and the pipes do not leak, it does not mean the job was done correctly. Because as the water pressure increases or someone accidentally touches the pipes, they may start leaking later.

However, we all fall into the trap of doing a quick test or a more thorough test, and when everything seems fine, we consider the job done. This is acceptable for leaking pipes, developing a simple website, or even software that is not critical. But for FPGAs, this is far from enough.

Developing a design for an FPGA means ensuring that the design works. It is about forcing the design tools to produce a fault-free bitstream, using the techniques they provide to achieve this goal.

It is like doing a mathematical proof for yourself, being certain that the logical design is correct, rather than thinking "if this happens, do that". The logic should work even in the strangest edge cases, not because these possibilities are considered separately, but because the correct mathematical expression remains true regardless.

In the end, the fact that it works is not a cause for celebration. It is the natural result of doing things correctly.

Rule #3:

Simulate wisely (or not at all)

One of the most common complaints from FPGA newcomers is that their simulation works perfectly, so their design must be fine, and the problem must be somewhere else. Of course, this is nonsense.

First, let's make one thing clear: unless the Verilog coding style follows some strict rules, simulation and hardware can be completely different things (this also applies to VHDL). See Rule #1.

But even if the simulation is done correctly, it only covers a limited amount of time and scenarios. Even for a moderately sized design, simulating what happens inside an FPGA over, say, 100 milliseconds can take a long time. Moreover, actually running the design on hardware often reveals scenarios that the designer did not imagine, and therefore did not simulate. So, a design that simulates perfectly can completely fail on hardware, simply because the FPGA runs for a much longer time than the simulation covers, or because something unexpected happens.

Worse still, debugging on hardware is difficult because everything happens in parallel. Unlike software debugging, where there is often a sequence of events to follow, and options for single-stepping, there is none of that here. Of course, there are some tools that can track signals inside an FPGA, but it is not always easy to figure out which signals to track, and what conditions to use as triggers to collect the tracking data.

So, use simulation as little as possible as a goal. If you are using simulation to fix your design, trying to "make it work", you will likely run into trouble when you try to run it on hardware.

Instead, try to write an FPGA design that works perfectly from the first run. This requires some thinking before writing the first line of code, and understanding what you are doing (see Rule #1 above). Simulation should only be used to confirm that you did things right, or possibly to detect some silly typos. Reaching this goal is a process of learning and improvement, but it is worth it. In the end, simulation becomes a waste of time, because it will never find anything that needs to be fixed.

This is not just about saving time, but also about making the bugs that need to be fixed on hardware fewer and easier to find.

I am well aware that a common first lesson in FPGA design is "first we simulate, then we run on hardware", but this only applies to the first lesson.

Rule #4:

Do not force it, or rather, stick to mature coding styles

When used for synthesis, Verilog is not a programming language. The main difference is that if you write something that is syntactically correct in any programming language, the compiler (or interpreter) guarantees to execute exactly what the syntax expresses. Or, in the worst case, reports an error.

... (rest of the translation remains the same)

On the other hand, synthesizers generate logic based on a limited set of logical elements. Therefore, it is easy to write Verilog code that results in unreliable logic, or even code that cannot be implemented on an FPGA. What's worse, if there is no way to implement the Verilog code as logic on an FPGA, the synthesizer will often produce logic with different behavior. Many times, the synthesizer will do so without issuing a warning. In other words, the logic on the FPGA is different from the expected (i.e., simulated) behavior, and there is no warning.

What's worse, bugs in synthesizers are more common than bugs in compilers. Creative coding styles can definitely expose such bugs.

All of the above applies to VHDL as well.

Therefore, the only way to avoid such problems is to use a widely-used coding style. The question to keep in mind is: "If the synthesizer is confused by this piece of code, how many people besides me will be frustrated?". If the answer is "many", then you are safe.

Sticking to a mature coding style has another advantage: portability. Like it or not, you may be using one synthesizer today, and tomorrow you may find yourself using a completely different FPGA and different development tools.

To understand what a mature coding style looks like, take a look at the Verilog code generated by the tool's built-in IP generators. There are some differences between different code authors, but certain coding patterns will be more common than others. These are the ones to emulate.

It almost goes without saying that you should work according to the RTL (Register-Transfer Level) paradigm. This is not only a mature coding style, but it is also what FPGA tools expect.

Textbooks and tutorials on Verilog may be misleading, because they often try to be comprehensive. As a result, they often cover many possibilities that are syntactically correct but rarely used, and sometimes not suitable for synthesis.

Rule #5:

Timing is Everything

Logic design is not software programming. Just getting the correct values in Verilog wires and registers is not enough; they need to appear in the correct place and at the correct time. Also, clocks need to be handled correctly.

These are the main topics, more or less:

  • Thinking about how and when data flows through different stages of a pipeline, particularly when the pipeline can be fully or partially stalled. In fact, any data flow is like this.

  • Ensuring that timing constraints are correct. In particular, reading the datasheets of external components and doing the calculations. This page discusses timing checks.

  • Paying attention to clocks: are they stable from the moment they are first used? Is their jitter low enough for their purpose?

  • Clock domain crossings: is resynchronization logic needed? If so, does it guarantee glitch-free transfer of signals from one domain to another? More on this is here. In short, unlike computer programs, logic design is not just about what happens, but also when it happens.

Conclusion

No one said that FPGA design is easy, and these five rules are not easy to follow. Each one requires knowledge and a certain degree of discipline. However, following these rules is absolutely worth it. It can save a lot of frustration, especially in the final stages of a project, when everything is supposed to work as expected.