Appendix A
VHDL
code for 4 bit processor
Introduction
This is intended to be an introduction to working with fpga technology
that does a little more than flash an led on and off as its goal. The software
that was used to do this simulation is free from Xilinx(http://www.xilinx.com/ise/logic_design_prod/webpack.htm). The
package will need 6 Gigabytes of permanent hard drive space and another 4
Gigabytes to temporarily hold the install package. This project worked on a
500Mhz Pentium 3 with 500 Meg of ram. The Linux version will run 15 to 25
percent faster than XP on the same pc
hardware. If you cannot use the Linux version try to use XP as there is
another 10 percent loss in speed with Vista.
The kit that was used to test the design is a low cost board (S3Board
for $99)from Digilent[1].
It is called the S3board and has many peripherals on board. The are 8 leds, 4
seven segment displays, a serial port, push buttons, a pc style keyboard input
and a primative vga output. There are examples of projects using this board on
Utube.
There is another Spartan 3E starter Board for $149 that has additional
features.
This is a single stack processor design. The single stack is used to
hold both data and return addresses for branches and jumps. Inherently, a 4 bit
processor has only 16 cells of memory storage, so the program size will be very
small. The display system, a single seven segment led device is driven by an
instruction, “disp” as a convenience for examining the top of the data stack.
To see this in a test bench, a port signal, “top” is added to the entity. “Top”
can be removed when the fpga is programmed.
The simple program pushes a 9 on the stack, and then displays it. To do
this a new location on the stack must be allocated and the top of stack pointer
“tos” adjusted to point to it. A 5 is then pushed to the stack and displayed.
At this point, another cell is allocated and becomes the new “tos”. The old
“tos” is now the next on stack or “nos”. The add instruction adds the two
values on the stack, stores the result in “nos” which becomes “tos”. The old
“tos” will overwritten when another cell is allocated. After the 5 has been
added to the 9 the result, a hexadecimal E will be displayed on the seven
segment display. A 1 is now pushed onto the stack, overwriting the 9 that was
there. A subtract is performed and the result “d” is displayed. The rest of the
program is halt instructions.
The test bench figure shows this if we look at the “top” waveform. Note
that the display occurs one clock after the data has changed. The “seg” data is
inverted because it is used with common anode displays which are used to make
the “top” signal easier to verify.
When the simulation is working, and it is time to try the hardware out,
we have to add a single step clock to be able to step through the program. The
safest way to do this is with a state machine using two pushbuttons as inputs.
The reset will also cause an initial state
change. The two states could be called “clk_lo” and “clock_hi”. We need
to write the hardware description of this state machine.
If the code were not modified we would not see the changes of the “top”
in the seven segment display as the on-board clock is 50 Mhz and the entire program
would have completed an the final value is all that we would be able to see.
The single step clock will allow us to see the intermediate steps not just the
final result.
The Xilinx Integrated Software
Environment (ISE)[2]
will allow you to import this file to save typing. The waveform is generated
automatically when you choose testbench waveform in the source menu.
Since VHDL is a
modeling language, simulations can be made of objects (a spring, for example)
that cannot be synthesized on silicon. The simulation will not know that the
model cannot be synthesized.
The quickstart manual will guide you through the essential steps and
allow this project to be simulated and synthesized. Note that the
program itself is embedded in the code and does not test all the instructions.
It cannot test all the instructions as there are only 16 nibbles available for
the code. The test other instructions only these 16 lines need to be modified.
-- Hsu_stack.vhd Edward Falat
-- This can be pasted into a file
library IEEE;
use IEEE.STD_LOGIC_1164.all;
-- use IEEE.std_logic.ARITH.all;
-- use ieee.std_logic.unsigned.all;
use ieee.numeric_std.all;
------------------------------------------------------------------------------------------
entity computer is
port (
button :
in bit; --
pushbutton
clock , reset : in bit; --
system clock
top :
out unsigned(3 downto 0):="1111"; --
tb tool
seg :
out unsigned(6 downto 0)); --
seven segment dusplay
end computer; -- entity
------------------------------------------------------------------------------------------
architecture stack of computer is
subtype int4bit is
integer range 0 to 15; -- decimal equivalent of 4 bit binary nums
subtype unsigned4 is unsigned (3 downto 0); --
unsigned 4 bit binary
subtype unsigned7 is unsigned (6 downto 0); -- seven bit binary
type memory is
array (int4bit) of unsigned(3 downto 0);
------------------------------------------------------------------------------------------
function decode_H4b (i:unsigned4)return unsigned7 is
variable
return_value : unsigned7;
begin --
decode_H4b
case (i) is
when
"0000" => return_value := "1000000";
when
"0001" => return_value := "1111001";
when
"0010" => return_value := "0100100";
when "0011"
=> return_value := "0110000";
when
"0100" => return_value := "0011001";
when
"0101" => return_value := "0010010";
when
"0110" => return_value := "0000010";
when
"0111" => return_value := "1111000";
when
"1000" => return_value := "0000000";
when
"1001" => return_value := "0010000";
when
"1010" => return_value := "0001000";
when
"1011" => return_value := "0000011";
when
"1100" => return_value := "1000110";
when
"1101" => return_value := "0100001";
when
"1110" => return_value := "0000100";
when
"1111" => return_value := "0001110";
When
others => return_value := "1111111";
end case;
return (return_value);
end decode_H4b;
---------------------------architecture-----------------------------------------------------
begin -- stack
---------------------------architecture-----------------------------------------------------
main : process is
---------------------------the opcodes and
mnemonics----------------------------------------
constant
add : unsigned4 := "0000"; -- simple add no carry
constant
sub : unsigned4 := "0001"; -- simple subtract no
borrow
constant andl
: unsigned4 := "0010"; --
logical bit and
constant
orl : unsigned4 := "0011"; -- logical bit or
constant comp
: unsigned4 := "0100"; --
complement
constant incr
: unsigned4 := "0101"; --
increment
constant
rsr : unsigned4 := "0110"; -- logical shift
right
constant
asr : unsigned4 := "0111"; -- arithmetic shift
right
constant push
: unsigned4 := "1000"; --
add cell to stack,copied from prog
constant
brz : unsigned4 := "1001"; -- if value of tos =
0, branch
constant
brn : unsigned4 :=
"1010"; -- if
mem(tos)(3)='1'->pc<=a3:a0
constant
jmp : unsigned4 := "1011"; -- pc<=a3:a0
constant
jsr : unsigned4 :=
"1100"; --
tos+=1;mem[tos]:=pc;pc:=a3:a0
constant
rts : unsigned4 :=
"1101"; --
pc=mem[tos];tos-=1
constant disp
: unsigned4 := "1110";
-- display mem[tos]
constant halt
: unsigned4 := "1111";
-- stop program execution
--------This
is the program---------------------------------------------------------------
constant
program : memory := ( push,
"1001", --
$0:$1 push 9
disp, -- $2 display "9"
push,
"0101", -- $3:$4 push 5
disp, -- $5 display "5"
add, -- $6 add
disp, -- $7 disp "E"
push,
"0001", --
$8$9 push 1
disp, -- $A disp "1"
sub, -- #B subtract 1 from E
disp, -- $C display "d"
halt, -- $D
halt, -- $E
halt); -- $F
--------This is the end of
program--------------------------------------------------------
variable
stack : memory; --
the stack where operands ans results are stored
variable
pc,tos : int4bit := 0; -- program
counter, Top Of Stack : pointers
variable nos
: int4bit; --
Next On Stack : pointer
variable insn
: unsigned4; --
Instruction
variable opa
: unsigned4; --
Temporary Operand register
variable
memaddr : int4bit;
-- This is the “clock” that we will have to single
step
begin
wait
until(clock'event and clock='1' and reset='0');
if
button = '1' then pc := 13 ;
end
if; -- if button pressed jumo to halt
insn :=
program(pc) mod 16; -- load
instruction register with opcode
pc := pc+1
mod 16 ; -- advance
rhe program counter to the nexr opcode
case (insn)
is
------------------------two
operands in, single result out-------------------------
when
add => nos := (tos - 1) mod 16; -- since stack grows upward nos is tos -1
stack(nos):=(stack(tos)+stack(nos))mod
16; -- add nos to tos
tos:=nos;
-- move pointer down to sum
when sub
=> nos:=(tos-1) mod 16 ;
stack(nos):=(stack(nos)
- stack(tos))mod 16;
tos:=nos; -- move pointer down to sum
when andl
=> nos:=(tos-1) mod 16;
stack(nos):=(stack(nos)and
stack(tos))mod 16;
tos:=nos; -- move pointer down to sum
when orl
=> nos:=tos-1 ;
stack(nos):=(stack(nos)or
stack(tos))mod 16;
tos:=nos; -- move pointer down to sum
-------------------------single
operand operations------------------------
when comp
=> stack(tos):=not stack(tos);
when incr
=> stack(tos):=(stack(tos)+1)mod 16;
when rsr
=> opa:=stack(tos) ;opa:=opa(0)&opa(3 downto 1);stack(tos):=opa;
when asr
=> opa:=stack(tos);opa:=opa(3)&opa(3 downto 1);stack(tos):=opa;
when push => tos:=tos+1; -- move pointer to next stack
location to be written to
stack(tos):=program(pc); -- copy data from program to top of stack
memory
pc:=pc+1;
-- advance pc past data to next opcode stack grow upward
----------------------branch/jump
operations---------------------------------
when brz
=> if(stack(tos)=0) --
branch on zero
then
pc:=to_integer(program(pc)); -- if true branch to new location
stack(tos):=to_unsigned(pc
+ 1,4);-- store next pc location on stack
else
pc:=pc+1; --
else skip to next opcode
end if;
when brn
=> opa:=stack(tos); -- assume signed
number
if(opa(3)
='0')
then -- branch if negative
pc:=to_integer(program(pc));--
if true branch to new location
stack(tos):=to_unsigned(pc
+ 1,4);-- store next pc location on stack
else
pc:=pc+1;
-- else skip to next opcode
end
if;
when jmp
=> pc := to_integer(program(pc));
when jsr
=> tos := tos+1; --
allocate new nibble on stack
stack(tos):=to_unsigned(pc + 1,4);-- store necr pc location on stack
pc:=to_integer(program(pc)); --
jump to subroutine at programmed location
when rts
=> pc:=to_integer(stack(tos)); --
restore program counter
tos:=tos-1; --
used for return from jump and branch
------------miscellaneous----------------------------------------------------
when disp
=> seg<=decode_H4b(stack(tos)); --
display hexadecimal as 7 segment
when halt
=> pc := pc ; --
stop
when
others => pc := pc; --
stop
end case;
top <= stack(tos); -- this is only for
test bench, not needed for actual processor
end process;
end stack;
