This project is a basic 8-bit CPU design building off the SAP-1. It is a combination of various modules developed as a part of the ECE298A Course at the University of Waterloo.
The control block is implemented using a 6 stage sequential counter for sequencing micro-instructions, and a LUT for corresponding op-code to operation(s).
The program counter enumerates all values between 0 and F (15) before looping back to 0 and starting again. The counter will clear back to 0 whenever the chip is reset.
The Instruction register stores the current instructions and breaks it up into the opcode and address, which are passed into corresponding locations
The 16 Byte memory module consists of 16 memory locations that store 1 byte each. The memory allows for both read and write operations, controlled by input signals, as well as data supplied by the MAR.
The MAR is a register which handles RAM interactions, namely specifying the address for store/load, as well as the data to be stored.
The 8-bit ripple carry adder assumes 2s complement inputs and thus supports addition and subtraction. It pushes the result to the bus via tri-state buffer. It also includes a zero flag and a carry flag to support conditional operation using an external microcontroller. These flags are synchronized to the rising edge of the clock and are updated when the adder outputs to the bus.
The Accumulator register functions to store the output of the adder. It is synchronized to the positive edge of the clock. The accumulator loads and outputs its value from the bus and is connected via tri-state buffer. The accumulator’s current value is always available as an output (and usually connected to the Register A input of the ALU)
The B register stores the second operand for ALU operations which is loaded from RAM.
The Output register outputs the value from register A onto the uo_out pins.
The 8 Bit Bus is driven by various blocks. We allow multiple blocks that are able to write using tri-state buffers.
Mnemonic | Opcode | Function |
---|---|---|
HLT | 0x0 | Stop processing |
NOP | 0x1 | No operation |
ADD {address} | 0x2 | Add B register to A register, leaving result in A |
SUB {address} | 0x3 | Subtract B register from A register, leaving result in A |
LDA {address} | 0x4 | Put RAM data at {address} into A register |
OUT | 0x5 | Put A register data into Output register and display |
STA {address} | 0x6 | Store A register data in RAM at {address} |
JMP {address} | 0x7 | Change PC to {address} |
Control Signal | Array | Component | Function |
---|---|---|---|
Cp | 14 | PC | Increments the PC by 1 |
Ep | 13 | PC | Enable signal for PC to drive the bus |
Lp | 12 | PC | Tells PC to load value from the bus |
nLma | 11 | MAR | Tells MAR when to load address from the bus |
nLmd | 10 | MAR | Tells MAR when to load memory from the bus |
nCE | 9 | RAM | Enable signal for RAM to drive the bus |
nLr | 8 | RAM | Tells RAM when to load memory from the MAR |
nLi | 7 | IR | Tells IR when to load instruction from the bus |
nEi | 6 | IR | Enable signal for IR to drive the bus |
nLa | 5 | A Reg | Tells A register to load data from the bus |
Ea | 4 | A Reg | Enable signal for A register to drive the bus |
Su | 3 | ALU | Activate subtractor instead of adder |
Eu | 2 | ALU | Enable signal for Adder/Subtractor to drive the bus |
nLb | 1 | B Reg | Tells B register to load data from the bus |
nLo | 0 | Output Reg | Tells Output register to load data from the bus |
Stage | HLT | NOP | STA | JMP |
---|---|---|---|---|
T0 | Ep, nLma | Ep, nLma | Ep, nLma | Ep, nLma |
T1 | Cp | Cp | Cp | Cp |
T2 | nCE, nLi | nCE, nLi | nCE, nLi | nCE, nLi |
T3 | ** | - | nEi, nLma | nEi, Lp |
T4 | - | - | Ea, nLmd | |
T5 | - | - | nLr |
Stage | LDA | ADD | SUB | OUT |
---|---|---|---|---|
T0 | Ep, nLma | Ep, nLma | Ep, nLma | Ep, nLma |
T1 | Cp | Cp | Cp | Cp |
T2 | nCE, nLi | nCE, nLi | nCE, nLi | nCE, nLi |
T3 | nEi, nLma | nEi, nLma | nEi, nLma | Ea, nLo |
T4 | nCE, nLa | nCE, nLb | nCE, nLb | - |
T5 | - | Eu, nLa | Su, Eu, nLa | - |
Stage | Control Signals | Programmer specific signals |
---|---|---|
T0 | Ep, nLMA | ready = 1 |
T1 | Cp | ready = 0 |
T2 | - | - |
T3 | nLmd | read_ui_in = 1 |
T4 | nLr | read_ui_in = 0, done_load = 1 |
T5 | - | done_load = 0 |
T0: Control Signals the same as the typical default microinstruction \– load the MAR with the address of the next instruction. Assert ready signal to alert MCU programmer (off chip) that CPU is ready to accept next line of RAM data.
T1: Increment the PC, the same as the typical default microinstruction. De-assert ready signal since the MCU programmer is polling for the rising edge.
T2: Do nothing to allow an entire clock cycle for programmer to prepare the data.
T3: Load the MAR with the data from the bus. Also, assert the read_ui_in signal which controls a series of tri-state buffers, attaches the ui_in pins straight to the bus.
T4: Load the RAM from the MAR. De-assert the read_ui_in signal (disconnect the ui_in pins from driving the bus since the ui_in pin data might be now inaccurate). Assert the done_load signal to indicate to the MCU that the chip is done with the ui_in data.
T5: De-assert done_load signal.
The MCU must be able to provide the data to the ui_in pins (steady) between receiving the ready signal (assume worst case end of T0), and the bus needing the values (assume worst case beginning of T3).
Therefore, the MCU must be able to provide the data at a maximum of 2 clock periods.
Name | Verilog | Description | I/O | Width | Trigger |
---|---|---|---|---|---|
clk | clk | Clock signal | I | 1 | Edge Transition |
resetn | rst_n | Set stage to 0 | I | 1 | Active Low |
opcode | opcode | Opcode from IR | I | 4 | NA |
out | control_signals | Control Signal Array | O | 15 | NA |
programming | programming | Programming mode | I | 1 | Active High |
done_load | done_load | Executed Load during prog | O | 1 | Active High |
read_ui_in | read_ui_in | Push ui_in onto bus | O | 1 | Active High |
ready | ready_for_ui | Ready to prog next byte | O | 1 | Active High |
HF | HF | Halting flag | O | 1 | Active High |
Name | Verilog | Description | I/O | Width | Trigger |
---|---|---|---|---|---|
bus | bus[3:0] | Connection to bus | IO | 4 | NA |
clk | clk | Clock signal | I | 1 | Falling Edge |
clr_n | rst_n | Clear to 0 | I | 1 | Active Low |
cp | Ep | Allow counter increment | I | 1 | Active High |
ep | Cp | Output to bus | I | 1 | Active High |
lp | Lp | Load from bus | I | 1 | Active High |
Name | Verilog | Description | I/O | Width | Trigger |
---|---|---|---|---|---|
bus | bus | Connection to bus | IO | 8 | NA |
clk | clk | Clock signal | I | 1 | Rising Edge |
clear | ~rst_n | Clear to 0 | I | 1 | Active High |
opcode | opcode | Opcode from IR | O | 4 | NA |
n_load | nLi | Load from Bus | I | 1 | Active Low |
n_enable | nEi | Output to bus | O | 1 | Active Low |
Name | Verilog | Description | I/O | Width | Trigger |
---|---|---|---|---|---|
addr | mar_to_ram_addr | Address for read/write | I | 4 | NA |
data_in | mar_to_ram_data | Data for write | I | 8 | NA |
data_out | bus | Connection to bus | O | 8 | NA |
lr_n | nLr | Load data from MAR | I | 1 | Active Low |
ce_n | nCE | Output to bus | I | 1 | Active Low |
clk | clk | Clock Signal | I | 1 | Rising edge |
rst_n | '1' | Clear RAM | I | 1 | Active Low |
Name | Verilog | Description | I/O | Width | Trigger |
---|---|---|---|---|---|
bus | bus | Connection to bus | IO | 8 | NA |
clk | clk | Clock signal | I | 1 | Rising Edge |
addr | mar_to_ram_addr | Address for read/write | O | 4 | NA |
data | mar_to_ram_data | Data for write | O | 8 | NA |
n_load_data | nLmd | Load data from Bus | I | 1 | Active Low |
n_load_addr | nLma | Load address from Bus | I | 1 | Active Low |
Name | Verilog | Description | I/O | Width | Trigger |
---|---|---|---|---|---|
clk | clk | Clock Signal | I | 1 | Rising edge |
enable_out | Eu | Output to bus | I | 1 | Active High |
Register A | reg_a | Accumulator Register | I | 8 | NA |
Register B | reg_b | Register B | I | 8 | NA |
subtract | sub | Perform Subtraction | I | 1 | Active High |
bus | bus | Connection to bus | O | 8 | NA |
Carry Out | CF | Carry-out flag | O | 1 | Active High |
Result Zero | ZF | Zero flag | O | 1 | Active High |
Name | Verilog | Description | I/O | Width | Trigger |
---|---|---|---|---|---|
clk | clk | Clock Signal | I | 1 | Rising edge |
bus | bus | Connection to bus | IO | 8 | NA |
load | nLa | Load from bus | I | 1 | Active Low |
enable_out | Ea | Output to bus | I | 1 | Active High |
Register A | reg_a | Accumulator Register | O | 8 | NA |
clear | rst_n | Clear Signal | I | 1 | Active Low |
Name | Verilog | Description | I/O | Width | Trigger |
---|---|---|---|---|---|
bus | bus | Connection to bus | IO | 8 | NA |
clk | clk | Clock Signal | I | 1 | Rising edge |
n_load | nLb | Load from bus | I | 1 | Active Low |
value | reg_b | B Register value | O | 8 | NA |
Name | Verilog | Description | I/O | Width | Trigger |
---|---|---|---|---|---|
bus | bus | Connection to bus | IO | 8 | NA |
clk | clk | Clock Signal | I | 1 | Rising edge |
n_load | nLo | Load from bus | I | 1 | Active Low |
value | uo_out | B Register value | O | 8 | NA |
Provide input of op-code. Check that the correct output bits are being asserted/de-asserted properly.
clk
pin.rst_n
pin is properly connected to allow resetting the chip.Initial Reset:
rst_n
pin low, waiting for 1 clock signal, and then pulling pulling the rst_n
high to initialize the chip.Load Program into RAM:
Use the ui_in
pins to load a test program into the RAM. Ensure the programming
pin is high during this process.
Perform a sync reset by pulling the rst_n
pin low, waiting for 1 clock signal, and then pulling pulling the rst_n
high to initialize the chip.
Wait for the ready_for_ui signal to go high, indicating that the CPU is ready to accept data.
Provide the first byte of data on the ui_in pins.
Wait for the done_load signal to go high, indicating that the data has been successfully loaded into the RAM.
Repeat the process for each byte of data:
Example program data:
0x10, # NOP
0x73, # JMP 0x3
0x00, # HLT
0x4F, # LDA 0xF
0x2E, # ADD 0xE
0x6D, # STA 0xD
0x50, # OUT
0x3F, # SUB 0xF
0x50, # OUT
0x4D, # LDA 0xD
0x50, # OUT
0x72, # JMP 0x2
0x10, # NOP
0x00, # Padding/empty instruction
0x02, # Constant 2 (data)
0x01 # Constant 1 (data)
Run Test Program:
programming
pin low to exit programming mode.rst_n
pin low, waiting for 1 clock signal, and then pulling pulling the rst_n
high to initialize the chip.uo_out
and uio_out
pins for expected outputs.Functional Tests:
HLT Instruction: Example program data:
0x4E, # LDA 0xE
0x50, # OUT
0x00, # HLT
0x4F, # LDA 0xF
0x50, # OUT
0x00, # HLT
0x00, # Padding/empty instruction
0x00, # Padding/empty instruction
0x00, # Padding/empty instruction
0x00, # Padding/empty instruction
0x00, # Padding/empty instruction
0x00, # Padding/empty instruction
0x00, # Padding/empty instruction
0x00, # Padding/empty instruction
0x09, # Constant 9 (data)
0xFF # Constant 255 (data)
This program should first output 9 and then NOT change that to 255. HF should be set to 1
NOP Instruction: Example program data:
0x42, # LDA 0x2
0x50, # OUT
0x10, # NOP / Constant 16 (data)
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x4E, # LDA 0xF
0x50, # OUT
0x1F, # NOP
0x1F, # NOP / Constant 31 (data)
This program should flash the lower 4 bits of the output register on and off with different on/off times
NOP Instruction: Example program data:
0x42, # LDA 0x2
0x50, # OUT
0x10, # NOP / Constant 16 (data)
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x1F, # NOP
0x4E, # LDA 0xF
0x50, # OUT
0x1F, # NOP
0x1F, # NOP / Constant 31 (data)
This program should flash the lower 4 bits of the output register on and off with different on/off times
ADD Instruction Example program data:
0x50, # OUT
0x2E, # ADD 0xE
0x70, # JMP 0x0
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0x01, # Constant 1 (data)
0xFF, # Padding/empty instruction
This program should add 1 to the A register, display it and loop back to the start. The output should be a counter from 0 to 255, then repeat.
CF should be set to 1 when the A register overflows, and 0 when it doesn't. CF=1 happens when the A register is 255 and 1 is added to it.
ZF should be set to 1 when the A register is 0, and 0 otherwise.
SUB Instruction Example program data:
0x50, # OUT
0x3E, # SUB 0xE
0x70, # JMP 0x0
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0x01, # Constant 1 (data)
0xFF, # Padding/empty instruction
This program should subtract 1 to the A register, display it and loop back to the start. The output should be a counter from 255 to 0, then repeat.
CF should be set to 1 when the A register overflows, and 0 when it doesn't. CF=0 happens when the A register is 0 and 1 is subtracted from it.
ZF should be set to 1 when the A register is 0, and 0 otherwise.
LDA Instruction
See above for example program data.
OUT Instruction
See above for example program data.
STA Instruction
Example program data:
0x4E, # LDA 0xE
0x2F, # ADD 0xF
0x5F, # OUT
0x6E, # STA 0xF
0x2F, # ADD 0xE
0x5F, # OUT
0x00, # HLT
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0x09, # Constant 9 (data)
0xFF # Constant 255 (data) -> Constant 8 (data)
This program should load 9 to the A register, add 255 to it, resulting in 8 (CF should set to 1) display it, store it in 0xF, add 9 to it, resulting in 17 (CF should set to 0) and display it. Then, it should halt, and set HF to 1.
JMP Instruction
Example program data:
0x44, # LDA 0x4
0x5F # OUT
0x7D, # JMP 0xD
0x0F, # HLT
0x00, # Constant 0 (data)
0xFF, # Constant 5 (data)
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0xFF, # Padding/empty instruction
0x45, # LDA 0x5
0x5F # OUT
0x0F, # HLT
This program should load 0x4 (0) to the A register, display it, NOT HALT, jump to 0xD, then load 0x5 (255) to the A register, display it, and halt. HF should be set to 1.
# | Input | Output | Bidirectional |
---|---|---|---|
0 | prog_in_0 | output_register_0 | in_programming |
1 | prog_in_1 | output_register_1 | out_ready_for_ui |
2 | prog_in_2 | output_register_2 | out_done_load |
3 | prog_in_3 | output_register_3 | out_CF |
4 | prog_in_4 | output_register_4 | out_ZF |
5 | prog_in_5 | output_register_5 | out_HF |
6 | prog_in_6 | output_register_6 | |
7 | prog_in_7 | output_register_7 |