Digital Front End Design Tools For Fpga
11. Design examples¶
11.1. Introduction¶
In previous chapters, some simple designs were introduces e.g. mod-m counter and flip-flops etc. to introduce the VHDL programming. In this chapter various examples are added, which can be used to implement or emulate a system on the FPGA board.
All the design files are provided inside the 'VHDLCodes' folder inside the main project directory; which can be used to implement the design using some other software as well. Each section shows the list of VHDL-files require to implement the design in that section. Lastly, all designs are tested using Modelsim and on Altera-DE2 FPGA board. Set a desired design as 'top-level entity' to implement or simulate it.
11.2. Random number generator¶
In this section, random number generator is implemented using linear feedback shift register. VHDL files required for this example are listed below,
- rand_num_generator.vhd
- rand_num_generator_visualTest.vhd
- clockTick.vhd
- modMCounter.vhd
Note that, 'clockTick.vhd' and 'modMCounter.vhd' are discussed in Chapter 8.
11.2.1. Linear feedback shift register (LFSR)¶
Long LFSR can be used as 'pseudo-random number generator'. These random numbers are generated based on initial values to LFSR. The sequences of random number can be predicted if the initial value is known. However, if LFSR is quite long (i.e. large number of initial values are possible), then the generated numbers can be considered as random numbers for practical purposes.
LFSR polynomial are written as \({{\mathbf{x}}^{\mathbf{3}}}{\mathbf{ + }}{{\mathbf{x}}^{\mathbf{2}}}{\mathbf{ + 1}}\)}, which indicates that the feedback is provided through output of 'xor' gate whose inputs are connected to positions 3, 2 and 0 of LFSR. Some of the polynomials are listed in Table Table 11.1.
Number of bits | Feedback polynomial |
---|---|
3 | \({x^3} + {x^2} + 1\) |
4 | \({x^4} + {x^3} + 1\) |
5 | \({x^5} + {x^3} + 1\) |
6 | \({x^6} + {x^5} + 1\) |
7 | \({x^7} + {x^6} + 1\) |
9 | \({x^9} + {x^5} + 1\) |
10 | \({x^{10}} + {x^7} + 1\) |
11 | \({x^{11}} + {x^9} + 1\) |
15 | \({x^{15}} + {x^{14}} + 1\) |
17 | \({x^{17}} + {x^{14}} + 1\) |
18 | \({x^{18}} + {x^{11}} + 1\) |
Random numbers are generated using LFSR in Listing 11.1. The code implements the design for 3 bit LFSR, which can be modified for LFSR with higher number of bits as shown below,
Explanation Listing 11.1
The listing is currently set according to 3 bit LFSR i.e. N = 3 in Line 16. 'q' is the output of LFSR, which is random in nature. Lines 29-32 sets the initial value for LFSR to 1 during reset operations. Note that, LFSR can not have '0' as initial values. Feedback polynomial is implemented at Line 41. Line 52 shifts the last N bits (i.e. N to 1) to the right by 1 bit and the $N^{th}$ bit is feed with 'feedback_value' and stored in 'r_next' signal. In next clock cycle, value of r_next is assigned to r_reg through Line 34. Lastly, the value r_reg is avalaible to output port from Line 53.
Simulation results are shown in Fig. 11.1. Here, we can see that total 7 different numbers are generated by LFSR, which can be seen between two cursors in the figure. Further, q values are represented in 'hexadecimal format' which are same as r_reg values in 'binary format'.
Note that, in Fig. 11.1, the generated sequence contains '8, C, 6, B, 5, 2 and 1'; and if we initialize the system with any of these values, outputs will contain same set of numbers again. But, if we initialize the system with '3' (which is not the set), then the generate sequences will be entirely different.
Fig. 11.1 Random number generation with N = 3
To modify the feedback polynomial, first insert the correct number of bits (i.e. N) in Line 16. Next, modify the feedback_value at line 41, according to new value of 'N'.
Note that maximum-length for a polynomial is defined as $2^N-1$, but not all the polynomials generate maximum length; e.g. N = 5 generates total 28 sequences (not 31) before repetition as shown in Fig. 11.2.
Note
Distributions using LFSR:
- Even number polynomial should be used for generating the 'Uniformly distributed numbers', as it does not miss any number from the sequences.
- Then, the uniformly distributed numbers can be used to generate the 'Gaussian distribution' using 'center limit theorem'.
Fig. 11.2 Total sequences are 28 (not 31) for N = 5
Listing 11.1 Random number generation with LFSR¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | -- rand_num_generator.vhd -- created by : Meher Krishna Patel -- date : 22-Dec-16 -- Feedback polynomial : x^3 + x^2 + 1 -- maximum length : 2^3 - 1 = 7 -- if generic value is changed, -- then choose the correct Feedback polynomial i.e. change 'feedback_value' pattern library ieee ; use ieee.std_logic_1164. all ; entity rand_num_generator is generic ( N : integer := 3 ); port ( clk , reset : in std_logic ; q : out std_logic_vector ( N downto 0 ) -- output of LFSR i.e. random number ); end rand_num_generator ; architecture arch of rand_num_generator is signal r_reg , r_next : std_logic_vector ( N downto 0 ); signal feedback_value : std_logic ; -- based on feedback polynomial begin process ( clk , reset ) begin if ( reset = '1' ) then -- set initial value to '1'. r_reg ( 0 ) <= '1' ; -- 0th bit = 1 r_reg ( N downto 1 ) <= ( others => '0' ); -- other bits are 0 elsif ( clk 'event and clk = '1' ) then r_reg <= r_next ; -- otherwise save the next state end if ; end process ; -- N = 3 -- Feedback polynomial : x^3 + x^2 + 1 -- total sequences (maximum) : 2^3 - 1 = 7 feedback_value <= r_reg ( 3 ) xor r_reg ( 2 ) xor r_reg ( 0 ); -- N = 4 -- feedback_value <= r_reg(4) xor r_reg(3) xor r_reg(0); -- N = 5, maximum length = 28 (not 31) -- feedback_value <= r_reg(5) xor r_reg(3) xor r_reg(0); -- N = 9 -- feedback_value <= r_reg(9) xor r_reg(5) xor r_reg(0); r_next <= feedback_value & r_reg ( N downto 1 ); q <= r_reg ; end arch ; |
11.2.2. Visual test¶
Listing 11.2 can be used to test the Listing 11.1 on the FPGA board. Here, 1 second clock pulse is used to visualize the output patterns. Please read Chapter 8 for better understanding of the listing. Note that, N = 3 is set in Line 13 according to Listing 11.1.
For displaying outputs on FPGA board, set reset to 1 and then to 0. Then LEDs will blink to display the generated bit patterns by LFSR; which are shown in Fig. 11.1.
Listing 11.2 Visual test : Random number generation with LFSR¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | -- rand_num_generator_visualTest.vhd -- created by : Meher Krishna Patel -- date : 22-Dec-16 -- if generic value is changed e.g. N = 5 -- then go to rand_num_generator for further modification library ieee ; use ieee.std_logic_1164. all ; entity rand_num_generator_visualTest is generic ( N : integer := 3 ); port ( CLOCK_50 , reset : in std_logic ; LEDR : out std_logic_vector ( N downto 0 ) ); end rand_num_generator_visualTest ; architecture arch of rand_num_generator_visualTest is signal clk_Pulse1s : std_logic ; begin -- clock 1 s clock_1s : entity work . clockTick generic map ( M => 50000000 , N => 26 ) port map ( clk => CLOCK_50 , reset => reset , clkPulse => clk_Pulse1s ); -- rand_num_generator testing with 1 sec clock pulse rand_num_generator_1s : entity work . rand_num_generator port map ( clk => clk_Pulse1s , reset => reset , q => LEDR ); end arch ; |
11.3. Shift register¶
Shift register are the registers which are used to shift the stored bit in one or both directions. In this section, shift register is implemented which can be used for shifting data in both direction. Further it can be used as parallel to serial converter or serial to parallel converter. VHDL files required for this example are listed below,
- shift_register.vhd
- shift_register_visualTest.vhd
- clockTick.vhd
- modMCounter.vhd
Note that, 'clockTick.vhd' and 'modMCounter.vhd' are discussed in Chapter 8.
11.3.1. Bidirectional shift register¶
Listing 11.3 implements the bidirectional shift register which is explained below,
Explanation Listing 11.3
In the listing, the 'ctrl' port is used for 'shifting', 'loading' and 'reading' data operation. Lines 32-39 clear the shift register during reset operation, otherwise go to the next state.
Lines 41-53 performs various operations based on 'ctrl' values. Note that, to perform right shift (Line 47), data is continuously provided from last port i.e. (data(N-1)); whereas for left shift (Line 49) data is provided from first port i.e. (data(0)).
Next, ctrl=''00'' is provided for reading the data. It can be used for serial to parallel conversion i.e. when all the bits are shifted and register is full; then set ctrl to ''00'' and read the data, after that set ctrl to ''01'' or ''10'' for getting next set of bits.
Similarly, for parallel to serial converter, first load the data using ctrl=''11''; and then perform the shift operation until all the bits are read and then again load the data. Note that, in this case, last bit propagates (i.e. data(N-1) for right shift or data(0) for left shift) during shifting; which is actually designed for serial to parallel converter. But this will affect the working of parallel to serial converter, as we will set ctrl to ''11'', when all the data is shifted, therefore all the register which were filled by values from last port, will be overwritten by the new parallel data.
Lastly, data is available on the output port 'q_reg' from Line 55. For, parallel to serial converter, use ony one pin of 'q_reg' i.e. q_reg(0) for right shift or q(N-1) for left shift; whereas for serial to parallel conversion, complete 'q_reg' should be read.
Fig. 11.3 shows the shifting operation performed by the listing. Here first data ( i.e. 00110000) is loaded with ctrl=''11''. Then shifted to right after first cursor and later to the left i.e. after second cursor.
Fig. 11.3 Right and left shifting operations
Listing 11.3 Bidirectional shift register¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | -- shift_register.vhd -- created by : Meher Krishna Patel -- date : 22-Dec-16 -- Functionality: -- load data and shift it data to left and right -- parallel to serial conversion (i.e. first load, then shift) -- serial to parallel conversion (i.e. first shift, then read) -- inputs: -- ctrl : to load-data and shift operations (right and left shift) -- data : it is the data to be shifted -- q_reg : store the outputs library ieee ; use ieee.std_logic_1164. all ; entity shift_register is generic ( N : integer := 8 ); port ( clk , reset : in std_logic ; ctrl : in std_logic_vector ( 1 downto 0 ); data : in std_logic_vector ( N - 1 downto 0 ); q_reg : out std_logic_vector ( N - 1 downto 0 ) ); end shift_register ; architecture arch of shift_register is signal s_reg , s_next : std_logic_vector ( N - 1 downto 0 ); begin process ( clk , reset ) begin if ( reset = '1' ) then s_reg <= ( others => '0' ); -- clear the content elsif ( clk 'event and clk = '1' ) then s_reg <= s_next ; -- otherwise save the next state end if ; end process ; process ( ctrl , s_reg ) begin case ctrl is when "00" => s_next <= s_reg ; -- no operation (to read data for serial to parallel) when "01" => s_next <= data ( N - 1 ) & s_reg ( N - 1 downto 1 ); -- right shift when "10" => s_next <= s_reg ( N - 2 downto 0 ) & data ( 0 ); -- left shift when others => s_next <= data ; -- load data (for parallel to serial) end case ; end process ; q_reg <= s_reg ; end arch ; |
11.3.2. Visual test¶
Listing 11.4 can be used to test the Listing 11.3 on the FPGA board. Here, 1 second clock pulse is used to visualize the output patterns. Here, outputs (i.e. q_reg) are displayed on LEDR; whereas 'shifting-control (i.e. ctrl)' and data-load (i.e. data) operations are performed using SW[16:15] and SW[7:0] respectively. Here, we can see the shifting of LEDR pattern towards right or left based on SW[16:15] combination. Please read Chapter 8 for better understanding of the listing.
Listing 11.4 Visual test : bidirectional shift register¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | -- shift_register_visualTest.vhd -- created by : Meher Krishna Patel -- date : 22-Dec-16 -- SW[16:15] : used for control library ieee ; use ieee.std_logic_1164. all ; entity shift_register_visualTest is generic ( N : integer := 8 ); port ( CLOCK_50 , reset : in std_logic ; SW : in std_logic_vector ( 16 downto 0 ); LEDR : out std_logic_vector ( N - 1 downto 0 ) ); end shift_register_visualTest ; architecture arch of shift_register_visualTest is signal clk_Pulse1s : std_logic ; begin -- clock 1 s clock_1s : entity work . clockTick generic map ( M => 50000000 , N => 26 ) port map ( clk => CLOCK_50 , reset => reset , clkPulse => clk_Pulse1s ); -- shift_register testing with 1 sec clock pulse shift_register_1s : entity work . shift_register generic map ( N => N ) port map ( clk => clk_Pulse1s , reset => reset , data => SW ( N - 1 downto 0 ), ctrl => ( SW ( 16 downto 15 )), q_reg => LEDR ); end arch ; |
11.3.3. Parallel to serial converter¶
If data is loaded first (i.e. ctrl = ''11''), and later shift operation is performed (i.e.. ctrl = ''01'' or ''10''); then Listing 11.3 will work as 'parallel to serial converter'. In Listing 11.5 shows the parallel to serial converter which is tested in Listing 11.8.
Listing 11.5 Parallel to serial converter¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | -- parallel_to_serial.vhd library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity parallel_to_serial is generic ( N : integer := 8 ); port ( clk , reset : in std_logic ; data_in : in std_logic_vector ( N - 1 downto 0 ); -- parallel data data_out : out std_logic ; -- serial data empty_tick : out std_logic -- 1 = empty, to control other devices ); end entity ; architecture arch of parallel_to_serial is type operation_type is ( idle , convert , done ); signal state_reg , state_next : operation_type ; signal data_reg , data_next : std_logic_vector ( N - 1 downto 0 ) := ( others => '0' ); signal count_reg , count_next : unsigned ( N - 1 downto 0 ); begin -- current register values process ( clk , reset ) begin if reset = '1' then state_reg <= idle ; data_reg <= ( others => '0' ); count_reg <= ( others => '0' ); elsif ( clk 'event and clk = '1' ) then state_reg <= state_next ; data_reg <= data_next ; count_reg <= count_next ; end if ; end process ; -- next value in the register -- note that, it is poor style of coding as output data calculation -- and next values in register are calculated simultaneously. These -- should be done in different process statements. process ( state_reg , data_in , count_reg , data_reg ) begin empty_tick <= '0' ; state_next <= state_reg ; data_next <= data_reg ; count_next <= count_reg ; case state_reg is when idle => state_next <= convert ; data_next <= data_in ; -- load the parallel data data empty_tick <= '1' ; count_next <= count_reg + 1 ; when convert => count_next <= count_reg + 1 ; if count_reg = N then -- note that N is used here (not N-1, see reason below) -- serial_to_parallel.vhd needs one clock cycle to transfer the converted -- data (i.e. parallel data) to next device, therefore it can not update the -- current value immediatly after the conversion. Therefore, count_reg = N, -- is used in above line (instead of N-1), so that one extra bit will be added -- in the end and will be discarded by serial_to_parallel.vhd as it will not -- read the last value. -- Also, data_next is not defined here, as last bit is not, therefore, value of -- data_next at count 'N' will be same as at 'N-1'. state_next <= done ; else -- shift the data to right and append zero in the beginning data_next <= '0' & data_reg ( N - 1 downto 1 ); end if ; when done => count_next <= ( others => '0' ); state_next <= idle ; end case ; end process ; -- send bit to output port data_out <= data_reg ( 0 ); end arch ; |
11.3.4. Serial to parallel converter¶
If shifting is performed first (i.e.. ctrl = ''01'' or ''10''), and later data is read (i.e. ctrl = ''00''); then Listing 11.3 will work as 'serial to parallel converter'. In Listing 11.6 shows the serial to parallel converter which is tested in Listing 11.8.
Listing 11.6 Serial to parallel converter¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | -- serial_to_parallel.vhd library ieee ; use ieee.std_logic_1164. all ; entity serial_to_parallel is generic ( N : natural := 4 ); port ( clk , reset : in std_logic ; in_tick : in std_logic ; -- input tick to control the conversion from other device din : in std_logic ; load : out std_logic ; -- use it as tick, to load data immidiately e.g. from FIFO done : out std_logic ; -- use it as tick, if data is obtained after one-clock cycle e.g. from generators dout : out std_logic_vector ( N - 1 downto 0 ) ); end entity serial_to_parallel ; architecture arch of serial_to_parallel is type state is ( idle , store0 , store1 ); signal state_reg , state_next : state ; signal y_reg , y_next : std_logic_vector ( N - 1 downto 0 ); signal i_reg , i_next : natural range 0 to N ; begin -- update registers process ( clk , reset ) begin if ( reset = '1' ) then i_reg <= 0 ; y_reg <= ( others => '0' ); elsif rising_edge ( clk ) then i_reg <= i_next ; y_reg <= y_next ; end if ; end process ; -- update states process ( clk , reset ) begin if reset = '1' then state_reg <= idle ; elsif rising_edge ( clk ) then state_reg <= state_next ; end if ; end process ; -- output data calculation process ( clk , din , y_next , i_next , i_reg , y_reg , state_reg , state_next ) begin y_next <= y_reg ; case state_reg is when idle => i_next <= 0 ; load <= '1' ; done <= '0' ; -- this loop is used to load the data immidiately in the registers if in_tick = '1' and din = '0' then -- i.e. if first value is zero state_next <= store0 ; -- then go to store0 elsif in_tick = '1' and din = '1' then -- i.e. if first value is one state_next <= store1 ; -- then go to store1 else state_next <= idle ; end if ; -- input data is 0. when store0 => i_next <= i_reg + 1 ; y_next ( i_reg ) <= '0' ; load <= '0' ; done <= '0' ; if i_reg = N - 1 then state_next <= idle ; done <= '1' ; elsif din = '1' then state_next <= store1 ; else state_next <= store0 ; end if ; -- input data is 1. when store1 => i_next <= i_reg + 1 ; y_next ( i_reg ) <= '1' ; load <= '0' ; done <= '0' ; if i_reg = N - 1 then state_next <= idle ; done <= '1' ; elsif din = '0' then state_next <= store0 ; else state_next <= store1 ; end if ; end case ; end process ; -- send data to output dout <= y_next when i_reg = N - 1 ; end architecture arch ; |
11.3.5. Top level connection between serial and parallel converters¶
In Listing 11.7, the parallel-counter-data is converted into serial data using Listing 11.5. Then received serial data is converted back to parallel data by Listing 11.6. The simulation results are shown in Fig. 11.4
Listing 11.7 Connection between serial and parallel converters¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | -- parallel_and_serial_top_v1.vhd library ieee ; use ieee.std_logic_1164. all ; library work ; entity parallel_and_serial_top_v1 is generic ( M : integer := 11 ; -- count upto M N : integer := 4 ); -- N bits required to count to M port ( reset : in std_logic ; clk : in std_logic ; data_out : out std_logic_vector ( 3 downto 0 ); data_in : out std_logic_vector ( 3 downto 0 ) ); end parallel_and_serial_top_v1 ; architecture arch of parallel_and_serial_top_v1 is signal count : std_logic_vector ( 3 downto 0 ); signal dout : std_logic ; signal e_tick : std_logic ; signal not_e_tick : std_logic ; begin -- register is not empty i.e. read data on this tick not_e_tick <= not ( e_tick ); -- generated count on data_out, whereas received count on data_out data_in <= count ; -- parallel to serial conversion unit_p_to_s : entity work . parallel_to_serial generic map ( N => N ) port map ( clk => clk , reset => reset , data_in => count , data_out => dout , empty_tick => e_tick ); -- serial to parallel conversion unit_s_to_p : entity work . serial_to_parallel generic map ( N => N ) port map ( clk => clk , reset => reset , in_tick => not_e_tick , din => dout , dout => data_out ); -- modMCounter to generate data (i.e. count) for transmission unit_counter : entity work . modMCounter generic map ( M => M , N => N ) port map ( clk => e_tick , reset => reset , count => count ); end arch ; |
Fig. 11.4 Simulation results for Listing 11.7
11.3.6. Visual test for serial and parallel converters¶
In Listing 11.8, the reduced clock-rate is applied to Listing 11.7, so that output can be seen on LEDs. Here, generated counter outputs are displayed on LEDG, whereas the received outputs (i.e. after conversion) are displayed on LEDG.
Listing 11.8 Visual test for serial and parallel converters¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | -- parallel_and_serial_top_visual_v1.vhd library ieee ; use ieee.std_logic_1164. all ; library work ; entity parallel_and_serial_top_visual_v1 is port ( reset : in std_logic ; CLOCK_50 : in std_logic ; -- generated count displayed on LEDG LEDG : out std_logic_vector ( 3 downto 0 ); -- received count displayed on LEDR LEDR : out std_logic_vector ( 3 downto 0 ) ); end parallel_and_serial_top_visual_v1 ; architecture arch of parallel_and_serial_top_visual_v1 is signal clk : std_logic ; begin -- parallel and serial conversion test unit_p_and_s : entity work . parallel_and_serial_top_v1 generic map ( M => 7 , N => 4 ) port map ( clk => clk , reset => reset , data_in => LEDR , data_out => LEDG ); -- clock tick to see outputs on LEDs unit_clkTick : entity work . clocktick generic map ( M => 5000000 , N => 23 ) port map ( clk => clock_50 , reset => reset , clkpulse => clk ); end arch ; |
11.4. Random access memory (RAM)¶
RAM is memory cells which are used to store or retrieve the data. Further, FPGA chips have separate RAM modules which can be used to design memories of different sizes and types, as shown in this section. VHDL files required for this example are listed below,
- single_port_RAM.vhd
- single_port_RAM_visualTest.vhd
- dual_port_RAM.vhd
- dual_port_RAM_visualTest.vhd
11.4.1. Single port RAM¶
Single port RAM has one input port (i.e. address line) which is used for both storing and retrieving the data, as shown in Fig. 11.5. Here 'addr[1:0]' port is used for both 'read' and 'write' operations. Listing 11.9 is used to generate this design.
Fig. 11.5 RTL view : Single port RAM (Listing 11.9)
Explanation Listing 11.9
In the listing port 'addr' (Line 31) is 2 bit wide as 'addr_width' is set to 2 (Line 24). Therefore, total '4 elements (i.e. \(2^2\))' can be stored in the RAM. Further, 'din' port (Line 32) is 3 bit wide as 'data_width' is set to 3 (Line 25); which means the data should be 3 bit wide. In summary, current RAM-designs can store '4 elements' in it and each elements should be 3-bit wide.
Write enable (we) port should be high and low for storing and retrieving the data respectively. 'din' port is used to write the data in the memory; whereas 'dout' port is used for reading the data from the memory. In Lines 43-48, the write operation is performed on rising edge of the clock; whereas read operation is performed at Line 53.
Note that, 'ram_type' (Line 38) is created using integer array (as 'addr_width' is integer at Line 2); whereas 'addr' port is of 'std_logic_vector' type (Line 31). Therefore, type conversion is required during write and read operations at Lines 46 and 53 respectively. Further, the type 'std_logic_vector' can not be converted directly to 'integer', therefore it is converted to 'unsigned' type before converting to 'integer'.
Lastly, Fig. 11.6 shows the simulation results for the design. Here, 'we' is set to 1 after first cursor and the data is written at three different addresses (not 4). Next, 'we' is set to 0 after second cursor and read operations are performed for all addresses. Since, no values is stored for address '10', therefore dout is displayed as 'UUU' for this address as shown after third cursor.
Fig. 11.6 Simulation results : Single port RAM (Listing 11.9)
Listing 11.9 Single port RAM¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | -- single_port_RAM.vhd -- created by : Meher Krishna Patel -- date : 26-Dec-16 -- Functionality: -- store and retrieve data from single port RAM -- ports: -- we : write enable -- addr : input port for getting address -- din : input data to be stored in RAM -- data : output data read from RAM -- addr_width : total number of elements to store (put exact number) -- addr_bits : bits requires to store elements specified by addr_width -- data_width : number of bits in each elements library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity single_port_RAM is generic ( addr_width : integer := 2 ; data_width : integer := 3 ); port ( clk : in std_logic ; we : in std_logic ; addr : in std_logic_vector ( addr_width - 1 downto 0 ); din : in std_logic_vector ( data_width - 1 downto 0 ); dout : out std_logic_vector ( data_width - 1 downto 0 ) ); end single_port_RAM ; architecture arch of single_port_RAM is type ram_type is array ( 2 ** addr_width - 1 downto 0 ) of std_logic_vector ( data_width - 1 downto 0 ); signal ram_single_port : ram_type ; begin process ( clk ) begin if ( clk 'event and clk = '1' ) then if ( we = '1' ) then -- write data to address 'addr' --convert 'addr' type to integer from std_logic_vector ram_single_port ( to_integer ( unsigned ( addr ))) <= din ; end if ; end if ; end process ; -- read data from address 'addr' -- convert 'addr' type to integer from std_logic_vector dout <= ram_single_port ( to_integer ( unsigned ( addr ))); end arch ; |
11.4.2. Visual test : single port RAM¶
Listing 11.10 can be use to test the Listing 11.9 on FPGA board. Different combination of switches can be used to store and retrieve the data from RAM. These data will be displayed on LEDs during read operations.
Listing 11.10 Visual test : single port RAM¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | -- single_port_RAM_visualTest.vhd -- created by : Meher Krishna Patel -- date : 26-Dec-16 -- Functionality: -- store and retrieve data from single port RAM -- ports: -- Write Enable (we) : SW[16] -- Address (addr) : SW[15-14] -- din : SW[2:0] -- dout : LEDR use ieee.numeric_std. all ; library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity single_port_RAM_visualTest is generic ( ADDR_WIDTH : integer := 2 ; DATA_WIDTH : integer := 3 ); port ( CLOCK_50 : in std_logic ; SW : in std_logic_vector ( 16 downto 0 ); LEDR : out std_logic_vector ( DATA_WIDTH - 1 downto 0 ) ); end single_port_RAM_visualTest ; architecture arch of single_port_RAM_visualTest is begin single_port_RAM_test : entity work . single_port_RAM port map ( clk => CLOCK_50 , we => SW ( 16 ), addr => SW ( 15 downto 14 ), din => SW ( 2 downto 0 ), dout => LEDR ); end arch ; |
11.4.3. Dual port RAM¶
In single port RAM, the same 'addr' port is used for read and write operations; whereas in dual port RAM dedicated address lines are provided for read and write operations i.e. 'addr_rd' and 'addr_wr' respectively, as shown in Fig. 11.7. Also, the listing can be further modified to allow read and write operation through both the ports.
Fig. 11.7 Dual port RAM
Listing 11.11 is used to implement Fig. 11.7, which is same as Listing 11.9 with three changes. First, two address ports are used at Line 32 (i.e. 'addr_rd' and 'addr_wr') instead of one. Next, 'addr_wr' is used to write the data at Line 47; whereas 'addr_rd' data is used to retrieve the data at Line 54. Hence, read and write operation can be performed simultaneously using these two address lines.
Fig. 11.8 shows the simulation results for dual port RAM. Here, on the first cursor, '011' is written at address '01'. On next cursor, this value is read along with writing operation as location '10'. Lastly, last two cursor shows that if read and write operation is performed simultaneously on one address e.g. '01', then new data will be available at 'dout' port after one clock cycle.
Fig. 11.8 Simulation results : Dual port RAM (Listing 11.11)
Listing 11.11 Dual port RAM¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | -- dual_port_RAM.vhd -- created by : Meher Krishna Patel -- date : 26-Dec-16 -- Functionality: -- store and retrieve data from single port RAM -- ports: -- we : write enable -- addr_wr : address for writing data -- addr_rd : address for reading -- din : input data to be stored in RAM -- data : output data read from RAM -- addr_width : total number of elements to store (put exact number) -- addr_bits : bits requires to store elements specified by addr_width -- data_width : number of bits in each elements library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity dual_port_RAM is generic ( addr_width : integer := 2 ; data_width : integer := 3 ); port ( clk : in std_logic ; we : in std_logic ; addr_wr , addr_rd : in std_logic_vector ( addr_width - 1 downto 0 ); din : in std_logic_vector ( data_width - 1 downto 0 ); dout : out std_logic_vector ( data_width - 1 downto 0 ) ); end dual_port_RAM ; architecture arch of dual_port_RAM is type ram_type is array ( 2 ** addr_width - 1 downto 0 ) of std_logic_vector ( data_width - 1 downto 0 ); signal ram_dual_port : ram_type ; begin process ( clk ) begin if ( clk 'event and clk = '1' ) then if ( we = '1' ) then -- write data to address 'addr_wr' -- convert 'addr_wr' type to integer from std_logic_vector ram_dual_port ( to_integer ( unsigned ( addr_wr ))) <= din ; end if ; end if ; end process ; -- get address for reading data from 'addr_rd' -- convert 'addr_rd' type to integer from std_logic_vector dout <= ram_dual_port ( to_integer ( unsigned ( addr_rd ))); end arch ; |
11.4.4. Visual test : dual port RAM¶
Listing 11.12 can be use to test the Listing 11.11 on FPGA board. Different combination of switches can be used to store and retrieve the data from RAM. These data will be displayed on LEDs during read operations.
Listing 11.12 Visual test : dual port RAM¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | -- dual_port_RAM_visualTest.vhd -- created by : Meher Krishna Patel -- date : 26-Dec-16 -- Functionality: -- store and retrieve data from dual port RAM -- ports: -- Write Enable (we) : SW[16] -- Address (addr_wr) : SW[15-14] -- Address (addr_rd) : SW[13-12] -- din : SW[2:0] -- dout : LEDR use ieee.numeric_std. all ; library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity dual_port_RAM_visualTest is generic ( ADDR_WIDTH : integer := 2 ; DATA_WIDTH : integer := 3 ); port ( CLOCK_50 : in std_logic ; SW : in std_logic_vector ( 16 downto 0 ); LEDR : out std_logic_vector ( DATA_WIDTH - 1 downto 0 ) ); end dual_port_RAM_visualTest ; architecture arch of dual_port_RAM_visualTest is begin dual_port_RAM_test : entity work . dual_port_RAM port map ( clk => CLOCK_50 , we => SW ( 16 ), addr_wr => SW ( 15 downto 14 ), addr_rd => SW ( 13 downto 12 ), din => SW ( 2 downto 0 ), dout => LEDR ); end arch ; |
language = Vhdl, label = {} ]{.vhd}
11.5. Read only memory (ROM)¶
ROMs are the devices which are used to store information permanently. In this section, ROM is implemented on FPGA to store the display-pattern for seven-segment device, which is explained in Section 8.5. VHDL files required for this example are listed below,
- ROM_sevenSegment.vhd
- ROM_sevenSegment_visualTest.vhd
11.5.1. ROM implementation using RAM (block ROM)¶
Listing 11.13 implements the ROM using a block of RAM, which stores the seven-segment display pattern in it. Fig. 11.9 shows the RTL view of the listing, where ROM is implemented using synchronous RAM (denoted by 'SYNC RAM' in the figure).
Note that, the 'write enable (WE)' port is not provided in the implementation, therefore 'WE' is set to '0' permanently (see Fig. 11.9); hence, new data can not be written in the RAM. And data stored during initialization of RAM will be stored permanently. Therefore, the RAM can be considered as ROM. This implementation of ROM is also referred as 'block ROM'.
Fig. 11.9 RTL view : block ROM (Listing 11.13)
Explanation Listing 11.13
Data from the ROM is accessed through address line; therefore two ports are provided in the design i.e. addr and data (Lines 27-28). 'addr_width' (Line 22) is the number of addresses, which corresponds to total number elements to be stored. Further, 'addr_bits' (Line 23) is also defined, which is the minimum number of bits required to represent the total number of addresses i.e. 'addr_width'. Lastly, 'data_width' is declared at Line 24, which defines the number of bits in each element.
Line 34 defines the ROM type (named as rom_type) i.e. ROM has 16 elements (i.e. addr_width) and each element contains 7 bits (i.e. data_width). Next in Line 36, a ROM is created (named as sevenSegment_ROM) of type 'rom_type'; and patterns for seven-segment display are inserted in Lines 37-52 (see Section 8.5 for details). These values are stored sequentially in ROM i.e. patterns 0, A and F are stored at location 0, 10 and 15 respectively.
Note that, ROM is created using 'integer' data type (see Line 22 and 34); whereas address line 'addr' has type 'std_logic_vector'. Therefore, to read the memory location, the 'addr' value must be changed to 'integer' which is done at Line 55. Since 'std_logic_vector' can not be converted directly to integer, therefore it is changed to 'unsigned' first, and then converted to 'integer'.
In summary, input 'addr' is the 'binary' address, which is converted into ''integer' format. Then ROM element is accessed based on this 'integer' address, which is finally provided at the output port i.e. 'data'. Fig. 11.10 shows the retrieved 'data' values from the ROM for different 'addr' values e.g. last waveform presents that 'addr' is '1111' and the corresponding 'data' value is '0111000'.
Fig. 11.10 Retrieving data from ROM
Listing 11.13 Seven segment display pattern stored in ROM¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | --ROM_sevenSegment.vhd -- created by : Meher Krishna Patel -- date : 25-Dec-16 -- Functionality: -- seven-segment display format for Hexadecimal values (i.e. 0-F) are stored in ROM -- ports: -- addr : input port for getting address -- data : ouput data at location 'addr' -- addr_width : total number of elements to store (put exact number) -- addr_bits : bits requires to store elements specified by addr_width -- data_width : number of bits in each elements library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity ROM_sevenSegment is generic ( addr_width : integer := 16 ; -- store 16 elements addr_bits : integer := 4 ; -- required bits to store 16 elements data_width : integer := 7 -- each element has 7-bits ); port ( addr : in std_logic_vector ( addr_bits - 1 downto 0 ); data : out std_logic_vector ( data_width - 1 downto 0 ) ); end ROM_sevenSegment ; architecture arch of ROM_sevenSegment is type rom_type is array ( 0 to addr_width - 1 ) of std_logic_vector ( data_width - 1 downto 0 ); signal sevenSegment_ROM : rom_type := ( "1000000" , -- 0, active low i.e. 0:display & 1:no display "1111001" , -- 1 "0100100" , -- 2 "0110000" , -- 3 "0011001" , -- 4 "0010010" , -- 5 "0000010" , -- 6 "1111000" , -- 7 "0000000" , -- 8 "0010000" , -- 9 "0001000" , -- a "0000011" , -- b "1000110" , -- c "0100001" , -- d "0000110" , -- e "0001110" -- f ); begin data <= sevenSegment_ROM ( to_integer ( unsigned ( addr ))); end arch ; |
11.5.2. ROM implementation logic cells (distributed ROM)¶
Note that, in Line 36 of Listing 11.13, the 'signal' keywords is used for 'sevenSegment_ROM', which stores the constant values in Lines 37-52. If the 'signal' keyword is replaced by 'constant', then the design will be implemented using logical cells i.e. with 'Mux' as presented in Fig. 11.11 (instead of block RAM). This design is referred as 'distributed ROM'.
Fig. 11.11 Partial RTL view : distributed ROM (modified Listing 11.13)
11.5.3. Distributed ROM vs Block ROM¶
Note that, Fig. 11.11 illustrate that large number of logical cells are required for distributed ROM implementation; hence it may result in lack of resources for implementing other logical designs.
Further, block RAM is the memory module which is embedded in the FPGA device. Therefore, ROM implementation using RAM does not use the regular logic cells; hence block ROM is the preferred way to implement the ROM as discussed in Section 11.5.1.
11.5.4. Visual test¶
Listing 11.14 is provided the input 'addr' through switches 'SW' (Line 20) and then output is from Listing 11.13 is received to signal 'data' (Lines 31-32); which is finally displayed on seven segment display devices (Line 34) and LEDs (Line 35). Here, we can see the switch value on the seven-segment display devices along with the ROM content on LEDs; e.g. if SW value is '011' then '3' will be displayed on seven-segment display and '0000110' will be displayed on LEDs.
Listing 11.14 Display ROM data on seven segment display and LEDs¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | -- ROM_sevenSegment_visualTest.vhd -- created by : Meher Krishna Patel -- date : 25-Dec-16 -- Functionality: -- retrieve data from ROM and display on seven-segment device and LEDs -- ports: -- SW : address in binary format -- HEX0 : display data on seven segment device -- LEDR : display data on LEDs library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity ROM_sevenSegment_visualTest is port ( SW : in std_logic_vector ( 3 downto 0 ); HEX0 : out std_logic_vector ( 6 downto 0 ); LEDR : out std_logic_vector ( 6 downto 0 ) ); end ROM_sevenSegment_visualTest ; architecture arch of ROM_sevenSegment_visualTest is -- signal to store received data, so that it can be displayed on -- two devices i.e. seven segment display and LEDs signal data : std_logic_vector ( 6 downto 0 ); begin seven_segment_ROM : entity work . ROM_sevenSegment port map ( addr => SW , data => data ); HEX0 <= data ; -- display on seven segment devices LEDR <= data ; -- display on LEDs end arch ; |
11.5.5. Defining ROM contents in file¶
We can define the content of the ROM in the separate file and then read this file using VHDL code. Please note following items about this style,
- The code will become device specific because Altera devices support the '.mif' files whereas Xilinx devices support the '.CGF' files, which have different formats for storing the ROM contents.
- Design can not be simulated directly using Modelsim.
Since, we are using 'Quartus software' in this tutorial, therefore '.mif' files are discussed in this section. ROM data is defined in 'seven_seg_data.mif' file as shown in Listing 11.15. In '.mif' file, the comments are written between two '% %' signs (both single line e.g. Line 1 and multiline e.g. Lines 8-12). Further, we need to define certain parameters i.e. data and address types (see comments for details). Lastly, at Line 18, we set the values at all the addresses as '0' and then values are assigned at each address. This can be useful, when we want to store data at fewer locations.
Next, Listing 11.16 is same as the Listing 11.13 except Lines 35-41 where ROM data is read from the '.mif file', instead of defining in the same file. Please read the comments for understand these lines.
Finally, Listing 11.17 is exactly same as Listing 11.14 except Listing 11.16 is instantiated at Line 31.
Listing 11.15 ROM data stored in '.mif file'¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | % seven_seg_data.mif % % ROM data for seven segment display % % data width and total data % width = 7 ; % number of bits in each data % depth = 16 ; % total number of data (i.e. total address) % % format of data and address stored in this file uns : unsigned , dec : decimal , hex : hexadecimal bin : binary , oct : octal % address_radix = uns ; % address is unsigned-type % data_radix = bin ; % data is binary-type % % ROM data % content begin [ 0. . 15 ] : 0000000 ; % optional : assign 0 to all address % 0 : 1000000 ; % format => signed : binary % 1 : 1111001 ; 2 : 0100100 ; 3 : 0110000 ; 4 : 0011001 ; 5 : 0010010 ; 6 : 0000010 ; 7 : 1111000 ; 8 : 0000000 ; 9 : 0010000 ; 10 : 0001000 ; 11 : 0000011 ; 12 : 1000110 ; 13 : 0100001 ; 14 : 0000110 ; 15 : 0001110 ; end ; |
Listing 11.16 VHDL code to read '.mif' file¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | --ROM_sevenSegment_mif.vhd -- created by : Meher Krishna Patel -- date : 25-Dec-16 -- Functionality: -- seven-segment display format for Hexadecimal values (i.e. 0-F) are stored in ROM -- ports: -- addr : input port for getting address -- data : ouput data at location 'addr' -- addr_width : total number of elements to store (put exact number) -- addr_bits : bits requires to store elements specified by addr_width -- data_width : number of bits in each elements library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity ROM_sevenSegment_mif is generic ( addr_width : integer := 16 ; -- store 16 elements addr_bits : integer := 4 ; -- required bits to store 16 elements data_width : integer := 7 -- each element has 7-bits ); port ( addr : in std_logic_vector ( addr_bits - 1 downto 0 ); data : out std_logic_vector ( data_width - 1 downto 0 ) ); end ROM_sevenSegment_mif ; architecture arch of ROM_sevenSegment_mif is type rom_type is array ( 0 to addr_width - 1 ) of std_logic_vector ( data_width - 1 downto 0 ); signal sevenSegment_ROM : rom_type ; -- note that 'ram_init_file' is not the user-defined-name (it is attribute name) attribute ram_init_file : string ; -- "seven_seg_data.mif" is the relative address with respect to project directory -- suppose ".mif" file is saved in folder "ROM", then use "ROM/seven_seg_data.mif" attribute ram_init_file of sevenSegment_ROM : signal is "seven_seg_data.mif" ; begin data <= sevenSegment_ROM ( to_integer ( unsigned ( addr ))); end arch ; |
Listing 11.17 Top level design for Listing 11.16 ¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | -- ROM_sevenSegment_mif_visualTest.vhd -- created by : Meher Krishna Patel -- date : 25-Dec-16 -- Functionality: -- retrieve data from ROM and display on seven-segment device and LEDs -- ports: -- SW : address in binary format -- HEX0 : display data on seven segment device -- LEDR : display data on LEDs library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity ROM_sevenSegment_mif_visualTest is port ( SW : in std_logic_vector ( 3 downto 0 ); HEX0 : out std_logic_vector ( 6 downto 0 ); LEDR : out std_logic_vector ( 6 downto 0 ) ); end ROM_sevenSegment_mif_visualTest ; architecture arch of ROM_sevenSegment_mif_visualTest is -- signal to store received data, so that it can be displayed on -- two devices i.e. seven segment display and LEDs signal data : std_logic_vector ( 6 downto 0 ); begin seven_segment_ROM : entity work . ROM_sevenSegment_mif port map ( addr => SW , data => data ); HEX0 <= data ; -- display on seven segment devices LEDR <= data ; -- display on LEDs end arch ; |
11.6. LCD interface¶
In this section, a package is defined in Listing 11.18 which converts the binary data into seven-segment-display-format and lcd-data-format.
11.6.1. Example 1¶
In Listing 11.19, this package is used to display the value provided by the switches on the seven-segment device and on the LCD. Further, the counting-pattern is also displayed on the LCD screen. The ASCII-values are shown in Fig. 11.12 which are used for displaying values on LCD screen. Note that, a clock divider is implemented in the design at Lines 85-100. Please see the comments for more details.
Fig. 11.12 Chart : ASCII values
Listing 11.18 Package for converting binary-format into seven-segment and ASCII format¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | -- LCD_SSD_display_pkg.vhd -- LCD and Seven-segment-display library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; package LCD_SSD_display_pkg is -- binary to seven-segment format function binary_to_ssd ( signal switch : unsigned ) return unsigned ; -- binary to LCD format function binary_to_lcd ( signal switch : unsigned ) return unsigned ; end LCD_SSD_display_pkg ; package body LCD_SSD_display_pkg is -- begin function "binary_to_ssd" function binary_to_ssd ( -- list all input here signal switch : unsigned ( 3 downto 0 ) ) -- only one value can be return return unsigned is variable sevenSegment : unsigned ( 6 downto 0 ); begin case switch is -- active low i.e. 0:display & 1:no display when "0000" => sevenSegment := "1000000" ; -- 0, when "0001" => sevenSegment := "1111001" ; -- 1 when "0010" => sevenSegment := "0100100" ; -- 2 when "0011" => sevenSegment := "0110000" ; -- 3 when "0100" => sevenSegment := "0011001" ; -- 4 when "0101" => sevenSegment := "0010010" ; -- 5 when "0110" => sevenSegment := "0000010" ; -- 6 when "0111" => sevenSegment := "1111000" ; -- 7 when "1000" => sevenSegment := "0000000" ; -- 8 when "1001" => sevenSegment := "0010000" ; -- 9 when "1010" => sevenSegment := "0001000" ; -- a when "1011" => sevenSegment := "0000011" ; -- b when "1100" => sevenSegment := "1000110" ; -- c when "1101" => sevenSegment := "0100001" ; -- d when "1110" => sevenSegment := "0000110" ; -- e when others => sevenSegment := "0001110" ; -- f end case ; return sevenSegment ; end binary_to_ssd ; -- end function "binary_to_ssd" -- begin function "binary_to_lcd" function binary_to_lcd ( -- list all input here signal switch : unsigned ( 3 downto 0 ) ) -- only one value can be return return unsigned is variable lcdDisplay : unsigned ( 7 downto 0 ); begin case switch is when "0000" => lcdDisplay := "00110000" ; -- 0, active low i.e. 0:display & 1:no display when "0001" => lcdDisplay := "00110001" ; -- 1 when "0010" => lcdDisplay := "00110010" ; -- 2 when "0011" => lcdDisplay := "00110011" ; -- 3 when "0100" => lcdDisplay := "00110100" ; -- 4 when "0101" => lcdDisplay := "00110101" ; -- 5 when "0110" => lcdDisplay := "00110110" ; -- 6 when "0111" => lcdDisplay := "00110111" ; -- 7 when "1000" => lcdDisplay := "00111000" ; -- 8 when "1001" => lcdDisplay := "00111001" ; -- 9 when "1010" => lcdDisplay := "01000001" ; -- a when "1011" => lcdDisplay := "01000010" ; -- b when "1100" => lcdDisplay := "01000011" ; -- c when "1101" => lcdDisplay := "01000100" ; -- d when "1110" => lcdDisplay := "01000101" ; -- e when others => lcdDisplay := "01000110" ; -- f end case ; return lcdDisplay ; end binary_to_lcd ; -- end function "binary_to_lcd" end LCD_SSD_display_pkg ; |
Listing 11.19 LCD and seven-segment display¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | -- LCD_SSD_display.vhd -- LCD and Seven-segment-display -------------------------------------------------- -- ASCII HEX TABLE -- Hex Low Hex Digit -- Value 0 1 2 3 4 5 6 7 8 9 A B C D E F -------------------------------------------------- --H 2 | SP ! " # $ % & ' ( ) * + , - . / --i 3 | 0 1 2 3 4 5 6 7 8 9 : ; < = > ? --g 4 | @ A B C D E F G H I J K L M N O --h 5 | P Q R S T U V W X Y Z [ \ ] ^ _ -- 6 | ` a b c d e f g h i j k l m n o -- 7 | p q r s t u v w x y z { | } ~ DEL -------------------------------------------------- library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; use work.LCD_SSD_display_pkg. all ; entity LCD_SSD_display is generic ( clk_dvd : natural := 500000 ; max_count : natural := 16 ); port ( CLOCK_50 , reset : in std_logic ; SW : in unsigned ( 3 downto 0 ); LEDG : out unsigned ( 3 downto 0 ); HEX0 : out unsigned ( 6 downto 0 ); LCD_RS , LCD_RW : out std_logic ; LCD_EN : out std_logic ; LCD_ON , LCD_BLON : out std_logic ; -- LCD ON, Backlight ON LCD_DATA : out unsigned ( 7 downto 0 ) ); end entity ; architecture arch of LCD_SSD_display is -- FSM for lcd display type stateType is ( init1 , init2 , init3 , init4 , clearDisplay , displayControl , Line1 , Line2 , writeData_row1 , writeData_row2 , returnHome ); signal state_reg , state_next : stateType ; signal input_lcd : unsigned ( 7 downto 0 ); signal count : natural := 0 ; signal char_count_reg , char_count_next : natural := 0 ; signal inc_count_reg , inc_count_next : integer := 0 ; signal lcd_clock : std_logic ; signal data1 , data2 : unsigned ( 3 downto 0 ); type character_string is array ( 0 to 31 ) of unsigned ( 7 downto 0 ); signal LCD_display_string : character_string ; begin -- Data to be displayed : LCD_display_string <= ( -- use spaces (X"20) for blank positions, then update these blank posistion -- with desired values e.g. in row 1 we udpated the blank position with switch value -- and in row 2, blank space is replaced by counting -- Line 1 X"4D" , X"45" , X"48" , X"45" , X"52" , X"20" , X"20" , X"53" , -- -- MEHER S X"57" , X"20" , X"3D" , X"20" , X"20" , X"20" , X"20" , X"20" , -- W = -- Line 2 X"43" , X"6F" , X"75" , X"6E" , X"74" , X"20" , X"3D" , X"20" , -- count = X"20" , X"20" , X"20" , X"20" , X"20" , X"20" , X"20" , X"20" -- 8 spaces ); -- LED display LEDG <= SW ; -- seven-segment display HEX0 <= binary_to_ssd ( SW ); -- lcd display LCD_ON <= '1' ; LCD_BLON <= '1' ; LCD_EN <= lcd_clock ; input_lcd <= binary_to_lcd ( SW ); -- clock divider which is used as "enable" signal for LCD -- this is required as for higher clock-rates, we can not see -- the data on the LCD. process ( CLOCK_50 , reset ) begin if reset = '1' then count <= 0 ; lcd_clock <= '0' ; elsif ( rising_edge ( CLOCK_50 )) then count <= count + 1 ; if count = clk_dvd then lcd_clock <= not lcd_clock ; count <= 0 ; end if ; end if ; end process ; process ( lcd_clock , reset ) begin if reset = '1' then state_reg <= init1 ; inc_count_reg <= 0 ; char_count_reg <= 0 ; elsif ( rising_edge ( lcd_clock )) then state_reg <= state_next ; char_count_reg <= char_count_next ; inc_count_reg <= inc_count_next ; end if ; end process ; process ( state_reg , char_count_reg , inc_count_reg , data1 , data2 , LCD_display_string , input_lcd ) begin char_count_next <= char_count_reg ; inc_count_next <= inc_count_reg ; case state_reg is -- extra initialization states are required for proper reset operation when init1 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00111000" ; -- change init2 to clearDisplay if extra-init is not required state_next <= init2 ; when init2 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00111000" ; state_next <= init3 ; when init3 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00111000" ; state_next <= init4 ; when init4 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00111000" ; state_next <= clearDisplay ; -- clear display and turn off cursor when clearDisplay => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00000001" ; state_next <= displayControl ; -- turn on Display and turn off cursor when displayControl => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00001100" ; state_next <= Line1 ; -- Line 1 -- write mode with auto-increment address and move cursor to the right when Line1 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00000110" ; state_next <= writeData_row1 ; -- write data when writeData_row1 => LCD_RS <= '1' ; LCD_RW <= '0' ; state_next <= writeData_row1 ; char_count_next <= char_count_reg + 1 ; if char_count_reg = 12 then -- replace space at position 13 with number LCD_DATA <= input_lcd ; elsif char_count_reg < 15 then LCD_DATA <= LCD_display_string ( char_count_reg ); else -- char_count_reg = 15 then LCD_DATA <= LCD_display_string ( char_count_reg ); state_next <= Line2 ; end if ; -- Line 2 when Line2 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "11000000" ; state_next <= writeData_row2 ; -- write data at line 2 when writeData_row2 => LCD_RS <= '1' ; LCD_RW <= '0' ; state_next <= writeData_row2 ; char_count_next <= char_count_reg + 1 ; if char_count_reg = 24 then -- replace space at location 26 with number -- decimal place of inc_count_reg data1 <= unsigned ( to_signed (( inc_count_reg / 10 ) mod 10 , 4 )); LCD_DATA <= binary_to_lcd ( data1 ); elsif char_count_reg = 25 then -- unit place of inc_count_reg data2 <= unsigned ( to_signed ( inc_count_reg mod 10 , 4 )); LCD_DATA <= binary_to_lcd ( data2 ); if inc_count_reg = max_count then -- reset if count reach to maximum inc_count_next <= 0 ; else inc_count_next <= inc_count_reg + 1 ; end if ; elsif char_count_reg < 31 then LCD_DATA <= LCD_display_string ( char_count_reg ); else -- char_count_reg = 31 then LCD_DATA <= LCD_display_string ( char_count_reg ); char_count_next <= 0 ; state_next <= returnHome ; end if ; -- Return write address to first character postion on line 1 when returnHome => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "10000000" ; state_next <= writeData_row1 ; end case ; end process ; end architecture ; |
11.6.2. Example 2¶
In Listing 11.19, we used the the 'mod' and 'division' operation to display the integer values on the LCD. Note that, the division operation requires lots of hardware and for large integer values it can not be synthesized as well. Therefore it is better to display the number in 'Hexadecimal' format as shown in Listing 11.20. Note that, the binary format is not suitable as well, as it will need large number of characters to display the values on LCD.
Further, in Listing 11.20 the message format is defined, but the actual values are obtained from the Listing 11.21. In this way, we can separate the 'calculation' and 'displaying' designs, which is more manageable than previous case.
Listing 11.20 Message displayed on the LCD¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | -- LCD_BER_display.vhd -- Message displayed on LCD and values are take from other entities -------------------------------------------------- -- Message | Values (from other designs) -- Errors= | 0x03F -- Bits= | 0x00000003F -------------------------------------------------- -- ASCII HEX TABLE -- Hex Low Hex Digit -- Value 0 1 2 3 4 5 6 7 8 9 A B C D E F -------------------------------------------------- --H 2 | SP ! " # $ % & ' ( ) * + , - . / --i 3 | 0 1 2 3 4 5 6 7 8 9 : ; < = > ? --g 4 | @ A B C D E F G H I J K L M N O --h 5 | P Q R S T U V W X Y Z [ \ ] ^ _ -- 6 | ` a b c d e f g h i j k l m n o -- 7 | p q r s t u v w x y z { | } ~ DEL -------------------------------------------------- library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; use work.LCD_SSD_display_pkg. all ; entity LCD_BER_display is generic ( -- dont go below 5000 as LCD will refresh very fast and we can see the values on LCD clk_dvd : natural := 50000 ; max_count : natural := 16 ); port ( CLOCK_50 , reset : in std_logic ; error_val : in unsigned ( 11 downto 0 ); total_bits : in unsigned ( 31 downto 0 ); LCD_RS , LCD_RW : out std_logic ; LCD_EN : out std_logic ; LCD_ON , LCD_BLON : out std_logic ; -- LCD ON, Backlight ON LCD_DATA : out unsigned ( 7 downto 0 ) ); end entity ; architecture arch of LCD_BER_display is -- FSM for lcd display type stateType is ( init1 , init2 , init3 , init4 , clearDisplay , displayControl , Line1 , Line2 , writeData_row1 , writeData_row2 , returnHome ); signal state_reg , state_next : stateType ; signal input_lcd : unsigned ( 7 downto 0 ); signal count : natural := 0 ; signal char_count_reg , char_count_next : natural := 0 ; -- read the new input values after displaying the current values signal error_val_reg , error_val_next : unsigned ( 11 downto 0 ); signal total_bits_reg , total_bits_next : unsigned ( 31 downto 0 ); -- clock for LCD signal lcd_clock : std_logic ; -- 16X2 LCD : ASCII-format type character_string is array ( 0 to 31 ) of unsigned ( 7 downto 0 ); signal LCD_display_string : character_string ; begin -- Data to be displayed : LCD_display_string <= ( -- use spaces (X"20) for blank positions, then update these blank posistion -- with desired values e.g. in row 1 we udpated the blank position with switch value -- and in row 2, blank space is replaced by counting -- Line 1 X"45" , X"72" , X"72" , X"6F" , X"72" , X"73" , X"3D" , X"20" , -- Errors= X"30" , X"78" , X"20" , X"20" , X"20" , X"20" , X"20" , X"20" , -- 0x 6-spaces -- Line 2 X"42" , X"69" , X"74" , X"73" , X"3D" , X"20" , X"30" , X"78" , -- Bits= 0x X"20" , X"20" , X"20" , X"20" , X"20" , X"20" , X"20" , X"20" -- 8 spaces ); -- lcd display settings LCD_ON <= '1' ; LCD_BLON <= '1' ; LCD_EN <= lcd_clock ; -- clock divider which is used as "enable" signal for LCD -- this is required as for higher clock-rates, we can not see -- the data on the LCD. process ( CLOCK_50 , reset ) begin if reset = '1' then count <= 0 ; lcd_clock <= '0' ; elsif ( rising_edge ( CLOCK_50 )) then count <= count + 1 ; if count = clk_dvd then lcd_clock <= not lcd_clock ; count <= 0 ; end if ; end if ; end process ; -- current states for LCD data process ( lcd_clock , reset ) begin if reset = '1' then state_reg <= init1 ; char_count_reg <= 0 ; total_bits_reg <= ( others => '0' ); error_val_reg <= ( others => '0' ); elsif ( rising_edge ( lcd_clock )) then state_reg <= state_next ; char_count_reg <= char_count_next ; total_bits_reg <= total_bits_next ; error_val_reg <= error_val_next ; end if ; end process ; -- write data on LCD and calculate next-states of LCD process ( state_reg , char_count_reg , error_val_reg , LCD_display_string , error_val , total_bits , total_bits_reg , input_lcd ) begin char_count_next <= char_count_reg ; total_bits_next <= total_bits_reg ; error_val_next <= error_val_reg ; case state_reg is -- extra initialization states are required for proper reset operation when init1 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00111000" ; -- change init2 to clearDisplay if extra-init is not required state_next <= init2 ; when init2 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00111000" ; state_next <= init3 ; when init3 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00111000" ; state_next <= init4 ; when init4 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00111000" ; state_next <= clearDisplay ; -- clear display and turn off cursor when clearDisplay => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00000001" ; state_next <= displayControl ; -- turn on Display and turn off cursor when displayControl => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00001100" ; state_next <= Line1 ; -- Line 1 -- write mode with auto-increment address and move cursor to the right when Line1 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "00000110" ; state_next <= writeData_row1 ; -- write data when writeData_row1 => LCD_RS <= '1' ; LCD_RW <= '0' ; state_next <= writeData_row1 ; char_count_next <= char_count_reg + 1 ; if char_count_reg = 10 then -- replace space at position 13 with number LCD_DATA <= binary_to_lcd ( error_val_reg ( 11 downto 8 )); elsif char_count_reg = 11 then LCD_DATA <= binary_to_lcd ( error_val_reg ( 7 downto 4 )); elsif char_count_reg = 12 then LCD_DATA <= binary_to_lcd ( error_val_reg ( 3 downto 0 )); elsif char_count_reg < 15 then LCD_DATA <= LCD_display_string ( char_count_reg ); else -- char_count_reg = 15 then LCD_DATA <= LCD_display_string ( char_count_reg ); state_next <= Line2 ; end if ; -- Line 2 when Line2 => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "11000000" ; state_next <= writeData_row2 ; -- write data at line 2 when writeData_row2 => LCD_RS <= '1' ; LCD_RW <= '0' ; state_next <= writeData_row2 ; char_count_next <= char_count_reg + 1 ; if char_count_reg = 24 then -- replace space at position 13 with number LCD_DATA <= binary_to_lcd ( total_bits_reg ( 31 downto 28 )); elsif char_count_reg = 25 then LCD_DATA <= binary_to_lcd ( total_bits_reg ( 27 downto 24 )); elsif char_count_reg = 26 then LCD_DATA <= binary_to_lcd ( total_bits_reg ( 23 downto 20 )); elsif char_count_reg = 27 then LCD_DATA <= binary_to_lcd ( total_bits_reg ( 19 downto 16 )); elsif char_count_reg = 28 then LCD_DATA <= binary_to_lcd ( total_bits_reg ( 15 downto 12 )); elsif char_count_reg = 29 then LCD_DATA <= binary_to_lcd ( total_bits_reg ( 11 downto 8 )); elsif char_count_reg = 30 then LCD_DATA <= binary_to_lcd ( total_bits_reg ( 7 downto 4 )); elsif char_count_reg = 31 then -- reached to end, hence go to "returnHome" LCD_DATA <= binary_to_lcd ( total_bits_reg ( 3 downto 0 )); state_next <= returnHome ; char_count_next <= 0 ; else LCD_DATA <= LCD_display_string ( char_count_reg ); end if ; -- Return write address to first character postion on line 1 when returnHome => LCD_RS <= '0' ; LCD_RW <= '0' ; LCD_DATA <= "10000000" ; state_next <= writeData_row1 ; error_val_next <= error_val ; total_bits_next <= total_bits ; end case ; end process ; end architecture ; |
Listing 11.21 Top level entity which provides the values to Listing 11.20 ¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | -- LCD_BER_display_top.vhd -- test the LCD display library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; use work.LCD_SSD_display_pkg. all ; entity LCD_BER_display_top is generic ( -- dont go below 5000 as LCD will refresh very fast and we can see the values on LCD clk_dvd : natural := 50000 ; max_count : natural := 16 ); port ( CLOCK_50 , reset : in std_logic ; SW : in unsigned ( 11 downto 0 ); LCD_RS , LCD_RW : out std_logic ; LCD_EN : out std_logic ; LCD_ON , LCD_BLON : out std_logic ; -- LCD ON, Backlight ON LCD_DATA : out unsigned ( 7 downto 0 ) ); end entity ; architecture arch of LCD_BER_display_top is begin unit_LCD_BER : entity work . LCD_BER_display port map ( CLOCK_50 => CLOCK_50 , reset => reset , -- convert 6 bits to 12 and 32 bits format error_val => to_unsigned ( to_integer ( SW ( 5 downto 0 )), 12 ), total_bits => to_unsigned ( to_integer ( SW ( 11 downto 6 )), 32 ), -- LCD control LCD_RS => LCD_RS , LCD_RW => LCD_RW , LCD_EN => LCD_EN , LCD_ON => LCD_ON , LCD_BLON => LCD_BLON , LCD_DATA => LCD_DATA ); end architecture ; |
11.6.3. Example 3 : Error counting¶
In Listing 11.21, we if we change the position of the switches, then corresponding values are shown in the LCD display. In this section, we will use two switches where one switch will act as transmitted signal and second switch will act as detected signal. If the two values are different, then error-count and transmitted-bit-count will increase; but if both the values are same then only bit-count will increase (and error-count will remain constant as there is not error).
For this, first we need to design a error-count circuit as shown in Listing 11.23. Next, we integrated Listing 11.23 and Listing 11.19 in one listing i.e. Listing 11.24; this is done so that we need to instantiate only one design to count and display the errors. In the other words, the main design should send the transmitted and detected bit patterns to Listing 11.24 and the rest of the job, i.e. error calculation and error display, will be performed by the Listing 11.24, as discussed in next paragraph.
Finally a top level design is created in Listing 11.22, which sends the transmitted and detected bit patterns (using SW) to the Listing 11.24. If both switches are at the same position then only transmitted-bit-count will increase, otherwise both error-count and transmitted-bit-count will increase. And the counting will stop when the maximum number of errors will be reached.
Note
- Different clock-rates are used for LCD-display and error-calculation, as both the design needs different frequency of operations, as shown in top level design for the error counter (see Lines 58 and 61 of Listing 11.22).
- The 'reset_n' is used in the design which is (KEY(3)) on the DE2 board. This is also important because the SW switches of the DE2 board are not implemented with debouncing circuits, therefore if the error calculation are performed on high clock rate (in actual system, not in the current design), then 200 errors will be detected on the reset operation itself.
- Further, error calculations is skipped for first few clocks as shown in Lines 67-76 of Listing 11.23. During these cycles, '0xFFF…' are displayed on the screen. Please, read the comments for more details.
- Lastly is better to use 'KEY (i.e. reset_n)' for reset operations at the top level design only; because by default 'KEY' is set to high position, therefore 'not' operation is required as shown in Line 28 of Listing 11.22. If we use 'reset_n' then it will create lots of confusion, as not operation is used with it for resetting the system.
Listing 11.22 Top level design which sends the transmitted and detected bit pattern¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | -- error_lcd_connection_test_v1.vhd library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity error_lcd_connection_test_v1 is generic ( -- total number of errors num_of_errors : unsigned ( 11 downto 0 ) := to_unsigned ( 100 , 12 ) ); port ( CLOCK_50 , reset_n : in std_logic ; SW : in std_logic_vector ( 1 downto 0 ); -- 1 tx bit, 0 detected bit LCD_RS , LCD_RW : out std_logic ; LCD_EN : out std_logic ; LCD_ON , LCD_BLON : out std_logic ; -- LCD ON, Backlight ON LCD_DATA : out unsigned ( 7 downto 0 ) ); end entity ; architecture arch of error_lcd_connection_test_v1 is signal total_errors_reg : unsigned ( 11 downto 0 ); signal total_bits_reg : unsigned ( 31 downto 0 ); signal clk_error , clk_LCD , reset : std_logic ; begin reset <= not reset_n ; unit_error_counter : entity work . error_lcd_connection_v1 generic map ( num_of_errors => num_of_errors ) port map ( clk_error => clk_error , clk_LCD => clk_LCD , reset => reset , tx_bit => SW ( 1 ), detected_bit => SW ( 0 ), -- LCD control LCD_RS => LCD_RS , LCD_RW => LCD_RW , LCD_EN => LCD_EN , LCD_ON => LCD_ON , LCD_BLON => LCD_BLON , LCD_DATA => LCD_DATA ); -- 1 ms clock unit_clockTick : entity work . clockTick generic map ( M => 5000000 , N => 26 ) port map ( clk => CLOCK_50 , reset => reset , clkPulse => clk_error -- clk_error ); clk_LCD <= CLOCK_50 ; -- clk_LCD end arch ; |
Listing 11.23 Error counter¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | -- error_counter_v1.vhd -- version : 1 -- Meher Krishna Patel -- Date : 21-Sep-2017 -- counts the pre-defined number of errors and and -- number of bits transmitted for the total number of errors library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity error_counter_v1 is generic ( -- total number of errors num_of_errors : unsigned ( 11 downto 0 ) := to_unsigned ( 50 , 12 ); -- 6000 for SF = 50; 500 for SF = 100 skip_clock : unsigned ( 15 downto 0 ) := to_unsigned ( 500 , 16 ) ); port ( clk , reset : in std_logic ; tx_bit , detected_bit : in std_logic ; total_errors : out unsigned ( 11 downto 0 ); total_bits : out unsigned ( 31 downto 0 ) ); end entity ; architecture arch of error_counter_v1 is signal count_errors , count_errors_next : unsigned ( 11 downto 0 ) := ( others => '0' ); signal count_bits , count_bits_next : unsigned ( 31 downto 0 ) := ( others => '0' ); -- skip first few clocks, so that reset signal settle down, -- it is required for proper BER display on the LCD, otherwise -- error will be calculated while reset is settling down -- This happens becuase, we are manually reseting the system. -- if require, increase/decrease the value of "count_skip_errors" signal count_skip_errors : unsigned ( 31 downto 0 ) := to_unsigned ( 20 , 32 ); signal count_skip_reg , count_skip_next : unsigned ( 32 downto 0 ); type stateType is ( setup , start ); --, done); signal state_reg , state_next : stateType ; begin process ( clk , reset ) begin if reset = '1' then count_bits <= ( others => '0' ); count_errors <= ( others => '0' ); count_skip_reg <= ( others => '0' ); state_reg <= setup ; elsif falling_edge ( clk ) then count_bits <= count_bits_next ; count_errors <= count_errors_next ; count_skip_reg <= count_skip_next ; state_reg <= state_next ; end if ; end process ; process ( tx_bit , detected_bit , count_errors , count_bits , state_reg , count_skip_reg , count_skip_errors ) begin count_bits_next <= count_bits ; count_errors_next <= count_errors ; count_skip_next <= count_skip_reg ; state_next <= state_reg ; case state_reg is when setup => -- do not count error for first few cycles if ( count_skip_reg /= count_skip_errors ) then count_bits_next <= ( others => '1' ); -- display 0xFFF count_errors_next <= ( others => '1' ); -- display 0xFFFFFFFF count_skip_next <= count_skip_reg + 1 ; elsif count_skip_reg = count_skip_errors then count_errors_next <= ( others => '0' ); count_bits_next <= ( others => '0' ); state_next <= start ; end if ; when start => -- start counting-errors if ( count_errors /= num_of_errors ) then if tx_bit /= detected_bit then count_bits_next <= count_bits + 1 ; -- increment values count_errors_next <= count_errors + 1 ; elsif tx_bit = detected_bit then count_bits_next <= count_bits + 1 ; count_errors_next <= count_errors ; end if ; elsif count_errors = num_of_errors then -- if maximum error reached count_bits_next <= count_bits ; -- then stop incrementing values count_errors_next <= count_errors ; end if ; end case ; end process ; -- assign values to output ports total_errors <= count_errors ; total_bits <= count_bits ; end arch ; |
Listing 11.24 Connect error-counting and LCD-display in one design¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | -- error_lcd_connection_v1.vhd -- version : 1 -- Meher Krishna Patel -- Date : 21-Sep-2017 -- connects the error_counter_v1.vhd and LCD_BER_display.vhd to display -- BER on the LCD library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity error_lcd_connection_v1 is generic ( -- total number of errors num_of_errors : unsigned ( 11 downto 0 ) := to_unsigned ( 200 , 12 ) ); port ( clk_error , clk_LCD , reset : in std_logic ; tx_bit , detected_bit : in std_logic ; --total_errors : out unsigned(11 downto 0); --total_bits : out unsigned(31 downto 0); LCD_RS , LCD_RW : out std_logic ; LCD_EN : out std_logic ; LCD_ON , LCD_BLON : out std_logic ; -- LCD ON, Backlight ON LCD_DATA : out unsigned ( 7 downto 0 ) ); end entity ; architecture arch of error_lcd_connection_v1 is signal total_errors_reg : unsigned ( 11 downto 0 ); signal total_bits_reg : unsigned ( 31 downto 0 ); begin unit_error_counter : entity work . error_counter_v1 generic map ( num_of_errors => num_of_errors ) port map ( clk => clk_error , reset => reset , tx_bit => tx_bit , detected_bit => detected_bit , total_errors => total_errors_reg , total_bits => total_bits_reg ); unit_LCD_BER : entity work . LCD_BER_display port map ( CLOCK_50 => clk_LCD , reset => reset , -- convert 6 bits to 12 and 32 bits format error_val => total_errors_reg , total_bits => total_bits_reg , -- LCD control LCD_RS => LCD_RS , LCD_RW => LCD_RW , LCD_EN => LCD_EN , LCD_ON => LCD_ON , LCD_BLON => LCD_BLON , LCD_DATA => LCD_DATA ); end arch ; |
11.7. VGA interface¶
In this section, VGA interface is crated for FPGA devices. VHDL files required for this example are listed below,
- sync_VGA.vhd
- sync_VGA_visualTest.vhd
- sync_VGA_visualTest2.vhd
- clockTick.vhd
- modMCounter.vhd
Note that, 'clockTick.vhd' and 'modMCounter.vhd' are discussed in Chapter 8.
11.7.1. Synchronization circuit¶
To display the data generated from FPGA on the VGA screen, we need to send synchronization signal along with some other signals e.g. horizontal synchronization signal, vertical synchronization signals and 25 MHz VGA clock etc. Listing 11.25 generates a synchronization circuit to display the data on the VGA screen.
Listing 11.25 VGA synchronization circuit¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | -- sync_VGA_visualTest.vhd -- created by : Meher Krishna Patel -- date : 24-Dec-16 -- Functionality: -- synchronize the VGA system -- ports: -- vga_clk : 25 MHz clock for VGA operation (generated by sync_VGA.vhd file) -- video_on : send video_on = '0' while synchronization ohtherwise '1' to display data -- hsync and vsync : synchronization signals required for VGA operation -- pixel_x and pixel_y : 10 bit pixel location library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity sync_VGA is port ( clk , reset : in std_logic ; hsync , vsync : out std_logic ; video_on , vga_clk : out std_logic ; pixel_x , pixel_y : out std_logic_vector ( 9 downto 0 ) ); end sync_VGA ; architecture arch of sync_VGA is -- VGA 640-by-480 constant HD : integer := 640 ; -- horizontal display area constant VD : integer := 480 ; -- vertical display area -- Horizontal and Vertical retraces constant HR : integer := 100 ; -- horizontal retrace constant VR : integer := 10 ; -- vertical retrace -- 25 MHz VGA clock signal vga_tick : std_logic ; -- pixel location signal h_pixel , h_pixel_next : unsigned ( 9 downto 0 ); signal v_pixel , v_pixel_next : unsigned ( 9 downto 0 ); -- store location of screen-ends for retracing operation signal h_end , v_end : std_logic ; begin -- 25 MHz clock for VGA operations clock_25MHz : entity work . clockTick generic map ( M => 2 , N => 2 ) port map ( clk => clk , reset => reset , clkPulse => vga_tick ); -- reset pixel location process ( clk , reset ) begin if reset = '1' then v_pixel <= ( others => '0' ); h_pixel <= ( others => '0' ); elsif ( clk 'event and clk = '1' ) then v_pixel <= v_pixel_next ; h_pixel <= h_pixel_next ; end if ; end process ; -- video on/off process ( clk , h_pixel , v_pixel ) begin if ( h_pixel < HD and v_pixel < VD ) then video_on <= '1' ; else video_on <= '0' ; end if ; end process ; -- end points for retrace h_end <= '1' when h_pixel = ( HD + HR - 1 ) else '0' ; v_end <= '1' when v_pixel = ( VD + VR - 1 ) else '0' ; -- set h_pixel_next to zero when end of horizontal-screen reached -- otherwise increment on vga_tick process ( vga_tick , h_pixel , h_end ) begin if vga_tick = '1' then if h_end = '1' then h_pixel_next <= ( others => '0' ); else h_pixel_next <= h_pixel + 1 ; end if ; else h_pixel_next <= h_pixel ; end if ; end process ; -- set v_pixel_next to zero when end of horizontal and vertical screen reached -- otherwise increment on vga_tick process ( vga_tick , v_pixel , h_end , v_end ) begin if vga_tick = '1' and h_end = '1' then if ( v_end = '1' ) then v_pixel_next <= ( others => '0' ); else v_pixel_next <= v_pixel + 1 ; end if ; else v_pixel_next <= v_pixel ; end if ; end process ; -- horizontal and vertical sync signals hsync <= '1' when ( h_pixel >= ( HD )) and ( h_pixel <= ( HD + HR - 1 )) else '0' ; vsync <= '1' when ( v_pixel >= ( VD )) and ( v_pixel <= ( VD + VR - 1 )) else '0' ; -- convert unsigned-pixel-locations to std_logic_vector format pixel_x <= std_logic_vector ( h_pixel ); pixel_y <= std_logic_vector ( v_pixel ); -- send clock to output port vga_clk <= vga_tick ; end arch ; |
11.7.2. Visual test : change screen color with switches¶
Listing 11.26 can be used to test Listing 11.25. The color of the screen will be change according to combination of switches.
Listing 11.26 Change screen color with switches¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | -- sync_VGA_visualTest.vhd -- created by : Meher Krishna Patel -- date : 24-Dec-16 -- Functionality: -- change the color of screen based on switch combination i.e. 0 to 7 -- ports: -- VGA_CLK : 25 MHz clock for VGA operation (generated by sync_VGA.vhd file) -- VGA_BLANK : required for VGA operations and set to 1 -- SW : combination will change the color of screen -- VGA_HS and VGA_VS : synchronization signals required for VGA operation -- VGA_R, VGA_G and VGA_B : 10 bit RGB signals for displaying colors on screen library ieee ; use ieee.std_logic_1164. all ; entity sync_VGA_visualTest is port ( CLOCK_50 , reset : in std_logic ; VGA_CLK , VGA_BLANK : out std_logic ; SW : in std_logic_vector ( 2 downto 0 ); VGA_HS , VGA_VS : out std_logic ; VGA_R , VGA_G , VGA_B : out std_logic_vector ( 9 downto 0 ) ); end sync_VGA_visualTest ; architecture arch of sync_VGA_visualTest is signal rgb_reg : std_logic_vector ( 2 downto 0 ); signal video_on : std_logic ; begin -- set VGA_BLANK to 1 VGA_BLANK <= '1' ; -- instantiate sync_VGA for synchronization sync_VGA_unit : entity work . sync_VGA port map ( clk => CLOCK_50 , reset => reset , hsync => VGA_HS , vsync => VGA_VS , video_on => video_on , vga_clk => VGA_CLK , pixel_x => open ); -- read switch and store in rgb_reg process ( CLOCK_50 , reset ) begin if reset = '1' then rgb_reg <= ( others => '0' ); elsif ( CLOCK_50 'event and CLOCK_50 = '1' ) then rgb_reg <= SW ; end if ; end process ; -- send MSB of rgb_reg to all the 10 bits of VGA_R -- repeat it for VGA_G and VGA_B with rgb_reg(1) and rgb_reg(0) respectively VGA_R <= ( others => rgb_reg ( 2 )) when video_on = '1' else ( others => '0' ); VGA_G <= ( others => rgb_reg ( 1 )) when video_on = '1' else ( others => '0' ); VGA_B <= ( others => rgb_reg ( 0 )) when video_on = '1' else ( others => '0' ); end arch ; |
11.7.3. Visual test : display different colors on screen¶
Listing 11.27 displays four different colors on the screen as shown in Fig. 11.13.
Fig. 11.13 Screen with four different colors (Listing 11.27)
Listing 11.27 Display different colors on screen¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | -- sync_VGA_visualTest2.vhd -- created by : Meher Krishna Patel -- date : 27-Dec-16 -- Functionality: -- display four squares of different colors on the screen -- ports: -- VGA_CLK : 25 MHz clock for VGA operation (generated by sync_VGA.vhd file) -- VGA_BLANK : required for VGA operations and set to 1 -- VGA_HS and VGA_VS : synchronization signals required for VGA operation -- VGA_R, VGA_G and VGA_B : 10 bit RGB signals for displaying colors on screen library ieee ; use ieee.std_logic_1164. all ; use ieee.numeric_std. all ; entity sync_VGA_visualTest2 is port ( CLOCK_50 , reset : in std_logic ; VGA_CLK , VGA_BLANK : out std_logic ; VGA_HS , VGA_VS : out std_logic ; VGA_R , VGA_G , VGA_B : out std_logic_vector ( 9 downto 0 ) ); end sync_VGA_visualTest2 ; architecture arch of sync_VGA_visualTest2 is signal rgb_reg : std_logic_vector ( 2 downto 0 ); signal video_on : std_logic ; signal pixel_x , pixel_y : std_logic_vector ( 9 downto 0 ); signal pix_x , pix_y : integer ; begin -- set VGA_BLANK to 1 VGA_BLANK <= '1' ; -- instantiate sync_VGA for synchronization sync_VGA_unit : entity work . sync_VGA port map ( clk => CLOCK_50 , reset => reset , hsync => VGA_HS , vsync => VGA_VS , video_on => video_on , vga_clk => VGA_CLK , pixel_x => pixel_x , pixel_y => pixel_y ); pix_x <= to_integer ( unsigned ( pixel_x )); pix_y <= to_integer ( unsigned ( pixel_y )); process ( CLOCK_50 ) begin if ( video_on = '1' ) then -- divide VGA screen i.e. 640-by-480 in four equal parts -- and display different colors in those parts -- Red color if ( pix_x < 320 and pix_y < 240 ) then -- 640/2 = 320 and 480/2 = 240 VGA_R <= ( others => '1' ); -- send '1' to all 10 bits of VGA_R VGA_G <= ( others => '0' ); VGA_B <= ( others => '0' ); -- Green color elsif ( pix_x >= 320 and pix_y < 240 ) then VGA_R <= ( others => '0' ); VGA_G <= ( others => '1' ); VGA_B <= ( others => '0' ); -- Blue color elsif ( pix_x < 320 and pix_y >= 240 ) then VGA_R <= ( others => '0' ); VGA_G <= ( others => '0' ); VGA_B <= ( others => '1' ); -- Yellow color else VGA_R <= ( others => '1' ); VGA_G <= ( others => '1' ); VGA_B <= ( others => '0' ); end if ; else VGA_R <= ( others => '0' ); VGA_G <= ( others => '0' ); VGA_B <= ( others => '0' ); end if ; end process ; end arch ; |
Digital Front End Design Tools For Fpga
Source: https://vhdlguide.readthedocs.io/en/latest/vhdl/dex.html
Posted by: herreramodyette60.blogspot.com
0 Response to "Digital Front End Design Tools For Fpga"
Post a Comment