Lesson 2. The program counter and ROM: lights by the program
In picturesAfter this lesson the processor walks through the program by itself: a light runs along the lamps there and back, and between the passes all the lamps flash. The code and number switches are no longer needed — the ROM replaces them.
2.1. The idea: a program is a list of commands
The commands lie in the ROM one after another: 256 words of 16 bits. The number of the current one is kept by the register PC — the program counter. Every clock PC adds 1, and the processor takes the next command. The command word:
ir12–ir15— the operation code (the same as on the switches);ir11— "number": 1 — the number is in the command itself (LD #5), 0 — from memory (lesson 3);ir0–ir7— the number itself or the jump address.
2.2. The idea: jumps and a subroutine
JMPputs the address from the command intoPC— the program goes round;JZ/JNZ,JC/JNCjump only if flagZorCis as required;CALL, before jumping, remembers the address of the next command in the stack,RETreturns there.
Our stack is one cell: a subroutine cannot call another one. That is enough for the card.
2.3. Building
The same project as in lesson 1.
Step 1. Remove the panel of lesson 1
The code and the number now come from the program: the code from the ROM (ir12–ir15), the number from a MUX2 out of the command word (bv0–bv7). The clock comes from a clock generator. Two parts on one label are a short circuit, so remove them first. The labels next to them can stay: the same names will appear on the pins of the new parts. The RST switch stays.
| Element | Action |
|---|---|
| Button whose output = CLK | delete |
| Input whose output = ir12 | delete |
| Input whose output = ir13 | delete |
| Input whose output = ir14 | delete |
| Input whose output = ir15 | delete |
| Input whose output = bv0 | delete |
| Input whose output = bv1 | delete |
| Input whose output = bv2 | delete |
| Input whose output = bv3 | delete |
| Input whose output = bv4 | delete |
| Input whose output = bv5 | delete |
| Input whose output = bv6 | delete |
| Input whose output = bv7 | delete |
Step 2. Chip HA — a half adder: sum and carry of two bits
If HA is already in the library (from "How a CPU Counts" or HB-04), skip this step.
| Part | Pin | Label |
|---|---|---|
| Input port | out | A |
| Input port | out | B |
| Output port | in | S |
| Output port | in | C |
| XOR | I0 I1 O0 | A B S |
| AND | I0 I1 O0 | A B C |
Ctrl+G → name HA. Pins: inputs A, B; outputs S, C.
Step 3. Chip OR3 — OR with 3 inputs
If OR3 is already in the library (from "A Watch and a Game"), skip this step.
| Part | Pin | Label |
|---|---|---|
| Input port | out | A |
| Input port | out | B |
| Input port | out | C |
| Output port | in | Y |
| OR | I0 I1 O0 | A B ab |
| OR | I0 I1 O0 | ab C Y |
Ctrl+G → name OR3. Pins: inputs A, B, C; outputs Y.
Step 4. The clock
Clock from the palette, click on the body — 2Hz: two commands per second, the lights can be followed by eye.
| Part | Pin | Label |
|---|---|---|
| Clock (2Hz) | out | CLK |
Step 5. The program ROM
The ROM is the memory that holds the program: 256 words of 16 bits, one word is one command. PC comes to the address, and the word ir0–ir15 is at the output right away: ir12–ir15 the code, ir11 "number", ir0–ir7 the number or the jump address.
Memory (RAM) from the palette, in the memory editor (⚙):
| Memory | Size | Data | Address | CLR |
|---|---|---|---|---|
| ROM | 256 words | 16b data | 16b addr | off |
Size: click 16 words ▸ → 256 → Enter. Do not put CLR on the ROM — it would erase the program. Pins A8–A15, WE, CLK and all D0–D15 go to GND (nobody writes to a ROM).
| Part | Pin | Label |
|---|---|---|
| Memory (RAM) ROM | A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15 Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 Q10 Q11 Q12 Q13 Q14 Q15 | pc0 pc1 pc2 pc3 pc4 pc5 pc6 pc7 GND GND GND GND GND GND GND GND ir0 ir1 ir2 ir3 ir4 ir5 ir6 ir7 ir8 ir9 ir10 ir11 ir12 ir13 ir14 ir15 |
Step 6. PC + 1
The next address is plus one: bit 0 flips, then the carry runs along the half adders while there are ones.
| Part | Pin | Label |
|---|---|---|
| NOT | I0 O0 | pc0 inc0 |
| HA | A B S C | pc1 pc0 inc1 ic2 |
| HA | A B S C | pc2 ic2 inc2 ic3 |
| HA | A B S C | pc3 ic3 inc3 ic4 |
| HA | A B S C | pc4 ic4 inc4 ic5 |
| HA | A B S C | pc5 ic5 inc5 ic6 |
| HA | A B S C | pc6 ic6 inc6 ic7 |
| HA | A B S C | pc7 ic7 inc7 ic8 |
Step 7. When to jump
jmp = 1 — the next address is taken from the command: JMP and CALL always, JZ/JNZ by flag Z, JC/JNC by C.
| Part | Pin | Label |
|---|---|---|
| NOT | I0 O0 | fZ nfZ |
| NOT | I0 O0 | fC nfC |
| AND | I0 I1 O0 | oJZ fZ tJZ |
| AND | I0 I1 O0 | oJNZ nfZ tJNZ |
| AND | I0 I1 O0 | oJC fC tJC |
| AND | I0 I1 O0 | oJNC nfC tJNC |
| OR3 | A B C Y | oJMP oCALL tJZ j1 |
| OR3 | A B C Y | tJNZ tJC tJNC j2 |
| OR | I0 I1 O0 | j1 j2 jmp |
Step 8. The next PC and register PC
The choice: PC + 1 or the address from the command (jmp), then the return address from the stack (RET). AND with nR is the reset to 0. PC is written every clock — no LD, just flip-flops.
| Part | Pin | Label |
|---|---|---|
| MUX2 | A B S Y | inc0 ir0 jmp jn0 |
| MUX2 | A B S Y | inc1 ir1 jmp jn1 |
| MUX2 | A B S Y | inc2 ir2 jmp jn2 |
| MUX2 | A B S Y | inc3 ir3 jmp jn3 |
| MUX2 | A B S Y | inc4 ir4 jmp jn4 |
| MUX2 | A B S Y | inc5 ir5 jmp jn5 |
| MUX2 | A B S Y | inc6 ir6 jmp jn6 |
| MUX2 | A B S Y | inc7 ir7 jmp jn7 |
| MUX2 | A B S Y | jn0 sk0 oRET nx0 |
| MUX2 | A B S Y | jn1 sk1 oRET nx1 |
| MUX2 | A B S Y | jn2 sk2 oRET nx2 |
| MUX2 | A B S Y | jn3 sk3 oRET nx3 |
| MUX2 | A B S Y | jn4 sk4 oRET nx4 |
| MUX2 | A B S Y | jn5 sk5 oRET nx5 |
| MUX2 | A B S Y | jn6 sk6 oRET nx6 |
| MUX2 | A B S Y | jn7 sk7 oRET nx7 |
| AND | I0 I1 O0 | nx0 nR pd0 |
| AND | I0 I1 O0 | nx1 nR pd1 |
| AND | I0 I1 O0 | nx2 nR pd2 |
| AND | I0 I1 O0 | nx3 nR pd3 |
| AND | I0 I1 O0 | nx4 nR pd4 |
| AND | I0 I1 O0 | nx5 nR pd5 |
| AND | I0 I1 O0 | nx6 nR pd6 |
| AND | I0 I1 O0 | nx7 nR pd7 |
| DFF | D CLK Q | pd0 CLK pc0 |
| DFF | D CLK Q | pd1 CLK pc1 |
| DFF | D CLK Q | pd2 CLK pc2 |
| DFF | D CLK Q | pd3 CLK pc3 |
| DFF | D CLK Q | pd4 CLK pc4 |
| DFF | D CLK Q | pd5 CLK pc5 |
| DFF | D CLK Q | pd6 CLK pc6 |
| DFF | D CLK Q | pd7 CLK pc7 |
Step 9. The stack: one cell
CALL remembers the address of the next command (PC + 1), RET returns there. There is one cell: a subroutine cannot call another one.
| Part | Pin | Label |
|---|---|---|
| RBIT | D LD nR CLK Q | inc0 oCALL nR CLK sk0 |
| RBIT | D LD nR CLK Q | inc1 oCALL nR CLK sk1 |
| RBIT | D LD nR CLK Q | inc2 oCALL nR CLK sk2 |
| RBIT | D LD nR CLK Q | inc3 oCALL nR CLK sk3 |
| RBIT | D LD nR CLK Q | inc4 oCALL nR CLK sk4 |
| RBIT | D LD nR CLK Q | inc5 oCALL nR CLK sk5 |
| RBIT | D LD nR CLK Q | inc6 oCALL nR CLK sk6 |
| RBIT | D LD nR CLK Q | inc7 oCALL nR CLK sk7 |
Step 10. The number from the command
ir11 = 1 — the number is taken from the command word (LD #5). Input A is din0–din7, the number from memory — it will appear in lesson 3; for now the din labels are not connected to anything, and the lesson's program takes only numbers.
| Part | Pin | Label |
|---|---|---|
| MUX2 | A B S Y | din0 ir0 ir11 bv0 |
| MUX2 | A B S Y | din1 ir1 ir11 bv1 |
| MUX2 | A B S Y | din2 ir2 ir11 bv2 |
| MUX2 | A B S Y | din3 ir3 ir11 bv3 |
| MUX2 | A B S Y | din4 ir4 ir11 bv4 |
| MUX2 | A B S Y | din5 ir5 ir11 bv5 |
| MUX2 | A B S Y | din6 ir6 ir11 bv6 |
| MUX2 | A B S Y | din7 ir7 ir11 bv7 |
Step 11. Display PC and lamps A
The display is the command address PC. The eight lamps are the bits of A (acc7 on the left): the lights run along them.
| Part | Pin | Label |
|---|---|---|
| Display | ⚙ | width 8, DEC, bus pc |
| LED | in | acc7 |
| LED | in | acc6 |
| LED | in | acc5 |
| LED | in | acc4 |
| LED | in | acc3 |
| LED | in | acc2 |
| LED | in | acc1 |
| LED | in | acc0 |
Step 12. The program in the ROM
⚙ on ROM → button ⬆ → file 02-lights.rom.json. Here it is in assembly:
; Lesson 2 — running lights: A in turn 1, 2, 4 … 128 and back; between the passes — a flash (a subroutine).; Only numbers (#) and jumps: there is no data memory yet (lesson 3).start: LD #1 LD #2 LD #4 LD #8 LD #16 LD #32 LD #64 LD #128 CALL flash ; flash — and back here LD #64 LD #32 LD #16 LD #8 LD #4 LD #2 CALL flash JMP start flash: LD #255 ; all lights LD #0 ; off RET ; return after the CALL2.4. Checking
| Clock | PC (display) | Command | A | Lamps acc7…acc0 | PC after | What you see |
|---|---|---|---|---|---|---|
| 1 | 0 | LD #1 | 1 | ○○○○○○○● | 1 | |
| 2 | 1 | LD #2 | 2 | ○○○○○○●○ | 2 | |
| 3 | 2 | LD #4 | 4 | ○○○○○●○○ | 3 | |
| 4 | 3 | LD #8 | 8 | ○○○○●○○○ | 4 | |
| 5 | 4 | LD #16 | 16 | ○○○●○○○○ | 5 | |
| 6 | 5 | LD #32 | 32 | ○○●○○○○○ | 6 | |
| 7 | 6 | LD #64 | 64 | ○●○○○○○○ | 7 | |
| 8 | 7 | LD #128 | 128 | ●○○○○○○○ | 8 | |
| 9 | 8 | CALL 17 | 128 | ●○○○○○○○ | 17 | CALL: remember 9, go to 17 |
| 10 | 17 | LD #255 | 255 | ●●●●●●●● | 18 | flash: all 8 lamps |
| 11 | 18 | LD #0 | 0 | ○○○○○○○○ | 19 | off |
| 12 | 19 | RET | 0 | ○○○○○○○○ | 9 | RET: back to 9 |
| 13 | 9 | LD #64 | 64 | ○●○○○○○○ | 10 | |
| 14 | 10 | LD #32 | 32 | ○○●○○○○○ | 11 | |
| 15 | 11 | LD #16 | 16 | ○○○●○○○○ | 12 | |
| 16 | 12 | LD #8 | 8 | ○○○○●○○○ | 13 | |
| 17 | 13 | LD #4 | 4 | ○○○○○●○○ | 14 | |
| 18 | 14 | LD #2 | 2 | ○○○○○○●○ | 15 | |
| 19 | 15 | CALL 17 | 2 | ○○○○○○●○ | 17 | CALL: remember 16, go to 17 |
| 20 | 17 | LD #255 | 255 | ●●●●●●●● | 18 | flash: all 8 lamps |
| 21 | 18 | LD #0 | 0 | ○○○○○○○○ | 19 | off |
| 22 | 19 | RET | 0 | ○○○○○○○○ | 16 | RET: back to 16 |
| 23 | 16 | JMP 0 | 0 | ○○○○○○○○ | 0 | JMP: round again |
| 24 | 0 | LD #1 | 1 | ○○○○○○○● | 1 |
First a reset: switch RST on, wait one or two clocks (display PC = 0), switch it off. Then the clock steps by itself: display PC shows which ROM command is running, the lamps show A after it.
- In the "Clock" row the
PCdisplay shows the address of the current command; after the clock it becomes "PC after". - The lights run left (1 → 128), all flash, run right, a flash — and again. Display
PC: 0 … 8, 17, 18, 19, 9 … 15, 17, 18, 19, 16, 0 — those are the jumpsCALL,RET,JMP. - While there is no program (the ROM is empty), all words = 0 =
LDa number from memory:A= 0 and the lamps are off — that is how it should be. - Display
Afrom lesson 1 also follows the lamps.
If it is not like this:
PCstays at 0 —RSTis on or there is noClock;PCjumps randomly — checkHA:Bof the first one ispc0, thenic2…ic7.- After 8 it does not go to 17 —
jmp:OR3withoCALL; after 19 it does not return to 9 — the stack (LD=oCALL) and the second row ofMUX2(S=oRET). - The lamps are off while
PCsteps —ir11onSof the numberMUX2s, or the program is not loaded.
Do not fold into a chip.
⚠ A common mistake. In the secondOR3(outputj2) the inputs are the commands themselves,oJNZ,oJC, instead oftJNZ,tJC. The lights run correctly — this program has no conditional jumps — butJNZandJCalways jump without looking at the flag. The mistake shows up in lesson 3. Check it with the program02-jumps-test.rom.json: correct — lamps1010 1010(170); all lit (255) — some jump is wrong.
Lesson 2 paste (Cmd+I)
First step 1 (delete the CLK button and the switches ir12–ir15, bv0–bv7).
# Lesson 2 — the program counter and ROM. First delete the CLK button and the switches ir12-ir15, bv0-bv7; then load 02-lights.rom.json into ROM.# ---------- HA ----------chip HA { portIn(A) portIn(B) portOut(S) portOut(C) XOR(A, B, S) AND(A, B, C)} # ---------- OR3 ----------chip OR3 { portIn(A) portIn(B) portIn(C) portOut(Y) OR(A, B, ab) OR(ab, C, Y)} clock{rt=2}(CLK) ram ROM{addrBits=16, dataBits=16, words=256}(A0=pc0, A1=pc1, A2=pc2, A3=pc3, A4=pc4, A5=pc5, A6=pc6, A7=pc7, A8=GND, A9=GND, A10=GND, A11=GND, A12=GND, A13=GND, A14=GND, A15=GND, WE=GND, CLK=GND, D0=GND, D1=GND, D2=GND, D3=GND, D4=GND, D5=GND, D6=GND, D7=GND, D8=GND, D9=GND, D10=GND, D11=GND, D12=GND, D13=GND, D14=GND, D15=GND, Q0=ir0, Q1=ir1, Q2=ir2, Q3=ir3, Q4=ir4, Q5=ir5, Q6=ir6, Q7=ir7, Q8=ir8, Q9=ir9, Q10=ir10, Q11=ir11, Q12=ir12, Q13=ir13, Q14=ir14, Q15=ir15) # ---------- PC + 1 ----------NOT(pc0, inc0)HA(A=pc1, B=pc0, S=inc1, C=ic2)HA(A=pc2, B=ic2, S=inc2, C=ic3)HA(A=pc3, B=ic3, S=inc3, C=ic4)HA(A=pc4, B=ic4, S=inc4, C=ic5)HA(A=pc5, B=ic5, S=inc5, C=ic6)HA(A=pc6, B=ic6, S=inc6, C=ic7)HA(A=pc7, B=ic7, S=inc7, C=ic8) NOT(fZ, nfZ)NOT(fC, nfC)AND(oJZ, fZ, tJZ)AND(oJNZ, nfZ, tJNZ)AND(oJC, fC, tJC)AND(oJNC, nfC, tJNC)OR3(A=oJMP, B=oCALL, C=tJZ, Y=j1)OR3(A=tJNZ, B=tJC, C=tJNC, Y=j2)OR(j1, j2, jmp) MUX2(A=inc0, B=ir0, S=jmp, Y=jn0)MUX2(A=inc1, B=ir1, S=jmp, Y=jn1)MUX2(A=inc2, B=ir2, S=jmp, Y=jn2)MUX2(A=inc3, B=ir3, S=jmp, Y=jn3)MUX2(A=inc4, B=ir4, S=jmp, Y=jn4)MUX2(A=inc5, B=ir5, S=jmp, Y=jn5)MUX2(A=inc6, B=ir6, S=jmp, Y=jn6)MUX2(A=inc7, B=ir7, S=jmp, Y=jn7)MUX2(A=jn0, B=sk0, S=oRET, Y=nx0)MUX2(A=jn1, B=sk1, S=oRET, Y=nx1)MUX2(A=jn2, B=sk2, S=oRET, Y=nx2)MUX2(A=jn3, B=sk3, S=oRET, Y=nx3)MUX2(A=jn4, B=sk4, S=oRET, Y=nx4)MUX2(A=jn5, B=sk5, S=oRET, Y=nx5)MUX2(A=jn6, B=sk6, S=oRET, Y=nx6)MUX2(A=jn7, B=sk7, S=oRET, Y=nx7)AND(nx0, nR, pd0)AND(nx1, nR, pd1)AND(nx2, nR, pd2)AND(nx3, nR, pd3)AND(nx4, nR, pd4)AND(nx5, nR, pd5)AND(nx6, nR, pd6)AND(nx7, nR, pd7)DFF(D=pd0, CLK=CLK, Q=pc0)DFF(D=pd1, CLK=CLK, Q=pc1)DFF(D=pd2, CLK=CLK, Q=pc2)DFF(D=pd3, CLK=CLK, Q=pc3)DFF(D=pd4, CLK=CLK, Q=pc4)DFF(D=pd5, CLK=CLK, Q=pc5)DFF(D=pd6, CLK=CLK, Q=pc6)DFF(D=pd7, CLK=CLK, Q=pc7) RBIT(D=inc0, LD=oCALL, nR=nR, CLK=CLK, Q=sk0)RBIT(D=inc1, LD=oCALL, nR=nR, CLK=CLK, Q=sk1)RBIT(D=inc2, LD=oCALL, nR=nR, CLK=CLK, Q=sk2)RBIT(D=inc3, LD=oCALL, nR=nR, CLK=CLK, Q=sk3)RBIT(D=inc4, LD=oCALL, nR=nR, CLK=CLK, Q=sk4)RBIT(D=inc5, LD=oCALL, nR=nR, CLK=CLK, Q=sk5)RBIT(D=inc6, LD=oCALL, nR=nR, CLK=CLK, Q=sk6)RBIT(D=inc7, LD=oCALL, nR=nR, CLK=CLK, Q=sk7) MUX2(A=din0, B=ir0, S=ir11, Y=bv0)MUX2(A=din1, B=ir1, S=ir11, Y=bv1)MUX2(A=din2, B=ir2, S=ir11, Y=bv2)MUX2(A=din3, B=ir3, S=ir11, Y=bv3)MUX2(A=din4, B=ir4, S=ir11, Y=bv4)MUX2(A=din5, B=ir5, S=ir11, Y=bv5)MUX2(A=din6, B=ir6, S=ir11, Y=bv6)MUX2(A=din7, B=ir7, S=ir11, Y=bv7) display{bits=8, base="DEC", busSource="pc"}()led(acc7)led(acc6)led(acc5)led(acc4)led(acc3)led(acc2)led(acc1)led(acc0)Stuck? Something doesn't work →