VHDL in digital circuit synthesis (tutorial) dr inż. Miron Kłosowski EA 309 klosowsk@ue.eti.pg.gda.pl Library declaration library IEEE; use IEEE.std_logic_1164.all; Obligatory IEEE library all - use all elements of the package std_logic_1164 package usage Other IEEE packages: IEEE.std_logic_signed.all For example std_logic_vector adder IEEE.std_logic_unsigned.all IEEE.std_logic_arith.all For example: type conversion: integer to std_logic_vector std.text_io.all For text file support (for simulation). IEEE.numeric_std.all; For example: function std_match for vector compare. Package included by default: std.standard.all contains definitions of basic types like: boolean, bit, character, string, integer, real, natural, positive, bit_vector. Sample design library IEEE; use IEEE.std_logic_1164.all; entity multiplexer is port ( signal s : in std_logic; signal x0,x1 : in std_logic_vector(7 downto 0); signal y : out std_logic_vector(7 downto 0) ); end entity multiplexer; architecture data_flow of multiplexer is begin y <= x1 when ( s = '1' ) else x0; end architecture data_flow; Design simulation – testbench (1) Sample TESTBENCH (template is usually generated automatically): LIBRARY ieee; USE ieee.std_logic_1164.ALL; USE ieee.std_logic_unsigned.all; USE ieee.numeric_std.ALL; Testbench generates and tests all signals therefore port is not used. ENTITY fpgalab1_tb_vhd IS END fpgalab1_tb_vhd; ARCHITECTURE behavior OF fpgalab1_tb_vhd IS -- Component Declaration for the Unit Under Test COMPONENT projekt1 PORT( clk : IN std_logic; reset : IN std_logic; sw0 : IN std_logic; sw1 : IN std_logic; sw2 : IN std_logic; sw3 : IN std_logic; an : OUT std_logic_vector(3 downto 0); seg : OUT std_logic_vector(7 downto 0); ld0 : OUT std_logic; ld1 : OUT std_logic ); END COMPONENT; (UUT) Simulated Circuit (UUT) is connected using component declaration mechanism (described in details later). Here declaration of names, types and directions of tested signals is present (signals which would be visible at the pins of synthesized physical FPGA circuit). Design simulation – testbench (2) SIGNAL SIGNAL SIGNAL SIGNAL SIGNAL SIGNAL SIGNAL SIGNAL sw0 sw1 tst trx an seg ld0 ld1 : : : : : : : : std_logic := '0'; std_logic := '0'; std_logic := '1'; std_logic := '1'; std_logic_vector(3 downto 0); std_logic_vector(7 downto 0); std_logic; std_logic; signal clk : std_logic := '0'; signal clk_p : std_logic := '0'; signal clk_n : std_logic := '1'; constant PERIOD : time := 10 ns; constant DUTY_CYCLE : real := 0.25; signal reset : std_logic := '1'; BEGIN -- Instantiate the Unit Under Test (UUT) uut: projekt1 PORT MAP( clk => clk, reset => reset, sw0 => sw0, sw1 => sw1, sw2 => tst, sw3 => trx, an => an, seg => seg, ld0 => ld0, ld1 => ld1 ); Definitions of signals used for component testing – signals are connected to the inputs of UUT and should be initialized. Definitions of additional signals and constants needed for testing. Initialization of those signals is usually recommended. Real connection to UUT component is created here, PORT MAP describe connections between component signals and testbench signals. Design simulation – testbench (3) -- clk_simple <= not clk_simple after PERIOD/2; clk_p <= not clk_p after PERIOD/2; clk_n <= not clk_n after PERIOD/2; Simple clock generator. Differential clock generator. clk <= '1' after (PERIOD - (PERIOD * DUTY_CYCLE)) when clk = '0' else '0' after (PERIOD * DUTY_CYCLE); Arbitrary duty cycle clock generator. Reset signal reset <= '0' after 5 ns; deassertion. tb : PROCESS BEGIN -- Wait 10 ns for global reset to finish wait for 10 ns; -- Place stimulus here sw0 <= '1'; sw1 <= '0'; Delay. wait for 20 ns; sw0 <= '0'; wait for 10 ns; sw1 <= '1'; wait; -- will wait forever END PROCESS; Process – instructions inside the process are executed sequentailly. Signal values are sequentially modified. wait instruction without arguments waits forever (without it the process would start again). Design simulation – testbench (4) tb1 : PROCESS Infinite loop instruction variable x : integer; BEGIN wait for 10 ns; loop x := 1; while x < 11 loop Conditional loop instruction tst <= not tst; wait for x*2 ns; Assert instruction – generates warning x := x + 1; and error messages and stops a assert x /= 11 simulation report „Last iteration finished" severity NOTE; end loop; wait until rising_edge(clk); ‘wait until’ instruction waits for a logic end loop; condition to be satisfied (in this ‘wait on’ instruction END PROCESS; example it waits for the rising edge of waits for a signal the clk signal) change tb2 : PROCESS BEGIN wait on clk_p; This assign instruction trx <= transport clk_p after (PERIOD/2)+3 ns; registers future change of the END PROCESS; signal trx. This is an example of a transport delay. END; Design simulation – testbench (5) Signal assignment (concurrent) signal_1 <= signal_2; signal_2 <= signal_3; signal_2 <= signal_3; signal_1 <= signal_2; Sequence of assignments is irrelevant (architecture body is an concurrent instruction area). Is this a problem ? Signal_1 <= Signal_2; Signal_1 <= Signal_3; It can be solved using the resolution function ! Signal_2 Signal_1 Signal_3 Important !!! Resolution function is defined for type std_logic and std_logic_vector. Type std_ulogic is a logic type without resolution function. Using std_logic and std_logic_vector types is recommended. Resolution function Resolution function for std_logic: U X 0 1 Z W L H --------------------------------U U U U U U U U U | U | U X X X X X X X X | X | U X 0 X 0 0 0 0 X | 0 | U X X 1 1 1 1 1 X | 1 | U X 0 1 Z W L H X | Z | U X 0 1 W W W W X | W | U X 0 1 L W L W X | L | U X 0 1 H W W H X | H | U X X X X X X X X | - | • Std_logic is a most frequently used logic type. It can be assigned 0 i 1 values and more: Z high impedance state (for top level signals only); - don't care (for example used by function std_match); Other values are useful in simulation: U uninitialized (at the beginning of simulation); X undefined strong signal; W undefined weak signal; L, H week 0 and 1 signals. Vectors (1) • Sample vector (one-dimensional array): signal x: std_logic_vector(7 downto 0); x <= "11001010"; x(7) <= '1'; x(6) <= '1'; x(5) <= '0'; x(4) <= '0'; x(3) <= '1'; x(2) <= '0'; x(1) <= '1'; x(0) <= '0'; • Vector with rising index: signal y: std_logic_vector(0 to 7); y <= "11001010"; y <= x; Previous assignment automatically perform 8 one-bit assignments: y(0) <= x(7); y(1) <= x(6); y(2) <= x(5); y(3) <= x(4); y(4) <= x(3); y(5) <= x(2); y(6) <= x(1); y(7) <= x(0); • Vector assignment is possible when the number and type of vector’s elements is the same in RHS and LHS. Vectors (2) • Because many library functions use vectors with MSB on the left side it is recommended to use downto indexing (index 0 is a LSB) signal q: std_logic_vector(1 downto 0); • You can address smaller subvectors using parenthesis: x(7 downto 6) <= q; x(5 downto 3) <= y(2 to 4); • You cannot change index direction of the vector, assignment: x(5 downto 3) <= y(4 downto 2); is not correct!!! • You can create vectors from logic signals or smaller vectors using the concatenation operator &: signal s: std_logic; x <= "10" & q & '1' & q & '0'; vec_from_sig <= s & s & s & s & s & s & s & s; Vectors (3) • You can create vectors using aggregates: x <= ('1', '0', q(1), q(0), '1', q(1), q(0), others => '0'); x <= (6=>'0', 7=>'1', 4=>q(0), 5=>q(1), 0=>'0', 2=>q(1), 1=>q(0), others => '1'); Word ‘others’ can be zero_vector <= ( others => '0'); used only at the end vec_from_sig <= ( others => s ); of the aggregate. vec_from_sig1 <= ( s1, s2, s3, s4, others => s ); vec_from_sig2 <= ( 0=>'1', 6 downto 2 => s2, 7|1 => s1 ); Logical operations on vectors: signal temp: std_logic_vector(7 downto 0); temp <= ( others => s ); y <= ( temp and x1 ) or ( not temp and x0 ); • You can perform following logic operations: not, and, or, nand, nor, xor, xnor. • Logic operation is performed on bits taken from the same position of operand vectors. Result is placed in the same position in the LHS vector. • Vectors used for the operation must have the same length. Sample design (2) library IEEE; use IEEE.std_logic_1164.all; entity multiplexer is port ( signal s : in std_logic; signal x0,x1 : in std_logic_vector(7 downto 0); signal y : out std_logic_vector(7 downto 0) ); end entity multiplexer; architecture logic of multiplexer is signal temp : std_logic_vector(7 downto 0); begin temp <= ( others => s ); y <= ( temp and x1 ) or ( not temp and x0 ); end architecture logic; Arithmetic operations on vectors • You can perform arithmetic operations not only on integer types. Std_logic_vector type can be also used for this task but only when one the following packages are used: use IEEE.std_logic_unsigned.all; use IEEE.std_logic_signed.all; • If in the same design entity signed and unsigned vectors are present you can use the signed and unsigned vector types defined in package IEEE.std_logic_arith • You can also used the IEEE.std_logic_signed.all package for all vectors but prefix the unsigned vectors with ‘0’ & to make them non negative. • Carry in and out synthesis (in addition and subtraction output vector length is equal to the longest input vector): signal res, x, y: std_logic_vector(7 downto 0); signal a: std_logic_vector(8 downto 0); signal cin, cout : std_logic; a <= ('0' & x) + ('0' & y) + cin; res <= a(7 downto 0); cout <= a(8); Type conversion (examples) Frequently used type conversion functions: • Packages IEEE.std_logic_unsigned and IEEE.std_logic_signed contain conversion function from type Std_logic_vector to type Integer: CONV_INTEGER(ARG: STD_LOGIC_VECTOR) • Package IEEE.std_logic_arith contains following conversion functions: - Integer to Std_logic_vector (you have to specify the size of the vector): CONV_STD_LOGIC_VECTOR(ARG: INTEGER; SIZE: INTEGER) - Integer to Unsigned/Signed: CONV_UNSIGNED(ARG: INTEGER; SIZE: INTEGER) CONV_SIGNED(ARG: INTEGER; SIZE: INTEGER) - Unsigned/Signed to Integer: CONV_INTEGER(ARG: UNSIGNED) CONV_INTEGER(ARG: SIGNED) - Std_logic_vector extension to length size: EXT(ARG: STD_LOGIC_VECTOR; SIZE: INTEGER) - Std_logic_vector signed extension to length size: SXT(ARG: STD_LOGIC_VECTOR; SIZE: INTEGER) • Package IEEE.std_logic_1164 contains conversion functions for Bit_Vector: TO_BITVECTOR(S: STD_LOGIC_VECTOR) TO_STDLOGICVECTOR(B: BIT_VECTOR) Processes Processes are used for behavioral description of the hardware. Instructions inside the process are executed sequentially to calculate the values of signals assigned in the process. architecture behavioral of multiplexer is begin Sensitivity list Optional label mux: process (x0, x1, s) is begin if s = '1' then y <= x1; else y <= x0; end if; end process mux; end architecture behavioral; Combinatorial processes (1) • In processes describing combinatorial logical circuits the body of the process (the algorithm) should be started only at the change of the signals present on the sensitivity list. • If the process has to describe the behavior of combinatorial circuit (i.e. Logic circuit without the latches or flip-flops) two conditions must be satisfied: 1. All the input signals of the logic circuit must be present on the sensitivity list of the process. 2. All the output signals of the logic circuit must be assigned a value at least once in every possible flow of the algorithm used in the body of the process. Good process example: Bad process example: mux: process (x0, x1, s) is mux: process (s) is Begin begin y <= x0; if s = '1' then if s = '1' then y <= x1; y <= x1; end if; end if; end process mux; end process mux; Combinatorial processes (2) • When there is a possibility that output signal is not assigned during the process run as the result of the synthesis a latch will be inferred – the process is no longer combinatorial. • You can use for example ‘else’ construct to avoid this. • Another method is to use default value for all output signals (see below): mux: process (x0, x1, s) is begin y <= x0; if s = '1' then y <= x1; end if; end process mux; Signal assignment in the process is not performed at the same time when the <= instruction is found. This assignment is rather programmed to be performed later – when the process finishes the algorithm and stops waiting for signals change. Therefore if more than one assignment to the signal is found during the process run only the last assignment is valid. All the programmed assignments are finalized at the same time when the process stops. Read value of the signal cannot be changed during the process run – when the signal is read it always returns the same value (even when the new value has been asserted). Combinatorial processes (3) • It is possible that output signal of the process is used at the same time as input signal of the same process. It is of course not allowed to create the combinatorial loop in any way – but you can create the chain logic. • In this situation remember that output signals which are used as an input also should satisfy both conditions from the previous page (sensitivity list and obligatory assignment): • Sometimes process is started process(a, b, c, x, y) is several times because the input signal begin has been changed during process x <= a and b; execution (delta cycles). In this y <= x or c; situation last assignment to the output z <= y xor a; signal along all the delta cycles is valid. end process; Delta cycles for process execution: t=0 ns (a=0, b=1, c=0, x=0, y=0, z=0) t=1 ns (a=1, b=1, c=0, x=0, y=0, z=0) t=1 ns + 1d (a=1, b=1, c=0, x=1, y=0, z=1) t=1 ns + 2d (a=1, b=1, c=0, x=1, y=1, z=1) t=1 ns + 3d (a=1, b=1, c=0, x=1, y=1, z=0) • Interesting property: different signals can be assigned in any sequence in the process. • Remember that you cannot assign the same signal in different processes without activating the resolution function (which is usually prohibited during synthesis). Variables (1) • Signals are used to communicate between the process and the rest of the system (concurrent). But to create algorithms inside the process you will need variables. • Variables are defined at the beginning of the process (just before ‘begin’) and they are visible within the process only. • Variables preserve its value between process runs (they can model the memory). • Variables change at the same time when the variable assignment is found (they behave like you would expect in a software programming language). process(a, b, c) is variable x,y:std_logic_vector(7 downto 0); begin x := a and b; y := x or c; z <= y xor a; end process; Delta cycles for process execution: t=0 ns (a=0, b=1, c=0, x=0, y=0, z=0) t=1 ns (a=1, b=1, c=0, x=0, y=0, z=0) t=1 ns + 1d (a=1, b=1, c=0, x=1, y=1, z=0) Contrary to signals, different variables must be assigned in the proper sequence – like in a software programming language. When you exchange the first line with the second the result will be different: (x=1, y=0, z=1) Variables (2) • You can assign the initial value to the variable: variable x: std_logic_vector(7 downto 0) := "00100010"; variable i: integer range 0 to 7 := 4; • The initial value is assigned to the variable only at the beginning of the synthesis or simulation. It means that if you want to assign the initial value every time the process is started you have to do it in the process body just after the ‘begin’ word. • Be careful – when you use the variable’s value before you assign the initial value to it you read the value from the previous process execution. This creates a memory element (usually latch). Sequential processes (1) • Processes can be used to describe sequential circuits if they use signals or variables as a memory. • signal memory – if signal assertion is not continuous but depends on input signals’ states (for example if you forgot about default value assertion like in the example below). • variable memory – if variable is not initialized at the beginning of the process and contents of the variable from previous process run is used. • Sequential processes use memory elements available in the target FPGA (FF, latches, etc.) – you cannot build them using gates. It is important to use strict design rules to achieve synthesizable sequential processes. Example of sequential process (q is not asserted continuously): latch: process (ena, d) is begin D-latch. When the ena signal is active if ena = '1' then q output is connected to the d input, q <= d; when ena is inactivated q output is end if; frozen. end process latch; Sequential processes (2) • Latches are not used frequently, but they are available in most FPGAs. • Usually latch synthesis is caused by an error (for example: not asserting default value to the output signal in combinatorial process). Synthesizer software usually warns about latches: „Warning: latch inferred ... ” • Classic method to build sequential circuits is to use edge-triggered flipflops. • Recall all the properties of the synchronous digital circuits, we will use this design paradigm in the VHDL and FPGAs. Edge triggered D-type flip-flop: dff: process (clk) is begin if clk = '1' and clk'event then q <= d; end if; end process dff; '0' for falling edge D-type flip-flop (rising edge triggered). If clk logic value changes, value of the attribute ‘event of the clk signal is set to the ‘1’ for the moment. If clk changes to ‘1’ (i.e. rising edge occurred) the signal value from the d input is transferred to the q output, and q is frozen till the next clk edge. Sequential processes (3) ’event attribute detects every change of the signal, it is suggested to use rising_edge() or falling_edge() functions which detect only changes from ‘0’ to ‘1’ or ‘1’ to ‘0’ respectively. Those functions do not detect changes between other std_logic type’s values like ‘U’,’X’,’Z’, etc. dff: process (clk) is begin if rising_edge(clk) then q <= d; end if; end process dff; dff: process (clk) is begin if falling_edge(clk) then q <= d; end if; end process dff; Synchronous circuit clocked with rising / falling edge of the clock: circuit_name: process (clock) is begin if rising/falling_edge(clock) then .. –- Sequential description of .. –- the digital synchronous circuit, .. –- all signals assigned here are .. -- synthesized as D-type flip-flops’ .. -- outputs. .. –- All logic expressions, conditionals, .. -- etc. are synthesized as combinatorial .. -- logic driving D inputs of flip-flops. end if; end process circuit_name; Sequential processes (4) • Usually flip-flops in FPGAs are designed with asynchronous reset and set inputs. Synchronous circuit clocked with rising / falling edge of the clock drff: process (clk, rst) is begin with asynchronous reset input: if rst = '1' then q <= '0'; elsif rising_edge(clk) then q <= d; end if; end process drff; dsff: process (clk, set) is begin if set = '1' then q <= '1'; if rising_edge(clk) then q <= d; end if; end process dsff; circuit_name: process (clock, reset) is begin if reset = '1' then -- or '0' sig1 <= "00110"; sig2 <= const; .. –- Values asserted to flip-flops .. –- when reset is active. .. –- Must be constant because .. –- asynchronous load of the signal .. –- is not usually supported. elsif rising/falling_edge(clock) then .. -- Sequential description of .. –- the digital synchronous circuit: sig1 <= ... sig2 <= ... end if; end process circuit_name; Sequential processes (5) • Global reset of entire system should be performed with disabled clock. When clock is running some flip-flops can miss a clock when the reset signal deassertion is delayed because of signal propagation. • Using the hardware reset input of the FPGA (PROG input in Xilinx) is recommended (use initial values of the signals). • Never drive asynchronous set/reset from combinatorial logic – it can contain hazards and therefore spikes are possible – use direct output of the flip-flops or use synchronous reset. • Avoid suggesting clock gating in process description (it is usually not supported), rather use synchronous clock enable: -- clock gating process !!! dceff: process (clk, rst) is begin if rst = '1' then q <= '0'; elsif clk_ena = '1' then if rising_edge(clk) then q <= d; end if; end if; end process dceff; Synchronous reset and clock enable: circuit_name: process (clock) is begin if rising/falling_edge(clock) then if sync_reset=‘1’ then q <= ‘0’; elsif clock_enable=‘1’ then q <= ..... end if; end if; end process circuit_name; Initial values of signals • You can assign initial value to the signal: signal x: std_logic_vector(7 downto 0) := "00100010"; signal i: integer range 0 to 7 := 4; • When the signal with initial value is used as the output of the sequential process (i.e. it represents the flip-flop) the initial value will be assigned to the flip-flop during the configuration process of the FPGA. • Most FPGAs have got the flip-flop initialization during configuration functionality. • When the initial value is not defined the flip-flop will be set to ‘0’ (Xilinx FPGAs’ property). Conditional instruction (1) In processes conditional instruction execution is performed using following reserved words: if, then, elsif, else, end if. See example below: -- signal s:std_logic_vector(2 downto 0); -- signal y:std_logic_vector(1 downto 0); priority_encoder: process (s) is begin if s(2) = '1' then y <= "11"; elsif s(1) = '1' then y <= "10"; elsif s(0) = '1' then y <= "01"; else y <= "00"; end if; end process priority_encoder; Case instruction (1) Expression after the ‘case’ word is calculated and then instructions assigned to the calculated value are executed. All possible expression values must be enumerated. If it is impossible or difficult use word ‘others’ (especially needed for std_logic_vector type). -- signal s:std_logic_vector(2 downto 0); -- signal y:std_logic_vector(1 downto 0); priority_encoder: process (s) is begin case s is when "111" | "110" | "101" | "100" => y <= "11"; when "011" | "010" => y <= "10"; when "001" => y <= "01"; when others => y <= "00"; end case; end process priority_encoder; Case instruction (2) For scalar and enumeration types it is allowed to use ranges (to, downto). It is allowed to use many instructions in one choice but at least one instruction must be used (it may be null instruction). After ‘when’ only constants or constant expressions can be used. -- signal s:std_logic_vector(2 downto 0); -- signal y:std_logic_vector(1 downto 0); -- constant jeden:integer := 1; priority_encoder: process (s) is begin y <= "10"; case CONV_INTEGER(s) is when 7 downto 4 => y <= "11"; if s = 5 then z <= 123; end if; when 3 downto jeden + 1 => null; when jeden => y <= "01"; when others => y <= "00"; end case; end process priority_encoder; Null instruction Loop instruction (1) • Loop instruction is used for easy synthesis of many similar elements. • Synthesizable loop must have limited number of iterations. • While loop is executed when condition after ‘while’ word is true. • While and For loops can be broken using ‘exit’ instruction. priority_encoder: process (s) is variable i:integer; begin i := 2; y <= "00"; L1: while i >= 0 loop if s(i) = '1' then y <= CONV_STD_LOGIC_VECTOR(i+1, 2); exit L1; end if; i := i - 1; end loop L1; end process priority_encoder; Loop instruction (2) • It is not obligatory to define the iteration variable in the For loop. • Synthesizable loop must have limited number of iterations. • You can use 'range attribute to define the loop iteration range. bit_counter: process (s) is variable num_bits:integer range 0 to s'length; begin num_bits := 0; L1: for i in s'range loop if s(i) = '1' then num_bits := num_bits + 1; end if; end loop L1; q <= num_bits; end process bit_counter; Loop instruction (3) • Use the ‘next’ instruction to brake single iteration without exiting the loop – just next iteration is started. • Synthesizable loop must have limited number of iterations. -- signal s:std_logic; -- signal crc_in, crc_out:std_logic_vector(31 downto 0); crc32_logic: process (s, crc_in) is constant coef:std_logic_vector(31 downto 0) := "0000_0100_1100_0001_0001_1101_1011_0111"; variable tmp:std_logic; begin c(x) = 1 + x + x2 + x4 + x5 + x7 + x8 + x10 + x11 + x12 for i in 0 to 31 loop + x16 + x22 + x23 + x26 + x32 if i = 0 then tmp := crc_in(31); else tmp := crc_in(i-1); end if; crc_out(i) <= tmp; if coef(i) = '0' then next; end if; crc_out(i) <= tmp xor s; end loop; end process crc32_logic; Shift registers • Using sll, srl, sla, sra, rol, ror operators for shift registers synthesis is not recommended (those operators work for bit_vector type only). • Below a recommended method of shift register synthesis is presented (16-bit right and left shift registers with serial input/output and parallel load). Rotating registers can be synthesized in the same way. shift_regs: process(clk, rst) begin if rst = '1' then reg_l <= (others => '0'); reg_r <= (others => '0'); elsif rising_edge(clk) then if load = '1' then reg_l <= data_l; reg_r <= data_r; else serial_data_out_l <= reg_l(15); reg_l(15 downto 1) <= reg_l(14 downto 0); reg_l(0) <= serial_data_in_l; serial_data_out_r <= reg_r(0); reg_r(14 downto 0) <= reg_r(15 downto 1); reg_r(15) <= serial_data_in_r; end if; end if; end process; Clocks in FPGA Clocks in FPGAs have to be designed carefully: 1. 2. 3. 4. 5. 6. Use one clock in the design if possible. Use external clock or generate it using single divider. When using divider remember to generate clock directly from FF output not from combinatorial logic! If you need more clock domains use Digital Clock Managers to generate needed clocks and keep them synchronous. Remember that clock resources are limited. If you need clock domains from independent clock sources remember to carefully design the data transfer between those clock domains (use FF synchronizers, asynchronous FIFO, two-port memories etc.). State machines (1) Connection for Mealy state machine Input Output Output function Transition function State registers CLK RESET • Moore and Mealy state machines can be easily implemented in VHDL. • Blue blocks are implemented using sequential synchronous processes. • Orange blocks are implemented using combinatorial processes. • Two orange blocks can be implemented in one combinatorial process. State machines (2) • To synthesize state variable enumerated type is usually used: type StateType is (idle, operate, finish); signal present_state, next_state : StateType; • It is possible to change the default state representation: - globally using synthesizer options, - locally using attributes of state variable (Xilinx example): attribute fsm_encoding: string; attribute fsm_encoding of present_state: signal is "compact"; -- "{auto|one-hot|compact|gray|sequential|johnson|user}" • It is possible to use user defined state representation (for fsm_encoding = user): type statetype is (ST0, ST1, ST2, ST3); attribute enum_encoding of statetype : type is "001 010 100 111"; signal state1 : statetype; signal state2 : statetype; State machines (3) -- Moore state machine -- example type StateType is (idle, operate, finish); signal present_state : StateType; signal next_state : StateType; seq: process (reset, clk) is begin if (reset = '1') then present_state <= idle; elsif rising_edge(clk) then present_state <= next_state; end if; end process seq; comb: process (present_state, go, brk, cln, rdy) is begin case present_state is when idle => if (go = '1') then next_state <= operate; else next_state <= idle; end if; power <= '0'; direction <= '0'; when operate => if (brk = '1') then next_state <= idle; elsif (cln = '1') then next_state <= finish; else next_state <= operate; end if; power <= '1'; direction <= '0'; when finish => if (rdy = '1') then next_state <= idle; else next_state <= finish; end if; power <= '1'; direction <= '1'; end case; end process comb; State machines (4) -- Mealy state machine -- example type StateType is (idle, operate, finish); signal present_state : StateType; signal next_state : StateType; seq: process (reset, clk) is begin if (reset = '1') then present_state <= idle; elsif rising_edge(clk) then present_state <= next_state; end if; end process seq; comb: process (present_state, go, brk, cln, rdy, emergency_off) is begin -- default value to avoid latches: next_state <= present_state; case present_state is when idle => if (go = '1') then next_state <= operate; end if; power <= '0'; direction <= '0'; when operate => if (brk = '1') then next_state <= idle; elsif (cln = '1') then next_state <= finish; end if; power <= not (emergency_off or brk); direction <= '0'; when finish => if (rdy = '1') then next_state <= idle; end if; power <= not emergency_off; direction <= '1'; end case; end process comb; State machines (5) input Output function Output registers Transition function State registers CLK output RESET synchronous output Mealy state machine • Blue blocks are implemented using sequential synchronous processes. • Orange blocks are implemented using combinatorial processes. • It is possible to synthesize synchronous output Mealy machine in one sequential process because machine’s output is taken directly from the FF outputs. State machines (6) -- synchronous output Mealy -- state machine example -- (coded in single process) type StateType is (idle, operate, finish); signal fsm_state : StateType; sync_mealy: process (reset, clk) is begin if reset = '1' then fsm_state <= idle; power <= '0'; direction <= '0'; elsif rising_edge(clk) then case fsm_state is when idle => if (go = '1') then fsm_state <= operate; power <= '1'; end if; when operate => if (brk = '1') then fsm_state <= idle; power <= '0'; elsif (cln = '1') then fsm_state <= finish; direction <= '1'; end if; when finish => if (rdy = '1') then fsm_state <= idle; power <= '0'; direction <= '0'; end if; end case; end process sync_mealy; State machines (7) Forbidden states • Usually state machines are not synthesized to automatically get out of forbidden states. For mission-critical systems it is possible to enable special option in synthesizer to enable safe machine synthesis (but usually that means much larger transition logic and slower clock). • Other methods for safe state machine synthesis: 1. Use direct state encoding (use user defined states or use std_logic_vector for state definition) and specify behavior of the machine for all possible (valid and invalid) states. 2. Add logic for detection of invalid states which triggers reset. State machines (8) -- Example of direct state encoding: signal present_state, next_state : std_logic_vector(2 downto 0); constant idle : std_logic_vector(2 downto 0) := "000"; constant prepare : std_logic_vector(2 downto 0) := "001"; constant operate : std_logic_vector(2 downto 0) := "010"; constant finish : std_logic_vector(2 downto 0) := "100"; constant failure : std_logic_vector(2 downto 0) := "111"; -- specify behavior for forbidden states: when others => next_state <= idle; -- you can easily revert to optimal (not safe) version of machine: when others => next_state <= "---"; State machines (9) -- Detection of forbidden states -- for state machines with one-hot state encoding: idl <= state = idle; prepar <= state = prepare; operat <= state = operate; finis <= state = finish; failur <= state = failure; inv <= (idl and (prepar or operat or finis or failur)) or (prepar and (operat or finis or failur)) or (operat and (finis or failur)) or (finis or failur) or (not (idl or prepar or operat or finis or failur)); .... if (inv = '1') then state <= idle; else .... .... State machines (10) How to avoid entering forbidden or wrong state ? Most common causes of state machine failures: • Clock frequency is too high (reduce critical path delay or clock frequency). • Signals entering state machine are not synchronous to the machine’s clock (signals generated from external devices like pushbuttons, sensors, interrupt lines, busses are usually asynchronous). Different propagation delays of signals inside the combination logic can cause the asynchronous signal change not to achieve all the FF inputs before active clock edge. D Q D Q 1-gate path: low delay Asynchronous input 3-gate path: high delay State machines (11) How to avoid entering forbidden or wrong state ? To avoid wrong FF operation you have to synchronize the input signal with the local clock domain. To achieve that you have to send the asynchronous signal through a pipe of two FFs. One FF is not enough because the metastability event in FF can occur. If you send multibit (vector) signals in this way use Gray coding or use any other method for error checking. D Q D Q Asynchronous input D Q D Q Constants and aliases • Constants can be defined inside entities, architectures, processes and packages: constant x: std_logic_vector(7 downto 0) := "00100010"; constant y: std_logic_vector(7 downto 0) := ( others => '1'); constant i: integer := 4; type state_type is (clear, initiate, run, stop); constant state_stop: state_type := stop; • Constants are useful for design configuration. You can define the size of the busses, counters, etc. • Alias names can be assigned to a part of the vector to make a partial vector access easier: signal iw: std_logic_vector(28 downto 0); alias opcode: std_logic_vector(4 downto 0) is iw(28 downto 24); alias src: std_logic_vector(7 downto 0) is iw(23 downto 16); alias dst: std_logic_vector(7 downto 0) is iw(15 downto 8); alias arg: std_logic_vector(3 downto 0) is iw(7 downto 4); alias option: std_logic_vector(2 downto 0) is iw(3 downto 1); alias reserved: std_logic_vector(3 downto 0) is iw(3 downto 0); Attributes • Attributes can be defined for entities, architectures, types and signals. type cnt is integer range 0 to 127; type state is (idle, start, stop, reset); type word is array(15 downto 0) of std_logic; • Sample attributes predefined for types and signals: 'left 'right 'high 'low 'length cnt'left = 0; state'left = idle; word'left = 15; cnt'right = 127; state'right = reset; word'right = 0; cnt'high = 127; state'high = reset; word'high = 15; cnt'low = 0; state'low = idle; word'low = 0; cnt'length = 128; state'length = 4; word'length = 16; • Sometimes the 'range attribute can be useful – it reveals the range and direction of the vector indexing, for example: word'range = 15 downto 0; • Another useful attribute 'event allows detection of a signal change. It can be used for synchronous circuits synthesis. • 'pos attribute returns the position the argument holds on the enumerated type list, for example: state'pos(start) = 1; character'pos('A') = 65; • 'val attribute is for the reverse operation, for example: state'val(2) = stop; character'val(66) = 'B'; • 'pos and 'val attributes can be used for character to ASCII code conversion and for reverse operation. 3-state buffers implementation 3-state buffers can be implemented on the external interface signals of the FPGA only. These buffers can be used for a bidirectional communication with external ICs. For 3-state buffers use a combinatorial process and a metalogic value ‘Z’: -- signal i,o,ena:std_logic; process (i,ena) is begin if ena = '1' then o <= i; else o <= 'Z'; end if; end process; You can also use a conditional assignment: -- signal i,o:std_logic_vector(127 downto 0); -- signal ena: std_logic; o <= i when ena = '1' else 'Z'; Design hierarchy (1) • Design entities can be used mutiple times in the same project. • Entire design can be composed of components implemented using different abstraction levels. • Design entities – components – can be implemented in separate files, packages and included in the project when needed. • „entity” construct is an interface of the design unit. • „component” construct is a usage declaration of the previously created entity. • „port map” construct is an instantiation of the previously declared component. Top level entity component bufor port( Entity A d, enable: in std_logic; q: out std_logic); Entity B end component bufor; Entity D ..... begin ..... buf1: bufor port map(res,ena,outp(0)); buf2: bufor port map(ack,ena,outp(1)); Entity B Entity C Entity C Design hierarchy (2) • Example – 1-bit adder: entity fulladder is port( a,b,cin: in std_logic; sum,cout: out std_logic); end entity fulladder; architecture dataflow of fulladder is begin sum <= a xor b xor cin; cout <= (a and b) or (a and cin) or (b and cin); end architecture dataflow; • 1-bit adder used for synthesis of 4-bit adder: entity adder_4 is port(a,b: in std_logic_vector(3 downto 0); cin: in std_logic; sum: out std_logic_vector(3 downto 0); cout: out std_logic); end entity adder_4; architecture structural of adder_4 is component fulladder port(a,b,cin:in std_logic; sum,cout:out std_logic); end component fulladder; signal c0,c1,c2: std_logic; begin fa0: fulladder port map(a(0),b(0),cin,sum(0),c0); fa1: fulladder port map(a(1),b(1),c0,sum(1),c1); fa2: fulladder port map(a(2),b(2),c1,sum(2),c2); fa3: fulladder port map(a(3),b(3),c2,sum(3),cout); end architecture structural; Design hierarchy (3) • Component port signals can be connected in any sequence when port names are specified. • „open” means that component’s output signal is not used. • Input signals when left open must have a default value defined in the component declaration. fa2: fa2: fa2: fa2: fulladder fulladder fulladder fulladder port port port port map(a(2),b(2),c1,sum(2),c2); map(b=>b(2), a=>a(2), sum=>sum(2), cout=>c2, cin=>c1); map(a(2),b(2),c1,sum(2),open); map(b=>b(2), a=>a(2), sum=>sum(2), cout=>open, cin=>c1); • Types and vector ranges of component signals and local signals must match (like in assignment instruction). entity borg is port(a: in std_logic_vector(3 to 7)); end entity borg; -----------------------------------------------------------component borg port(a:in std_logic_vector(10 downto 6)); end component borg; Design hierarchy (4) • When „unconstrained” vector is used in the component declaration you can connect a vector of the same base type but with any length and index range. The architecture of the component must be aware of this and must use attributes to synthesize the component automatically matched to the input and output vectors. entity borg is port(a: in std_logic_vector); end entity borg; ------------------------------------------------------------ component borg port(a:in std_logic_vector(567 downto 123)); end component borg; • Remember to properly set the direction specifiers when connecting components (in, out, inout, buffer). • You can pass parameters to the components using the „generic” declaration. Parameters in design units Default value of the parameter (optional). entity adder_n is generic (size : integer := 4 ); port (a,b :in std_logic_vector(size-1 downto 0); sum :out std_logic_vector(size-1 downto 0); cout :out std_logic); You can use parameters as soon as end adder_n; they are defined. • Default parameters value can be changed in the component declaration: component adder_n is generic (size : integer := 8 ); port (a,b :in std_logic_vector(size-1 downto 0); sum :out std_logic_vector(size-1 downto 0); cout :out std_logic); end adder_n; • You can change parameters during component instantiation: fa2: adder_n generic map(size=>12) port map(a,b,sum,carry); fa3: adder_n generic map(14) port map(sum=>s, a=>d1, b=>d2, carry=>p); Generate instruction (1) • To automatically repeat synthesis of the instructions in the concurent part of the architecture you can use the „generate” instruction (this is equivalent to the „for” loop in processes). You can automatically instantiate components this way. entity fulladder_n is generic (size : integer := 4); port(a,b : in std_logic_vector(size-1 downto 0); sum:out std_logic_vector(size-1 downto 0); cin:in std_logic; The label is cout:out std_logic); mandatory! The i variable is end entity fulladder_n; automatically defined architecture dataflow of fulladder_n is signal c:std_logic_vector(size downto 0); begin c(0) <= cin; G: for i in 0 to size-1 generate sum(i) <= a(i) xor b(i) xor c(i); c(i+1) <= (a(i) and b(i)) or (a(i) and c(i)) or (b(i) and c(i)); end generate; cout <= c(size); end architecture dataflow; Generate instruction (2) • The „generate” instruction is used to automatically generate regular structures based on a template structure. • Inside the „generate” loop any concurent instruction can be used including the „generate” itself. • There is also the concurent ”if” instruction which can be used for irregular structures and conditional synthesis. Generate instruction (3) • Generation of irregular structures (half adder used for LSB – no carry input): entity adder_n is generic (size : integer := 4 ); port (a,b: in std_logic_vector(size-1 downto 0); sum: out std_logic_vector(size-1 downto 0); cout: out std_logic); end adder_n; architecture dataflow of adder_n is component fulladder port(a,b,cin:in std_logic; sum,cout:out std_logic); end component fulladder; signal c:std_logic_vector(size downto 1); begin G1: for i in 0 to size-1 generate G2: if i=0 generate sum(i)<=a(i) xor b(i); -- halfadder c(i+1)<=a(i) and b(i); end generate; -- else and elsif are not allowed!!! G3: if i>0 generate fa: fulladder port map(a(i),b(i),c(i),sum(i),c(i+1)); end generate; end generate; cout <= c(size); end architecture dataflow;