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:
- put the blitter in settings mode using control port
0xb0
- provide an address, and screen coordinates (x, y, width and height) using settings port
0xb1
- 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/