Урок 2. Счётчик команд и ПЗУ: огоньки по программе
В картинкахПосле этого урока процессор сам шагает по программе: огонёк бежит по лампам туда и обратно, а между проходами все лампы вспыхивают. Тумблеры кода и числа больше не нужны — их заменяет ПЗУ.
2.1. Идея: программа — это список команд
Команды лежат в ПЗУ одна за другой: 256 слов по 16 бит. Номер текущей помнит регистр PC — счётчик команд. Каждый такт PC прибавляет 1, и процессор берёт следующую команду. Слово команды:
ir12–ir15— код операции (тот же, что был на тумблерах);ir11— «число»: 1 — число в самой команде (LD #5), 0 — из памяти (урок 3);ir0–ir7— само число или адрес перехода.
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 |
| XOR | I0 I1 O0 | A B S |
| AND | I0 I1 O0 | A B C |
Ctrl+G → имя HA. Ноги: входы A, B; выходы S, C.
Шаг 3. Чип OR3 — ИЛИ на 3 входа
Если OR3 уже есть в библиотеке (из «Часов и игры») — шаг пропусти.
| Деталь | Нога | Метка |
|---|---|---|
| Порт входа | выход | A |
| Порт входа | выход | B |
| Порт входа | выход | C |
| Порт выхода | вход | Y |
| OR | I0 I1 O0 | A B ab |
| OR | I0 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 |
|---|---|---|---|---|
| ROM | 256 words | 16b data | 16b addr | выключен |
Ёмкость: клик по 16 words ▸ → 256 → Enter. CLR на ПЗУ не ставить — он стёр бы программу. Ноги A8–A15, WE, CLK и все D0–D15 — на GND (в ПЗУ не пишут).
| Деталь | Нога | Метка |
|---|---|---|
| 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 |
Шаг 6. PC + 1
Следующий адрес — плюс один: бит 0 переворачивается, дальше перенос бежит по полусумматорам, пока единицы.
| Деталь | Нога | Метка |
|---|---|---|
| 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 |
Шаг 7. Когда переходить
jmp = 1 — следующий адрес берётся из команды: JMP и CALL всегда, JZ/JNZ — по флагу Z, JC/JNC — по C.
| Деталь | Нога | Метка |
|---|---|---|
| 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 |
Шаг 8. Следующий PC и регистр PC
Выбор: PC + 1 или адрес из команды (jmp), потом — адрес возврата из стека (RET). AND с nR — сброс в 0. PC пишется каждый такт — без LD, просто триггеры.
| Деталь | Нога | Метка |
|---|---|---|
| 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 |
Шаг 9. Стек: одна ячейка
CALL запоминает адрес следующей команды (PC + 1), RET возвращается туда. Ячейка одна: подпрограмма не может вызвать другую.
| Деталь | Нога | Метка |
|---|---|---|
| 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 |
Шаг 10. Число из команды
ir11 = 1 — число берётся из слова команды (LD #5). Вход A — din0–din7, число из памяти — появится в уроке 3; пока метки din ни к чему не подключены, и программа урока берёт только числа.
| Деталь | Нога | Метка |
|---|---|---|
| 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 |
Шаг 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 ; вернуться за CALL2.4. Проверка
| Такт | PC (табло) | Команда | A | Лампы acc7…acc0 | PC после | Что видно |
|---|---|---|---|---|---|---|
| 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: запомнить 9, перейти на 17 |
| 10 | 17 | LD #255 | 255 | ●●●●●●●● | 18 | вспышка: все 8 ламп |
| 11 | 18 | LD #0 | 0 | ○○○○○○○○ | 19 | погасить |
| 12 | 19 | RET | 0 | ○○○○○○○○ | 9 | RET: обратно на 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: запомнить 16, перейти на 17 |
| 20 | 17 | LD #255 | 255 | ●●●●●●●● | 18 | вспышка: все 8 ламп |
| 21 | 18 | LD #0 | 0 | ○○○○○○○○ | 19 | погасить |
| 22 | 19 | RET | 0 | ○○○○○○○○ | 16 | RET: обратно на 16 |
| 23 | 16 | JMP 0 | 0 | ○○○○○○○○ | 0 | JMP: по кругу |
| 24 | 0 | LD #1 | 1 | ○○○○○○○● | 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)Не получается? Что-то не работает →