TECHNICAL INFORMATION

Emulating ZX Spectrum on such a slow machine as TI89 is, requires a lot of knowledge of ZX Spectrum emulators to make emulation as fast as possible. Thus chapter requires knowledge of Z80 and 68000 assembler.

Decoding

The most critical part is decoding of instructions. Emulator interprets Z80 instructions and based on them executes subroutines in 68000 assembly language that execute similar code. Written in C language, such routine could be

while (!escaped) {pc++; switch memory[pc] {case 0: nop();break; case 1: ldbcNN(); break; case 2: ldmbca(); break....}}

but it is too slow. Imagine how slow would be checking memory addresses, executing subroutines and testing for escape, if we used classic methods.

Instead, I decided that emulator will be 64 K long. Look the example of decoding

 

01BB80: 1B 5E B1 10 MOVE.B (A6)+,(-$4EF0,A5)

01BB84: 4E E4 20 04 JMP ($2004,A4)

where A6 simulates PC and A5 and A4 almost always in this example have same value 0020A76. First instruction takes operation code from address pointed by A6 (pseudo PC) and puts it at address 20A76-4EF0 = 1BB86. Assuming that A6 pointed to value C5, the second instruction is modified to JMP ($CD04,A4). At this address started emulation of instruction CALL, which has operation code CD! Well, this is selfmodified code, something that every programmers book avoids, but it is in the most critical routine. Adding only one instruction in decoding routine will slow down emulator for 20%. I can not imagine faster decoding routine, without many additional memory tables.

Z80 has also instructions with twobyte operation code. So for every instruction I have 256 bytes. These 256 bytes are organized as:

 

0 Interrupt routine

4 Instruction without prefix

40 Instruction after prefix ED

76 Instruction after prefix CB

104 Instruction after DAA instruction

112 Instruction after prefix DD

148 Instruction after prefix FD

184 Instruction after DD CB prefix

220 Instruction after FD CB prefix

 

The main advantage of such schema is big speed. Disadvantage is bigger size of emulator and that longer routines must be cuted in blocks of about 30 bytes.

Decoding routine is put at end of every instruction. Instructions that has opcode which is indeed prefix execute slightly different decoding routine, A5 register is used in both instructions, and lover byte of offset in second (jump) instruction is not 4 but value corresponding with prefix. (for example 4C (76) for instructions after CB). Meaning of A4 and A5 registers will be described in chapter about interrupt emulation.

Registers

To make emulator as fast as possible, Z80 registers are emulated by 68000 registers. The purpose of 68000 registers in Tezxas are:

D0 - upper 16 bits hold register A', lower 16 bits hold register A

D1 - holds pseudo flag registers. Z80 has more flags than 68000, so in upper 16 bits is SR register that corresponds to F' register , in lower 16 bits is SR register that corresponds to F register. Z80 has two additional documented (N, H) and two undocumented flags (F3, F5). They are not emulated, because no regularry written program uses undocumented flags, and documented flags are used only by DAA instruction which is implemented in different way. Sometimes occur hazard use of them, however.

D2 - upper 16 bits hold register pair BC', lower 16 bits hold register pair BC

D3 - upper 16 bits hold register pair DE', lower 16 bits hold register pair DE

D4 - upper 16 bits hold zero, lower 16 bits hold register pair HL

D5 - upper 16 bits hold zero, lower 16 bits are used as help register

D6 - upper 16 bits hold HL', lower 16 bits are used as help register

D7 - upper 16 bits hold zero, lower 16 bits are used as SP

A0 - points to table of flag conversion

A1 - points to the beginning of emulated Z80 memory

A2 - Pseudo IX register, i.e. conatins A1+IX. Putting index register in address register makes faster instructions like
LD A,(IX+23)

A3 - Pseudo IY register, i.e. contains A1+IY

A4 - points to the middle of the 68000 code that emulates Z80 instructions or to address lower by 4

A5 - points to the middle of the 68000 code that emulates Z80 instructions

A6 - Pseudo PC, points to current Z80 instruction, i.e. contains A1+PC. This means that PC can be beyond 65535, but I copied first 128 bytes of RAM at emulated address 65536 and the program will soon finish in restart routine as should be.

 

Of course, while executing special routines (as screen updating) these registers have different values.

Someone can remark that putting emulated SP into address register could make faster instruction like PUSH, POP, CALL and RET, however this can make crash of the whole calculator, not only emulated ZX Spectrum program.

I register and interrupt flip/flops are kept in memory, while R register is calculated as random number with kept highest bit in memory.

Interrupt emulation

The only source of interrupt on ZX Spectrum is nonprogramable timer that bits 50 times per second. Processor then jumps to address got from table pointed by I register in case IM2, or to address 38h (IM0 and IM1).

Timer on TI89 is set to same value. To avoid executing interrupt routine inside emulated Z80 instruction, TI89 interrupt routine only sets A4 to A5 - 4 (if emulated interrupt is enabled) and returns to execution of the emulated Z80 instruction. At end of the emulated Z80 instruction is decoding the next instruction. After we changed A5, JMP instruction in decoding routine will not start next instruction, but service routine, which will put emulated PC on stack, disable further interrupts, change emulated PC to the begining of emulated interrupt routine, put A4 to original value (A5)and execute decoding routine.

Tezxas is slower than original machine and sometimes occur that interrupt routine is not well synchronised with rest of the program. Some programs can crash in this case. One possible solution is slowing down interrupt frequence.

Screen emulation

Rather than updating screen together with emulating of instructions, or checking if target address belongs to video memory, Tezxas updates whole screen at once, 5 times per second. It is done with double buffering, Tezxas checks which bytes were changed and if they were, it modifies corresponding bytes of the real video memory. It is the fastest way of video updating, and solves problems of sprite flickering in many cases. However, sometimes it is necessary to change moment of screen update to make program faster/smoother, or reduce flickering.

TI89 and TI92+ have smaller screen than ZX Spectrum, and screen must be reduced. See example in FAQ with differences of video modes.

Other hardware

**** In construction ****

How to single step through Tezxas

If game crashed on Tezxas, it is necessary to discover why. My method is following: I start VTI to emulate TI89 on my PC, and load Tezxas with game on it. At the same time I start some good ZX Spectrum emulator for PC with debugger (I use Warajevo in CGA mode, to make it running in window). With F11 I start debugger on VTI. then I paralelly single step through both emulators. Compare shown Z80 registers on one emulator with shown 68000 registers on other emulator.If you need breakpoint when "double emulated" program reachs some Spectrum PC address, on VTI debugger select Set data breakpoint - Single byte read at A1 + PC.

File format

 

Snapshot files

0000: signature **TI89**
0008: 1,0
000A foldername spec
000E padded foldername 0,0,0,0
0010 repeated zeros
003A 1,0,$52,0,0,0
0040 filename padded to 8 bytes with zeros
0048 $B,0,$F,$0
004C File size with header, little endian (csize+$F0)
004E 0,0,$A5,$5A,0,0,0,0
0056 File size without header, big endian (csize+$96)
0058 Z80 registers I, L', H', E', D', C', B', F', A'
0060 L, H, E, D, C, B, IYl, IYh, IXl, IXh, IFF2, R, F, SPl, SPh, Imode, BORDER
0073 size of compressed memory (big endian) (csize)
0075 sound options /s
0076 remaping mode of scan line /lu:=0 /ld:=1 /la:=3 /lb:=2
0077 screen updating moment /m
0078 IRQ perion /i
0079 Refresh rate of screen /r
007A Keyboard remap /k
00DA snapshot indicator to avoid crash $37
00DB reverse option
00DE padded with zeros - reserved
00EE Compressed program, used LZ77 algorythm which has signatures that show
whetether byte to output file is copied from input file, or group of bytes
from output file is copied to another place in output file. See decompression routine.
****
xxxx $F3 , EXPR indicator
xxxx 0
xxxx two byte checksum, little endian, calculated as sum of all bytes from address $56 to last zero

Decompression routine

DECOMP0
move.w #4096,D0
CLRBDBUF clr.b (A0)+
dbra.s D0,CLRBDBUF
sub.l #4096,A0
move.b (A1)+,D6
rol.w #8,D6
move.b (A1)+,D6
rol.w #8,D6
clr.w D0
move.w #4078,D1
DECODE2 lsr.w #1,D0
btst.w #8,D0
bne DECODE3
bsr GETC
move.w #$FF00,D0
move.b D3,D0
DECODE3 btst.b #0,D0
beq DECODE4
bsr GETC
move.b D3,0(A0,D1.w)
addq.w #1,D1
and.w #4095,D1
move.b D3,(A2)+
bra DECODE2
DECODE4 bsr GETC
move.b D3,D4
bsr GETC
clr.w D5
move.b D3,D5
asl.w #4,D5
move.b D4,D5
move.b D3,D2
and.b #$0F,D2
addq.b #3,D2
DECODE5 and.w #4095,D5
move.b 0(A0,D5.w),D3
move.b D3,0(A0,D1.w)
addq.w #1,D1
and.w #4095,D1
move.b D3,(A2)+
addq.w #1,D5
subq.b #1,D2
bne DECODE5
bra DECODE2
GETC move.b (A1)+,D3
subq.w #1,D6
beq EOF
rts

Tape files

0000: signature **TI89**
0008: 1,0
000A foldername spec
000E padded foldername 0,0,0,0
0010 repeated zeros
003A 1,0,$52,0,0,0
0040 filename padded to 8 bytes with zeros
0048 $B,0,$F,$0
004C File size with header, little endian (totlen + $5F)
004E 0,0,$A5,$5A,0,0,0,0
0056 File size without header, big endian (totlen+5)
0058 File size of used tape (filesize of source .TAP file)
005A Total size of tape, including unused space (totlen)
005C tape as in Z80 TAP format
xxxx Padded with $FF to fill unused space
xxxx 0
xxxx two byte checksum, little endian, calculated as sum of all bytes from address $56 to last zero