1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
|
# 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: OPL3
```
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 |
| 0xc0 | write | Sound register selector (primary) |
| 0xc1 | write | Sound data (primary) |
| 0xc2 | write | Sound register selector (secondary)|
| 0xc3 | write | Sound data (secondary) |
| 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:
```asm
; 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` |
### Sound: OPL3
TR8 includes an OPL3 emulator that can be conttolled using ports `0xc0` to
select register and `0xc1` to write data in the primary set of registers
(channels 0 to 8), and `0xc2` to select register and `0xc3` to write data in
the secondary set of registers (channels 9 to 17).
There are a few good resources online on how to program this chip:
* [Programming the AdLib/Sound Blaster FM Music Chips](http://www.shipbrook.net/jeff/sb.html)
* [Programmer's Guide to Yamaha YMF 262/OPL3 FM Music Synthesizer](https://www.fit.vutbr.cz/~arnost/opl/opl3.html)
Also look at the example code in this repo for some simple sound drivers.
### 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
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, 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:
```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).
**.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 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.
Project website: https://git.usebox.net/tr8vm/about/
|