Welcome

Do you want to create a computer? All by yourself?

Do you want to do it step-by-step, starting with a single bit and ending with a design that can run real programs, written in C?

Do you also want to see how the design can be done using different languages, such as VHDL and Verilog?

Then this might be a book for you. The book will show you, in a step-by-step manner, how a simple computer can be created. While doing so, it will also provide an introduction to how computers work, and how their main parts can be constructed, and put together into a functioning design.

We start with a simple building block that can store one bit, and we end with a computer that can run software that is compiled and linked using gcc.

We choose to implement a subset of a real computer architecture - the OR1K architecture. In this way, we can convey the experience of building a real system, while at the same time - since we choose a suitable small subset - making the task small enough to be completed without a large implementation effort.

Using an already available architecture also allows us to use available tools, such as this gcc with newlib for OR1K.

The book is designed as a Layered Book. This means that there are common parts, covering the general aspects of computer design, but also specific parts, treating layer-specific material. We have chosen the layers as languages. This gives us a possibility to treat the topic of computer design using different languages, but still keeping the material contained in one book.

The book has the following layers.

You are now reading the VHDL layer. The purpose of this layer is to show how VHDL can be used to construct a computer that implements a specific architecture.

The book contains links that let you navigate between the layers. Here is an example:

This is the VHDL layer The other layers are: Verilog SystemC/TLM

You will see these links, e.g. at the beginning or the end of a section, or near a figure showing a piece of code.

Acknowledgements

This book has been produced using pandoc and Python.

The html-version of the book has been styled using this css file from this pandoc demo page.

Choosing a Language

This is the VHDL layer The other layers are: Verilog SystemC/TLM

We choose to describe our computer using a language. In this way, we can have a textual representation of the computer, and we can use the textual representation as input to software tools, that will help us to simulate the behavior of our computer.

VHDL is a hardware description language. We use VHDL to describe our computer, and to simulate its functionality. It is also possible to use VHDL to actually synthesize a computer, in real hardware, for example in an FPGA.

VHDL is standardized by IEEE.

Information about VHDL can be found e.g. from Doulos.

Hello World

This is the VHDL layer The other layers are: Verilog SystemC/TLM

A simple example will get us started. We use a classical "Hello, world'' example, which will do nothing meaningful except printing a text string. The code for the example is shown in Figure 1.

The code for the example is from the GHDL guide. We will start using GHDL in Section Getting some tools.

use std.textio.all;

entity hello_world is
end hello_world;

architecture behavior of hello_world is
begin
  process
    variable the_line: line;
  begin
    write(the_line, String'("Hello, world"));
    writeline(output, the_line);
    wait;
  end process;
end behavior;

Figure 1. A hello world example in VHDL.

The code in Figure 1 starts with a use clause. The purpose of this clause is to indicate which VHDL libraries that will be used. In this case we use one library, with functionality for printing text.

The code in Figure 1 contains an entity. The entity is empty, since we do not have any input ports or output ports.

Then comes the architecture part, where a process is defined.

The process assigns a string to a variable called the_line. The string is then printed, and a call to wait is done, for the purpose of pausing the simulation.

We remark that the code in Figure 1 generates an artificial, simulated behavior. It does not provide any code that can be used for synthesizing actual hardware.

You can read about VHDL in Wikipedia, and at other places. A book called Free Range VHDL is available for free download. You may also want to look at this VHDL Guide from Doulos.

Getting some Tools

This is the VHDL layer The other layers are: Verilog SystemC/TLM

We need some tools, in the form of software. We search for software that can be obtained without cost.

We use a Linux computer with Ubuntu 16.04, and a Mac computer with OS X El Capitan.

We decide to use GHDL.

We can install GHDL in Ubuntu, by building GHDL from source. This requires downloading of source code, and a sequence of build commands. The procedure is described in this blog post about Installing GHDL in Ubuntu 16.04.

We can install GHDL on Mac, by downloading from this GHDL Download page.

We download the ghdl - gcc version for Apple OS X (Mac), which results in download of a zip file named ghdl-0.31-gcc-darwin13.mpkg.zip. The zip file can be unzipped, by clicking on it, and the installation can then be started by "right''-clicking on the file ghdl-0.31-gcc-darwin13.mpkg, and then selecting Open.

Make it Run

This is the VHDL layer The other layers are: Verilog SystemC/TLM

The code in Figure 1 can be compiled and run.

Assuming that the program is stored in a file hello.vhdl, compiling can be done as

ghdl -a hello.vhdl

The above command generates the file hello.o, which can be elaborated, using the command

ghdl -e hello_world

where the string hello_world refers to the name of the entity in Figure 1.

The code can now be run, by doing

ghdl -r hello_world

which results in the printout

Hello, world

Building a Computer

We have chosen a language, to describe our computer. We have taken a first, tiny step, and we have seen how we can get hold of some tools.

Before we take our next steps towards building our computer, let's spend some time talking a bit of what we want to build.

Our goal is to create a computer. A computer reads instructions from a memory. Each instruction is represented as a sequence of bits. The values of the bits determine the type of instruction, and sometimes also arguments that the instruction shall use. The allowed instructions, for a given computer, belong to the computer's instruction set.

Most computers have instructions for loading data from a memory, and storing data to a memory. Other common instructions are instructions for doing mathematical operations, such as addition and subtraction, and instructions for making decisions. The decisions can be based on evaluations of certain conditions, such as checking if a number is zero, or if a certain bit is set in a piece of data.

An instruction that has been read is decoded, meaning that the computer interprets the bits of the instruction, and then, depending on the values of the bits, takes different actions.

The action taken is determined by the instruction. As an example, an instruction for addition results in the actual addition of two numbers, and most often also the storing of the result of the addition.

As we move on, we start with a small building block, that can store only one bit. We then extend the building block, so that we can store larger pieces of information. At a certain stage in our development, we are ready to implement our first instruction.

Storing one Bit

A bit can have the values 0 or 1. In a computer, these values are represented by a low and a high value of an electrical signal.

The value of a bit can be stored. This means that the value is remembered, as long as it is stored. While the value is stored, the value can be read, and used, for the purpose of performing different operations. As an example, a bit could be used in an addition operation, or it could be copied so that it is stored somewhere else, for example at another place in a memory.

A D Flip-flop

This is the VHDL layer The other layers are: Verilog SystemC/TLM

The value of a bit can be stored in a building block called D flip-flop.

A D flip-flop stores one bit of data. A new value can be stored when a clock signal changes value. A component which can change its stored value only when a clock signal changes is called a synchronous component.

A D flip-flop implementation in VHDL is shown in Figure 2.

library ieee;
use ieee.std_logic_1164.all; 

entity d_ff is
  port(
    clk: in std_logic;
    data_in: in std_logic;
    data_out: out std_logic);
  end d_ff;

architecture rtl of d_ff is

  signal reg_value: std_logic;

begin

  update: process(clk)
  begin
    if rising_edge(clk) then
      reg_value <= data_in;
    end if;
  end process; 

  data_out <= reg_value;

end rtl; 

Figure 2. A D flip-flop in VHDL.

The code in Figure 2 starts with a reference to a library. We use the library to get access to a data type called std_logic. Variables of this data type represent binary data.

An entity is then defined. The entity has a port where inputs and outputs are defined. We have two inputs, called clk and data_in, and we have one output, called data_out.

The architecture block, which is called rtl, for register-transfer level, defines a variable called reg_value. The variable reg_value is defined using the keyword signal.

The variable reg_value will contain the actual value stored in the D flip-flop.

The variable reg_value is called a state variable.

A VHDL process called update defines actions to be taken at every rising edge of the clock signal. We see that the only action taken is to assign the value of the input data_in to the state variable reg_value. This assignment ensures that the state variable reg_value is updated at every rising edge of the clock.

An assignment of the variable data_out is done, outside of the process update. This assignment ensures that the output data_out has the same value as the current value of the state variable reg_value.

A Testbench

This is the VHDL layer The other layers are: Verilog SystemC/TLM

The D flip-flop implementation in Figure 2 has inputs and outputs. An external module, referred to as a testbench, can be used for the purpose of generating input signals to the D flip-flop, and observing output signals from the D-flip-flop.

A VHDL testbench is shown in Figure 3.

library ieee; 
use ieee.std_logic_1164.all;

entity d_ff_tb is
end d_ff_tb;

architecture behavior of d_ff_tb is

  component d_ff
    port(
      clk: in std_logic;
      data_in: in std_logic;
      data_out: out std_logic);
  end component;

  signal clk: std_logic := '1';

  constant clk_half_period: time := 2 ns; 
  constant n_clk_cycles: integer := 4; 

  signal d_ff_data_in: std_logic := '1'; 
  signal d_ff_data_out: std_logic;

begin

  d_ff_0: d_ff
    port map(
      clk => clk,
      data_in => d_ff_data_in,
      data_out => d_ff_data_out);

  clk_gen: process is
  begin
    for i in 1 to n_clk_cycles loop
      clk <= '1';
      wait for clk_half_period;
      clk <= '0';
      wait for clk_half_period; 
    end loop;
    wait; 
  end process;

  stim_gen: process is
  begin
    wait for 1 ns;
    d_ff_data_in <= '0';
    wait for 5 ns;
    d_ff_data_in <= '1';
    wait for 3 ns;
    d_ff_data_in <= '0';
    wait;
  end process; 

  reporter: process(clk, d_ff_data_in) is
  begin
    if (rising_edge(clk) or d_ff_data_in'event) then
       report "data_in=" & std_logic'image(d_ff_data_in) & 
              ", data_out=" & std_logic'image(d_ff_data_out);
    end if; 
  end process; 

end behavior; 

Figure 3. A D flip-flop testbench in VHDL.

The testbench in Figure 3 starts with a library reference, followed by a definition of an empty entity. The architecture section defines a signal variable called clk. This variable represents the clock signal. The actual shape of the clock signal is defined in the process named clk_gen, by the lines

      clk <= '1';
      wait for clk_half_period;
      clk <= '0';
      wait for clk_half_period; 

The input signal to the D flip-flop is defined by the signal variable d_ff_data_in. The values used for the input signal are defined in the stim_gen process, as

  stim_gen: process is
  begin
    wait for 1 ns;
    d_ff_data_in <= '0';
    wait for 5 ns;
    d_ff_data_in <= '1';
    wait for 3 ns;
    d_ff_data_in <= '0';
    wait;
  end process; 

The architecture section of the testbench in Figure 3 is named behavior, to indicate that the testbench is a behavioral model. A behavioral model can be used in simulation, but can not be synthesized into a working digital system, for use in e.g. an FPGA or an ASIC.

Build and Run

This is the VHDL layer The other layers are: Verilog SystemC/TLM

The D flip-flop in Figure 2 and the testbench in Figure 3 can be analyzed using

ghdl -a d_ff.vhdl 
ghdl -a d_ff_tb.vhdl 

The combined system, containing the D flip-flop and the testbench, can be elaborated by the command

ghdl -e d_ff_tb

The simulation can be run by giving the command

ghdl -r d_ff_tb

The resulting printout is shown in Figure 4.

d_ff_tb.vhdl:63:8:@1ns:(report note): data_in='0', data_out='U'
d_ff_tb.vhdl:63:8:@4ns:(report note): data_in='0', data_out='U'
d_ff_tb.vhdl:63:8:@6ns:(report note): data_in='1', data_out='0'
d_ff_tb.vhdl:63:8:@8ns:(report note): data_in='1', data_out='0'
d_ff_tb.vhdl:63:8:@9ns:(report note): data_in='0', data_out='1'
d_ff_tb.vhdl:63:8:@12ns:(report note): data_in='0', data_out='1'

Figure 4. Printout from running the testbench in Figure 3.

The printout in Figure 4 shows the values of data_in and data_out for a sequence of time instants. The time instants are defined by an if-statement inside the reporter process in Figure 3, as

  reporter: process(clk, d_ff_data_in) is
  begin
    if (rising_edge(clk) or d_ff_data_in'event) then
       report "data_in=" & std_logic'image(d_ff_data_in) & 
              ", data_out=" & std_logic'image(d_ff_data_out);
    end if; 
  end process; 

with the effect that a printout is done whenever the clock signal has a rising edge, or the variable d_ff_data_in changes value. The changes for the variable d_ff_data_in are defined in the stim_gen process in Figure 3.

Making Waves

This is the VHDL layer The other layers are: Verilog SystemC/TLM

The testbench in Figure 3 generates printouts as shown in Figure 4. The printouts show values of digital signals, each having the value one or zero. We can represent these signals as waveforms, with the level of the waveform being one or zero. Thinking of the value one as a high voltage level, and the value zero as a low voltage level, we can think of the waveforms as representing actual voltages, in an actual digital system.

A waveform can be visualized using the GTKWave program. We can download a GTKWave version for Mac, in the form of a zip-file that contains an executable GTKWave program. The GTKWave program can be started from a Mac Terminal, by giving the command open followed by the app file name of the program. As an example, I could start the program by doing

open /Users/oladahl/prog/gtkwave/gtkwave.app

A GTKWave version for Ubuntu can be installed in Ubuntu, by giving the command

sudo apt-get install gtkwave

The program can then be started by giving the command gtkwave.

A waveform can be generated from VHDL by running the ghdl program with an added command line switch. As a first step, we use ghdl commands as described in Section Build and Run for analysis and elaboration. The command line switch --vcd is then added to the ghdl command for running the simulation, as

ghdl -r d_ff_tb --vcd=d_ff_tb_wave.vcd

Waveforms, generated from the testbench in Figure 3, are shown in Figure 5.

fig_d_ff_tb_wave_vhdl
Figure 5. Waveforms, obtained from running the testbench in Figure 3.

We see in Figure 5 how the waveforms correspond to the printouts shown in Figure 4.

Storing Data in Registers

When a computer executes instructions, it often needs intermediate storage places. Reading instructions from memory, writing results back to memory. For example adding numbers, and writing back only when all numbers have been added. Then registers can be used, to hold the intermediate sum, while the calculation is ongoing. We can refer to such a row using the term register. Another use of registers is for addressing. In this scenario, the value stored in the register is an address, addressing a part of the memory. One such register is holding an address pointing to the next instruction to be executed. This register is referred to as the program counter.

A Register

A D flip-flop can store one bit. We can imagine a register as a row of D flip-flops, each storing one bit, with the possibility to load new values into all D flip-flops simultaneously.

This is the VHDL layer The other layers are: Verilog SystemC/TLM

A register implementation in VHDL is shown in Figure 6.

library ieee;
use ieee.std_logic_1164.all; 

entity n_bit_register is
  generic (N: integer := 8); 
  port(
    clk: in std_logic;
    data_in: in std_logic_vector(N-1 downto 0);
    data_out: out std_logic_vector(N-1 downto 0));
  end n_bit_register;

architecture rtl of n_bit_register is

  signal reg_value: std_logic_vector(N-1 downto 0);

begin

  update: process(clk)
  begin
    if rising_edge(clk) then
      reg_value <= data_in;
    end if;
  end process; 

  data_out <= reg_value;

end rtl; 

Figure 6. A register in VHDL.

The code in Figure 6 defines an entity called n_bit_register. The entity has a port where inputs and outputs are defined. We have two inputs, called clk and data_in, and we have one output, called data_out.

The architecture block defines a variable called reg_value. The variable reg_value will contain the actual value stored in the register.

A VHDL process called update ensures that the state variable reg_value is updated at every rising edge of the clock.

An assignment of the variable data_out is done, outside of the process update. This assignment ensures that the output data_out has the same value as the current value of the state variable reg_value.

A Testbench

An external module, referred to as a testbench, can be used for the purpose of generating input signals to, and observing output signals from, the register in Figure 6.

In the testbench module, we use a parameter, to specify the width of the register.

This is the VHDL layer The other layers are: Verilog SystemC/TLM

The parameter is defined as a VHDL constant, as

  constant N: integer := 4;

The clock signal is generated using a variable named clk, together with two constants, as

  signal clk: std_logic := '0';

  constant clk_half_period: time := 2 ns; 
  constant n_clk_cycles: integer := 5; 

The actual clock generation is done in a VHDL process, as

  clk_gen: process is
  begin
    for i in 1 to n_clk_cycles loop
      clk <= '1';
      wait for clk_half_period;
      clk <= '0';
      wait for clk_half_period; 
    end loop;
    wait; 
  end process;

The generation of input signals to the register in Figure 6 is done using a VHDL process, as

  stim_gen: process(clk) is
  begin
    if (rising_edge(clk)) then
      reg_data_in <= std_logic_vector(
        unsigned(reg_data_in) + 1);
    end if; 
  end process; 

The input signal and the output signal are defined as

  signal reg_data_in: std_logic_vector(N-1 downto 0) :=
    (0 => '1', others => '0'); 
  signal reg_data_out: std_logic_vector(N-1 downto 0);

The signals are used in the instantiation of the register, which is done as

  n_bit_register_0: n_bit_register
    port map(
      clk => clk,
      data_in => reg_data_in,
      data_out => reg_data_out);

The reporting of the results is done in a process, as

  reporter: process(clk) is
  begin
    if (rising_edge(clk)) then
       report "data_in=" &
         reverse_string(std_logic_vector_to_string(reg_data_in)) & 
         ", data_out=" &
         reverse_string(std_logic_vector_to_string(reg_data_out));
    end if; 
  end process; 

Build and Run

The register in Figure 6 and a testbench, with code as shown in in Section A Testbench, can be built and run.

A makefile can be created. The makefile can contain commands for building and running the register and the testbench.

This is the VHDL layer The other layers are: Verilog SystemC/TLM

A makefile is shown in Figure 7.

OBJS := n_bit_register.o n_bit_register_tb.o

n_bit_register_tb: $(OBJS)
    ghdl -e $@

%.o: %.vhdl
    ghdl -a $<

.PHONY: clean

clean: 
    rm n_bit_register_tb $(OBJS) e~n_bit_register_tb.o \
    work-obj93.cf

Figure 7. A makefile for building and running the register in Figure 6.

It can be seen, in the makefile in Figure 7, that the ghdl command is used, in the same way as described in Section Build and Run in Chapter Storing one bit.

Assume the register is stored in a file named n_bit_register.vhdl, and the testbench is stored in a file named n_bit_register_tb.vhdl. Running the makefile, by giving the command make results in printouts, as

$ make
ghdl -a n_bit_register.vhdl
ghdl -a n_bit_register_tb.vhdl
ghdl -e n_bit_register_tb

A script file can be created, and used for running the simulated register and the testbench. Using a script file named run.sh, with contents as

#!/bin/bash

ghdl -r n_bit_register_tb --vcd=n_bit_register_tb_wave_vhdl.vcd

for running the simulation, gives the result as shown in Figure 8.

$ ./run.sh 
n_bit_register_tb.vhdl:110:8:@0ms:(report note): data_in=0001, data_out=UUUU
n_bit_register_tb.vhdl:110:8:@4ns:(report note): data_in=0010, data_out=0001
n_bit_register_tb.vhdl:110:8:@8ns:(report note): data_in=0011, data_out=0010
n_bit_register_tb.vhdl:110:8:@12ns:(report note): data_in=0100, data_out=0011
n_bit_register_tb.vhdl:110:8:@16ns:(report note): data_in=0101, data_out=0100

Figure 8. Printouts from a simulation of the register in Figure 6.

We can generate waveforms, in the same way as described in Section Making Waves. The resulting waveform, for the register with printouts as shown above, is displayed in Figure 9.

fig_n_bit_register_tb_wave_vhdl
Figure 9. Waveforms from a simulation with printouts as shown in Figure 8.

Our First Instruction

A computer executes programs by following instructions. The instructions belong to an an instruction set. As mentioned in Chapter Welcome, we will use a subset of the OR1K instruction set as the instruction set for our computer.

As a first step, we will try to build a computer with only one instruction. Although somewhat restricted, this computer will be able to

We will start with deciding on a program to run our our computer. The program will be stored in a memory, and its instructions will be read, one by one, and actions will be taken.