An introduction to 3BA
3BINS Assembly is designed to be extremely simple - it has only eight opcodes (with variations between them). The following table describes all fifteen mnemonics provided, alongside their meanings and word, doubleword, and quadword variants.
This tutorial assumes the reader already has a decent grasp on normal assembly and its paradigms. For a more useful assembly tutorial, check out Tutorialspoint: Assembly Programming Tutorial
Instruction Set
Mnemonic | Meaning | 16 Bit Variant | 32 Bit Variant | 64 Bit Variant |
---|---|---|---|---|
MMI | Move Memory <- Immediate | MMIW | MMID | MMIQ |
MOV | Move Memory <- Memory | MOVW | MOVD | MOVQ |
ADD | Add | ADDW | ADDD | ADDQ |
SUB | Subtract | SUBW | SUBD | SUBQ |
OR | Bitwise OR | ORW | ORD | ORQ |
NOT | Bitwise NOT | NOTW | NOTD | NOTQ |
INT | System Interrupt | INTW | INTD | INTQ |
CMP | Compare | CMPW | CMPD | CMPQ |
JMP | Unconditional Jump | JMPW | JMPD | JMPQ |
JE | Jump if Equal | JEW | JED | JEQ |
JNE | Jump if Not Equal | JNEW | JNED | JNEQ |
JG | Jump if Greater | JGW | JGD | JGQ |
JL | Jump if Less | JLW | JLD | JLQ |
JO | Jump if Overflow | JOW | JOD | JOQ |
JC | Jump if Carry | JCW | JCD | JCQ |
Missing Instructions
Users may notice the absence of several instructions - namely AND and XOR. These can be expressed as combinations of other operations, therefore they were not included in the instruction set. For example, AND can be expressed as the following:
NOT(OR[NOT(A), NOT(B)])
The above can be expressed in 3BINS as the following, given input locations A
and B
, and expected output in location A
:
.and:
NOT A
NOT B
OR A, B
NOT A
Reserved Locations
3BINS has eight bytes of reserved space - from 0x00 to 0x07. The first doubleword, from 0x00 to 0x03, acts as a 32-bit program counter. The second, from 0x04 to 0x07, acts as a FLAGS register for recording the results of arithmetic and comparison instructions.
Assembler
The current assembler is made with customasm. As such, it follows the rules and features of customasm as described in the docs.
12-Bit CPU Definition
; This CPUDEF defines the encoding for a 12-bit mode compatible 3BINS assembler
#cpudef
{
#bits 8
MOV {dst}, {src} -> 0b000 @ 0b00000 @ dst[11:0] @ src[11:0]
MMI {dst}, {imm} -> 0b000 @ 0b00001 @ dst[11:0] @ imm[11:0]
ADD {dst}, {src} -> 0b001 @ 0b00000 @ dst[11:0] @ src[11:0]
SUB {dst}, {src} -> 0b010 @ 0b00000 @ dst[11:0] @ src[11:0]
OR {dst}, {src} -> 0b011 @ 0b00000 @ dst[11:0] @ src[11:0]
NOT {dst} -> 0b100 @ 0b00000 @ dst[11:0] @ 0b0000 @ 0b00000000
INT {imm} -> 0b101 @ 0b00000 @ imm[23:0]
CMP {dst}, {src} -> 0b110 @ 0b00000 @ dst[11:0] @ src[11:0]
JMP {mem} -> 0b111 @ 0b00000 @ mem[23:0]
JE {mem} -> 0b111 @ 0b00001 @ mem[23:0]
JNE {mem} -> 0b111 @ 0b00010 @ mem[23:0]
JG {mem} -> 0b111 @ 0b00011 @ mem[23:0]
JL {mem} -> 0b111 @ 0b00100 @ mem[23:0]
JO {mem} -> 0b111 @ 0b00101 @ mem[23:0]
JC {mem} -> 0b111 @ 0b00110 @ mem[23:0]
}
16-Bit CPU Definition
; This CPUDEF defines the encoding for a 16-bit mode compatible 3BINS assembler
#cpudef
{
#bits 8
MOV {dst}, {src} -> 0b000 @ 0b00000 @ dst[11:0] @ src[11:0]
MMI {dst}, {imm} -> 0b000 @ 0b00001 @ dst[11:0] @ imm[11:0]
ADD {dst}, {src} -> 0b001 @ 0b00000 @ dst[11:0] @ src[11:0]
SUB {dst}, {src} -> 0b010 @ 0b00000 @ dst[11:0] @ src[11:0]
OR {dst}, {src} -> 0b011 @ 0b00000 @ dst[11:0] @ src[11:0]
NOT {dst} -> 0b100 @ 0b00000 @ dst[11:0] @ 0b0000 @ 0b00000000
INT {imm} -> 0b101 @ 0b00000 @ imm[23:0]
CMP {dst}, {src} -> 0b110 @ 0b00000 @ dst[11:0] @ src[11:0]
JMP {mem} -> 0b111 @ 0b00000 @ mem[23:0]
JE {mem} -> 0b111 @ 0b00001 @ mem[23:0]
JNE {mem} -> 0b111 @ 0b00010 @ mem[23:0]
JG {mem} -> 0b111 @ 0b00011 @ mem[23:0]
JL {mem} -> 0b111 @ 0b00100 @ mem[23:0]
JO {mem} -> 0b111 @ 0b00101 @ mem[23:0]
JC {mem} -> 0b111 @ 0b00110 @ mem[23:0]
MOVW {dst}, {src} -> 0b000 @ 0b10000 @ dst[11:0] @ src[11:0]
MMIW {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]]
ADDW {dst}, {src} -> 0b001 @ 0b10000 @ dst[11:0] @ src[11:0]
SUBW {dst}, {src} -> 0b010 @ 0b10000 @ dst[11:0] @ src[11:0]
ORW {dst}, {src} -> 0b011 @ 0b10000 @ dst[11:0] @ src[11:0]
NOTW {dst} -> 0b100 @ 0b10000 @ dst[11:0] @ 0b0000 @ 0b00000000
INTW {imm} -> 0b101 @ 0b10000 @ imm[23:0]
CMPW {dst}, {src} -> 0b110 @ 0b10000 @ dst[11:0] @ src[11:0]
JMPW {mem} -> 0b111 @ 0b10000 @ mem[23:0]
JEW {mem} -> 0b111 @ 0b10001 @ mem[23:0]
JNEW {mem} -> 0b111 @ 0b10010 @ mem[23:0]
JGW {mem} -> 0b111 @ 0b10011 @ mem[23:0]
JLW {mem} -> 0b111 @ 0b10100 @ mem[23:0]
JOW {mem} -> 0b111 @ 0b10101 @ mem[23:0]
JCW {mem} -> 0b111 @ 0b10110 @ mem[23:0]
}
32-Bit CPU Definition
; This CPUDEF defines the encoding for a 32-bit mode compatible 3BINS assembler
#cpudef
{
#bits 8
MOV {dst}, {src} -> 0b000 @ 0b00000 @ dst[11:0] @ src[11:0]
MMI {dst}, {imm} -> 0b000 @ 0b00001 @ dst[11:0] @ imm[11:0]
ADD {dst}, {src} -> 0b001 @ 0b00000 @ dst[11:0] @ src[11:0]
SUB {dst}, {src} -> 0b010 @ 0b00000 @ dst[11:0] @ src[11:0]
OR {dst}, {src} -> 0b011 @ 0b00000 @ dst[11:0] @ src[11:0]
NOT {dst} -> 0b100 @ 0b00000 @ dst[11:0] @ 0b0000 @ 0b00000000
INT {imm} -> 0b101 @ 0b00000 @ imm[23:0]
CMP {dst}, {src} -> 0b110 @ 0b00000 @ dst[11:0] @ src[11:0]
JMP {mem} -> 0b111 @ 0b00000 @ mem[23:0]
JE {mem} -> 0b111 @ 0b00001 @ mem[23:0]
JNE {mem} -> 0b111 @ 0b00010 @ mem[23:0]
JG {mem} -> 0b111 @ 0b00011 @ mem[23:0]
JL {mem} -> 0b111 @ 0b00100 @ mem[23:0]
JO {mem} -> 0b111 @ 0b00101 @ mem[23:0]
JC {mem} -> 0b111 @ 0b00110 @ mem[23:0]
MOVW {dst}, {src} -> 0b000 @ 0b10000 @ dst[11:0] @ src[11:0]
MMIW {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]
ADDW {dst}, {src} -> 0b001 @ 0b10000 @ dst[11:0] @ src[11:0]
SUBW {dst}, {src} -> 0b010 @ 0b10000 @ dst[11:0] @ src[11:0]
ORW {dst}, {src} -> 0b011 @ 0b10000 @ dst[11:0] @ src[11:0]
NOTW {dst} -> 0b100 @ 0b10000 @ dst[11:0] @ 0b0000 @ 0b00000000
INTW {imm} -> 0b101 @ 0b10000 @ imm[23:0]
CMPW {dst}, {src} -> 0b110 @ 0b10000 @ dst[11:0] @ src[11:0]
JMPW {mem} -> 0b111 @ 0b10000 @ mem[23:0]
JEW {mem} -> 0b111 @ 0b10001 @ mem[23:0]
JNEW {mem} -> 0b111 @ 0b10010 @ mem[23:0]
JGW {mem} -> 0b111 @ 0b10011 @ mem[23:0]
JLW {mem} -> 0b111 @ 0b10100 @ mem[23:0]
JOW {mem} -> 0b111 @ 0b10101 @ mem[23:0]
JCW {mem} -> 0b111 @ 0b10110 @ mem[23:0]
MOVD {dst}, {src} -> 0b000 @ 0b01000 @ dst[11:0] @ src[11:0]
MMID {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]
ADDD {dst}, {src} -> 0b001 @ 0b01000 @ dst[11:0] @ src[11:0]
SUBD {dst}, {src} -> 0b010 @ 0b01000 @ dst[11:0] @ src[11:0]
ORD {dst}, {src} -> 0b011 @ 0b01000 @ dst[11:0] @ src[11:0]
NOTD {dst} -> 0b100 @ 0b01000 @ dst[11:0] @ 0b0000 @ 0b00000000
INTD {imm} -> 0b101 @ 0b01000 @ imm[23:0]
CMPD {dst}, {src} -> 0b110 @ 0b01000 @ dst[11:0] @ src[11:0]
JMPD {mem} -> 0b111 @ 0b01000 @ mem[23:0]
JED {mem} -> 0b111 @ 0b01001 @ mem[23:0]
JNED {mem} -> 0b111 @ 0b01010 @ mem[23:0]
JGD {mem} -> 0b111 @ 0b01011 @ mem[23:0]
JLD {mem} -> 0b111 @ 0b01100 @ mem[23:0]
JOD {mem} -> 0b111 @ 0b01101 @ mem[23:0]
JCD {mem} -> 0b111 @ 0b01110 @ mem[23:0]
}
64-Bit CPU Definition
; This CPUDEF defines the encoding for a 64-bit mode compatible 3BINS assembler
#cpudef
{
#bits 8
MOV {dst}, {src} -> 0b000 @ 0b00000 @ dst[11:0] @ src[11:0]
MMI {dst}, {imm} -> 0b000 @ 0b00001 @ dst[11:0] @ imm[11:0]
ADD {dst}, {src} -> 0b001 @ 0b00000 @ dst[11:0] @ src[11:0]
SUB {dst}, {src} -> 0b010 @ 0b00000 @ dst[11:0] @ src[11:0]
OR {dst}, {src} -> 0b011 @ 0b00000 @ dst[11:0] @ src[11:0]
NOT {dst} -> 0b100 @ 0b00000 @ dst[11:0] @ 0b0000 @ 0b00000000
INT {imm} -> 0b101 @ 0b00000 @ imm[23:0]
CMP {dst}, {src} -> 0b110 @ 0b00000 @ dst[11:0] @ src[11:0]
JMP {mem} -> 0b111 @ 0b00000 @ mem[23:0]
JE {mem} -> 0b111 @ 0b00001 @ mem[23:0]
JNE {mem} -> 0b111 @ 0b00010 @ mem[23:0]
JG {mem} -> 0b111 @ 0b00011 @ mem[23:0]
JL {mem} -> 0b111 @ 0b00100 @ mem[23:0]
JO {mem} -> 0b111 @ 0b00101 @ mem[23:0]
JC {mem} -> 0b111 @ 0b00110 @ mem[23:0]
MOVW {dst}, {src} -> 0b000 @ 0b10000 @ dst[11:0] @ src[11:0]
MMIW {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]
ADDW {dst}, {src} -> 0b001 @ 0b10000 @ dst[11:0] @ src[11:0]
SUBW {dst}, {src} -> 0b010 @ 0b10000 @ dst[11:0] @ src[11:0]
ORW {dst}, {src} -> 0b011 @ 0b10000 @ dst[11:0] @ src[11:0]
NOTW {dst} -> 0b100 @ 0b10000 @ dst[11:0] @ 0b0000 @ 0b00000000
INTW {imm} -> 0b101 @ 0b10000 @ imm[23:0]
CMPW {dst}, {src} -> 0b110 @ 0b10000 @ dst[11:0] @ src[11:0]
JMPW {mem} -> 0b111 @ 0b10000 @ mem[23:0]
JEW {mem} -> 0b111 @ 0b10001 @ mem[23:0]
JNEW {mem} -> 0b111 @ 0b10010 @ mem[23:0]
JGW {mem} -> 0b111 @ 0b10011 @ mem[23:0]
JLW {mem} -> 0b111 @ 0b10100 @ mem[23:0]
JOW {mem} -> 0b111 @ 0b10101 @ mem[23:0]
JCW {mem} -> 0b111 @ 0b10110 @ mem[23:0]
MOVD {dst}, {src} -> 0b000 @ 0b01000 @ dst[11:0] @ src[11:0]
MMID {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]
ADDD {dst}, {src} -> 0b001 @ 0b01000 @ dst[11:0] @ src[11:0]
SUBD {dst}, {src} -> 0b010 @ 0b01000 @ dst[11:0] @ src[11:0]
ORD {dst}, {src} -> 0b011 @ 0b01000 @ dst[11:0] @ src[11:0]
NOTD {dst} -> 0b100 @ 0b01000 @ dst[11:0] @ 0b0000 @ 0b00000000
INTD {imm} -> 0b101 @ 0b01000 @ imm[23:0]
CMPD {dst}, {src} -> 0b110 @ 0b01000 @ dst[11:0] @ src[11:0]
JMPD {mem} -> 0b111 @ 0b01000 @ mem[23:0]
JED {mem} -> 0b111 @ 0b01001 @ mem[23:0]
JNED {mem} -> 0b111 @ 0b01010 @ mem[23:0]
JGD {mem} -> 0b111 @ 0b01011 @ mem[23:0]
JLD {mem} -> 0b111 @ 0b01100 @ mem[23:0]
JOD {mem} -> 0b111 @ 0b01101 @ mem[23:0]
JCD {mem} -> 0b111 @ 0b01110 @ mem[23:0]
MOVQ {dst}, {src} -> 0b000 @ 0b11000 @ dst[11:0] @ src[11:0]
MMIQ {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]
ADDQ {dst}, {src} -> 0b001 @ 0b11000 @ dst[11:0] @ src[11:0]
SUBQ {dst}, {src} -> 0b010 @ 0b11000 @ dst[11:0] @ src[11:0]
ORQ {dst}, {src} -> 0b011 @ 0b11000 @ dst[11:0] @ src[11:0]
NOTQ {dst} -> 0b100 @ 0b11000 @ dst[11:0] @ 0b0000 @ 0b00000000
INTQ {imm} -> 0b101 @ 0b11000 @ imm[23:0]
CMPQ {dst}, {src} -> 0b110 @ 0b11000 @ dst[11:0] @ src[11:0]
JMPQ {mem} -> 0b111 @ 0b11000 @ mem[23:0]
JEQ {mem} -> 0b111 @ 0b11001 @ mem[23:0]
JNEQ {mem} -> 0b111 @ 0b11010 @ mem[23:0]
JGQ {mem} -> 0b111 @ 0b11011 @ mem[23:0]
JLQ {mem} -> 0b111 @ 0b11100 @ mem[23:0]
JOQ {mem} -> 0b111 @ 0b11101 @ mem[23:0]
JCQ {mem} -> 0b111 @ 0b11110 @ mem[23:0]
}
Crash Course
Following is a crash course source code that is also a tutorial:
; Comments begin with a semicolon and are ignored by the compiler.
myvar1 = 0x77 ; Constants are defined as above
myvar2 = myvar1 + 0x11
start: ; Labels mark memory addresses.
; Start has special meaning as the program entry point.
MMI 0x08, 32 ; Move the immediate decimal value 32 into memory
; at address 0x08
MOV 0x0C, 0x08 ; Move (Copy) the value at address 0x08 into 0x0C
MOV 0x08, myvar1 ; Move the value of the constant myvar1 into 0x08.
JMP jumphere ; JMP jumps unconditionally to the provided label
skipme: ; This code will be skipped by the JMP above.
NOT 0x08 ; NOT inverts all the bits in a doubleword
NOT 0x0C
OR 0x08, 0x0C ; OR performs a bitwise OR on the two operands and stores
; the result in the left operand.
NOT 0x08 ; This implicitly leaves the result in location 0x08
jumphere:
MMI 0x10, 5
MMI 0x14, 10
ADD 0x10, 0x14 ; Adds 5 to 10, and stores the result in 0x10
; Every arithmetic and bitwise operation changes
; a set of flags which is tested by certain jumps
MMI 0xFE, 0
CMP 0x10, 0xFE ; Compare the value at 0x10 (15) with the value at 0xFE (0)
JE jumphere ; this will never trigger, because 5+10 is not equal to zero
SUB 0x10, 0x14 ; This evaluates as 0x10(15) - 0x14(10), and stores the result in 0x10
CMP 0x10, 0x14 ; This compares the values in 0x10 and 0x14 and sets flags
JL loops ; This will trigger because the CMP found that the values
; of 0x10 < 0x14
func: ; This function computes (a+b)-c. It expects a, b, and c
; to be found at 0x18, 0x1C, and 0x1F, respectively. It
; will leave the output at 0x24, and leave the inputs untouched.
; Finally, it expects a return address at 0x28.
MOV 0xE0, 0x18 ; Copy the values into temporary memory so they dont
MOV 0xE4, 0x1C ; get clobbered.
MOV 0xE8, 0x1F
ADD 0xE0, 0xE4 ; Do the computation
SUB 0xE0, 0xE8
MOV 0x24, 0xE0 ; Move the result into the expected location.
JMP 0x28 ; JMP-ing to a memory address takes the value at that addr
; as the jump destination.
loops:
MMI 0x20, 5
MMI 0x24, 1
.loop: ; Local Labels begin with a . and are only valid in the scope of the outer label.
SUB 0x20, 0x24 ; Decrement the counter.
MMI 0xF0, 0
CMP 0x20, 0xF0
JE .loopEnd ; If the counter is zero, skip to the end of the loop. Otherwise,
JMP .loop ; Restart loop
.loopEnd:
MMI 0x28, ret ; Set return address
ret:
MMI 0x18, 5 ; Set up input data.
MMI 0x1C, 8
MMI 0x1F, 2
JMP func ; Jump to the subroutine
This assembles to:
(Hexdump)
00 | 01 00 80 20 00 00 c0 08 00 00 80 77 e0 00 00 20 | ... .......w... |
10 | 80 00 80 00 80 00 c0 00 60 00 80 0c 80 00 80 00 | ........`....... |
20 | 01 01 00 05 01 01 40 0a 20 01 00 14 01 0f e0 00 | [email protected] ....... |
30 | c0 01 00 fe e1 00 00 20 40 01 00 14 c0 01 00 14 | ....... @....... |
40 | e4 00 00 60 00 0e 00 18 00 0e 40 1c 00 0e 80 1f | ...`[email protected] |
50 | 20 0e 00 e4 40 0e 00 e8 00 02 40 e0 e0 00 00 28 | [email protected]@....( |
60 | 01 02 00 05 01 02 40 01 40 02 00 24 01 0f 00 00 | [email protected]@..$.... |
70 | c0 02 00 f0 e1 00 00 7c e0 00 00 68 01 02 80 80 | ...........h.... |
80 | 01 01 80 05 01 01 c0 08 01 01 f0 02 e0 00 00 44 | ...............D |
(Raw)
010080200000c00800008077e0000020800080008000c0006000800c80008000010100050101400a20010014010fe000c00100fee100002040010014c0010014e4000060000e0018000e401c000e801f200e00e4400e00e8000240e0e0000028010200050102400140020024010f0000c00200f0e100007ce000006801028080010180050101c0080101f002e0000044
Setting up a Stack
Since 3BINS by default has no stack, it has no way of passing information to and from subroutines and system calls besides pre-defined memory locations. Of course, that method gets messy quickly, since there are no established standards. As such, any 3BINS-compatible environment provides a CPU interrupt to establish a stack, if the running program requires one.
To define a stack, call CPU interrupt 0xC2
. The interrupt expects an array of 32-bit wide parameters stored beginning at 0x08
(the third doubleword) in memory. The parameters are as follows:
- An integer value representing the stack width, in bits.
- A pointer to the location of the stack pointer, initialized to the base of the stack.
- In 12, 16, and 32-bit mode, this will be interpreted as an immediate value representing the start address of the stack.
- In 64-bit mode, this will be interpreted as a 32-bit wide pointer to a 64-bit value representing the start address of the stack
- The address to push and pop to
- This behaves the same as the previous parameter.
Afterwards, CPU interrupts 0xD0 and 0xD1 will push and pop to the specified location, respectively. The stack grows downwards in memory.