VHDL project template for open-source projects
written on November 18, 2020
VHDL development is often associated with old and bloated (mostly proprietary) software, installations that take forever and take up a huge amount of space... what if there were a simple open-source alternative?
I do work on some VHDL projects (like this one, or this one) from time to time, so I wanted a simple, Makefile-based project template I could build upon, which would be supported at least on Linux and macOS, and which would be easy to install and setup and yet powerful and scalable. So I rolled my own VHDL open-source project template. In this blogpost I'd like to explore how it works and give a concrete example of building something with it.
It depends on GHDL and GTKWave (installation instructions for Linux and macOS are provided in the repo's README). GTKWave actually isn't strictly required, as all building/testing can be achieved with GHDL alone, but it's fun to have and it sometimes helps to visually debug an IP using a digital timing diagram.
Anyway, let's say we want to build a simple 3-input AND
gate IP. We first have to clone the template repo:
$ shell
$ git clone https://github.com/kokkonisd/vhdl-template/
We can then rename the folder to reflect our actual project name, and get rid of all the Git-related stuff we don't need:
$ shell
$ mv vhdl-template/ my-awesome-ip/
$ cd my-awesome-ip/
$ rm -rf .git/ LICENSE README.md
As you can see, the project template is laid out as follows:
$ shell
$ tree
.
├── Makefile
├── simu
├── src
└── tb
3 directories, 1 file
There are three main folders: src
, which contains the
VHDL sources (like MyIP.vhdl
), tb
which contains the test benches associated with the
sources (like MyIP_tb.vhdl
) and finally simu
, which contains the simulation files generated based on
the test benches. These files can then be opened by GTKWave, but we'll get to that.
Let's start by building a simple 2-input AND
gate:
src/SimpleAndGate.vhdl
library ieee;
use ieee.std_logic_1164.all;
entity SimpleAndGate is
port (
A : in std_logic;
B : in std_logic;
O : out std_logic
);
end entity;
architecture behavioral of SimpleAndGate is
begin
O <= A and B;
end architecture;
Very simple, just has two inputs and one output (all std_logic
), and the output is equal to the AND
ing of the two inputs. Let's try to compile this IP by
running make
:
$ shell
$ make
[Compiling `SimpleAndGate.vhdl` ...]
ghdl -s --workdir=build src/SimpleAndGate.vhdl
ghdl -a --workdir=build src/SimpleAndGate.vhdl
No errors! Our simple IP is working. The first command checks the syntax of the .vhdl
file, and the second one analyzes it. It should now be
ready to be used by other IPs. Speaking of which, let's code a 3-input AND
gate IP based on the one we just implemented:
src/AwesomeTripleAndGate.vhdl
library ieee;
use ieee.std_logic_1164.all;
entity AwesomeTripleAndGate is
port (
A : in std_logic;
B : in std_logic;
C : in std_logic;
O : out std_logic
);
end entity;
architecture behavioral of AwesomeTripleAndGate is
signal temp : std_logic;
begin
AND0: entity work.SimpleAndGate(behavioral) port map (A, B, temp);
AND1: entity work.SimpleAndGate(behavioral) port map (C, temp, O);
end architecture;
Let's also write a simple test bench we can use to verify that it's working. Note that the test
bench file must have exactly the same name as the source IP file, plus "_tb"
. So in this case, it will be tb/AwesomeTripleAndGate_tb.vhdl
:
tb/AwesomeTripleAndGate_tb.vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity AwesomeTripleAndGate_tb is
end entity;
architecture test of AwesomeTripleAndGate_tb is
signal A : std_logic;
signal B : std_logic;
signal C : std_logic;
signal O : std_logic;
signal temp : std_logic_vector (2 downto 0) := "000";
begin
T0: entity work.AwesomeTripleAndGate(behavioral) port map (A, B, C, O);
test: process
begin
-- For inputs 000 through 110, output should be zero
for i in 0 to 6 loop
temp <= std_logic_vector(to_unsigned(i, 3));
wait for 1 ns;
A <= temp(0);
B <= temp(1);
C <= temp(2);
wait for 1 ns;
assert O = '0' report "Output is wrong for inputs other than 111" severity error;
end loop;
A <= '1';
B <= '1';
C <= '1';
wait for 1 ns;
assert O = '1' report "Output is wrong for input 111" severity error;
end process;
end architecture;
Once again, very simple: we set up an entity to test, and then we start a test
process,
where we set the inputs of the triple AND
gate to
various values and assert the output, triggering an error if it's incorrect.
Let's make
and see if this whole thing works:
$ shell
$ make
[Compiling `AwesomeTripleAndGate.vhdl` & `AwesomeTripleAndGate_tb.vhdl` ...]
ghdl -s --workdir=build src/AwesomeTripleAndGate.vhdl tb/AwesomeTripleAndGate_tb.vhdl
src/AwesomeTripleAndGate.vhdl:18:23:error: unit "simpleandgate" not found in library "work"
src/AwesomeTripleAndGate.vhdl:19:23:error: unit "simpleandgate" not found in library "work"
make: *** [AwesomeTripleAndGate] Error 1
Whoops, that doesn't seem right. What happened? Well, I named the IPs on purpose, so that the second one would get picked up by the Makefile before the first one. That way, it tries to compile the second IP without first compiling its dependency. The simplest way to fix this is to declare the dependency in the Makefile, by replacing this:
Makefile
# Dependencies
# <my entity>: <my other entity>
With this:
Makefile
# Dependencies
AwesomeTripleAndGate: SimpleAndGate
So now that the dependencies issue is fixed, let's re-run make
:
$ shell
$ make
[Compiling `SimpleAndGate.vhdl` ...]
ghdl -s --workdir=build src/SimpleAndGate.vhdl
ghdl -a --workdir=build src/SimpleAndGate.vhdl
[Compiling `AwesomeTripleAndGate.vhdl` & `AwesomeTripleAndGate_tb.vhdl` ...]
ghdl -s --workdir=build src/AwesomeTripleAndGate.vhdl tb/AwesomeTripleAndGate_tb.vhdl
ghdl -a --workdir=build src/AwesomeTripleAndGate.vhdl tb/AwesomeTripleAndGate_tb.vhdl
ghdl -e --workdir=build -o build/AwesomeTripleAndGate_tb AwesomeTripleAndGate_tb
[Running simulation of `AwesomeTripleAndGate_tb` ...]
ghdl:info: simulation stopped by --stop-time @100ns
[`AwesomeTripleAndGate` PASS]
Now the SimpleAndGate
gets built first, and then when
it's AwesomeTripleAndGate
's turn to be built, it can
find the reference to SimpleAndGate
and build
successfully.
Just to properly test out all of the features of this Makefile, we can open the resulting simulation in GTKWave, by running:
$ shell
$ make simu
This will open up GTKWave and will allow us to visually check the test bench simulation:

And that's it! You can now build, debug and run automated tests using this lightweight VHDL project template.