Inference vs. Instantiation vs. Core Generation (GUI tool)

How should you create your module blocks?

If there’s one thing that’s true about FPGAs, it’s that there’s always more than one way to get the job done. There are two languages, there are multiple FPGA companies, there are various simulation tools, sometimes the choices can be overwhelming. Yet another choice you have to make is how to generate your modules inside of your FPGA. There are three main choices for creating blocks of code:

  • Infer the Module
  • Instantiate the Module
  • Use a GUI to create the Module

The modules that I’m talking about are the primitives of the FPGA fabric. There are actually a number of elements that are built into the FPGA fabric by default. A few of them are tri-state buffers, Block RAMs, FIFOs, Multipliers, etc. If you want to create a Block RAM for example, you have a few different ways to choose to do it. This article dicusses each in detail so you can choose what works best for your application. I will use as an example the creation of a Single Port Block RAM (BRAM) inside of your FPGA. It might help to first read this some background information about What is a Block RAM. Below I demonstrate how to infer, instantiate, and use a GUI to create a Single Port Block RAM component and discuss the pros and cons of each.

Inferring A Module in VHDL or Verilog

The definition of infer is, “to deduce from evidence and reasoning” and this is basically what’s happening when you infer some functionality in VHDL or Verilog. The tools are trying to deduce what you want based on your VHDL or Verilog code. Inference is usually my go-to approach when trying to get my FPGA to do what I want. The reason why I like this approach is that it’s the most flexible. If you decide to change from Xilinx to Altera for example, your VHDL or Verilog code does not have to change at all. Or if you need to create a slightly different variant of an existing module, you can usually just do this with Generics or Parameters, which is an easy process. Let’s look at how to infer a Single Port BRAM.

The Single Port Block RAM configuration is useful when there is just one interface that needs to retrieve data. This is also the simplest configuration. One use case would be storing Read-Only Data that is written to a fixed value when the FPGA is programmed. We will infer, instantiate, and create via the GUI a Single Port Block RAM.

Now a Block RAM is a memory storage element, so when the tools (synthesis tools) see a large memory, they will likely try to push it to Block RAM. If you create memory that’s something like 8 bits wide and 16 words deep that may or may not be pushed to Block RAM. This small of a memory might just be done using regular flip-flops. This is the downside of inference, you can’t always guarantee results. You need to double check that the synthesis tools are creating what you want! Let’s show some Verilog code.

module SRAM_Single_Port #(parameter WIDTH = 16,
                          parameter DEPTH = 256)
  (
   input                     i_Clk,
   input                     i_Wr_En,
   input [$clog2(DEPTH)-1:0] i_Addr,
   input [WIDTH-1:0]         i_Wr_Data,
   output reg [WIDTH-1:0]    o_Rd_Data,
   );
  
  reg [WIDTH-1:0] r_Mem [DEPTH-1:0];

  always @(posedge i_Clk)
  begin
    if (i_Wr_En)
      r_Mem[i_Addr] = i_Wr_Data;
  end

  assign o_Rd_Data = r_Mem[i_Addr];

endmodule // SRAM_Single_Port

Briefly I’ll explain what’s going on above. We have created a module with parameters that specifcy the WIDTH and the DEPTH of this Single Port RAM. This is great because if we need a Single Port RAM in the future with a different WIDTH or DEPTH combinations, it’s easy to specifcy those when we instantiate this module! The memory itself is the r_Mem reg, which has width and depth (making it a 2D memory storage element). The same process can be done for VHDL as well and the tools will do the rest. Again, this is my favorite method because it’s the most flexible and portable, so use it unless there’s a good reason you can’t!



Instantiating a Module in VHDL or Verilog

Instantiation is probably the most complicated of the three ways to create blocks. It’s also 100% not portable. Meaning if you instantiate an Altera BRAM, it will not work to port that code to Xilinx. However, the main benefit to instatiation is that you know exactly what you’re getting. Also there are some situations in which you’re just unable to tell the synthesis tools to infer something for you. One example would be a high-speed transceiver (SERDES). Those almost always require either instantiation or creation via the GUI.

One trick to making instantiation slightly more portable is to wrap up an instantiated module inside of a wrapper VHDL or Verilog file. That way if you change FPGA vendors, you only need to change your wrapper file and not a bunch of code inside of a big complicated module. Another example where I’ve had to use instantiation was to create specific behavior of a FIFO. I wanted to create a First-Word-Fall-Through (FWFT) FIFO but I was unable to get the tools to infer one, so I had to instantiate it directly. Let’s take a look at instantiating a Block RAM in Lattice.

SB_RAM256x16 ram256x16_inst (
.RDATA(RDATA_c[15:0]),
.RADDR(RADDR_c[7:0]),
.RCLK(RCLK_c),
.RCLKE(RCLKE_c),
.RE(RE_c),
.WADDR(WADDR_c[7:0]),
.WCLK(WCLK_c),
.WCLKE(WCLKE_c),
.WDATA(WDATA_c[15:0]),
.WE(WE_c),
.MASK(MASK_c[15:0])
);

Clear as mud huh? That’s the other downside to instation, it’s not obious what the hell is going on because FPGA vendors like to use horrible signal names! The way I found the instantiation pattern for a Lattice BRAM was to find the Memory Usage Guide and look there. If you really want to understand the BRAM instantiation above, you’ll have to read that document.

Creation of a Module using the GUI

Screencap of tool

Example of GUI creating Block RAM (Xilinx)

This is probably the best method for a beginner in FPGAs to get started with. The reason is that it’s hard to make mistakes, the GUI can be helpful in guiding you. Also if you have a very complicated module like a LPDDR Memory Controller then you likely will need to rely on the GUI to help you through that process. There are just too many options to set up, the GUI will make sure you don’t make mistakes. The problem with GUI creation is that it does not scale at all. If you need a 1024 x 16 BRAM and then another 256 x 8 BRAM you need to run through the GUI tool twice to create each of those components. And then if anything changes you have to run through the whole process again.

Summary

Let’s summarize what we’ve learned in this article

  • Inference – Portable between FPGA Vendors (e.g. Lattice/Xilinx/Altera)
  • Inference – Flexible (Can just change Generics/Parameters and have a new module)
  • Inference – The tools need to understand what you want, double-check results
  • Instantiation – You get exactly what you want how you want it
  • Instantiation – Easy to make mistakes and most confusing (recommended for advanced FPGA people)
  • GUI Tool – Easiest to get started with (recommended for beginners)
  • GUI Tool – Downside is each module needs to be generated even if minor change is made (e.g. Width of BRAM changes)

FPGA-101