Skip to content

aslak3/cpu

Repository files navigation

16 bit processor in VHDL

This is a project for self-learning, and is pretty much useless except for anyone interested in how a simple processor could be implemented. That said, I think it's cool.

This is of course a work in progress.

Summary of features

  • 16 bit address and databuses
  • 16 bit opcodes
  • Byte and word size memory accesses, with signed/unsigned extension on byte reads
    • Bus error signal on unaligned word transfers
  • Some opcodes (like LOADI, JUMPs, BRANCHes, ALUMI, CALLs) have one following immediate value/address
  • 8 x 16 bit general purpose registers
  • 16 bit Program Counter
  • Load an immediate 16 bit quantity at the following address
  • Load and store instructions operate either through a register, an immediate address or a register with an immediate displacement, or the program counter with an immediate displacement
  • Clear instruction
  • Simple status bits: zero, negative, carry
  • ALU operations including
    • add, add with carry, subtract, subtract with carry, signed and unsigned 8 bit to 16 bit multiply, increment, decrement, and, or, xor, not, shift left, shift right, copy, negation, etc
  • ALU operations are of the form DEST <= DEST op OPERAND, or DEST <= op DEST
    • ALUMI operates with an immediate operand, eg. add r0,#123
  • Conditional and uncoditional jumps and branches: always, on each flag set or clear with don't cares
  • Nop and Halt instructions
  • Stacking: call/return and push and pop
  • No microcode: a coded state machine is used
  • Most instructions take 3 cycles
  • CustomASM (https://github.com/hlorenzi/customasm) is the current assembler

TODO

  • Interrupts, including software traps
  • Testbench for the controller
  • Add more instructions!
    • (Better) multiply and divide?
    • Barrel shifter?
    • Restricting ALU ops to byte wide values might be useful, but probably not
    • ....
  • Better status bits: not currently settable via an opcode, nor are they changed on anything other then an ALU instruction
    • This unfortuantely includes the LOADRD and STORED opcodes, which is confusing and wrong
  • It should be possible to do a build without multiply support, as very small FPGAs will not have sufficent resources

Top level RTL diagram (OUT OF DATE)

Top level RTL

Opcode map

Opcode 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
NOP 0b000000 -
Do nothing
JUMP 0b000010 - Flag cares Flag polariy
If (Flags AND Flag cares = Flag polarity) then PC ← IMMEDIATE
BRANCH 0b000011 - Flag cares Flag polariy
If (Flags AND Flag cares = Flag polarity) then PC ← PC + IMMEDIATE
CLEAR 0b001100 - Reg
Reg ← 0
LOADI 0b000100 Byte Signed - Dst reg
Dst reg ← IMMEDIATE
LOADM 0b001000 Byte Signed - Dst reg
Dst reg ← [IMMEDIATE]
STOREM 0b001001 Byte Signed -Src reg
[IMMEDIATE] ← Src reg
LOADR 0b001010 Byte Signed - Src addr reg Dst reg
Dst reg ← [Src addr reg]
STORER 0b001011 Byte Signed - Dst addr reg Src reg
[Dst addr reg] ← Src reg
LOADRD 0b011010 Byte Signed - Src addr reg Dst reg
Dst reg ← [Src addr reg + IMMEDIATE]
STORERD 0b011011 Byte Signed - Dst addr reg Src reg
[Dst addr reg + IMMEDIATE] ← Src reg
LOADPCD 0b011110 Byte Signed - Dst reg
Dst reg ← [PC reg + IMMEDIATE]
STOREPCD 0b011111 Byte Signed - Src reg
[PC reg + IMMEDIATE] ← Src reg
ALUM 0b001110 ALU multi op Operand reg Dst reg
Dst reg ← Dst reg ALU mlti op Src reg
ALUS 0b001111 ALU single op - Dst reg
Dst reg ← ALU single op Dst reg
ALUMI 0b011000 ALU multi op - Dst reg
Dst reg ← Dst reg ALU multi op IMMEDIATE
CALLJUMP * 0b010000 - Stack reg Stack reg
Stack reg ← Stack reg - 2 ; [Stack reg] ← PC ; PC ← IMMEDIATE
CALLBRANCH 0b010001 - Stack reg Stack reg
Stack reg ← Stack reg - 2 ; [Stack reg] ← PC ; PC ← PC + IMMEDIATE
RETURN 0b010010 - Stack reg Stack reg
PC ← [Stack reg] ; Stack reg ← Stack reg + 2
PUSHQUICK 0b010100 - Stack reg Src reg
Stack reg ← Stack reg - 2 ; [Stack reg] ← Src reg
POPQUICK 0b010101 - Stack reg Dst reg
Dst reg ← [Stack reg] ; Stack reg ← Stack reg + 2

Flag cares and flag polarity

2 1 0
Carry Zero Negative

Registers

0b000 r0
0b001 r1
0b010 r2
0b011 r3
0b100 r4
0b101 r5
0b110 r6
0b111 r7

ALU multi (destination and operand) operations

0b0000 Add
0b0001 Add with cary
0b0010 Subtract
0b0011 Subtract with cary
0b0100 Bitwise AND
0b0101 Bitwise OR
0b0110 Bitwise XOR
0b0111 Copy
0b1000 Compare
0b1001 Bitwise test
0b1010 Unsigned 8 bit to 16 bit multiply
0b1011 Signed 8 bit to 16 bit multiply
0b1100-0b1111 Unused

ALU single (destination only) operations

0b0000 Increment
0b0001 Decrement
0b0010 Double increment
0b0011 Double decrement
0b0100 Bitwise NOT
0b0101 Left shift
0b0110 Right shift
0b0111 Negation
0b1000 Byte swap
0b1001 Compare with zero
0b1010-0b1111 Unused

Sample code

The currently used CustomASM CPU definition makes it possible to write very presentable assembly by, for example, combing LOADI, LOADM, LOADR and LOADRD into a single "load" mnemonic with the width represented by .w, .bu or .bs. ALU operations are similarly represented.

; prints the messsge in r2 at row r0 coloumn r1

printmsg:       shiftleft r0                    ; word wide address so shift
                load.w r0,(rowoffsets,r0)       ; get the start of the row
                add r0,r1                       ; add the column
.loop:          load.bu r1,(r2)                 ; get the char
                test r1                         ; checking for null
                branchz printmsgo               ; done?
                store.b (r0),r1                 ; output the char
                inc r2                          ; move to next source char
                inc r0                          ; move to next video addr
                branch .loop                    ; get more
printmsgo:      return                          ; done

; polls the ps2 port for a byte, returning 0 in r0 if nothing is available

getps2byte:     load.bu r0,PS2_STATUS           ; get from the status reg
                test r0                         ; nothing?
                branchz .nothing                ; keep waiting yet....
                load.bu r0,PS2_SCANCODE         ; get the scancode
                compare r0,#0xf0                ; break?
                branchz .nothing                ; no, carry on
                store.w SEVENSEG,r0             ; display it for debug
                return                          ; done
.nothing:       clear r0                        ; return 0
                return                          ; done