This lesson starts at commit b9d3256d69143ecab22c8b939a4d09dfe6218f72.
1. Setting up the project
In this first session, we'll start from this template, which is nothing but a Vivado project with a single design file, a testbench, and a Makefile to make programming the Mimas A7 a bit easier.
We roughly know what the structure of our processor is going to look like. As discussed before, we will build a 5-stage pipeline with fetch, decode, execute, memory, and register writeback stages. We'll make different modules for these, and to keep things structured nicely we'll create a core folder for all the source files that are used in the core.
Let's start with the first stage, which is the fetch stage. First, we duplicate the top_level module from our template, rename it to fetch, place it in the core folder, clean up the logic and input/output ports, and rename the entity to fetch.
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
library ieee;
|
| 2 |
+
use ieee.std_logic_1164.all;
|
| 3 |
+
use ieee.numeric_std.all;
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
entity fetch is
|
| 7 |
+
port (
|
| 8 |
+
clk: in std_logic
|
| 9 |
+
);
|
| 10 |
+
end fetch;
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
architecture rtl of fetch is
|
| 14 |
+
begin
|
| 15 |
+
|
| 16 |
+
process (clk)
|
| 17 |
+
begin
|
| 18 |
+
if rising_edge(clk) then
|
| 19 |
+
-- TODO: implement
|
| 20 |
+
end if;
|
| 21 |
+
end process;
|
| 22 |
+
|
| 23 |
+
end rtl;
|
In the core, the output of one stage will be used as the input to the next stage. To keep this a bit more manageable, we'll group the in- and outputs in records, which are defined in core/types.vhd.
|
@@ -2,10 +2,13 @@ library ieee;
|
|
| 2 |
use ieee.std_logic_1164.all;
|
| 3 |
use ieee.numeric_std.all;
|
| 4 |
|
|
|
|
|
|
|
| 5 |
|
| 6 |
entity fetch is
|
| 7 |
port (
|
| 8 |
-
clk: in std_logic
|
|
|
|
| 9 |
);
|
| 10 |
end fetch;
|
| 11 |
|
|
|
|
| 2 |
use ieee.std_logic_1164.all;
|
| 3 |
use ieee.numeric_std.all;
|
| 4 |
|
| 5 |
+
use work.core_types.all;
|
| 6 |
+
|
| 7 |
|
| 8 |
entity fetch is
|
| 9 |
port (
|
| 10 |
+
clk: in std_logic;
|
| 11 |
+
output: out fetch_output_t
|
| 12 |
);
|
| 13 |
end fetch;
|
| 14 |
|
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
library ieee;
|
| 2 |
+
use ieee.std_logic_1164.all;
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
package core_types is
|
| 6 |
+
type fetch_output_t is record
|
| 7 |
+
end record fetch_output_t;
|
| 8 |
+
end package core_types;
|
It will also turn out to be useful to create constants for the "default" output of a stage. We'll define this (and possibly other constants that are used in the core) in core/constants.vhd. We'll use the convention of using all-caps for constants.
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
library ieee;
|
| 2 |
+
use ieee.std_logic_1164.all;
|
| 3 |
+
|
| 4 |
+
use work.core_types.all;
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
package core_constants is
|
| 8 |
+
constant DEFAULT_FETCH_OUTPUT: fetch_output_t := ();
|
| 9 |
+
end package core_constants;
|
|
@@ -3,12 +3,13 @@ use ieee.std_logic_1164.all;
|
|
| 3 |
use ieee.numeric_std.all;
|
| 4 |
|
| 5 |
use work.core_types.all;
|
|
|
|
| 6 |
|
| 7 |
|
| 8 |
entity fetch is
|
| 9 |
port (
|
| 10 |
clk: in std_logic;
|
| 11 |
-
output: out fetch_output_t
|
| 12 |
);
|
| 13 |
end fetch;
|
| 14 |
|
|
|
|
| 3 |
use ieee.numeric_std.all;
|
| 4 |
|
| 5 |
use work.core_types.all;
|
| 6 |
+
use work.core_constants.all;
|
| 7 |
|
| 8 |
|
| 9 |
entity fetch is
|
| 10 |
port (
|
| 11 |
clk: in std_logic;
|
| 12 |
+
output: out fetch_output_t := DEFAULT_FETCH_OUTPUT
|
| 13 |
);
|
| 14 |
end fetch;
|
| 15 |
|
We'll now add a placeholder for the next stage, the decode stage. It will be very similar to the placeholder for the fetch stage, so we just copy-paste that for now.
|
@@ -6,4 +6,5 @@ use work.core_types.all;
|
|
| 6 |
|
| 7 |
package core_constants is
|
| 8 |
constant DEFAULT_FETCH_OUTPUT: fetch_output_t := ();
|
|
|
|
| 9 |
end package core_constants;
|
|
|
|
| 6 |
|
| 7 |
package core_constants is
|
| 8 |
constant DEFAULT_FETCH_OUTPUT: fetch_output_t := ();
|
| 9 |
+
constant DEFAULT_DECODE_OUTPUT: decode_output_t := ();
|
| 10 |
end package core_constants;
|
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
library ieee;
|
| 2 |
+
use ieee.std_logic_1164.all;
|
| 3 |
+
use ieee.numeric_std.all;
|
| 4 |
+
|
| 5 |
+
use work.core_types.all;
|
| 6 |
+
use work.core_constants.all;
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
entity decode is
|
| 10 |
+
port (
|
| 11 |
+
clk: in std_logic;
|
| 12 |
+
output: out decode_output_t := DEFAULT_DECODE_OUTPUT
|
| 13 |
+
);
|
| 14 |
+
end decode;
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
architecture rtl of decode is
|
| 18 |
+
begin
|
| 19 |
+
|
| 20 |
+
process (clk)
|
| 21 |
+
begin
|
| 22 |
+
if rising_edge(clk) then
|
| 23 |
+
-- TODO: implement
|
| 24 |
+
end if;
|
| 25 |
+
end process;
|
| 26 |
+
|
| 27 |
+
end rtl;
|
|
@@ -5,4 +5,7 @@ use ieee.std_logic_1164.all;
|
|
| 5 |
package core_types is
|
| 6 |
type fetch_output_t is record
|
| 7 |
end record fetch_output_t;
|
|
|
|
|
|
|
|
|
|
| 8 |
end package core_types;
|
|
|
|
| 5 |
package core_types is
|
| 6 |
type fetch_output_t is record
|
| 7 |
end record fetch_output_t;
|
| 8 |
+
|
| 9 |
+
type decode_output_t is record
|
| 10 |
+
end record decode_output_t;
|
| 11 |
end package core_types;
|
The only difference is that the decode stage is not the first stage, so it takes the input from the previous stage, the fetch stage, as input.
|
@@ -9,6 +9,7 @@ use work.core_constants.all;
|
|
| 9 |
entity decode is
|
| 10 |
port (
|
| 11 |
clk: in std_logic;
|
|
|
|
| 12 |
output: out decode_output_t := DEFAULT_DECODE_OUTPUT
|
| 13 |
);
|
| 14 |
end decode;
|
|
|
|
| 9 |
entity decode is
|
| 10 |
port (
|
| 11 |
clk: in std_logic;
|
| 12 |
+
input: in fetch_output_t;
|
| 13 |
output: out decode_output_t := DEFAULT_DECODE_OUTPUT
|
| 14 |
);
|
| 15 |
end decode;
|
The placeholders for the register read, execute, and memory stages are similar to the one for the decode stage, so we copy-paste and rename the types, constants, and modules.
|
@@ -7,4 +7,6 @@ use work.core_types.all;
|
|
| 7 |
package core_constants is
|
| 8 |
constant DEFAULT_FETCH_OUTPUT: fetch_output_t := ();
|
| 9 |
constant DEFAULT_DECODE_OUTPUT: decode_output_t := ();
|
|
|
|
|
|
|
| 10 |
end package core_constants;
|
|
|
|
| 7 |
package core_constants is
|
| 8 |
constant DEFAULT_FETCH_OUTPUT: fetch_output_t := ();
|
| 9 |
constant DEFAULT_DECODE_OUTPUT: decode_output_t := ();
|
| 10 |
+
constant DEFAULT_EXECUTE_OUTPUT: execute_output_t := ();
|
| 11 |
+
constant DEFAULT_MEMORY_OUTPUT: memory_output_t := ();
|
| 12 |
end package core_constants;
|
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
library ieee;
|
| 2 |
+
use ieee.std_logic_1164.all;
|
| 3 |
+
use ieee.numeric_std.all;
|
| 4 |
+
|
| 5 |
+
use work.core_types.all;
|
| 6 |
+
use work.core_constants.all;
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
entity execute is
|
| 10 |
+
port (
|
| 11 |
+
clk: in std_logic;
|
| 12 |
+
input: in decode_output_t;
|
| 13 |
+
output: out execute_output_t := DEFAULT_EXECUTE_OUTPUT
|
| 14 |
+
);
|
| 15 |
+
end execute;
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
architecture rtl of execute is
|
| 19 |
+
begin
|
| 20 |
+
|
| 21 |
+
process (clk)
|
| 22 |
+
begin
|
| 23 |
+
if rising_edge(clk) then
|
| 24 |
+
-- TODO: implement
|
| 25 |
+
end if;
|
| 26 |
+
end process;
|
| 27 |
+
|
| 28 |
+
end rtl;
|
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
library ieee;
|
| 2 |
+
use ieee.std_logic_1164.all;
|
| 3 |
+
use ieee.numeric_std.all;
|
| 4 |
+
|
| 5 |
+
use work.core_types.all;
|
| 6 |
+
use work.core_constants.all;
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
entity memory is
|
| 10 |
+
port (
|
| 11 |
+
clk: in std_logic;
|
| 12 |
+
input: in execute_output_t;
|
| 13 |
+
output: out memory_output_t := DEFAULT_MEMORY_OUTPUT
|
| 14 |
+
);
|
| 15 |
+
end memory;
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
architecture rtl of memory is
|
| 19 |
+
begin
|
| 20 |
+
|
| 21 |
+
process (clk)
|
| 22 |
+
begin
|
| 23 |
+
if rising_edge(clk) then
|
| 24 |
+
-- TODO: implement
|
| 25 |
+
end if;
|
| 26 |
+
end process;
|
| 27 |
+
|
| 28 |
+
end rtl;
|
|
@@ -8,4 +8,10 @@ package core_types is
|
|
| 8 |
|
| 9 |
type decode_output_t is record
|
| 10 |
end record decode_output_t;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
end package core_types;
|
|
|
|
| 8 |
|
| 9 |
type decode_output_t is record
|
| 10 |
end record decode_output_t;
|
| 11 |
+
|
| 12 |
+
type execute_output_t is record
|
| 13 |
+
end record execute_output_t;
|
| 14 |
+
|
| 15 |
+
type memory_output_t is record
|
| 16 |
+
end record memory_output_t;
|
| 17 |
end package core_types;
|
The register write stage is similar again, but since it is the last stage it does not have output.
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
library ieee;
|
| 2 |
+
use ieee.std_logic_1164.all;
|
| 3 |
+
use ieee.numeric_std.all;
|
| 4 |
+
|
| 5 |
+
use work.core_types.all;
|
| 6 |
+
use work.core_constants.all;
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
entity write is
|
| 10 |
+
port (
|
| 11 |
+
clk: in std_logic;
|
| 12 |
+
input: in memory_output_t
|
| 13 |
+
);
|
| 14 |
+
end write;
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
architecture rtl of write is
|
| 18 |
+
begin
|
| 19 |
+
|
| 20 |
+
process (clk)
|
| 21 |
+
begin
|
| 22 |
+
if rising_edge(clk) then
|
| 23 |
+
-- TODO: implement
|
| 24 |
+
end if;
|
| 25 |
+
end process;
|
| 26 |
+
|
| 27 |
+
end rtl;
|
Now, we're ready to make a module for our core. For now, it just takes the output from modules and passes it to the next module.
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
library ieee;
|
| 2 |
+
use ieee.std_logic_1164.all;
|
| 3 |
+
use ieee.numeric_std.all;
|
| 4 |
+
|
| 5 |
+
use work.core_types.all;
|
| 6 |
+
use work.core_constants.all;
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
entity core is
|
| 10 |
+
port (
|
| 11 |
+
clk: in std_logic
|
| 12 |
+
);
|
| 13 |
+
end core;
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
architecture rtl of core is
|
| 17 |
+
signal fetch_output: fetch_output_t;
|
| 18 |
+
signal decode_output: decode_output_t;
|
| 19 |
+
signal execute_output: execute_output_t;
|
| 20 |
+
signal memory_output: memory_output_t;
|
| 21 |
+
|
| 22 |
+
component fetch is
|
| 23 |
+
port (
|
| 24 |
+
clk: in std_logic;
|
| 25 |
+
output: out fetch_output_t
|
| 26 |
+
);
|
| 27 |
+
end component;
|
| 28 |
+
|
| 29 |
+
component decode is
|
| 30 |
+
port (
|
| 31 |
+
clk: in std_logic;
|
| 32 |
+
input: in fetch_output_t;
|
| 33 |
+
output: out decode_output_t
|
| 34 |
+
);
|
| 35 |
+
end component;
|
| 36 |
+
|
| 37 |
+
component execute is
|
| 38 |
+
port (
|
| 39 |
+
clk: in std_logic;
|
| 40 |
+
input: in decode_output_t;
|
| 41 |
+
output: out execute_output_t
|
| 42 |
+
);
|
| 43 |
+
end component;
|
| 44 |
+
|
| 45 |
+
component memory is
|
| 46 |
+
port (
|
| 47 |
+
clk: in std_logic;
|
| 48 |
+
input: in execute_output_t;
|
| 49 |
+
output: out memory_output_t
|
| 50 |
+
);
|
| 51 |
+
end component;
|
| 52 |
+
|
| 53 |
+
component write is
|
| 54 |
+
port (
|
| 55 |
+
clk: in std_logic;
|
| 56 |
+
input: in memory_output_t
|
| 57 |
+
);
|
| 58 |
+
end component;
|
| 59 |
+
|
| 60 |
+
begin
|
| 61 |
+
fetch_inst: fetch port map(clk => clk, output => fetch_output);
|
| 62 |
+
|
| 63 |
+
decode_inst: decode port map(clk => clk, input => fetch_output, output => decode_output);
|
| 64 |
+
|
| 65 |
+
execute_inst: execute port map(clk => clk, input => decode_output, output => execute_output);
|
| 66 |
+
|
| 67 |
+
memory_inst: memory port map(clk => clk, input => execute_output, output => memory_output);
|
| 68 |
+
|
| 69 |
+
write_inst: write port map(clk => clk, input => memory_output);
|
| 70 |
+
|
| 71 |
+
end rtl;
|
Now, we create a simple testbench for the core module.
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
library ieee;
|
| 2 |
+
use ieee.std_logic_1164.all;
|
| 3 |
+
use ieee.numeric_std.all;
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
entity core_tb is
|
| 7 |
+
end core_tb;
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
architecture behavioral of core_tb is
|
| 11 |
+
constant clk_period: time := 10 ns;
|
| 12 |
+
signal clk: std_logic := '1';
|
| 13 |
+
|
| 14 |
+
component core is
|
| 15 |
+
port (
|
| 16 |
+
clk: in std_logic
|
| 17 |
+
);
|
| 18 |
+
end component;
|
| 19 |
+
|
| 20 |
+
begin
|
| 21 |
+
clk_process :process
|
| 22 |
+
begin
|
| 23 |
+
clk <= '1';
|
| 24 |
+
wait for clk_period / 2;
|
| 25 |
+
clk <= '0';
|
| 26 |
+
wait for clk_period / 2;
|
| 27 |
+
end process;
|
| 28 |
+
|
| 29 |
+
core_inst: core port map(clk => clk);
|
| 30 |
+
|
| 31 |
+
end behavioral;
|
When we add all the files in Vivado and try to simulate the core, we get a wonderful error message about how empty records are not supported... Sigh. OK. Let's add some placeholders then.
|
@@ -5,8 +5,19 @@ use work.core_types.all;
|
|
| 5 |
|
| 6 |
|
| 7 |
package core_constants is
|
| 8 |
-
constant DEFAULT_FETCH_OUTPUT: fetch_output_t := (
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
end package core_constants;
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
package core_constants is
|
| 8 |
+
constant DEFAULT_FETCH_OUTPUT: fetch_output_t := (
|
| 9 |
+
placeholder => '0'
|
| 10 |
+
);
|
| 11 |
+
|
| 12 |
+
constant DEFAULT_DECODE_OUTPUT: decode_output_t := (
|
| 13 |
+
placeholder => '0'
|
| 14 |
+
);
|
| 15 |
+
|
| 16 |
+
constant DEFAULT_EXECUTE_OUTPUT: execute_output_t := (
|
| 17 |
+
placeholder => '0'
|
| 18 |
+
);
|
| 19 |
+
|
| 20 |
+
constant DEFAULT_MEMORY_OUTPUT: memory_output_t := (
|
| 21 |
+
placeholder => '0'
|
| 22 |
+
);
|
| 23 |
end package core_constants;
|
|
@@ -4,14 +4,18 @@ use ieee.std_logic_1164.all;
|
|
| 4 |
|
| 5 |
package core_types is
|
| 6 |
type fetch_output_t is record
|
|
|
|
| 7 |
end record fetch_output_t;
|
| 8 |
|
| 9 |
type decode_output_t is record
|
|
|
|
| 10 |
end record decode_output_t;
|
| 11 |
|
| 12 |
type execute_output_t is record
|
|
|
|
| 13 |
end record execute_output_t;
|
| 14 |
|
| 15 |
type memory_output_t is record
|
|
|
|
| 16 |
end record memory_output_t;
|
| 17 |
end package core_types;
|
|
|
|
| 4 |
|
| 5 |
package core_types is
|
| 6 |
type fetch_output_t is record
|
| 7 |
+
placeholder: std_logic;
|
| 8 |
end record fetch_output_t;
|
| 9 |
|
| 10 |
type decode_output_t is record
|
| 11 |
+
placeholder: std_logic;
|
| 12 |
end record decode_output_t;
|
| 13 |
|
| 14 |
type execute_output_t is record
|
| 15 |
+
placeholder: std_logic;
|
| 16 |
end record execute_output_t;
|
| 17 |
|
| 18 |
type memory_output_t is record
|
| 19 |
+
placeholder: std_logic;
|
| 20 |
end record memory_output_t;
|
| 21 |
end package core_types;
|
Now, when we try to simulate core_tb again, it works. Of course, it does nothing, but it works, and we're ready to start working on the first CPU stage.