# 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, 8M 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 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 | | --- | --- | --- | | 0xf0 | read | Status of the controller 1 | | 0xf1 | read | Status of the controller 2 | ### 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. ``` 76543210 |||||||\ a (fire 1) ||||||\ b (fire 2) |||||\ d-pad up ||||\ d-pad down |||\ d-pad left ||\ d-pad right |\ select \ start ``` ### Palette Current palette (not final) is the default EGA 16 colour 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: ```asm ; setup an int handler so we ; can use the frame int o sync ld a, 0xff ld x, 0 ; lower 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. ### 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, and being 8 MIPS it can fit `133333` 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: ```asm ; 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: ```asm ; 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). .equ label imm Define a label assigning an arbitrary immediate. .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 colours will be matched with the TR8 palette and any non-matching color will be condiered 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. #### 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 #### 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](https://www.usebox.net/jjm/about/me/). The code is MIT licensed.