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.
- 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
- 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
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 |
2 | 1 | 0 |
Carry | Zero | Negative |
0b000 | r0 |
0b001 | r1 |
0b010 | r2 |
0b011 | r3 |
0b100 | r4 |
0b101 | r5 |
0b110 | r6 |
0b111 | r7 |
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 |
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 |
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