Friday, 3 January 2014

An 8 bit counter with 7-segment display implemented on a CPLD using VHDL

It looks like my problems with the counter were down to the 555 timer.  Replacing the crufty old 555 with an Atmel ATMega8 generating the clock signal caused the counter to start working properly.

Here is the ABEL code:

MODULE counter

// Defined inputs

CLOCK                pin 1;
RESET                pin 2;

// Defined outputs

Q3..Q0            pin 8,9,11,12 istype 'reg';

DATA = [Q3..Q0];


DATA := DATA + 1;


The interesting part is the assignment of the clock line to each bit in the counter, the linking of the reset line, and the actual increment to the counter value.

None of this is very exciting, but it was nice to actually get a design I wrote (well, borrowed) working in real hardware.

ABEL is fine, but there are more modern (and more time-efficient) alternatives.  The two (there may be more) competitors, as I mentioned in my previous post, are Verilog and VHDL.  After playing with both, I have decided I like VHDL more.  While it is more "wordy" then Verilog, there is a certain quality to the code which, at least to my untrained eye, seems to make it clearer then Verilog.  I have no doubt it boils down to a preference thing.  In any case, I have decided to stick to VHDL for the time being, and see what I can create with it.  My eventual aim is to produce, using either the XC9572 or some more sophisticated CPLD or FPGA, a custom chip for my 6809 computer.  But in the mean time, to help me learn VHDL I have been following various tutorials on the subject, and in the process coming up with some of my own designs.

One "beginner" practical use for VHDL I want to explore is to create a design which implements the IO glue, which I previously previously implemented in ABEL.  This will be trivial to implement in VHDL.  But in addition to the IO glue logic functions, I would like to replace the simple LED output latch with something easier to read.  The output latch in my computer is used for debugging mostly, and as a simple output device.  But reading the value from a set of LEDs is a little awkward, so I would like to replace it with a handy two digit seven segment display.  I have several 7 segment LED modules sitting in my parts drawer for months and wasn't sure what to do with them.  This seems like a good use.

But before implementing a full-on "IO glue + 7 segment output latch" HDL design, I thought it sensible to work up to it, by implementing something simpler that doesn't require integration with the 6809 computer.

So I have implemented the "tail end" of this idea: a two digit hexadecimal display driven from an 8 bit counter.  To make things more interesting, the counter can go down as well as up.

First up is a description of the code running on the ATMega8.  (This is overkill, and was the chip I had to hand.  An ATTiny would have done the job just as well and taken less breadboard space).  We need two signals, one for the counter and one to drive a multiplexer which can drive both digits using a single 7 segment decoder.

The code in the AVR is as follows:

#include <avr/io.h>
#include <util/delay.h>

int main(void)
        DDRC = 0x3f; /* Outputs for bits 5 through 0. */         

        while (1)
                unsigned char x, c;
                for (x = 0; x < 0x20; x++) /* 00000 -> 11111 */
                        PORTC = x; /* Set the value on the port */
                        for (c = 0; c < 0x80; c++) /* About 128ms worth */
                                PORTC ^= 0x20; /* Toggle bit 5. */
        return 0;                                 

So for port C, pin 0 through 4 can be used for different clock rates, from about 256mS in period for pin 0 up to about 4096mS for pin 4.  Pin 5 on the same port will toggle at a frequency of about 500Hz.  This is used to drive the multiplexing display.  It does seem quite crazy to use, what is essentially, a very small computer to generate a couple of square waves.  We live in interesting times!

The block-level diagram for the circuit is as follows:

The yellow box represents the boundary of the CPLD.  The names of the "ports" on the components, and the names of the wires entering the CPLD match the VHDL code shown later.

Each block is a separate circuit, or component in VHDL speak, with it's own inputs and outputs.  The 8 bit counter - with clock, reset and direction inputs - is fed into a two by four bit multiplexer, with a 7-segment decoder driven from that.  Outputs to drive the common side of the 7 segment display are tapped off the mux, which also needs a rapidly changing address input to switch between the two digits.  It is also possible to disable the display (but not the counting) with the EN(able) input.

Starting from the left, the counter is implemented with the following VHDL:

-- An 8 bit up/down couner with reset

library IEEE;

entity counter is
    port (    CLK : in STD_LOGIC;                  -- Counter clock
              RESET : in STD_LOGIC;                -- Cunter reset
              DIR : in STD_LOGIC;                  -- Cointer direction (0 for up)
              X : out STD_LOGIC_VECTOR (7 downto 0));  -- And the counter output
end counter;

architecture Behavioral of counter is
    signal COUNT : STD_LOGIC_VECTOR (7 downto 0) := x"00";
    process (CLK, RESET)
        if (CLK'Event and CLK = '0') then
            -- Up and down based on DIR (automatic wrapping)
            if (DIR = '0') then
                COUNT <= COUNT + '1';
                COUNT <= COUNT - '1';
            end if;
        end if;
        -- Sychronus reset
        if (RESET = '1') then
            COUNT <= x"00";
        end if;
    end process;
    -- Copy the count to the output, because you cant do sum on outputs
    X <= COUNT;
end Behavioral;

Quite simple stuff, I'm sure, to a seasoned HDL engineer, but for me this is new ground!

A process is a coding block that runs when one of the inputs in parenthesis changes.  In this case, we are interested in the clock and reset inputs.  On a rising clock edge, the direction input is examined and the counter value changed accordingly.  Reset causes the count to return to the initial value, 0.

Next up is the 2 way, 4 bit Mux.  This is a little interesting because the decoded value of the address is also output, so the correct digit can be lit up:

-- 2 way, 4 bit multiplexer with indicator outputs

library IEEE;

entity twobyfourmux is
    port (    A : in STD_LOGIC_VECTOR (3 downto 0);    -- First input set
              B : in STD_LOGIC_VECTOR (3 downto 0);    -- Second input set
              SEL : in STD_LOGIC;                      -- 0 for A, 1 for B
              O : out STD_LOGIC_VECTOR (3 downto 0);   -- Output
              SEL_A : out STD_LOGIC;                   -- High for A
              SEL_B : out STD_LOGIC);                  -- High for B
end twobyfourmux;

architecture Behavioral of twobyfourmux is
    O <= A when (SEL = '0') else B;                    -- Standard Mux stuff
    SEL_A <= '1' when (SEL = '0') else '0';            -- Set selector A
    SEL_B <= '1' when (SEL = '1') else '0';            -- Set selector B
end Behavioral; 

This is simple combinational logic, implemented with the "when" construct.  The SEL_A and SEL_B outputs drive the common LED line for a digit, as the high level diagram shows.

Finally the 7-segment decoder:

-- A 7 segment (hex) decoder with enable input

library IEEE;

entity sevensegdecoder is
    port (    DATA : in STD_LOGIC_VECTOR (3 downto 0);
              Q : out STD_LOGIC_VECTOR (6 downto 0);
              EN : in STD_LOGIC);                            -- Active low
end sevensegdecoder;

architecture Behavioral of sevensegdecoder is
process (DATA, EN)
    Q <= "0000000";
    if (EN = '0') then
        case DATA is
            when x"0" => Q <= "1111110";
            when x"1" => Q <= "0110000";
            when x"2" => Q <= "1101101";
            when x"3" => Q <= "1111001";
            when x"4" => Q <= "0110011";
            when x"5" => Q <= "1011011";
            when x"6" => Q <= "1011111";
            when x"7" => Q <= "1110000";
            when x"8" => Q <= "1111111";
            when x"9" => Q <= "1111011";
            when x"a" => Q <= "1110111";
            when x"b" => Q <= "0011111";
            when x"c" => Q <= "1001110";
            when x"d" => Q <= "0111101";
            when x"e" => Q <= "1001111";
            when x"f" => Q <= "1000111";
            when others => Q <= "0000001";
        end case;
    end if;
end process;
end Behavioral;

Again a process construct is used, with sensitivity on the input value and the enable line.  The mapping of the output bits to LED segments is not really ideal, and was a mistake.  The lowest bit is the "g" LED (the bar across the middle of the digit) and not the "a" segment.  I noticed this on the initial testing.  The cool part was that I could correct the error by remapping the pins on the CPLD, in software, instead of moving wires around on the breadboard.

All the parts have thus been described.  The only remaining element is the outer most one, which wraps the 3 components.  Here it is:

library IEEE;

entity bytedisplay is
    port (      CLK : in STD_LOGIC;         -- Counter clock
                DIR : in STD_LOGIC;         -- Counter direction
                RESET : in STD_LOGIC;       -- Counter reset
                DIGIT_ADDR : in STD_LOGIC;  -- Which digit to show (Mux input)
                DIGIT_A : out STD_LOGIC;    -- Show digit A (from Mux)
                DIGIT_B : out STD_LOGIC;    -- Show digit B (from Mux)
                EN : in STD_LOGIC;          -- Decoder enable
                SEGS : out STD_LOGIC_VECTOR (6 downto 0)); -- 7 segment output
end bytedisplay;

architecture Behavioral of bytedisplay is
    component counter is   
        port ( ... ));
    end component;
    component twobyfourmux is
        port ( ... );   
        end component;
    component sevensegdecoder is
        port ( ... );
    end component;
    signal COUNT : STD_LOGIC_VECTOR(7 downto 0);    -- 8 bit counter
    signal MUX_OUT : STD_LOGIC_VECTOR(3 downto 0);  -- 4 bits from Mux
    signal NOT_SEGS : STD_LOGIC_VECTOR(6 downto 0); -- 7 outputs, high for light
    counter1: counter port map (CLK, RESET, DIR, COUNT(7 downto 0));
    mux1: twobyfourmux port map (COUNT(3 downto 0), COUNT(7 downto 4), DIGIT_ADDR, MUX_OUT, DIGIT_A, DIGIT_B);
    decoder1: sevensegdecoder port map(MUX_OUT, NOT_SEGS, EN);
    -- Invert outputs as we have common cathode display
    SEGS <= not NOT_SEGS;
end Behavioral;

Each component interface must be repeated inside the behavioral description, which is a little bit annoying.  It can probably be avoided if the components are published in a library (this is a guess).  Anyway, you can see how the components are chained up.  The count value is split in half and fed as two inputs into the multiplexer, which then has it's output fed into the 7 segment decoder.  Finally, because I am using a common cathode display, the segment values are inverted.  This is done outside of the decoder to make the decoder design useful with both types (inverting and non inverting) of display.

Note that the signals here are for the "internal wires" in the block diagram, above.

One thing that would be really cool is if the Xilinx ISE software could generate a schematic from the VHDL code.  I think newer versions can, but the one I'm using (10.1, which was the last one to support ABEL) cannot.  When I eventually upgrade I will be interested to see what the circuits which have been synthesized from my VHDL code actually look like.

One thing ISE gives you is a report on what "resources" inside the CPLD your design implements is using.  A resource is a register bit, a pin, or a macrocell (which make up the programmable gates).

The current implementation is using the following resources:
  • Macrocells Used: 17/72  (24%)
  • Pterms Used: 89/360  (25%)
  • Registers Used: 8/72  (12%)
  • Pins Used: 14/34  (42%)
  • Function Block Inputs Used: 23/144  (16%)
I must admit I don't know what all of that means.  But from the looks of things, I'm probably using about 20% (ignoring pins) of what a XC9572 is capable of.  Pretty good, considering.  It would be fun to extend the width of the counter (and thus the mux) and see how many digits this CPLD could drive with this kind of design.  I should also point out that I haven't tried to tune the code to use less resources.

Here is a picture of the breadboard rig:

The AVR, with LED on the clock line, is at the top left. The box at the top is the JTAG programmer.  Because I'm so pleased with this little device I've made, I have made a short video:

In the video, power is applied to the circuit, which starts the counter counting.  After a while the counter direction button is pushed which sets the counter counting backwards.  The reset action is also shown.

Some conclusions I have drawn from this little exersise:
  • VHDL rocks.  While I'm sure I will find new challenges with it, I'm amazed that the fairly sophisticated circuit I've implemented was coded up in a matter of a couple of days, whilst learning the language!
  • These tutorials were invaluable and are a great way to get started in VHDL, and programmable logic in general.
  • It's amazing to be able to make code changes, upload them into a piece of hardware, and then see the circuit change right there and then.
  • The ISE tools are a bit basic really.  They work, but that's all that can really be said about them.  They aren't bad as such, but they are just not very good.  I am still using versions that are about 5 years old though.
  • It would be interesting to see how portable my VHDL design is.  I'm guessing it could easily be implemented in another vendor's CPLD or FPGA, or even turned into an ASIC (boy that would be the most pointless ASIC ever).
  • Running Windows 7 inside a VM on a host that only has 2GB RAM is quite painful.
My next challenge, and it's a pretty big one, is to change over my 7 segment display to sit on the 6809 bus.  I think initially I will forgo including the IO glue logic inside the same CPLD, and simply get the latch display working.  This will involve watching for a Chip Select line, Write line, and moving whats on the data bus into the latch for muxing and decoding.  One nice thing is that I already have a mux address line - the E clock.  This should give me a nice (and very very fast) switch between the LED digits.  If I want to deal with using the latch to read data to the 6809 (maybe using some DIP switches) I will have to deal with tri-stating the data bus as well.  I might start to run out of IO pins at that point though...

So many fun things to be getting on with, but not nearly enough free time....