VHDL Implementation of a Microcoded CPU
(c) 2015 Warren Toomey, GPL3 Licence
Version
1.2, 4 July 2015. Merged some of the code from the Nexys4 version.
1.1, 14 June 2015. Some tidying up done. More testing code needed.
1.0, 13 June 2015. First version. Needs a fair bit of tidying up.
Download this file:
Introduction
This is a VHDL
implementation of my microcoded
CPU which was originally written for Logisim. You can find the
Logisim version of the CPU and a detailed description of how it works
there. This VHDL version is a reasonably faithful implementation of
the CPU with a few minor differences which are noted at the end.
Getting GHDL
The VHDL code has
been written to work with GHDL in a Linux environment. Here is how
you can get GHDL installed on your Ubuntu Linux box.
GHDL is not in the
mainstream Ubuntu pakage set, so you need to add a PPA to your Ubuntu
system, update your packages and finally install GHDL:
% sudo add-apt-repository ppa:pgavin/ghdl
% sudo apt-get update
% sudo apt-get install ghdl
% sudo apt-get install build-essential minicom gtkwave
The build_essential package is needed as we will be compiling some C code. The minicom
package is needed as we have to connect to the CPU's serial port. The
gtkwave package is useful if you want to examine the waveforms
of the control and data lines at the end of each CPU simulation.
Running the Microcoded CPU
Assuming that you
have unpacked the tarball with the CPU source, you will find a
Makefile, a pile of *.vhd files (the CPU source), the full_microcode
source to the CPU, a microcode assember uassem, a high-level
assembler massem and an example assembly program
basic_program.s.
At the shell prompt,
run make to build and execute the CPU in GHDL:
% make
ghdl -a --ieee=synopsys regfile.vhd # Build the VHDL source
...
gcc -c ghdl_pty.c # Build the UART code
./uassem full_microcode # Assemble the microcode
./massem basic_program.s # Assemble example program
./cpu --vcd=test.vcd # Simulate the CPU
Psudoterminal: /dev/pts/9 # This is the tty to use
<a few warning messages>
cpu.vhd: ... CPU simulation finished # CPU stops after a certain
./cpu:error: report failed # number of cycles. This
./cpu:error: simulation failed # is done by aborting the
make: [runcpu] Error 1 (ignored) # simulation.
The CPU produces two
outputs. One is the file test.vcd which holds the waveforms of
the CPU simulation. You can look at the contents of this file with
the gtkwave tool:
% gtkwave test.vcd
You will have to read up on how to use gtkwave!
Using the CPU's Serial Port
The second output
from the CPU is through a simulated serial port. When you run the CPU
with the make command, and it gets to the ./cpu
–vcd=test.vcd command, the cpu executable starts up,
opens up a pseudoterminal, tells you which one it is (e.g.
/dev/pts/9), waits for two seconds and then runs the assembly
program.
In a separate
window, run:
% minicom -p /dev/pts/9
to connect to the
/dev/pts/9 pseudoterminal. You have to do this in the two
seconds that the program waits, otherwise the pseudoterminal does not
exist. Luckily, once minicom connects to the pseudoterminal,
it will keep the pseudoterminal open and you can run the CPU (with
make) over and over again.
The example program
prints out a string, two numbers in decimal, adds the numbers
together and prints out the sum. With minicom attached to the
CPU's pseudoterminal, you should see this output:
Here are two numbers followed by their sum:
3142
23
3165
Read through web
page for the Logisim
version of the CPU to understand how the CPU works. Read through
the full_microcode and the list_of_instructions files
to see the CPU's microcode. Read through the basic_program.s
to see an example program for the CPU. If something doesn't make
sense, send me some e-mail. My address is at the bottom of this page.
Logisim and VHDL Differences
Here is a list of
the main differences between the original
Logisim version and this VHDL implementation.
The Logisim
version has 64K of RAM. The VHDL version has 16K of ROM at location
0x0000, 16K of RAM at location 0x4000, nothing at the 16K segment
starting at 0x8000, and the UART is mapped into a few locations
starting at 0xC000. Read through memory.vhd for more details.
In the
register file reg.vhd, there are OR gates that connect the
demuxers to the register load lines. This is because a register can
be both the dreg and the sreg at the same time, and we
may be writing the dreg and not writing the sreg. The
OR gates ensure that a write on one d/s/t-reg overwrites a non-write
to the same underlying register.
The ALU uses
the GHDL built-in arithmetic operations + - * / and rem. /
and rem abort the simulation if the second operand is zero.
There is some extra logic to stop the second operand from ever being
zero in this situation.
There are a
few extra microinstructions added to the CPU, and the high-level
assembler has a few pseudo-directives to generate ASCII strings and
data values.
Cheers, Warren
Toomey, wkt (at) tuhs (dot) org