Welcome to Play Assembler »github.com/stijnsanders/plasm
In an effort to self-learn more about (virtual) machine code and compilers/parsers/lexers, I created this toy virtual CPU with its own assembler. It's distinctly unlike anything I know exists, specifically to investigate what it takes to make something like this work, and have a free choice of what to put in and what to leave out; also to notice whether old ideas have already nested in my head and if tunnel vision would take hold.
The CPU is kept strictly simple: it has 5 general purpose registers: A,B,C,D,E; a stack pointer S; an instruction pointer I when executing is set to the address of the next instruction to run; and a boolean flag F.
The CPU has access to memory, which, for the purpose of this play environment, is based on a plain javascript array, so register and memory values are (signed!) integers of an unspecified bit width. Access memory by writing [A] to read or write memory at the address stored in register A. To mimic address modes, some arithmetic (e.g. [A+D+2]) is available but is limited to addition with registers B,C,D,E or constant offsets, since in an hypothetical effective CPU design this would have a negative impact on branch prediction.
There is also a constant W for the machine word width byte count, but is currently 1 since addresses index into a plain array of integers.
Write A=B to copy (assign) the value of B to A. Write A+B to calculate the sum of the value of A and B and store it in A. Also A-B subtraction, A*B multiplication, A/B division, A%B modulus, A|B or, A&B and, A^B xor, A<<B shift left, A>>B shift right.
Write A?B to compare the value of A to the value of B and have the flag F set accordingly. Also A<B less than, A>B greater than, A<=B less then or equal, A>=B greater than or equal.
Preceed an instruction with ? to have it only perform when the flag F is set.
Preceed an instruction with ! to have it only perform when the flag F is not set.
Preceed an instruction with , to have it behave atomically with the preceding instruction. This currently has no effect since this virtual CPU won't be handling IRQ's, but in a theoretical physical design this is important to guarantee correct operation with conditional instructions. Instructions prefixed with ? or ! also behave atomically.
Push? There is no push. Do S-W,[S]=A. Pop? There is no pop. Do A=[S],S+W.
Jump? There is no jump. Manipulate the instruction pointer I to hold the address of the next instruction to perform. (Program loading supports labels prefixed with :, see below.)
Call? Do S-W,[S]=I,[S]+2,I=A (the [S]+2 instruction is to have the return address point to past the I=A instruction, see below).
Ret? Do S+W,I=[S-W] (but not I=[S],S+W since I=[S] would jump and never reach S+W).
Enter instructions in the yellow box and press Enter to have them executed to manipulate or inspect the processor state and memory.
Press Program, then Load to have a program processed for running. Instructions are shown on the right, as if each one takes one index (RISC-like) starting at 400. The binary encoding is not yet specified, but not relevant to this experiment anyway. It is loaded in separate memory (it's not Von Neumann for now) only indexed by the instruction pointer. To use the same memory would require the binary encoding of the instructions, but that's perhaps for when I'm done playing with this. Instructions are shown to the right in the blue box, click a line to toggle a breakpoint. Press Step to perform the next instruction at I or Run to start execution at I.
Machine interaction
Input/output is very limited for now, mainly to output numerical values to see how calculation programs perform.
Do S-W,[S]=I,I=11 to output the value of A, 12 for B, etc.
Do S-W,[S]=I,I=21 to output the value of [A], 22 for [B], etc.
Do I=9 to halt execution cleanly. (Running past the end of the program would cause an Instruction pointer past end of program error.)