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} |
- All instructions consist of an opcode (most significant 4 bits), and an address (least significant 4 bits, where applicable)
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 |
- The control sequencer is negative edge triggered, so that control signals can be steady for the next positive clock edge, where the actions are executed.
- In each clock cycle, there can only be one source of data for the bus, however any number components can read from the bus.
- Before each run, a CLR signal is sent to the PC and the IR.
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 | - |
- First three micro-operations are common to all instructions.
- NOP operation executes only the first three micro-operations.
- Cp signal is not asserted during the HLT instruction in T2.
- ** Halt internal register is set to 1. More on this later
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 |
- Counter increments only when Cp is asserted, otherwise it will stay at the current value.
- Ep controls whether the counter is being output to the bus. If this signal is low, our output is high impedance (Tri-State Buffers).
- When CLR is low, the counter is cleared back to 0, the program will restart.
- The program counter updates its value on the falling edge of the clock.
- Lp indicates that we want to load the value on the bus into the counter (used for jump instructions). When this is asserted, we will read from the bus and instead of incrementing the counter, we will update each flip-flop with the appropriate bit and prepare to output.
- The least significant 4 bits from the 8-bit bus will be used to store the value on the program counter (0-15). Will be read from (JMP asserted) and written to (Ep asserted).
- clr_n has precedence over all.
- Lp takes precedence over Cp.
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 |
- The A Register updates its value on the rising edge of the clock.
- nEi controls whether the instruction is being output to the bus[3:0]. If this signal is high, our output is high impedance (Tri-State Buffers).
- nLi indicates that we want to load the value on the bus into the IR. When this is low, we will read from the bus and write to the register.
- When clear is high, the opcode is cleared back to NOP.
- IR always outputs the current value of the register to CB.
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 |
- Addressing: The memory is 4-bit addressable, where the address specifies which register (out of 16) is being accessed for reading or writing.
- Write operation: A byte of data is written to specific register in RAM, where the location is determined by the address. Requires write enable lr_n signal as active (low) and the clock edge to occur.
- Read operation: Data can be read from a specific register in RAM determined by the input address. Requires chip enable ce_n signal as active (low). The data is output on the bus, and it is updated on the clock edge.
- Output: Data is presented on the bus line when the chip is enabled for reading, and high-impedance (Z) otherwise.
- RAM is never reset, rather, we always flash it.
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 |
- The MAR updates its value on the rising edge of the clock.
- nLmd indicates that we want to load the value on the bus into the data register. When this is low, we will read from the bus and write to the register.
- nLma indicates that we want to load the value on the bus[3:0] into the address register. When this is low, we will read from the bus and write to the register.
- MAR always outputs the current value of the data and address registers to the RAM module.
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 |
- Eu controls whether the counter is being output to the bus. If this signal is low, our output is high impedance (Tri-State Buffers).
- A Register and B Register always provide the ALU with their current values.
- When sub is not asserted, the ALU will perform addition: Result = A + B
- When sub is asserted, the ALU will perform subtraction by taking 2s complement of operand B: Result = A - B = A + !B + 1
- Carry Out and Result Zero flags are updated on rising clock edge.
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 |
- The A Register updates its value on the rising edge of the clock.
- Ea controls whether the counter is being output to the bus. If this signal is low, our output is high impedance (Tri-State Buffers).
- nLa indicates that we want to load the value on the bus into the A Register. When this is low, we will read from the bus and write to the register.
- When CLR is low, the register is cleared back to 0.
- (Register A) always outputs the current value of the register to the ALU.
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 |
- The B Register updates its value on the rising edge of the clock.
- nLb indicates that we want to load the value on the bus into the B Register. When this is low, we will read from the bus and write to the register.
- B Register always outputs the current value of the register to the ALU.
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 |
- The Output Register updates its value on the rising edge of the clock.
- nLo indicates that we want to load the value on the bus into the B Register. When this is low, we will read from the bus and write to the register.
Provide input of op-code. Check that the correct output bits are being asserted/de-asserted properly.
- Power Supply: Connect the chip to a stable power supply as per the voltage specifications.
- Clock Signal: Provide a stable clock signal to the
clk
pin. - Reset: Ensure the
rst_n
pin is properly connected to allow resetting the chip.
-
Initial Reset:
- Perform a sync reset by pulling the
rst_n
pin low, waiting for 1 clock signal, and then pulling pulling therst_n
high to initialize the chip.
- Perform a sync reset by pulling the
-
Load Program into RAM:
-
Use the
ui_in
pins to load a test program into the RAM. Ensure theprogramming
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 therst_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:
- Wait for ready_for_ui to go high.
- Provide the next byte of data on the ui_in pins.
- Wait for done_load to go high.
-
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:
- Set the
programming
pin low to exit programming mode. - Perform a sync reset by pulling the
rst_n
pin low, waiting for 1 clock signal, and then pulling pulling therst_n
high to initialize the chip. - Monitor the
uo_out
anduio_out
pins for expected outputs. - Verify the control signals and data outputs at each clock cycle.
- Set the
-
Functional Tests:
- Perform specific functional tests for each instruction (e.g., ADD, SUB, LDA, STA, JMP, HLT).
- Verify the correct execution of each instruction by checking the output and control signals.
-
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.
- Darius Rudaitis, Eshann Mehta: RAM
- Evan Armoogan, Catherine Ye: PC
- Damir Gazizullin, Owen Golden: ALU, Accumulator
- Roni Kant, Jeremy Kam: MAR, B Register, Output Register, Instruction Register
- Gerry Chen, Siddharth Nema: Control Block and Programmer
- ECE 298A Course Staff: Prof. John Long, Prof. Vincent Gaudet, Refik Yalcin