This project implements a 64-bit virtual machine called "Maize." This is an outgrowth of my Tortilla project, which began life as an x86 emulator implemented in C# on .NET and then later became a virtual CPU of my own making.
The near-term goal for the Maize project is to implement a set of devices to bridge from the virtual environment to the host machine, create a "BIOS" layer above the virtual devices, and implement a simple OS and a subset of Unix/Linux system calls (interrupt $80),
- A 64-bit virtual machine implemented in C++ that executes a custom byte code
- An assembly language that represents the byte code
- An assembler implemented in C++ that generates byte code from the assembly language
- A very simple BIOS and OS that bridges the VM and the underlying machine
- An execution environment implemented in C++ that so far runs on Windows and Linux and could easily be ported to other platforms
It's a long story.
In 2016 I had a contract working on an ARM system, and I wasn't too familiar with ARM assembly. I had an idea to write an ARM emulator, since I've always believed that the best way to understand a system is to try to build one. After getting stuck with the ARM emulator, I decided to first build an x86 emulator and then go back to ARM. While I knew x86 assembly well enough to debug it, I wasn't really an expert at it, and I didn't know the lowest levels of machine language. I thought that tackling an ISA I knew would help me get the basics sorted out, and I'd come back to ARM later.
I got the x86 emulator working well enough to run code generated by standard compilers, but by then I wasn't working on ARM anymore, and I was more interested in learning about how CPUs work. I also found Ben Eater's Youtube! channel, where he builds an 8-bit computer from scratch, and I decided to use those as guidance for building a virtual CPU of my own design. The first implementation of that was the Tortilla project.
With Tortilla, I wrote code for every single cycle of each instruction, as if the CPU were moving data around the buses like a physical CPU. That was fun and enlightening, but it was also terribly inefficient. I decided to rewrite the entire thing in C++ and make the virtual machine more of a byte-code execution environment rather than a simulation of a CPU, and that became the Maize project. The idea is to be able to compile any language to Maize byte code and run it on any system that can run the Maize VM.
No, I never got back around to the ARM emulator, and at this point I doubt I will.
Honestly, it's mainly a toy to learn about a few concepts:
- How byte-code virtual machines work
- The construction of an assembly language and corresponding assemblers and disassemblers
- Porting compiler back-ends to a new architecture
- Learning how to write an OS and BIOS for a new architecture
- Learning how systems integrate with hardware.
It's been really useful for all of the above, but what I'm most excited about is the promise of compiling any language to Maize byte code and running it anywhere that can run the Maize VM.
Maize is implemented in standard C++ and will run on Windows and Linux. The primary executable is maize, which accepts a path to a binary file to execute. You may generate a binary from Maize assembly with the mazm executable, which accepts a path to an assembly file and outputs a BIN file that can then be executed with maize.
I haven't finished porting all of the instructions from the .NET implementation yet, but now that I've finished restructuring the code it shouldn't take too long to complete them.
The assembler is still VERY bare-bones, but it's enough to generate usable executables.
As I said in the original .NET implementation, it's very early days for Maize, so don't expect too much in the way of application usability... yet! I'm still porting the basic text-mode console for input and output. Next, I'll start creating a file-system device. I am currently porting QBE to output Maize assembly so that I can write Maize binaries with standard C and eventually port Linux to the virtual CPU.
In the short term, I'm implementing a very basic OS over a simple BIOS (core.asm). It will provide a basic character-mode CLI to allow building and running simple Maize programs from within the virtual CPU environment.
So far, this implementation in C++ is MUCH faster and MUCH tighter than the .NET version.
The near-term road map is as follows:
- Finish implementing all of the instructions documented below (in progress)
- Add a Maize back end to CProc and build Maize BIOS & OS in standard C (in progress)
- Implement a linker so that binaries can be built independently and linked together
- Introduce "devices," which were partially implemented in the .NET version
- Clean up the assembler (mazm) and introduce some proper error checking
- Make the assembler read Unicode source files
- Implement floating-point arithmetic
- Add a Maize backend to GCC and port GNU tools to Maize.
Here is a simple "Hello, World!" application written in Maize assembly.
; **********************************************************************************
; The entry point. Execution begins at segment $00000000, address $00000000
$0000`0000: ; The back-tick (`) is used as a number separator.
; Underscore (_) and comma (,) may also be used as separators.
CALL main
HALT ; For now, HALT exits the Maize interpreter, but in the
; future it will pause the CPU and wait for an interrupt.
; **********************************************************************************
; The output message
hw_string:
STRING "Hello, world!\0"
; **********************************************************************************
; Return the length of a zero-terminated string. Equivalent to the following C code:
;
; int strlen(char const *str) {
; int len = 0;
; while (str[len]) {
; ++len;
; }
; return len;
; }
;
; Parameters:
; R0.H0: Address of string
; Return:
; RV: Length of string
strlen:
PUSH BP ; Save the base pointer
CP SP BP ; Copy the stack pointer to the base pointer
SUB $04 SP ; Make room for a 32-bit (4-byte) counter on the stack
LEA $-04 BP RT.H0 ; Load the address of the counter's stack location into RT.H0
ST $00 @RT.H0 ; Set the counter to zero.
loop_condition:
LEA @RT.H0 R0.H0 R1.H0 ; Add the counter value to the address and put the result into H.H0.
LD @R1.H0 R1.B4 ; Load the character value at the address in R1.H0 into R1.B4.
JZ loop_exit ; LD sets the zero flag if the value copied to R1.B4 is zero.
loop_body:
LD @RT.H0 RT.H1 ; Load the counter value at the address in RT.H0 into RT.H1
INC RT.H1 ; Add 1 to the temporary
ST RT.H1 @RT.H0 ; Store the new value back into the counter's address
JMP loop_condition ; ...and continue the loop.
loop_exit:
LD @RT.H0 RV ; Load the (sign-extended) counter value into RV, the return register.
CP BP SP ; Restore the stack
POP BP ; Restore the base pointer
RET ; Pop return address from stack and place into PC.
; **********************************************************************************
; The main function
main:
CP hw_string R0.H0 ; Copy the address of the message string into R0.H0
CALL strlen ; Call strlen function to get the string length
CP $01 R0 ; $01 in R0 indicates output to stdout
CP hw_string R1.H0 ; R1.H0 holds address of message to output
CP RV R2 ; Put the string length into R2
SYS $01 ; Call output function implemented in Maize VM
CP $0 RV ; Set return value for main
RET ; Leave main
Numeric values are represented in Maize assembly and Maize documentation in binary, decimal, or hexadecimal formats. The % character precedes binary-encoded values, the # character precedes decimal-encoded values, and the $ character precedes hexadecimal-encoded values.
%00000001 binary value
#123 decimal value
$FFFE1000 hexadecimal value
The underscore, back-tick (`) and comma (,) characters may all be used as numeric separators in all encodings.
Examples:
%0000`0001
%1001_1100
#123,456,789
$0000_FFFF
$FE,DC,BA,98
$1234`5678
#123,456_789`021
Instructions are variable-length encoded and may have zero, one, or two parameters. An instruction opcode is encoded as a single eight-bit byte defining the opcode and instruction flags, and each instruction parameter is defined in an additional byte per parameter. Immediate values are encoded following the opcode and parameter bytes in little-endian format (least-significant bytes are stored lower in memory).
When an instruction has two parameters, the first parameter is the source parameter, and the second parameter is the destination parameter.
Example: Copy the immediate value $01 into register R0.
CP $01 R0
Example: Encoding of "CP $FFCC4411 R3", which copies the immediate value $FFCC4411 into register R3. $41 is the opcode and flags for the instruction which copies an immediate value into a register. $02 is the parameter byte specifying a four-byte immediate value as the source parameter. $3E is the parameter byte specifying the 64-bit register R3 as the destination parameter. The bytes following the parameters bytes are the immediate value in little-endian format.
$41 $02 $3E $11 $44 $CC $FF
Immediate values may used as pointers into memory. This is represented in assembly by the '@' prefix in front of the immediate value.
Example: Copy the 64-bit value at address $0000`1000 into register R1.
CP @$0000`1000 R1
Register values may be used as pointers into memory by adding a '@' prefix in front of the register name.
Example: Store the value $FF into the byte pointed at by the R0.H0 register:
ST $FF @R0.H0
Example: Load the quarter-word located at the address stored in R3.H0 into sub-register RT.Q3:
LD @R3.H0 RT.Q3
Registers are 64-bits wide (a "word") and are each divided into smaller sub-registers.
R0 General purpose
R1 General purpose
R2 General purpose
R3 General purpose
R4 General purpose
R5 General purpose
R6 General purpose
R7 General purpose
R8 General purpose
R9 General purpose
RT Temporary register
RV Return-value register
RF Flag register
RI Instruction register
RP Program execution register
RS Stack register
Sub-registers are defined as half-word (H), quarter-word (Q), and byte (B) widths. The full 64-bit value of a register (for example, register R0) may be coded as "R0" or "R0.W0". The register value may also be accessed as separate half-word (32-bit) values, coded as R0.H1 (upper 32 bits) and R0.H0 (lower 32-bits). The 16-bit quarter-words are similarly coded as R0.Q3, R0.Q2, R0.Q1, and R0.Q0. Finally, the individual byte values are coded as R0.B7, R0.B6, R0.B5, R0.B4, R0.B3, R0.B2, R0.B1, and R0.B0.
Shown graphically, the 64-bit value $FEDCBA9876543210 would be stored as follows:
FE DC BA 98 76 54 32 10
[B7][B6][B5][B4][B3][B2][B2][B0]
[Q3 ][Q2 ][Q1 ][Q0 ]
[H1 ][H0 ]
[W0 ]
In other words, if the following instruction were executed:
CP $FEDCBA9876543210 R0
The value stored in register R0 could then be represented as follows:
R0 = $FEDCBA9876543210
R0.W0 = $FEDCBA9876543210
R0.H1 = $FEDCBA98
R0.H0 = $76543210
R0.Q3 = $FEDC
R0.Q2 = $BA98
R0.Q1 = $7654
R0.Q0 = $3210
R0.B7 = $FE
R0.B6 = $DC
R0.B5 = $BA
R0.B4 = $98
R0.B3 = $76
R0.B2 = $54
R0.B1 = $32
R0.B0 = $10
There are six special-purpose registers.
RT Temporary register. This is used as temporary storage in a function.
RV Return-value register. Return values from functions are placed into this register.
RF Flags register. FL is an alias for RF.H0, which contains a bit field of individual flags.
Flags in RF.H1 may only be set in privileged mode.
RI Instruction register, set by the instruction decoder as instructions and parameters are
read from memory. This register can only be set by the decoder.
RP Program execution register, which is the pointer to the next instruction to be decoded
and executed. This is further sub-divided into RP.H1, which is the current code segment,
and RP.H0, which is the effective program counter within the current segment. RP.H1 may
only be written to in privileged mode. PC is an alias for RP.H0.
RS Stack register, which is the location within the current segment at which the stack
starts (growing downward in memory). This is further sub-divided into RS.H1, which is
the base pointer, and RS.H0, which is the current stack pointer. SP is an alias for RS.H0,
and BP is an alias for RS.H1.
(more coming on this)
The CPU starts in privileged mode, and the program counter is initially set to segment $00000000, address $00000000. When in privileged mode, the privilege flag is set, and instructions marked as privileged may be executed. When the privilege flag is cleared, instruction execution and memory access are limited to the current segment, and certain flags, registers, and instructions are inaccessible. Program execution may return to privileged mode via hardware interrupts or via software-generated (INT instruction) interrupts.
Opcodes are defined in an 8-bit byte separated into two flag bits and six opcode bits.
%BBxx`xxxx Flags bit field (bits 6 and 7)
%xxBB`BBBB Opcode bit field (bits 0 through 5)
When an instruction has a source parameter that may be either a register or an immediate value, then bit 6 is interpreted as follows:
%x0xx`xxxx source parameter is a register
%x1xx`xxxx source parameter is an immediate value
When an instruction's source parameter may be either a value or a pointer to a value, then bit 7 is interpreted as follows:
%0xxx`xxxx source parameter is a value
%1xxx`xxxx source parameter is a memory address
Binary Hex Mnemonic Parameters Description
---------- --- -------- ---------- --------------------------------------------------------------------------------------------------------------------------------------
%0000`0000 $00 HALT Halt the clock, thereby stopping execution (privileged)
%0000`0001 $01 CP regVal reg Copy source register value into destination register
%0100`0001 $41 CP immVal reg Copy immediate value into destination register
%1000`0001 $81 LD regAddr reg Load value at address in source register into destination register
%1100`0001 $C1 LD immAddr reg Load value at immediate address into destination register
%0000`0010 $02 ST regVal regAddr Store source register value at address in second register
%0100`0010 $42 ST immVal regAddr Store immediate value at address in destination register
%0000`0011 $03 ADD regVal reg Add source register value to destination register
%0100`0011 $43 ADD immVal reg Add immediate value to destination register
%1000`0011 $83 ADD regAddr reg Add value at address in source register to destination register
%1100`0011 $C3 ADD immAddr reg Add value at immediate address to destination register
%0000`0100 $04 SUB regVal reg Subtract source register value from destination register
%0100`0100 $44 SUB immVal reg Subtract immediate value from destination register
%1000`0100 $84 SUB regAddr reg Subtract value at address in source register from destination register
%1100`0100 $C4 SUB immAddr reg Subtract value at immediate address from destination register
%0000`0101 $05 MUL regVal reg Multiply destination register by source register value
%0100`0101 $45 MUL immVal reg Multiply destination register by immediate value
%1000`0101 $85 MUL regAddr reg Multiply destination register by value at address in source register
%1100`0101 $C5 MUL immAddr reg Multiply destination register by value at immediate address
%0000`0110 $06 DIV regVal reg Divide destination register by source register value
%0100`0110 $46 DIV immVal reg Divide destination register by immediate value
%1000`0110 $86 DIV regAddr reg Divide destination register by value at address in source register
%1100`0110 $C6 DIV immAddr reg Divide destination register by value at immediate address
%0000`0111 $07 MOD regVal reg Modulo destination register by source register value
%0100`0111 $47 MOD immVal reg Modulo destination register by immediate value
%1000`0111 $87 MOD regAddr reg Modulo destination register by value at address in source register
%1100`0111 $C7 MOD immAddr reg Modulo destination register by value at immediate address
%0000`1000 $08 AND regVal reg Bitwise AND destination register with source register value
%0100`1000 $48 AND immVal reg Bitwise AND destination register with immediate value
%1000`1000 $88 AND regAddr reg Bitwise AND destination register with value at address in source register
%1100`1000 $C8 AND immAddr reg Bitwise AND destination register with value at immediate address
%0000`1001 $09 OR regVal reg Bitwise OR destination register with source register value
%0100`1001 $49 OR immVal reg Bitwise OR destination register with immediate value
%1000`1001 $89 OR regAddr reg Bitwise OR destination register with value at address in source register
%1100`1001 $C9 OR immAddr reg Bitwise OR destination register with value at immediate address
%0000`1010 $0A NOR regVal reg Bitwise NOR destination register with source register value
%0100`1010 $4A NOR immVal reg Bitwise NOR destination register with immediate value
%1000`1010 $8A NOR regAddr reg Bitwise NOR destination register with value at address in source register
%1100`1010 $CA NOR immAddr reg Bitwise NOR destination register with value at immediate address
%0000`1011 $0B NAND regVal reg Bitwise NAND destination register with source register value
%0100`1011 $4B NAND immVal reg Bitwise NAND destination register with immediate value
%1000`1011 $8B NAND regAddr reg Bitwise NAND destination register with value at address in source register
%1100`1011 $CB NAND immAddr reg Bitwise NAND destination register with value at immediate address
%0000`1100 $0C XOR regVal reg Bitwise XOR destination register with source register value
%0100`1100 $4C XOR immVal reg Bitwise XOR destination register with immediate value
%1000`1100 $8C XOR regAddr reg Bitwise XOR destination register with value at address in source register
%1100`1100 $CC XOR immAddr reg Bitwise XOR destination register with value at immediate address
%0000`1101 $0D SHL regVal reg Shift value in destination register left by value in source register
%0100`1101 $4D SHL immVal reg Shift value in destination register left by immediate value
%1000`1101 $8D SHL regAddr reg Shift value in destination register left by value at address in source register
%1100`1101 $CD SHL immAddr reg Shift value in destination register left by value at immediate address
%0000`1110 $0E SHR regVal reg Shift value in destination register right by value in source register
%0100`1110 $4E SHR immVal reg Shift value in destination register right by immediate value
%1000`1110 $8E SHR regAddr reg Shift value in destination register right by value at address in source register
%1100`1110 $CE SHR immAddr reg Shift value in destination register right by value at immediate address
%0000`1111 $0F CMP regVal reg Set flags by subtracting source register value from destination register
%0100`1111 $4F CMP immVal reg Set flags by subtracting immediate value from destination register
%1000`1111 $8F CMP regAddr reg Set flags by subtracting value at address in source register from destination register
%1100`1111 $CF CMP immAddr reg Set flags by subtracting value at immediate address from destination register
%0001`0000 $10 TEST regVal reg Set flags by ANDing source register value with destination register
%0101`0000 $50 TEST immVal reg Set flags by ANDing immediate value with destination register
%1001`0000 $90 TEST regAddr reg Set flags by ANDing value at address in source register with destination register
%1101`0000 $D0 TEST immAddr reg Set flags by ANDing value at immediate address with destination register
%0001`0001 $11 CMPXCHG regVal reg reg Compare value in operand 2 with value in operand 3. If equal, set zero flag and copy value in operand 1 register into operand 2. Otherwise, clear zero flag and copy value in operand 2 into operand 3.
%0101`0001 $51 CMPXCHG immVal reg reg Compare value in operand 2 with value in operand 3. If equal, set zero flag and copy value in operand 1 immediate value into operand 2. Otherwise, clear zero flag and copy value in operand 2 into operand 3.
%1001`0001 $91 CMPXCHG regAddr reg reg Compare value in operand 2 with value in operand 3. If equal, set zero flag and load value at address in operand 1 register into operand 2. Otherwise, clear zero flag and load value in operand 2 into operand 3.
%1101`0001 $D1 CMPXCHG immAddr reg reg Compare value in operand 2 with value in operand 3. If equal, set zero flag and load value at address in operand 1 immediate value into operand 2. Otherwise, clear zero flag and load value in operand 2 into operand 3.
%0001`0010 $12 LEA regVal reg reg Add register value in operand 1 to value in operand 2 register and store result in operand 3 register
%0101`0010 $52 LEA immVal reg reg Add immediate value in operand 1 to value in operand 2 register and store result in operand 3 register
%1001`0010 $92 LEA regAddr reg reg Add value at address in operand 1 register to value in operand 2 register and store result in operand 3 register
%1101`0010 $D2 LEA immAddr reg reg Add value at immediate address in operand 1 to value in operand 2 register and store result in operand 3 register
%0001`0011 $13 CPZ regVal reg Copy source register value into destination register with zero extension
%0101`0011 $53 CPZ immVal reg Copy immediate value into destination register with zero extension
%1001`0011 $93 LDZ regAddr reg Load value at address in source register into destination register with zero extension
%1101`0011 $D3 LDZ immAddr reg Load value at immediate address into destination register with zero extension
%0001`0100 $14 OUT regVal imm Output value in source register to destination port
%0101`0100 $54 OUT immVal imm Output immediate value to destination port
%1001`0100 $94 OUT regAddr imm Output value at address in source register to destination port
%1101`0100 $D4 OUT immAddr imm Output value at immediate address to destination port
%0001`0101 $15 LNGJMP regVal Jump to segment and address in source register and continue execution (privileged)
%0101`0101 $55 LNGJMP immVal Jump to immediate segment and address and continue execution (privileged)
%1001`0101 $95 LNGJMP regAddr Jump to segment and address pointed to by source register and continue execution (privileged)
%1101`0101 $D5 LNGJMP immAddr Jump to segment and address pointed to by immediate value and continue execution (privileged)
%0001`0110 $16 JMP regVal Jump to address in source register and continue execution
%0101`0110 $56 JMP immVal Jump to immediate address and continue execution
%1001`0110 $96 JMP regAddr Jump to address pointed to by source register and continue execution
%1101`0110 $D6 JMP immAddr Jump to address pointed to by immediate value and continue execution
%0001`0111 $17 JZ regVal If Zero flag is set, jump to address in source register and continue execution
%0101`0111 $57 JZ immVal If Zero flag is set, jump to immediate address and continue execution
%1001`0111 $97 JZ regAddr If Zero flag is set, jump to address pointed to by source register and continue execution
%1101`0111 $D7 JZ immAddr If Zero flag is set, jump to address pointed to by immediate value and continue execution
%0001`1000 $18 JNZ regVal If Zero flag is not set, jump to address in source register and continue execution
%0101`1000 $58 JNZ immVal If Zero flag is not set, jump to immediate address and continue execution
%1001`1000 $98 JNZ regAddr If Zero flag is not set, jump to address pointed to by source register and continue execution
%1101`1000 $D8 JNZ immAddr If Zero flag is not set, jump to address pointed to by immediate value and continue execution
%0001`1001 $19 JLT regVal If Negative flag is not equal to Overflow flag, jump to address in source register and continue execution
%0101`1001 $59 JLT immVal If Negative flag is not equal to Overflow flag, jump to immediate address and continue execution
%1001`1001 $99 JLT regAddr If Negative flag is not equal to Overflow flag, jump to address pointed to by source register and continue execution
%1101`1001 $D9 JLT immAddr If Negative flag is not equal to Overflow flag, jump to address pointed to by immediate value and continue execution
%0001`1010 $1A JB reg If Carry flag is set, jump to address in source register and continue execution
%0101`1010 $5A JB imm If Carry flag is set, jump to immediate address and continue execution
%1001`1010 $9A JB regAddr If Carry flag is set, jump to address pointed to by source register and continue execution
%1101`1010 $DA JB immAddr If Carry flag is set, jump to address pointed to by immediate value and continue execution
%0001`1011 $1B JGT regVal If Zero flag is clear and Negative flag is not equal to Overflow flag, jump to address in source register and continue execution
%0101`1011 $5B JGT immVal If Zero flag is clear and Negative flag is not equal to Overflow flag, jump to immediate address and continue execution
%1001`1011 $9B JGT regAddr If Zero flag is clear and Negative flag is not equal to Overflow flag, jump to address pointed to by source register and continue execution
%1101`1011 $DB JGT immAddr If Zero flag is clear and Negative flag is not equal to Overflow flag, jump to address pointed to by immediate value and continue execution
%0001`1100 $1C JA regVal If Carry flag is clear and Zero flag is clear, jump to address in source register and continue execution
%0101`1100 $5C JA immVal If Carry flag is clear and Zero flag is clear, jump to immediate address and continue execution
%1001`1100 $9C JA regAddr If Carry flag is clear and Zero flag is clear, jump to address pointed to by source register and continue execution
%1101`1100 $DC JA immAddr If Carry flag is clear and Zero flag is clear, jump to address pointed to by immediate value and continue execution
%0001`1101 $1D CALL regVal Push PC.H0 to stack, jump to address in source register and continue execution until RET is executed
%0101`1101 $5D CALL immVal Push PC.H0 to stack, jump to immediate address and continue execution until RET is executed
%1001`1101 $9D CALL regAddr Push PC.H0 to stack, jump to address pointed to by source register and continue execution until RET is executed
%1101`1101 $DD CALL immAddr Push PC.H0 to stack, jump to address pointed to by immediate value and continue execution until RET is executed
%0001`1110 $1E OUTR regVal reg Output value in source register to port in destination register
%0101`1110 $5E OUTR immVal reg Output immediate value to port in destination register
%1001`1110 $9E OUTR regAddr reg Output value at address in source register to port in destination register
%1101`1110 $DE OUTR immAddr reg Output value at immediate address to port in destination register
%0001`1111 $1F IN regVal reg Read value from port in source register into destination register
%0101`1111 $5F IN immVal reg Read value from port in immediate value into destination register
%1001`1111 $9F IN regAddr reg Read value from port at address in source register into destination register
%1101`1111 $DF IN immAddr reg Read value from port at immediate address into destination register
%0010`0000 $20 PUSH regVal Copy register value into memory at location in RS.H0, decrement RS.H0 by size of register
%0110`0000 $60 PUSH immVal Copy immediate value into memory at location in RS.H0, decrement RS.H0 by size of immediate value
%0010`0010 $22 CLR regVal Set register to zero (0).
%0010`0100 $24 INT regVal Push FL and PC to stack and generate a software interrupt at index stored in register (privileged)
%0110`0100 $64 INT immVal Push FL and PC to stack and generate a software interrupt using immediate index (privileged)
%0010`0110 $26 POP regVal Increment SP.H0 by size of register, copy value at SP.H0 into register
%0010`0111 $27 RET Pop PC.H0 from stack and continue execution at that address. Used to return from CALL.
%0010`1000 $28 IRET Pop FL and PC from stack and continue execution at segment/address in PC. Used to return from interrupt (privileged).
%0010`1001 $29 SETINT Set the Interrupt flag, thereby enabling hardware interrupts (privileged)
%0010`1011 $2B reserved
%0110`1011 $6B reserved
%1010`1011 $AB reserved
%1110`1011 $EB reserved
%0010`1100 $2C reserved
%0110`1100 $6C reserved
%1010`1100 $AC reserved
%1110`1100 $EC reserved
%0010`1101 $2D reserved
%0110`1101 $6D reserved
%1010`1101 $AD reserved
%1110`1101 $ED reserved
%0010`1110 $2E reserved
%0110`1110 $6E reserved
%1010`1110 $AE reserved
%1110`1110 $EE reserved
%0011`0101 $35 reserved
%0111`0101 $75 reserved
%1011`0101 $B5 reserved
%1111`0101 $F5 reserved
%0011`0110 $36 reserved
%0111`0110 $76 reserved
%1011`0110 $B6 reserved
%1111`0110 $F6 reserved
%0011`0111 $37 reserved
%0111`0111 $77 reserved
%1011`0111 $B7 reserved
%1111`0111 $F7 reserved
%0011`1000 $38 reserved
%0111`1000 $78 reserved
%1011`1000 $B8 reserved
%1111`1000 $F8 reserved
%0011`1001 $39 reserved
%0111`1001 $79 reserved
%1011`1001 $B9 reserved
%1111`1001 $F9 reserved
%0011`1010 $3A reserved
%0111`1010 $7A reserved
%1011`1010 $BA reserved
%1111`1010 $FA reserved
%0011`1011 $3B reserved
%0111`1011 $7B reserved
%1011`1011 $BB reserved
%1111`1011 $FB reserved
%0011`1100 $3C reserved
%0111`1100 $7C reserved
%1011`1100 $BC reserved
%1111`1100 $FC reserved
%0011`1101 $3D reserved
%0111`1101 $7D reserved
%1011`1101 $BD reserved
%1111`1101 $FD reserved
%0011`1110 $3E reserved
%0111`1110 $7E reserved
%1011`1110 $BE reserved
%1111`1110 $FE reserved
%0010`1111 $2F CMPIND regVal regAddr Set flags by subtracting source register value from value at address in destination register
%0110`1111 $6F CMPIND immVal regAddr Set flags by subtracting immediate value from value at address in destination register
%0011`0000 $30 TSTIND regVal regAddr Set flags by ANDing source register value with value at address in destination register
%0111`0000 $70 TSTIND immVal regAddr Set flags by ANDing immediate value with value at address in destination register
%0011`0001 $31 INC regVal Increment register by 1.
%0011`0010 $32 DEC regVal Decrement register by 1.
%0011`0011 $33 NOT regVal Bitwise negate value in register, store result in register.
%0011`0100 $34 SYS regVal Execute a system call using the system-call index stored in register (privileged)
%0111`0100 $74 SYS immVal Execute a system call using the immediate index (privileged)
%1010`1010 $AA NOP No operation. Used as an instruction placeholder.
%1110`0000 $E0 XCHG reg reg Atomically exchange the values in two registers
%1110`0001 $E1 SETCRY Set the Carry flag
%1110`0010 $E2 CLRCRY Clear the Carry flag
%1110`0011 $E3 CLRINT Clear the Interrupt flag, thereby disabling hardware interrupts (privileged)
%1110`0100 $E4 DUP Duplicate the top value on the stack
%1110`0101 $E5 SWAP Swap the top two values on the stack
%1111`1111 $FF BRK Trigger a debug break
%0000`xxxx $0 R0 register
%0001`xxxx $1 R1 register
%0010`xxxx $2 R2 register
%0011`xxxx $3 R3 register
%0100`xxxx $4 R4 register
%0101`xxxx $5 R5 register
%0110`xxxx $6 R6 register
%0111`xxxx $7 R7 register
%1000`xxxx $8 R8 register
%1001`xxxx $9 R9 register
%1010`xxxx $A RT register
%1011`xxxx $B RV register
%1100`xxxx $C RF register (flags)
%1101`xxxx $D RI register (instruction)
%1110`xxxx $E RP register (program segment / counter)
%1111`xxxx $F RS register (stack pointers)
%xxxx`0000 $0 X.B0 (1-byte data)
%xxxx`0001 $1 X.B1 (1-byte data)
%xxxx`0010 $2 X.B2 (1-byte data)
%xxxx`0011 $3 X.B3 (1-byte data)
%xxxx`0100 $4 X.B4 (1-byte data)
%xxxx`0101 $5 X.B5 (1-byte data)
%xxxx`0110 $6 X.B6 (1-byte data)
%xxxx`0111 $7 X.B7 (1-byte data)
%xxxx`1000 $8 X.Q0 (2-byte data)
%xxxx`1001 $9 X.Q1 (2-byte data)
%xxxx`1010 $A X.Q2 (2-byte data)
%xxxx`1011 $B X.Q3 (2-byte data)
%xxxx`1100 $C X.H0 (4-byte data)
%xxxx`1101 $D X.H1 (4-byte data)
%xxxx`1110 $E X (8-byte data)
%xxxx`0xxx Read immediate value as operand
%xxxx`1xxx Perform math operation with value (not implemented yet)
%xxxx`x000 $x0 instruction reads 1 byte immediate (8 bits)
%xxxx`x001 $x1 instruction reads 2-byte immediate (16 bits)
%xxxx`x010 $x2 instruction reads 4-byte immediate (32 bits)
%xxxx`x011 $x3 instruction reads 8-byte immediate (64 bits)
%0000`xxxx $0x ADD immediate to previous operand
%0001`xxxx $1x SUB immediate from previous operand
%0010`xxxx $2x MUL previous operand by immediate
%0011`xxxx $3x DIV previous operand by immediate
%0100`xxxx $4x AND previous operand with immediate
%0101`xxxx $5x OR previous operand with immediate
%0110`xxxx $6x XOR previous operand with immediate
%0111`xxxx $7x NOR previous operand with immediate
%1000`xxxx $8x NAND previous operand with immediate
%1001`xxxx $9x SHL previous operand by immediate
%1010`xxxx $Ax SHR previous operand by immediate
%1011`xxxx $Bx reserved
%1100`xxxx $Cx reserved
%1101`xxxx $Dx reserved
%1110`xxxx $Ex reserved
%1111`xxxx $Fx reserved
BIOS calls will track as closely as possible to the "standard" BIOS routines found on typical x86 PCs. The x86 registers used in BIOS calls will map to Maize registers as follows:
AX -> R0.Q0
AL -> R0.B0
AH -> R0.B1
BX -> R0.Q1
BL -> R0.B2
BH -> R0.B3
CX -> R0.Q2
CL -> R0.B4
CH -> R0.B5
DX -> R0.Q3
DL -> R0.B6
DH -> R0.B7
The first ten arguments to OS-level routines will be placed, from left to right, into the R0, R1, R2, R3, R4, R5, R6, R7, R8, and R9 registers. Any remaining arguments will be pushed onto the stack.
For example:
; Call C function "void random_os_function(int32_t a, const char *b, size_t c, int32_t* d)"
CP $0000`1234 R0 ; int32_t a
CP R9.H0 R1 ; const char* b, assuming the pointer is in R9.H0
CP $0000`00FF R2 ; size_t c
CP R8.H1 R3 ; int32_t* d, assuming the pointer is in R8.H1
CALL random_os_function
Return values will placed into the RV register. For example:
; Implement C function "int add(int a, int b)"
CP R0 RV
ADD R1 RV
RET
The same standard will be followed for syscall parameters. The syscall number will be placed into the R9 register prior to calling the interrupt.
; Output a string using sys_write
CP $01 R0 ; file descriptor 1 (STDOUT) in register R0
CP hello_world R1.H0 ; string address in register R1
CP hello_world_end R2
SUB hello_world R2 ; string length in register R2
CP $01 R9 ; syscall 1 = sys_write
INT $80 ; call sys_write
The same syscall may be made with the SYS instruction, which will execute the syscall directly.
; Output a string using sys_write, calling directly via SYS instruction
CP $01 R0 ; file descriptor 1 (STDOUT) in register G
CP hello_world R1.H0 ; string address in register H
CP hello_world_end R2
SUB hello_world R2 ; string length in register J
SYS $01 ; call sys_write
(This section is incomplete and a bit of a work in progress. Refer to HelloWorld.asm, stdlib.asm, and core.asm for practical examples.)
%00000001 binary
#123 decimal
$FFFE1000 hexadecimal
Other syntax, to be described more fully later:
LABEL labelName labelData | AUTO
DATA dataValue [dataValue] [dataValue] [...]
STRING "stringvalue"
ADDRESS address | labelName
Binary Hex Mnemonic Parameters Description
---------- --- -------- ---------- --------------------------------------------------------------------------------------------------------------------------------------
%0000`0000 $00 HALT Halt the clock, thereby stopping execution (privileged)
%0000`0001 $01 CP regVal reg Copy source register value into destination register
%0000`0010 $02 ST regVal regAddr Store source register value at address in second register
%0000`0011 $03 ADD regVal reg Add source register value to destination register
%0000`0100 $04 SUB regVal reg Subtract source register value from destination register
%0000`0101 $05 MUL regVal reg Multiply destination register by source register value
%0000`0110 $06 DIV regVal reg Divide destination register by source register value
%0000`0111 $07 MOD regVal reg Modulo destination register by source register value
%0000`1000 $08 AND regVal reg Bitwise AND destination register with source register value
%0000`1001 $09 OR regVal reg Bitwise OR destination register with source register value
%0000`1010 $0A NOR regVal reg Bitwise NOR destination register with source register value
%0000`1011 $0B NAND regVal reg Bitwise NAND destination register with source register value
%0000`1100 $0C XOR regVal reg Bitwise XOR destination register with source register value
%0000`1101 $0D SHL regVal reg Shift value in destination register left by value in source register
%0000`1110 $0E SHR regVal reg Shift value in destination register right by value in source register
%0000`1111 $0F CMP regVal reg Set flags by subtracting source register value from destination register
%0001`0000 $10 TEST regVal reg Set flags by ANDing source register value with destination register
%0001`0001 $11 CMPXCHG regVal reg Compare value in operand 2 with value in operand 3. If equal, set zero flag and copy value in operand 1 register into operand 2. Otherwise, clear zero flag and copy value in operand 2 into operand 3.
%0001`0010 $12 LEA regVal reg reg
%0001`0011 $13 CPZ regVal reg Copy source register value into destination register with sign extension
%0001`0100 $14 OUT regVal imm Output value in source register to destination port
%0001`0101 $15 LNGJMP regVal Jump to segment and address in source register and continue execution (privileged)
%0001`0110 $16 JMP regVal Jump to address in source register and continue execution
%0001`0111 $17 JZ regVal If Zero flag is set, jump to address in source register and continue execution
%0001`1000 $18 JNZ regVal If Zero flag is not set, jump to address in source register and continue execution
%0001`1001 $19 JLT regVal If Negative flag is not equal to Overflow flag, jump to address in source register and continue execution
%0001`1010 $1A JB reg If Carry flag is set, jump to address in source register and continue execution
%0001`1011 $1B JGT regVal If Zero flag is clear and Negative flag is not equal to Overflow flag, jump to address in source register and continue execution
%0001`1100 $1C JA regVal If Carry flag is clear and Zero flag is clear, jump to address in source register and continue execution
%0001`1101 $1D CALL regVal Push PC.H0 to stack, jump to address in source register and continue execution until RET is executed
%0001`1110 $1E OUTR regVal reg Output value in source register to port in destination register
%0001`1111 $1F IN regVal reg Read value from port in source register into destination register
%0010`0000 $20 PUSH regVal Copy register value into memory at location in RS.H0, decrement RS.H0 by size of register
%0010`0001 $21
%0010`0010 $22 CLR regVal Set register to zero (0).
%0010`0011 $23
%0010`0100 $24 INT regVal Push FL and PC to stack and generate a software interrupt at index stored in register (privileged)
%0010`0101 $25
%0010`0110 $26 POP regVal Increment SP.H0 by size of register, copy value at SP.H0 into register
%0010`0111 $27 RET Pop PC.H0 from stack and continue execution at that address. Used to return from CALL.
%0010`1000 $28 IRET Pop FL and PC from stack and continue execution at segment/address in PC. Used to return from interrupt (privileged).
%0010`1001 $29 SETINT Set the Interrupt flag, thereby enabling hardware interrupts (privileged)
%0010`1010 $2A
%0010`1011 $2B reserved
%0010`1100 $2C reserved
%0010`1101 $2D reserved
%0010`1110 $2E reserved
%0010`1111 $2F CMPIND regVal regAddr Set flags by subtracting source register value from value at address in destination register
%0011`0000 $30 TSTIND regVal regAddr Set flags by ANDing source register value with value at address in destination register
%0011`0001 $31 INC regVal Increment register by 1.
%0011`0010 $32 DEC regVal Decrement register by 1.
%0011`0011 $33 NOT regVal Bitwise negate value in register, store result in register.
%0011`0100 $34 SYS regVal Execute a system call using the system-call index stored in register (privileged)
%0011`0101 $35 reserved
%0011`0110 $36 reserved
%0011`0111 $37 reserved
%0011`1000 $38 reserved
%0011`1001 $39 reserved
%0011`1010 $3A reserved
%0011`1011 $3B reserved
%0011`1100 $3C reserved
%0011`1101 $3D reserved
%0011`1110 $3E reserved
%0011`1111 $3F
%0100`0000 $40
%0100`0001 $41 CP immVal reg Copy immediate value into destination register
%0100`0010 $42 ST immVal regAddr Store immediate value at address in destination register
%0100`0011 $43 ADD immVal reg Add immediate value to destination register
%0100`0100 $44 SUB immVal reg Subtract immediate value from destination register
%0100`0101 $45 MUL immVal reg Multiply destination register by immediate value
%0100`0110 $46 DIV immVal reg Divide destination register by immediate value
%0100`0111 $47 MOD immVal reg Modulo destination register by immediate value
%0100`1000 $48 AND immVal reg Bitwise AND destination register with immediate value
%0100`1001 $49 OR immVal reg Bitwise OR destination register with immediate value
%0100`1010 $4A NOR immVal reg Bitwise NOR destination register with immediate value
%0100`1011 $4B NAND immVal reg Bitwise NAND destination register with immediate value
%0100`1100 $4C XOR immVal reg Bitwise XOR destination register with immediate value
%0100`1101 $4D SHL immVal reg Shift value in destination register left by immediate value
%0100`1110 $4E SHR immVal reg Shift value in destination register right by immediate value
%0100`1111 $4F CMP immVal reg Set flags by subtracting immediate value from destination register
%0101`0000 $50 TEST immVal reg Set flags by ANDing immediate value with destination register
%0101`0001 $51 CMPXCHG immVal reg Compare value in operand 2 with value in operand 3. If equal, set zero flag and copy value in operand 1 immediate value into operand 2. Otherwise, clear zero flag and copy value in operand 2 into operand 3.
%0101`0010 $52 LEA immVal reg reg Add immediate value in operand 1 to value in operand 2 register and store result in operand 3 register
%0101`0011 $53 CPZ immVal reg Copy immediate value into destination register with sign extension
%0101`0100 $54 OUT immVal imm Output immediate value to destination port
%0101`0101 $55 LNGJMP immVal Jump to immediate segment and address and continue execution (privileged)
%0101`0110 $56 JMP immVal Jump to immediate address and continue execution
%0101`0111 $57 JZ immVal If Zero flag is set, jump to immediate address and continue execution
%0101`1000 $58 JNZ immVal If Zero flag is not set, jump to immediate address and continue execution
%0101`1001 $59 JLT immVal If Negative flag is not equal to Overflow flag, jump to immediate address and continue execution
%0101`1010 $5A JB imm If Carry flag is set, jump to immediate address and continue execution
%0101`1011 $5B JGT immVal If Zero flag is clear and Negative flag is not equal to Overflow flag, jump to immediate address and continue execution
%0101`1100 $5C JA immVal If Carry flag is clear and Zero flag is clear, jump to immediate address and continue execution
%0101`1101 $5D CALL immVal Push PC.H0 to stack, jump to immediate address and continue execution until RET is executed
%0101`1110 $5E OUTR immVal reg Output immediate value to port in destination register
%0101`1111 $5F IN immVal reg Read value from port in immediate value into destination register
%0110`0000 $60 PUSH immVal Copy immediate value into memory at location in RS.H0, decrement RS.H0 by size of immediate value
%0110`0001 $61
%0110`0010 $62
%0110`0011 $63
%0110`0100 $64 INT immVal Push FL and PC to stack and generate a software interrupt using immediate index (privileged)
%0110`0101 $65
%0110`0110 $66
%0110`0111 $67
%0110`1000 $68
%0110`1001 $69
%0110`1010 $6A
%0110`1011 $6B reserved
%0110`1100 $6C reserved
%0110`1101 $6D reserved
%0110`1110 $6E reserved
%0110`1111 $6F CMPIND immVal regAddr Set flags by subtracting immediate value from value at address in destination register
%0111`0000 $70 TSTIND immVal regAddr Set flags by ANDing immediate value with value at address in destination register
%0111`0001 $71
%0111`0010 $72
%0111`0011 $73
%0111`0100 $74 SYS immVal Execute a system call using the immediate index (privileged)
%0111`0101 $75 reserved
%0111`0110 $76 reserved
%0111`0111 $77 reserved
%0111`1000 $78 reserved
%0111`1001 $79 reserved
%0111`1010 $7A reserved
%0111`1011 $7B reserved
%0111`1100 $7C reserved
%0111`1101 $7D reserved
%0111`1110 $7E reserved
%0111`1111 $7F
%1000`0000 $80
%1000`0001 $81 LD regAddr reg Load value at address in source register into destination register
%1000`0010 $82
%1000`0011 $83 ADD regAddr reg Add value at address in source register to destination register
%1000`0100 $84 SUB regAddr reg Subtract value at address in source register from destination register
%1000`0101 $85 MUL regAddr reg Multiply destination register by value at address in source register
%1000`0110 $86 DIV regAddr reg Divide destination register by value at address in source register
%1000`0111 $87 MOD regAddr reg Modulo destination register by value at address in source register
%1000`1000 $88 AND regAddr reg Bitwise AND destination register with value at address in source register
%1000`1001 $89 OR regAddr reg Bitwise OR destination register with value at address in source register
%1000`1010 $8A NOR regAddr reg Bitwise NOR destination register with value at address in source register
%1000`1011 $8B NAND regAddr reg Bitwise NAND destination register with value at address in source register
%1000`1100 $8C XOR regAddr reg Bitwise XOR destination register with value at address in source register
%1000`1101 $8D SHL regAddr reg Shift value in destination register left by value at address in source register
%1000`1110 $8E SHR regAddr reg Shift value in destination register right by value at address in source register
%1000`1111 $8F CMP regAddr reg Set flags by subtracting value at address in source register from destination register
%1001`0000 $90 TEST regAddr reg Set flags by ANDing value at address in source register with destination register
%1001`0001 $91 CMPXCHG regAddr reg Compare value in operand 2 with value in operand 3. If equal, set zero flag and load value at address in operand 1 register into operand 2. Otherwise, clear zero flag and load value in operand 2 into operand 3.
%1001`0010 $92 LEA regAddr reg reg Add value at address in operand 1 register to value in operand 2 register and store result in operand 3 register
%1001`0011 $93 LDZ regAddr reg Load value at address in source register into destination register with sign extension
%1001`0100 $94 OUT regAddr imm Output value at address in source register to destination port
%1001`0101 $95 LNGJMP regAddr Jump to segment and address pointed to by source register and continue execution (privileged)
%1001`0110 $96 JMP regAddr Jump to address pointed to by source register and continue execution
%1001`0111 $97 JZ regAddr If Zero flag is set, jump to address pointed to by source register and continue execution
%1001`1000 $98 JNZ regAddr If Zero flag is not set, jump to address pointed to by source register and continue execution
%1001`1001 $99 JLT regAddr If Negative flag is not equal to Overflow flag, jump to address pointed to by source register and continue execution
%1001`1010 $9A JB regAddr If Carry flag is set, jump to address pointed to by source register and continue execution
%1001`1011 $9B JGT regAddr If Zero flag is clear and Negative flag is not equal to Overflow flag, jump to address pointed to by source register and continue execution
%1001`1100 $9C JA regAddr If Carry flag is clear and Zero flag is clear, jump to address pointed to by source register and continue execution
%1001`1101 $9D CALL regAddr Push PC.H0 to stack, jump to address pointed to by source register and continue execution until RET is executed
%1001`1110 $9E OUTR regAddr reg Output value at address in source register to port in destination register
%1001`1111 $9F IN regAddr reg Read value from port at address in source register into destination register
%1010`0000 $A0
%1010`0001 $A1
%1010`0010 $A2
%1010`0011 $A3
%1010`0100 $A4
%1010`0101 $A5
%1010`0110 $A6
%1010`0111 $A7
%1010`1000 $A8
%1010`1001 $A9
%1010`1010 $AA NOP No operation. Used as an instruction placeholder.
%1010`1011 $AB reserved
%1010`1100 $AC reserved
%1010`1101 $AD reserved
%1010`1110 $AE reserved
%1010`1111 $AF
%1011`0000 $B0
%1011`0001 $B1
%1011`0010 $B2
%1011`0011 $B3
%1011`0100 $B4
%1011`0101 $B5 reserved
%1011`0110 $B6 reserved
%1011`0111 $B7 reserved
%1011`1000 $B8 reserved
%1011`1001 $B9 reserved
%1011`1010 $BA reserved
%1011`1011 $BB reserved
%1011`1100 $BC reserved
%1011`1101 $BD reserved
%1011`1110 $BE reserved
%1011`1111 $BF
%1100`0000 $C0
%1100`0001 $C1 LD immAddr reg Load value at immediate address into destination register
%1100`0010 $C2
%1100`0011 $C3 ADD immAddr reg Add value at immediate address to destination register
%1100`0100 $C4 SUB immAddr reg Subtract value at immediate address from destination register
%1100`0101 $C5 MUL immAddr reg Multiply destination register by value at immediate address
%1100`0110 $C6 DIV immAddr reg Divide destination register by value at immediate address
%1100`0111 $C7 MOD immAddr reg Modulo destination register by value at immediate address
%1100`1000 $C8 AND immAddr reg Bitwise AND destination register with value at immediate address
%1100`1001 $C9 OR immAddr reg Bitwise OR destination register with value at immediate address
%1100`1010 $CA NOR immAddr reg Bitwise NOR destination register with value at immediate address
%1100`1011 $CB NAND immAddr reg Bitwise NAND destination register with value at immediate address
%1100`1100 $CC XOR immAddr reg Bitwise XOR destination register with value at immediate address
%1100`1101 $CD SHL immAddr reg Shift value in destination register left by value at immediate address
%1100`1110 $CE SHR immAddr reg Shift value in destination register right by value at immediate address
%1100`1111 $CF CMP immAddr reg Set flags by subtracting value at immediate address from destination register
%1101`0000 $D0 TEST immAddr reg Set flags by ANDing value at immediate address with destination register
%1101`0001 $D1 CMPXCHG immAddr reg Compare value in operand 2 with value in operand 3. If equal, set zero flag and load value at address in operand 1 immediate value into operand 2. Otherwise, clear zero flag and load value in operand 2 into operand 3.
%1101`0010 $D2 LEA immAddr reg reg Add value at immediate address in operand 1 to value in operand 2 register and store result in operand 3 register
%1101`0011 $D3 LDZ immAddr reg Load value at immediate address into destination register with sign extension
%1101`0100 $D4 OUT immAddr imm Output value at immediate address to destination port
%1101`0101 $D5 LNGJMP immAddr Jump to segment and address pointed to by immediate value and continue execution (privileged)
%1101`0110 $D6 JMP immAddr Jump to address pointed to by immediate value and continue execution
%1101`0111 $D7 JZ immAddr If Zero flag is set, jump to address pointed to by immediate value and continue execution
%1101`1000 $D8 JNZ immAddr If Zero flag is not set, jump to address pointed to by immediate value and continue execution
%1101`1001 $D9 JLT immAddr If Negative flag is not equal to Overflow flag, jump to address pointed to by immediate value and continue execution
%1101`1010 $DA JB immAddr If Carry flag is set, jump to address pointed to by immediate value and continue execution
%1101`1011 $DB JGT immAddr If Zero flag is clear and Negative flag is not equal to Overflow flag, jump to address pointed to by immediate value and continue execution
%1101`1100 $DC JA immAddr If Carry flag is clear and Zero flag is clear, jump to address pointed to by immediate value and continue execution
%1101`1101 $DD CALL immAddr Push PC.H0 to stack, jump to address pointed to by immediate value and continue execution until RET is executed
%1101`1110 $DE OUTR immAddr reg Output value at immediate address to port in destination register
%1101`1111 $DF IN immAddr reg Read value from port at immediate address into destination register
%1110`0000 $E0 XCHG reg reg Atomically exchange the values in two registers
%1110`0001 $E1 SETCRY Set the Carry flag
%1110`0010 $E2 CLRCRY Clear the Carry flag
%1110`0011 $E3 CLRINT Clear the Interrupt flag, thereby disabling hardware interrupts (privileged)
%1110`0100 $E4 DUP Duplicate the top value on the stack
%1110`0101 $E5 SWAP Swap the top two values on the stack
%1110`0110 $E6
%1110`0111 $E7
%1110`1000 $E8
%1110`1001 $E9
%1110`1010 $EA
%1110`1011 $EB reserved
%1110`1100 $EC reserved
%1110`1101 $ED reserved
%1110`1110 $EE reserved
%1110`1111 $EF
%1111`0000 $F0
%1111`0001 $F1
%1111`0010 $F2
%1111`0011 $F3
%1111`0100 $F4
%1111`0101 $F5 reserved
%1111`0110 $F6 reserved
%1111`0111 $F7 reserved
%1111`1000 $F8 reserved
%1111`1001 $F9 reserved
%1111`1010 $FA reserved
%1111`1011 $FB reserved
%1111`1100 $FC reserved
%1111`1101 $FD reserved
%1111`1110 $FE reserved
%1111`1111 $FF BRK Trigger a debug break