Об этом блоге

Об этом блоге

Добра тебе, читатель.
Это запасной аэродром Великого Позитроника.
А основная писанина — в тележеньке.

четверг, 28 февраля 2013 г.

Пилим восьмибитный процессор. Часть третья: ядро и блок регистров.

Эта часть будет очень короткой, не смотря на то, что должна описывать самую сложную часть процессора. Дело в том, что как раз на проектировании ядра я остановился.

Что такое ядро, и чем оно должно заниматься? Чёткого ответа на оба вопроса нет, но обобщённо - это именно та часть схемы, которая занимается выполнением инструкций. Ему на вход подали команду - он отдал результат на выход.
Со входом на данный момент более-менее порядок: контроллер памяти успешно читает и парсит команды, подавая их на вход ядру. Что дальше?
Взглянем на набросок схемы, которым представлено текущее видение ядра:


Клик для увеличения
У процессора четыре входа: OPCODE - на неё подаётся опкод команды (который, напомню, состоит из команды и указателя типов данных переданных параметров), PARAM1 и PARAM2 - соответственно, первого и второго параметра команды и execute - сигнальной линии выполнения. Когда на входе execute ноль, то остальные контакты отсоединяются, и процессор ничего не делает. Когда на этот вход подаётся сигнал, процессор должен выполнить инструкцию.
Для выполнения нужно проделать следующий алгоритм:
1) Определить команду.
2) Определить количество параметров команды.
3) Определить тип каждого параметра.
4) Определить допустимость параметров.
5) Получить данные по параметрам.
6) Выполнить инструкцию над полученными данными.
Плюс, на каждом этапе нужно проверять корректность всего и вся. Самое очевидное - четвёртый пункт, который должен, скажем, отсечь команду MOV 1,2 (перемещение числа в число).
Часть пунктов этого алгоритма уже решена, или решается достаточно легко. Так количество параметров определяет схема Operand counter, мы можем поставить её в ядро, или же брать уже полученное значение из парсера команд (оба решения, внезапно, правильные).
Определить же саму команду призвана схема Opcode selector (обозначена, как OS), она очень простая, и похожа по устройству и действию на счётчик, что я делал самым первым:
Определение типа параметров тоже элементарно, и схоже по устройству (схема Operand selector, обозначена OpS):
Не вижу смысла разбирать эти схемы подробно, их строение очевидно.
А вот дальше начинается интересное. В зависимости от типа параметров одна и та же команда может отрабатывать совершенно по разному. Приведу примеры, сделав допущение, что на данном моменте ошибочные инструкции, вроде вышеупомянутой MOV 1,2 отсеяны:
- Команда работает с регистрами (пишет в регистр, читает в регистр, или и то и другое), допустим это команда XOR A,B. Процессор должен взять значение регистра A, значение регистра B, выполнить над ними XOR, записать результат обратно в A. Четыре действия.
- Команда работает со стеком, допустим XOR S,10h. Взять значение стека, сделать XOR, записать в стек. Три действия.
- Команда работает с памятью (это самое интересное), допустим XOR [10h],[0Fh]. Считать значение по адресу 10h, считать значение по адресу 0Аh, выполнить XOR, записать значение по адресу 10h. Снова четыре действия, которые, между прочим, совсем не атомарны - контроллеру памяти нужно перейти по адресам, что займёт три такта (ладно, два, если оптимизировать инструкцию и читать параметры в обратном порядке).
А ещё есть комбинации этих параметров - память и стек, регистр и константа, стек и регистр...
Задача не выглядит сложной: бери значения, вписывай в буфер, выполняй операцию, пиши обратно. Но: всё это потерянные такты процессора, а значит - потерянное быстродействие. Что делать? Работать со значениями напрямую, без буферов.
Получать прямые значения, подавая их на обработчик команд будет схема Data selector (помечена, как DS). Она ещё не готова, поэтому её внутренности я показывать не буду (там четыре транзистора и всё), но опишу принцип действия.
DS подключена ко всем подустройствам ввода-вывода, на вход её подаются параметры команд и типы этих параметров. В зависимости от типа, DS переключается между подустройствами, подавая им номера, адреса, или чего ещё они требуют для возврата значения. Возвращённое значение отдаётся в DS, которая тут же возвращает его на свой выход. Вся операция занимает полтакта (за исключением моментов, когда подустройство не может сразу вернуть данные). Каждый Data selector ставится за входами параметров, таким образом производя их преобразование в абсолютные значения.
В чём подвох?
Возьмём, к примеру, регистры и команду MOV A,B. Для её выполнения нужно получить данные сразу по регистрам A и B, и тут же изменить значение A (поскольку команда, очевидно, будет выполнена моментально). Возникает что-то наподобие коллизии, которая, в лучшем случае, решается буферизацией значений, а, значит потерей тактов.
Впрочем, обойти этот косяк можно проще: нужно только спроектировать подустройства так, чтобы они умели отдавать сразу два значения. Регистр - значение двух регистров, память - значение двух адресов (это вполне решаемая задача, и очень интересная, я вернусь к ней в конце статьи), стек... ну, стек это отдельная песня.
На данный момент именно так у меня спроектирован блок регистров. Он состоит из двух схем: Registry I/O и Registry I, они так и обозначены. Первая умеет как читать значения заданного регистра, так и писать, вторая - умеет только читать, но действуют они независимо друг от друга. Посмотрим на их устройство.
Registry I/O:

Клик для увеличения
Registry I:

Клик для увеличения
Вся разница в том, что у Registry I отсутствует блок изменения регистров, поэтому разберу только устройство Registry I/O.
С входом Read Selector всё понятно - там порядковый номер регистра, значение которого надо выводить на выход Value. Схема Registry Selector (RS), в зависимости от этого номера подаёт разрешающий сигнал на соответствующую ногу, подключённую к выходу того или иного регистра:

Клик для увеличения
С записью интереснее, за это отвечают аж четыре входа:
- Store: значение, которое надо писать в регистр.
- Write Selector: порядковый номер регистра, в который это значение надо писать (работает аналогично Read Selector).
- Write: флаг, разрешающий запись (иначе значения регистров будут постоянно обновляться, хотим мы этого, или нет).
- Такт: флаг, переключение которого приведёт к записи значения в регистр.
Таким образом, схема позволяет одновременно читать значение из любого регистра и писать значение в любой регистр - то, что и требуется.
Дополнительно данные с каждого регистра подаются на отдельный выход, уходящий в схему Registry I, которая, в свою очередь, тоже не терминирует выходы, таким образом, позволяя подключать параллельно несколько копий схем чтения (или просто датчиков, как это сделано у меня). Почему не сделать вторую схему пишущей, чтобы и писать сразу можно было в два регистра? Потому что а) придётся решать коллизии в ситуации, когда запись на обеих схемах производится в один и тот же регистр б) это всё равно не нужно, т.к. запись в регистр - атомарная операция, и может быть произведена только одна такая операция в один момент времени.
Вот и всё, собственно. Напоследок несколько мыслей:
Можно заметить, что у ядра свой собственный тактовый генератор, и он может работать с собственной частотой (обычно ядра работают быстрее периферии). В этом процессоре это ничего не даёт, но именно на этом принципе можно реализовать очень много интересных вещей. Например, пока контроллер памяти получает очередной набор инструкций (что занимает у него от 1 до 3 собственных тактов), ядро может выполнять какие-то другие операции. Или же можно приделать ядру немножко своей памяти, работающей на его частоте, и в работать через неё - это и называется кешем первого уровня. Этим варианты не ограничиваются, но дальше уже идёт довольно высокий пилотаж.
Как получить из памяти значение сразу по двум адресам? Один из способов я показал на примере блока регистров - а ОЗУ - это тот же самый блок регистров. Второй способ - блок предсказаний: можно сделать схему, которая предполагает, что оператор команды - это всегда адрес в памяти, и тут же буферизует его, пока процессор выполняет предыдущую команду. Но блоки предсказаний, используемые в процессорах - это пилотаж ещё более высокий. Нам бы для начала разобраться с куда более приземлёнными вещами =).
Пока что всё. Продолжение, надеюсь, следует - как только на него появится время.

Комментариев нет: