I APOLOGIZE THAT THIS IS GETTING LONG-ish. There are many updates at the end, and I begin editing the original to bring some finding and WARNINGs into it.
BACKGROUND
I am building myself a Z80 breadboard computer from scratch. It has 64 kB RAM and an 8 kB EEPROM (28C64). I have the EEPROM mapped in a mode where writes go to RAM and reads from ROM when address lines A13, A14, and A15 are low, /MREQ is low, and /WR is high. I can do that all with just one stage of a 74LS04 hex inverter and a 74HC32 quad 2 input OR gate. All well and good. Except I have this crazy desire to allow the system to re-program its own EEPROM for "firmware upgrades". Today I almost broke off a pin taking the EEPROM out one time to often, so I really want to get this programming mode done. I had it almost working. But it's unreliable.
The issue is the EEPROM /WE line doesn't behave like an SRAM /WE line. It's not a level latch, it requires a low-pulse of 100 ns to 1 μs. Here is the timing specification from the AT28C64 data sheet:
Someone said that setup time might be an issue, but on a Z80 it is definitely not an issue, because the address lines and /MREQ appear way before /WE, separated by an entire clock cycle, with an optional ability to make the CPU /WAIT for several more cycles.
FIRST IDEAS
Here is what I came up with, and it's sometimes working and sometimes it isn't working. So it's unreliable. (BTW, I cannot find the schematics editor any more?!)
The 3-position switch SW1 here sets the ROM addressing mode. In the middle it's the normal mode, read from ROM, write to RAM. On the high side it's ROM off, read and write from RAM. On the low side it's ROM on even for write, that, together with the /WE pulse should allow firmware upgrade.
C1 may even have been 10 nF and it also worked, or worked better even. The first time I put this together I even had R1 being 1 kΩ. No idea how this worked, but it did. Sometimes.
To make sure I didn't mess anything else up, I can run my computer in super slow mode and manually push a button of the kind that Ben Eater did his first manual EEPROM programmer.
He used R1 = 680 Ω. I have 330 Ω, the pulse needs to be short. So with this and if I push the button exactly when the /MREQ and /WR and /CS,ROM light come on, I could program a few bytes in. So I know my general set-up works. I just can't get the pulse right.
I figure the 74LS04 can sink 20 mA in output low state. 1 nF at 5V contains 5 nC when fully charged, and so at 20 mA (20 mC/s) it would take me 5 nC / 20 mC s, or 0.5 μs. So that should be just fine. With the R1 = 330 Ω, I would have 15 mA going through, that would still be 0.33 μs, well above the minimum of 100 ns.
Yet, it's not working. And I don't understand how it could have worked with a C1 = 10 nF and R1 = 1 kΩ. Yet, it has, once or twice in odd hours of the night.
I figured, perhaps the 74LS04 cannot sink enough current or so, or that it doesn't source enough to recharge C1 in high state. So I tried with a BJT and the R2 = 10 kΩ that Ben Eater used.
but to no avail.
WARNING As one respondent here said: while this does create a low-going pulse at the falling edge, it also creates a -- possibly chip-destroying!! --- high pulse at the rising edge! And this is true. Be warned, I actually just blew a chip with a mere button and a 1 nF capacitor!
As you can see here on the bench, the circuit creates a nice square wave of a few 100 ns in size on the falling edge. Don't worry about it being positive pulse instead of a negative, I just simulated this all around a single 74HC14 Schmitt-trigger inverter.
here is the input side, yellow trace is the falling edge of the buffer after the button. The blue trace is the capacitor acting on the falling edge, creating our pulse. This is the good side of the theory that is working as Ben Eater has taught. But warning, while yellow is scaled at 1 V/div, the blue trace is at 2.5 V/div for a reason!
Now look at ugly other side of the input, the rising edge.
it reaches levels twice the height of the normal 5 V high.
After taking several shots of this, my poor final inverter stage that had the capacitor input got shot so many times, it started failing. The chip got hot. It would still recover once. Then a zombie-like death state occurred, where it's still switching DC, somewhat, but when actually hit by changing levels, just breaks down. Chip fried, death by capacitor.
So therefore, now with the scope, I can possibly imagine that my following observation might be due to a subtle injury of the /WE input. I had observed -- without a scope -- "Last night it had worked. And today I wanted to show it off and now again I just can't get it to work any more."
Fortunately, so far, when plugging the chip into my Arduino based EEPROM programmer, it still works. So my old EEPROM chip might have barely escaped death by capacitor.
I still want something simple but reliable enough that it would work any time of day. So I will still try my transistor based approach, but modified so as to not create a recovery pulse higher than 5.5 V.
Theoretical Experiments with LTspice
Sadly, LTspice doesn't come with a library of real existing logic gates. There should be a set of 74LS and HC and HCT gates, but I don't have one. I tried to set Isink=20mA, but I don't trust that I am setting these parameters correctly.
However I have a hunch that in some situations the down spike doesn't even go all the way to ground. This is part of the issue, but anyway the approach is wrong because of the death threat to the EEPROM.
LTspice might actually under-estimate the problem with the chip-destroying capacitor pulse on the up-slope. I could see that up pulse, twice the level of Vcc, but didn't want to think it would be bad in practice. Yet it is. It's not reduced I think because the Iout is only in the μA range while Isink is 8 or 20 mA, no. Has nothing to do with current coming out of the pin, it's about what comes out of the capacitor. I assumed that Ben Eater's simple button also doesn't have this problem because the R2 = 10 kΩ, but I was wrong as my scope pictures above prove.
The next circuit I will test is what I ended up trying with LTspice. This here appears to limit the recovery over-voltage on the rising edge. But I will see this on the breadboard next. [Update to come later today, the following is still all just theoretical.]
So, V2 is my input square wave. Now I have 50% duty cycle because I think I'm going to go with an inverted /WE (or WE, active high). Right out of U1A.
Reason is that I realized if I drive C right out of the gate output pin, it's not going to pull low enough. And all the weird C1 = 10 nF and R1 = 1 kΩ trickery is just magic that depends on the clock speed and the time into the multiple runs and whatever magic.
Because I don't really know how to set correct parameters for the inverter gate of LTspice to match a 74LS04, HC04 or LS14 schmitt trigger, I just use two different resistors with diodes in or out of the output pin. This configuration is supposed to emulate the 74LS14 with only 8 mA sink current when low and 0.4 mA source current when high.
Then I adjusted the R1 and R2 to pull low enough. There is some tolerance here. The over-voltage at the re-charge seems perhaps below chip-killing levels now [WARNING: unconfirmed speculation!], and if I use the schmitt inverters, then I could run it through 2 more stages of it to clean that up.
Practical Test of the BJT Circuit
I now built the circuit that I have come up with and found that indeed the BJT somehow does not create the 2 x Vcc killer-pulse. There is a small hump above Vcc but it remains well inside the range (especially because my Vcc is somewhat low anyway.) I don't care about the exact values of R and C right now, just the principle. Here is the best result with the 74HC14 schmitt-trigger (yes, this is the one that got its input pin 9 fried by the killer-pulse.)
I was curious how the other 3 alternatives gates would perform here. And I show the results. First the 74LS04 which I like to use because it worked well for me as an oscillator for 1-4 MHz or less. But clearly for this purpose it fails.
The low pulse is barely there, possibly in spec for the EEPROM, but not reliable because it comes immediately up, 99% of the RC curve is irrelevant and the pulse only at the tiny tip. Not going to be reliable. Next with the 74HC04, it is slightly better:
and you might complain that it just should never be done without the help of Mr. Schmitt. But it is better with the HC non-schmitt than with the LS schmitt:
The benefit of the BJT circuit is that it doesn't do the killer pulse, and possibly pulls stronger to zero than if the previous output was directly connected to the capacitor. But the actual delay is hard to manage, and not even very good for the LS14 schmitt trigger.
Now I will proceed building all the other proposed solutions.
Best and Simplest Approach -- its practical difficulties and work-around.
I want to mention a really cool answer came in the form of a comment by the ever helpful Bruce Abbott. He said to just use a MHz clock and the problem is resolved. I did that, and it didn't work, for me, yet. Today with the scope I see that the Arduino alone doesn't provide enough power. The 74LS04 I use as an oscillator only outputs 2.7 V or so. When I connect a 3 A 5V power supply, I get a little higher. But never to 5 V. Anyway high enough to clock the Z80, so with the external power supply it probably would work.
However, now the problem is that the Arduino Nano, which I use as to serve Z80's IO requests, cannot keep up. It doesn't have enough time to throw the /WAIT line when it receives the /RE & /IORQ, interrupt doesn't trigger fast enough. So this method would work if I did not rely on the Arduino in high speed mode. I suppose I could use slow mode to copy the content into RAM first, then count down a 22 bit counter. Would take forever in 1 kHz mode, but in 4 MHz mode would take a few seconds (I measured in 4 MHz clock in HALT mode the M1 flashes at 1 MHz). Once the write is complete, CPU would go into HALT and I can see the red light come on. Then I can switch back to the 555 clock and be done with it.
The fun thing is, I can bootstrap myself to this because once I move the flash image into RAM, I can execute it from there.