CS232 Project 7: CPU
The purpose of this project is to build a simple CPU that integrates all the necessary aspects of a general-purpose computer.
This is the second part of three coordinated projects. You should demonstrate the functionality of your CPU at the end of the second project.
The overall task is to create a functional CPU, using a ROM for the program memory, a RAM for the data memory, and a separate ALU circuit.
Examine the CPU Design before starting.
- Make a new Quartus project for project 7 in a new folder. I'll refer to the project and top-level circuit as cpu.
- Using the Tools:MegaWizard Plugin Manager, create a 1-port ROM that has 256 words of 16 bits each. I'll refer to this circuit as ProgramROM. Tell it to initialize the ROM using this MIF file. This step will create a VHDL file that you need to add to your project.
- Using the Tools:MegaWizard Plugin Manager, create a 1-port RAM that has 256 words of 16 bits each. I'll refer to this circuit as DataRAM, Tell it to initialize the RAM using this MIF file. This step will create a VHDL file that you need to add to your project.
Create a top-level VHDL file called cpu.vhd with an entity called cpu.
Your cpu entity should have a clock (std_logic), reset (std_logic),
input port (std_logic_vector(7 downto 0)), and an output port
(std_logic_vector(15 downto 0)). You can use the 4-state Moore
machine template, if you wish.
To use the cpu test bench for simulation, you will also need to add the following signals to your cpu port statement. The port statement that matches the cpubench usage is below.
port ( clk : in std_logic; -- main clock reset : in std_logic; -- reset button PCview : out std_logic_vector( 7 downto 0); -- debugging outputs IRview : out std_logic_vector(15 downto 0); RAview : out std_logic_vector(15 downto 0); RBview : out std_logic_vector(15 downto 0); RCview : out std_logic_vector(15 downto 0); RDview : out std_logic_vector(15 downto 0); REview : out std_logic_vector(15 downto 0); iport : in std_logic_vector(7 downto 0); -- input port oport : out std_logic_vector(15 downto 0)); -- output port
Create component statements for both the ProgramROM and DataRAM in the architecture header.
Create an arithmetic logic unit VHDL design file, alu.vhd. Add the
file to your project. You can use this template
as the basis for the ALU circuit design. The circuit is completely
asynchronous, and it does not require any additioanl signals beyond
what is in the template in order to build the circuit.
When you have your alu design complete, test it using this testbench program. It should produce the output below using ghdl/gtkwave.
ghdl -a alutestbench.vhd alu.vhd
ghdl -e alutestbench
ghdl -r alutestbench --vcd=alutestbench.vcd
As noted in the CPU design, you probably
want to use a 6-state state machine for your CPU. The first state is
a startup state, and the final state is a halt state. After eight
clock cycles, this state should move to the fetch state. You will need
a small internal counter (3 bits) to implement the pause in the start
state. The state machine should never return to the start state unless
it is reset.
The remaining states are fetch, execute-setup, execute-process, and execute-write. Set up your overall state machine structure to follow this model. Each state always leads to the next state in the process, with the execute-write state going back to the fetch state.
Set up your skeleton six state state machine, putting in only the state transitions for now. No state should go to the halt state (yet).
Create the remaining internal signals you need for your CPU. There
are a lot. Following the cpu diagram,
there should be a signal for every register, including the condition
register. In addition, you will need a signal to represent the two
ALU input buses and the ALU output bus.
You will also want signals for the output port wire, the ROM data-out wire, the RAM data-out wire, the RAM write-enable wire, the ALU opcode, and the ALU condition argument.
Port map your ProgramROM, DataRAM, and ALU circuits into your cpu.
The clock, PC and ROM data wire should map to the ROM clock, address,
The MAR and MBR should map to the RAM address and data-in parameters. The RAM data wire, RAM write enable wire, and clock should map to the RAM output, RAM write enable (wren) and clock.
The srcA, srcB, and dest parameters should map to the ALU input buses and the output bus, respectively. Map the ALU opcode and condition outputs to their respective signals.
Implement your CPU.
- If the reset button is activated, set the registers PC, MAR, MBR, RA, RB, RC, RD, RE, SP, and CR to zeros. The state should should be reset to the startup state and the small counter should be reset to zeros.
- The startup state should increment the small counter and stay there until it reaches 7. Then it should move to the fetch state.
- The fetch state should copy the ROM data wire contents to the IR, increment the PC, and move to the execute-setup state.
The execute-setup state should set up each of the instructions.
- For any unary or binary operator that uses the ALU, set the aluopcode to the OP bits from the instruction.
- For the load and store instructions, move the correct RAM address into the MAR. If IR(11) is set, use the 8 low bits of the IR plus register E (RE). If IR(11) is not set, just use the low bits of the IR. You will also need to put the data to write into the RAM into the MBR.
- For the unconditional branch, set the PC to the low 8 bits of the IR.
- For the conditional branch, check the condition flag specified in the instruction and move the low 8 bits of the IR to the PC if the condition is true.
- For the CALL instruction, set the PC to the low 8 bits of the IR, set the MAR to the SP, set the MBR to the concatenation of four zeros, the CR, and the PC. Then increment the stack pointer.
- For the RETURN instruction, set the MAR to the value (SP-1) and decrement the SP. (i.e. the address will refer to what is currently at the top of the stack, and this operation is meant to pop the address off the top of the stack, so you need to decrement the stack pointer for next time).
- For the push operation, put the current vaue of the SP into the MAR, increment the SP, and put the value specified in the source bits into the MBR.
- For the pop operation, put the value (SP-1) into the MAR and decrement the SP.
- For the binary ALU operations (add, sub, and, or xor) set up srcA and srcB.
- For the unary ALU operations (shift, rotate) set up srcA and put the direction bit in the low bit of srcB.
- For the move operation, set up srcA, which will come either from a register or be a sign extended value from the immediate value bits of the IR (bits 10 downto 3).
- The execute-process state should set the RAM write enable signal to high if the operation is a store (opcode 0001, or integer 1), a push, or a CALL.
The execute-write state should handle the final stage of the various
operations. At the beginning of the state, it should set the write
enable flag to '0'.
- For the load operation, it should write the contents of the RAM data wire to the specified destination register.
- The store, unconditional branch, conditional branch, call, and push operations require no action.
- The return operation requires that the proper parts of the MBR be written to the PC and CR.
- For the pop operation, it should write the value of the RAM data wire to the destination specified in the instruction.
- For the write to the output port, it should set the output port wire to the specified value.
- For the load from the input port, it should write the input port value to the specified register.
- For the binary and unary arithmetic operations (add, sub, and, or, xor, shift, rotate), it should write the destination value to the proper register. It should also assign the ALU condition flags to the condition register [CR].
- For the move operation it should write the destination value to the proper register and set the condition register [CR] from the ALU condition flags.
Test your CPU using the program given in the programROM.mif file above and using the cpubench.vhd test file. You should get a simulation like the following. Note that on the second command below you need to make sure the filenames match your own.
ghdl -a --ieee=synopsys -fexplicit --work=altera_mf /opt/altera/12.1/quartus/eda/sim_lib/altera*.vhd
ghdl -a --ieee=synopsys --work=altera_mf cpubench.vhd cpu.vhd alu.vhd programROM.vhd dataRAM.vhd
ghdl -e --ieee=synopsys -fexplicit --work=altera_mf cpubench
ghdl -r cpubench --vcd=cpubench.vcd
gtkwave cpubench.vcd &
- Write a program for your computer that creates the first 10 values of the fibonnaci sequence. Simulate your circuit and demonstrate that it works.
- Download your CPU to the board and demonstrate it works properly.
- Write more programs, such as an unsigned multiply program or a program that includes functions (CALL/RETURN).
- Write a Python program that can generate an MIF file from some other, more easily readable, format.
- Be creative with input.
Create a wiki page with your writeup. For each task, write a short description of the task, in your own words.
- Include a description of your top-level design.
- Include the initial contents of both your RAM and ROM.
- Include the screen shot of your simulation.
- Describe the hardware testing you undertook to prove the circuit works.
- Include a description, and pictures, of any extensions.
Give your wiki page the label cs232s14project7.
Put your VHDL and MIF files in a folder named Proj7 in your private subdirectory on the CS232 server. Please put just the vhdl and mif files on the server.