# Assemble with gas
# --register-prefix-optional --bitwise-or
+# reminder: d2-d7/a2-a6 are callee-save
.macro ldarg arg, stacksz, reg
move.l (4 + \arg * 4 + \stacksz)(%sp), \reg
# read single phase from controller
# d0 - result
-# destroys d1,d2
+# destroys d1
.global get_input
get_input:
move.b #0x40,(0xa10003)
movem.l (sp)+, d2-d7/a2
rts
+.global test_vcnt_loops
+test_vcnt_loops:
+ movem.l d2, -(sp)
+ movea.l #0xc00007, a0
+ movea.l #0xfff000, a1
+ move.b #0xff, d0 /* d0 = current_vcnt */
+ moveq.l #0, d1 /* d1 = loop counter */
+ move.w #315-1, d2 /* d2 = line limit */
+0:
+ btst #3, (a0)
+ beq 0b /* not blanking */
+0:
+ btst #3, (a0)
+ bne 0b /* blanking */
+
+ addq.w #1, a0
+0:
+ addq.w #1, d1 /* 4 */
+ cmp.b (a0), d0 /* 8 vcnt changed? */
+ beq 0b /* 10 */
+
+ move.w d0, (a1)+ /* 8 save */
+ move.w d1, (a1)+
+ move.b (a0), d0 /* 8 new vcnt */
+ moveq.l #0, d1
+ dbra d2, 0b
+
+ movem.l (sp)+, d2
+ rts
+
.global test_hint
test_hint:
move.w d0, -(sp) /* 8 */
movem.l (sp)+, d2-d7
rts
+.macro ymwrite areg, dreg addr dat
+ move.b \addr, (\areg) /* 12 addr */
+ nbcd d0 /* 6 delay to reach 17 ym cycles (M/7) */
+ move.b \dat, (\dreg) /* 12 data */
+.endm
+
+.global test_ym_stopped_tick
+test_ym_stopped_tick:
+ movem.l a2-a3, -(sp)
+ movea.l #0xa04000, a0
+ movea.l #0xa04001, a1
+ movea.l #0xc00007, a2
+ movea.l #0xfff000, a3
+
+ ymwrite a0, a1, #0x27, #0x30 /* 30 disable, clear */
+ ymwrite a0, a1, #0x26, #0xff /* 30 timer b shortest interval */
+ move.b #0x27, (a0) /* 12 addr prep */
+0:
+ btst #3, (a2)
+ beq 0b /* not blanking */
+0:
+ btst #3, (a2)
+ bne 0b /* blanking */
+
+ addq.l #1, a2
+0:
+ tst.b (a2)
+ bne 0b /* not line 0 - waiting for sequential vcnt */
+
+ move.b #0x0a, (a1) /* 12 start timer b */
+ moveq.l #0, d0
+ moveq.l #2, d1
+0:
+ move.b (a0), d0
+ and.b d1, d0
+ beq 0b
+0:
+# move.w (a2), (a3)+ /* 12 save hvcnt */
+ move.b (a2), d0
+ cmp.b (a2), d0
+ bne 0b
+ move.w d0, (a3)+
+ move.b #0x30, (a1) /* 12 stop b, clear */
+
+ move.w #(1900/10-1), d0 /* waste cycles */
+0:
+ dbra d0, 0b
+ moveq.l #0, d0
+
+ move.w (a0), (a3)+ /* 12 save status */
+ move.b #0x0a, (a1) /* 12 start b */
+0:
+ move.b (a0), d0
+ and.b d1, d0
+ beq 0b
+
+0:
+# move.w (a2), (a3)+ /* 12 save hvcnt */
+ move.b (a2), d1
+ cmp.b (a2), d1
+ bne 0b
+ move.w d1, (a3)+
+ move.w d0, (a3)+ /* 12 save status */
+
+ movem.l (sp)+, a2-a3
+ rts
+
+.global test_ym_ab_sync
+test_ym_ab_sync:
+ movea.l #0xa04000, a0
+ movea.l #0xa04001, a1
+
+ ymwrite a0, a1, #0x27, #0x30 /* 30 disable, clear */
+ ymwrite a0, a1, #0x24, #0xfc /* 30 timer a */
+ ymwrite a0, a1, #0x25, #0x01 /* 30 =15 - why 15? expected 16 */
+ ymwrite a0, a1, #0x26, #0xff /* 30 timer b shortest interval */
+ move.b #0x27, (a0) /* 12 addr prep */
+ nop
+ nop
+
+ move.b #0x0a, (a1) /* 12 start timer b */
+ moveq.l #0, d0
+ moveq.l #2, d1
+0:
+ move.b (a0), d0 /* 8 */
+ and.b d1, d0 /* 4 */
+ beq 0b /* 10|8 */
+0:
+ move.b #0x3f, (a1) /* 12 start a, clear */
+ moveq.l #3, d1 /* 12 cycles for old flag to clear itself */
+ nop
+ nop
+0:
+ move.b (a0), d0
+ and.b d1, d0
+ beq 0b
+ move.b (a0), d0 /* re-read, else very occasionally get 1 */
+ rts
+
# vim:filetype=asmM68k:ts=4:sw=4:expandtab
}
#define expect(ok_, v0_, v1_) \
-if ((v0_) != (v1_)) { \
+do { if ((v0_) != (v1_)) { \
printf("%s: %08x %08x\n", #v0_, v0_, v1_); \
ok_ = 0; \
-}
+}} while (0)
#define expect_range(ok_, v0_, vmin_, vmax_) \
-if ((v0_) < (vmin_) || (v0_) > (vmax_)) { \
+do { if ((v0_) < (vmin_) || (v0_) > (vmax_)) { \
printf("%s: %02x /%02x-%02x\n", #v0_, v0_, vmin_, vmax_); \
ok_ = 0; \
-}
+}} while (0)
#define expect_bits(ok_, v0_, val_, mask_) \
-if (((v0_) & (mask_)) != (val_)) { \
+do { if (((v0_) & (mask_)) != (val_)) { \
printf("%s: %04x & %04x != %04x\n", #v0_, v0_, mask_, val_); \
ok_ = 0; \
-}
+}} while (0)
static int t_dma_zero_wrap(void)
{
return ok;
}
-#define Z80_CP_CYCLES(b) (118 + ((b) - 1) * 21 + 26 + 17)
+#define Z80_C_DISPATCH 113 // see z80_test.s80
+#define Z80_C_END 17
+#define Z80_C_END_VCNT 67
+
+#define Z80_CYLES_TEST1(b) (Z80_C_DISPATCH + ((b) - 1) * 21 + 26 + Z80_C_END)
static int t_z80mem_vdp_r(void)
{
zram[0x1100] = zram[0x1101] = zram[0x1102] = 0x5a;
mem_barrier();
write16(0xa11100, 0x000);
- burn10(Z80_CP_CYCLES(2) * 15 / 7 * 2 / 10);
+ burn10(Z80_CYLES_TEST1(2) * 15 / 7 / 10);
write16(0xa11100, 0x100);
while (read16(0xa11100) & 0x100)
zram[0x1101] = 0x66;
mem_barrier();
write16(0xa11100, 0x000);
- burn10(Z80_CP_CYCLES(2) * 15 / 7 * 2 / 10);
+ burn10(Z80_CYLES_TEST1(2) * 15 / 7 / 10);
write16(0xa11100, 0x100);
while (read16(0xa11100) & 0x100)
return ok;
}
-#define Z80_RD_V_CYCLES(b) (132 + (b) * 38 + 50 + 17)
+static int t_tim_z80_loop(void)
+{
+ u8 pal = read8(0xa10001) & 0x40;
+ u8 *zram = (u8 *)0xa00000;
+ u16 z80_loops = pal ? 3420*(313*2+1)/15/100 : 3420*(262*2+1)/15/100; // 2fr + 1ln
+ u16 _68k_loops = pal ? 3420*(313*2+1)/7/10 : 3420*(262*2+1)/7/10;
+ int ok = 1;
+
+ zram[0x1000] = 3; // idle loop, save vcnt
+ write16_z80le(&zram[0x1002], 0); // src (unused)
+ write16_z80le(&zram[0x1004], 0x1100); // vcnt dst
+ write16_z80le(&zram[0x1006], z80_loops); // x100 cycles
+ zram[0x1100] = 0;
+ mem_barrier();
+
+ vdp_wait_for_line_0();
+ write16(0xa11100, 0x000);
+ burn10(_68k_loops + (Z80_C_DISPATCH + Z80_C_END_VCNT) * 15 / 7 / 10);
+
+ write16(0xa11100, 0x100);
+ while (read16(0xa11100) & 0x100)
+ ;
+ expect(ok, zram[0x1000], 0);
+ expect(ok, zram[0x1100], 1);
+ return ok;
+}
+
+#define Z80_CYCLES_TEST2(b) (Z80_C_DISPATCH + (b) * 38 + Z80_C_END_VCNT)
// 80 80 91 95-96
static void z80_read_loop(u8 *zram, u16 src)
vdp_wait_for_line_0();
write16(0xa11100, 0x000);
- burn10(Z80_RD_V_CYCLES(pairs) * 15 / 7 * 4 / 10);
+ burn10(Z80_CYCLES_TEST2(pairs) * 15 / 7 * 2 / 10);
write16(0xa11100, 0x100);
while (read16(0xa11100) & 0x100)
return ok;
}
+static int t_tim_vcnt_loops(void)
+{
+ const u16 *ram16 = (u16 *)0xfff004;
+ u8 pal = read8(0xa10001) & 0x40;
+ u16 i, lines = pal ? 313 : 262;
+ int ok = 1;
+
+ test_vcnt_loops();
+ expect(ok, ram16[-1*2+0], 0xff);
+ expect_range(ok, ram16[-1*2+1], 21, 22);
+ for (i = 0; i < lines; i++)
+ expect_range(ok, ram16[i*2+1], 19, 21);
+ expect(ok, ram16[lines*2+0], 0);
+ expect_range(ok, ram16[lines*2+1], 20, 21);
+ return ok;
+}
+
static int t_tim_hblank_h40(void)
{
const u8 *r = (u8 *)0xff0000;
return ok;
}
+static const u8 hcnt2tm[] =
+{
+ 0x0a, 0x1d, 0x31, 0x44, 0x58, 0x6b, 0x7f, 0x92,
+ 0xa6, 0xb9, 0xcc, 0x00, 0x00, 0x00, 0xe2, 0xf6
+};
+
+static int t_tim_ym_timer_z80(int is_b)
+{
+ u8 pal = read8(0xa10001) & 0x40;
+ u8 *zram = (u8 *)0xa00000;
+ u8 *z80 = zram;
+ u16 _68k_loops = 3420*(302+5+1)/7/10; // ~ (72*1024*2)/(3420./7)
+ u16 start, end, diff;
+ int ok = 1;
+
+ zram[0x1000] = 4 + is_b; // ym2612 timer a/b test
+ zram[0x1100] = zram[0x1101] = zram[0x1102] = zram[0x1103] = 0;
+ mem_barrier();
+
+ vdp_wait_for_line_0();
+ write16(0xa11100, 0x000);
+
+ burn10(_68k_loops + (Z80_C_DISPATCH + Z80_C_END_VCNT) * 15 / 7 / 10);
+
+ write16(0xa11100, 0x100);
+ while (read16(0xa11100) & 0x100)
+ ;
+ mem_barrier();
+ expect(ok, zram[0x1000], 0);
+ (void)hcnt2tm;
+ //start = ((u16)zram[0x1102] << 8) | hcnt2tm[zram[0x1103] >> 4];
+ //end = ((u16)zram[0x1100] << 8) | hcnt2tm[zram[0x1101] >> 4];
+ start = zram[0x1102];
+ end = zram[0x1100];
+ diff = end - start;
+ if (pal)
+ expect_range(ok, diff, 0xf4, 0xf6);
+ else
+ expect_range(ok, diff, 0x27, 0x29);
+ write8(&z80[0x4001], 0); // stop, but should keep the flag
+ mem_barrier();
+ burn10(32*6/10); // busy bit, 32 FM ticks (M/7/6)
+ if (is_b) {
+ expect(ok, z80[0x4000], 2);
+ write8(&z80[0x4001], 0x20); // reset flag (reg 0x27, set up by z80)
+ }
+ else {
+ expect(ok, z80[0x4000], 1);
+ write8(&z80[0x4001], 0x10);
+ }
+ mem_barrier();
+ burn10(32*6/10);
+ expect(ok, z80[0x4000], 0);
+ return ok;
+}
+
+static int t_tim_ym_timera_z80(void)
+{
+ return t_tim_ym_timer_z80(0);
+}
+
+static int t_tim_ym_timerb_z80(void)
+{
+ return t_tim_ym_timer_z80(1);
+}
+
+static int t_tim_ym_timerb_stop(void)
+{
+ const struct {
+ //u8 vcnt_start;
+ //u8 hcnt_start;
+ u16 vcnt_start;
+ u16 stat0;
+ //u8 vcnt_end;
+ //u8 hcnt_end;
+ u16 vcnt_end;
+ u16 stat1;
+ } *t = (void *)0xfff000;
+ u8 *z80 = (u8 *)0xa00000;
+ u16 diff;
+ int ok = 1;
+ write16(0xa11100, 0x100);
+ while (read16(0xa11100) & 0x100)
+ ;
+ test_ym_stopped_tick();
+ mem_barrier();
+ //start = ((u16)t->vcnt_start << 8) | hcnt2tm[t->hcnt_start >> 4];
+ //end = ((u16)t->vcnt_end << 8) | hcnt2tm[t->hcnt_end >> 4];
+ //diff = end - start;
+ diff = t->vcnt_end - t->vcnt_start;
+ //expect_range(ok, diff, 0x492, 0x5c2); // why so much variation?
+ expect_range(ok, diff, 4, 5);
+ expect(ok, t->stat0, 0);
+ expect(ok, t->stat1, 2);
+ expect(ok, z80[0x4000], 2);
+ write8(&z80[0x4001], 0x30);
+ return ok;
+}
+
+static int t_tim_ym_timer_ab_sync(void)
+{
+ u16 v = test_ym_ab_sync();
+ int ok = 1;
+ expect(ok, v, 3);
+ return ok;
+}
+
struct irq_test {
u16 cnt;
union {
u16 pad;
};
+// broken on fresh boot due to uknown reasons
static int t_irq_hint(void)
{
struct irq_test *it = (void *)0xfff000;
+ struct irq_test *itv = it + 1;
int ok = 1;
+ memset_(it, 0, sizeof(*it) * 2);
+ memcpy_((void *)0xff0100, test_hint, test_hint_end - test_hint);
+ memcpy_((void *)0xff0140, test_vint, test_vint_end - test_vint);
+
+ // without this, tests fail after cold boot
+ while (!(read16(VDP_CTRL_PORT) & 8))
+ /* not blanking */;
+
// for more fun, disable the display
VDP_setReg(VDP_MODE2, VDP_MODE2_MD);
- it->cnt = it->first.hv = it->last.hv = 0;
- memcpy_((void *)0xff0100, test_hint, test_hint_end - test_hint);
VDP_setReg(10, 0);
while (read8(VDP_HV_COUNTER) != 100)
;
move_sr(0x2700);
expect(ok, it->first.v, 229); // pending irq trigger
expect(ok, it->cnt, 1);
+ expect(ok, itv->cnt, 0);
// count irqs
it->cnt = it->first.hv = it->last.hv = 0;
{ T_MD, t_z80mem_vdp_r, "z80 vdp read" },
// { t_z80mem_vdp_w, "z80 vdp write" }, // hang
{ T_MD, t_tim_loop, "time loop" },
+ { T_MD, t_tim_z80_loop, "time z80 loop" },
{ T_MD, t_tim_z80_ram, "time z80 ram" },
{ T_MD, t_tim_z80_ym, "time z80 ym2612" },
{ T_MD, t_tim_z80_vdp, "time z80 vdp" },
{ T_MD, t_tim_z80_bank_rom, "time z80 bank rom" },
{ T_MD, t_tim_vcnt, "time V counter" },
+ { T_MD, t_tim_vcnt_loops, "time vcnt loops" },
{ T_MD, t_tim_hblank_h40, "time hblank h40" },
{ T_MD, t_tim_hblank_h32, "time hblank h32" },
{ T_MD, t_tim_vdp_as_vram_w, "time vdp vram w" },
{ T_MD, t_tim_vdp_as_cram_w, "time vdp cram w" },
+ { T_MD, t_tim_ym_timera_z80, "time timer a z80" },
+ { T_MD, t_tim_ym_timerb_z80, "time timer b z80" },
+ { T_MD, t_tim_ym_timerb_stop, "timer b stop" },
+ { T_MD, t_tim_ym_timer_ab_sync,"timer ab sync" },
{ T_MD, t_irq_hint, "irq4 / line" },
{ T_MD, t_irq_both_cpu_unmask, "irq both umask" },
{ T_MD, t_irq_ack_v_h, "irq ack v-h" },
write16(0xa11100, 0x000);
burn10(1);
write16(0xa11200, 0x100);
- burn10(1);
+
+ burn10(50 * 15 / 7 / 10); // see z80_test.s80
// take back the bus
write16(0xa11100, 0x100);
static void wait_next_vsync(void)
{
- while (read16(VDP_CTRL_PORT) & 8)
+ while (read16(VDP_CTRL_PORT) & SR_VB)
/* blanking */;
- while (!(read16(VDP_CTRL_PORT) & 8))
+ while (!(read16(VDP_CTRL_PORT) & SR_VB))
/* not blanking */;
}
printf_ypos = 0;
printf(" ");
- while (!(get_input() & BTNM_A))
+ for (i = 0; i < 60*60 && !(get_input() & BTNM_A); i++)
wait_next_vsync();
+#ifndef PICO
+ // blank due to my lame tv being burn-in prone
+ VDP_setReg(VDP_MODE2, VDP_MODE2_MD);
+#endif
+ while (!(get_input() & BTNM_A))
+ burn10(488*100/10);
+ VDP_setReg(VDP_MODE2, VDP_MODE2_MD | VDP_MODE2_DMA | VDP_MODE2_DISP);
{