aboutsummaryrefslogtreecommitdiff

TR-8

Important: this is a work in progress with pre-alpha quality. Not all the information in this document is final, complete or even accurate.

TR8 is an 8-bit fantasy console inspired by MIPS, Z80 and 6502 real world CPUs.

Specs

Display:  128 x 128 pixels 16 colors
Memory:   64K
CPU:      TR-8, 4M VM instr/sec
Sound:    TBD

Other features:

  • programmable in ASM
  • 16-bit registers: stack pointer (SP) and program counter (PC)
  • 8-bit registers: 4 general purpose registers (a, b, x, y), flags register (F)
  • frame interrupt (60Hz) and external IO interrupt (planned)
  • port based IO
  • hardware blitter

Memory map:

0x0000
        program (48896 bytes)
0xbf00
        frame buffer (16K)
0xff00
        frame interrupt vector
0xff02
        IO interrupt vector
0xff04
        stack (251 bytes)
0xffff

How to build

It requires SDL2 and SDL_Mixer.

On Linux install a C compiler and the development packages for the requirements.

Example on Debian/Ubuntu using sudo:

sudo apt install build-essential libsdl2-dev libsdl2-mixer-dev

Then run make.

This will result in two binaries:

  • tr8as: the TR8 assembler
  • tr8vm: the TR8 VM Player that can run tr8 files

You can then compile and run the example with make example.

Detailed specs

Ports

Port Type Result
0xb0 write Blitter control
0xb1 write Blitter settings
0xf0 read Status of the controller 1
0xf1 read Status of the controller 2

The blitter

TR8 implements a hardware blitter that can be controlled by writing to ports.

On a successful port call, the read value equals to the port number, so the call preserves the port register.

To draw with the blitter the following steps are required:

  1. put the blitter in settings mode using control port 0xb0
  2. provide an address, and screen coordinates (x, y, width and height) using settings port 0xb1
  3. set the blitter in one of the draw modes using the control port 0xb0

The address will be the source data in write mode, and the destination in read mode.

Step 3 can be repeated as many times as needed, the blitter will keep the settings until the control port is set in settings mode.

The settings byte is as follow:

Bits Effect
x0000000 Set blitter in settings mode
0000000x Write to video RAM
000000x0 The source includes a transparent bit
00000x00 Read from video RAM
0xxxx000 Unused

Example:

    ; blitter in settings mode
    ld x, 128
    ld a, 0xb0
    port a, x

    ; setup
    inc a
    ; source
    ld x, <sprite
    port a, x
    ld x, >sprite
    port a, x
    ; destination (56, 56)
    ld x, 56
    port a, x
    port a, x
    ; size 16x16
    ld x, 16
    port a, x
    port a, x

    ; now blit
    dec a
    ld x, 3
    ; 3: write with transparent color support
    port a, x

When bit 2 in the settings byte is set to 1 and the most significant bit in the source data is set (e.g. 128), that pixel won't be drawn (transparent).

Controller

The controller support d-pad with 4 directions, 2 action buttons, select and start.

Read the port 0xf0 for the 8 1-bit flags reporting the state of the controller 1, and 0xf1 for controller 2.

Bits Meaning
0000000x a (fire 1)
000000x0 b (fire 2)
00000x00 d-pad up
0000x000 d-pad down
000x0000 d-pad left
00x00000 d-pad right
0x000000 select
x0000000 start

Palette

Current palette (not final) is the default EGA 16 color palette:

Index RGB (hex)
0 0x000000
1 0x0000aa
2 0x00aa00
3 0x00aaaa
4 0xaa0000
5 0xaa00aa
6 0xaa5500
7 0xaaaaaa
8 0x555555
9 0x5555ff
10 0x55ff55
11 0x55ffff
12 0xff5555
13 0xff55ff
14 0xffff55
15 0xffffff

Frame interrupt

When interrupts are enabled (IF not set), 60 times per second there will be a frame interrupt:

  • the PC is saved on the stack
  • the F register (flags) is saved on the stack
  • all the flags are cleared and IF is set
  • the execution goes to the address in 0xff00 (the frame interrupt vector)
  • the interruption handler code must return with IRET, that will restore the PC and the F register

By default the vector points to 0x0000 so it has to be setup before enabling interrupts with CIF.

Example:

    ; setup an int handler so we
    ; can use the frame int to sync
    ld a, 0xff
    ld x, 0
    ; lower byte of the address
    ld b, <int_handler
    ld [a : x], b
    inc x
    # higher byte of the address
    ld b, >int_handler
    ld [a : x], b

    ; enable interrupts
    cif

loop:
    halt
    jmp loop

int_handler:
    ; just return
    iret

Because the flags are preserved, any code run in the interrupt handler only needs to preserve the 4 registers and the stack.

Labels and IDs

In tr8as an ID can have letters, numbers and special characters: $_.#<>, but can't start with > or <, or match one of the reserved keywords.

A label is defined as an ID that ends in :.

Instructions

All the instructions are 16-bit, with the exception of JMP addr and CALL addr that use an extra 16-bit parameter for "addr". All the instructions take the same time to run (with the exception of cmpi, cmpd, ldi and ldd that take extra time), and being 4 MIPS it can fit 66666 instructions in a frame (at 60Hz).

There are no 16-bit registers, but they can be implemented with 2 registers, and that is supported in some addressing modes with [r1:r2].

For example:

    ; fill with color 15
    ld a, 15
    push a
    call fill
    pop a

    ; halt the CPU
    sif
    halt

    ;
    ; void fill(uint8_t color)
    ;
    ; fill frame-buffer with a color
    ;
fill:
    ; grab the color from the stack
    ld b, [sp + 2]
    ; 64 * 256 is 16K
    ld y, 64
    ; a:x is our 16-bit register
    ld a, 0xbf
    ld x, 0
fill_loop:
    ld [a : x], b
    inc x
    bno
    jmp fill_loop
    ; x overflows, so we increment a
    ; and decrement y (one 256 block done)
    inc a
    dec y
    bnz
    jmp fill_loop
    ret

Arithmetic operations on the "16-bit" register can be performed easily using branching instructions:

    ; inc a:x
    inc x
    ; if x overflows we need to increase a
    bo
    inc a

Assembler directives

.org addr
Set the address to that value. By default the starting address is 0x0000.

.db imm [, imm]
Literal byte. Label can be used with < prefix for the low byte of the address and >for the high byte.

.dw imm [, imm]
Literal word (16-bit value).

.str "string"
Include the string as literal bytes. Use \" to escape a quote and \\ to escape de backslash. The string is not zero ended, use .db to add a zero if needed.

.ds count, imm
Generates count bytes with value imm.

.equ id value
Define an ID assigning a constant value. When the id is used, it will be replaced with its value.

.include "filename"
Assemble the file at current position.

.incbin "filename"
Read the file and add the content to the output at current position.

.incpng "filename"
Read the PNG image and add the content to the output at current position. The image colors will be matched with the TR8 palette and any non-matching color will be considered transparent and the index 128 will be used.

The blitter won't draw any pixel with index larger than 15.

Load and Store

LD r1, r2
Load the value of r2 in r1.

LD r1, imm
Load the immediate value in r1.

LD [r1:r2], r3
Load the value of r3 in the memory address provided by r1:r2.

LD r3, [r1:r2]
Load into r3 the value in the memory address provided by r1:r2.

LD [SP + imm], r1
Load r1 into the stack on top address plus the immediate value.

LD r1, [SP + imm]
Load into r1 the stack value on top address plus the immediate value.

LDI
Perform a LD of [a : x] to [b : y], and increments x (and a if x overflows) and y (and b if y overflows). This instruction takes 4 times longer than a regular instruction.

LDD
Perform a LD of [a : x] to [b : y], and decrements x (and a if x overflows) and y (and b if y overflows). This instruction takes 4 times longer than a regular instruction.

Stack

PUSH r1
Save the value of r1 on the top of the stack.

POP r1
Restore the value of r1 by popping it from the top of the stack.

PUSH F
Save the flags in the stack.

POP F
Restore the flags from the top of the stack.

XSP r1
Exchange the high byte of the stack pointer by the value in r1.

Logical

AND r1, r2
Logical AND between r1 and r2, storing the result in r1.
Affected flags: ZF, SF

AND r1, imm
Logical AND between r1 and immediate value, storing the result in r1.
Affected flags: ZF, SF

OR r1, r2
Logical OR between r1 and r2, storing the result in r1.
Affected flags: ZF, SF

OR r1, imm
Logical OR between r1 and immediate value, storing the result in r1.
Affected flags: ZF, SF

XOR r1, r2
Logical exclusive OR between r1 and r2, storing the result in r1.
Affected flags: ZF, SF

XOR r1, imm
Logical exclusive OR between r1 and immediate value, storing the result in r1.
Affected flags: ZF, SF

Arithmetic

INC r1
Incremet r1.
Affected flags: ZF, CF, OF, SF

DEC r1
Decrement r1.
Affected flags: ZF, CF, OF, SF

ADD r1, r2
Add r1 and r2, storing the result in r1.
Affected flags: ZF, CF, OF, SF

ADD r1, imm
Add r1 and the immediate value, storing the result in r1.
Affected flags: ZF, CF, OF, SF

SUB r1, r2
Subtract r2 to r1, storing the result in r1.
Affected flags: ZF, CF, OF, SF

SUB r1, imm
Subtract the immediate value to r1, storing the result in r1.
Affected flags: ZF, CF, OF, SF

CMP r1, r2
Perform a SUB of r1 minus r2, without storing the result and only updating the flags.
Affected flags: ZF, CF, OF, SF

CMP r1, imm
Perform a SUB of r1 minus the immediate value without storing the result and only updating the flags.
Affected flags: ZF, CF, OF, SF

CMPI
Perform a SUB of [a : x] minus [b : y], without storing the result and only updating the flags. It also increments x (and a if x overflows) and y (and b if y overflows). The flags are not affected by incrementing a, b, x and y. This instruction takes 4 times longer than a regular instruction.
Affected flags: ZF, CF, OF, SF

CMPD
Perform a SUB of [a : x] minus [b : y], without storing the result and only updating the flags. It also decrements x (and a if x overflows) and y (and b if y overflows). The flags are not affected by decrementing a, b, x and y. This instruction takes 4 times longer than a regular instruction.
Affected flags: ZF, CF, OF, SF

Bit Operations

SHL r1, n
Shift r1 n bits to the left (0 to 7), storing the result in r1.
Affected flags: ZF, CF, SF

SHR r1, n
Shift r1 n bits to the right (0 to 7), storing the result in r1.
Affected flags: ZF, CF, SF

ROL r1, n
Rotate r1 n bits to the left (0 to 7), storing the result in r1.
Affected flags: ZF, CF, SF

ROR r1, n
Rotate r1 n bits to the right (0 to 7), storing the result in r1.
Affected flags: ZF, CF, SF

BIT r1, n
Test bit n (0 to 7) or r1, setting ZF if it is set or clearing it otherwise. Affected flags: ZF

Jump and Call

JMP addr
Set the PC to the 16-bit address.

CALL addr
Store the next PC in the stack (16-bit address) and sets the PC to the 16-bit address.

CALL [r1:r2]
Store the next PC in the stack (16-bit address) and sets te PC to the 16-bit address provided by r1:r2.

RET
Return from a call by setting PC to the top 16-bit value popped from the stack.

Branching

BZ inst
Branch if Zero, will run the next instruction if ZF is set.
Affected flags: BF

BNZ inst
Branch if not Zero, will run the next instruction if ZF flag is not set.
Affected flags: BF

BC inst
Branch if Carry, will run the next instruction if CF flag is set.
Affected flags: BF

BNC inst
Branch if not Carry, will run the next instruction if CF flag is not set.
Affected flags: BF

BO inst
Branch if Overflow, will run the next instruction if OF flag is set.
Affected flags: BF

BNO inst
Branch if not Overflow, will run the next instruction if OF flag is not set.
Affected flags: BF

BS inst
Branch if Sign, will run the next instruction if SF flag is set.
Affected flags: BF

BNS inst
Branch if not Sign, will run the next instruction if SF flag is set.
Affected flags: BF

BI inst
Branch if Interrupt, will run the next instruction if IF flag is set.
Affected flags: BF

BNI inst
Branch if not Interrupt, will run the next instruction if IF flag is not set.
Affected flags: BF

IO, flags and Misc

HALT
Stop the execution until there is frame interrupt. If the interruption flag is set, this will hang the CPU.

PORT r1, r2
Write the value of r2 in the port number provided by r1. If there is an output, the value will be stored in r1.

NOP
No instruction has no effect.

SIF
Set IF, disabling the interrupt.
Affected flags: IF

CIF
Clear IF, enabling the interrupt. If called in an interrupt handler, it won't have effect. Use IRET instead to return from the interrupt.
Affected flags: IF

CCF
Clear carry flag.
Affected flags: CF

SCF
Set carry flag.
Affected flags: CF

COF
Clear overflow flag.
Affected flags: OF

IRET
Return from an interrupt handler by restoring F and PC from the stack.
Affected flags: IF

TR8 file format

It is a binary file to be run on the TR-8.

It doesn't have a header and it will be loaded at 0x0000 address. The execution will start on that address with interruptions disabled (interruption flag set).

See the memory map for further information.

Author and Licence

This was made by Juan J. Martinez.

The code is MIT licensed.

Project website: https://git.usebox.net/tr8vm/about/