@neovand/zilion 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,790 @@
1
+ // Zilion — WebGPU Z80 core (WGSL), generated from a differentially-tested
2
+ // implementation. Do not edit by hand; see the project README.
3
+ //
4
+ // `__MEM_SIZE__` and `__MEM_MASK__` are replaced at shader build time with the
5
+ // per-instance memory size (a power of two) and its address mask.
6
+ export const Z80_CORE_WGSL = /* wgsl */ `
7
+ // === Z80 CPU State (per invocation) ===
8
+ var<private> cpu_a: u32;
9
+ var<private> cpu_f: u32;
10
+ var<private> cpu_b: u32;
11
+ var<private> cpu_c: u32;
12
+ var<private> cpu_d: u32;
13
+ var<private> cpu_e: u32;
14
+ var<private> cpu_h: u32;
15
+ var<private> cpu_l: u32;
16
+ var<private> cpu_sp: u32;
17
+ var<private> cpu_pc: u32;
18
+ var<private> cpu_a2: u32;
19
+ var<private> cpu_f2: u32;
20
+ var<private> cpu_b2: u32;
21
+ var<private> cpu_c2: u32;
22
+ var<private> cpu_d2: u32;
23
+ var<private> cpu_e2: u32;
24
+ var<private> cpu_h2: u32;
25
+ var<private> cpu_l2: u32;
26
+ var<private> cpu_halted: u32;
27
+ var<private> cpu_iff1: u32;
28
+ var<private> cpu_iff2: u32;
29
+ var<private> cpu_ix: u32;
30
+ var<private> cpu_iy: u32;
31
+ // Index-prefix state for the instruction currently executing:
32
+ // idx_mode: 0 = HL, 1 = IX, 2 = IY
33
+ // idx_disp: sign-extended displacement for (IX+d)/(IY+d)
34
+ // idx_uses_mem: 1 when this instruction dereferences (IX+d)/(IY+d), which
35
+ // means H/L operands are NOT substituted by IXH/IXL (real Z80 rule).
36
+ var<private> idx_mode: u32;
37
+ var<private> idx_disp: u32;
38
+ var<private> idx_uses_mem: u32;
39
+
40
+ // HOST CONTRACT: the host shader must declare the following BEFORE this core:
41
+ // fn mem_read(addr: u32) -> u32 // read one byte from memory
42
+ // fn mem_write(addr: u32, val: u32) // write one byte to memory
43
+ // fn on_fetch_opcode(op: u32) -> bool // return true to skip (NOP) an
44
+ // // opcode after prefix resolution
45
+ // This lets the host choose the memory model (mask, modulo, storage buffer, …)
46
+ // and hook opcode execution (e.g. instruction suppression). See buildComputeShader.
47
+
48
+ // Z80 flag bits
49
+ const CF: u32 = 0x01u;
50
+ const NF: u32 = 0x02u;
51
+ const PF: u32 = 0x04u;
52
+ const F3: u32 = 0x08u;
53
+ const HF: u32 = 0x10u;
54
+ const F5: u32 = 0x20u;
55
+ const ZF: u32 = 0x40u;
56
+ const SFl: u32 = 0x80u;
57
+
58
+
59
+ fn z80_fetch() -> u32 {
60
+ let val = mem_read(cpu_pc);
61
+ cpu_pc = (cpu_pc + 1u) & 0xffffu;
62
+ return val;
63
+ }
64
+
65
+ fn z80_fetch_word() -> u32 {
66
+ let lo = z80_fetch();
67
+ let hi = z80_fetch();
68
+ return (hi << 8u) | lo;
69
+ }
70
+
71
+ fn z80_push16(val: u32) {
72
+ cpu_sp = (cpu_sp - 1u) & 0xffffu;
73
+ mem_write(cpu_sp, (val >> 8u) & 0xffu);
74
+ cpu_sp = (cpu_sp - 1u) & 0xffffu;
75
+ mem_write(cpu_sp, val & 0xffu);
76
+ }
77
+
78
+ fn z80_pop16() -> u32 {
79
+ let lo = mem_read(cpu_sp);
80
+ cpu_sp = (cpu_sp + 1u) & 0xffffu;
81
+ let hi = mem_read(cpu_sp);
82
+ cpu_sp = (cpu_sp + 1u) & 0xffffu;
83
+ return (hi << 8u) | lo;
84
+ }
85
+
86
+ fn signed_byte(b: u32) -> i32 {
87
+ let sb = i32(b);
88
+ if (sb > 127) { return sb - 256; }
89
+ return sb;
90
+ }
91
+
92
+ // === Register Access ===
93
+ fn get_bc() -> u32 { return (cpu_b << 8u) | cpu_c; }
94
+ fn get_de() -> u32 { return (cpu_d << 8u) | cpu_e; }
95
+ fn get_hl() -> u32 { return (cpu_h << 8u) | cpu_l; }
96
+ fn get_af() -> u32 { return (cpu_a << 8u) | cpu_f; }
97
+
98
+ fn set_bc(v: u32) { cpu_b = (v >> 8u) & 0xffu; cpu_c = v & 0xffu; }
99
+ fn set_de(v: u32) { cpu_d = (v >> 8u) & 0xffu; cpu_e = v & 0xffu; }
100
+ fn set_hl(v: u32) { cpu_h = (v >> 8u) & 0xffu; cpu_l = v & 0xffu; }
101
+ fn set_af(v: u32) { cpu_a = (v >> 8u) & 0xffu; cpu_f = v & 0xffu; }
102
+
103
+ fn get_reg(idx: u32) -> u32 {
104
+ switch(idx) {
105
+ case 0u: { return cpu_b; }
106
+ case 1u: { return cpu_c; }
107
+ case 2u: { return cpu_d; }
108
+ case 3u: { return cpu_e; }
109
+ case 4u: {
110
+ if (idx_mode != 0u && idx_uses_mem == 0u) { return (idx_reg16() >> 8u) & 0xffu; } // IXH/IYH
111
+ return cpu_h;
112
+ }
113
+ case 5u: {
114
+ if (idx_mode != 0u && idx_uses_mem == 0u) { return idx_reg16() & 0xffu; } // IXL/IYL
115
+ return cpu_l;
116
+ }
117
+ case 6u: { return mem_read(idx_addr()); }
118
+ case 7u: { return cpu_a; }
119
+ default: { return 0u; }
120
+ }
121
+ }
122
+
123
+ fn set_reg(idx: u32, val: u32) {
124
+ let v = val & 0xffu;
125
+ switch(idx) {
126
+ case 0u: { cpu_b = v; }
127
+ case 1u: { cpu_c = v; }
128
+ case 2u: { cpu_d = v; }
129
+ case 3u: { cpu_e = v; }
130
+ case 4u: {
131
+ if (idx_mode != 0u && idx_uses_mem == 0u) { set_idx_reg16((idx_reg16() & 0x00ffu) | (v << 8u)); }
132
+ else { cpu_h = v; }
133
+ }
134
+ case 5u: {
135
+ if (idx_mode != 0u && idx_uses_mem == 0u) { set_idx_reg16((idx_reg16() & 0xff00u) | v); }
136
+ else { cpu_l = v; }
137
+ }
138
+ case 6u: { mem_write(idx_addr(), v); }
139
+ case 7u: { cpu_a = v; }
140
+ default: {}
141
+ }
142
+ }
143
+
144
+ fn get_reg16(idx: u32) -> u32 {
145
+ switch(idx) {
146
+ case 0u: { return get_bc(); }
147
+ case 1u: { return get_de(); }
148
+ case 2u: { return idx_reg16(); } // HL / IX / IY
149
+ case 3u: { return cpu_sp; }
150
+ default: { return 0u; }
151
+ }
152
+ }
153
+
154
+ fn set_reg16(idx: u32, val: u32) {
155
+ let v = val & 0xffffu;
156
+ switch(idx) {
157
+ case 0u: { set_bc(v); }
158
+ case 1u: { set_de(v); }
159
+ case 2u: { set_idx_reg16(v); } // HL / IX / IY
160
+ case 3u: { cpu_sp = v; }
161
+ default: {}
162
+ }
163
+ }
164
+
165
+ fn get_reg16_af(idx: u32) -> u32 {
166
+ if (idx == 3u) { return get_af(); }
167
+ return get_reg16(idx);
168
+ }
169
+
170
+ fn set_reg16_af(idx: u32, val: u32) {
171
+ if (idx == 3u) { set_af(val); } else { set_reg16(idx, val); }
172
+ }
173
+
174
+ // === Index register (IX/IY) helpers ===
175
+ // The 16-bit register the current prefix maps HL to (HL itself when no prefix).
176
+ fn idx_reg16() -> u32 {
177
+ if (idx_mode == 1u) { return cpu_ix; }
178
+ if (idx_mode == 2u) { return cpu_iy; }
179
+ return get_hl();
180
+ }
181
+ fn set_idx_reg16(v: u32) {
182
+ if (idx_mode == 1u) { cpu_ix = v & 0xffffu; }
183
+ else if (idx_mode == 2u) { cpu_iy = v & 0xffffu; }
184
+ else { set_hl(v); }
185
+ }
186
+ // Address used for (HL) / (IX+d) / (IY+d).
187
+ fn idx_addr() -> u32 {
188
+ if (idx_mode != 0u) { return (idx_reg16() + idx_disp) & 0xffffu; }
189
+ return get_hl();
190
+ }
191
+ // Sign-extend a displacement byte to 16 bits (two's complement).
192
+ fn signext(b: u32) -> u32 {
193
+ if (b >= 0x80u) { return b | 0xff00u; }
194
+ return b;
195
+ }
196
+ // Does this main opcode dereference (HL)? (Determines displacement fetch and
197
+ // whether H/L operands are IXH/IXL or real H/L under a DD/FD prefix.)
198
+ fn op_uses_hl_mem(op: u32) -> bool {
199
+ let x = (op >> 6u) & 3u;
200
+ let y = (op >> 3u) & 7u;
201
+ let z = op & 7u;
202
+ if (x == 1u) { return (y == 6u || z == 6u) && !(y == 6u && z == 6u); } // LD r,(HL)/(HL),r (not HALT)
203
+ if (x == 2u) { return z == 6u; } // ALU A,(HL)
204
+ if (x == 0u) {
205
+ if (z == 4u || z == 5u || z == 6u) { return y == 6u; } // INC/DEC (HL), LD (HL),n
206
+ return false;
207
+ }
208
+ return false;
209
+ }
210
+ // Write a REAL 8-bit register (no IX/IY substitution) — used by the
211
+ // undocumented DDCB/FDCB register-copy side effect.
212
+ fn set_reg_raw(idx: u32, val: u32) {
213
+ let v = val & 0xffu;
214
+ switch(idx) {
215
+ case 0u: { cpu_b = v; }
216
+ case 1u: { cpu_c = v; }
217
+ case 2u: { cpu_d = v; }
218
+ case 3u: { cpu_e = v; }
219
+ case 4u: { cpu_h = v; }
220
+ case 5u: { cpu_l = v; }
221
+ case 7u: { cpu_a = v; }
222
+ default: {}
223
+ }
224
+ }
225
+
226
+ // === Flag Helpers ===
227
+ fn sz_flags(val: u32) -> u32 {
228
+ var f = val & SFl;
229
+ if (val == 0u) { f |= ZF; }
230
+ f |= val & (F3 | F5);
231
+ return f;
232
+ }
233
+
234
+ fn parity(val: u32) -> bool {
235
+ var p = val;
236
+ p ^= p >> 4u;
237
+ p ^= p >> 2u;
238
+ p ^= p >> 1u;
239
+ return (p & 1u) == 0u;
240
+ }
241
+
242
+ fn check_cc(cc: u32) -> bool {
243
+ switch(cc) {
244
+ case 0u: { return (cpu_f & ZF) == 0u; }
245
+ case 1u: { return (cpu_f & ZF) != 0u; }
246
+ case 2u: { return (cpu_f & CF) == 0u; }
247
+ case 3u: { return (cpu_f & CF) != 0u; }
248
+ case 4u: { return (cpu_f & PF) == 0u; }
249
+ case 5u: { return (cpu_f & PF) != 0u; }
250
+ case 6u: { return (cpu_f & SFl) == 0u; }
251
+ case 7u: { return (cpu_f & SFl) != 0u; }
252
+ default: { return false; }
253
+ }
254
+ }
255
+
256
+ // === ALU ===
257
+ fn z80_alu(op: u32, val: u32) {
258
+ let a = cpu_a;
259
+ let c = cpu_f & CF;
260
+ switch(op) {
261
+ case 0u: { // ADD
262
+ let r = a + val;
263
+ cpu_f = sz_flags(r & 0xffu) | select(0u, CF, r > 0xffu) |
264
+ ((a ^ val ^ r) & HF) |
265
+ select(0u, PF, ((~(a ^ val)) & (a ^ r) & 0x80u) != 0u);
266
+ cpu_a = r & 0xffu;
267
+ }
268
+ case 1u: { // ADC
269
+ let r = a + val + c;
270
+ cpu_f = sz_flags(r & 0xffu) | select(0u, CF, r > 0xffu) |
271
+ ((a ^ val ^ r) & HF) |
272
+ select(0u, PF, ((~(a ^ val)) & (a ^ r) & 0x80u) != 0u);
273
+ cpu_a = r & 0xffu;
274
+ }
275
+ case 2u: { // SUB
276
+ let r = i32(a) - i32(val);
277
+ let ru = u32(r) & 0xffu;
278
+ cpu_f = sz_flags(ru) | NF | select(0u, CF, r < 0) |
279
+ ((a ^ val ^ u32(r)) & HF) |
280
+ select(0u, PF, (((a ^ val) & (a ^ u32(r))) & 0x80u) != 0u);
281
+ cpu_a = ru;
282
+ }
283
+ case 3u: { // SBC
284
+ let r = i32(a) - i32(val) - i32(c);
285
+ let ru = u32(r) & 0xffu;
286
+ cpu_f = sz_flags(ru) | NF | select(0u, CF, r < 0) |
287
+ ((a ^ val ^ u32(r)) & HF) |
288
+ select(0u, PF, (((a ^ val) & (a ^ u32(r))) & 0x80u) != 0u);
289
+ cpu_a = ru;
290
+ }
291
+ case 4u: { // AND
292
+ cpu_a = a & val;
293
+ cpu_f = sz_flags(cpu_a) | HF | select(0u, PF, parity(cpu_a));
294
+ }
295
+ case 5u: { // XOR
296
+ cpu_a = a ^ val;
297
+ cpu_f = sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));
298
+ }
299
+ case 6u: { // OR
300
+ cpu_a = a | val;
301
+ cpu_f = sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));
302
+ }
303
+ case 7u: { // CP
304
+ let r = i32(a) - i32(val);
305
+ let ru = u32(r) & 0xffu;
306
+ cpu_f = (ru & SFl) | select(0u, ZF, ru == 0u) | (val & (F3 | F5)) | NF |
307
+ select(0u, CF, r < 0) | ((a ^ val ^ u32(r)) & HF) |
308
+ select(0u, PF, (((a ^ val) & (a ^ u32(r))) & 0x80u) != 0u);
309
+ }
310
+ default: {}
311
+ }
312
+ }
313
+
314
+ fn z80_inc8(val: u32) -> u32 {
315
+ let r = (val + 1u) & 0xffu;
316
+ cpu_f = (cpu_f & CF) | sz_flags(r) |
317
+ select(0u, PF, val == 0x7fu) |
318
+ select(0u, HF, (r & 0x0fu) == 0u);
319
+ return r;
320
+ }
321
+
322
+ fn z80_dec8(val: u32) -> u32 {
323
+ let r = (val - 1u) & 0xffu;
324
+ cpu_f = (cpu_f & CF) | sz_flags(r) | NF |
325
+ select(0u, PF, val == 0x80u) |
326
+ select(0u, HF, (val & 0x0fu) == 0u);
327
+ return r;
328
+ }
329
+
330
+ fn z80_add_hl(val: u32) {
331
+ let hl = idx_reg16(); // ADD HL,rp / ADD IX,rp / ADD IY,rp
332
+ let r = hl + val;
333
+ cpu_f = (cpu_f & (SFl | ZF | PF)) |
334
+ select(0u, CF, r > 0xffffu) |
335
+ select(0u, HF, ((hl ^ val ^ r) & 0x1000u) != 0u) |
336
+ ((r >> 8u) & (F3 | F5));
337
+ set_idx_reg16(r & 0xffffu);
338
+ }
339
+
340
+ // === Rotate/Shift for accumulator ===
341
+ fn z80_rot_accum(y: u32) {
342
+ let a = cpu_a;
343
+ let c = cpu_f & CF;
344
+ let keep = cpu_f & (SFl | ZF | PF);
345
+ switch(y) {
346
+ case 0u: { // RLCA
347
+ cpu_a = ((a << 1u) | (a >> 7u)) & 0xffu;
348
+ cpu_f = keep | (a >> 7u) | (cpu_a & (F3 | F5));
349
+ }
350
+ case 1u: { // RRCA
351
+ cpu_a = ((a >> 1u) | (a << 7u)) & 0xffu;
352
+ cpu_f = keep | (a & 1u) | (cpu_a & (F3 | F5));
353
+ }
354
+ case 2u: { // RLA
355
+ cpu_a = ((a << 1u) | c) & 0xffu;
356
+ cpu_f = keep | (a >> 7u) | (cpu_a & (F3 | F5));
357
+ }
358
+ case 3u: { // RRA
359
+ cpu_a = ((a >> 1u) | (c << 7u)) & 0xffu;
360
+ cpu_f = keep | (a & 1u) | (cpu_a & (F3 | F5));
361
+ }
362
+ case 4u: { // DAA
363
+ var correction = 0u;
364
+ var carry = c;
365
+ if ((cpu_f & HF) != 0u || (a & 0x0fu) > 9u) { correction |= 0x06u; }
366
+ if (c != 0u || a > 0x99u) { correction |= 0x60u; carry = 1u; }
367
+ if ((cpu_f & NF) != 0u) { cpu_a = (a - correction) & 0xffu; }
368
+ else { cpu_a = (a + correction) & 0xffu; }
369
+ cpu_f = (cpu_f & NF) | sz_flags(cpu_a) | carry |
370
+ ((a ^ cpu_a) & HF) | select(0u, PF, parity(cpu_a));
371
+ }
372
+ case 5u: { // CPL
373
+ cpu_a = (~a) & 0xffu;
374
+ cpu_f = (cpu_f & (SFl | ZF | PF | CF)) | HF | NF | (cpu_a & (F3 | F5));
375
+ }
376
+ case 6u: { // SCF
377
+ cpu_f = (cpu_f & (SFl | ZF | PF)) | CF | (cpu_a & (F3 | F5));
378
+ }
379
+ case 7u: { // CCF
380
+ cpu_f = (cpu_f & (SFl | ZF | PF)) |
381
+ select(0u, HF, c != 0u) |
382
+ select(CF, 0u, c != 0u) |
383
+ (cpu_a & (F3 | F5));
384
+ }
385
+ default: {}
386
+ }
387
+ }
388
+
389
+ // === CB Prefix (bit ops, rotates, shifts) ===
390
+ fn z80_cb_rot(op: u32, val: u32) -> u32 {
391
+ let c = cpu_f & CF;
392
+ var r = 0u;
393
+ switch(op) {
394
+ case 0u: { r = ((val << 1u) | (val >> 7u)) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }
395
+ case 1u: { r = ((val >> 1u) | (val << 7u)) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }
396
+ case 2u: { r = ((val << 1u) | c) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }
397
+ case 3u: { r = ((val >> 1u) | (c << 7u)) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }
398
+ case 4u: { r = (val << 1u) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }
399
+ case 5u: { r = ((val >> 1u) | (val & 0x80u)) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }
400
+ case 6u: { r = ((val << 1u) | 1u) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }
401
+ case 7u: { r = (val >> 1u) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }
402
+ default: { r = val; }
403
+ }
404
+ return r;
405
+ }
406
+
407
+ fn z80_exec_cb() {
408
+ let op = z80_fetch();
409
+ let x = (op >> 6u) & 3u;
410
+ let y = (op >> 3u) & 7u;
411
+ let z = op & 7u;
412
+ let val = get_reg(z);
413
+ switch(x) {
414
+ case 0u: { set_reg(z, z80_cb_rot(y, val)); }
415
+ case 1u: { // BIT
416
+ cpu_f = (cpu_f & CF) | HF |
417
+ select(0u, ZF | PF, (val & (1u << y)) == 0u) |
418
+ select(0u, SFl, y == 7u && (val & 0x80u) != 0u) |
419
+ (val & (F3 | F5));
420
+ }
421
+ case 2u: { set_reg(z, val & ~(1u << y)); }
422
+ case 3u: { set_reg(z, val | (1u << y)); }
423
+ default: {}
424
+ }
425
+ }
426
+
427
+ // DDCB / FDCB: operates on (IX+d)/(IY+d). The displacement (idx_disp) has
428
+ // already been fetched. For rot/shift/RES/SET the result is written to memory
429
+ // AND (undocumented) copied to the real register in the low 3 bits unless it is
430
+ // 6. For BIT, the undocumented F3/F5 come from the high byte of the address.
431
+ fn z80_exec_idxcb(cbop: u32) {
432
+ let addr = (idx_reg16() + idx_disp) & 0xffffu;
433
+ let cx = (cbop >> 6u) & 3u;
434
+ let cy = (cbop >> 3u) & 7u;
435
+ let cz = cbop & 7u;
436
+ let val = mem_read(addr);
437
+ switch(cx) {
438
+ case 0u: {
439
+ let r = z80_cb_rot(cy, val);
440
+ mem_write(addr, r);
441
+ if (cz != 6u) { set_reg_raw(cz, r); }
442
+ }
443
+ case 1u: { // BIT n,(IX+d)
444
+ cpu_f = (cpu_f & CF) | HF |
445
+ select(0u, ZF | PF, (val & (1u << cy)) == 0u) |
446
+ select(0u, SFl, cy == 7u && (val & 0x80u) != 0u) |
447
+ ((addr >> 8u) & (F3 | F5));
448
+ }
449
+ case 2u: {
450
+ let r = val & ~(1u << cy);
451
+ mem_write(addr, r);
452
+ if (cz != 6u) { set_reg_raw(cz, r); }
453
+ }
454
+ case 3u: {
455
+ let r = val | (1u << cy);
456
+ mem_write(addr, r);
457
+ if (cz != 6u) { set_reg_raw(cz, r); }
458
+ }
459
+ default: {}
460
+ }
461
+ }
462
+
463
+ // === Block Transfer (ED prefix) ===
464
+ fn z80_ldi() {
465
+ let val = mem_read(get_hl());
466
+ mem_write(get_de(), val);
467
+ set_hl((get_hl() + 1u) & 0xffffu);
468
+ set_de((get_de() + 1u) & 0xffffu);
469
+ set_bc((get_bc() - 1u) & 0xffffu);
470
+ let n = (val + cpu_a) & 0xffu;
471
+ cpu_f = (cpu_f & (SFl | ZF | CF)) |
472
+ select(0u, PF, get_bc() != 0u) |
473
+ (n & F3) | select(0u, F5, (n & 0x02u) != 0u);
474
+ }
475
+
476
+ fn z80_ldd() {
477
+ let val = mem_read(get_hl());
478
+ mem_write(get_de(), val);
479
+ set_hl((get_hl() - 1u) & 0xffffu);
480
+ set_de((get_de() - 1u) & 0xffffu);
481
+ set_bc((get_bc() - 1u) & 0xffffu);
482
+ let n = (val + cpu_a) & 0xffu;
483
+ cpu_f = (cpu_f & (SFl | ZF | CF)) |
484
+ select(0u, PF, get_bc() != 0u) |
485
+ (n & F3) | select(0u, F5, (n & 0x02u) != 0u);
486
+ }
487
+
488
+ fn z80_cpi() {
489
+ let val = mem_read(get_hl());
490
+ let r = (cpu_a - val) & 0xffu;
491
+ let hf = (cpu_a ^ val ^ r) & HF;
492
+ // Undocumented F3/F5 come from n = A-(HL)-HF (bit 3 -> F3, bit 1 -> F5).
493
+ let n = (r - select(0u, 1u, hf != 0u)) & 0xffu;
494
+ set_hl((get_hl() + 1u) & 0xffffu);
495
+ set_bc((get_bc() - 1u) & 0xffffu);
496
+ cpu_f = (cpu_f & CF) | (r & SFl) | select(0u, ZF, r == 0u) | NF |
497
+ hf | select(0u, PF, get_bc() != 0u) |
498
+ (n & F3) | ((n & 0x02u) << 4u);
499
+ }
500
+
501
+ fn z80_cpd() {
502
+ let val = mem_read(get_hl());
503
+ let r = (cpu_a - val) & 0xffu;
504
+ let hf = (cpu_a ^ val ^ r) & HF;
505
+ let n = (r - select(0u, 1u, hf != 0u)) & 0xffu;
506
+ set_hl((get_hl() - 1u) & 0xffffu);
507
+ set_bc((get_bc() - 1u) & 0xffffu);
508
+ cpu_f = (cpu_f & CF) | (r & SFl) | select(0u, ZF, r == 0u) | NF |
509
+ hf | select(0u, PF, get_bc() != 0u) |
510
+ (n & F3) | ((n & 0x02u) << 4u);
511
+ }
512
+
513
+ // === ED Prefix ===
514
+ fn z80_exec_ed() {
515
+ let op = z80_fetch();
516
+ switch(op) {
517
+ case 0xa0u: { z80_ldi(); }
518
+ case 0xa8u: { z80_ldd(); }
519
+ case 0xb0u: { z80_ldi(); if (get_bc() != 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } } // LDIR
520
+ case 0xb8u: { z80_ldd(); if (get_bc() != 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } } // LDDR
521
+ case 0xa1u: { z80_cpi(); }
522
+ case 0xa9u: { z80_cpd(); }
523
+ case 0xb1u: { z80_cpi(); if (get_bc() != 0u && (cpu_f & ZF) == 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } }
524
+ case 0xb9u: { z80_cpd(); if (get_bc() != 0u && (cpu_f & ZF) == 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } }
525
+ // NEG
526
+ case 0x44u, 0x4cu, 0x54u, 0x5cu, 0x64u, 0x6cu, 0x74u, 0x7cu: {
527
+ let a = cpu_a; cpu_a = 0u; z80_alu(2u, a);
528
+ }
529
+ // RETN/RETI
530
+ case 0x45u, 0x4du, 0x55u, 0x5du, 0x65u, 0x6du, 0x75u, 0x7du: {
531
+ cpu_iff1 = cpu_iff2; cpu_pc = z80_pop16();
532
+ }
533
+ // LD I,A / LD R,A / LD A,I / LD A,R
534
+ case 0x47u: {} // LD I,A - no I register in our sim
535
+ case 0x4fu: {} // LD R,A
536
+ // LD A,I / LD A,R: I and R are not modelled (treated as 0), so A becomes 0.
537
+ // Flags: S/Z from the loaded value, PF = IFF2, N/H reset, C preserved.
538
+ case 0x57u: { cpu_a = 0u; cpu_f = (cpu_f & CF) | sz_flags(0u) | select(0u, PF, cpu_iff2 != 0u); }
539
+ case 0x5fu: { cpu_a = 0u; cpu_f = (cpu_f & CF) | sz_flags(0u) | select(0u, PF, cpu_iff2 != 0u); }
540
+ // LD (nn), rr
541
+ case 0x43u, 0x53u, 0x63u, 0x73u: {
542
+ let nn = z80_fetch_word();
543
+ let rp = (op >> 4u) & 3u;
544
+ let val = get_reg16(rp);
545
+ mem_write(nn, val & 0xffu);
546
+ mem_write((nn + 1u) & 0xffffu, (val >> 8u) & 0xffu);
547
+ }
548
+ // LD rr, (nn)
549
+ case 0x4bu, 0x5bu, 0x6bu, 0x7bu: {
550
+ let nn = z80_fetch_word();
551
+ let rp = (op >> 4u) & 3u;
552
+ let lo = mem_read(nn);
553
+ let hi = mem_read((nn + 1u) & 0xffffu);
554
+ set_reg16(rp, (hi << 8u) | lo);
555
+ }
556
+ // ADC HL, rr
557
+ case 0x4au, 0x5au, 0x6au, 0x7au: {
558
+ let rp = (op >> 4u) & 3u;
559
+ let hl = get_hl();
560
+ let val = get_reg16(rp);
561
+ let c = cpu_f & CF;
562
+ let r = hl + val + c;
563
+ cpu_f = ((r >> 8u) & SFl) | select(0u, ZF, (r & 0xffffu) == 0u) |
564
+ select(0u, HF, ((hl ^ val ^ r) & 0x1000u) != 0u) |
565
+ select(0u, PF, ((~(hl ^ val)) & (hl ^ r) & 0x8000u) != 0u) |
566
+ select(0u, CF, r > 0xffffu) | ((r >> 8u) & (F3 | F5));
567
+ set_hl(r & 0xffffu);
568
+ }
569
+ // SBC HL, rr
570
+ case 0x42u, 0x52u, 0x62u, 0x72u: {
571
+ let rp = (op >> 4u) & 3u;
572
+ let hl = get_hl();
573
+ let val = get_reg16(rp);
574
+ let c = cpu_f & CF;
575
+ let r = i32(hl) - i32(val) - i32(c);
576
+ let ru = u32(r) & 0xffffu;
577
+ cpu_f = ((ru >> 8u) & SFl) | select(0u, ZF, ru == 0u) | NF |
578
+ select(0u, HF, ((hl ^ val ^ u32(r)) & 0x1000u) != 0u) |
579
+ select(0u, PF, (((hl ^ val) & (hl ^ u32(r))) & 0x8000u) != 0u) |
580
+ select(0u, CF, r < 0) | ((ru >> 8u) & (F3 | F5));
581
+ set_hl(ru);
582
+ }
583
+ // RRD
584
+ case 0x67u: {
585
+ let m = mem_read(get_hl());
586
+ mem_write(get_hl(), ((cpu_a << 4u) | (m >> 4u)) & 0xffu);
587
+ cpu_a = (cpu_a & 0xf0u) | (m & 0x0fu);
588
+ cpu_f = (cpu_f & CF) | sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));
589
+ }
590
+ // RLD
591
+ case 0x6fu: {
592
+ let m = mem_read(get_hl());
593
+ mem_write(get_hl(), ((m << 4u) | (cpu_a & 0x0fu)) & 0xffu);
594
+ cpu_a = (cpu_a & 0xf0u) | (m >> 4u);
595
+ cpu_f = (cpu_f & CF) | sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));
596
+ }
597
+ // IN r,(C) - simplified, just set to 0
598
+ case 0x40u, 0x48u, 0x50u, 0x58u, 0x60u, 0x68u, 0x70u, 0x78u: {
599
+ set_reg((op >> 3u) & 7u, 0u);
600
+ }
601
+ default: {} // unknown ED ops = NOP
602
+ }
603
+ }
604
+
605
+ // === Main Opcode Execution ===
606
+ fn z80_exec_x0(y: u32, z: u32, p: u32, q: u32) {
607
+ switch(z) {
608
+ case 0u: {
609
+ switch(y) {
610
+ case 0u: {} // NOP
611
+ case 1u: { // EX AF,AF'
612
+ var t = cpu_a; cpu_a = cpu_a2; cpu_a2 = t;
613
+ t = cpu_f; cpu_f = cpu_f2; cpu_f2 = t;
614
+ }
615
+ case 2u: { // DJNZ
616
+ let d = signed_byte(z80_fetch());
617
+ cpu_b = (cpu_b - 1u) & 0xffu;
618
+ if (cpu_b != 0u) { cpu_pc = u32(i32(cpu_pc) + d) & 0xffffu; }
619
+ }
620
+ case 3u: { // JR
621
+ let d = signed_byte(z80_fetch());
622
+ cpu_pc = u32(i32(cpu_pc) + d) & 0xffffu;
623
+ }
624
+ default: { // JR cc (y-4)
625
+ let d = signed_byte(z80_fetch());
626
+ if (check_cc(y - 4u)) { cpu_pc = u32(i32(cpu_pc) + d) & 0xffffu; }
627
+ }
628
+ }
629
+ }
630
+ case 1u: {
631
+ if (q == 0u) { set_reg16(p, z80_fetch_word()); }
632
+ else { z80_add_hl(get_reg16(p)); }
633
+ }
634
+ case 2u: {
635
+ if (q == 0u) {
636
+ switch(p) {
637
+ case 0u: { mem_write(get_bc(), cpu_a); }
638
+ case 1u: { mem_write(get_de(), cpu_a); }
639
+ case 2u: { let nn = z80_fetch_word(); let hl = idx_reg16(); mem_write(nn, hl & 0xffu); mem_write((nn+1u) & 0xffffu, (hl >> 8u) & 0xffu); }
640
+ case 3u: { mem_write(z80_fetch_word(), cpu_a); }
641
+ default: {}
642
+ }
643
+ } else {
644
+ switch(p) {
645
+ case 0u: { cpu_a = mem_read(get_bc()); }
646
+ case 1u: { cpu_a = mem_read(get_de()); }
647
+ case 2u: { let nn = z80_fetch_word(); let lo = mem_read(nn); let hi = mem_read((nn+1u) & 0xffffu); set_idx_reg16((hi << 8u) | lo); }
648
+ case 3u: { cpu_a = mem_read(z80_fetch_word()); }
649
+ default: {}
650
+ }
651
+ }
652
+ }
653
+ case 3u: {
654
+ if (q == 0u) { set_reg16(p, (get_reg16(p) + 1u) & 0xffffu); }
655
+ else { set_reg16(p, (get_reg16(p) - 1u) & 0xffffu); }
656
+ }
657
+ case 4u: { set_reg(y, z80_inc8(get_reg(y))); }
658
+ case 5u: { set_reg(y, z80_dec8(get_reg(y))); }
659
+ case 6u: { set_reg(y, z80_fetch()); }
660
+ case 7u: { z80_rot_accum(y); }
661
+ default: {}
662
+ }
663
+ }
664
+
665
+ fn z80_exec_x3(y: u32, z: u32, p: u32, q: u32) {
666
+ switch(z) {
667
+ case 0u: { if (check_cc(y)) { cpu_pc = z80_pop16(); } }
668
+ case 1u: {
669
+ if (q == 0u) { set_reg16_af(p, z80_pop16()); }
670
+ else {
671
+ switch(p) {
672
+ case 0u: { cpu_pc = z80_pop16(); } // RET
673
+ case 1u: { // EXX
674
+ var t = cpu_b; cpu_b = cpu_b2; cpu_b2 = t;
675
+ t = cpu_c; cpu_c = cpu_c2; cpu_c2 = t;
676
+ t = cpu_d; cpu_d = cpu_d2; cpu_d2 = t;
677
+ t = cpu_e; cpu_e = cpu_e2; cpu_e2 = t;
678
+ t = cpu_h; cpu_h = cpu_h2; cpu_h2 = t;
679
+ t = cpu_l; cpu_l = cpu_l2; cpu_l2 = t;
680
+ }
681
+ case 2u: { cpu_pc = idx_reg16(); } // JP (HL)/(IX)/(IY)
682
+ case 3u: { cpu_sp = idx_reg16(); } // LD SP,HL/IX/IY
683
+ default: {}
684
+ }
685
+ }
686
+ }
687
+ case 2u: { let nn = z80_fetch_word(); if (check_cc(y)) { cpu_pc = nn; } }
688
+ case 3u: {
689
+ switch(y) {
690
+ case 0u: { cpu_pc = z80_fetch_word(); } // JP nn
691
+ case 1u: { z80_exec_cb(); }
692
+ case 2u: { z80_fetch(); } // OUT (n),A - no I/O device, discard (matches zff outPort no-op)
693
+ case 3u: { z80_fetch(); cpu_a = 0u; } // IN A,(n) - no I/O device, reads 0 (matches zff inPort→0)
694
+ case 4u: { // EX (SP),HL / EX (SP),IX / EX (SP),IY
695
+ let lo = mem_read(cpu_sp);
696
+ let hi = mem_read((cpu_sp + 1u) & 0xffffu);
697
+ let hl = idx_reg16();
698
+ mem_write(cpu_sp, hl & 0xffu);
699
+ mem_write((cpu_sp + 1u) & 0xffffu, (hl >> 8u) & 0xffu);
700
+ set_idx_reg16((hi << 8u) | lo);
701
+ }
702
+ case 5u: { // EX DE,HL
703
+ let td = cpu_d; let te = cpu_e;
704
+ cpu_d = cpu_h; cpu_e = cpu_l;
705
+ cpu_h = td; cpu_l = te;
706
+ }
707
+ case 6u: { cpu_iff1 = 0u; cpu_iff2 = 0u; }
708
+ case 7u: { cpu_iff1 = 1u; cpu_iff2 = 1u; }
709
+ default: {}
710
+ }
711
+ }
712
+ case 4u: { let nn = z80_fetch_word(); if (check_cc(y)) { z80_push16(cpu_pc); cpu_pc = nn; } }
713
+ case 5u: {
714
+ if (q == 0u) { z80_push16(get_reg16_af(p)); }
715
+ else {
716
+ switch(p) {
717
+ case 0u: { let nn = z80_fetch_word(); z80_push16(cpu_pc); cpu_pc = nn; } // CALL nn
718
+ case 1u, 3u: {} // DD/FD prefix handled in z80_step
719
+ case 2u: { z80_exec_ed(); }
720
+ default: {}
721
+ }
722
+ }
723
+ }
724
+ case 6u: { z80_alu(y, z80_fetch()); }
725
+ case 7u: { z80_push16(cpu_pc); cpu_pc = y * 8u; } // RST
726
+ default: {}
727
+ }
728
+ }
729
+
730
+ fn z80_execute(op: u32) {
731
+ let x = (op >> 6u) & 3u;
732
+ let y = (op >> 3u) & 7u;
733
+ let z = op & 7u;
734
+ let p = (y >> 1u) & 3u;
735
+ let q = y & 1u;
736
+ switch(x) {
737
+ case 0u: { z80_exec_x0(y, z, p, q); }
738
+ case 1u: {
739
+ if (y == 6u && z == 6u) { cpu_halted = 1u; }
740
+ else { set_reg(y, get_reg(z)); }
741
+ }
742
+ case 2u: { z80_alu(y, get_reg(z)); }
743
+ case 3u: { z80_exec_x3(y, z, p, q); }
744
+ default: {}
745
+ }
746
+ }
747
+
748
+ fn z80_step() {
749
+ if (cpu_halted != 0u) { return; }
750
+ // Reset per-instruction index-prefix state.
751
+ idx_mode = 0u;
752
+ idx_uses_mem = 0u;
753
+ idx_disp = 0u;
754
+
755
+ var op = z80_fetch();
756
+ // A DD/FD prefix selects IX/IY for the following opcode.
757
+ if (op == 0xddu || op == 0xfdu) {
758
+ idx_mode = select(2u, 1u, op == 0xddu);
759
+ let next = z80_fetch();
760
+ // A prefix immediately followed by another prefix or ED is a wasted M1:
761
+ // this step consumes just the prefix; back up so the next step restarts
762
+ // at the following byte (matches real Z80 timing and avoids an
763
+ // unbounded fetch loop on all-prefix programs).
764
+ if (next == 0xddu || next == 0xfdu || next == 0xedu) {
765
+ cpu_pc = (cpu_pc - 1u) & 0xffffu;
766
+ return;
767
+ }
768
+ op = next;
769
+ }
770
+
771
+ // Host hook: skip execution (treat as NOP) if requested.
772
+ if (on_fetch_opcode(op)) { return; }
773
+
774
+ if (idx_mode != 0u) {
775
+ if (op == 0xcbu) {
776
+ // DDCB/FDCB: displacement precedes the CB opcode.
777
+ idx_disp = signext(z80_fetch());
778
+ let cbop = z80_fetch();
779
+ z80_exec_idxcb(cbop);
780
+ return;
781
+ }
782
+ if (op_uses_hl_mem(op)) {
783
+ idx_uses_mem = 1u;
784
+ idx_disp = signext(z80_fetch());
785
+ }
786
+ }
787
+ z80_execute(op);
788
+ }
789
+ `;
790
+ //# sourceMappingURL=z80-core.wgsl.js.map