Lesson 5. The IO wire and the card tester: echo
In picturesAfter this lesson the card talks over one wire. You set a number on the switches, press "send" — and a moment later the display shows the card's answer: your number plus one. The byte went down the wire, the card read it and sent it back.
5.1. The idea: one wire for two
A card has only 8 contacts, and the data goes over one of them — IO. They talk in turn:
- whoever is silent lets go of the wire, and the resistor holds it at 1;
- whoever talks pulls it to 0 with its transistor;
- an ordinary output will not do here: it drives both 0 and 1, and two of them on one wire are a short circuit.
Such an output — "pull only" — is called an open drain. That is how the I/O line of real cards works.
5.2. The idea: a byte bit by bit
A frame: a start bit 0, 8 bits top bit first, a stop bit 1. Each bit is held for 16 clocks.
- the card receives and sends a byte by its program: every command is one clock, the loop per bit is exactly 16 commands;
- the tester does it in hardware: a frame counter and one shift register for receiving and sending;
- a guard interval: you may answer only after the other side's stop bit. Without it the answer shifts by a bit.
5.3. The idea: the "exchange — wait" lamp
After a transmission the card needs time to think. The tester holds a pause and lights lamp lamp. While it is lit — wait, the SEND button does nothing. It went off — the exchange is over, look at the answer.
5.4. Building
The same project as in lesson 4.
The card's side is small: register OUT and a transistor, the program does everything else. The terminal's side is the adapter: switches → "send" → wire → the answer on the lamps. Later it will become the ATM's port.
Step 1. The clock — faster
A byte over the wire is 10 bits of 16 clocks = 160 clocks. At 2Hz that is a minute and a half, so click the Clock body up to MAX. The lights and the display will flicker from now on — that is how it should be.
Step 2. The card's IO port
The command ST 29 (address $1D) puts bit 0 of A into register OUT. OUT = 1 — the transistor pulls the IO wire to 0, OUT = 0 — lets go. The wire is read by the command LD 28 ($1C) — that is AND(IO, selIO) from lesson 3. The card itself never pulls the wire to 1: the pull-up belongs to the terminal (the adapter's resistor).
| Part | Pin | Label |
|---|---|---|
| AND | I0 I1 O0 | oST selIO wOUT |
| RBIT | D LD nR CLK Q | acc0 wOUT nR CLK pull |
| nmos | gate drain source | pull IO GND |
Step 3. The adapter: line pull-up and panel
Put the adapter as a separate group to the right of the card, and move Clock and the RST switch there too: in lesson 8 you will select the whole card with a frame and fold it into a chip, and they will stay outside. The adapter is the terminal's side, another device on the same CLK clock. So it has its own NOT(RST) — nRT. The resistor holds the IO wire at 1 while nobody pulls it; its own transistor TPULL is like the card's. Switches T0–T7 are the byte, button SEND is "send".
| Part | Pin | Label |
|---|---|---|
| NOT | I0 O0 | RST nRT |
| Resistor | end 1 end 2 | VCC IO |
| nmos | gate drain source | TPULL IO GND |
| Input (switch) | out | T0 |
| Input (switch) | out | T1 |
| Input (switch) | out | T2 |
| Input (switch) | out | T3 |
| Input (switch) | out | T4 |
| Input (switch) | out | T5 |
| Input (switch) | out | T6 |
| Input (switch) | out | T7 |
| Button | out | SEND |
Step 4. The button: a new press and the start
sendS is the button latched by the clock; sendP is the same one a clock earlier. A press = sendS and not sendP: exactly one clock, however long you hold it. The start of sending goTX — only when the adapter is free (free, the "exchange" lamp is off); the start of receiving goRX — not busy and the wire has dropped to 0 (the card has started talking).
| Part | Pin | Label |
|---|---|---|
| DFF | D CLK Q | SEND CLK sendS |
| DFF | D CLK Q | sendS CLK sendP |
| NOT | I0 O0 | sendP nSendP |
| OR | I0 I1 O0 | TXB RXB busy |
| NOT | I0 O0 | busy idle |
| AND3 | A B C Y | sendS nSendP free goTX |
| NOT | I0 O0 | IO ioLow |
| AND | I0 I1 O0 | ioLow idle goRX |
Step 5. The frame counter
While busy, counter cn steps +1 every clock, otherwise it stays at 0. The top 4 bits are the bit number in the frame (0 start, 1–8 data, 9 stop), the low ones are the clock inside the bit (0–15). Bit 0 simply flips on +1 — that is nc0, the same NOT that is needed below for the clock number: we do not place a second one.
| Part | Pin | Label |
|---|---|---|
| HA | A B S C | cn1 cn0 ci1 cc2 |
| HA | A B S C | cn2 cc2 ci2 cc3 |
| HA | A B S C | cn3 cc3 ci3 cc4 |
| HA | A B S C | cn4 cc4 ci4 cc5 |
| HA | A B S C | cn5 cc5 ci5 cc6 |
| HA | A B S C | cn6 cc6 ci6 cc7 |
| HA | A B S C | cn7 cc7 ci7 cc8 |
| OR | I0 I1 O0 | busy WTB lamp |
| NOT | I0 O0 | lamp free |
| OR3 | A B C Y | eTX eRX goRX cz |
| NOT | I0 O0 | cz ncz |
| AND3 | A B C Y | lamp nRT ncz cntOn |
| AND | I0 I1 O0 | nc0 cntOn cd0 |
| AND | I0 I1 O0 | ci1 cntOn cd1 |
| AND | I0 I1 O0 | ci2 cntOn cd2 |
| AND | I0 I1 O0 | ci3 cntOn cd3 |
| AND | I0 I1 O0 | ci4 cntOn cd4 |
| AND | I0 I1 O0 | ci5 cntOn cd5 |
| AND | I0 I1 O0 | ci6 cntOn cd6 |
| AND | I0 I1 O0 | ci7 cntOn cd7 |
| DFF | D CLK Q | cd0 CLK cn0 |
| DFF | D CLK Q | cd1 CLK cn1 |
| DFF | D CLK Q | cd2 CLK cn2 |
| DFF | D CLK Q | cd3 CLK cn3 |
| DFF | D CLK Q | cd4 CLK cn4 |
| DFF | D CLK Q | cd5 CLK cn5 |
| DFF | D CLK Q | cd6 CLK cn6 |
| DFF | D CLK Q | cd7 CLK cn7 |
| NOT | I0 O0 | cn0 nc0 |
| NOT | I0 O0 | cn1 nc1 |
| NOT | I0 O0 | cn2 nc2 |
| NOT | I0 O0 | cn3 nc3 |
| NOT | I0 O0 | cn4 nc4 |
| NOT | I0 O0 | cn5 nc5 |
| NOT | I0 O0 | cn6 nc6 |
| NOT | I0 O0 | cn7 nc7 |
Step 6. Bit number and end of frame
ph0, ph9 are bit 0 and bit 9; inData is the data bits; sub15, sub7 are the last and the middle clock of a bit. Sending (TXB) ends on the last clock of the stop bit, receiving (RXB) in the middle of the stop bit.
| Part | Pin | Label |
|---|---|---|
| AND4 | A B C D Y | nc4 nc5 nc6 nc7 ph0 |
| AND4 | A B C D Y | cn4 nc5 nc6 cn7 ph9 |
| NOR | I0 I1 O0 | ph0 ph9 inData |
| AND4 | A B C D Y | cn0 cn1 cn2 cn3 sub15 |
| AND4 | A B C D Y | cn0 cn1 cn2 nc3 sub7 |
| AND | I0 I1 O0 | ph9 sub15 endTX |
| AND | I0 I1 O0 | ph9 sub7 endRX |
| NOT | I0 O0 | endTX nEndTX |
| NOT | I0 O0 | endRX nEndRX |
| AND | I0 I1 O0 | TXB nEndTX txKeep |
| OR | I0 I1 O0 | goTX txKeep txN |
| AND | I0 I1 O0 | txN nRT txD |
| DFF | D CLK Q | txD CLK TXB |
| AND | I0 I1 O0 | RXB nEndRX rxKeep |
| OR | I0 I1 O0 | goRX rxKeep rxN |
| AND | I0 I1 O0 | rxN nRT rxD |
| DFF | D CLK Q | rxD CLK RXB |
Step 7. The shift register
One register for receiving and sending: the line goes one way at a time anyway. The start of sending takes the switches; then a shift left: when sending — at the end of a bit, when receiving — in the middle, and the line bit comes in at the bottom (bitIn).
| Part | Pin | Label |
|---|---|---|
| AND3 | A B C Y | TXB inData sub15 shTX |
| AND3 | A B C Y | RXB inData sub7 shRX |
| OR | I0 I1 O0 | shTX shRX sh |
| AND | I0 I1 O0 | RXB IO bitIn |
| MUX2 | A B S Y | sr0 bitIn sh sa0 |
| MUX2 | A B S Y | sr1 sr0 sh sa1 |
| MUX2 | A B S Y | sr2 sr1 sh sa2 |
| MUX2 | A B S Y | sr3 sr2 sh sa3 |
| MUX2 | A B S Y | sr4 sr3 sh sa4 |
| MUX2 | A B S Y | sr5 sr4 sh sa5 |
| MUX2 | A B S Y | sr6 sr5 sh sa6 |
| MUX2 | A B S Y | sr7 sr6 sh sa7 |
| MUX2 | A B S Y | sa0 T0 goTX sb0 |
| MUX2 | A B S Y | sa1 T1 goTX sb1 |
| MUX2 | A B S Y | sa2 T2 goTX sb2 |
| MUX2 | A B S Y | sa3 T3 goTX sb3 |
| MUX2 | A B S Y | sa4 T4 goTX sb4 |
| MUX2 | A B S Y | sa5 T5 goTX sb5 |
| MUX2 | A B S Y | sa6 T6 goTX sb6 |
| MUX2 | A B S Y | sa7 T7 goTX sb7 |
| DFF | D CLK Q | sb0 CLK sr0 |
| DFF | D CLK Q | sb1 CLK sr1 |
| DFF | D CLK Q | sb2 CLK sr2 |
| DFF | D CLK Q | sb3 CLK sr3 |
| DFF | D CLK Q | sb4 CLK sr4 |
| DFF | D CLK Q | sb5 CLK sr5 |
| DFF | D CLK Q | sb6 CLK sr6 |
| DFF | D CLK Q | sb7 CLK sr7 |
Step 8. Line, lamps and display
Sending: start bit — pull, data bit 0 — pull (the top bit of the register), stop — let go. The lamps and the display are the register: after receiving it holds the card's answer, and the display shows it as a number right away.
| Part | Pin | Label |
|---|---|---|
| NOT | I0 O0 | sr7 nsr7 |
| AND | I0 I1 O0 | inData nsr7 d0 |
| OR | I0 I1 O0 | ph0 d0 pl |
| AND | I0 I1 O0 | TXB pl TPULL |
| LED | in | sr7 |
| LED | in | sr6 |
| LED | in | sr5 |
| LED | in | sr4 |
| LED | in | sr3 |
| LED | in | sr2 |
| LED | in | sr1 |
| LED | in | sr0 |
| Display | ⚙ | width 8, DEC, bus sr |
Step 9. The "exchange — wait" lamp
While the exchange is going on, lamp lamp is lit — wait. After its own transmission the adapter waits for the answer for another 256 clocks (WTB): the card needs time to think. If it starts answering — we receive; if it does not (a command of several bytes, the card waits for the next one) — the pause ends and the lamp goes off. After the answer — the same pause while the card finishes. The lamp is off — you can do the next action; while it is lit, SEND does nothing. lamp = busy or waiting (OR), free is the opposite; the frame counter also runs in the pause (cntOn), and at the end of a frame and at the start of receiving it starts from zero (cz). eTX, eRX are the end of a frame, only while one is going: in the pause the counter also passes 151 and 159, but that is not the end.
| Part | Pin | Label |
|---|---|---|
| AND | I0 I1 O0 | TXB endTX eTX |
| AND | I0 I1 O0 | RXB endRX eRX |
| AND4 | A B C D Y | cn4 cn5 cn6 cn7 hi15 |
| AND | I0 I1 O0 | sub15 hi15 c255 |
| NOT | I0 O0 | c255 nc255 |
| AND | I0 I1 O0 | WTB nc255 wKeep |
| OR3 | A B C Y | eTX eRX wKeep wN |
| NOT | I0 O0 | goRX nGoRX |
| AND3 | A B C Y | wN nGoRX nRT wD |
| DFF | D CLK Q | wD CLK WTB |
| LED | in | lamp |
Step 10. The program in the ROM
⚙ on ROM → button ⬆ → file 05-echo.rom.json. The main thing in it is receiving and sending a byte by the program: every command is one clock, so the loop per bit is exactly 16 commands (spare clocks are NOP, the empty command). These are the same rx and tx that will be in the card's program.
; Lesson 5 — echo over one wire: the card receives a byte over IO and answers with the same byte + 1.; Receiving and sending are done by the program: bit by bit, exactly 16 clocks per bit (every command is one clock).; --- RAM (8 bytes) ---.equ B $00 ; byte: received / sent.equ N $01 ; bit counter.equ D $02 ; delay counter.equ H $03 ; first argument byte / high byte of the cipher.equ L $04 ; second argument byte / low byte of the cipher.equ OK $05 ; 1 = PIN is correct in this session; --- permanent memory (on transistors) ---.equ ACC $10 ; account number.equ PINH $11 ; PIN, digits 1–2.equ PINL $12 ; PIN, digits 3–4.equ K0 $13 ; key, 4 bytes.equ K1 $14.equ K2 $15.equ K3 $16; --- try register and port ---.equ TRY $18 ; wrong tries in a row (3 = blocked).equ IN $1C ; bit 0 = the IO line.equ OUT $1D ; bit 0 = 1 → pull the line to 0 start: LD #0 ST OUT ; let go of the line ST B ; RAM at power-on is garbage: write cell B first, then read it (rx shifts it)loop: CALL rx ; receive a byte → B LD B ADD #1 ST B ; B + 1 CALL tx ; send B JMP loop ; --- receiving a byte: wait for the start bit, then 8 bits top first, one every 16 clocks ---rx: LD IN AND #1 JNZ rx ; line is 1 — wait LD #8 ST N .rept 10 ; get to the middle of the first bit NOP .endrrxbit: NOP NOP NOP NOP NOP LD B ADD B ; shift B left ST B LD IN ; ← here we read the bit AND #1 ADD B ST B LD N SUB #1 ST N JNZ rxbit ; the loop is exactly 16 clocks LD #6 ST Drxg: LD D ; guard: wait out the terminal's stop bit (2 + 6 × 4 = 26 clocks) SUB #1 ST D JNZ rxg RET ; --- sending byte B: start 0, 8 bits top first, stop 1 ---tx: LD #1 ST OUT ; start bit .rept 7 NOP .endr LD #8 ST Ntxbit: LD B ADD B ; top bit → C ST B LD #0 ADC #0 ; A = C XOR #1 ; bit 1 → do not pull, 0 → pull ST OUT ; ← here the bit goes out on the line NOP NOP NOP NOP NOP LD N SUB #1 ST N JNZ txbit ; the loop is exactly 16 clocks NOP NOP NOP NOP NOP LD #0 ST OUT ; stop bit LD #8 ST Ddly: LD D ; guard interval: 2 + 8 × 4 = 34 clocks SUB #1 ST D JNZ dly RET5.5. Checking
Go down the rows from top to bottom: one row is one action. After SEND wait until the "exchange" lamp goes off (lamp), and compare with the "Lamps" column: the card added 1 to your number and sent it back.
| № | What to do | Lamps (● lit) | What it is |
|---|---|---|---|
| 1 | Clock — MAX (click on the body) | ||
| 2 | RST on and off | ||
| 3 | switches T7…T0 = 0100 0001, press SEND | ○●○○○○●○ 66 | 65 + 1 = 66 |
| 4 | switches T7…T0 = 0000 0000, press SEND | ○○○○○○○● 1 | 0 + 1 = 1 |
| 5 | switches T7…T0 = 1111 1111, press SEND | ○○○○○○○○ 0 | 255 + 1 = 0 (8 bits: 256 → 0) |
| 6 | switches T7…T0 = 1010 0101, press SEND | ●○●○○●●○ 166 | 165 + 1 = 166 |
| 7 | switches T7…T0 = 0111 1111, press SEND | ●○○○○○○○ 128 | 127 + 1 = 128 |
- While the "exchange" lamp is lit, the
srlamps blink: the register pushes the switches out into the wire, and the answer slides in from the other end. It went off — the exchange is over, you can do the next one. - Nothing changes — check the pull-up: a
ResistorbetweenVCCandIO; without it the wire "floats" in Real (orange). - Memory at power-on is garbage (in Real unwritten latches are orange, Z). So the program first writes cell
B(ST B) and only then reads it. Forget it — and in RealAturns orange on the very firstADD B. - In Real the adapter's lamps are orange until the first transmission: register
srhas no reset and has not remembered anything yet. After the firstSENDthey are normal.
Do not fold into a chip. Leave the adapter on the canvas next to it — in lesson 8 the card goes into a chip, and the adapter stays outside.
⚠ Common mistakes. (1) NoAND→txD: flip-flopTXB"floats", the whole tester turns orange, and after it the wire and the card. (2) The register outputs are labelled something other thansr— the lamps and the line do not see the register. (3) In Real the tester lamps are orange until the first transmission — that is normal: the register has no reset.
Lesson 5 paste (Cmd+I)
On top of lesson 4.
# Lesson 5 — the IO wire: the card port and the adapter (tester). On top of lesson 4; Clock MAX; then load 05-echo.rom.json into ROM.AND(oST, selIO, wOUT)RBIT(D=acc0, LD=wOUT, nR=nR, CLK=CLK, Q=pull)nmos(pull, IO, GND) NOT(RST, nRT)resistor(VCC, IO)nmos(TPULL, IO, GND)input(Out=T0)input(Out=T1)input(Out=T2)input(Out=T3)input(Out=T4)input(Out=T5)input(Out=T6)input(Out=T7)button(Out=SEND) DFF(D=SEND, CLK=CLK, Q=sendS)DFF(D=sendS, CLK=CLK, Q=sendP)NOT(sendP, nSendP)OR(TXB, RXB, busy)NOT(busy, idle)AND3(A=sendS, B=nSendP, C=free, Y=goTX)NOT(IO, ioLow)AND(ioLow, idle, goRX) HA(A=cn1, B=cn0, S=ci1, C=cc2)HA(A=cn2, B=cc2, S=ci2, C=cc3)HA(A=cn3, B=cc3, S=ci3, C=cc4)HA(A=cn4, B=cc4, S=ci4, C=cc5)HA(A=cn5, B=cc5, S=ci5, C=cc6)HA(A=cn6, B=cc6, S=ci6, C=cc7)HA(A=cn7, B=cc7, S=ci7, C=cc8)OR(busy, WTB, lamp)NOT(lamp, free)OR3(A=eTX, B=eRX, C=goRX, Y=cz)NOT(cz, ncz)AND3(A=lamp, B=nRT, C=ncz, Y=cntOn)AND(nc0, cntOn, cd0)AND(ci1, cntOn, cd1)AND(ci2, cntOn, cd2)AND(ci3, cntOn, cd3)AND(ci4, cntOn, cd4)AND(ci5, cntOn, cd5)AND(ci6, cntOn, cd6)AND(ci7, cntOn, cd7)DFF(D=cd0, CLK=CLK, Q=cn0)DFF(D=cd1, CLK=CLK, Q=cn1)DFF(D=cd2, CLK=CLK, Q=cn2)DFF(D=cd3, CLK=CLK, Q=cn3)DFF(D=cd4, CLK=CLK, Q=cn4)DFF(D=cd5, CLK=CLK, Q=cn5)DFF(D=cd6, CLK=CLK, Q=cn6)DFF(D=cd7, CLK=CLK, Q=cn7)NOT(cn0, nc0)NOT(cn1, nc1)NOT(cn2, nc2)NOT(cn3, nc3)NOT(cn4, nc4)NOT(cn5, nc5)NOT(cn6, nc6)NOT(cn7, nc7) AND4(A=nc4, B=nc5, C=nc6, D=nc7, Y=ph0)AND4(A=cn4, B=nc5, C=nc6, D=cn7, Y=ph9)NOR(ph0, ph9, inData)AND4(A=cn0, B=cn1, C=cn2, D=cn3, Y=sub15)AND4(A=cn0, B=cn1, C=cn2, D=nc3, Y=sub7)AND(ph9, sub15, endTX)AND(ph9, sub7, endRX)NOT(endTX, nEndTX)NOT(endRX, nEndRX)AND(TXB, nEndTX, txKeep)OR(goTX, txKeep, txN)AND(txN, nRT, txD)DFF(D=txD, CLK=CLK, Q=TXB)AND(RXB, nEndRX, rxKeep)OR(goRX, rxKeep, rxN)AND(rxN, nRT, rxD)DFF(D=rxD, CLK=CLK, Q=RXB) AND3(A=TXB, B=inData, C=sub15, Y=shTX)AND3(A=RXB, B=inData, C=sub7, Y=shRX)OR(shTX, shRX, sh)AND(RXB, IO, bitIn)MUX2(A=sr0, B=bitIn, S=sh, Y=sa0)MUX2(A=sr1, B=sr0, S=sh, Y=sa1)MUX2(A=sr2, B=sr1, S=sh, Y=sa2)MUX2(A=sr3, B=sr2, S=sh, Y=sa3)MUX2(A=sr4, B=sr3, S=sh, Y=sa4)MUX2(A=sr5, B=sr4, S=sh, Y=sa5)MUX2(A=sr6, B=sr5, S=sh, Y=sa6)MUX2(A=sr7, B=sr6, S=sh, Y=sa7)MUX2(A=sa0, B=T0, S=goTX, Y=sb0)MUX2(A=sa1, B=T1, S=goTX, Y=sb1)MUX2(A=sa2, B=T2, S=goTX, Y=sb2)MUX2(A=sa3, B=T3, S=goTX, Y=sb3)MUX2(A=sa4, B=T4, S=goTX, Y=sb4)MUX2(A=sa5, B=T5, S=goTX, Y=sb5)MUX2(A=sa6, B=T6, S=goTX, Y=sb6)MUX2(A=sa7, B=T7, S=goTX, Y=sb7)DFF(D=sb0, CLK=CLK, Q=sr0)DFF(D=sb1, CLK=CLK, Q=sr1)DFF(D=sb2, CLK=CLK, Q=sr2)DFF(D=sb3, CLK=CLK, Q=sr3)DFF(D=sb4, CLK=CLK, Q=sr4)DFF(D=sb5, CLK=CLK, Q=sr5)DFF(D=sb6, CLK=CLK, Q=sr6)DFF(D=sb7, CLK=CLK, Q=sr7) NOT(sr7, nsr7)AND(inData, nsr7, d0)OR(ph0, d0, pl)AND(TXB, pl, TPULL)led(sr7)led(sr6)led(sr5)led(sr4)led(sr3)led(sr2)led(sr1)led(sr0)display{bits=8, base="DEC", busSource="sr"}() AND(TXB, endTX, eTX)AND(RXB, endRX, eRX)AND4(A=cn4, B=cn5, C=cn6, D=cn7, Y=hi15)AND(sub15, hi15, c255)NOT(c255, nc255)AND(WTB, nc255, wKeep)OR3(A=eTX, B=eRX, C=wKeep, Y=wN)NOT(goRX, nGoRX)AND3(A=wN, B=nGoRX, C=nRT, Y=wD)DFF(D=wD, CLK=CLK, Q=WTB)led(lamp)Stuck? Something doesn't work →