Close

About parasites and capacitors

A project log for XRX - eXperimental Research X

Another homebrew computer based on the MSX standard. But it uses a Z380 microprocessor.

marcellus-pereiraMarcellus Pereira 07/05/2026 at 10:090 Comments

Regarding yesterday's problem, I made a quick (and a bit better) fix.

As you may recall, the way C-BIOS detects RAM involves writing a value to a specific address and reading it back. This works well for emulators and the Z80, but it didn't work for the Z380. So, I made a quick adjustment: the CPLD would place the value 0xFF on the data bus if C-BIOS attempted to read RAM from slots 1 and 2. However, I need a better solution if I want to use cartridges.

With the help of my good friend Claude, we reached the following conclusion:

Between the write of $0F and the read-back, a Z80 must run the M1 opcode fetch of cp (hl) on the bus. During that fetch the ROM drives $BE onto the shared data bus. So by the time the read cycle of cp (hl) happens, the last thing to have driven the bus was the ROM, not the CPU's $0F. On a floating/held empty slot, the read-back returns ~$BE ≠ $0F → jr nz fires → empty slot correctly rejected.

On a Z80 there is no gap without a bus cycle: every instruction carries an opcode fetch that "stirs" the shared bus with a ROM byte. Capacitive hold never gets to carry a data pattern across, because an opcode fetch always lands in between.

The Z380 prefetches. In that tight loop, ld (hl),a and cp (hl) are already sitting in the instruction queue, fetched earlier during idle bus time. So the execute stage runs the write cycle and the read cycle back-to-back on the memory bus with no opcode fetch between them. Nothing stirs the bus. The buffer/capacitance still holds $0F from the write, the read-back returns $0F, the compare passes — and the same happens for $F0. The two-pattern defense collapses precisely because the pipeline deleted the intervening bus cycle it implicitly relied on.

Now, the chkram_find function is something like this:

chkram_find:
        IFNDEF ZDS_BUILD
                ld      a,$0F
                ld      (hl),a
                cp      (hl)
                jr      nz,chkram_find_end
                cpl
                ld      (hl),a
                cp      (hl)
                jr      nz,chkram_find_end
                dec     h
                jr      chkram_find
        ELSE
                ; Only for the XRX
                ; Between the write of $0F and the read-back, a Z80 must run the M1 opcode fetch of 
                ; cp (hl) on the bus. During that fetch the ROM drives $BE onto the shared data bus. 
                ; So by the time the read cycle of cp (hl) happens, the last thing to have driven the 
                ; bus was the ROM, not the CPU's $0F. On a floating/held empty slot, the read-back 
                ; returns ~$BE ≠ $0F → jr nz fires → empty slot correctly rejected.
                ; 
                ; On a Z80 there is no gap without a bus cycle: every instruction carries an opcode 
                ; fetch that "stirs" the shared bus with a ROM byte. Capacitive hold never gets to 
                ; carry a data pattern across, because an opcode fetch always lands in between.
                ; 
                ; The Z380 prefetches. In that tight loop, ld (hl),a and cp (hl) are already sitting 
                ; in the instruction queue, fetched earlier during idle bus time. So the execute stage 
                ; runs the write cycle and the read cycle back-to-back on the memory bus with no opcode 
                ; fetch between them. Nothing stirs the bus. The buffer/capacitance still holds $0F 
                ; from the write, the read-back returns $0F, the compare passes — and the same happens 
                ; for $F0. The two-pattern defense collapses precisely because the pipeline deleted 
                ; the intervening bus cycle it implicitly relied on.
                ld      a,$0F
                ld      (hl),a       ; X   <- $0F
                inc     l
                ld      a,$F0
                ld      (hl),a       ; X+1 <- $F0   (stir)
                dec     l
                ld      a,(hl)
                cp      $0F
                jr      nz,chkram_find_end
                ld      a,$F0
                ld      (hl),a       ; X   <- $F0
                inc     l
                ld      a,$0F
                ld      (hl),a       ; X+1 <- $0F   (opposite stir)
                dec     l
                ld      a,(hl)
                cp      $F0
                jr      nz,chkram_find_end
                dec     h
                jr      chkram_find
        ENDIF
chkram_find_end:
                inc     h
                ; Update the best values.
                ld      a,h
                or      a
                jr      z,chkram_sslot_end
                exx
                cp      h
                jr      c,chkram_update
                jr      z,chkram_update
                exx
                jr      chkram_sslot_end

And it seems to work!

Discussions