What do HDLs compile/synthesize to?

Just like a procedural programming language goes through several steps (compile, assemble, link) to produce an executable, HDLs must pass through several processes before a usable configuration file for the FPGA is generated. These include

  • Synthesis --- convert the HDL code into a netlist describing connections between logical elements.

  • Mapping --- Convert the netlist into a more refined netlist that uses resources actually available on your FPGA device.

  • Place and route --- select which of the actual resources on the device will be used for each of the required elements in the mapper output, and choose which routing resources will be used to interconnect them.

  • Bitfile generation --- convert the place and route output to the format actually used to program the device.

So if, when you ask what is the output of synthesis, you mean what's the output of the first step of this process, then it's an intermediate file used as input to the mapper. If you mean what is the output of the whole process, it's a bitfile that the FPGA can use to configure all of its logic and routing resources.

Register Transfer Logic (RTL) is the result of the first translation phase, before it is mapped to the vendor-specific resources, which are not portable between vendors or even between different FPGA from the same vendor. Essentially RTL shows both the combinational logic and the synchronous registers (D flip flops), so state machines are recognizable. RTL is pretty consistent between Altera and Xilinx, and is probably the most interesting and useful phase to inspect. Synthesis problems first become visible at the RTL phase, and the design is still recognizable. Once it goes to vendor-specific mapping it gets chopped up and scrambled. Trying to decode a chip-specific bitstream is high-cost, low-benefit, and will be useless when you move to a different vendor or even a different size FPGA in the same family. You can see what you need to see at the RTL level.

It's always good practice to test your newly developed Verilog or VHDL code by instantiating it inside a test bench or a simple toplevel module, and inspecting the RTL code. Xilinx ISE is very nice for inspecting RTL as a schematic (though it sometimes misses things.) The most common issues are:

  • 1-bit nets where a bus was intended
  • chunks of logic unexpectedly being removed by the optimizer... similar to how a simple spinlock delay loop gets silently deleted by code optimization.
  • outputs not completely specified, because of procedural approach instead of truth-table approach. If tool thinks output ends up always 0 or always 1, it will drop all the logic that generates that result.
  • module logic gets trimmed out because one of the sub-modules was optimized to always 0 or always 1; this can cascade all the way up to toplevel

This RTL inspection gets very unwieldy unless you keep your modules small and simple. Using a testbench is an important tool.

I too came from embedded systems programming first and verilog second, and the biggest hazard for people like us when learning HDL coding is that it looks like a procedural programming language, and it feels like a procedural programming language (during simulation), but then everything blows up when you try to synthesize working code. You really have to think about what the hardware has to look like, and make sure the RTL code includes all the hardware you expect.

Other than the fact that Verilog/VHDL involve typing some source code into a computer file, there's not really much resemblance to traditional C/C++/etc. Very little of your programming experience will transfer. Focus on dividing big problems into little problems, documenting everything in great detail, and writing test benches. Also invest in a good digital sampling oscilloscope if you don't already have one. Take a look at some of the example code published on opencores.org, as with C/C++ you can learn a lot of technique (both good and bad) from reading other people's code.

One thing that drives me nuts about FPGA development is that source control is not something that the toolchain vendors seem to think is an important feature. Xilinx Vivado is particularly bad in this regard, their advice seems to be to re-generate the project files from scratch when doing a new checkout. Trying to do a project handoff with 100Mb+ zip files is daunting.

The other thing that drives me nuts about FPGA development is that Quartus/ISE/Vivado tools don't really have a satisfying way to quell the flood of warning messages. When I write C/C++ programs, I expect to be able to address every warning message individually and either fix it or sanction it, so that I can eventually get a clean compile with zero warnings. Never really seen anyone achieve that in FPGA development; other FPGA developers (who are smarter than me) seem to just accept that a normal project has lots of diagnostic messages, which they often simply ignore, leaving it down to doing labwork and verifying on real hardware.

If you ever develop your own FPGA board (which I don't recommend), be sure to bring out any unused I/O pins to a header somewhere -- as many as you can manage -- because that's going to be your lifeline when you have to debug the FPGA code, or implement some eleventh-hour patch.

You mentioned programming in assembly language as a way to exercise precise control over what the computer is doing, and it is possible to exercise similarly precise control over FPGA code by using non-portable, vendor-specific primitives. This will be different for each vendor and each FPGA, just as assembly language is different for different CPUs. For Xilinx you would write a constraints file (different for ISE toolchain or Vivado toolchain). The constraints file would call out specific instances or specific nets, and specify timing requirements. Typically the low-level CLBs/LUTs/whateverUnits are arranged in a grid, so you can pin down a specific low-level primitive to live at a specific X,Y grid location. Look up the old Xilinx "FPGA Editor" for the Spartan 3 series, they used to encourage people to use it that way. I think the newer series 7 and Zynq chips it's not supported. Like assembly, it's very specific to the technology, and is thus kind of a volatile skill set.

Similar to assembly, for anything other than a trivial 'homework' exercise, you really want to minimize how much assembly you write; use C/C++ for 98%-99% and only write assembly for the 1% that is performance-sensitive. If for example you have an FPGA design that requires some sub-process to run at 200MHz, it's worth diving into the low-level mapping to see what the tools are doing. Best payoff for optimization is if you can eliminate unnecessary work stages. Only after you're pared the hot elements down to the bare minimum, only then is it worthwhile to start manually routing which IOBs belong at which grid locations. Let the machine do the bulk of the work, so you can focus your efforts.

The physical primitive of an FPGA is a configurable logic block (CLB).

Each logic block is given a dedicated location in memory, so-called configuration memory, that determines how it is configured and where it connects to.

HDL ultimately ends up as a bunch of ones and zeroes, a so-called bitstream that is placed in this configuration memory.

Most FPGAs do not have on-board non-volatile configuration memory. Instead, the configuration bitstream is stored on an external configuration FLASH ROM and on power-up the FPGA loads that bitstream from external non-volatile memory into its internal configuration SRAM which is directly connected to and controls the CLBs.

Unlike software, this bitstream is not "run". It is just loaded and afterwards it simply "is". It is less like instructions being executed and more like registers containing settings.

It is a file such as a *.bit. There is no standard format. I am not sure why you would want to write a simulator yourself when FPGA development tools come with a simulator. Much effort is put into this and they know their devices better than anyone else because unlike software, each primitive that is specified in the bitstream must be physically located somewhere on the FPGA die and the floor plan can make or break some designs.