1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module iv
.zymosis
.z80emu
/*is aliced*/;
20 //version = Zymosis_Testing; // to use with FUSE testing suite
21 //version = Zymosis_Check_Contention;
23 version(Zymosis_Testing
) pragma(msg
, "Zymosis: building test version");
24 version(Zymosis_Check_Contention
) pragma(msg
, "Zymosis: do some sanity checks for contention tables");
27 // ////////////////////////////////////////////////////////////////////////// //
28 alias ZymCPU
= ZymCPUBase
!16384; ///
30 public class ZymCPUBase(ushort MemPageSize
=16384) {
33 static struct MemPage
{
34 //enum Size = 4096; // in page
35 //enum Size = 16384; // in page
36 enum Size
= MemPageSize
; /// in page
37 ubyte* mem
; /// pointer to Size bytes
38 bool contended
; /// is this page contended?
39 bool rom
; /// read-only?
40 bool writeHook
; /// call write hook before something was written to this page
45 enum Z80Flag
: ubyte {
46 C
= 0x01, /// carry flag
47 N
= 0x02, /// add/substract flag (0: last was add, 1: last was sub)
48 PV
= 0x04, /// parity/overflow flag
50 H
= 0x10, /// half-carry flag
52 Z
= 0x40, /// zero flag
53 S
= 0x80, /// sign flag
59 /// Previous instruction type.
61 LdIorR
= -1, /// LD A,I or LD A,R
62 Normal
, /// normal instruction
63 BlockInt
/// EI/FD/DD (they blocks /INT)
67 union RegPair(string hi
, string lo
) {
70 version(LittleEndian
) {
71 mixin("struct { align(1): ubyte "~lo
~", "~hi
~"; }");
74 mixin("struct { align(1): ubyte "~hi
~", "~lo
~"; }");
76 alias w
this; // allow to use RegPair as ushort
80 RegPair
!("h", "l")* DD
; // pointer to current HL/IX/IY (inside this class) for the current command
81 ubyte mIM
; // Interrupt Mode (0-2)
84 MemPage
[65536/MemPage
.Size
+1] mem
; /// machine memory; MUST be initialized before executing anything
85 bool[65536] bpmap
; /// breakpoint map; breakpoint hook will be called if this is true; no autoreset
86 ubyte[] ulacont
; /// contention for memory access, with mreq; indexed by `tstates`
87 ubyte[] ulacontport
; /// contention for memory access operation, without mreq; indexed by `tstates`
89 RegPair
!("b", "c") BC
; ///
90 RegPair
!("d", "e") DE
; ///
91 RegPair
!("h", "l") HL
; ///
92 RegPair
!("a", "f") AF
; ///
93 RegPair
!("xh", "xl") IX
; ///
94 RegPair
!("yh", "yl") IY
; ///
95 /// alternate registers
96 RegPair
!("b", "c") BCx
; ///
97 RegPair
!("d", "e") DEx
; ///
98 RegPair
!("h", "l") HLx
; ///
99 RegPair
!("a", "f") AFx
; ///
100 RegPair
!("hi", "lo") MEMPTR
; /// special MEMPTR register
101 ubyte I
, R
; /// C.O.: I and R registers respectively
102 ushort SP
; /// stack pointer
103 ushort PC
; /// program counter
104 ushort prevPC
; /// first byte of the previous (before last) executed command
105 ushort lastPC
; /// first byte of the last executed command
106 bool IFF1
, IFF2
; /// interrupt flip-flops
107 /** is CPU halted? main progam must manually reset this flag when it's appropriate
108 * Zymosis will automatically reset it in intr() and nmi(). */
110 int tstates
; /// t-states passed from previous interrupt (0-...)
111 int nextEventTS
= -1; /// zym_exec() will exit when tstates>=nextEventTS
112 /** previous instruction type.
113 * Zymosis will reset this flag to Normal only if it executed at least one instruction. */
115 bool evenM1
; /// emulate 128K/Scorpion M1 contention?
116 bool bpHit
; /// will be set if execution loop was stopped by breakpoint; autoreset
117 ubyte flags_q
; /// SCF/CCF emulation: presumably, internal register where Z80 assembles the new content of the F register, before moving it back to F
121 * Page execute hook. Will be called before opcode fetching and tstate changing.
122 * Ideal place to switch ROMs.
124 * PC is current instruction address (to be fetched). Entering page `mpage`.
126 void execHook () nothrow @trusted {}
128 /// this will be called before writing occurs
129 void memWriteHook (ushort addr
, ubyte b
) nothrow @trusted {}
134 * PC is current instruction address (to be fetched). Return `true` to break emulation loop.
136 bool checkBreakpoint () nothrow @trusted { return false; }
138 version(Zymosis_Testing
) {
140 * Will be called when port contention is necessary.
141 * Function must increase z80.tstates by at least 'atstates' arg.
143 * Default: tstates += atstates;
146 * port = port address
147 * atstates = how much tstates we should spend (always 1 when 'early' is set and 2 otherwise)
148 * doIN = true, if this is 'IN' instruction and false if this is 'OUT' instruction
149 * early = true, if doing 'early' port contention (yes, ZX Spectrum port contention is complex)
154 void portContention (ushort port
, int atstates
, bool doIN
, bool early
) nothrow @trusted { tstates
+= atstates
; }
156 void memContention (ushort addr
, bool mreq
) nothrow @trusted {}
157 void memReading (ushort addr
) nothrow @trusted {}
158 void memWriting (ushort addr
, ubyte b
) nothrow @trusted {}
162 * Read byte from emulated port.
165 * addr = port address
168 * readed byte from emulated port
170 abstract ubyte portRead (ushort port
) nothrow @trusted;
173 * Write byte to emulated port.
176 * addr = port address
177 * value = byte to store
182 abstract void portWrite (ushort port
, ubyte value
) nothrow @trusted;
184 /// get byte from memory, don't trigger any breakpoints or such
185 public final ubyte memPeekB (ushort addr
) nothrow @trusted {
186 pragma(inline
, true);
187 return mem
.ptr
[addr
/MemPage
.Size
].mem
[addr
%MemPage
.Size
];
190 /// set byte in memory, don't trigger any breakpoints or such, but respect ROM
191 public final void memPokeB (ushort addr
, ubyte b
) nothrow @trusted {
192 pragma(inline
, true);
193 if (!mem
.ptr
[addr
/MemPage
.Size
].rom
) mem
.ptr
[addr
/MemPage
.Size
].mem
[addr
%MemPage
.Size
] = b
;
197 * This function will be called on invalid ED command (so-called 'trap command').
198 * CPU's PC will point to the next instruction (use origpc to inspect trap code).
200 * trapCode=0xFB: .SLT trap
201 * HL: address to load;
202 * A: A --> level number
203 * return: CARRY complemented --> error
206 * trapCode = actual trap code
209 * 'stop emulation' flag (return true to stop emulation loop immediately)
211 bool trapED (ubyte trapCode
) nothrow @trusted { return false; }
214 * This function will be called *AFTER* RETI command executed, iff changed and return address set.
217 * opcode = actual opcode (there is more than one RETI in Z80)
220 * 'stop emulation' flag (return true to stop emulation loop immediately)
222 bool trapRETI (ubyte opcode
) nothrow @trusted { return false; }
225 * This function will be called *AFTER* RETN command executed, iff changed and return address set.
228 * opcode = actual opcode (there is more than one RETI in Z80)
231 * 'stop emulation' flag (return true to stop emulation loop immediately)
233 bool trapRETN (ubyte opcode
) nothrow @trusted { return false; }
236 // ////////////////////////////////////////////////////////////////////// //
245 /** "Soft" reset: clear only absolutely necessary things. */
247 PC
= prevPC
= lastPC
= 0;
254 prevWasEIDDR
= EIDDR
.Normal
;
259 /** Reset emulated CPU. Will NOT reset tstate counter. */
260 void reset (bool poweron
=false) {
261 //TODO: don't touch IX and IY values?
262 BC
= DE
= HL
= AF
= SP
= IX
= IY
= (poweron ?
0xFFFF: 0);
263 BCx
= DEx
= HLx
= AFx
= (poweron ?
0xFFFF: 0);
268 /** This will be called before reset seqience is complete. */
269 abstract void setupMemory ();
271 // ////////////////////////////////////////////////////////////////////////// //
273 @property ubyte IM () const pure nothrow @safe @nogc { pragma(inline
, true); return mIM
; } /** get Interrupt Mode (0-2) */
274 @property void IM (in ubyte v
) pure nothrow @safe @nogc { pragma(inline
, true); mIM
= (v
> 2 ?
0 : v
); } /** set Interrupt Mode (0-2) */
276 /** increment R register. note that Z80 never changes the high bit of R. */
277 void incR () pure nothrow @safe @nogc { pragma(inline
, true); R
= ((R
+1)&0x7f)|
(R
&0x80); }
280 * Execute emulated CPU instructions.
281 * This function will execute Z80 code until tstates reaches nextEventTS
282 * or at least tscount tstates passed.
283 * Note that it can spend more that tscount states and slightly miss
284 * nextEventTS so tstates will be >= nextEventTS.
286 * WARNING: don't decrease tstates value in callbacks, or everything
287 * will be really bad!
290 * tscount = how much tstates we should spend executing;
291 * pass -1 to execute until nextEventTS reached
294 * number of tstates actually spent
296 int exec (int tscount
=-1) nothrow @trusted {
297 static bool isRepeated (ushort opc
) pure nothrow @safe @nogc { pragma(inline
, true); return ((opc
&0x10) != 0); }
298 static bool isBackward (ushort opc
) pure nothrow @safe @nogc { pragma(inline
, true); return ((opc
&0x08) != 0); }
302 ubyte tmpB
, tmpC
, rsrc
, rdst
;
304 int tstart
= tstates
;
306 /* previous Q for SCF/CCF */
309 /* `code` is guaranteed to be correct (i.e. [0..7]) */
310 static bool doCond (ubyte code
, ubyte flags
) {
312 final switch (code&0x07) {
313 case 0: return (flags&Z80Flag.Z) == 0;
314 case 1: return (flags&Z80Flag.Z) != 0;
315 case 2: return (flags&Z80Flag.C) == 0;
316 case 3: return (flags&Z80Flag.C) != 0;
317 case 4: return (flags&Z80Flag.PV) == 0;
318 case 5: return (flags&Z80Flag.PV) != 0;
319 case 6: return (flags&Z80Flag.S) == 0;
320 case 7: return (flags&Z80Flag.S) != 0;
324 /* branch-less (assuming that `!` is branch-less) code */
325 immutable ubyte[4] ccmask
= [Z80Flag
.Z
, Z80Flag
.C
, Z80Flag
.PV
, Z80Flag
.S
];
326 return (!(flags
&ccmask
[code
>>1]))^
(code
&0x01u
);
329 //FIXME: call enter/leave hooks here too?
330 ubyte fetchOpcodeExt () nothrow @trusted {
331 //pragma(inline, true);
332 contention(PC
, 4, true);
333 //contention(((I<<8)|R), 2, false); // memory refresh
334 // it doesn't really matter when R is incremented here
340 while ((nextEventTS
< 0 || tstates
< nextEventTS
) && (tscount
< 0 || tstates
-tstart
<= tscount
)) {
341 // process enter/leave hooks
343 // process breakpoints
344 if (bpmap
[PC
] && checkBreakpoint()) { bpHit
= true; return tstates
-tstart
; }
345 // read opcode -- OCR(4)
346 // t1: setting /MREQ & /RD
348 // t3, t4: decode command, increment R
349 // Woody: The Z80 places IR on to the address bus during T3+T4 of opcode fetch (M1) cycles for DRAM refresh,
350 // then if following cycles have internal operations before placing another address on the address bus
351 // then the ULA continues to see IR and contends as normal as it would with any other register paired address.
352 // it doesn't matter if we will really emulate IR here, 'cause T1 contention should end up in "non-contended" state
353 contention(PC
, 4, true);
354 //contention(((I<<8)|R), 2, false); // memory refresh
355 if (evenM1
&& (tstates
&0x01)) ++tstates
;
360 version(Zymosis_Run_Log
) { { import core
.stdc
.stdio
; stderr
.fprintf("PC=%04X; OP:%02X; tstates=%d\n", PC
, opcode
, tstates
); } }
363 /* previous Q for SCF/CCF */
366 prevWasEIDDR
= EIDDR
.Normal
;
370 if (halted
) { --PC
; continue; }
371 // check for I[XY] prefix
372 if (opcode
== 0xdd || opcode
== 0xfd) {
373 //TODO: generate this table in compile time
374 static immutable uint[8] withIndexBmp
= [0x00,0x700000,0x40404040,0x40bf4040,0x40404040,0x40404040,0x0800,0x00];
376 DD
= (opcode
== 0xdd ?
cast(RegPair
!("h", "l")*)&IX
: cast(RegPair
!("h", "l")*)&IY
);
377 // read opcode -- OCR(4)
378 opcode
= fetchOpcodeExt();
379 // test if this instruction have (HL)
380 if (withIndexBmp
[opcode
>>5]&(1<<(opcode
&0x1f))) {
381 // 3rd byte is always DISP here
382 disp
= peekb_3ts_args();
383 if (disp
> 127) disp
-= 256;
385 MEMPTR
.w
= (cast(int)DD
.w
+disp
)&0xffff;
386 } else if (opcode
== 0xdd && opcode
== 0xfd) {
387 // double prefix; restart main loop
388 prevWasEIDDR
= EIDDR
.BlockInt
;
389 /*FIXME: logically this should reset our Q value, but i am not sure; anyway, this works*/
395 // ED-prefixed instructions
396 if (opcode
== 0xed) {
397 DD
= &HL
; // а нас -- рать!
398 // read opcode -- OCR(4)
399 opcode
= fetchOpcodeExt();
401 // LDI, LDIR, LDD, LDDR
402 case 0xa0: case 0xb0: case 0xa8: case 0xb8:
403 tmpB
= peekb_3ts(HL
);
406 contention_by1ts(DE
, 2);
408 tmpB
= (tmpB
+AF
.a
)&0xff;
409 flags_q
= AF
.f
= // BOO! FEAR THE MIGHTY BITS!
410 (tmpB
&Z80Flag
.F3
)|
(AF
.f
&(Z80Flag
.C|Z80Flag
.Z|Z80Flag
.S
))|
411 (BC
!= 0 ? Z80Flag
.PV
: 0)|
412 (tmpB
&0x02 ? Z80Flag
.F5
: 0);
413 if (isRepeated(opcode
)) {
416 contention_by1ts(DE
, 5);
419 MEMPTR
.w
= (PC
+1)&0xffff;
422 if (!isBackward(opcode
)) { ++HL
; ++DE
; } else { --HL
; --DE
; }
424 // CPI, CPIR, CPD, CPDR
425 case 0xa1: case 0xb1: case 0xa9: case 0xb9:
427 if (isRepeated(opcode
) && (!(BC
== 1 ||
memPeekB(HL
) == AF
.a
))) {
428 MEMPTR
.w
= (lastPC
+1)&0xffff;
430 MEMPTR
.w
= (cast(int)MEMPTR
.w
+(isBackward(opcode
) ?
-1 : 1))&0xffff;
432 tmpB
= peekb_3ts(HL
);
434 contention_by1ts(HL
, 5);
436 AF
.f
= // BOO! FEAR THE MIGHTY BITS!
439 (BC
!= 0 ? Z80Flag
.PV
: 0)|
440 (cast(int)(AF
.a
&0x0f)-cast(int)(tmpB
&0x0f) < 0 ? Z80Flag
.H
: 0);
441 tmpB
= (cast(int)AF
.a
-cast(int)tmpB
)&0xff;
442 AF
.f |
= (tmpB
== 0 ? Z80Flag
.Z
: 0)|
(tmpB
&Z80Flag
.S
);
443 if (AF
.f
&Z80Flag
.H
) tmpB
= (cast(ushort)tmpB
-1)&0xff;
444 AF
.f |
= (tmpB
&Z80Flag
.F3
)|
(tmpB
&0x02 ? Z80Flag
.F5
: 0);
446 if (isRepeated(opcode
)) {
448 if ((AF
.f
&(Z80Flag
.Z|Z80Flag
.PV
)) == Z80Flag
.PV
) {
450 contention_by1ts(HL
, 5);
455 if (isBackward(opcode
)) --HL
; else ++HL
;
457 // OUTI, OTIR, OUTD, OTDR
458 case 0xa3: case 0xb3: case 0xab: case 0xbb:
462 // INI, INIR, IND, INDR
463 case 0xa2: case 0xb2: case 0xaa: case 0xba:
464 MEMPTR
.w
= (cast(int)BC
.w
+(isBackward(opcode
) ?
-1 : 1))&0xffff;
466 contention_by1ts_ir(1);
469 tmpB
= peekb_3ts(HL
); // MRD(3)
470 port_write(BC
, tmpB
);
471 tmpW
= cast(ushort)(HL
+(isBackward(opcode
) ?
-1 : 1));
472 tmpC
= (tmpB
+tmpW
)&0xff;
475 tmpB
= port_read(BC
);
476 pokeb_3ts(HL
, tmpB
); // MWR(3)
478 if (isBackward(opcode
)) tmpC
= (cast(int)tmpB
+cast(int)BC
.c
-1)&0xff; else tmpC
= (tmpB
+BC
.c
+1)&0xff;
481 (tmpB
&0x80 ? Z80Flag
.N
: 0)|
482 (tmpC
< tmpB ? Z80Flag
.H|Z80Flag
.C
: 0)|
483 tblParity
.ptr
[(tmpC
&0x07)^BC
.b
]|
485 if (isRepeated(opcode
)) {
486 // repeating commands
488 ushort a
= (opcode
&0x01 ? BC
: HL
);
490 contention_by1ts(a
, 5);
495 if (isBackward(opcode
)) --HL
; else ++HL
;
497 // not strings, but some good instructions anyway
499 if ((opcode
&0xc0) == 0x40) {
501 final switch (opcode
&0x07) {
504 MEMPTR
.w
= (BC
.w
+1)&0xffff;
505 tmpB
= port_read(BC
);
506 flags_q
= AF
.f
= tblSZP53
.ptr
[tmpB
]|
(AF
.f
&Z80Flag
.C
);
507 final switch ((opcode
>>3)&0x07) {
508 case 0: BC
.b
= tmpB
; break;
509 case 1: BC
.c
= tmpB
; break;
510 case 2: DE
.d
= tmpB
; break;
511 case 3: DE
.e
= tmpB
; break;
512 case 4: HL
.h
= tmpB
; break;
513 case 5: HL
.l
= tmpB
; break;
514 case 6: break; // 6 affects only flags
515 case 7: AF
.a
= tmpB
; break;
520 MEMPTR
.w
= (BC
.w
+1)&0xffff;
521 final switch ((opcode
>>3)&0x07) {
522 case 0: tmpB
= BC
.b
; break;
523 case 1: tmpB
= BC
.c
; break;
524 case 2: tmpB
= DE
.d
; break;
525 case 3: tmpB
= DE
.e
; break;
526 case 4: tmpB
= HL
.h
; break;
527 case 5: tmpB
= HL
.l
; break;
528 case 6: tmpB
= 0; break; // 0 on NMOS, 255 on CMOS
529 case 7: tmpB
= AF
.a
; break;
531 port_write(BC
, tmpB
);
533 // SBC HL,rr/ADC HL,rr
536 contention_by1ts_ir(7);
537 switch ((opcode
>>4)&0x03) {
538 case 0: tmpW
= BC
; break;
539 case 1: tmpW
= DE
; break;
540 case 2: tmpW
= HL
; break;
541 default: tmpW
= SP
; break;
543 HL
= (opcode
&0x08 ?
ADC_DD(tmpW
, HL
) : SBC_DD(tmpW
, HL
));
545 // LD (nn),rr/LD rr,(nn)
548 MEMPTR
.w
= (tmpW
+1)&0xffff;
551 final switch ((opcode
>>4)&0x03) {
552 case 0: BC
= peekw_6ts(tmpW
); break;
553 case 1: DE
= peekw_6ts(tmpW
); break;
554 case 2: HL
= peekw_6ts(tmpW
); break;
555 case 3: SP
= peekw_6ts(tmpW
); break;
559 final switch ((opcode
>>4)&0x03) {
560 case 0: pokew_6ts(tmpW
, BC
); break;
561 case 1: pokew_6ts(tmpW
, DE
); break;
562 case 2: pokew_6ts(tmpW
, HL
); break;
563 case 3: pokew_6ts(tmpW
, SP
); break;
575 // RETI: 0x4d, 0x5d, 0x6d, 0x7d
576 // RETN: 0x45, 0x55, 0x65, 0x75
578 MEMPTR
.w
= PC
= pop_6ts();
581 if (trapRETI(opcode
)) return tstates
-tstart
;
584 if (trapRETN(opcode
)) return tstates
-tstart
;
590 case 0x56: case 0x76: mIM
= 1; break;
591 case 0x5e: case 0x7e: mIM
= 2; break;
592 default: mIM
= 0; break;
597 final switch (opcode
) {
601 contention_by1ts_ir(1);
607 contention_by1ts_ir(1);
611 case 0x57: LD_A_IR(I
); break;
613 case 0x5f: LD_A_IR(R
); break;
615 case 0x67: RRD_A(); break;
617 case 0x6F: RLD_A(); break;
621 // slt and other traps
622 if (trapED(opcode
)) return tstates
-tstart
;
628 // CB-prefixed instructions
629 if (opcode
== 0xcb) {
630 // shifts and bit operations
631 // read opcode -- OCR(4)
633 opcode
= fetchOpcodeExt();
635 contention(PC
, 3, true);
636 //FIXME: call enter/leave hooks here too?
638 contention_by1ts_pc(2, true);
642 tmpW
= cast(ushort)(cast(int)DD
.w
+disp
);
643 tmpB
= peekb_3ts(tmpW
);
644 contention_by1ts(tmpW
, 1);
646 final switch (opcode
&0x07) {
647 case 0: tmpB
= BC
.b
; break;
648 case 1: tmpB
= BC
.c
; break;
649 case 2: tmpB
= DE
.d
; break;
650 case 3: tmpB
= DE
.e
; break;
651 case 4: tmpB
= HL
.h
; break;
652 case 5: tmpB
= HL
.l
; break;
653 case 6: tmpB
= peekb_3ts(HL
); contention(HL
, 1, false); break;
654 case 7: tmpB
= AF
.a
; break;
657 switch ((opcode
>>3)&0x1f) {
658 case 0: tmpB
= RLC(tmpB
); break;
659 case 1: tmpB
= RRC(tmpB
); break;
660 case 2: tmpB
= RL(tmpB
); break;
661 case 3: tmpB
= RR(tmpB
); break;
662 case 4: tmpB
= SLA(tmpB
); break;
663 case 5: tmpB
= SRA(tmpB
); break;
664 case 6: tmpB
= SLL(tmpB
); break;
665 case 7: tmpB
= SLR(tmpB
); break;
667 final switch ((opcode
>>6)&0x03) {
668 case 1: BIT((opcode
>>3)&0x07, tmpB
, (gotDD ||
(opcode
&0x07u
) == 6)); break;
669 case 2: tmpB
&= ~(1<<((opcode
>>3)&0x07)); break; // RES
670 case 3: tmpB |
= (1<<((opcode
>>3)&0x07)); break; // SET
674 if ((opcode
&0xc0) != 0x40) {
675 // BITs are not welcome here
677 // tmpW was set earlier
678 if ((opcode
&0x07) != 6) pokeb_3ts(tmpW
, tmpB
);
680 final switch (opcode
&0x07) {
681 case 0: BC
.b
= tmpB
; break;
682 case 1: BC
.c
= tmpB
; break;
683 case 2: DE
.d
= tmpB
; break;
684 case 3: DE
.e
= tmpB
; break;
685 case 4: HL
.h
= tmpB
; break;
686 case 5: HL
.l
= tmpB
; break;
687 case 6: pokeb_3ts(cast(ushort)(cast(int)DD
.w
+disp
), tmpB
); break;
688 case 7: AF
.a
= tmpB
; break;
694 final switch (opcode
&0xc0) {
697 final switch (opcode
&0x07) {
698 // misc,DJNZ,JR,JR cc
704 trueCC
= doCond((opcode
>>3)&0x03, AF
.f
);
707 if ((opcode
&0x08) == 0) {
710 contention_by1ts_ir(1);
712 trueCC
= (BC
.b
!= 0);
718 //???disp = peekb_3ts_args(); // FUSE reads it only if condition was taken
720 // execute branch (relative)
721 disp
= peekb_3ts_args(); // FUSE reads it only if condition was taken
723 if (disp
> 127) disp
-= 256; // convert to int8_t
724 contention_by1ts_pc(5, false);
729 peekb_3ts_args_noread(); // FUSE reads it only if condition was taken
734 if (opcode
!= 0) exafaf();
737 // LD rr,nn/ADD HL,rr
742 contention_by1ts_ir(7);
743 final switch ((opcode
>>4)&0x03) {
744 case 0: DD
.w
= ADD_DD(BC
, DD
.w
); break;
745 case 1: DD
.w
= ADD_DD(DE
, DD
.w
); break;
746 case 2: DD
.w
= ADD_DD(DD
.w
, DD
.w
); break;
747 case 3: DD
.w
= ADD_DD(SP
, DD
.w
); break;
752 final switch ((opcode
>>4)&0x03) {
753 case 0: BC
= tmpW
; break;
754 case 1: DE
= tmpW
; break;
755 case 2: DD
.w
= tmpW
; break;
756 case 3: SP
= tmpW
; break;
762 final switch ((opcode
>>3)&0x07) {
764 case 0: pokeb_3ts(BC
, AF
.a
); MEMPTR
.lo
= (BC
.c
+1)&0xff; MEMPTR
.hi
= AF
.a
; break;
766 case 1: AF
.a
= peekb_3ts(BC
); MEMPTR
.w
= (BC
.w
+1)&0xffff; break;
768 case 2: pokeb_3ts(DE
, AF
.a
); MEMPTR
.lo
= (DE
.e
+1)&0xff; MEMPTR
.hi
= AF
.a
; break;
770 case 3: AF
.a
= peekb_3ts(DE
); MEMPTR
.w
= (DE
.w
+1)&0xffff; break;
774 MEMPTR
.w
= (tmpW
+1)&0xffff;
775 pokew_6ts(tmpW
, DD
.w
);
780 MEMPTR
.w
= (tmpW
+1)&0xffff;
781 DD
.w
= peekw_6ts(tmpW
);
786 MEMPTR
.lo
= (tmpW
+1)&0xff;
788 pokeb_3ts(tmpW
, AF
.a
);
793 MEMPTR
.w
= (tmpW
+1)&0xffff;
794 AF
.a
= peekb_3ts(tmpW
);
801 contention_by1ts_ir(2);
804 final switch ((opcode
>>4)&0x03) {
807 case 2: --DD
.w
; break;
812 final switch ((opcode
>>4)&0x03) {
815 case 2: ++DD
.w
; break;
822 final switch ((opcode
>>3)&0x07) {
823 case 0: BC
.b
= INC8(BC
.b
); break;
824 case 1: BC
.c
= INC8(BC
.c
); break;
825 case 2: DE
.d
= INC8(DE
.d
); break;
826 case 3: DE
.e
= INC8(DE
.e
); break;
827 case 4: DD
.h
= INC8(DD
.h
); break;
828 case 5: DD
.l
= INC8(DD
.l
); break;
830 if (gotDD
) { --PC
; contention_by1ts_pc(5, false); ++PC
; }
831 tmpW
= cast(ushort)(cast(int)DD
.w
+disp
);
832 tmpB
= peekb_3ts(tmpW
);
833 contention_by1ts(tmpW
, 1);
835 pokeb_3ts(tmpW
, tmpB
);
837 case 7: AF
.a
= INC8(AF
.a
); break;
842 final switch ((opcode
>>3)&0x07) {
843 case 0: BC
.b
= DEC8(BC
.b
); break;
844 case 1: BC
.c
= DEC8(BC
.c
); break;
845 case 2: DE
.d
= DEC8(DE
.d
); break;
846 case 3: DE
.e
= DEC8(DE
.e
); break;
847 case 4: DD
.h
= DEC8(DD
.h
); break;
848 case 5: DD
.l
= DEC8(DD
.l
); break;
850 if (gotDD
) { --PC
; contention_by1ts_pc(5, false); ++PC
; }
851 tmpW
= cast(ushort)(cast(int)DD
.w
+disp
);
852 tmpB
= peekb_3ts(tmpW
);
853 contention_by1ts(tmpW
, 1);
855 pokeb_3ts(tmpW
, tmpB
);
857 case 7: AF
.a
= DEC8(AF
.a
); break;
862 tmpB
= peekb_3ts_args();
864 final switch ((opcode
>>3)&0x07) {
865 case 0: BC
.b
= tmpB
; break;
866 case 1: BC
.c
= tmpB
; break;
867 case 2: DE
.d
= tmpB
; break;
868 case 3: DE
.e
= tmpB
; break;
869 case 4: DD
.h
= tmpB
; break;
870 case 5: DD
.l
= tmpB
; break;
872 if (gotDD
) { --PC
; contention_by1ts_pc(2, false); ++PC
; }
873 tmpW
= cast(ushort)(cast(int)DD
.w
+disp
);
874 pokeb_3ts(tmpW
, tmpB
);
876 case 7: AF
.a
= tmpB
; break;
881 final switch ((opcode
>>3)&0x07) {
882 case 0: RLCA(); break;
883 case 1: RRCA(); break;
884 case 2: RLA(); break;
885 case 3: RRA(); break;
886 case 4: DAA(); break;
889 flags_q
= AF
.f
= (AF
.a
&Z80Flag
.F35
)|
(Z80Flag
.N|Z80Flag
.H
)|
(AF
.f
&(Z80Flag
.C|Z80Flag
.PV|Z80Flag
.Z|Z80Flag
.S
));
892 flags_q
= AF
.f
= (AF
.f
&(Z80Flag
.PV|Z80Flag
.Z|Z80Flag
.S
))|
((AF
.a|
(lastq^AF
.f
))&Z80Flag
.F35
)|Z80Flag
.C
;
895 flags_q
= AF
.f
= (AF
.f
&(Z80Flag
.PV|Z80Flag
.Z|Z80Flag
.S
))|
((AF
.a|
(lastq^AF
.f
))&Z80Flag
.F35
)|
(AF
.f
&Z80Flag
.C ? Z80Flag
.H
: Z80Flag
.C
);
898 * Patrik Rak however later discovered that the way how the flags 5 and 3 are affected after SCF/CCF actually depends on
899 * the previous instruction completed. In case of genuine Zilog CPU, if an instruction modifies the flags, the immediately
900 * following SCF/CCF does move of bits 5 and 3 from A to F, whereas if an instruction doesn't modify the flags (and after
901 * interrupt), the SCF/CCF does OR of bits 5 and 3 from A to F. In case of NEC and other clones, it is similar, except that
902 * instead of OR it does AND with some unknown value, making the result unreliable. */
907 // 0x40..0x7F: LD r8,r8
909 if (opcode
== 0x76) { halted
= true; --PC
; continue; } // HALT
910 rsrc
= (opcode
&0x07);
911 rdst
= ((opcode
>>3)&0x07);
912 final switch (rsrc
) {
913 case 0: tmpB
= BC
.b
; break;
914 case 1: tmpB
= BC
.c
; break;
915 case 2: tmpB
= DE
.d
; break;
916 case 3: tmpB
= DE
.e
; break;
917 case 4: tmpB
= (gotDD
&& rdst
== 6 ? HL
.h
: DD
.h
); break;
918 case 5: tmpB
= (gotDD
&& rdst
== 6 ? HL
.l
: DD
.l
); break;
920 if (gotDD
) { --PC
; contention_by1ts_pc(5, false); ++PC
; }
921 tmpW
= cast(ushort)(cast(int)DD
.w
+disp
);
922 tmpB
= peekb_3ts(tmpW
);
924 case 7: tmpB
= AF
.a
; break;
926 final switch (rdst
) {
927 case 0: BC
.b
= tmpB
; break;
928 case 1: BC
.c
= tmpB
; break;
929 case 2: DE
.d
= tmpB
; break;
930 case 3: DE
.e
= tmpB
; break;
931 case 4: if (gotDD
&& rsrc
== 6) HL
.h
= tmpB
; else DD
.h
= tmpB
; break;
932 case 5: if (gotDD
&& rsrc
== 6) HL
.l
= tmpB
; else DD
.l
= tmpB
; break;
934 if (gotDD
) { --PC
; contention_by1ts_pc(5, false); ++PC
; }
935 tmpW
= cast(ushort)(cast(int)DD
.w
+disp
);
936 pokeb_3ts(tmpW
, tmpB
);
938 case 7: AF
.a
= tmpB
; break;
941 // 0x80..0xBF: ALU A,r8
943 final switch (opcode
&0x07) {
944 case 0: tmpB
= BC
.b
; break;
945 case 1: tmpB
= BC
.c
; break;
946 case 2: tmpB
= DE
.d
; break;
947 case 3: tmpB
= DE
.e
; break;
948 case 4: tmpB
= DD
.h
; break;
949 case 5: tmpB
= DD
.l
; break;
951 if (gotDD
) { --PC
; contention_by1ts_pc(5, false); ++PC
; }
952 tmpW
= cast(ushort)(cast(int)DD
.w
+disp
);
953 tmpB
= peekb_3ts(tmpW
);
955 case 7: tmpB
= AF
.a
; break;
957 final switch ((opcode
>>3)&0x07) {
958 case 0: ADD_A(tmpB
); break;
959 case 1: ADC_A(tmpB
); break;
960 case 2: SUB_A(tmpB
); break;
961 case 3: SBC_A(tmpB
); break;
962 case 4: AND_A(tmpB
); break;
963 case 5: XOR_A(tmpB
); break;
964 case 6: OR_A(tmpB
); break;
965 case 7: CP_A(tmpB
); break;
970 final switch (opcode
&0x07) {
973 contention_by1ts_ir(1);
974 trueCC
= doCond((opcode
>>3)&0x07, AF
.f
);
975 if (trueCC
) MEMPTR
.w
= PC
= pop_6ts();
981 final switch ((opcode
>>4)&0x03) {
983 case 0: MEMPTR
.w
= PC
= pop_6ts(); break;
985 case 1: exx(); break;
987 case 2: PC
= DD
.w
; break;
991 contention_by1ts_ir(2);
998 final switch ((opcode
>>4)&0x03) {
999 case 0: BC
= tmpW
; break;
1000 case 1: DE
= tmpW
; break;
1001 case 2: DD
.w
= tmpW
; break;
1002 case 3: AF
= tmpW
; break;
1008 trueCC
= doCond((opcode
>>3)&0x07, AF
.f
);
1009 MEMPTR
.w
= getpcw(0);
1010 if (trueCC
) PC
= MEMPTR
.w
;
1012 // special1/special3
1014 final switch ((opcode
>>3)&0x07) {
1016 case 0: MEMPTR
.w
= PC
= getpcw(0); break;
1019 tmpW
= peekb_3ts_args();
1021 MEMPTR
.lo
= (tmpW
+1)&0xff;
1024 port_write(tmpW
, AF
.a
);
1028 tmpB
= peekb_3ts_args();
1029 tmpW
= cast(ushort)((AF
.a
<<8)|tmpB
);
1031 MEMPTR
.w
= (tmpW
+1)&0xffff;
1032 AF
.a
= port_read(tmpW
);
1037 tmpW
= peekw_6ts(SP
);
1038 contention_by1ts((SP
+1)&0xffff, 1);
1040 pokew_6ts_inverted(SP
, DD
.w
);
1041 contention_by1ts(SP
, 2);
1042 MEMPTR
.w
= DD
.w
= tmpW
;
1051 case 6: IFF1
= IFF2
= 0; break;
1053 case 7: IFF1
= IFF2
= 1; prevWasEIDDR
= EIDDR
.BlockInt
; break;
1058 trueCC
= doCond((opcode
>>3)&0x07, AF
.f
);
1059 MEMPTR
.w
= getpcw(!!trueCC
);
1068 if (((opcode
>>4)&0x03) == 0) {
1070 MEMPTR
.w
= tmpW
= getpcw(1);
1077 contention_by1ts_ir(1);
1078 switch ((opcode
>>4)&0x03) {
1079 case 0: tmpW
= BC
; break;
1080 case 1: tmpW
= DE
; break;
1081 case 2: tmpW
= DD
.w
; break;
1082 default: tmpW
= AF
; break;
1089 tmpB
= peekb_3ts_args();
1091 final switch ((opcode
>>3)&0x07) {
1092 case 0: ADD_A(tmpB
); break;
1093 case 1: ADC_A(tmpB
); break;
1094 case 2: SUB_A(tmpB
); break;
1095 case 3: SBC_A(tmpB
); break;
1096 case 4: AND_A(tmpB
); break;
1097 case 5: XOR_A(tmpB
); break;
1098 case 6: OR_A(tmpB
); break;
1099 case 7: CP_A(tmpB
); break;
1105 contention_by1ts_ir(1);
1107 MEMPTR
.w
= PC
= opcode
&0x38;
1113 return tstates
-tstart
;
1117 * Execute one instruction.
1118 * WARNING: this function ignores z80.nextEventTS.
1124 * number of tstates spent
1127 int one
= nextEventTS
;
1134 /** Execute at least 'atstates' t-states; return real number of executed t-states.
1135 * WARNING: this function ignores z80.nextEventTS.
1138 * atstates = minimum tstates to spend
1141 * number of tstates actually spent
1143 int execTS (in int atstates
) {
1145 int one
= nextEventTS
;
1154 /** Initiate maskable interrupt (if interrupts are enabled).
1155 * May change z80.tstates.
1161 * number of tstates taken by interrupt initiation or 0 if interrupts was disabled/ignored
1165 /*FIXME: what is the state of `z80->flags_q` here? */
1166 if (prevWasEIDDR
== EIDDR
.LdIorR
) { prevWasEIDDR
= EIDDR
.Normal
; flags_q
= (AF
.f
&= ~(Z80Flag
.PV
)); } // Z80 bug, NMOS only
1167 if (prevWasEIDDR
== EIDDR
.BlockInt ||
!IFF1
) return 0; // not accepted
1168 flags_q
= 0; /* we cannot do it earlier, because ignored interrupt won't reset flags */
1169 if (halted
) { halted
= false; ++PC
; }
1170 IFF1
= IFF2
= false; // disable interrupts
1171 final switch (mIM
&0x03) {
1172 case 3: /* ??? */ /*IM = 0;*/ /* fallthru */ goto case 0;
1173 case 0: // take instruction from the bus (for now we assume that reading from bus always returns 0xff)
1174 // with a CALL nnnn on the data bus, it takes 19 cycles:
1175 // M1 cycle: 7 T to acknowledge interrupt (where exactly data bus reading occures?)
1176 // M2 cycle: 3 T to read low byte of 'nnnn' from data bus
1177 // M3 cycle: 3 T to read high byte of 'nnnn' and decrement SP
1178 // M4 cycle: 3 T to write high byte of PC to the stack and decrement SP
1179 // M5 cycle: 3 T to write low byte of PC and jump to 'nnnn'
1180 // BUT! FUSE says this:
1181 // Only the first byte is provided directly to the Z80: all remaining bytes
1182 // of the instruction are fetched from memory using PC, which is incremented as normal.
1186 case 1: // just do RST #38
1188 tstates
+= 7; // M1 cycle: 7 T to acknowledge interrupt and decrement SP
1189 // M2 cycle: 3 T states write high byte of PC to the stack and decrement SP
1190 // M3 cycle: 3 T states write the low byte of PC and jump to #0038
1192 MEMPTR
.w
= PC
= 0x38;
1196 tstates
+= 7; // M1 cycle: 7 T to acknowledge interrupt and decrement SP
1197 // M2 cycle: 3 T states write high byte of PC to the stack and decrement SP
1198 // M3 cycle: 3 T states write the low byte of PC
1200 // M4 cycle: 3 T to read high byte from the interrupt vector
1201 // M5 cycle: 3 T to read low byte from bus and jump to interrupt routine
1202 ushort a
= ((cast(ushort)I
)<<8)|
0xff;
1203 MEMPTR
.w
= PC
= peekw_6ts(a
);
1206 return tstates
-ots
; // accepted
1209 /** Initiate non-maskable interrupt.
1210 * May change z80.tstates.
1216 * number of tstates taken by interrupt initiation or 0 if interrupts was disabled/ignored
1220 /*FIXME: what is the state of `z80->flags_q` here? */
1221 if (prevWasEIDDR
== EIDDR
.LdIorR
) { prevWasEIDDR
= EIDDR
.Normal
; flags_q
= (AF
.f
&= ~(Z80Flag
.PV
)); } // emulate Z80 bug with interrupted LD A,I/R, NMOS only
1222 if (prevWasEIDDR
== EIDDR
.BlockInt ||
!IFF1
) return 0; // not accepted
1223 flags_q
= 0; /* we cannot do it earlier, because ignored interrupt won't reset flags */
1224 if (halted
) { halted
= false; ++PC
; }
1226 IFF1
= false; // IFF2 is not changed
1227 tstates
+= 5; // M1 cycle: 5 T states to do an opcode read and decrement SP
1228 // M2 cycle: 3 T states write high byte of PC to the stack and decrement SP
1229 // M3 cycle: 3 T states write the low byte of PC and jump to #0066
1231 MEMPTR
.w
= PC
= 0x66;
1235 /** Pop 16-bit word from stack without contention and breakpoint triggering. Changes SP.
1243 ushort pop () nothrow @trusted {
1244 ushort res
= memPeekB(SP
++);
1245 res |
= memPeekB(SP
++)<<8;
1249 /** Push 16-bit word to stack without contention and breakpoint triggering. Changes SP.
1252 * value = word to push
1257 void push (ushort value
) nothrow @trusted {
1258 memPokeB(--SP
, (value
>>8)&0xff);
1259 memPokeB(--SP
, value
&0xff);
1262 /** Execute EXX command. */
1263 void exx () @safe nothrow @nogc {
1264 ushort t
= BC
; BC
= BCx
; BCx
= t
;
1265 t
= DE
; DE
= DEx
; DEx
= t
;
1266 t
= HL
; HL
= HLx
; HLx
= t
;
1269 /** Execute EX AF,AF' command. */
1270 void exafaf () @safe nothrow @nogc {
1271 ushort t
= AF
; AF
= AFx
; AFx
= t
;
1274 protected nothrow @trusted /*@nogc*/:
1275 /* ************************************************************************** */
1276 /* simulate contented memory access */
1277 /* (tstates = tstates+contention+atstates)*cnt */
1278 /* (ushort addr, int tstates, MemIO mio) */
1279 final void contention (ushort addr
, int cnt
, bool mreq
) {
1280 version(Zymosis_Testing
) memContention(addr
, mreq
);
1281 if (mem
.ptr
[addr
/MemPage
.Size
].contended
) {
1282 auto cc
= (mreq ? ulacont
: ulacontport
);
1283 if (tstates
>= 0 && tstates
< cc
.length
) {
1284 tstates
+= cc
.ptr
[tstates
];
1285 //{ import core.stdc.stdio; printf("CT: #%04X ts:%u; (%u)\n", cast(uint)PC, cast(uint)(tstates-3), cast(uint)ulacont.ptr[tstates-3]); }
1286 version(Zymosis_Check_Contention
) if (cc
.ptr
[tstates
] != 0) assert(0, "intertal error: contention tables are wrong");
1292 final void contention_by1ts (ushort addr
, int cnt
, bool mreq
=true) {
1293 if (mem
.ptr
[addr
/MemPage
.Size
].contended
) {
1294 auto cc
= (mreq ? ulacont
: ulacontport
);
1296 version(Zymosis_Testing
) memContention(addr
, mreq
);
1297 if (tstates
>= 0 && tstates
< cc
.length
) {
1298 tstates
+= cc
.ptr
[tstates
];
1299 version(Zymosis_Check_Contention
) if (cc
.ptr
[tstates
] != 0) assert(0, "intertal error: contention tables are wrong");
1304 version(Zymosis_Testing
) {
1306 memContention(addr
, mreq
);
1315 final void contention_by1ts_ir (int cnt
) { contention_by1ts(((I
<<8)|R
), cnt
, false); }
1316 final void contention_by1ts_pc (int cnt
, bool mreq
) { contention_by1ts(PC
, cnt
, mreq
); }
1318 /* ************************************************************************** */
1319 version(Zymosis_Testing
) {} else {
1321 // Ports with A0 low are contended at the 2nd T-state
1322 // Ports with A0 high are uncontended
1323 // Ports with a high byte between 0x40 and 0x7F comply with 1 and 2 above, but are additionally contended:
1324 // If A0 is low, the 1st T-state is also contended, 3rd and 4th are not.
1325 // If A0 is high, all T-states are contended
1326 // Reading the floating bus returns the second attribute byte of fetched pair (the last byte fetched), or 0xFF.
1327 final void doPortOp(bool lateio
) (ushort port
, scope void delegate (ushort port
) nothrow @trusted doio
) nothrow @trusted {
1328 bool ulaPort
= ((port
&0x01) == 0);
1329 if (mem
.ptr
[port
/MemPage
.Size
].contended
) {
1330 // the port looks like contended memory address
1331 // first tstate is contended
1332 if (tstates
>= 0 && tstates
< ulacontport
.length
) tstates
+= ulacontport
.ptr
[tstates
];
1335 static if (!lateio
) doio(port
);
1336 // second tstate is contended for all ULA ports
1337 if (tstates
>= 0 && tstates
< ulacontport
.length
) tstates
+= ulacontport
.ptr
[tstates
];
1339 // here port i/o occurs, at third tstate
1340 //static if (lateio) doio();
1341 // third and fourth tstates are contended for non-ULA ports
1342 if (mem
.ptr
[port
/MemPage
.Size
].contended
) {
1343 if (!ulaPort
&& tstates
>= 0 && tstates
< ulacontport
.length
) tstates
+= ulacontport
.ptr
[tstates
];
1345 if (!ulaPort
&& tstates
>= 0 && tstates
< ulacontport
.length
) tstates
+= ulacontport
.ptr
[tstates
];
1349 static if (lateio
) doio(port
); // and FUSE says that it is here
1354 ubyte port_read (ushort port
) {
1356 version(Zymosis_Testing
) {
1357 portContention(port
, 1, true, true); /* early */
1358 value
= portRead(port
);
1359 portContention(port
, 2, true, false); /* normal */
1362 doPortOp
!true(port
, (ushort port
) { value
= portRead(port
); });
1367 void port_write (ushort port
, ubyte value
) {
1368 version(Zymosis_Testing
) {
1369 portContention(port
, 1, false, true); /* early */
1370 portWrite(port
, value
);
1371 portContention(port
, 2, false, false); /* normal */
1374 doPortOp
!false(port
, (ushort port
) { portWrite(port
, value
); });
1378 // ////////////////////////////////////////////////////////////////////////// //
1379 //TODO: memory breakpoints
1380 ubyte peekb (ushort addr
) {
1381 pragma(inline
, true);
1382 version(Zymosis_Testing
) memReading(addr
);
1383 return mem
.ptr
[addr
/MemPage
.Size
].mem
[addr
%MemPage
.Size
];
1386 void pokeb (ushort addr
, ubyte b
) {
1387 version(Zymosis_Testing
) memWriting(addr
, b
);
1388 if (auto mpg
= mem
.ptr
+addr
/MemPage
.Size
) {
1390 if (mpg
.writeHook
) memWriteHook(addr
, b
);
1391 mpg
.mem
[addr
%MemPage
.Size
] = b
;
1396 // t1: setting /MREQ & /RD
1398 ubyte peekb_3ts (ushort addr
) {
1399 pragma(inline
, true);
1400 version(Zymosis_Testing
) {} else pragma(inline
, true);
1401 contention(addr
, 3, true);
1405 void peekb_3ts_args_noread () {
1406 version(Zymosis_Testing
) {} else pragma(inline
, true);
1407 contention(PC
, 3, false); //FIXME???
1410 ubyte peekb_3ts_args () {
1411 pragma(inline
, true);
1412 version(Zymosis_Testing
) {} else pragma(inline
, true);
1413 contention(PC
, 3, true);
1417 // t1: setting /MREQ & /WR
1419 void pokeb_3ts (ushort addr
, ubyte b
) {
1420 //pragma(inline, true);
1421 contention(addr
, 3, true);
1425 ushort peekw_6ts (ushort addr
) {
1426 version(Zymosis_Testing
) {} else pragma(inline
, true);
1427 ushort res
= peekb_3ts(addr
++);
1428 res |
= peekb_3ts(addr
)<<8;
1432 void pokew_6ts (ushort addr
, ushort value
) {
1433 //pragma(inline, true);
1434 pokeb_3ts(addr
, value
&0xff);
1435 pokeb_3ts((addr
+1)&0xffff, (value
>>8)&0xff);
1438 void pokew_6ts_inverted (ushort addr
, ushort value
) {
1439 //pragma(inline, true);
1440 pokeb_3ts((addr
+1)&0xffff, (value
>>8)&0xff);
1441 pokeb_3ts(addr
, value
&0xff);
1444 ushort getpcw (int wait1
) {
1445 //pragma(inline, true);
1446 ushort res
= peekb_3ts_args();
1448 res |
= peekb_3ts_args()<<8;
1449 if (wait1
) contention_by1ts(PC
, wait1
, true);
1455 ushort getpcw_cc (int wait1, bool truecc) {
1456 //pragma(inline, true);
1457 peekb_3ts_args_noread();
1458 version(Zymosis_Testing) {
1459 /*if (truecc)*/ memReading(PC);
1460 ushort res = memPeekB(PC++);
1462 ushort res = peekb(PC++);
1464 peekb_3ts_args_noread();
1465 version(Zymosis_Testing) {
1466 /*if (truecc)*/ memReading(PC);
1467 res |= memPeekB(PC)<<8;
1469 res |= peekb(PC)<<8;
1471 if (wait1) contention_by1ts_pc(wait1, true);
1478 pragma(inline
, true);
1479 ushort res
= peekb_3ts(SP
++);
1480 res |
= peekb_3ts(SP
++)<<8;
1484 // 3 T states write high byte of PC to the stack and decrement SP
1485 // 3 T states write the low byte of PC and jump to #0066
1486 void push_6ts (ushort value
) {
1487 //pragma(inline, true);
1488 pokeb_3ts(--SP
, (value
>>8)&0xff);
1489 pokeb_3ts(--SP
, value
&0xff);
1492 // you are not expected to understand the following bitmess
1493 // the only thing you want to know that IT WORKS; just believe me and testing suite
1494 nothrow @trusted @nogc {
1495 void ADC_A (ubyte b
) {
1496 pragma(inline
, true);
1497 ushort newv
, o
= AF
.a
;
1498 AF
.a
= (newv
= cast(ushort)(o
+b
+(AF
.f
&Z80Flag
.C
)))&0xff; // Z80Flag.C is 0x01, so it's safe
1500 tblSZ53
.ptr
[newv
&0xff]|
1501 (newv
> 0xff ? Z80Flag
.C
: 0)|
1502 ((o^
(~(b
)))&(o^newv
)&0x80 ? Z80Flag
.PV
: 0)|
1503 ((o
&0x0f)+(b
&0x0f)+(AF
.f
&Z80Flag
.C
) >= 0x10 ? Z80Flag
.H
: 0);
1506 void SBC_A (ubyte b
) {
1507 pragma(inline
, true);
1508 ushort newv
, o
= AF
.a
;
1509 AF
.a
= (newv
= (cast(int)o
-cast(int)b
-cast(int)(AF
.f
&Z80Flag
.C
))&0xffff)&0xff; // Z80Flag.C is 0x01, so it's safe
1512 tblSZ53
.ptr
[newv
&0xff]|
1513 (newv
> 0xff ? Z80Flag
.C
: 0)|
1514 ((o^b
)&(o^newv
)&0x80 ? Z80Flag
.PV
: 0)|
1515 (cast(int)(o
&0x0f)-cast(int)(b
&0x0f)-cast(int)(AF
.f
&Z80Flag
.C
) < 0 ? Z80Flag
.H
: 0);
1518 void ADD_A (ubyte b
) {
1519 pragma(inline
, true);
1520 AF
.f
&= ~(Z80Flag
.C
);
1524 void SUB_A (ubyte b
) {
1525 pragma(inline
, true);
1526 AF
.f
&= ~(Z80Flag
.C
);
1530 void CP_A (ubyte b
) {
1531 pragma(inline
, true);
1532 ubyte o
= AF
.a
, newv
= (cast(int)o
-cast(int)b
)&0xff;
1537 (newv
== 0 ? Z80Flag
.Z
: 0)|
1538 (o
< b ? Z80Flag
.C
: 0)|
1539 ((o^b
)&(o^newv
)&0x80 ? Z80Flag
.PV
: 0)|
1540 (cast(int)(o
&0x0f)-cast(int)(b
&0x0f) < 0 ? Z80Flag
.H
: 0);
1543 void AND_A (ubyte b
) { pragma(inline
, true); flags_q
= AF
.f
= tblSZP53
.ptr
[AF
.a
&= b
]|Z80Flag
.H
; }
1544 void OR_A (ubyte b
) { pragma(inline
, true); flags_q
= AF
.f
= tblSZP53
.ptr
[AF
.a |
= b
]; }
1545 void XOR_A (ubyte b
) { pragma(inline
, true); flags_q
= AF
.f
= tblSZP53
.ptr
[AF
.a ^
= b
]; }
1548 ubyte DEC8 (ubyte b
) {
1549 pragma(inline
, true);
1552 (b
== 0x80 ? Z80Flag
.PV
: 0)|
1553 (b
&0x0f ?
0 : Z80Flag
.H
)|
1554 tblSZ53
.ptr
[(cast(int)b
-1)&0xff];
1556 return (cast(int)b
-1)&0xff;
1560 ubyte INC8 (ubyte b
) {
1561 pragma(inline
, true);
1564 (b
== 0x7f ? Z80Flag
.PV
: 0)|
1565 ((b
+1)&0x0f ?
0 : Z80Flag
.H
)|
1566 tblSZ53
.ptr
[(b
+1)&0xff];
1568 return ((b
+1)&0xff);
1571 // cyclic, carry reflects shifted bit
1573 pragma(inline
, true);
1574 ubyte c
= ((AF
.a
>>7)&0x01);
1575 AF
.a
= cast(ubyte)((AF
.a
<<1)|c
);
1576 flags_q
= AF
.f
= cast(ubyte)(c|
(AF
.a
&Z80Flag
.F35
)|
(AF
.f
&(Z80Flag
.PV|Z80Flag
.Z|Z80Flag
.S
)));
1579 // cyclic, carry reflects shifted bit
1581 pragma(inline
, true);
1582 ubyte c
= (AF
.a
&0x01);
1583 AF
.a
= cast(ubyte)((AF
.a
>>1)|
(c
<<7));
1584 flags_q
= AF
.f
= cast(ubyte)(c|
(AF
.a
&Z80Flag
.F35
)|
(AF
.f
&(Z80Flag
.PV|Z80Flag
.Z|Z80Flag
.S
)));
1587 // cyclic thru carry
1589 pragma(inline
, true);
1590 ubyte c
= ((AF
.a
>>7)&0x01);
1591 AF
.a
= cast(ubyte)((AF
.a
<<1)|
(AF
.f
&Z80Flag
.C
));
1592 flags_q
= AF
.f
= cast(ubyte)(c|
(AF
.a
&Z80Flag
.F35
)|
(AF
.f
&(Z80Flag
.PV|Z80Flag
.Z|Z80Flag
.S
)));
1595 // cyclic thru carry
1597 pragma(inline
, true);
1598 ubyte c
= (AF
.a
&0x01);
1599 AF
.a
= (AF
.a
>>1)|
((AF
.f
&Z80Flag
.C
)<<7);
1600 flags_q
= AF
.f
= c|
(AF
.a
&Z80Flag
.F35
)|
(AF
.f
&(Z80Flag
.PV|Z80Flag
.Z|Z80Flag
.S
));
1603 // cyclic thru carry
1604 ubyte RL (ubyte b
) {
1605 pragma(inline
, true);
1606 ubyte c
= (b
>>7)&Z80Flag
.C
;
1607 flags_q
= AF
.f
= tblSZP53
.ptr
[(b
= ((b
<<1)&0xff)|
(AF
.f
&Z80Flag
.C
))]|c
;
1611 ubyte RR (ubyte b
) {
1612 pragma(inline
, true);
1614 flags_q
= AF
.f
= tblSZP53
.ptr
[(b
= (b
>>1)|
((AF
.f
&Z80Flag
.C
)<<7))]|c
;
1618 // cyclic, carry reflects shifted bit
1619 ubyte RLC (ubyte b
) {
1620 pragma(inline
, true);
1621 ubyte c
= ((b
>>7)&Z80Flag
.C
);
1622 flags_q
= AF
.f
= tblSZP53
.ptr
[(b
= ((b
<<1)&0xff)|c
)]|c
;
1626 // cyclic, carry reflects shifted bit
1627 ubyte RRC (ubyte b
) {
1628 pragma(inline
, true);
1630 flags_q
= AF
.f
= tblSZP53
.ptr
[(b
= cast(ubyte)((b
>>1)|
(c
<<7)))]|c
;
1634 ubyte SLA (ubyte b
) {
1635 pragma(inline
, true);
1636 ubyte c
= ((b
>>7)&0x01);
1637 flags_q
= AF
.f
= tblSZP53
.ptr
[(b
<<= 1)]|c
;
1641 ubyte SRA (ubyte b
) {
1642 pragma(inline
, true);
1644 flags_q
= AF
.f
= tblSZP53
.ptr
[(b
= (b
>>1)|
(b
&0x80))]|c
;
1648 ubyte SLL (ubyte b
) {
1649 pragma(inline
, true);
1650 ubyte c
= ((b
>>7)&0x01);
1651 flags_q
= AF
.f
= tblSZP53
.ptr
[(b
= cast(ubyte)((b
<<1)|
0x01))]|c
;
1655 ubyte SLR (ubyte b
) {
1656 pragma(inline
, true);
1658 flags_q
= AF
.f
= tblSZP53
.ptr
[(b
>>= 1)]|c
;
1662 static immutable ubyte[8] hct
= [ 0, Z80Flag
.H
, Z80Flag
.H
, Z80Flag
.H
, 0, 0, 0, Z80Flag
.H
];
1665 ushort ADD_DD (ushort value
, ushort ddvalue
) {
1666 pragma(inline
, true);
1667 uint res
= cast(uint)value
+cast(uint)ddvalue
;
1668 ubyte b
= ((value
&0x0800)>>11)|
((ddvalue
&0x0800)>>10)|
((res
&0x0800)>>9);
1669 MEMPTR
.w
= (ddvalue
+1)&0xffff;
1671 (AF
.f
&(Z80Flag
.PV|Z80Flag
.Z|Z80Flag
.S
))|
1672 (res
> 0xffff ? Z80Flag
.C
: 0)|
1673 ((res
>>8)&Z80Flag
.F35
)|
1675 return cast(ushort)res
;
1679 ushort ADC_DD (ushort value
, ushort ddvalue
) {
1680 pragma(inline
, true);
1681 ubyte c
= (AF
.f
&Z80Flag
.C
);
1682 uint newv
= cast(uint)value
+cast(uint)ddvalue
+cast(uint)c
;
1683 ushort res
= newv
&0xffff;
1684 MEMPTR
.w
= (ddvalue
+1)&0xffff;
1686 ((res
>>8)&Z80Flag
.S35
)|
1687 (res
== 0 ? Z80Flag
.Z
: 0)|
1688 (newv
> 0xffff ? Z80Flag
.C
: 0)|
1689 ((value^
((~(ddvalue
))&0xffff))&(value^newv
)&0x8000 ? Z80Flag
.PV
: 0)|
1690 ((value
&0x0fff)+(ddvalue
&0x0fff)+c
>= 0x1000 ? Z80Flag
.H
: 0);
1695 ushort SBC_DD (ushort value
, ushort ddvalue
) {
1696 //pragma(inline, true);
1698 MEMPTR
.w
= (ddvalue
+1)&0xffff;
1699 AF
.a
= ddvalue
&0xff;
1702 AF
.a
= (ddvalue
>>8)&0xff;
1703 SBC_A((value
>>8)&0xff);
1706 flags_q
= AF
.f
= (res ? AF
.f
&(~(Z80Flag
.Z
)) : AF
.f|Z80Flag
.Z
);
1710 void BIT (ubyte bit
, ubyte num
, bool mptr
) {
1711 pragma(inline
, true);
1716 (num
&(1<<bit
) ?
0 : Z80Flag
.PV|Z80Flag
.Z
)|
1717 (bit
== 7 ? num
&Z80Flag
.S
: 0);
1718 if (mptr
) AF
.f
= ((AF
.f
&~(Z80Flag
.F35
))|
(MEMPTR
.hi
&Z80Flag
.F35
))&0xff;
1723 //pragma(inline, true);
1724 ubyte tmpI
= 0, tmpC
= (AF
.f
&Z80Flag
.C
), tmpA
= AF
.a
;
1725 if ((AF
.f
&Z80Flag
.H
) ||
(tmpA
&0x0f) > 9) tmpI
= 6;
1726 if (tmpC
!= 0 || tmpA
> 0x99) tmpI |
= 0x60;
1727 if (tmpA
> 0x99) tmpC
= Z80Flag
.C
;
1728 if (AF
.f
&Z80Flag
.N
) SUB_A(tmpI
); else ADD_A(tmpI
);
1729 flags_q
= AF
.f
= (AF
.f
&~(Z80Flag
.C|Z80Flag
.PV
))|tmpC|tblParity
.ptr
[AF
.a
];
1734 //pragma(inline, true);
1735 ubyte tmpB
= peekb_3ts(HL
);
1737 MEMPTR
.w
= (HL
+1)&0xffff;
1738 contention_by1ts(HL
, 4, true);
1739 pokeb_3ts(HL
, cast(ubyte)((AF
.a
<<4)|
(tmpB
>>4)));
1740 AF
.a
= (AF
.a
&0xf0)|
(tmpB
&0x0f);
1741 flags_q
= AF
.f
= (AF
.f
&Z80Flag
.C
)|tblSZP53
.ptr
[AF
.a
];
1745 //pragma(inline, true);
1746 ubyte tmpB
= peekb_3ts(HL
);
1748 MEMPTR
.w
= (HL
+1)&0xffff;
1749 contention_by1ts(HL
, 4, true);
1750 pokeb_3ts(HL
, cast(ubyte)((tmpB
<<4)|
(AF
.a
&0x0f)));
1751 AF
.a
= (AF
.a
&0xf0)|
(tmpB
>>4);
1752 flags_q
= AF
.f
= (AF
.f
&Z80Flag
.C
)|tblSZP53
.ptr
[AF
.a
];
1755 void LD_A_IR (ubyte ir
) {
1756 pragma(inline
, true);
1758 prevWasEIDDR
= EIDDR
.LdIorR
;
1759 contention_by1ts_ir(1);
1760 flags_q
= AF
.f
= tblSZ53
.ptr
[AF
.a
]|
(AF
.f
&Z80Flag
.C
)|
(IFF2 ? Z80Flag
.PV
: 0);
1763 // ////////////////////////////////////////////////////////////////////////// //
1764 // opcode info interface
1766 /// conditions for various jump instructions
1777 /// indirect memory access type
1778 enum ZOIMemIO
: int {
1788 /// indirect jump type
1789 enum ZOIJump
: int {
1798 enum ZOIPortIO
: int {
1801 BCM1
= -2 /// (B-1)C, for OUT*
1804 /// stack access type
1807 BC
= 0, DE
, HL
, AF
, IX
, IY
, PC
///
1810 enum ZOIDisp
{ None
= 0xffff }
1814 int len
= 0; /// instruction length
1815 bool memrwword
= false; /// true: reading word
1816 int memread
= ZOIMemIO
.None
; /// ZOIMemIO or addr
1817 int memwrite
= ZOIMemIO
.None
; /// ZOIMemIO or addr
1818 int jump
= ZOIJump
.None
; /// ZOIJump or addr
1819 ZOICond cond
= ZOICond
.None
; /// ZOICond
1820 int portread
= ZOIPortIO
.None
; /// ZOIPortIO or addr; if addr is specified, high byte must be taken from A
1821 int portwrite
= ZOIPortIO
.None
; /// ZOIPortIO or addr; if addr is specified, high byte must be taken from A
1822 ZOIStack
push = ZOIStack
.None
; /// ZOIStack; CALL/RST will set ZOIStack.PC
1823 ZOIStack
pop = ZOIStack
.None
; /// ZOIStack; RET will set ZOIStack.PC
1824 int disp
= ZOIDisp
.None
; /// for (IX+n) / (IY+n), else ZOIDisp.None
1825 int trap
= -1; /// slt and other trap opcode or -1
1828 /// Get opcode information. Useful for debuggers.
1829 final void opcodeInfo (ref ZOInfo nfo
, ushort pc
) nothrow @trusted {
1830 static bool isRepeated() (ushort opc
) pure nothrow @safe @nogc { pragma(inline
, true); return ((opc
&0x10) != 0); }
1833 int ixy
= -1; // 0: ix; 1: iy
1838 ubyte memReadPC () nothrow @trusted { immutable ubyte b
= mem
.ptr
[pc
/MemPage
.Size
].mem
[pc
%MemPage
.Size
]; ++pc
; return b
; }
1839 ushort readPCW () nothrow @trusted { ushort res
= memReadPC
; res |
= memReadPC
<<8; return res
; }
1841 ubyte opcode
= memReadPC
;
1842 if (opcode
== 0xdd || opcode
== 0xfd) {
1843 static immutable uint[8] withIndexBmp
= [0x00u
,0x700000u
,0x40404040u
,0x40bf4040u
,0x40404040u
,0x40404040u
,0x0800u
,0x00u
];
1845 ixy
= (opcode
== 0xfd ?
1 : 0); // just in case, hehe
1846 //opcode = memRead(pc++, MemIO.Other);
1848 if (withIndexBmp
[opcode
>>5]&(1<<(opcode
&0x1f))) {
1849 // 3rd byte is always DISP here
1851 if (disp
> 127) disp
-= 256; // convert to int8_t
1853 nfo
.memread
= (ixy ? ZOIMemIO
.IY
: ZOIMemIO
.IX
);
1854 } else if (opcode
== 0xdd && opcode
== 0xfd) {
1862 if (opcode
== 0xed) {
1863 ixy
= 0; // а нас -- рать!
1866 // LDI, LDIR, LDD, LDDR
1867 case 0xa0: case 0xb0: case 0xa8: case 0xb8:
1868 nfo
.memwrite
= ZOIMemIO
.DE
;
1870 // CPI, CPIR, CPD, CPDR
1871 case 0xa1: case 0xb1: case 0xa9: case 0xb9:
1872 nfo
.memread
= ZOIMemIO
.HL
;
1873 if (isRepeated(opcode
)) { nfo
.cond
= ZOICond
.BNZ
; nfo
.jump
= orgpc
; }
1875 // INI, INIR, IND, INDR
1876 case 0xa2: case 0xb2: case 0xaa: case 0xba:
1878 // OUTI, OTIR, OUTD, OTDR
1879 case 0xa3: case 0xb3: case 0xab: case 0xbb:
1880 if (opcode
&0x01) nfo
.portwrite
= ZOIPortIO
.BCM1
; else nfo
.portread
= ZOIPortIO
.BC
;
1881 if (isRepeated(opcode
)) { nfo
.cond
= ZOICond
.BNZ
; nfo
.jump
= orgpc
; }
1883 // not strings, but some good instructions anyway
1885 if ((opcode
&0xc0) == 0x40) {
1886 switch (opcode
&0x07) {
1888 case 0: nfo
.portread
= ZOIPortIO
.BC
; break;
1890 case 1: nfo
.portwrite
= ZOIPortIO
.BC
; break;
1891 // SBC HL,rr/ADC HL,rr
1893 // LD (nn),rr/LD rr,(nn)
1895 if (opcode
&0x08) nfo
.memread
= readPCW
; else nfo
.memwrite
= readPCW
;
1896 nfo
.memrwword
= true;
1902 nfo
.jump
= ZOIJump
.RET
;
1903 nfo
.pop = ZOIStack
.PC
;
1904 nfo
.cond
= (opcode
&0x08 ? ZOICond
.RETI
: ZOICond
.RETN
);
1905 nfo
.memread
= ZOIMemIO
.SP
;
1906 nfo
.memrwword
= true;
1914 /*case 0x47: break;*/
1916 /*case 0x4f: break;*/
1918 /*case 0x57: break;*/
1925 nfo
.memread
= nfo
.memwrite
= ZOIMemIO
.HL
;
1938 } else if (opcode
== 0xcb) {
1939 // shifts and bit operations
1940 //opcode = memRead(pc++, MemIO.Other);
1942 if (!gotDD
&& (opcode
&0x07) == 6) nfo
.memread
= nfo
.memwrite
= ZOIMemIO
.HL
;
1943 if ((opcode
&0xc0) != 0x40) {
1944 if (gotDD
) nfo
.memwrite
= nfo
.memread
; // all except BIT writes back
1946 nfo
.memwrite
= ZOIMemIO
.None
;
1951 final switch (opcode
&0xc0) {
1954 switch (opcode
&0x07) {
1955 // misc,DJNZ,JR,JR cc
1959 if (opcode
&0x20) nfo
.cond
= cast(ZOICond
)((opcode
>>3)&0x03); // JR cc
1960 else if ((opcode
&0x08) == 0) nfo
.cond
= ZOICond
.BNZ
; // DJNZ ; else -- JR
1961 //disp = memRead(pc++, MemIO.Other);
1963 if (disp
> 127) disp
-= 256; // convert to byte
1964 nfo
.jump
= (pc
+disp
)&0xffff;
1965 } // else EX AF,AF' or NOP
1967 // LD rr,nn/ADD HL,rr
1969 if (!(opcode
&0x08)) pc
= (pc
+2)&0xffff;
1973 final switch ((opcode
>>3)&0x07) {
1975 case 0: nfo
.memwrite
= ZOIMemIO
.BC
; break;
1977 case 1: nfo
.memread
= ZOIMemIO
.BC
; break;
1979 case 2: nfo
.memwrite
= ZOIMemIO
.DE
; break;
1981 case 3: nfo
.memread
= ZOIMemIO
.DE
; break;
1984 nfo
.memwrite
= readPCW
;
1985 nfo
.memrwword
= true;
1989 nfo
.memread
= readPCW
;
1990 nfo
.memrwword
= true;
1994 nfo
.memwrite
= readPCW
;
1998 nfo
.memread
= readPCW
;
2009 if (((opcode
>>3)&0x07) == 6) {
2011 if (gotDD
) nfo
.memwrite
= nfo
.memread
;
2012 else nfo
.memwrite
= nfo
.memread
= ZOIMemIO
.HL
;
2018 if (((opcode
>>3)&0x07) == 6) {
2019 if (!gotDD
) nfo
.memwrite
= ZOIMemIO
.HL
;
2020 else { nfo
.memwrite
= nfo
.memread
; nfo
.memread
= ZOIMemIO
.None
; }
2028 // 0x40..0x7F: LD r8,r8
2030 if (opcode
!= 0x76) {
2031 if (!gotDD
&& (opcode
&0x07) == 6) nfo
.memread
= ZOIMemIO
.HL
;
2032 if (((opcode
>>3)&0x07) == 6) { nfo
.memwrite
= (gotDD ? nfo
.memread
: ZOIMemIO
.HL
); nfo
.memread
= ZOIMemIO
.None
; }
2035 // 0x80..0xBF: ALU A,r8
2037 if (!gotDD
&& (opcode
&0x07) == 6) nfo
.memread
= ZOIMemIO
.HL
;
2041 final switch (opcode
&0x07) {
2044 nfo
.jump
= ZOIJump
.RET
;
2045 nfo
.pop = ZOIStack
.PC
;
2046 nfo
.cond
= cast(ZOICond
)((opcode
>>3)&0x07);
2047 nfo
.memread
= ZOIMemIO
.SP
;
2048 nfo
.memrwword
= true;
2054 switch ((opcode
>>4)&0x03) {
2057 nfo
.jump
= ZOIJump
.RET
;
2058 nfo
.pop = ZOIStack
.PC
;
2059 nfo
.memread
= ZOIMemIO
.SP
;
2060 nfo
.memrwword
= true;
2066 nfo
.jump
= (ixy
< 0 ? ZOIJump
.HL
: (ixy ? ZOIJump
.IY
: ZOIJump
.IX
));
2074 nfo
.memread
= ZOIMemIO
.SP
;
2075 nfo
.memrwword
= true;
2076 final switch ((opcode
>>4)&0x03) {
2077 case 0: nfo
.pop = ZOIStack
.BC
; break;
2078 case 1: nfo
.pop = ZOIStack
.DE
; break;
2079 case 2: nfo
.pop = (ixy
< 0 ? ZOIStack
.HL
: (ixy ? ZOIStack
.IY
: ZOIStack
.IX
)); break;
2080 case 3: nfo
.pop = ZOIStack
.AF
; break;
2087 nfo
.cond
= cast(ZOICond
)((opcode
>>3)&0x07);
2089 // special1/special3
2091 switch ((opcode
>>3)&0x07) {
2098 //nfo.portwrite = memRead(pc++, MemIO.Other);
2099 nfo
.portwrite
= memReadPC
;
2103 //nfo.portread = memRead(pc++, MemIO.Other);
2104 nfo
.portread
= memReadPC
;
2108 nfo
.memread
= nfo
.memwrite
= ZOIMemIO
.SP
;
2109 nfo
.memrwword
= true;
2123 nfo
.push = ZOIStack
.PC
;
2124 nfo
.cond
= cast(ZOICond
)((opcode
>>3)&0x07);
2125 nfo
.memwrite
= ZOIMemIO
.SP
;
2126 nfo
.memrwword
= true;
2131 if (((opcode
>>4)&0x03) == 0) {
2134 nfo
.push = ZOIStack
.PC
;
2135 nfo
.memwrite
= ZOIMemIO
.SP
;
2136 nfo
.memrwword
= true;
2140 nfo
.memwrite
= ZOIMemIO
.SP
;
2141 nfo
.memrwword
= true;
2142 final switch ((opcode
>>4)&0x03) {
2143 case 0: nfo
.push = ZOIStack
.BC
; break;
2144 case 1: nfo
.push = ZOIStack
.DE
; break;
2145 case 2: nfo
.push = (ixy
>= 0 ?
(ixy ? ZOIStack
.IY
: ZOIStack
.IX
) : ZOIStack
.HL
); break;
2146 case 3: nfo
.push = ZOIStack
.AF
; break;
2156 nfo
.jump
= (opcode
&0x38);
2157 nfo
.push = ZOIStack
.PC
;
2158 nfo
.memwrite
= ZOIMemIO
.SP
;
2159 nfo
.memrwword
= true;
2165 nfo
.len
= (pc
>= orgpc ? pc
-orgpc
: pc
+0x10000-orgpc
);
2168 // ////////////////////////////////////////////////////////////////////////// //
2171 /// disassembled opcode
2173 int len
; /// instruction length
2174 char[128] disbuf
; /// buffer that holds disassembled text
2175 const(char)[] mnemo
; /// points into disbuf
2176 const(char)[][3] args
; /// points into disbuf
2177 // disassembler options
2178 bool decimal
; /// use decimal numbers instead of #XXXX
2179 bool locase
; /// use lower-cased text
2182 /// Disassemble command
2183 final void disasm (ref ZDisop nfo
, ushort pc
) nothrow @trusted {
2184 ubyte memReadPC () nothrow @trusted { /*pragma(inline, true);*/ immutable ubyte b
= mem
.ptr
[pc
/MemPage
.Size
].mem
[pc
%MemPage
.Size
]; ++pc
; return b
; }
2185 ushort memReadPCW () nothrow @trusted {
2186 ushort w
= mem
.ptr
[pc
/MemPage
.Size
].mem
[pc
%MemPage
.Size
];
2188 w |
= mem
.ptr
[pc
/MemPage
.Size
].mem
[pc
%MemPage
.Size
]<<8;
2194 scope(exit
) nfo
.len
= (pc
< stpc ? pc
+0x10000-stpc
: pc
-stpc
);
2208 if (nfo
.mnemo
.length
) assert(0, "internal error");
2209 nfo
.mnemo
= nfo
.disbuf
[0..dbpos
];
2214 if (curarg
>= nfo
.args
.length
) assert(0, "internal error");
2215 nfo
.args
[curarg
++] = nfo
.disbuf
[dbstpos
..dbpos
];
2219 void putKeepCase (const(char)[] s
...) nothrow @trusted {
2220 assert(s
.length
<= 1024);
2221 if (dbpos
+s
.length
> nfo
.disbuf
.length
) s
= s
[0..nfo
.disbuf
.length
-dbpos
];
2223 nfo
.disbuf
[dbpos
..dbpos
+s
.length
] = s
[];
2224 dbpos
+= cast(uint)s
.length
;
2228 void put (const(char)[] s
...) nothrow @trusted {
2229 assert(s
.length
<= 1024);
2230 if (dbpos
+s
.length
> nfo
.disbuf
.length
) s
= s
[0..nfo
.disbuf
.length
-dbpos
];
2231 foreach (char ch
; s
) {
2233 if (ch
>= 'A' && ch
<= 'Z') ch
+= 32;
2235 if (ch
>= 'a' && ch
<= 'z') ch
-= 32;
2237 nfo
.disbuf
.ptr
[dbpos
++] = ch
;
2241 void putMnemo (string s
) nothrow @trusted {
2246 void putArg (string s
) nothrow @trusted {
2251 void putUIntDec (uint n
) nothrow @trusted {
2252 char[16] buf
= void;
2253 uint bpos
= cast(uint)buf
.length
;
2254 do { buf
.ptr
[--bpos
] = cast(char)('0'+n
%10); } while ((n
/= 10) != 0);
2258 void putUIntHex (uint n
, int len
) nothrow @trusted {
2259 char[16] buf
= void;
2260 uint bpos
= cast(uint)buf
.length
;
2261 if (len
> 16) len
= 16;
2262 do { buf
.ptr
[--bpos
] = cast(char)('0'+n
%16+(n
%16 > 9 ?
7 : 0)); } while ((n
/= 16) != 0);
2263 while (buf
.length
-bpos
< len
) buf
.ptr
[--bpos
] = '0';
2264 buf
.ptr
[--bpos
] = '#';
2268 void putUInt (uint n
, int len
) nothrow @trusted {
2269 if (nfo
.decimal
) putUIntDec(n
); else putUIntHex(n
, len
);
2272 void putDisp (int n
) nothrow @trusted {
2282 void putCC (uint n
) {
2284 case 0: putArg("NZ"); break;
2285 case 1: putArg("Z"); break;
2286 case 2: putArg("NC"); break;
2287 case 3: putArg("C"); break;
2288 case 4: putArg("PO"); break;
2289 case 5: putArg("PE"); break;
2290 case 6: putArg("P"); break;
2291 case 7: putArg("M"); break;
2295 void putR8(string hlspec
=null) (uint n
) {
2297 case 0: putArg("B"); break;
2298 case 1: putArg("C"); break;
2299 case 2: putArg("D"); break;
2300 case 3: putArg("E"); break;
2301 case 4: putArg("H"); break;
2302 case 5: putArg("L"); break;
2304 static if (hlspec
.length
) {
2309 if (gotDD
) putDisp(disp
);
2313 case 7: putArg("A"); break;
2317 void putR16SP(string hlspec
=null) (uint n
) {
2319 case 0: putArg("BC"); break;
2320 case 1: putArg("DE"); break;
2321 case 2: static if (hlspec
.length
) putArg(hlspec
); else putArg(DD
); break;
2322 case 3: putArg("SP"); break;
2326 void putR16AF(string hlspec
=null) (uint n
) {
2328 case 0: putArg("BC"); break;
2329 case 1: putArg("DE"); break;
2330 case 2: static if (hlspec
.length
) putArg(hlspec
); else putArg(DD
); break;
2331 case 3: putArg("AF"); break;
2335 ubyte opcode
= memReadPC
;
2339 // check for I[XY] prefix
2340 if (opcode
== 0xdd || opcode
== 0xfd) {
2341 //TODO: generate this table in compile time
2342 static immutable uint[8] withIndexBmp
= [0x00,0x700000,0x40404040,0x40bf4040,0x40404040,0x40404040,0x0800,0x00];
2344 DD
= (opcode
== 0xdd ?
"IX" : "IY");
2345 // read opcode -- OCR(4)
2347 // test if this instruction have (HL)
2348 if (withIndexBmp
[opcode
>>5]&(1<<(opcode
&0x1f))) {
2349 // 3rd byte is always DISP here
2351 if (disp
> 127) disp
-= 256;
2352 } else if (opcode
== 0xdd && opcode
== 0xfd) {
2353 // double prefix; put special NOP
2354 --pc
; // rollback for correct length
2356 if (opcode
== 0xdd) putArg("#DD"); else putArg("#FD");
2361 // ED-prefixed instructions
2362 if (opcode
== 0xed) {
2363 DD
= "HL"; // а нас -- рать!
2364 // read opcode -- OCR(4)
2367 // LDI, LDIR, LDD, LDDR
2368 case 0xa0: putMnemo("LDI"); return;
2369 case 0xb0: putMnemo("LDIR"); return;
2370 case 0xa8: putMnemo("LDD"); return;
2371 case 0xb8: putMnemo("LDDR"); return;
2372 // CPI, CPIR, CPD, CPDR
2373 case 0xa1: putMnemo("CPI"); return;
2374 case 0xb1: putMnemo("CPIR"); return;
2375 case 0xa9: putMnemo("CPD"); return;
2376 case 0xb9: putMnemo("CPDR"); return;
2377 // OUTI, OTIR, OUTD, OTDR
2378 case 0xa3: putMnemo("OUTI"); return;
2379 case 0xb3: putMnemo("OTIR"); return;
2380 case 0xab: putMnemo("OUTD"); return;
2381 case 0xbb: putMnemo("OTDR"); return;
2382 // INI, INIR, IND, INDR
2383 case 0xa2: putMnemo("INI"); return;
2384 case 0xb2: putMnemo("INIR"); return;
2385 case 0xaa: putMnemo("IND"); return;
2386 case 0xba: putMnemo("INDR"); return;
2387 // not strings, but some good instructions anyway
2389 if ((opcode
&0xc0) == 0x40) {
2391 final switch (opcode
&0x07) {
2392 case 0: // IN r8,(C)
2393 case 1: // OUT (C),r8
2394 if (opcode
&0x07) { putMnemo("OUT"); putArg("(C)"); } else putMnemo("IN");
2395 if (opcode
&0x07) putR8
!"0"((opcode
>>3)&0x07); else putR8
!"F"((opcode
>>3)&0x07); // 0 on NMOS, 255 on CMOS
2396 if ((opcode
&0x07) == 0) putArg("(C)");
2398 // SBC HL,rr/ADC HL,rr
2400 putMnemo(opcode
&0x08 ?
"ADC" : "SBC");
2402 putR16SP
!"HL"((opcode
>>4)&0x03);
2404 // LD (nn),rr/LD rr,(nn)
2407 if ((opcode
&0x08) == 0) {
2410 putUInt(memReadPC
, 4);
2414 putR16SP
!"HL"((opcode
>>4)&0x03);
2415 if ((opcode
&0x08) != 0) {
2418 putUInt(memReadPC
, 4);
2429 // RETI: 0x4d, 0x5d, 0x6d, 0x7d
2430 // RETN: 0x45, 0x55, 0x65, 0x75
2431 if (opcode
&0x08) putMnemo("RETI"); else putMnemo("RETN");
2437 case 0x56: case 0x76: putArg("1"); break;
2438 case 0x5e: case 0x7e: putArg("2"); break;
2439 default: putArg("0"); break;
2444 final switch (opcode
) {
2445 case 0x47: // LD I,A
2450 case 0x4f: // LD R,A
2455 case 0x57: // LD A,I
2460 case 0x5f: // LD A,R
2474 // slt and other traps
2475 if (opcode
== 0xFB) {
2479 putUIntHex(opcode
, 2);
2488 putUIntHex(opcode
, 2);
2492 // CB-prefixed instructions
2493 if (opcode
== 0xcb) {
2494 // shifts and bit operations
2498 switch ((opcode
>>3)&0x1f) {
2499 case 0: putMnemo("RLC"); break;
2500 case 1: putMnemo("RRC"); break;
2501 case 2: putMnemo("RL"); break;
2502 case 3: putMnemo("RR"); break;
2503 case 4: putMnemo("SLA"); break;
2504 case 5: putMnemo("SRA"); break;
2505 case 6: putMnemo("SLL"); break;
2506 case 7: putMnemo("SLR"); break;
2508 final switch ((opcode
>>6)&0x03) {
2509 case 1: putMnemo("BIT"); break;
2510 case 2: putMnemo("RES"); break;
2511 case 3: putMnemo("SET"); break;
2513 putUIntDec((opcode
>>3)&0x07);
2525 putR8
!"(HL)"(opcode
&0x07);
2527 // possible extra operand
2528 if ((opcode
&0xc0) != 0x40) {
2529 // BITs are not welcome here
2535 final switch (opcode
&0xc0) {
2538 final switch (opcode
&0x07) {
2539 // misc,DJNZ,JR,JR cc
2546 putCC((opcode
>>3)&0x03);
2549 putMnemo(opcode
&0x08 ?
"JR" : "DJNZ");
2552 if (disp
> 127) disp
-= 256; // convert to int8_t
2553 ushort addr
= cast(ushort)(pc
+disp
);
2567 // LD rr,nn/ADD HL,rr
2573 putR16SP((opcode
>>4)&0x03);
2577 putR16SP((opcode
>>4)&0x03);
2578 putUInt(memReadPCW
, 4);
2585 final switch ((opcode
>>3)&0x07) {
2587 case 0: putArg("(BC)"); putArg("A"); break;
2589 case 1: putArg("A"); putArg("(BC)"); break;
2591 case 2: putArg("(DE)"); putArg("A"); break;
2593 case 3: putArg("A"); putArg("(DE)"); break;
2595 case 4: put("("); putUInt(memReadPCW
, 4); put(")"); endArg(); put(DD
); endArg(); break;
2597 case 5: put(DD
); endArg(); put("("); putUInt(memReadPCW
, 4); put(")"); endArg(); break;
2599 case 6: put("("); putUInt(memReadPCW
, 4); put(")"); endArg(); putArg("A"); break;
2601 case 7: putArg("A"); put("("); putUInt(memReadPCW
, 4); put(")"); endArg(); break;
2606 putMnemo(opcode
&0x08 ?
"DEC" : "INC");
2607 putR16SP((opcode
>>4)&0x03);
2609 // INC/DEC r8; LD r8,n
2613 final switch (opcode
&0x07) {
2614 case 4: putMnemo("INC"); break;
2615 case 5: putMnemo("DEC"); break;
2616 case 6: putMnemo("LD"); break;
2618 final switch ((opcode
>>3)&0x07) {
2619 case 0: putArg("B"); break;
2620 case 1: putArg("C"); break;
2621 case 2: putArg("D"); break;
2622 case 3: putArg("E"); break;
2623 case 4: if (gotDD
) put(DD
); putArg("H"); endArg(); break;
2624 case 5: if (gotDD
) put(DD
); putArg("L"); endArg(); break;
2628 if (gotDD
) putDisp(disp
);
2632 case 7: putArg("A"); break;
2635 if ((opcode
&0x07) == 6) {
2636 putUInt(memReadPC
, 2);
2642 final switch ((opcode
>>3)&0x07) {
2643 case 0: putMnemo("RLCA"); break;
2644 case 1: putMnemo("RRCA"); break;
2645 case 2: putMnemo("RLA"); break;
2646 case 3: putMnemo("RRA"); break;
2647 case 4: putMnemo("DAA"); break;
2648 case 5: putMnemo("CPL"); break;
2649 case 6: putMnemo("SCF"); break;
2650 case 7: putMnemo("CCF"); break;
2655 // 0x40..0x7F: LD r8,r8
2657 if (opcode
== 0x76) { putMnemo("HALT"); return; }
2659 immutable ubyte rsrc
= (opcode
&0x07);
2660 immutable ubyte rdst
= ((opcode
>>3)&0x07);
2661 final switch (rsrc
) {
2662 case 0: putArg("B"); break;
2663 case 1: putArg("C"); break;
2664 case 2: putArg("D"); break;
2665 case 3: putArg("E"); break;
2666 case 4: if (gotDD
&& rdst
!= 6) put(DD
); put("H"); endArg(); break;
2667 case 5: if (gotDD
&& rdst
!= 6) put(DD
); put("L"); endArg(); break;
2671 if (gotDD
) putDisp(disp
);
2675 case 7: putArg("A"); break;
2677 final switch (rdst
) {
2678 case 0: putArg("B"); break;
2679 case 1: putArg("C"); break;
2680 case 2: putArg("D"); break;
2681 case 3: putArg("E"); break;
2682 case 4: if (gotDD
&& rsrc
!= 6) put(DD
); put("H"); endArg(); break;
2683 case 5: if (gotDD
&& rsrc
!= 6) put(DD
); put("L"); endArg(); break;
2687 if (gotDD
) putDisp(disp
);
2691 case 7: putArg("A"); break;
2694 // 0x80..0xBF: ALU A,r8
2696 final switch ((opcode
>>3)&0x07) {
2697 case 0: putMnemo("ADD"); putArg("A"); break;
2698 case 1: putMnemo("ADC"); putArg("A"); break;
2699 case 2: putMnemo("SUB"); putArg("A"); break;
2700 case 3: putMnemo("SBC"); putArg("A"); break;
2701 case 4: putMnemo("AND"); break;
2702 case 5: putMnemo("XOR"); break;
2703 case 6: putMnemo("OR"); break;
2704 case 7: putMnemo("CP"); break;
2711 final switch (opcode
&0x07) {
2715 putCC((opcode
>>3)&0x07);
2721 final switch ((opcode
>>4)&0x03) {
2723 case 0: putMnemo("RET"); break;
2725 case 1: putMnemo("EXX"); break;
2727 case 2: putMnemo("JP"); put("("); put(DD
); put(")"); endArg(); break;
2729 case 3: putMnemo("LD"); putArg("SP"); put(DD
); endArg(); break;
2734 putR16AF((opcode
>>4)&0x03);
2740 putCC((opcode
>>3)&0x07);
2741 putUInt(memReadPCW
, 4);
2744 // special1/special3
2746 final switch ((opcode
>>3)&0x07) {
2748 case 0: putMnemo("JP"); putUInt(memReadPCW
, 4); endArg(); break;
2750 case 2: putMnemo("OUT"); put("("); putUInt(memReadPC
, 2); put(")"); endArg(); putArg("A"); break;
2752 case 3: putMnemo("IN"); putArg("A"); put("("); putUInt(memReadPC
, 2); put(")"); endArg(); break;
2754 case 4: putMnemo("EX"); putArg("(SP)"); put(DD
); endArg(); break;
2756 case 5: putMnemo("EX"); putArg("DE"); putArg("HL"); break;
2758 case 6: putMnemo("DI"); break;
2760 case 7: putMnemo("EI"); break;
2766 putCC((opcode
>>3)&0x07);
2767 putUInt(memReadPCW
, 4);
2773 if (((opcode
>>4)&0x03) == 0) {
2776 putUInt(memReadPCW
, 4);
2782 putR16AF((opcode
>>4)&0x03);
2787 final switch ((opcode
>>3)&0x07) {
2788 case 0: putMnemo("ADD"); putArg("A"); break;
2789 case 1: putMnemo("ADC"); putArg("A"); break;
2790 case 2: putMnemo("SUB"); putArg("A"); break;
2791 case 3: putMnemo("SBC"); putArg("A"); break;
2792 case 4: putMnemo("AND"); break;
2793 case 5: putMnemo("XOR"); break;
2794 case 6: putMnemo("OR"); break;
2795 case 7: putMnemo("CP"); break;
2798 putUInt(memReadPC
, 2);
2804 putUInt(opcode
&0x38, 2);
2814 // ////////////////////////////////////////////////////////////////////////// //
2815 // build tables through CTFE
2816 private immutable ubyte[256] tblParity
= (){
2818 foreach (immutable f
; 0..256) {
2820 for (n
= f
, p
= 0; n
!= 0; n
>>= 1) p ^
= n
&0x01;
2821 t
[f
] = (p ?
0 : ZymCPU
.Z80Flag
.PV
);
2826 private immutable ubyte[256] tblSZ53
= (){
2828 foreach (immutable f
; 0..256) t
[f
] = (f
&ZymCPU
.Z80Flag
.S35
);
2829 t
[0] |
= ZymCPU
.Z80Flag
.Z
;
2833 private immutable ubyte[256] tblSZP53
= (){
2835 foreach (immutable f
; 0..256) {
2837 for (n
= f
, p
= 0; n
!= 0; n
>>= 1) p ^
= n
&0x01;
2838 t
[f
] = (f
&ZymCPU
.Z80Flag
.S35
)|
(p ?
0 : ZymCPU
.Z80Flag
.PV
);
2840 t
[0] |
= ZymCPU
.Z80Flag
.Z
;