testpico: more timing tests
authornotaz <notasas@gmail.com>
Tue, 27 Jun 2023 17:59:12 +0000 (20:59 +0300)
committernotaz <notasas@gmail.com>
Tue, 27 Jun 2023 20:20:37 +0000 (23:20 +0300)
testpico/asmtools.S
testpico/asmtools.h
testpico/hcnt2linear.c [new file with mode: 0644]
testpico/main.c
testpico/z80_test.s80

index 811f35d..807ac9a 100644 (file)
@@ -1,5 +1,6 @@
 # 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
@@ -40,7 +41,7 @@ write16_x16:
 
 # read single phase from controller
 #  d0 - result
-#  destroys d1,d2
+#  destroys d1
 .global get_input
 get_input:
        move.b          #0x40,(0xa10003)
@@ -195,6 +196,36 @@ test_vcnt_vb:
     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 */
@@ -378,4 +409,103 @@ test_hb:
     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
index 63974f8..8711183 100644 (file)
@@ -20,10 +20,13 @@ void  memcpy_(void *dst, const void *src, unsigned short size);
 void  memset_(void *dst, int d, unsigned short size);
 
 void test_vcnt_vb(void);
+void test_vcnt_loops(void);
 void test_f(void);
 void test_hb(void);
 void test_v_h_2(void);
 void test_h_v_2(void);
+void test_ym_stopped_tick(void);
+short test_ym_ab_sync(void);
 
 extern const char test_hint[];
 extern const char test_hint_end[];
diff --git a/testpico/hcnt2linear.c b/testpico/hcnt2linear.c
new file mode 100644 (file)
index 0000000..5f4c708
--- /dev/null
@@ -0,0 +1,46 @@
+#include <stdio.h>
+#include <assert.h>
+
+//|         Mode |H32     (RSx=00) |H40     (RSx=11) |
+//|HCounter      |[1]0x000-0x127   |[1]0x000-0x16C   | 00-93 00-b6
+//|progression   |[2]0x1D2-0x1FF   |[2]0x1C9-0x1FF   | e9-ff e4-ff
+
+int main()
+{
+       unsigned char result[256];
+       int i, j, val, vals[420];
+
+       for (i = val = 0; val < 0x200; i++)
+       {
+               vals[i] = val++;
+               if (val == 0x16d)
+                       val = 0x1C9;
+       }
+       assert(i == 420);
+       for (i = 0; i < 256; i++)
+       {
+               result[i] = 0;
+               for (j = 0; j < 420; j++)
+                       if (vals[j] / 2 == i)
+                               break;
+               if (j < 420)
+                       result[i] = (255 * j + 210) / 419;
+       }
+
+       printf("{");
+       for (i = 0; i < 256; i++)
+               printf(" 0x%02x%s", result[i], i < 255 ? "," : "");
+       printf(" }\n");
+
+       printf("{");
+       for (i = 0; i < 64; i++)
+               printf(" 0x%02x%s", result[i*4+2], i < 63 ? "," : "");
+       printf(" }\n");
+
+       printf("{");
+       for (i = 0; i < 16; i++)
+               printf(" 0x%02x%s", result[i*16+8], i < 15 ? "," : "");
+       printf(" }\n");
+
+       return 0;
+}
index 4202748..d7ed087 100644 (file)
@@ -438,22 +438,22 @@ static void t_dma_zero_fill_early(void)
 }
 
 #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)
 {
@@ -968,7 +968,11 @@ static int t_z80mem_noreq_w(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)
 {
@@ -986,7 +990,7 @@ 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)
@@ -1018,7 +1022,7 @@ static unused int t_z80mem_vdp_w(void)
     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)
@@ -1047,7 +1051,34 @@ static int t_tim_loop(void)
     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)
@@ -1063,7 +1094,7 @@ 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)
@@ -1191,6 +1222,23 @@ static int t_tim_vcnt(void)
     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;
@@ -1260,6 +1308,113 @@ static int t_tim_vdp_as_cram_w(void)
     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 {
@@ -1269,16 +1424,24 @@ struct irq_test {
     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)
         ;
@@ -1291,6 +1454,7 @@ static int t_irq_hint(void)
     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;
@@ -1791,15 +1955,21 @@ static const struct {
     { 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" },
@@ -1844,7 +2014,8 @@ static void setup_z80(void)
     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);
@@ -1854,9 +2025,9 @@ static void setup_z80(void)
 
 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 */;
 }
 
@@ -1982,8 +2153,15 @@ int main()
     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);
 
 
     {
index 824d80c..5fbada1 100644 (file)
@@ -1,26 +1,66 @@
+; for sjasm 0.42
   ORG $0
 
 init
-  di
-  im  $1
-  ld  sp, $2000
+  di               ;       4
+  im  $1           ;       8
+
+loop_prep
+  ld  sp, $1002    ;      10
+  ld  hl, $1000    ;      10
+  ld  b, jtable    ;       7
+  ld  d, $0        ;       7
+  xor a            ;       4  (50)
 
 loop
-  ld  a, ($1000)   ;      13
-  or  a            ;       4
-  jp  z, loop      ;      10  27 (41 worst)
+  add a, (hl)      ;       7
+  jp  z, loop      ;      10  17 (27 worst)
 
-  ld  hl, ($1002)  ; src  20
-  ld  de, ($1004)  ; dst  20
-  ld  bc, ($1006)  ; len  20
+  add a            ;       4  a *= 2
+  add b            ;       4  a += table
+  ld  e, a         ;       4
+  ld  a, (de)      ;       7
+  inc e            ;       4
+  ld  ixl, a       ;       9
+  ld  a, (de)      ;       7
+  ld  ixh, a       ;       9
+
+  pop hl           ; src  10  ld  hl, ($1002)
+  pop de           ; dst  10  ld  de, ($1004)
+  pop bc           ; len  10  ld  bc, ($1006)
+  jp  ix           ;       8  (86+27)
+
+end
+  xor a            ;       4
+  ld  ($1000), a   ;      13  (17)
+  jp  loop_prep
+
+; ---
+
+  BLOCK $38-$
 
-  cp  a, 2         ;       7
-  jp  z, rd_timing ;      10  77 (118)
+irq
+  ret
 
-  ldir             ;      21/16
+; ---
+
+jtable
+  dw end
+  dw t_copy
+  dw t_rd_timing
+  dw t_idle_loop
+  dw t_timer_a
+  dw t_timer_b
+jtable_end
+  ld b, jtable_end ; ensure < $100
+
+; - 1 -
+t_copy
+  ldir             ;      21/16        ((de) <- (hl)) bc times
   jp  end          ;      10
 
-rd_timing
+; - 2 -
+t_rd_timing
   ld  d, h         ;       4
   ld  e, l         ;       4
   inc de           ;       6  14 (132)
@@ -32,22 +72,108 @@ loop_read
   or  a, c         ;       4
   jp  nz,loop_read ;      10  38
 
+end_vcnt
   ld  a, ($7f08)   ; vcnt 13
   ld  de, ($1004)  ; dst  20
   ld  (de), a      ;       7
   jp  end          ;      10  50
 
-; ---
+; - 3 -
+t_idle_loop
+  exx              ;       4  (waste cycles)
+  ld  b, $4        ;       7
+1 djnz 1b          ;      13  13*3+8=47
+  exx              ;       4
+  nop              ;       4  (66)
 
-  BLOCK $38-$
+  dec bc           ;       6
+  ld  a, b         ;       4
+  or  a, c         ;       4
+  jp  z,end_vcnt   ;      10
+  jp  t_idle_loop  ;      10  (66+34)
 
-irq
-  ret
+; helper for tests 5 and 6
+macro ld_a 2
+  ld  a, @2        ;       7
+  ld  @1, a        ;       7
+endmacro
+macro save_hvcount_unstable dst
+0
+  ld  bc, ($7f08)  ; hvc  20
+  ld  hl, ($7f08)  ; hvc  16
+  ld  a, b         ;       4
+  xor a, h         ;       4
+  and a, $c0       ;       7  hcnt bad sample?
+  jp  nz,0b        ;      10  (61 loop)
+  ld  a, c         ;       4
+  xor a, l         ;       4  vcnt changed while sampling?
+  jp  nz,0b        ;      10  (79 loop)
+  ld  (dst), hl    ;      20  (99)
+endmacro
+macro save_hvcount dst
+  ld  hl, $7f08    ; vcnt 10
+0
+  ld  a, (hl)      ;       7
+  cp  a, (hl)      ;       7
+  jp  nz,0b        ;      10
+  ld  (dst), a
+endmacro
+t_timer_prepare
+  ld  d, $2        ;       7
+  ld  hl, $4000    ; addr 10
+  ld  bc, $4001    ; data 14
+  ld  (hl), $26    ;      10
+  ld_a (bc), $ff   ;      14
+  ld  (hl), $27    ;      10
+  ld_a (bc), $3a   ;      14  enable timer+flag,clear
+0
+  ld  a, (hl)      ;       7
+  and d            ;       4
+  jp  z,0b         ;      10
+  xor a            ;       4
+  ld  (bc), a      ;       7  stop (timer tick in (16*72*2)*7/15)
+  ld  a, ($7f08)   ; vcnt 13
+  exx              ;       4
+  save_hvcount $1102 ;    99+
+  exx              ;       4
+  ret              ;      10
 
-; ---
+; - 4 -
+t_timer_a
+  ld  sp, $2000    ;      10
+  call t_timer_prepare ;  17++
+  ld  d, $1        ;       7
+  xor a            ;       4
+  ld  (hl), $24    ;      10
+  ld  (bc), a      ;       7
+  ld  (hl), $25    ;      10
+  ld  (bc), a      ;       7
+  ld  (hl), $27    ;      10
+  ld_a (bc), $35   ;      14  enable timer+flag,clear (  max 67
+  nop              ;       4  flag clear delay
+tim_a_loop
+  ld  a, (hl)      ;       7
+  and d            ;       4
+  jp  z,tim_a_loop ;      10
+  save_hvcount $1100 ;    99+
+  jp  end          ;      10
 
-end
+; - 5 -
+t_timer_b
+  ld  sp, $2000    ;      10  copy-pasta from t_timer_a because I'm lazy
+  call t_timer_prepare ;  17++
+  ld  d, $2        ;       7
   xor a            ;       4
-  ld  ($1000), a   ;      13
-  jp  loop         ;      10  27
+  ld  (hl), $26    ;      10
+  ld  (hl), $26    ;      10  dup for timing
+  ld_a (bc), $c0   ;      14
+  ld  (hl), $27    ;      10
+  ld_a (bc), $3a   ;      14  enable timer+flag,clear
+  nop              ;       4  flag clear delay
+tim_b_loop
+  ld  a, (hl)      ;       7
+  and d            ;       4
+  jp  z,tim_b_loop ;      10
+  save_hvcount $1100 ;    99+
+  jp  end          ;      10