More Advanced CPU
CPU project is one of the projects designed in department of computer engineering at TTU as a lab project. The main aims of this project are:
- Developing a simple generic CPU
- Developing peripherals for it
- Writing a compiler and assembler for it
- Compiling GCC for this architecture
- Booting a lightweight linux on it
This project has many interesting points and which can be used for laboratories in different courses.
Contents
Hardware Design
CPU Design
Functionality Requirements
The CPU is supposed to be able to perform the following operations:
- Addition/Subtraction
- Increment/Decrement
- Arithmetic and Logical Shift and Rotate through carry
- Bitwise AND, OR, XOR and NOT
- Negation
- Load/Store
- Unconditional Branch (jump)
- Branch if zero / Branch if Overflow / Branch if Carry/ Branch if Equal
- Clear Registers/Flags
- PUSH / POP
- NOP/HALT
It can use these operations to build more sophisticated operations later.
Architecture
The architecture of this CPU is based on Harvard architecture which has separate instruction and data memory. The instructions are assumed to be in the instruction memory before boot.
Instruction Format
Our CPU's instruction has 8 bit of Op-code and one operand that can be as long as 32 bit. First 2 bits of OP-code are at the moment reserved.
Addressing Modes
The following Addressing modes are supported in out processor:
- direct: program counter jumps to an address directly provided to it through instruction's operand
- relative: the program counter will jump to a location relative to its current location
- indirect: program counter will jump to an address stored in a memory location
- register: program counter will jump to an address stored in a register
- indexed: program counter will jump to an address stored in the memory with address stored in a register
Instruction Set (IS)
The following instructions designed for the CPU:
Instruction | Register Transfer Language | OpCode | Reg_in_sel | Reg_out_sel | DPU Command | Data To DPU | rd_MemAddress | Next PC | |
---|---|---|---|---|---|---|---|---|---|
1 | Add_A_R | A <-- A + R(operand) | 00 0000 | ---- | operand(2 downto 0) | 00 000 0000 10 | ---- | ---- | PC_out+1 |
2 | Add_A_Mem | A <-- A + Mem[Operand] | 00 0001 | ---- | ---- | 00 000 0000 00 | ---- | Operand | PC_out+1 |
3 | Add_A_Dir | A <-- A + Operand | 00 0010 | ---- | ---- | 00 000 0000 01 | Operand | ---- | PC_out+1 |
4 | Sub_A_R | A <-- A - R(operand) | 00 0011 | ---- | operand(2 downto 0) | 00 000 0001 10 | ---- | ---- | PC_out+1 |
5 | Sub_A_Mem | A <-- A - Mem[Operand] | 00 0100 | ---- | ---- | 00 000 0001 00 | ---- | Operand | PC_out+1 |
6 | Sub_A_Dir | A <-- A - Operand | 00 0101 | ---- | ---- | 00 000 0001 01 | Operand | ---- | PC_out+1 |
7 | IncA | A <-- A + 1 | 00 0110 | ---- | ---- | 00 000 0000 11 | ---- | ---- | PC_out+1 |
8 | DecA | A <-- A - 1 | 00 0111 | ---- | ---- | 00 000 0001 11 | ---- | ---- | PC_out+1 |
9 | ShiftArithR | A <-- A(7) & A(7 downto 1) | 00 1000 | ---- | ---- | 00 000 0111 00 | ---- | ---- | PC_out+1 |
10 | ShiftArithL | A <-- A(7) & A(5 downto 0)& '0' | 00 1001 | ---- | ---- | 00 000 1000 00 | ---- | ---- | PC_out+1 |
11 | ShiftA_R | A <-- A(6 downto 0)& '0' | 00 1010 | ---- | ---- | 00 000 1010 00 | ---- | ---- | PC_out+1 |
12 | ShiftA_L | A <-- '0' & A(7 downto 1) | 00 1011 | ---- | ---- | 00 000 1011 00 | ---- | ---- | PC_out+1 |
13 | RRC | A <-- C & A(7 downto 1) ,C<-- A(0) | 00 1100 | ---- | ---- | 00 000 1110 XX | ---- | ---- | PC_out+1 |
14 | RLC | A <-- A(6 downto 0) & C ,C<-- A(7) | 00 1101 | ---- | ---- | 00 000 1111 XX | ---- | ---- | PC_out+1 |
15 | And_A_B | A <-- A and R(operand) | 00 1110 | ---- | operand(2 downto 0) | 00 000 0100 10 | ---- | ---- | PC_out+1 |
16 | OR_A_B | A <-- A or R(operand) | 00 1111 | ---- | operand(2 downto 0) | 00 000 0101 10 | ---- | ---- | PC_out+1 |
17 | XOR_A_B | A <-- A xor R(operand) | 01 0000 | ---- | operand(2 downto 0) | 00 000 0110 10 | ---- | ---- | PC_out+1 |
18 | FlipA | A <-- not (A) | 01 0001 | ---- | ---- | 00 000 1100 00 | ---- | ---- | PC_out+1 |
19 | NegA | A <-- not(A) + 1 | 01 0010 | ---- | ---- | 00 000 1001 00 | ---- | ---- | PC_out+1 |
20 | Jmp | PC <-- Operand | 01 0011 | ---- | ---- | 00 000 0010 XX | ---- | ---- | Operand |
21 | JmpZ | if Z = 1: PC <-- Operand | 01 0100 | ---- | ---- | 00 000 0010 XX | ---- | ---- | if Z=1 then Operand else PC_out+1 |
22 | JmpOV | if OV = 1: PC <-- Operand | 01 0101 | ---- | ---- | 00 000 0010 XX | ---- | ---- | if OV=1 then Operand else PC_out+1 |
23 | JmpC | if C = 1: PC <-- Operand | 01 0110 | ---- | ---- | 00 000 0010 XX | ---- | ---- | if C=1 then Operand else PC_out+1 |
24 | Jmp_rel | PC <-- PC + Operand | 01 0111 | ---- | ---- | 00 000 0010 XX | ---- | ---- | PC <-- PC + Operand |
25 | JMPEQ | if EQ = 1: PC <-- Operand | 01 1000 | ---- | ---- | 00 000 0010 XX | ---- | ---- | if EQ=1 then Operand else PC_out+1 |
26 | ClearZ | Z <--- 0 | 01 1001 | ---- | ---- | 00 001 0010 XX | ---- | ---- | PC_out+1 |
27 | ClearOV | OV <--- 0 | 01 1010 | ---- | ---- | 00 010 0010 XX | ---- | ---- | PC_out+1 |
28 | ClearC | C <--- 0 | 01 1011 | ---- | ---- | 00 100 0010 XX | ---- | ---- | PC_out+1 |
29 | ClearACC | ACC <-- 0 | 01 1100 | ---- | ---- | 00 000 1101 XX | ---- | ---- | PC_out+1 |
30 | LoadPC | PC <---- A | 01 1101 | ---- | ---- | 00 000 0010 XX | ---- | ---- | A |
31 | SavePC | A <---- PC | 01 1110 | ---- | ---- | 00 000 0011 01 | PC | ---- | PC_out+1 |
32 | Load_A_Mem | A <-- Mem[Operand] | 01 1111 | ---- | ---- | 00 000 0011 00 | ---- | Operand | PC_out+1 |
33 | Store_A_Mem | Mem[Operand] <-- A | 10 0000 | ---- | ---- | 00 000 0010 XX | ---- | ---- | PC_out+1 |
34 | Load_R0_Dir | R0 <-- Operand | 10 0001 | 00000001 | ---- | 01 000 0010 XX | Operand | ---- | PC_out+1 |
35 | Load_R0_Mem | R0 <-- Mem[Operand] | 10 0010 | 00000001 | ---- | 11 000 0010 XX | ---- | Operand | PC_out+1 |
36 | Load_A_R | A <-- R(operand) | 10 0011 | ---- | operand(2 downto 0) | 00 000 0011 XX | ---- | ---- | PC_out+1 |
37 | Load_R_A | R(operand) <-- A | 10 0100 | operand(7 downto 0) | ---- | 10 000 0010 XX | ---- | ---- | PC_out+1 |
38 | Load_Ind_A | A <-- M[A] | 10 0101 | ---- | ---- | 00 000 0011 00 | ---- | A | PC_out+1 |
39 | PUSH | Mem [0 + SP] <--- A,SP <--- SP + 1 | 11 1100 | ---- | ---- | 00 000 0010 XX | ---- | ---- | PC_out+1 |
40 | POP | A <--- Mem [0 + SP - 1],SP <--- SP - 1 | 11 1101 | ---- | ---- | 00 000 0011 00 | ---- | SP - 1 | PC_out+1 |
41 | NOP | NOP | 11 1110 | ---- | ---- | 00 000 0010 XX | ---- | ---- | PC_out+1 |
42 | HALT | HALT | 11 1111 | ---- | ---- | 00 000 0010 XX | ---- | ---- | PC |
Implementation of complex instructions
the following instructions can be also implemented with the ones in IS:
- Call "function_name":
PUSH SavePC Push Jmp "function address" POP
- Return:
POP Add_A_Dir 4 LoadPC
- IndJMP "MemAddress":
PUSH Load_A_Mem "MemAddress" LoadPC
Note: its important to POP back the ACC value on the jump destination.
- JmpR:
PUSH Load_A_R "----" LoadPC
Note: its important to POP back the ACC value on the jump destination.
- JmpIndx:
PUSH Load_Ind_A LoadPC
Note: its important to POP back the ACC value on the jump destination.
Different Implementations
Different implementations of PicoCPU are available:
Graphics Card Design
VGA Controller
A simple VGA controller has been designed and synthesised on Nexsys 3 board for the PicoCPU. Controller reads a frame from a video memory and displays it on the monitor. The memory is shared between VGA controller and the graphics processor (coming soon). The user constraint file (UCF) for the vga controller can be found here:
missing ucf file
Graphics processor
Future plans
The following are the future plans for CPU:
- implement barrel shift on acc
- Adding I/O
- first try would be Input and Output registers
- wishbone bus maybe?
- Adding interrupts + super user mode (motorla has it MC68K)?
- Pipelining
- Branch prediction
- UART implementation
- implementation of Timers/Counters and peripherals
- Direct Memory Access (DMA)
- Memory management unit (MMU)
Assembler
Python Assembly translator
A simple assembly translator was designed to make debugging process faster. Here you can see 32 bit version of the code:
import re
InstructionOpCode = {
'Add_A_B': "000000",
'Add_A_Mem': "000001",
'Add_A_Dir': "000010",
'Sub_A_B': "000011",
'Sub_A_Mem': "000100",
'Sub_A_Dir': "000101",
'IncA': "000110",
'DecA': "000111",
'ShiftArithR': "001000",
'ShiftArithL': "001001",
'ShiftA_R': "001010",
'ShiftA_L': "001011",
'RRC': "001100",
'RLC': "001101",
'And_A_B': "001110",
'OR_A_B': "001111",
'XOR_A_B': "010000",
'FlipA': "010001",
'NegA': "010010 ",
'Jump': "010011",
'JmpZ': "010100",
'JmpOV': "010101",
'JmpC': "010110",
'Jmp_rel': "010111",
'JMPEQ': "011000",
'ClearZ': "011001",
'ClearOV': "011010 ",
'ClearC': "011011",
'ClearACC': "011100",
'LoadPC': "011101",
'SavePC': "011110",
'Load_A_Mem': "011111",
'Store_A_Mem': "100000",
'Load_B_Dir': "100001",
'Load_B_Mem': "100010",
'Load_A_B': "100011",
'Load_B_A': "100100",
'Load_Ind_A ': "100101",
'PUSH': "111100",
'POP': "111101",
'NOP': "111110",
'HALT': "111111",
}
AssemblyFile = open('Assembly.txt', 'r+')
MachineCodeFile = open('MachineCode.txt', 'w')
counter=0
for line in AssemblyFile:
for key in InstructionOpCode:
if key in line:
operand= "00000000"
if "Mem" in line:
operand = re.findall(r'\d+',line)[0]
elif "Jmp" in line:
operand = re.findall(r'\d+',line)[0]
elif "Dir" in line:
operand = re.findall(r'\d+',line)[0]
operand = "00000000"+"00000000"+"00000000"+ operand
MachineCodeFile.write(str(counter)+ " => "+ "\"00"+InstructionOpCode[key]+operand+'\",'+'\n')
counter +=1
MachineCodeFile.close()
AssemblyFile.close()
Java Assembler
This Assembler is wrote by Karl Janson as a project during system modelling course. You can find the information about how to use it and also the links for downloading it in the User Manual.
Note: Codes and User Manual for the assembler are placed in the Public Domain with the authorization of its author, Karl Janson.