45M Manual

45M is a tiny virtual console. It can execute sequences of bytes that have a special meaning. These sequences are called "ROMs" and the bytes "machine code". To make writing ROMs easier for Humans, 45M also has an assembly language. The language exposes special words that translate directly to machine words. These are called mnemonics. In this book, we will learn about the mnemonics to build simple games with 45M.

Getting started

If you want to follow along, you can open the 45M editor, or have a text file open. Every 45M program fit in a single file. Now let's start writing some 45M.

All 45M ROMs must start with `0 0` or other numbers. We will learn why soon.

0 0 LIT 1 INC

There is a lot going on in the above. Let's work through it.

First, LIT is an instruction that tells 45M to push the next number into the parameter stack.

Then, INC is another instruction that increases by one the top of the parameter stack.

If you haven't done so, execute the above program. You should see that the pstack now has 02 stored in it.

Manipulating the stack

Now that we've seen the stack, let's see how we can manipulate it.

LIT lets you push the next number to the stack. LIT2 lets you push the next two numbers:

LIT2 5 5

The above is the same as LIT 5 LIT 5, but saves one byte.

You can read about the other instructions in the glossary. Let's look at DUP. It says that the purpose is the "Duplicate the top of the stack". There's another column called "Effect" which says:

n -- n n

This is a way to indicate the effect of DUP on the stack. DUP requires a number to be on the stack. This number is the "n" represented on the left of "--". After its execution, the stack now has "n" twice.

Learning how to read stack effects is a great way to quickly see what parameters an instruction needs on the stack, and how it will modify the stack.

Arithmetic

All numbers are expressed in hexadecimal.

LIT f \ push the decimal value 15 to the stack

It's possible to represent a number in binary.

LIT #11111111 \ push the decimal value 15 to the stack

Let's add two numbers together:

LIT2 1 1 ADD

Now, let's multiply the result by two:

LIT 2 MUL

How about we divide it by 4?

LIT 4 DIV

If you ever need a random number, you can use the RND instruction which will place on the stack a random number in the 0 255 range.

Labels and addresses

When your program gets assembled, every mnemonic gets mapped to a corresponding machine code value, which fits in a byte. Since every instruction fits in a single byte, we can refer to a particular place in your ROM by its offset. In 45M, this offset is called an address.

Imagine the following program:

0 0 LIT2 5 5 ADD

The address of the LIT2 instruction is 2. The address of the ADD instruction is 5. Let's now look at the following program:

0 0 start: LIT2 5 5 ADD

In the above code, we added a label called start. It could have been called anything else. When 45M's preprocessor finds a label, it remembers its name and location, so that we can use this location later on.

Jumping

Let's look at the following program:

0 0 LIT @start JMP
start: LIT2 5 5 ADD BRK
LIT 2

Let's try to understand what happens. When executed, the Instruction Pointer (IP) starts at address 2. It finds LIT, so it pushes the number that follows it into the stack. In this case, @start represents the memory address of the start label, which is 5. Our stack now has a 5 in it. Then it finds a JMP instruction. The JMP instruction tells the IP to take the value of the top of the stack, so 5, and to carry on. Our program then executes the LIT2 instruction, then the ADD one, and finally BRK. BRK is a special instruction that stops the execution of the program. Meaning that the remaining LIT 2 instruction won't be executed.

There are different ways to jump. For example, JMR will store the position of the next instruction in a special stack called the return stack. This allows to resume the execution with the RET instruction.

Drawing on the screen

The screen on 45M is 16x16 pixels, meaning that a screen position can be encoded in one byte. The address of the top left pixel is 00, and the address of the bottom right if ff. The high nibble represents the rows, and the low nibble the columns. For example, the pixel at the 4th row and 10th column is 4a.

Pixels are 1 byte values that encode a color. The format used is RGB332. Let's look at the following example:

1 1 1 0 0 0 0 0

We know that a byte can be represented with 8 bits. In RGB 332, the highest 3 bits encode the R (red). In the above, the first three bits are set to 1, so the resulting color will be red.

1 1 1 0 0 1 0 1

As you can see, colors can be mixed by turning on or off the relevant bit.

Let's now draw a blue pixel on the screen:

0 0 ZER LIT2 #00000011 0 SET

Let's unpack. ZER is an instruction that pushes 0 to the pstack. It's the equivalent of LIT 0, but saves one byte. Then, we push the color blue, followed by 0. This 0 represents the layer. 0 is background, 1 is foreground. Finally, SET draws the pixel on the screen.

Controller

Like most consoles, 45M has a controller. A very basic one! It has your usual up left down right keys, as well as A and B which are mapped the the X and C key of the keyboard, respectively.

You can push to the stack the value of the current key being pressed with the KEY instruction. The value has the following format:

0 0 0 B A R L D U

B: the B button
A: the A button
R: the right arrow
L: the left arrow
D: the down arrow
U: the up arrow

Vectors

Remember, each ROM starts with two values: 0 0. These values are actually memory addresses. Let's take the following example:

@frame 0 BRK
frame: ZER LIT2 #00000011 0 SET

Let's start with memory address 0. It's the screen vector. The value at this address will be called 60 times per second. In the example above, we have set it to the frame label.

Now let's continue with memory address 1. The controller vector:

@frame @key BRK
frame: ZER LIT2 #00000011 0 SET
key: KEY

The key vector will be executed anytime a key from the controller has been pressed.

Creating a snake game

snake_color= ff
target_color= LIT #0010101
load= ZER LDB
store= ZER STB

@game @kpress

LIT2 55 0 STB
LIT2 54 1 STB
LIT2 54 2 STB

BRK

game:
  FRM LIT 4 MOD IFB
  LIT2 0 1 CLS
  LITa @target $target_color KUP SET
  LIT @move JMR
  $load KUP LDS LIT @hit JCR
  LIT @draw JMR
BRK

move:
  LITa @len DEC
  domove:
    PSH
    RSI DEC LDB
    RSI STB
    PUL DEC DUP ZER NEQ
    LIT @domove JCN
  POP
  LITa @dir CTL @up @down @left @right 0 0
RET
  up:
    $load CFZ AND NOT LIT @lost JCN
    $load TEN SUB $store
  RET
  down:
    $load CFZ AND LIT f0 EQU LIT @lost JCN
    $load TEN ADD $store
  RET
  left:
    $load CZF AND NOT LIT @lost JCN
    $load DEC $store
  RET
  right:
    $load CZF AND LIT f EQU LIT @lost JCN
    $load INC $store
  RET

draw:
  LITa @len
  dodraw:
    PSH
    RSI DEC LDB LIT2 $snake_color 1 SET
    PUL
    DEC DUP LIT 1 NEQ LIT @dodraw JCN
  POP
  $load KUP LDS LIT $snake_color EQU LIT @lost JCR
  $load LIT2 $snake_color 1 SET
RET

hit:
  LITa @len DEC LDB LITa @len STB
  LITa @len INC LIT @len STA
  RND LIT @target STA
RET

lost: LIT2 0 0 STA BRK

kpress: KEY LIT @dir STA BRK

dir: 8
len: 3
target: dd
			

Specs

Memory layout

Screen

Preprocessor

Macros

Labels

Execution

Instructions

Mnemonic Effect Purpose
Stack manipulation
LIT :: n Push following number to the stack, increase IP by 2
LIT2 :: n1 n2 Push n1 and n2 to the stack, increase IP by 3
LITa :: a Push the value at address a to stack, move IP by 2
POP n Pop top of the stack
DUP n -- n n Duplicate top of the stack
SWP n1 n2 -- n2 n1 Swap top of the stack
ROT n1 n2 n3 -- n2 n3 n1 Rotate top of the stack
OVR n1 n2 -- n1 n2 n1 Copy n1 to top of the stack
PSH n -- Take value off pstack and push it to rstack
PUL -- n Pull value off rstack and push it to pstack
RSI -- n Copy top of rstack without affecting it
RSJ -- n Copy second item of rstack without affecting it
Memory access
STA n a Store n to address
LDA a -- n Load from address
LDS n l -- n Load pixel from screen layer l
STB n a Store n in buffer at address a
LDB a -- n Load from buffer address a
Arithmetic
ADD n1 n2 -- n1+n2 Add top two stack values
SUB n1 n2 -- n1-n2 Substract top stack value from second
INC n -- n+1 Increment top of the stack
DEC n -- n-1 Decrement top of the stack
MUL n1 n2 -- n1*n2 Multiply top two stack values
DIV n1 n2 -- n1/n2 Divide top two stack values
MOD n1 n2 -- n1%n2 Divide n1 by n2 and push remainder
RND -- n Push random number
Comparisons
EQU n1 n2 -- f Check if n1 and n2 are equal
NEQ n1 n2 -- f Check if n1 and n2 are different
Bitwise
AND n1 n2 -- n1&n2 Push n1 & n2 to the stack
INV n -- ~n Invert the bits of n
Logic
NOT n -- !n Push 1 if n is 0, 0 otherwise
Flow
BRK Stop evaluation
RET Pop rstack into IP
JMP a Set the IP to a
JMR a Push IP+1 to rstack, then JMP
IFB f BRK if f is 0, otherwise continue
JCN f a Set IP to a if f is not 0
JCR f a Push IP+1 to rstack, then JCN
Controller
KEY -- n Push the key being pressed
CTL k -- :: u d l r a b Switch on k, set IP to adr or move IP by 8
Screen
CLS c l Set screen layer to color
SET o c l Set pixel at offset o to color a on layer l
HLN o w c l Draw hline at offset o with width of w, color c on layer l
VLN o h c l Draw vline at offset o with height of h, color c on layer l
FRM -- n Push current frame to stack
SPR a o c l Draw 2 byte sprite from address a to offset o
Constants
ZER -- 0 Push zero
TEN -- 0x10 Push 10
KUP -- 0b00000001 Push controller value for UP
KDO -- 0b00000010 Push controller value for DOWN
KLE -- 0b00000100 Push controller value for LEFT
KRI -- 0b00001000 Push controller value for RIGHT
KEA -- 0b00010000 Push controller value for A
KEB -- 0b00100000 Push controller value for B
CZF -- 0f Push 0f
CFZ -- f0 Push f0
CFF -- ff Push ff