Урок 2. Счётчик команд и ПЗУ: огоньки по программе

В картинках

После этого урока процессор сам шагает по программе: огонёк бежит по лампам туда и обратно, а между проходами все лампы вспыхивают. Тумблеры кода и числа больше не нужны — их заменяет ПЗУ.


2.1. Идея: программа — это список команд

Команды лежат в ПЗУ одна за другой: 256 слов по 16 бит. Номер текущей помнит регистр PC — счётчик команд. Каждый такт PC прибавляет 1, и процессор берёт следующую команду. Слово команды:

  • ir12–ir15 — код операции (тот же, что был на тумблерах);
  • ir11 — «число»: 1 — число в самой команде (LD #5), 0 — из памяти (урок 3);
  • ir0–ir7 — само число или адрес перехода.
PCсчётчик командПЗУ256 слов × 16 битадрескодir15–12#ir11число / адресir7–0слово командыPC + 1стекCALL кладёт, RET берётследующий PC+1 · переход · возвратадрес переходапо фронту CLK — в PC, и всё сначала
Процессор сам шагает по программе: PC выбирает слово в ПЗУ, слово говорит, что делать. Следующий адрес — PC + 1, или адрес из команды (переход), или адрес из стека (возврат из подпрограммы).

2.2. Идея: переходы и подпрограмма

  • JMP кладёт в PC адрес из команды — программа идёт по кругу;
  • JZ/JNZ, JC/JNC переходят, только если флаг Z или C такой, как надо;
  • CALL перед переходом запоминает адрес следующей команды в стеке, RET возвращается туда.

Стек у нас — одна ячейка: подпрограмма не может вызвать другую. Карте этого хватит.

2.3. Сборка

Тот же проект, что в уроке 1.

Шаг 1. Убрать пульт урока 1

Код и число теперь даёт программа: код — ПЗУ (ir12–ir15), число — MUX2 из слова команды (bv0–bv7). Такт — генератор. Две детали на одной метке — закоротка, поэтому сначала удалить. Метки возле них можно оставить: те же имена появятся на ногах новых деталей. Тумблер RST остаётся.

ЭлементДействие
Button, выход которого = CLKудалить
Input, выход которого = ir12удалить
Input, выход которого = ir13удалить
Input, выход которого = ir14удалить
Input, выход которого = ir15удалить
Input, выход которого = bv0удалить
Input, выход которого = bv1удалить
Input, выход которого = bv2удалить
Input, выход которого = bv3удалить
Input, выход которого = bv4удалить
Input, выход которого = bv5удалить
Input, выход которого = bv6удалить
Input, выход которого = bv7удалить

Шаг 2. Чип HA — полусумматор: сумма и перенос двух бит

Если HA уже есть в библиотеке (из «Как считает процессор» или HB-04) — шаг пропусти.

ДетальНогаМетка
Порт входавыходA
Порт входавыходB
Порт выходавходS
Порт выходавходC
XORI0
I1
O0
A
B
S
ANDI0
I1
O0
A
B
C

Ctrl+G → имя HA. Ноги: входы A, B; выходы S, C.

Шаг 3. Чип OR3 — ИЛИ на 3 входа

Если OR3 уже есть в библиотеке (из «Часов и игры») — шаг пропусти.

ДетальНогаМетка
Порт входавыходA
Порт входавыходB
Порт входавыходC
Порт выходавходY
ORI0
I1
O0
A
B
ab
ORI0
I1
O0
ab
C
Y

Ctrl+G → имя OR3. Ноги: входы A, B, C; выходы Y.

Шаг 4. Такт

Clock из палитры, клик по корпусу — 2Hz: две команды в секунду, огоньки видно глазом.

ДетальНогаМетка
Clock (2Hz)выходCLK

Шаг 5. ПЗУ программы

ПЗУ — память, где лежит программа: 256 слов по 16 бит, одно слово — одна команда. На адрес приходит PC, на выходе сразу — слово ir0–ir15: ir12–ir15 код, ir11 «число», ir0–ir7 число или адрес перехода.

Memory (RAM) из палитры, в редакторе памяти (⚙):

ПамятьЁмкостьДанныеАдресCLR
ROM256 words16b data16b addrвыключен

Ёмкость: клик по 16 words ▸ → 256 → Enter. CLR на ПЗУ не ставить — он стёр бы программу. Ноги A8–A15, WE, CLK и все D0–D15 — на GND (в ПЗУ не пишут).

ДетальНогаМетка
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

Шаг 6. PC + 1

Следующий адрес — плюс один: бит 0 переворачивается, дальше перенос бежит по полусумматорам, пока единицы.

ДетальНогаМетка
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

Шаг 7. Когда переходить

jmp = 1 — следующий адрес берётся из команды: JMP и CALL всегда, JZ/JNZ — по флагу Z, JC/JNC — по C.

ДетальНогаМетка
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

Шаг 8. Следующий PC и регистр PC

Выбор: PC + 1 или адрес из команды (jmp), потом — адрес возврата из стека (RET). AND с nR — сброс в 0. PC пишется каждый такт — без LD, просто триггеры.

ДетальНогаМетка
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

Шаг 9. Стек: одна ячейка

CALL запоминает адрес следующей команды (PC + 1), RET возвращается туда. Ячейка одна: подпрограмма не может вызвать другую.

ДетальНогаМетка
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

Шаг 10. Число из команды

ir11 = 1 — число берётся из слова команды (LD #5). Вход A — din0–din7, число из памяти — появится в уроке 3; пока метки din ни к чему не подключены, и программа урока берёт только числа.

ДетальНогаМетка
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

Шаг 11. Табло PC и лампы A

Табло — адрес команды PC. Восемь ламп — биты A (слева acc7): по ним и бегут огоньки.

ДетальНогаМетка
Display⚙ширина 8, DEC, шина pc
LEDвходacc7
LEDвходacc6
LEDвходacc5
LEDвходacc4
LEDвходacc3
LEDвходacc2
LEDвходacc1
LEDвходacc0

Шаг 12. Программа в ПЗУ

⚙ у ROM → кнопка ⬆ → файл 02-lights.rom.json. Вот она на ассемблере:

; Урок 2 — бегущие огоньки: A по очереди 1, 2, 4 … 128 и обратно; между проходами — вспышка (подпрограмма).; Только числа (#) и переходы: памяти данных ещё нет (урок 3).start:  LD #1        LD #2        LD #4        LD #8        LD #16        LD #32        LD #64        LD #128        CALL flash       ; вспышка — и обратно сюда        LD #64        LD #32        LD #16        LD #8        LD #4        LD #2        CALL flash        JMP start flash:  LD #255          ; все огни        LD #0            ; погасить        RET              ; вернуться за CALL

2.4. Проверка

ТактPC (табло)КомандаAЛампы acc7…acc0PC послеЧто видно
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: запомнить 9, перейти на 17
1017LD #255255●●●●●●●●18вспышка: все 8 ламп
1118LD #00○○○○○○○○19погасить
1219RET0○○○○○○○○9RET: обратно на 9
139LD #6464○●○○○○○○10
1410LD #3232○○●○○○○○11
1511LD #1616○○○●○○○○12
1612LD #88○○○○●○○○13
1713LD #44○○○○○●○○14
1814LD #22○○○○○○●○15
1915CALL 172○○○○○○●○17CALL: запомнить 16, перейти на 17
2017LD #255255●●●●●●●●18вспышка: все 8 ламп
2118LD #00○○○○○○○○19погасить
2219RET0○○○○○○○○16RET: обратно на 16
2316JMP 00○○○○○○○○0JMP: по кругу
240LD #11○○○○○○○●1

Сначала сброс: включи RST, дождись одного-двух тактов (табло PC = 0), выключи. Дальше генератор шагает сам: табло PC показывает, какую команду ПЗУ выполняет, лампы — A после неё.

  • В строке «Такт» табло PC показывает адрес текущей команды; после такта он станет «PC после».
  • Огоньки бегут влево (1 → 128), вспыхивают все, бегут вправо, вспышка — и снова. Табло PC: 0 … 8, 17, 18, 19, 9 … 15, 17, 18, 19, 16, 0 — это переходы CALL, RET, JMP.
  • Пока программы нет (ПЗУ пустое), все слова = 0 = LD числа из памяти: A = 0 и лампы не горят — так и должно быть.
  • Табло A урока 1 тоже идёт вместе с лампами.

Если не так:

  • PC стоит на 0 — RST включён или нет Clock; PC прыгает как попало — проверь HA: B первого — pc0, дальше ic2…ic7.
  • После 8 не уходит на 17 — jmp: OR3 с oCALL; после 19 не возвращается на 9 — стек (LD = oCALL) и второй ряд MUX2 (S = oRET).
  • Лампы не горят, а PC шагает — ir11 на S у MUX2 числа, программа не загружена.

В чип не сворачивать.

⚠ Частая ошибка. Во втором OR3 (выход j2) на входах стоят сами команды oJNZ, oJC вместо tJNZ, tJC. Огоньки бегут правильно — в этой программе нет условных переходов, — но JNZ и JC прыгают всегда, не глядя на флаг. Ошибка вылезет в уроке 3. Проверь программой 02-jumps-test.rom.json: верно — лампы 1010 1010 (170), горят все (255) — какой-то переход неверный.

Вставка урока 2 (Cmd+I)

Сначала шаг 1 (удалить кнопку CLK и тумблеры ir12–ir15, bv0–bv7).

# Урок 2 — счётчик команд и ПЗУ: огоньки по программе.# Вставка: Cmd+I поверх урока 1, после шага 1 (удалить кнопку CLK и тумблеры ir12–ir15, bv0–bv7).# Потом: ПЗУ — загрузить программу lessons/02-lights.rom.json (⬆ в редакторе памяти 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) # ---------- Следующий PC и регистр PC ----------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) # ---------- Табло PC и лампы A ----------display{bits=8, base="DEC", busSource="pc"}()led(acc7)led(acc6)led(acc5)led(acc4)led(acc3)led(acc2)led(acc1)led(acc0)

Не получается? Что-то не работает →