Complex sequential systems
7.5 VHDL model of a simple microprocessor
The following VHDL units model the microprocessor described in the previous section.
The entire model, including a basic testbench, runs to around 320 lines of code. The model is synthesizable (with one minor modification) and so could be implemented on an FPGA.
The first unit is a set of definitions, contained in a package. The definitions in the package are public and may be used in any unit that references the package. The opcodes are defined as an enumerated type. Bit patterns corresponding to the opcodes are defined in the package body, which is private. Therefore the package contains two conversion functions for translating between the abstract opcodes and the bit patterns:
slv2opand op2slv. The size of the bus and the number of bits in the opcode are defined by constants. The use of this package means that the size of the CPU and the actual opcodes can be changed without altering any other part of the model. This is important in maintaining the modularity of the design.
168 Complex sequential systems
Figure 7.13 Modification of ASM chart to include branching.
s7
type opcode is (load, store, add, sub, bne);
constant word_w : NATURAL := 8;-- no. of bits for bus constant op_w : NATURAL := 3;-- no. of bits for opcode constant rfill : std_logic_vector(op_w - 1 downto 0)
:= (others => '0');
-- padding for address
function slv2op (slv : in std_logic_vector) return opcode;
function op2slv (op : in opcode) return std_logic_vector;
end package cpu_defs;
package body cpu_defs is
type optable is array (opcode)
of std_logic_vector(op_w – 1 downto 0);
constant trans_table : optable
:= ("000", "001", "010", "011", "100");
function op2slv (op : in opcode) return std_logic_vector is begin
return trans_table(op);
end function op2slv;
function slv2op (slv : in std_logic_vector) return opcode is
variable transop : opcode;
VHDL model of a simple microprocessor 169
begin
-- This is how it should be done, but some synthesis -- tools may not support this.
for i in opcode loop
if slv = trans_table(i) then transop := i;
end if;
end loop;
-- This is a less elegant method! If the definitions -- of opcode and/or trans_table are changed, this -- code also has to be changed. There is therefore -- potential for inconsistency.
-- case slv is
-- when "000" => transop := load;
-- when "001" => transop := store;
-- when "010" => transop := add;
-- when "011" => transop := sub;
-- when "100" => transop := bne;
-- when others => null;
-- end case;
return transop;
end function slv2op;
end package body cpu_defs;
The controller or sequencer is described above by an ASM chart. Therefore the VHDL description also takes the form of a state machine. The inputs to the state machine are the clock, reset, an opcode and the zero flag from the accumulator.
The outputs are the control signals of Table 7.1. Notice that the two-process model is used. Notice, too, that all the output signals are given a default value at the start of the next state and output logic process.
library IEEE;
use IEEE.std_logic_1164.all;
use WORK.cpu_defs.all;
entity sequencer is
port (clock, reset, z_flag : in std_logic;
op : in opcode;
ACC_bus, load_ACC, PC_bus, load_PC, load_IR, load_MAR, MDR_bus, load_MDR, ALU_ACC, ALU_add, ALU_sub, INC_PC, Addr_bus, CS, R_NW : out std_logic);
end entity sequencer;
architecture rtl of sequencer is
type state is (s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10);
signal present_state, next_state : state;
begin
seq : process (clock, reset) is
170 Complex sequential systems
com : process (present_state, op, z_flag) is begin
-- reset all the control signals to default ACC_bus <= '0';
VHDL model of a simple microprocessor 171
172 Complex sequential systems
The datapath side of the design, as shown in Figure 7.10, has been described in four parts. Each of these parts is similar to the type of sequential building block described in the last chapter. The system bus is described as a bidirectional port in each of the following four modules. A concurrent assignment sets a high impedance state onto the bus unless the appropriate output enable signal is set. In the port declarations, sysbushas inout mode. The first module models the ALU and Accumulator.
library IEEE;
use IEEE.std_logic_1164.all, IEEE.numeric_std.all;
use WORK.cpu_defs.all;
entity ALU is
port (clock, reset : in std_logic;
ACC_bus, load_ACC, ALU_ACC, ALU_add, ALU_sub : in std_logic;
sysbus : inout std_logic_vector (word_w – 1 downto 0);
z_flag : out std_logic);
end entity ALU;
architecture rtl of ALU is
signal acc : unsigned(word_w – 1 downto 0);
constant zero : unsigned(word_w – 1 downto 0) :=
(others => '0');
begin
sysbus <= std_logic_vector(acc) when ACC_bus = '1' else (others => 'Z');
z_flag <= '1' when acc = zero else '0';
process (clock, reset) is begin
if reset = '1' then
acc <= (others => '0');
elsif rising_edge(clock) then if load_ACC = '1' then
if ALU_ACC = '1' then if ALU_add = '1' then
acc <= acc + unsigned(sysbus);
elsif ALU_sub = '1' then
acc <= acc – unsigned(sysbus);
end if;
else
acc <= unsigned(sysbus);
end if;
end if;
end if;
end process;
end architecture rtl;
VHDL model of a simple microprocessor 173
The program counter is similar in structure to the ALU and Accumulator.
library IEEE;
use IEEE.std_logic_1164.all, IEEE.numeric_std.all;
use WORK.cpu_defs.all;
entity PC is
port (clock, reset : in std_logic;
PC_bus, load_PC, INC_PC : in std_logic;
sysbus : inout std_logic_vector
(word_w – 1 downto 0));
end entity PC;
architecture rtl of PC is
signal count : unsigned(word_w – op_w – 1 downto 0);
begin
sysbus <= rfill & std_logic_vector(count)
when PC_bus = '1' else (others => 'Z');
process (clock, reset) is begin
if reset = '1' then
count <= (others => '0');
elsif rising_edge(clock) then if load_PC = '1' then
if INC_PC = '1' then count <= count + 1;
else
count <= unsigned(
sysbus(word_w – op_w – 1 downto 0));
end if;
end if;
end if;
end process;
end architecture rtl;
The instruction register is basically an enabled register. The opcode is decoded for input to the sequencer.
library IEEE;
use IEEE.std_logic_1164.all;
use WORK.cpu_defs.all;
entity IR is
port (clock, reset : in std_logic;
Addr_bus, load_IR : in std_logic;
op : out opcode;
sysbus : inout std_logic_vector
(word_w – 1 downto 0));
end entity IR;
174 Complex sequential systems
architecture rtl of IR is
signal instr_reg: std_logic_vector(word_w – 1 downto 0);
begin
sysbus <= rfill & instr_reg(word_w – op_w – 1 downto 0) when Addr_bus = '1' else (others => 'Z');
op <= slv2op(instr_reg(word_w – 1 downto word_w – op_w));
process (clock, reset) is begin
if reset = '1' then
instr_reg <= (others => '0');
elsif rising_edge(clock) then if load_IR = '1' then
instr_reg <= sysbus;
end if;
end if;
end process;
end architecture rtl;
The memory module is, again, very similar to the static RAM of the last chapter.
A short program has been loaded in the RAM, using constant declarations.
library IEEE;
use IEEE.std_logic_1164.all, IEEE.numeric_std.all;
use WORK.cpu_defs.all;
entity RAM is
port (clock, reset : in std_logic;
MDR_bus, load_MDR, load_MAR, CS, R_NW : in std_logic;
sysbus : inout std_logic_vector
(word_w – 1 downto 0));
end entity RAM;
architecture rtl of RAM is
signal mdr : std_logic_vector(word_w – 1 downto 0);
signal mar : unsigned(word_w – op_w – 1 downto 0);
begin
sysbus <= mdr when MDR_bus = '1' else (others => 'Z');
process (clock, reset) is
type mem_array is array (0 to 2**(word_w – op_w) – 1) of std_logic_vector(word_w – 1 downto 0);
variable mem : mem_array;
constant prog : mem_array := ( 0 => op2slv(load) &
std_logic_vector(to_unsigned(4, word_w – op_w)), 1 => op2slv(add) &
std_logic_vector(to_unsigned(5, word_w – op_w)),
VHDL model of a simple microprocessor 175
2 => op2slv(store) &
std_logic_vector(to_unsigned(6, word_w – op_w)), 3 => op2slv(bne) &
std_logic_vector(to_unsigned(7, word_w – op_w)), 4 => std_logic_vector(to_unsigned(2, word_w)), 5 => std_logic_vector(to_unsigned(2, word_w)), others => (others => '0'));
begin
if reset = '1' then
mdr <= (others => '0');
mar <= (others => '0');
mem := prog;
elsif rising_edge(clock) then if load_MAR = '1' then
mar <= unsigned(sysbus(word_w – op_w – 1 downto 0));
elsif load_MDR = '1' then mdr <= sysbus;
elsif CS = '1' then if R_NW = '1' then
mdr <= mem(to_integer(mar));
else
mem(to_integer(mar)) := mdr;
end if;
end if;
end if;
end process;
end architecture rtl;
The various parts of the microprocessor can now be pulled together by instantiating them as components.
library IEEE;
use IEEE.std_logic_1164.all;
use WORK.cpu_defs.all;
entity CPU is
port (clock, reset : in std_logic;
sysbus : inout std_logic_vector
(word_w – 1 downto 0));
end entity CPU;
architecture top of CPU is
signal ACC_bus, load_ACC, PC_bus, load_PC, load_IR, load_MAR, MDR_bus, load_MDR, ALU_ACC, ALU_add, ALU_sub, INC_PC, Addr_bus, CS, R_NW, z_flag : std_logic;
signal op : opcode;
176 Complex sequential systems
begin
s1: entity WORK.sequencer port map (clock, reset,
z_flag, op, ACC_bus, load_ACC, PC_bus, load_PC, load_IR, load_MAR, MDR_bus, load_MDR, ALU_ACC, ALU_add, ALU_sub, INC_PC, Addr_bus, CS, R_NW);
i1: entity WORK.IR port map (clock, reset, Addr_bus, load_IR, op, sysbus);
p1: entity WORK.PC port map (clock, reset, PC_bus, load_PC, INC_PC, sysbus);
a1: entity WORK.ALU port map (clock, reset, ACC_bus, load_ACC, ALU_ACC, ALU_add, ALU_sub, sysbus, z_flag);
r1: entity WORK.RAM port map (clock, reset, MDR_bus, load_MDR, load_MAR, CS, R_NW, sysbus);
end architecture top;
The following piece of VHDL generates a clock and reset signal to allow the program defined in the RAM module to be executed. Obviously, this testbench would not be synthesized.
library IEEE;
use IEEE.std_logic_1164.all;
use WORK.cpu_defs.all;
entity testcpu is end entity testcpu;
architecture tb of testcpu is
signal clock, reset : std_logic := '0';
signal sysbus : std_logic_vector(word_w – 1 downto 0);
begin
c1 : entity WORK.CPU port map (clock, reset, sysbus);
reset <= '1' after 1 ns, '0' after 2 ns;
clock <= not clock after 10 ns;
end architecture tb;
Summary
In this chapter we have looked at linked ASM charts and at splitting a design between a controller, which is designed using formal sequential design methods, and a datapath that consists of standard building blocks. The example of a simple CPU has been used to illustrate this partitioning. The VHDL model can be both simulated and synthesized.
Exercises 177
Further reading
Formal techniques exist for partitioning state machines. These are described in Unger.
The controller/datapath model is used in a number of high-level synthesis tools; see, for example, de Micheli. The CPU model is based on an example in Maccabe.
Exercises
7.1 Any synchronous sequential system can be described by a single ASM chart. Why