In Part 1 of my Design Guide I looked at the outline of a new project, a temperature meter, and the blocks I’d need to get started. This time I will look at the code used to display the digits on the display and examine a little of what is going on. However I want to first thank people for the questions - that's what this is all about - and address the hardware questions. I will be making a effort to generate the circuits in Eagle so that you can see the full circuits used. However I want to focus on the FPGA design first, so it will all come in the final part of this Design Guide.
I’m going to look first not at things like structure, the Entity or Architecture but the raw code used. I’ll also start out by not writing this code very well and use poor structure. The reason is I want to show you the code and ignore ‘some’ of that to start with. Later on in my next post when I explain structure, I’ll place this code in that structure.
Now referring back to the last article you will see we had blocks of functions that do things, so I’ll first start with the counter.
-- generate counter
process(clk_i) is
begin
if rising_edge(clk_i) then
cnt_r <= cnt_r + 1;
end if;
end process;
You will see that its quite a small block of code and uses two varibles that in VHDL we would call signal’s (think signal wires) called clik_i and cnt_r. Now we cant see the size of these but don't worry for now. What you will see is that the code is wrapped up in something called a process. This is were the hardware is getting defined and you will see the process lists clk_i at the start. The process needs to know what signals will effect the output. In our case clk_i is the clock input. We then see a ‘if’ statement that looks at the rising edge of the clock and then you can see that our signal cnt_r gets changed if this is true. Now this is not like a programming language like C, you cant say X equals Y here, you have to say that X is ‘assigned’ with Y. So in our code we are ‘assigning’ cnt_r to the value of cnt_r plus one. And because the rising edge is a event the values increases each clock cycle. Its important to point out that we need to do this stuff on the rising edge, not when the clock signal is high. That's because ‘high’ is a state not a event and our little counter would run as fast as it can in a race condtion effect clicking up very fast. I’ll come back to the size of the signals later on.
The next block of code is our binary to BCD convertor.
function to_bcd ( bin : std_logic_vector(7 downto 0) ) return std_logic_vector is
variable i : integer:=0;
variable bcd : std_logic_vector(11 downto 0) := (others => '0');
variable bint : std_logic_vector(7 downto 0) := bin;
begin
for i in 0 to 7 loop -- repeating 8 times.
bcd(11 downto 1) := bcd(10 downto 0); --shifting the bits.
bcd(0) := bint(7);
bint(7 downto 1) := bint(6 downto 0);
bint(0) :='0';
if(i < 7 and bcd(3 downto 0) > "0100") then --add 3 if BCD digit is greater than 4.
bcd(3 downto 0) := bcd(3 downto 0) + "0011";
end if;
if(i < 7 and bcd(7 downto 4) > "0100") then --add 3 if BCD digit is greater than 4.
bcd(7 downto 4) := bcd(7 downto 4) + "0011";
end if;
if(i < 7 and bcd(11 downto 8) > "0100") then --add 3 if BCD digit is greater than 4.
bcd(11 downto 8) := bcd(11 downto 8) + "0011";
end if;
end loop;
return bcd;
end to_bcd;
VHDL is as you know a way of describing hardware. To this there is a way of thinking, that the code is about thinking hardware, NOT software or sequential. However there ways of writing in VHDL that looks and feels just like normal C code. These are called ‘functions’ in VHDL and sit in a special place in the code structure which we will see later. They then get called by the normal VHDL a bit like a subroutine in normal code but its all really happening in logic, there is no processor running this code. Functions like C code can be passed signals (values) and return them too. Inside you can generate variables and write your code. Here I have taken code written in true C to do the conversions. I have had to tweak it because in VHDL you use the ‘:=’ operator not the normal ‘=’ operator for passing values between variables (not these are NOT signals).
To use this above function in our VHDL code we simply use:
bcd(11 downto 0) <= to_bcd(cnt_r(29 downto 22));
Just like C code, VHDL pops off and takes in some signals, and processes the function and gives us a answer back (well think of it this way for now). Its again all in signals so the value of the ‘bcd’ signal is assigned with the result of our function.
The next part of my code is the look up table. Here its shown in summary only:
process (clk_i) is
begin
if rising_edge(clk_i) then
case bcd(3 downto 0) is
when B"0000" =>
col_data_1(4 downto 1) <= B"1000";
col_data_2(4 downto 1) <= B"1010";
col_data_3(4 downto 1) <= B"1010";
col_data_4(4 downto 1) <= B"1010";
col_data_5(4 downto 1) <= B"1000";
when B"0001" =>
col_data_1(4 downto 1) <= B"1101";
col_data_2(4 downto 1) <= B"1001";
col_data_3(4 downto 1) <= B"1101";
col_data_4(4 downto 1) <= B"1101";
col_data_5(4 downto 1) <= B"1000";
:
:
when others =>
col_data_1(4 downto 1) <= B"1111";
col_data_2(4 downto 1) <= B"1111";
col_data_3(4 downto 1) <= B"1000";
col_data_4(4 downto 1) <= B"1111";
col_data_5(4 downto 1) <= B"1111";
end case;
case bcd(7 downto 4) is
when B"0000" =>
col_data_1(8 downto 5) <= B"1111";
col_data_2(8 downto 5) <= B"1111";
col_data_3(8 downto 5) <= B"1111";
col_data_4(8 downto 5) <= B"1111";
col_data_5(8 downto 5) <= B"1111";
when B"0001" =>
col_data_1(8 downto 5) <= B"1101";
col_data_2(8 downto 5) <= B"1001";
col_data_3(8 downto 5) <= B"1101";
col_data_4(8 downto 5) <= B"1101";
col_data_5(8 downto 5) <= B"1000";
:
:
when others =>
col_data_1(8 downto 5) <= B"1111";
col_data_2(8 downto 5) <= B"1111";
col_data_3(8 downto 5) <= B"1000";
col_data_4(8 downto 5) <= B"1111";
col_data_5(8 downto 5) <= B"1111";
end case;
end if;
end process;
Even so this is a big chuck of code! Once again we see the ‘Process’ being sensitive to the clock in our system and once again only doing anything when there is a rising clock edge. For my look up table I’m using a ‘case’ statement. Its a lot like the ‘switch - case’ statement you see in C code. However the ‘case’ statement here defines what your looking at and then it uses a ‘where’ statement to instruct what happens. So here I have used the top ‘case’ to look at only one of the BCD digits (4 bits). The ‘where’ looks at the BCD value and can then generate or assert values onto 5 signals. These signals that are set are the LED pattens that will be used on the display. Look closely at the 1’s and 0’s and you can see that shape of the numbers to be displayed. You will also see that for the first bcd digit, when the value is zero I don't display anything. That's so we don't get a leading zero (01 for example). We can also use the ‘others’ keyword at the end of each case to cover unexpected results, like a value other than in the range 0 to 9.
Next block to look at is the shift register that drives the rows of the display:
process (cnt_r(12)) is
begin
if rising_edge(cnt_r(12)) then
row_shift(3 downto 0) <= row_shift( 4 downto 1);
row_shift(4) <= row_shift(0);
end if;
end process;
Once again we see the process but its not looking at the clock this time. We said in the last article that we need a slower speed for the shift register as too fast and we get problems on the LED matrix. So here I have used, for now, the 12th bit of the counter we generated. There are a few ways of making a shift register but this is my way of making it so you can see whats happening. Here we simply assign the top 4 bits to the lower bits and then wrap around the last bit.
-- drive row's on LED matrix
LED_row <= row_shift(4 downto 0);
Once we have the shift register its a simple line of VHDL to push these line out to the LED matrix.
So driving the rows was easy, and we have gone from counter via BCD to look-up table. Now we have generated the columns we need to drive them out for each row.
-- for each row, drive the col data into matrix
process (clk_i, row_shift(4 downto 0)) is
begin
if rising_edge(clk_i) then
case row_shift(4 downto 0) is
when B"11110" =>
LED_col <= col_data_1;
when B"11101" =>
LED_col <= col_data_2;
when B"11011" =>
LED_col <= col_data_3;
when B"10111" =>
LED_col <= col_data_4;
when B"01111" =>
LED_col <= col_data_5;
when others =>
LED_col <= B"11111111";
end case;
end if;
end process;
Again the ‘case’ comes in and we look at the row (shift register) signal to decide which colum of signals need to be pushed out to the LED matrix.
So now you have seen the chunks of code, below I have a link to the one file with it all in. This is not best practice but hopefully you will from the explain above be able to read it and understand it. Next time I’ll go deeper and explain the links between the blocks of code, talk about the larger structure of the code and more.
As always your feedback is welcome.
Thanks
Paul (@monpjc)