Счётчик команд (Program Counter)
Когда ты читаешь книгу, ты водишь пальцем по строчкам или используешь закладку, чтобы не забыть, на каком месте остановился. Процессору тоже нужна такая «закладка», ведь программа может состоять из тысяч инструкций, расположенных в памяти друг за другом.
Эту роль выполняет Счётчик команд (Program Counter, или просто PC). Это специальный регистр, который всегда хранит внутри себя адрес той ячейки памяти (в ПЗУ), где лежит следующая инструкция.
По умолчанию Счётчик команд работает очень просто: после каждого такта процессора он делает шаг вперёд (прибавляет к своему значению +1 или +2). Прочитал нулевую строчку — перешёл к первой, прочитал первую — перешёл ко второй. Но иногда программе нужно перескочить в другое место (например, если начался цикл). В этот момент процессор посылает Счётчику команд новый адрес, перезаписывая его значение, и чтение кода продолжается уже с совершенно нового места.
Из чего собирается PC
Счётчик команд — это знакомые блоки, соединённые в кольцо: Register8 хранит текущий адрес, ALU8 прибавляет к нему 1, а результат снова записывается в регистр. Конкретно: выход регистра Q подаётся на вход A АЛУ — это текущий адрес; на вход B АЛУ подаётся BusConstant со значением 1 — это инкремент. Выход АЛУ (Result) возвращается на вход Data регистра, так что на следующем такте регистр получит адрес+1. Тактовый сигнал Clock подключён ко входу Clock регистра — каждый фронт двигает адрес дальше.
Получается цикл «регистр → АЛУ → регистр» — тот же паттерн, что и в счётчиках, которые ты уже встречал, только вместо кнопки «+1» работает настоящая схема сложения. Вход WE регистра можно оставить неподключённым: по умолчанию запись разрешена.
PC указывает на следующую инструкцию
Зачем вообще увеличивать адрес? Память команд (ROM) хранит инструкции друг за другом, а PC показывает, какую ячейку ROM читать прямо сейчас. Процессор берёт инструкцию по адресу из PC, исполняет её — а PC тем временем уже подрос и указывает на следующую. Так программа выполняется шаг за шагом, автоматически.
В нашем компьютере память команд и память данных разделены — это Harvard-архитектура, о которой мы писали в статье «Harvard-архитектура». А прочитанный байт инструкции попадает в дешифратор: как он превращает код в управляющие сигналы, описано в статье «Дешифратор инструкций».
Трассировка: ADD 10, STA 20, HLT
Проследим за счётчиком команд при выполнении простой программы: ADD 10, STA 20, HLT.
| Такт | PC | Что делает процессор |
|---|---|---|
| 0 | 0 | Читает из ROM[0] инструкцию ADD 10 и исполняет её |
| 1 | 1 | Читает из ROM[1] инструкцию STA 20 и исполняет её |
| 2 | 2 | Читает из ROM[2] инструкцию HLT и останавливается |
Обрати внимание на третий такт: HLT — это инструкция остановки. После неё выполнение прекращается, и PC больше не растёт.
А если бы на месте HLT стоял JMP 0? После его исполнения процессор принудительно записал бы в PC значение 0, и ADD 10 выполнилась бы снова — получился бы бесконечный цикл. Счётчик команд и инструкции перехода — это фундамент любых циклов в программах.
Двухбайтовые инструкции: шаг +2
В байтовом формате каждая инструкция занимает один байт, поэтому PC шагает на +1. Но в словесном (word) формате инструкция состоит из двух байтов: байт 0 — опкод, байт 1 — операнд (число от 0 до 255). Если PC продолжит шагать на +1, он «сядет» на середину инструкции — на байт операнда, и процессор примет данные за команду.
Поэтому у готового чипа ProgramCounter есть вход Inc (порт 4) — он определяет величину шага. По умолчанию, если вход не подключён, Inc = +1. А на уровне 2.19 к этому входу подключается BusConstant со значением 2 — и PC начинает шагать 0, 2, 4, 6…
Числовой пример: два формата
Представь программу из двух инструкций в словесном формате: ADD 10 и HLT.
ROM[0] = опкод ADD (0x10), ROM[1] = операнд 10, ROM[2] = опкод HLT, ROM[3] = операнд (не используется).
1. PC=0: процессор читает байт ROM[0] — опкод ADD, а затем следующий байт ROM[1] — операнд 10. Исполняет ADD 10.
2. PC шагает на +2: теперь PC=2. Читает ROM[2] — опкод HLT. Стоп.
В байтовом формате те же две инструкции заняли бы ROM[0] и ROM[1], и PC шагал бы на +1: 0, 1. Разница лишь в цене: шаг PC должен совпадать с размером инструкции, иначе процессор прочитает операнд как команду.
Частые ошибки
- Не подать результат АЛУ обратно в регистр: без петли обратной связи адрес расти не будет.
- Подключить Clock к входу A АЛУ вместо тактового входа регистра — тактировать нужно регистр.
- Забыть про вход Inc на уровне 2.19: без константы 2 PC будет читать середины инструкций.
Резюме
1. PC — это регистр, который хранит адрес следующей инструкции.
2. Он собирается из Register8 + ALU8 + константы 1 по схеме «регистр → АЛУ → регистр».
3. Каждый такт адрес увеличивается на величину шага: на +1 в байтовом формате.
4. Инструкция JMP перезаписывает PC новым адресом — так работают циклы и ветвления.
5. В word-формате инструкция занимает два байта, поэтому на вход Inc подаётся константа 2, и PC шагает 0, 2, 4, 6…
6. Если шаг не совпадает с размером инструкции, процессор читает мусор.
На уровне 1.16 тебе предстоит собрать счётчик команд из регистра, сумматора и константы 1, а на уровне 2.19 ты подключишь константу 2 ко входу Inc, чтобы PC шагал по двухбайтовым инструкциям.