Lesson 2. The program counter and ROM: lights by the program

In pictures

After 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.
PCprogram counterROM256 words × 16 bitsaddrcodeir15–12#ir11number / addressir7–0the instruction wordPC + 1stackCALL puts, RET takesnext PC+1 · jump · returnjump addresson the CLK edge — into PC, and around again
The processor walks through the program by itself: PC selects a word in the ROM, the word says what to do. The next address is PC + 1, or the address from the instruction (a jump), or the address from the stack (return from a subroutine).

2.2. The idea: jumps and a subroutine

  • JMP puts the address from the command into PC — the program goes round;
  • JZ/JNZ, JC/JNC jump only if flag Z or C is as required;
  • CALL, before jumping, remembers the address of the next command in the stack, RET returns 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.

ElementAction
Button whose output = CLKdelete
Input whose output = ir12delete
Input whose output = ir13delete
Input whose output = ir14delete
Input whose output = ir15delete
Input whose output = bv0delete
Input whose output = bv1delete
Input whose output = bv2delete
Input whose output = bv3delete
Input whose output = bv4delete
Input whose output = bv5delete
Input whose output = bv6delete
Input whose output = bv7delete

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.

PartPinLabel
Input portoutA
Input portoutB
Output portinS
Output portinC
XORI0
I1
O0
A
B
S
ANDI0
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.

PartPinLabel
Input portoutA
Input portoutB
Input portoutC
Output portinY
ORI0
I1
O0
A
B
ab
ORI0
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.

PartPinLabel
Clock (2Hz)outCLK

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 (⚙):

MemorySizeDataAddressCLR
ROM256 words16b data16b addroff

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).

PartPinLabel
Memory (RAM) ROMA0
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.

PartPinLabel
NOTI0
O0
pc0
inc0
HAA
B
S
C
pc1
pc0
inc1
ic2
HAA
B
S
C
pc2
ic2
inc2
ic3
HAA
B
S
C
pc3
ic3
inc3
ic4
HAA
B
S
C
pc4
ic4
inc4
ic5
HAA
B
S
C
pc5
ic5
inc5
ic6
HAA
B
S
C
pc6
ic6
inc6
ic7
HAA
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.

PartPinLabel
NOTI0
O0
fZ
nfZ
NOTI0
O0
fC
nfC
ANDI0
I1
O0
oJZ
fZ
tJZ
ANDI0
I1
O0
oJNZ
nfZ
tJNZ
ANDI0
I1
O0
oJC
fC
tJC
ANDI0
I1
O0
oJNC
nfC
tJNC
OR3A
B
C
Y
oJMP
oCALL
tJZ
j1
OR3A
B
C
Y
tJNZ
tJC
tJNC
j2
ORI0
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.

PartPinLabel
MUX2A
B
S
Y
inc0
ir0
jmp
jn0
MUX2A
B
S
Y
inc1
ir1
jmp
jn1
MUX2A
B
S
Y
inc2
ir2
jmp
jn2
MUX2A
B
S
Y
inc3
ir3
jmp
jn3
MUX2A
B
S
Y
inc4
ir4
jmp
jn4
MUX2A
B
S
Y
inc5
ir5
jmp
jn5
MUX2A
B
S
Y
inc6
ir6
jmp
jn6
MUX2A
B
S
Y
inc7
ir7
jmp
jn7
MUX2A
B
S
Y
jn0
sk0
oRET
nx0
MUX2A
B
S
Y
jn1
sk1
oRET
nx1
MUX2A
B
S
Y
jn2
sk2
oRET
nx2
MUX2A
B
S
Y
jn3
sk3
oRET
nx3
MUX2A
B
S
Y
jn4
sk4
oRET
nx4
MUX2A
B
S
Y
jn5
sk5
oRET
nx5
MUX2A
B
S
Y
jn6
sk6
oRET
nx6
MUX2A
B
S
Y
jn7
sk7
oRET
nx7
ANDI0
I1
O0
nx0
nR
pd0
ANDI0
I1
O0
nx1
nR
pd1
ANDI0
I1
O0
nx2
nR
pd2
ANDI0
I1
O0
nx3
nR
pd3
ANDI0
I1
O0
nx4
nR
pd4
ANDI0
I1
O0
nx5
nR
pd5
ANDI0
I1
O0
nx6
nR
pd6
ANDI0
I1
O0
nx7
nR
pd7
DFFD
CLK
Q
pd0
CLK
pc0
DFFD
CLK
Q
pd1
CLK
pc1
DFFD
CLK
Q
pd2
CLK
pc2
DFFD
CLK
Q
pd3
CLK
pc3
DFFD
CLK
Q
pd4
CLK
pc4
DFFD
CLK
Q
pd5
CLK
pc5
DFFD
CLK
Q
pd6
CLK
pc6
DFFD
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.

PartPinLabel
RBITD
LD
nR
CLK
Q
inc0
oCALL
nR
CLK
sk0
RBITD
LD
nR
CLK
Q
inc1
oCALL
nR
CLK
sk1
RBITD
LD
nR
CLK
Q
inc2
oCALL
nR
CLK
sk2
RBITD
LD
nR
CLK
Q
inc3
oCALL
nR
CLK
sk3
RBITD
LD
nR
CLK
Q
inc4
oCALL
nR
CLK
sk4
RBITD
LD
nR
CLK
Q
inc5
oCALL
nR
CLK
sk5
RBITD
LD
nR
CLK
Q
inc6
oCALL
nR
CLK
sk6
RBITD
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.

PartPinLabel
MUX2A
B
S
Y
din0
ir0
ir11
bv0
MUX2A
B
S
Y
din1
ir1
ir11
bv1
MUX2A
B
S
Y
din2
ir2
ir11
bv2
MUX2A
B
S
Y
din3
ir3
ir11
bv3
MUX2A
B
S
Y
din4
ir4
ir11
bv4
MUX2A
B
S
Y
din5
ir5
ir11
bv5
MUX2A
B
S
Y
din6
ir6
ir11
bv6
MUX2A
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.

PartPinLabel
Display⚙width 8, DEC, bus pc
LEDinacc7
LEDinacc6
LEDinacc5
LEDinacc4
LEDinacc3
LEDinacc2
LEDinacc1
LEDinacc0

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 CALL

2.4. Checking

ClockPC (display)CommandALamps acc7…acc0PC afterWhat you see
10LD #11○○○○○○○●1
21LD #22○○○○○○●○2
32LD #44○○○○○●○○3
43LD #88○○○○●○○○4
54LD #1616○○○●○○○○5
65LD #3232○○●○○○○○6
76LD #6464○●○○○○○○7
87LD #128128●○○○○○○○8
98CALL 17128●○○○○○○○17CALL: remember 9, go to 17
1017LD #255255●●●●●●●●18flash: all 8 lamps
1118LD #00○○○○○○○○19off
1219RET0○○○○○○○○9RET: back to 9
139LD #6464○●○○○○○○10
1410LD #3232○○●○○○○○11
1511LD #1616○○○●○○○○12
1612LD #88○○○○●○○○13
1713LD #44○○○○○●○○14
1814LD #22○○○○○○●○15
1915CALL 172○○○○○○●○17CALL: remember 16, go to 17
2017LD #255255●●●●●●●●18flash: all 8 lamps
2118LD #00○○○○○○○○19off
2219RET0○○○○○○○○16RET: back to 16
2316JMP 00○○○○○○○○0JMP: round again
240LD #11○○○○○○○●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 PC display 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 jumps CALL, RET, JMP.
  • While there is no program (the ROM is empty), all words = 0 = LD a number from memory: A = 0 and the lamps are off — that is how it should be.
  • Display A from lesson 1 also follows the lamps.

If it is not like this:

  • PC stays at 0 — RST is on or there is no Clock; PC jumps randomly — check HA: B of the first one is pc0, then ic2…ic7.
  • After 8 it does not go to 17 — jmp: OR3 with oCALL; after 19 it does not return to 9 — the stack (LD = oCALL) and the second row of MUX2 (S = oRET).
  • The lamps are off while PC steps — ir11 on S of the number MUX2s, or the program is not loaded.

Do not fold into a chip.

⚠ A common mistake. In the second OR3 (output j2) the inputs are the commands themselves, oJNZ, oJC, instead of tJNZ, tJC. The lights run correctly — this program has no conditional jumps — but JNZ and JC always jump without looking at the flag. The mistake shows up in lesson 3. Check it with the program 02-jumps-test.rom.json: correct — lamps 1010 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)

¿No te sale? Algo no funciona →