gfoot wrote:
Using a VIA, I think I'd be inclined to lean heavily on its capabilities - counting pulses to generate an interrupt at the right time, for example, and using the internal shift register to receive the data.
But without a VIA, using only 74-series logic with direct connection to a shared bus and IRQ support, this was my thought:
Attachment:
ps2keyboard_74xxx.png
Reading from the port resets everything - U3A gets set, U3B and U2 get cleared. Now when the keyboard sends data, the start bit enters U3A, then feeds through the shift register U2 and U3B - but note that it gets inverted going from U3A to U2. So when it arrives at U3B we see a rising edge. This asserts /IRQ, and also clocks the storage register of the '595 (U2). The CPU should now read the data, and when it does everything is reset again.
The keyboard will send one more clock pulse, for the stop bit. If the CPU has already read the data then this is a no-op - it feeds 1 into U3A, matching its existing state, and U3A's inverted output is 0, matching the reset state of the 595. If the CPU hasn't read the data yet then this next clock pulse might clear U3B and reset /IRQ, which is undesirable. So better read the data within 100us. Failing that, it would be possible to use a separate D flipflop for /IRQ, so that it's sticky.
Again thinking in terms of polled rather than /IRQ PS/2, could the dual 4bit SR 74HC4015 (DSR1 & DSR2) that Dr. Jefyll referred to above be used for counting to two clock pulses after the data is stored to register?
This idea would lose parity, because it would involve stalling the SSR ('595) clock after the inverted start bit reaches QH:
DSR1_SCLK := SSR_SCLK := NAND(SSR_QH',PS2CLK)
SSR_RCLK := SSR_QH.
DSR1 is used as a one cycle delay, using a NOT gate to invert DSR1_QA for the '595 serial input:
DSR1_CLR := NOT(/READ)
DSR1_SER := PS2DAT
/SSR_CLR := /READ
/SSR_OE := /READ
SSR_SER := NOT(DSR1_SER)
DSR2 runs directly off the PS2CLK, so it trails by half a phase. Since it resets to 0, it is fed SSR_QH, so all outputs remain 0 until the inverted start bit arrives at SSR_QH during the MSbit Data phase, and the SSR stalls there, so SSR_QH remains high. In Parity phase, 1 arrives at DSR2_QA. In stop phase, 1 arrives at DSR2_QB. This is used to pull the PS2CLK low for flow control until the byte is read, which resets the shift register and releases the PS2CLK:
DSR2_E := NOT(/READ)
DSR2_CLK := PS2CLK
DSR2_SER := SSR_QH
NOT(DSR2_QB) =: -DIODE+ PS2CLK := pullup+5v
/READYQ is the active low to read the ready signal, which is an open collector held high unless the PS2CLK is being held low:
NAND(NOT(DSR2_QB),/READYQ) =: -DIODE+ =: BUS_D7 := pullup+VCC
PS2 is lsbit first, so D0 inverted is SSR_QG, through to D6 inverted at SSR_QA, and D7 inverted at NOT(DSR1_QA), but the last is not tri-stated, so it also needs to be an open collector/drain:
NAND(DSR1_QA,NOT(/READ)) =: -DIODE+ =: BUS_D7 := pullup+VCC
IIUC, as open collector/drain outputs, a single pullup on BUS_D7 suffices ... either one added or an existing pull-up bus terminator.
Then in the polling timed /IRQ routine, "BIT PS2RDY : BMI READKBD" would check the ready bit and "LDA #$FF : EOR PS2DATA" would read the data.
The decoding of the two active low selects is additional overhead, but this adds 3 NOTs and 3 two-input NANDs, so if there is a free OR gate, a common /PS2 could give OR(/PS2,A0) as /READ and NAND(NOT(/PS2),A0) as /READYQ, leaving two gates free from a hex inverter.
An upgrade to that one would be to have the resets happen at system reset as well as /Read.