I want to talk about a CPU that I designed way back in 2016. I implemented it in C as a VM. A friend of mine, Bjørn is the one who made an assembler for it which is written in F#.
Periwinkle is an OISC as opposed to RISC and CISC. It also has no pipelining. Inherently, performance is not the main goal of this project of mine, but it is for fun and education.
The one who named it is my girlfriend, Allonah. It is from the flower.
There are many instruction types for an OISC. But for Periwinkle, it is the move instruction. You just move a literal to a register, or move a value from a register to a register. Logical and arithmetic operations, branching, etc are performed via registers.
Periwinkle’s instruction length is consistently 40 bits. The data bus is 32 bits long.
Periwinkle only has a total of 64 registers.
I am just going to put the outline of these registers here:
Program Counter (PC)
- It can count up to 32bit
- Program storage size is 255 40bit word(depends on implementation)
- Moving a -1 to PC will make it explicitly stop.
General Purpose Stack (STK)
- A stack for whatever(16-levels deep)
- no signal for overflow or underflow
- Reading an empty stack will return 0
- Moving a value to here is a push operation
- Moving from here is a pop operation
Random Number Generator (RNG)
- Generates a random 32bit number per call
- Moving a value here seems nonsensical
Skip If Zero (SIZ)
- Skips next instruction if the value moved here is zero
Skip If Non-zero (SINZ)
- Skips next instruction if the value move here is non-zero
- Use to point at an address based on what is moved here
- It can only hold a 6-bit number, a larger number is truncated to 6-bit
- Dereferences where REF is pointed at
Reserved Registers (RSV)
- Moving a value here does nothing. It will still hold zero.
- It could be implemented for something when porting the VM to a microcontroller or whatever.
- For future/extra stuff
- It can be used to delete an item from the General purpose stack and operation registers if you move a value to here.(Not recommended)
- Returns 0 when reading it.
General Purpose Registers (GPR0-GPR31)
- It can hold a 32bit number
- It can be used to delete an item from the General purpose stack and operation registers if you move a value to here.
- Returns 0 when reading it
- 0000 0000 0000 0000 0000 0000 000P ZVNC
- -contains 5 flags(C,N,V,Z,P)
- Parity(Even parity)
- PLUS register will affect C,N,V,Z,P flags
- AND, OR, XOR registers will affect N,Z,P flags
- The latest triggered operation will affect the status register.
- Moving a value here seems nonsensical.
But how do the PLUS, AND, OR, XOR registers work? These 4 registers has some sort of stack which is a compute stack per se. When the compute stack contains 2 numbers, the operation is triggered.
This is an example for the PLUS register. It is still the same for the other 3, just change the one in the example to the corresponding operator.
But how do I do subtraction, multiplication, division, etc with just 4 operators? As the goal of this project is for fun and education as I’ve stated earlier. You can synthesize these things that you want to do on your own.
Subtraction can be done via addition with the two’s complement of a number. You can get the two’s complement by inverting the bits via pushing 0xFFFFFFFF to XOR and the number. And adding 1 to it via the PLUS register. The assembler has support for negative numbers though so you don’t have to do this if you don’t want.
Multiplication is just successive addition.
Division is just how many times a number can fit into a number. You can do this via successive subtraction.
Bit-shifting can be done with multiplying the number by 2 or dividing it by 2.
You just think how the other operations can be synthesized.
Here is a github repository of some assembly programs I made for Periwinkle if you are interested. The assembly language move instruction is from left to right:
- #50 gpr0 //move literal 50(base-10) to gpr0
- gpr0 gpr1 //move value of gpr0 to gpr1
Also, I’ll try to maybe upload the executable of the Periwinkle VM here and the assembler. What should I build the VM for? (Windows(x86? x86-64?), Linux (x86? x86-64? ARM?, ARM64?, etc?), etc?). As the assembler for it is written in F#, I think it can be executed anywhere, you just need the .NET framework, you can also look into Mono.
If you know the PIC16 architecture, you may have noticed some sort of similarity(STATUS, SIZ, SINZ, REF, DEF) from it for the Periwinkle architecture. I got some inspiration from there because that’s the first architecture that I got familiar with to program for in assembly language.