The examples presented in this document are all inferred on the dedicated resources of FPGA (adder, counter, RAM...). They are synthesizable VHDL codes that can be used to describe a circuit that we want to implement on FPGA or ASIC.
Processes in VHDL are used to describe the operation of a part of a circuit, in particular circuits (using a clock) or complex combinatorial circuits. In processes it is possible to use conditional structures such as the classic if/elsif/else and case.
A process has a sensitivity list, i.e. a list of input signals. The process reacts to the changes of state of the signals that are present in the sensitivity list. It is crucial to fill in this sensitivity list correctly.
On the other hand, a signal that is only updated in the process does not need to be present in the sensitivity list.
A synchronous process has, as its name indicates, an operation synchronized by the events affecting a signal in its sensitivity list. We call this signal the clock, and most often processes of this type are synchronized on the rising edge of the clock, i.e. the passage from state 0 to state 1. The process updates the signals that it controls only at the time of the rising edge, whatever the evolution of the signals used in this process. For this reason, the sensitivity list of a synchronous process is a bit special. It contains only the clock and possibly a reset in the case of an asynchronous reset. Even if signals are read or used in the synchronous part, it is not necessary to put them in the sensitivity list.
1 2 3 4 5 6 7 8 91011
process(I_Clock)-- sensitivity list in brackets, consisting of a clock and eventually a resetbeginif(rising_edge(I_Clock))thenifI_Reset='1'then-- active high synchronous reset-- what happens if the reset is activeelse-- what happens at the time of a clock rising edge without resetendif;endif;endprocess;
Is the asynchronous reset mandatory ?
The asynchronous reset is often present in VHDL synchronous process. But in some if not most FPGA designs, it is vastly optionnal. Its presence might even be a nuisance.
A really good overview of the question is presented here Get your Priorities Right — Make your Design Up to 50% Smallerhttps://www.xilinx.com/support/documentation/white_papers/wp275.pdf
The asynchronous process, being by definition combinatorial, must react immediately to the change of state of any of its input signals.
It is therefore imperative to fill in the sensitivity list exhaustively.
1 2 3 4 5 6 7 8 91011
process(S1,S2,S3,S3,S5,S6)begin-- description of the circuit using the signals in the sensitivity list. For example :if(S1='1')thenS7<=S5xorS6;-- S7 does not have to be in the sensitivity list, it is not readelsif(S2='1')thenS7<=S3;elseS7<=S4;endif;endprocess;
It is possible to describe the operation of a circuit outside an explicit process.
This is called an implicit process. Implicit processes are used when it is not necessary to use a process as such. Putting a permanent connection (assignment) between two signals in an explicit process is of no interest, it is done outside:
1 2 3 4 5 6 7 8 91011
process(...)begin...endprocess;A<=BandC;-- Implicit process hereprocess(...)begin...endprocess;
It is necessary to use an external package (preferably standardized) that defines the operators. This is the role of the numeric_std library. It will then be easy to switch from one format to another.
to_integer : Transforms a vector of type signed or unsigned to integer
to_unsigned : Transforms a signal of type integer, into a vector of type unsigned. Also allows to extend the format of an unsigned vector
to_signed : Same property as the src_vhdl[:exports code]{to_unsigned} function for signed numbers.
123456789
integer_Signal<=to_integer(unsigned_Signal);integer_Signal<=to_integer(signed_Signal);unsigned_Signal<=to_unsigned(a_natural_signal,length_of_unsigned_Signal);unsigned_Signal<=to_unsigned(an_integer_signal,length_of_unsigned_Signal);signed_Signal<=to_signed(another_natural_signal,length_of_signed_Signal);signed_Signal<=to_signed(another_integer_signal,length_of_signed_Signal);--signalB:signed(3downto0);-- 4 bits widthA<=to_unsigned(B,5);-- A is 5 bits wide
The resize function is used to expand the size of an unsigned or a signed signal. This function is signed aware and will expand the MSB to preserve the signe of the signal. This function is particularly useful when doing arithmetic to manage overflow.
123456
signalS:signed(11downto0);signalA:signed5downto0);...S<=resize(A,12);-- resize A to be on 12 bits
Here is one example of a register with synchronous init and enable. Whenever the init input is high and there is a rising edge of the clock, the value stored in the O_S signal will be set to "00000000". When the init is low, the load is high and there is a rising edge of the clock, the O_S signal will be set to the value present in I_D.
Most FPGAs include specific blocks dedicated to implement RAMs in an optimal way. These are not special LUTs, but directly RAMs that can be used if the VHDL is written correctly.
See section
Even at a relatively low level of complexity, a circuit is often divided into several main blocks. Most of the time an operative unit, containing all the arithmetic operators, processing units etc, and a control part, implemented by a state machine.
A FSM allows to :
Indicate what state the system is in
evaluate what will be the future state of the system according to the present state and the values present on the input signals
drive the control signals of the operative part and, if necessary, of the system's outputs according to the present and eventually states of the inputs.
We distinguish two types of FSM, Moore's FSM whose output signals depend only on the present state, and Mealy's machines whose output signals depend on the present and input states. The Mealy machines have the advantage of being more reactive because we can make the output signals evolve not only according to the present state, but also according to the inputs, thus on the transitions between states. Speaking of output signals, they can be synchronized or not. If they are not synchronized, the FSM will be more reactive, but will introduce a longer logic path, thus potentially a lower operating frequency. If they are, the FSM will be less reactive, but will be better in terms of achievable frequency.
Regarding the previous architecture, the most intuitive way of describing a FSM in VHDL is to use three processes :
The first one is a synchronous process used to update the present state register with the value of the futur state
The second one is a combinatorial process used to compute the futur state based on the present state and the inputs
The third one is also a combinatorial process used to compute the values of the outputs based on the present state alone in the case of a Moore FSM, or on the present state and the inputs in the case of a Mealy FSM
libraryieee;useieee.std_logic_1164.all;useieee.numeric_std.all;entityfsmisport(I_Clock:instd_logic;I_reset:instd_logic;I_1:instd_logic;I_2:instd_logic;I_3:instd_logic;I_4:instd_logic;I_5:instd_logic;O_1:outstd_logic;O_2:outstd_logic;O_3:outstd_logic;O_4:outstd_logic);endentityfsm;architecturea_fsmoffsmistypeT_Stateis(ST0,ST1,ST2,ST3,ST4,ST5,ST6,ST7);signalSR_present:T_State;signalSC_futur:T_State;beginprocess(I_Clock,I_reset)isbeginifI_reset='1'thenSR_present<=ST0;elsif(rising_edge(I_Clock))thenSR_present<=SC_futur;endif;endprocess;process(SR_present,I_1,I_2,I_3,I_4,I_5)begincaseSR_presentiswhenST0=>SC_futur<=ST1;whenST1=>if(I_1='1')then--Cond1SC_futur<=ST2;elseSC_futur<=ST1;endif;whenST2=>if(I_2='1'andI_4='0')then--Cond2SC_futur<=ST3;elseSC_futur<=ST2;endif;whenST3=>if(I_3='1')then--Cond3SC_futur<=ST4;elsif(I_2='1')then--Cond4SC_futur<=ST6;elseSC_futur<=ST3;endif;whenST4=>if(I_1='0'andI_2='1')then--Cond5SC_futur<=ST5;elsif(I_5='1')thenSC_futur<=ST7;elseSC_futur<=ST4;endif;whenST5=>if(I_5='1')then--Cond8SC_futur<=ST3;elseSC_futur<=ST5;endif;whenST6=>if(I_4='1'andnot(I_2='1'xorI_3='0'))then--Cond7SC_futur<=ST1;elseSC_futur<=ST6;endif;whenST7=>SC_futur<=ST7;whenothers=>SC_futur<=ST0;endcase;endprocess;process(SR_present,I_1,I_2,I_3,I_4,I_5)begin-- default output valuesO_1<='0';O_2<='0';O_3<='0';O_4<='0';caseSR_presentiswhenST2=>O_1<='1';--moore output (depending on state only)if(I_2='1'andI_4='0')then--Cond2O_2<='1';--mealy output (depending on state and input)elseO_2<='0';--mealy output (depending on state and input)endif;whenST3=>O_1<='1';O_3<='1';whenST4=>O_2<='1';O_3<='1';if(I_1='0'andI_2='1')then--Cond5O_4<='1';elseO_4<='0';endif;whenST5=>O_1<='1';whenST6=>O_1<='1';if(I_4='1'andnot(I_2='1'xorI_3='0'))then--Cond7O_2<='1';elseO_2<='0';endif;whenST7=>SR_present<=ST7;whenothers=>SR_present<=ST0;endcase;endprocess;enda_fsm;
The last process, managing the outputs, can be split in several explicit or implicit processes for more convenience.
libraryieee;useieee.std_logic_1164.all;useieee.numeric_std.all;entityfsmisport(I_Clock:instd_logic;I_reset:instd_logic;I_1:instd_logic;I_2:instd_logic;I_3:instd_logic;I_4:instd_logic;I_5:instd_logic;O_1:outstd_logic;O_2:outstd_logic;O_3:outstd_logic;O_4:outstd_logic);endentityfsm;architecturea_fsmoffsmistypeT_Stateis(ST0,ST1,ST2,ST3,ST4,ST5,ST6,ST7);signalSR_present:T_State;beginprocess(I_Clock,I_reset)isbeginifI_reset='1'thenO_1<='0';O_2<='0';O_3<='0';O_4<='0';SR_present<=ST0;elsif(rising_edge(I_Clock))thencaseSR_presentiswhenST0=>SR_present<=ST1;whenST1=>if(I_1='1')then--Cond1SR_present<=ST2;elseSR_present<=ST1;endif;whenST2=>O_1<='1';--moore output (depending on state only)if(I_2='1'andI_4='0')then--Cond2SR_present<=ST3;O_2<='1';--mealy output (depending on state and input)elseSR_present<=ST2;O_2<='0';--mealy output (depending on state and input)endif;whenST3=>O_1<='1';O_3<='1';if(I_3='1')then--Cond3SR_present<=ST4;elsif(I_2='1')then--Cond4SR_present<=ST6;elseSR_present<=ST3;endif;whenST4=>O_2<='1';O_3<='1';if(I_1='0'andI_2='1')then--Cond5SR_present<=ST5;O_4<='1';elsif(I_5='1')thenSR_present<=ST7;O_4<='0';elseSR_present<=ST4;O_4<='0';endif;whenST5=>O_1<='1';if(I_5='1')then--Cond8SR_present<=ST3;elseSR_present<=ST5;endif;whenST6=>O_1<='1';if(I_4='1'andnot(I_2='1'xorI_3='0'))then--Cond7SR_present<=ST1;O_2<='1';elseSR_present<=ST6;O_2<='0';endif;whenST7=>SR_present<=ST7;O_1<='1';O_2<='1';O_3<='1';whenothers=>SR_present<=ST0;endcase;endif;endprocess;endarchitecturea_fsm;
To create a component, it is often very interesting to split it into sub-components. Once the subcomponents have been created and tested, they must be assembled. The use of a sub-component in the current component is called an instantiation, we can use one or multiple instances of a sub-component. Once the sub-components are instantiated, they must be connected to the inputs/outputs/signals of the current component. In this case we speak of structural description.
libraryieee;useieee.std_logic_1164.all;useieee.numeric_std.all;entityComp1isgeneric(G_SizeInput:natural;G_SizeS1:natural;G_SizeS2:natural;G_SizeS3:natural);port(I_Clock:instd_logic;I_E1:instd_logic_vector(G_SizeInput-1downto0);I_E2:instd_logic_vector(G_SizeInput-1downto0);I_Control:instd_logic;O_S1:outstd_logic_vector(G_SizeS1-1downto0);O_S2:outstd_logic_vector(G_SizeS2-1downto0);O_S3:outstd_logic_vector(G_SizeS3-1downto0);O_S4:outstd_logic_vector(G_SizeS3-1downto0);O_S5:outstd_logic_vector(G_SizeS1-1downto0);O_S6:outstd_logic_vector(G_SizeS2-1downto0));endentityComp1;architecturearchi_Comp1ofComp1iscomponentCompAisgeneric(G_Data:natural;G_Out:natural);port(I_Clock:instd_logic;I_Data1:instd_logic_vector(G_Data-1downto0);I_Data2:instd_logic_vector(G_Data-1downto0);I_Control1:instd_logic;I_Control2:instd_logic;O_Control:outstd_logic;O_Data1:outstd_logic_vector(G_Out-1downto0);O_Data2:outstd_logic_vector(G_Out-1downto0));endcomponent;componentCompWisgeneric(G_X1:natural;G_X3:natural;G_Y1:natural;G_Y2:natural);port(I_X1:instd_logic_vector(G_X1-1downto0);I_X2:instd_logic;I_X3:instd_logic_vector(G_X3-1downto0);O_Z1:outstd_logic;O_Y1:outstd_logic_vector(G_Y1-1downto0);O_Y2:outstd_logic_vector(G_Y2downto0));endcomponent;signalSC_Z1_1:std_logic;signalSC_Z1_2:std_logic;signalSC_Data1:std_logic_vector(G_SizeS3-1downto0);signalSC_Data2:std_logic_vector(G_SizeS3-1downto0);signalSC_X1:std_logic;beginA:CompAgenericmap(G_Data=>G_SizeInput,G_Out=>G_SizeS3)portmap(I_Clock=>I_Clock,I_Data1=>I_E1,I_Data2=>I_E2,I_Control1=>SC_Z1_1,I_Control2=>SC_Z1_2,O_Control=>SC_X1,O_Data1=>SC_Data1,O_Data2=>SC_Data2);W1:CompWgenericmap(G_X1=>G_SizeInput,G_X3=>G_SizeS3,G_Y1=>G_SizeS1,G_Y2=>G_SizeS2)portmap(I_X1=>I_E1,I_X2=>SC_X1,I_X3=>SC_Data1,O_Z1=>SC_Z1_1,O_Y1=>O_S1,O_Y2=>O_S2);W2:CompWgenericmap(G_X1=>G_SizeInput,G_X3=>G_SizeS3,G_Y1=>G_SizeS1,G_Y2=>G_SizeS2)portmap(I_X1=>I_E1,I_X2=>SC_X1,I_X3=>SC_Data2,O_Z1=>SC_Z1_2,O_Y1=>O_S5,O_Y2=>O_S6);-- Il est possible d'ajouter des process autour de ces instanciations-- pour manipuler des signaux internes si besoin--process(...)--begin-- .-- .-- .--end process;endarchitecture;
The design for FPGA is based on the same principles as for ASIC. However, because of the difference between these two types of circuits, optimizations are possible in FPGA design. This is the case for the use of an asynchronous reset on the flip-flops. Where it is almost mandatory on an ASIC, it is suboptional in FPGA, and often we can do without it from a functional point of view. This allows to limit the use of resources (LUT) and thus to improve the performances (resources used, consumption, maximum achievable frequency...).
When configuring an FPGA with a bitstream, all its elements can be initialized. So the interest of an asynchronous global reset, whose main goal on an ASIC is to start the design in a known state by initializing all the memory points, is limited on FPGA.