My little Z80 breadboard project got stuck in the mud where I am pulling my hair out. It looks like the CPU is making a calculation error on a relative jump. Here is my code, and I don't think ZX Spectrum or things like this. I don't have any I/O devices, only an Arduino Nano and I'm running at a dozen Hz of a 555 astable clock.
So all the I/O I have is the Arduino interrupting on Z80's /RD CHANGE and on /WD FALLING. Then /IOREQ is sampled to determine if it's an I/O request for us or memory activity. If it's an IOREQ right now it asserts the /WAIT state and I can type a response on the serial console. Otherwise, I just observe memory activity, and it works pretty nicely. I see the code come by my monitor as it is executed. Here is my program:
0000 main
0000 3E 3E LD A, '>' ; output a "prompt"
0002 D3 00 OUT (0), A
0004 DB 00 IN A, (0) ; input a byte
0006 57 LD D, A
0007 CA 27 00 JP Z, halte ; by accident I left JP and it works
000A DB 00 IN A, (0)
000C 15 DEC D
000D 28 1B JR Z, echo
000F 6F LD L, A
0010 DB 00 IN A, (0)
0012 67 LD H, A
0013 15 DEC D
0014 28 18 JR Z, exec
...
0027 halte
0027 ; just stop
0027 76 HALT
0028 18 D6 JR main
002A echo
002A ; write the same byte out that was just read
002A D3 00 OUT (0), A
002C 18 D2 JR main
002E exec
002E E9 JP (HL) ; I wish I could push PC to (SP)
002F ; bye bye, no return
...
0045 18 B9 JR main ; last instruction
I wrote this into my EEPROM by
- erasing everything with $FF
- filling $0000 - $0200 with $00
- then writing the code in
3E 3E D3 00 DB 00 57 CA 27 00 DB 00 15 28 1B 6F
DB 00 67 15 28 18 DB 00 4F DB 00 47 15 28 10 15
28 15 15 28 18 18 D9 76 18 D6 D3 00 18 D2 E9 DB
00 77 23 10 FA 18 C9 7E 23 10 FC 18 C3 DB 00 5F
DB 00 57 ED B0 18 B9 00 00 00 00 00 00 00 00 00
This will be useful for what's happening next.
I see system reach HALT state if I respond with $00 on the console. Here is a transcript of what my Arduino thing shows (I make comments with ;)
0000 > 3e
0001 > 3e
0002 > d3
0003 > 00
IO: 0000 < 00 ; sampling the data lines worked well with monitoring MREQ but with IOREQ it fails :(, should be 3E not 00
0004 > db
0005 > 00
IO: 0000 > .. ; here my /WAIT lamp comes on and CPU is waiting
g 00 ; I say "give 00" and it continues
0006 > 57
0007 > ca
0008 > 27
0009 > 00
0027 > 76 ; the HALT makes the /HALT LED come on
0028 > 18 ; and that is what happens during HALT, we keep reading 18 from the next opcode but never proceeding
0028 > 18
0028 > 18
This made me very happy as it is my first Z80 program actually running on my breadboard.
But when I respond with 01 or 02, something very strange happens!
0000 > 3e
0001 > 3e
0002 > d3
0003 > 00
IO: 0000 < f0 ; no idea why this time the data lines were sampled as $F0, but you see for sure that memory is read well through those same wires.
0004 > db
0005 > 00
IO: 0000 > .. ; /WAIT state asserted
> g 01 ; this time I give not 0 but 1
0006 > 57
0007 > ca
0008 > 27
0009 > 00 ; JP halte not taken
000b > 00 ; here Arduino skipped a beat 000a DB, but we can see that it is just a sampling failure, CPU marches on:
000c > 15
000d > 28 ; JR
000e > 1b ; 1B should get us to $0f + $1b = $2a, yes
0036 > 00 ; but WTF! Why are we at $36 now??
0037 > 00 ; and it's all 00
0038 > 00
0039 > 00
003a > 00
I need to explain here that I only sample the address lines A0 - A5, that means if I see $36 comes out as 00, it could only be $76, $B6, $F6, $136, $176, $1B6, $1F6, remembering that my EEPROM doesn't have 0 until after address $0046 and then only until $0200, after which it's all $FF. With a relative jump, however, from $000F I could not reach past $008f, so $76 is the only possible address where we jumped to.
Why would we jump to this weird address?
The only reason I could imagine is I have twisted some data lines (the Z80 pin-out is crazy that way), but the chance for that is pretty low, since it reads all the other instructions correctly.
I tried to find some flipped bit, but when I take the next code 02, it gets stranger even:
0000 > 3e
0001 > 3e
0002 > d3
0003 > 00
IO: 0000 < ff ; it must be a timing issue that I can't sample the data lines during IOREQ with /WR low (active)
0004 > db
0005 > 00
IO: 0000 .. ; /WAIT is asserted
g 02 ; I reply
0006 > 57
0007 > ca
0008 > 27
0009 > 00
000b > 00 ; we skipped the exact same beat again $000A with $DB
; oops, weren't we supposed to get the IN IOREQ now? missed it :/
000c > 15 ; since we took 000B as NOP, we are at the DEC D, now
000d > 28
000e > 1b
0010 > db ; and we didn't skip the IN IOREQ here
0011 > 00
IO: 0000 .. ; /WAIT is asserted
g 00 ; I respond with 00 (that's an operand, the function code is still being decremented in D
0012 > 67 ;
0013 > 15 ; DEC D to zero and
0014 > 28 ; JR
0015 > 18 ; to echo but ...
0037 > 00 ; DAMMIT!
0038 > 00
0039 > 00
So again the JR is going off into the open pastures. And there is no discernible constant error here. In fact, while the last time we jumped to 36, now we jump to 37.
Surely I still have timing issues, but I can be pretty certain that the 28 1b and 28 18 JR instructions have been read as have all the others, and het they go off into the wilderness.
UPDATE: I see I already got an answer, but I just found a very peculiar case that I want to keep as it may be symptomatic and indicative of the problem.
0006 > 57
0007 > ca ; skipped 3 beats here!!
000b > 00
000c > 15
000e > 1b
000f > 6f
0010 > db ; we jumped mid-instruction over 3 bytes!!!
0014 > 28 ; and we took this as the argument to the IN instruction!!!
IO: 0028 > .. ; here is the proof. DB ... 28 was executed
g ff ; now I give FF as the result of the IN
0015 > 18 ; but now we are off and take the address of JR Z, +18 as a JR unconditional
0016 > db ; with DB as the increment, negative!
0016 > 00
0017 > 00
0018 > 00
And we are back in the territory between 0046 and 0200. How can we possibly get here with a backward relative jump!?
What can make a PC skip three beats between M1 and the operand read? It's like a button bouncing. Noise? And why does $000A get consistently skipped over?
As for the other ideas, all good. But right now I have all inputs tied to high with a 1k resistor. So /IRQ and /NMI are held high.
BUSAK,BUSRQ
pins also I think you could useWAIT
too but that would need more circuitry I think