4. HDL

Primary approach to digital logic design in FPGAs and ASICs. We use it to describe functionality and/or behavior of digital circuits. It is how abstract, design and develop at a higher level.

The most popular are VHDL, Verilog and SystemVerilog. VHDL seems to be the preferred language in Defense industry. Verilog for tech/chip/commerical.. the rest of the world. SystemVerilog also in tech/commercial, and primarily/intially used for verification.

VHDL enforces stricter rules and is typed centric? verbose. wordy, lengthy, less permissive. Strict. libraries..

Verilog is more C like, compact. loosely typed.. Don’t have to do component instantiation.

SystemVerilog, even more compact code. More object oriented like? higher level of abstraction. Helps with verification environment and approaches.

I’m not going to list all the differences under the sun..

I feel like.. HDL should just be purely HDL like basic syntaxs and templates… with out application. then as we talk about the different logic operation or circuits… we’ll provide snippets of the HDL code which will synthesize into these circuits. HDL sprinked across the sections……

The HDL language is expansive? but for FPGA synthesis, you can only use a subset of the language. The language can be used for both development and verification. Alot of the verification syntax/HDL is not synthesizable. We will first focus on synthesizable and return for test bench, validation, verification.

4.1. VHDL

just some of the basic stuff… examples and templates.

4.1.1. libraries and use

1  LIBRARY IEEE;
2  USE IEEE.std_logic_1164.ALL;
3  USE IEEE.numeric_std.ALL;

Warning

DO USE

IEEE.numeric_std.ALL;

DO NOT USE

IEEE.std_logic_arith

4.1.2. entity

this is how we abstract digital components and modules. this is how you encapsulate functionality, or create black box.

 1  LIBRARY IEEE;
 2  USE IEEE.std_logic_1164.ALL;
 3  USE IEEE.numeric_std.ALL;
 4
 5  entity fpga_top is port (
 6          clk : in std_logic;
 7          rst : in std_logic;
 8          somein : in std_logic;
 9          anotherin : in std_logic_vector(7 downto 0);
10          someout : out std_logic;
11          anotherout : out std_logic_vector(15 downto 0)      -- notice no ';'
12  );                                                          -- notice ';'
13  end fpga_top;
14
15  architecture rtl of fpga_top is
16
17      --signal declarations
18      --component declarations
19
20  begin
21
22      -- RTL body.
23
24  end rtl;

in the following, which can be in some other file, we create more entities. i usually keep one entity per file for organization, but here i am just listing two

 1  LIBRARY IEEE;
 2  USE IEEE.std_logic_1164.ALL;
 3  USE IEEE.numeric_std.ALL;
 4
 5  entity some_component is port (
 6          clk : in std_logic;
 7          rst : in std_logic;
 8          someout : out std_logic
 9  );
10  end some_component;
11
12  architecture rtl of some_component is
13
14      --signal declarations
15      --component declarations
16
17  begin
18
19      -- RTL body.
20
21  end rtl;
22
23  -- just for example...
24  entity another_comp is port (
25          clk : in std_logic;
26          rst : in std_logic;
27          someout : out std_logic
28  );
29  end another_comp;
30
31  architecture rtl of another_comp is
32
33      --signal declarations
34      --component declarations
35
36  begin
37
38      -- RTL body.
39
40  end rtl;

4.1.2.1. architecture vs structure vs behavior

4.1.3. component

  1. you create your component with entity directive? (see entity section)

  2. then you declare its usage, in another entity or testbench.

  3. then you instantiate the component where it is used and label it.

  4. map port signals

re-using the fpga_top entity we created earlier..

 1  LIBRARY IEEE;
 2  USE IEEE.std_logic_1164.ALL;
 3  USE IEEE.numeric_std.ALL;
 4
 5  entity fpga_top is port (
 6          clk100 : in std_logic;
 7          clk150 : in std_logic;
 8          rst : in std_logic;
 9          dout1 : out std_logic;
10          dout2 : out std_logic
11  );
12  end fpga_top;
13
14  architecture rtl of fpga_top is
15      --signal declarations
16
17      -- 2. component declarations        -- for code readability, can create a separate component.vhd file and declare them all there.
18                                          -- actually, i'd recommend doing so, it prevents this section from becoming unnecessarily long
19      component some_component is port (
20          clk : in std_logic;
21          rst : in std_logic;
22          someout : out std_logic
23      );
24      end component some_component;
25
26      component another_comp is port (
27          clk : in std_logic;
28          rst : in std_logic;
29          someout : out std_logic
30      );
31      end component another_comp;
32
33  begin
34      --  3. component instantiation
35      DUT1_label : some_component port map (
36          clk => clk100,
37          rst => rst,
38          someout => dout1
39      );
40
41      DUT2_label : another_comp port map (
42          clk => clk150,
43          rst => rst,
44          someout => dout2
45      );
46  end rtl;

By instantiating components within other entities, you create a hierarchy and organization for your design.

Note

Notice => used to assign signals to ports. verus <= to assign values or signals to signals!

4.1.4. Object Types

signals

variable

constants

4.1.4.1. Signals

These are the common ones I’ve used.

std_logic

std_logic_vector

unsigned

signed

integer

range natural 0 to

natural

arrays

custom

4.1.4.2. Assignment/Assigning

<= signal assignment

:= variable assignment, signal initialization.

4.1.4.3. Conversions and Cast/Casting

first, make sure you are using IEEE.numeric_std.ALL

second, the ones to take notice of are the integer related ones. because the integer uses the system bit rep, most likely 32bits. we’ll need to specify how many bits/registers we want to use to represent a numeric value when entering the circuit/hw/bit level.

4.1.4.3.1. TO UNSIGNED
4.1.4.3.1.1. integer to unsigned
1  unsigned_signal <= to_unsigned(integer_signal, desired_unsign_length);
4.1.4.3.1.2. signed to unsigned
4.1.4.3.1.3. std_logic_vector to unsigned
1  unsigned_signal <= unsigned(std_logic_vector_signal);
4.1.4.3.2. TO SIGNED
4.1.4.3.2.1. integer to signed
1  signed_signal <= to_signed(integer_signal, desired_sign_length);
4.1.4.3.2.2. unsigned to signed

keep in mind the needed extra bit for the sign!

1  signed_signal <= signed(signed_signal);
4.1.4.3.2.3. std_logic_vector to signed
1  signed_signal <= signed(std_logic_vector_signal);
4.1.4.3.3. TO STD_LOGIC_VECTOR
4.1.4.3.3.1. integer to std_logic_vector
1  -- only positive integers
2  SLV_signal <= std_logic_vector(to_unsigned(integer_signal, unsigned_length));
3
4  -- positive and negative integers
5  SLV_signal <= std_logic_vector(to_signed(integer_signal, signed_length));
4.1.4.3.3.2. unsigned to std_logic_vector
1  SLV_signal <= std_logic_vector(unsigned_signal);
4.1.4.3.3.3. signed to std_logic_vector
1  SLV_signal <= std_logic_vector(signed_signal);
4.1.4.3.4. TO INTEGER

i dont think i ever really use this.. maybe in verification?

4.1.4.3.4.1. std_logic_vector to integer

but remember whether signed or unsigned, it is just a set of registers, a vector.. signed/unsigned is just how we interpret the vector. we need to tell the system how to interpret our SLV.

1  int_signal <= integer(unsigned(std_logic_vector_signal));
2
3  int_signal <= integer(signed(std_logic_vector_signal));
4.1.4.3.4.2. unsigned to integer
1  int_signal <= to_integer(unsigned_signal);
4.1.4.3.4.3. signed to integer
1  int_signal <= to_integer(signed_signal);

4.1.4.4. resize

Again, make sure you’re using IEEE.numeric_std.ALL Use this to resize “sign extend” your vector/register that holds your signed/unsigned value. This is because the MSB in a signed value needs to be repeated to maintain original value. Use before doing arithmetic.

1  resize (signed; size);
2
3  resize (unsigned; size);

4.1.5. process

You’ll be writing/using this, alot.

1  process (sensitivity list) begin
2
3      -- RTL code
4
5  end process;

The sensitivity list is a list of signals, that we tell the process to monitor and to act upon/ do something when said signals change. basically, execute the process when change in sensitivity signals detected.

a process can be combinational or sequential.

when writing combinational process, you must list all the input signals to logic function. leaving or forgetting signals, results in inferred latches and combinational loops. in generally, neither are desired.

 1  LIBRARY IEEE;
 2  USE IEEE.std_logic_1164.ALL;
 3  USE IEEE.numeric_std.ALL;
 4
 5  entity another_component is port (
 6      clk : in std_logic;
 7      rst : in std_logic;
 8      someout : out std_logic
 9  );
10  end another_component;
11
12  architecture rtl of fpga_top is
13      --signal declarations
14      --component declarations
15  begin
16      --combinational, you must list all the input signals
17      process (sensitivity signals) begin
18          if () then
19          else
20          end if;
21      end process;
22
23      --combinational
24      process (all) begin     --VHDL2008, to use all
25          if () then
26          else
27          end if;
28      end process;
29
30      --sequential, sync reset
31      process (clk) begin
32          if () then
33          else
34          end if;
35      end process;
36
37      --sequential - w async reset
38      process (clk, rst) begin
39          if () then
40          else
41          end if;
42      end process;
43
44  end rtl;

4.1.6. if else

very similar to what many see in ‘software’ programming. syntactically similar, but different end result. this is probably redundant, but in programming, code is executed sequentially by the processor. in FPGAs, they result result in encoders and muxes, LUTs. they represent a logic function.

 1  -- this is in a process block, with all signals listed or all in VHDL2008
 2  -- sequential version
 3  process (din1, din2, sel) begin
 4      if (sel = '1') then
 5          dout <= din1;
 6      else
 7          dout <= din2;
 8      end if;
 9  end process;
10
11  -- will result in priority encoding
12  process (all) begin                 --VHDL2008
13      if (wen) then
14          --some assignment
15      elsif (ren) then
16          --some assignment
17      else
18          --some assignment
19      end if;
20  end process;

Warning

There is no ternary if-else shorthand, seen in C and verilog

dout <= sel ? din1 : din2;

4.1.7. when else

i’ve never really used this, but have started to see it more often

1  -- concurrent version.
2  -- this doesn't have to be in a process block.
3  dout <= din1 when sel else din2;
4
5  b <= "1000" when a = "00" else
6       "0100" when a = "01" else
7       "0010" when a = "10" else
8       "0001" when a = "11";

4.1.8. with select

i’ve never really used this..

1  with a select b <=
2      "1000" when "00",
3      "0100" when "01",
4      "0010" when "10",
5      "0001" when "11";

4.1.9. case

another similar to software programming, but again end result not the same.

Generally cases is used for and/or will become.. MUX vs. FSM

i dont think you can synth a priority encoder with the VHDL case statement, per the requirement of the syntax. The case ‘sel’ variable/signal and when condition width have to match for comparison.

 1  --case used for MUX
 2  process (all) begin     --VHDL2008
 3      case sel is
 4          when "00" =>
 5              dataout <= datain1;
 6          when "01" =>
 7              dataout <= datain1;
 8          when "10" =>
 9              dataout <= datain1;
10          when "11" =>
11              dataout <= datain1;
12          when others =>
13              dataout <= 0;
14      end case;
15  end process;
16
17  -- case used in FSM
18  type state_type is (IDLE, WRITE, WR_WAIT, READ);       -- one hot 4 states = 4bits.
19  signal state : state_type;
20
21  process (clk) begin
22      if (rising_edge(clk)) then
23          if (rst) then               -- sync reset
24              state <= IDLE;>
25          else
26              case state is
27                  when IDLE =>
28
29                      -- some output
30                      busy <= '0';>
31
32                      --next state condition
33                      if (some input condition) then        -- next state depends on current state and some input condition, maybe write enable.
34                          state <= WRITE;
35                      end if;
36
37                  when WRITE =>
38
39                      -- some output
40                      data_reg <= data_reg[14 downto 1] & datain;
41                      busy <= '1';
42
43                      --next state condition
44                      if (some input condition) then     -- maybe counter value, bits written
45                          state <= WR_WAIT;
46                      end if;
47
48                  when PARITY =>
49                      -- some output
50                      parity_reg <= datain xor something;
51
52                      --next state condition
53                      if (some input condition) then      -- maybe read en.
54                          state <= READ;
55                      end if;
56
57                  when READ =>
58
59                      dataout <= mem[i];      -- depends on current state
60
61                      --next state condition
62                      if (some condition) then        -- maybe counter, bits read.
63                          state <= IDLE;
64                      end if;
65
66                  when others =>
67                      state <= IDLE;>
68
69              end case;
70          end if;
71      end if;
72  end process;

4.1.10. generics

Is used to parametrize design. Enable re-use/customization. Often seen for bit width, among other block settings.

1      component some_component is
2              generic (N : integer := 6);
3      port (
4                      clk     : in std_logic;
5                      en : in std_logic;
6                      rst     : in std_logic;
7                      datain : in std_logic_vector(13 downto 0);
8      );
9      end component some_component;

4.1.11. generate

Use/Synthesize block of code if condition is true

 1  if (some condition is true) generate
 2      --constants
 3  begin
 4      --some code, processes etc.
 5  end generate;
 6
 7  if (some condition is true) generate
 8      --constants
 9  begin
10      --some code, processes etc.
11  end generate;

4.1.12. package

for organizing and centralizing re-use constants, records, functions

 1  package some_package is
 2      --records
 3      --constants
 4      --function declarations
 5  end some_package;
 6
 7  package body some_package IS
 8      -- body
 9      --function definition
10  end some_package;

4.1.13. record

Define/create them in the packages. Can be used as a type of signal that is a collection of signals.

1  type eth_packet is
2  record
3      datain : std_logic_vector(127 downto 0);
4      keep : std_logic_vector(15 downto 0);
5      end : std_logic;
6      start : std_logic;
7      valid : std_logic;
8  end record;

4.1.14. for loop

They are not sequentially executed as in software programming. they are used to reduce, simplify your code. you have to think about it from a hardware perspective. the for loop is rolling up? the code, but when synthesized, it is ‘unrolled’/ flattened

 1  -- for instance, this is saying we have 8 data_ff
 2  -- our datain is also 8 bits wide,
 3  -- we want to connect one input bit/signal to the data_ff
 4  -- so instead of writing this 8 times,
 5  -- we write it once.
 6
 7  for i in 0 to 7 loop
 8      data_ff[i] <= datain[i];
 9  end loop;
10
11  for i in 0 to 7 loop
12      DUT[i] <= datain[i];
13  end loop;

4.1.15. arrays

used to store data, can become RAM.

 1  --1D array is your std_logic_vector
 2
 3  --2D array (N depth, by 16 SL) or 1D of 16bit SLVs
 4  type mem is array (0 to N) of std_logic_vector(15 downto 0);
 5
 6  --2D array of SLVs. N width/column, M depth/height/row
 7  --3D array because each indice is not a bit, but a vector,
 8  type mem is array (0 to N, 0 to M) of std_logic_vector(15 downto 0);
 9
10  --for instance in video/imaging
11  --1920x1080 = (height x width) or (row x column) "array or matrix", but each index holds a pixel.
12  --if it is an RGB pixel, then it is 8bit x 8bit x 8bit = 24bit per pixel = std_logic_vector(23 downto 0)
13  -- 8x8x8 = 512 color levels.

4.1.16. operators

1

4.1.17. functions

They are combinational! I never really see this in designs, more in verification/validation.

 1  function <function_name> (
 2          input parameters : type
 3          input parameters : type
 4  ) return <return_type> is
 5      --constant_or_variable_declaration
 6  begin
 7      --HDL code here
 8
 9      return <value>
10  end function;

4.1.18. Template

Putting it all together, template!

 1  LIBRARY IEEE;
 2  USE IEEE.std_logic_1164.ALL;
 3  USE IEEE.numeric_std.ALL;
 4
 5  entity is port (
 6          clk : in std_logic;
 7          rst : in std_logic;
 8          someout : out std_logic
 9  );
10  end fpga_top;
11
12  architecture rtl of fpga_top is
13      --signal declarations
14      --component declarations
15  begin
16      process (sensitivity) begin
17          if () then
18          else
19          end if;
20      end process;
21
22      process (clk) begin
23          if () then
24          else
25          end if;
26      end process;
27  end rtl;

4.2. Verilog

Later..

4.3. SystemVerilog

Later.. as I dont use enough.

4.4. HDL PT. 2

This section is to emphasize HDL on FPGAs or vendor specific (primarily Xilinx bc that is what I use at the moment). It should be revisited after reading about combinatorial and sequential circuits. I will probably discuss some of it there too, so there will be some redundancy in information depending where your entry is.

Or continue if you’re already familiar.

4.4.1. Register/FlipFlops (FF)

There is only D FF in an FPGA.. other styles FF you learn in digital logic class do not exist. If you try implementing other flavors (SR, JK, T), you’ll just use the available DFF and surrounding LUTs to realize their functionality.

Do initialize at the top. Not every register needs a reset condition. Know if register state matters when reset.. means trace how it is used and specifically when.

Code for active high.. avoid active low. ie. make clock enables high. if so and so = ‘1’ then .. blah blah.

4.4.2. Reset

Asynchronously setting or resetting registers are synthesized into preset or clear registers.

avoid async reset on BRAM (block ram) DSPs

Sequential functionality in device resources, such as block RAM components and DSP blocks, can be set or reset synchronously only.

Do not describe flip-flops with both a set and a reset. No flip-flop primitives feature both a set and a reset, whether synchronous or asynchronous. Flip-flop primitives featuring both a set and a reset can adversely affect area and performance.

Avoid operational set/reset logic whenever possible. There can be other, less expensive, ways to achieve the desired effect, such as taking advantage of the circuit global reset by defining an initial content. Always describe the clock enable, set, and reset control inputs of flip-flop primitives as active-High. If they are described as active-Low, the resulting inverter logic penalizes circuit performance.

 1  -- sometimes i write like this
 2  process(clk)
 3      if(rst) then
 4
 5  ...
 6
 7  -- sometimes i write like this
 8  process(clk)
 9      if(rst = '1') then
10
11  ...

4.4.3. Inferring and Instantiation

SRL, F7 F8 F9 mux, CL multiplier and counters using DSP BUFG IO reg (SDR) input DDR reg Select IO

4.4.4. Synthesis/Implementation

I want to focus on HDL, RTL and implementation results. Think hardware.

know the difference between if-else vs. case statement with regards to implementation. if-else becomes priority encoder. whatever is at the top of the if else becomes whatever is closest to the output. or into the register. if the first else case statement is true it is executed and the others dont matter.

if your control register is for instance 4 bits wide. and you only use one of each bit as the control signal. that means they are not mutually exclusive.

mutually exclusive means unique. each if-else or case statement is unique. so it doesn’t matter if you use if-else or case.. because you can create priority or parallel with either one. it depends how the statements are… but for good pratice.. if-else is usually used for priority encoding. case for parallel mux, where decision is mutually exclusive. mutually exclusive means only one decision or branch can be true at any given time. show example of code of everything you’re saying here. explicitly!!

while case statement is generally used for muxes, improper use can create a priority mux. if the conditions of an if-else are mutually exclusive, it will create a true mux. if it is not, it will most likely synthesize a priority encoder. basically in both case it depends how you write the conditions.

4.4.5. Using Dedicated Hardware

Like what it means to use dedicated hardware, inference(ing) vs. LUT.

You’ll want to write code such that it will utilize dedicated hardware when you can such as….

RAM: BRAM vs distributed memory.. , DSP, for adding/subtracting larger vectors, multiplying large vectors, FIR filters SRL, shift registers

Data is written synchronously into the RAM for both types. The primary difference between distributed RAM (made from LUT/FF = LUTRAM) and dedicated block RAM lies in the way data is read from the RAM. See the following table.

Action  Distributed RAM     Dedicated Block RAM
Write   Synchronous         Synchronous
Read    Asynchronous        Synchronous

Generally you will always want to take advantage of RAM, DSP, SRL, MUX? over their LUT equivalents.. better performance. they are tightly stiched already.. “dedicated hardware/circuits” their area or real estate is already in place. if you dont use it you lose it. its already there for you.

Dedicated resource/hardware consumes less power, is faster than LUTs/flip flops and their timing have already been done/taken care of.

4.4.6. Finate State Machine

Vivado synthesis supports specification of Finite State Machine (FSM) in both Moore and Mealy form. An FSM consists of the following:

A state register A next state function An outputs function

Mealy depends on current state and input. Moore depends only on current state. “More is less.”

One hot encoding - use this in most case, tool with recognize.

Gray state encoding - use this when passing value such as pointer counter across clock domains.