Here's the upd7810 code that checks the strobe, grabs the character from the latch and toggles busy and acknowledge:

[Linked Image from]
2092  LXI HL, $C002   puts the address c002 into the HL register
2095 LDAX (HL)    reads $c002 into A   (c002 = has data been latched)
2096 ONI  A,$80    now we check the high bit of A, if true we skip the next instruction
2098 RET        if no character, we return 

2099  DI  disable interrupts
209A LDEAX (HL++)    read c002 and c003 into EA register   c003 is the actual centronics data, HL now contains $c004
209C LDAX (HL)         read c004  (picks up busy set so bit 1 is set)
209D ORI A,$20            set bit 5 ack
209F STAX (HL)         store to c004  (set ack low, busy high)
20A0 NOP NOP NOP  3 nops
20A3 ANI  A,$FE         clear bit 0 busy (set busy clear)
20A5 STAX (HL)         store to c004
20A8 ANI A,$DF       clears bit 5  ack
20AB STAX (HL)     store to c004 (set ack high)

from the time we read the latch at c002 to the first write to c004:

ONI = 7
RET (SKIP) = 4 when skipped
DI = 4
LDEAX (HL++) = 14
LDAX (HL) = 7
ORI = 7
STAX (HL) = 7

so 7+4+4+14+7+7+7 = 50 T states = (3 T states per clock)

> (1/14.7456e6) * (50*3)
1.0172526041667e-05 or 10 us from the instant we read c002

so there's 3 writes at 209F, 20A5 and 20AB

between the write at 209F and 20A5:

going by the instruction timing NOP = 4 ANI = 7 STAX = 7 we get 4*3 (for the 3 NOPS) + 7 + 7 = 12 + 14 = 26 T states
> (1/14.7456e6) * (26*3)
5.2897135416667e-06 = time = 5.289714e-06 (5.3 us)
and then the timing between 20A5 and 20AB
NOP NOP = 4 * 2
ANI A,$DF = 7
EI = 4
STAX = 7
so again 26 T states (8+7+4+7)

nack=0 busy=0 datalatched=0
WRITE_C004_TIME: time = 2.349610e+01

nack=1 busy=1 datalatched=c
WRITE_C004_TIME: time = 5.289714e-06 (5.2 us)

nack=1 busy=0 datalatched=c
WRITE_C004_TIME: time = 5.289714e-06 (5.2 us)

nack=0 busy=0 datalatched=c
WRITE_C004_TIME: time = 9.358724e-05 (93 us)

nack=0 busy=0 datalatched=c

It's interesting that it doesn't match the published specs of 12us but ~10.6 is still in the ballpark.

So if it reads c002 at the exact moment that the strobe is initiated and the strobe lasts 1us, there should be minimum 10 + 10.6 us (~20 us) before the ack rises to clock the flip flop, which is plenty of time.

[Linked Image from]

[Linked Image from]

[Linked Image from]
[Linked Image from]
[Linked Image from]

The real problem is that the apple2 can get ahead of the ap2000, so when the strobe is activated it sets the variable m_data_latched, which is read at c002 but the machine time is different when the ap2000 gets to run. The strobe is activated at time x, but when the ap2000 runs, it can see the latch at time x - some amount, so by the time it acknowledges, the apple 2 says "hey the strobe is still active", and throws it away because the machine time is different when it gets the write line callback.

Vas's code starts a timer for the strobe and if the timer is still active when it gets the ack it ignores the ack. So the strobe is supposed to be 1us, and the acknowledge should come 10us after that is initiated.

My solution was to timestamp the strobe activation (in the apple2's context) and then pretend it didn't happen until the time in the ap2000's context when it would actually be equal to or after the timestamp when it did happen.