Микропроцессоры: Попытки разработки собственной архитектуры
Добавлено: Чт янв 12, 2017 07:08:47
Вариация на тему каким бы мог быть процессор 1975 года, если бы …
Eщё будучи школьником, когда разобрался более-менее в основах работы микропроцессора РАДИО-86РК и открыл, что все сигналы, за исключением выводов частота/ожидание/прерывание/сброс, генерирует сам процессор по мере надобности, начались предприниматься попытки разработки собственного идеального процессора. Конечно, опираясь на опыт работы с ИК80, а позднее - и Z80…
В первую очередь, всегда мною предпринималась попытка разработать идеальную таблицу команд, не требующей существенных усилий для запоминания соответствий машинного кода к мнемонике и самой операции. Что стало моей идеалогией. Так, например, меня возмутило, что в i8086 коду 00 соответствует операция ADD, что сбивает с толку, если память обнулена. В поисках стабильности и защиты от сбоев, коду 00 я навсегда в своих набросках присвоил операцию останова HLT. И это вполне здравое решение.
Когда начал практически разрабатывать эмулятор, пришёл к выводу, что команды условного возврата из подпрограммы - нужные, но излишние в таблице. Система команд x86 изобилует реликтовым хламом, который зря захламляет всё и отжирает драгоценный байт-код: AAA, AAD, AAM, AAS. Команды ENTER/LEAVE используются при входе в подпрограмму и возврате из неё, т.е. не достаточно часто. Честно говоря, было много вариантов с перестановкой позиций команд i8080 (не Z80) в поисках оптимального. Думаете, от перестановки кодового соответствия практически ничего не меняется? Это - не так и после немногих раздумий я присвоил операции RET код FF, чтобы получить трюковую операцию условного возврата из подпрограммы через замыкание команды условного относительного перехода на свой собственный аргумент. Тем самым, появился набор команд условного возврата через трюк замыкания, не затрачивая ячейки таблицы команд…
Так как мой процессор должен иметь дружественную систему команд, интуитивно лёгкую к запоминанию для ручного программирурования машинного кода в спартанских условиях (ассемблер ещё не был готов), большинство операций занимают в таблице позиции с интуитивно прозрачными позициями: A0-AF - ALU, B0-BF - Branch, C0-CF - inCrement, D0-DF - Decrement, E0-EF - Exchange, F0-FF - Functions…
Спустя несколько месяцев упорной работы был худо-бедно написан ассемблер и дизассемблер. Трудность была в том, что когда я вносил поправку в систему команд, требовалось вносить и модификации в ассемблер. Но, потом я решение нашёл посредством создания нескольких структурных массивов. Вся система команд описывается сотнюю строк дешифратора в стиле постулатов Verilog: 0-1-X. Что существенно упростило дальнейщую разработку. На данный момент дешифратор имеет шину из 18 разрядов. Что примерно около 4 миллионов команд. Конечно, команд у меня гораздо меньше. Так как группами битов просто указывается при какой ситуации состояния процессора и даже АЛУ команда распознаётся дешифратором…
Следует указать ещё один любопытный момент. Было давно замечено, что АЛУ неспособно выставлять флажки процессора ни при каких обстоятельствах в три комбинации: Ноль со знаком (SF+ZF), ноль с нечётным паритетом (PF+ZF) и ноль со знаком нечётного паритета (SF+PF+ZF). Так как это попросту не вписывается в таблицу постулатов работы вычислительных узлов. Тем самым, в своей архитектуре процессора я это использовал на уровне самого дешифратора команд. И эти три неиспользуемых комбинации флажков статуса процессора несколько расширили возможности системы команд. Появилась возможность повтора одной инструкции несколько раз (аналог x86-REP) и возможность линейного пропуска нескольких операций без ветвления (аналог x86-CMOVcc). Инструкции получили мнемонику LOOP и SKIP соответственно…
Третья же комбинация флажков (SF+PF+ZF) не имеет прямой инструкции и достигается трюковым ветвлением с замыканием на себя (не аргумент FF, а на код операции JMP). Так как я свой процессор пытаюсь сделать хоть не умным, но адекватным, то замыкание на бесконечный цикл является трюковой операцией WAIT - аналог LOOP, но с условным выполнением: Без установленного флага CF цикл не работает (нечто сходное с x86-REPNZ)…
Команды работы с портами ввода/вывода IN/OUT давно называют костылями пережитков стародавних архитектур. И в системе команд моего процессора они явно отсутствуют. Хотя и имеются через трюковой WAIT-цикл. И это не просто так. Когда внешнее устройство готово, WAIT-цикл продолжается всего одну итерацию. А если устройство занято, WAIT-цикл займёт от 7 до 255 итераций. И программа сразу будет знать, устройство готово или занято, так как в этом случае IN/OUT изменяют флаг CF…
Процессором имеется всего 10 команд программного прерывания INT 0-9 с машинным кодом F0-F9 соответственно (Fn0-Fn9). В комбинации в WAIT-цикле эти INT-инструкции творят чудеса: Дешифратор команд отключается от внешней шины адреса/данных и подключается к внутреннему буферу команд, в котором можно запомнить до 7 инструкций. Внутри WAIT-цикла INT-инструкция заставляет дешифратор команд читать инструкции только внутреннего буфера, которые, естественно, способны выполняться за 1 такт. Тем самым, получается подобие макроса или гибкой микрокоманды, которая выполняется условным циклом до 255 раз. Программируется INT-прошивка так же легко - инструкцией SKIP: Все пропускаемые дешифратором операции попадают во внутренний буфер…
Аппаратные прерывания процессор явно обрабатывать не умеет. Архитектурно всё задумано так, чтобы система на базе данного процессора была мультизадачной. Аппаратно процессор имеет 128 регистровых файла, каждый из которых имеет объём до 256 слов, несущий в себе значения всех РОН, указателей и счётчиков. Тем самым, процессор способен мгновенно переключать контексты задач. При этом, после запуска процессор начинает выполнять задачу с нулевым контекстом, предоставляя ей привелегии ядра системы. Внутри кода этого ядра операции WAIT+IN/OUT, например, работают с портами напрямую, как и должно быть. Но в контексте прикладных задач подобные комбинации просто являются переключателями на контекст ядра и не позволяет вторичному кода программировать периферию напрямую. То же самое происходит и с аппаратными прерываниями, которые просто откидывают контекст задачи до ядра, где уже и выполняется соответствующая обработка. Следует отметить то, что инструкция HLT внутри кода ядра работает как единственный особый префикс, управляющий переключателем контекстов задач. Говоря проще, ядро передаёт управление прикладному коду посредством комбинации WAIT+HLT на время, после чего в авто-режиме SKIP пропускает до 7 следующих команд JMP (напоминает ON n GOTO Бейсика) в соответствии с ситуацией. Ситуаций всего бывает 8: запрос к API, программное прерывание, сбой страниц памяти, истечение кванта времени выполнения конкретной задачи, попытка доступа к порту ввода/вывода, программная ошибка вычислений, внешнее прерывание, приход сигнала Reset.
После запуска системы выполняется, естественно, ситуация с Reset. Причём, если сигнал Reset приходит позднее, процессор не сбрасывается, а передаёт управление контексту ядра, которое должно адекватно успеть совершить соответствующие действия. Если ядро не смогло обработать это событие или повисло, с обнулением счётчика тактов процессор сбрасывается в исходное состояние…
Таким образом, в 18-разрядной шиной дешифратора команд 8 разрядов выделяется собственно под код самой команды. Три бита - код префикса команды. Один бит - признак трюкового замыкания, когда непосредственный байт аргумента команды равен -1 (FF). Ещё три бита - расширенние дешифрации команд флагами процессора (LOOP/WAIT/EVAL + бит Boot-mode). Оставшиеся три бита не играют особой роли и нежны лишь для сцепления смежных байт-кодов (F0-F7 + F8-F9; FA-FD / EA-ED; префиксов с восходящим/нисходящим/прямым замыканием и т.д.).
После появления напряжения питания процессор оказывается в состоянии "холодного старта". В этом состоянии он не реагирует на сигналы генератора тактовой частоты и все его шины находятся в высокоимпендансном состоянии. С приходом сигнала "сброса" счётчик тактов должен отсчитать 65536 запускающих тактовых импульсов, прежде чем процессор запустится. Если на протяжении всего запускающего периода уровень сигнала "сброса" не изменялся, всё процессорное устройство включается в стандартный рабочий режим. Иначе счётчик запускающих тактов обнуляется вновь. Такой усложнённый алгоритм старта был введён с целью обезопасить операционную среду от разных нелинейных ситуаций.
Во время действия операционной среды исключаются случайные возможности со сбросом системы в исходное состояние приходом кратковременного сигнала "сброса". Как и при "холодном старте", перезапуск состоится не ранее отсчёта 65536 тактовых импульсов. За исключением того, что сам код ядра может влиять на отсчёт запускающего периода и откладывать его на неопределённый срок. Тем не менее, если вся система зависла и не откликается на внешние сигналы, сигнал "сброса" достаточной продолжительности произведёт перезапуск процессорного устройства в исходное состояние…
Так как флажки состояние процессора изменяются не только АЛУ, но и тесно связаны с дешифратором команд, флаг CF используется как опциональный не только в WAIT-циклах, но и при поступлении какого-либо события в системе. Так, на флаг CF влияет как фронт, так и срез сигнала "сброса", достаточно информируя о всех факторах.
Помимо контекстных файлов процессорное устройство имеет 8 управляющих регистром, доступные для чтения во всех прикладных задачах. Приложение может свободно прочитать и основной нулевой регистр с индексом собственного контекста в среде, а также счётчик оставшихся тактов до момента передачи управления другой задаче в очереди. Однако, для записи в эти 7 регистров приложению необходимо неявно сгенерировать обращение к ядру с ожиданием. Нулевой управляющий регистр со стороны приложения для записи явно не представлен никак, так как доступ к нему является инструкцией останова HLT…
Следует заметить, что холостых операций в процессоре всего 8. Одна из которых имеет код FE, а остальные 7 - её префикс-расширенные модификации. Все они действительно гарантируют задержку на указаное число тактов. В составе с LOOP можно ставить в программах точки с управляемой задержкой. Тогда как в составе с WAIT эти NOP/1-7 несут свободный смысл и могут уведомлять операционную среду о возможной паузе в прикладном процессе без необходимости возвращаться к нему некоторый период времени или о снижении приоритета задачи в очереди. Особо следует также отметить сочетание WAIT+HLT и LOOP+HLT, означающие явное завершение того или иного действия. Так, LOOP+HLT может означать прекращение работы конкретного процесса. Тогда как WAIT+HLT свидетельствует о необходимости выдачи ожидаемого ресурса от системы за неопределённый период. Как пример, процедура перерисовки указателя мыши требует обязательного прихода уведомления о смене позиции на экране или какого-либо другого действия, что может укладываться в довольно продолжительный интервал времени в периоде ожидания действий пользователя…
Вот краткое и не документабельное изложение сути поставленной мной задачи. Выполняю всё в одиночку, медленно, сердито и скудно. Был бы рад энтузиастам, готовым подключиться к проработке некоторых ньюансов проекта.
Страничка проекта с эмулятором под Chrome - в окне эмулятора:
F1 - 1 шаг эмуляции;
F4 - запуск автоэмуляции;
Alt+1..7 - выбор префикса в таблице команд;
Alt+8..0 - трюковые префиксы INT/LOOP/WAIT;
Alt+K - буфер клавиатуры;
Alt+P,T,R - поле адреса/транслятора/управления соответственно.
P.S.: Принимается адекватная критика.
Eщё будучи школьником, когда разобрался более-менее в основах работы микропроцессора РАДИО-86РК и открыл, что все сигналы, за исключением выводов частота/ожидание/прерывание/сброс, генерирует сам процессор по мере надобности, начались предприниматься попытки разработки собственного идеального процессора. Конечно, опираясь на опыт работы с ИК80, а позднее - и Z80…
В первую очередь, всегда мною предпринималась попытка разработать идеальную таблицу команд, не требующей существенных усилий для запоминания соответствий машинного кода к мнемонике и самой операции. Что стало моей идеалогией. Так, например, меня возмутило, что в i8086 коду 00 соответствует операция ADD, что сбивает с толку, если память обнулена. В поисках стабильности и защиты от сбоев, коду 00 я навсегда в своих набросках присвоил операцию останова HLT. И это вполне здравое решение.
Когда начал практически разрабатывать эмулятор, пришёл к выводу, что команды условного возврата из подпрограммы - нужные, но излишние в таблице. Система команд x86 изобилует реликтовым хламом, который зря захламляет всё и отжирает драгоценный байт-код: AAA, AAD, AAM, AAS. Команды ENTER/LEAVE используются при входе в подпрограмму и возврате из неё, т.е. не достаточно часто. Честно говоря, было много вариантов с перестановкой позиций команд i8080 (не Z80) в поисках оптимального. Думаете, от перестановки кодового соответствия практически ничего не меняется? Это - не так и после немногих раздумий я присвоил операции RET код FF, чтобы получить трюковую операцию условного возврата из подпрограммы через замыкание команды условного относительного перехода на свой собственный аргумент. Тем самым, появился набор команд условного возврата через трюк замыкания, не затрачивая ячейки таблицы команд…
Так как мой процессор должен иметь дружественную систему команд, интуитивно лёгкую к запоминанию для ручного программирурования машинного кода в спартанских условиях (ассемблер ещё не был готов), большинство операций занимают в таблице позиции с интуитивно прозрачными позициями: A0-AF - ALU, B0-BF - Branch, C0-CF - inCrement, D0-DF - Decrement, E0-EF - Exchange, F0-FF - Functions…
Спустя несколько месяцев упорной работы был худо-бедно написан ассемблер и дизассемблер. Трудность была в том, что когда я вносил поправку в систему команд, требовалось вносить и модификации в ассемблер. Но, потом я решение нашёл посредством создания нескольких структурных массивов. Вся система команд описывается сотнюю строк дешифратора в стиле постулатов Verilog: 0-1-X. Что существенно упростило дальнейщую разработку. На данный момент дешифратор имеет шину из 18 разрядов. Что примерно около 4 миллионов команд. Конечно, команд у меня гораздо меньше. Так как группами битов просто указывается при какой ситуации состояния процессора и даже АЛУ команда распознаётся дешифратором…
Следует указать ещё один любопытный момент. Было давно замечено, что АЛУ неспособно выставлять флажки процессора ни при каких обстоятельствах в три комбинации: Ноль со знаком (SF+ZF), ноль с нечётным паритетом (PF+ZF) и ноль со знаком нечётного паритета (SF+PF+ZF). Так как это попросту не вписывается в таблицу постулатов работы вычислительных узлов. Тем самым, в своей архитектуре процессора я это использовал на уровне самого дешифратора команд. И эти три неиспользуемых комбинации флажков статуса процессора несколько расширили возможности системы команд. Появилась возможность повтора одной инструкции несколько раз (аналог x86-REP) и возможность линейного пропуска нескольких операций без ветвления (аналог x86-CMOVcc). Инструкции получили мнемонику LOOP и SKIP соответственно…
Третья же комбинация флажков (SF+PF+ZF) не имеет прямой инструкции и достигается трюковым ветвлением с замыканием на себя (не аргумент FF, а на код операции JMP). Так как я свой процессор пытаюсь сделать хоть не умным, но адекватным, то замыкание на бесконечный цикл является трюковой операцией WAIT - аналог LOOP, но с условным выполнением: Без установленного флага CF цикл не работает (нечто сходное с x86-REPNZ)…
Команды работы с портами ввода/вывода IN/OUT давно называют костылями пережитков стародавних архитектур. И в системе команд моего процессора они явно отсутствуют. Хотя и имеются через трюковой WAIT-цикл. И это не просто так. Когда внешнее устройство готово, WAIT-цикл продолжается всего одну итерацию. А если устройство занято, WAIT-цикл займёт от 7 до 255 итераций. И программа сразу будет знать, устройство готово или занято, так как в этом случае IN/OUT изменяют флаг CF…
Процессором имеется всего 10 команд программного прерывания INT 0-9 с машинным кодом F0-F9 соответственно (Fn0-Fn9). В комбинации в WAIT-цикле эти INT-инструкции творят чудеса: Дешифратор команд отключается от внешней шины адреса/данных и подключается к внутреннему буферу команд, в котором можно запомнить до 7 инструкций. Внутри WAIT-цикла INT-инструкция заставляет дешифратор команд читать инструкции только внутреннего буфера, которые, естественно, способны выполняться за 1 такт. Тем самым, получается подобие макроса или гибкой микрокоманды, которая выполняется условным циклом до 255 раз. Программируется INT-прошивка так же легко - инструкцией SKIP: Все пропускаемые дешифратором операции попадают во внутренний буфер…
Аппаратные прерывания процессор явно обрабатывать не умеет. Архитектурно всё задумано так, чтобы система на базе данного процессора была мультизадачной. Аппаратно процессор имеет 128 регистровых файла, каждый из которых имеет объём до 256 слов, несущий в себе значения всех РОН, указателей и счётчиков. Тем самым, процессор способен мгновенно переключать контексты задач. При этом, после запуска процессор начинает выполнять задачу с нулевым контекстом, предоставляя ей привелегии ядра системы. Внутри кода этого ядра операции WAIT+IN/OUT, например, работают с портами напрямую, как и должно быть. Но в контексте прикладных задач подобные комбинации просто являются переключателями на контекст ядра и не позволяет вторичному кода программировать периферию напрямую. То же самое происходит и с аппаратными прерываниями, которые просто откидывают контекст задачи до ядра, где уже и выполняется соответствующая обработка. Следует отметить то, что инструкция HLT внутри кода ядра работает как единственный особый префикс, управляющий переключателем контекстов задач. Говоря проще, ядро передаёт управление прикладному коду посредством комбинации WAIT+HLT на время, после чего в авто-режиме SKIP пропускает до 7 следующих команд JMP (напоминает ON n GOTO Бейсика) в соответствии с ситуацией. Ситуаций всего бывает 8: запрос к API, программное прерывание, сбой страниц памяти, истечение кванта времени выполнения конкретной задачи, попытка доступа к порту ввода/вывода, программная ошибка вычислений, внешнее прерывание, приход сигнала Reset.
После запуска системы выполняется, естественно, ситуация с Reset. Причём, если сигнал Reset приходит позднее, процессор не сбрасывается, а передаёт управление контексту ядра, которое должно адекватно успеть совершить соответствующие действия. Если ядро не смогло обработать это событие или повисло, с обнулением счётчика тактов процессор сбрасывается в исходное состояние…
Таким образом, в 18-разрядной шиной дешифратора команд 8 разрядов выделяется собственно под код самой команды. Три бита - код префикса команды. Один бит - признак трюкового замыкания, когда непосредственный байт аргумента команды равен -1 (FF). Ещё три бита - расширенние дешифрации команд флагами процессора (LOOP/WAIT/EVAL + бит Boot-mode). Оставшиеся три бита не играют особой роли и нежны лишь для сцепления смежных байт-кодов (F0-F7 + F8-F9; FA-FD / EA-ED; префиксов с восходящим/нисходящим/прямым замыканием и т.д.).
После появления напряжения питания процессор оказывается в состоянии "холодного старта". В этом состоянии он не реагирует на сигналы генератора тактовой частоты и все его шины находятся в высокоимпендансном состоянии. С приходом сигнала "сброса" счётчик тактов должен отсчитать 65536 запускающих тактовых импульсов, прежде чем процессор запустится. Если на протяжении всего запускающего периода уровень сигнала "сброса" не изменялся, всё процессорное устройство включается в стандартный рабочий режим. Иначе счётчик запускающих тактов обнуляется вновь. Такой усложнённый алгоритм старта был введён с целью обезопасить операционную среду от разных нелинейных ситуаций.
Во время действия операционной среды исключаются случайные возможности со сбросом системы в исходное состояние приходом кратковременного сигнала "сброса". Как и при "холодном старте", перезапуск состоится не ранее отсчёта 65536 тактовых импульсов. За исключением того, что сам код ядра может влиять на отсчёт запускающего периода и откладывать его на неопределённый срок. Тем не менее, если вся система зависла и не откликается на внешние сигналы, сигнал "сброса" достаточной продолжительности произведёт перезапуск процессорного устройства в исходное состояние…
Так как флажки состояние процессора изменяются не только АЛУ, но и тесно связаны с дешифратором команд, флаг CF используется как опциональный не только в WAIT-циклах, но и при поступлении какого-либо события в системе. Так, на флаг CF влияет как фронт, так и срез сигнала "сброса", достаточно информируя о всех факторах.
Помимо контекстных файлов процессорное устройство имеет 8 управляющих регистром, доступные для чтения во всех прикладных задачах. Приложение может свободно прочитать и основной нулевой регистр с индексом собственного контекста в среде, а также счётчик оставшихся тактов до момента передачи управления другой задаче в очереди. Однако, для записи в эти 7 регистров приложению необходимо неявно сгенерировать обращение к ядру с ожиданием. Нулевой управляющий регистр со стороны приложения для записи явно не представлен никак, так как доступ к нему является инструкцией останова HLT…
Следует заметить, что холостых операций в процессоре всего 8. Одна из которых имеет код FE, а остальные 7 - её префикс-расширенные модификации. Все они действительно гарантируют задержку на указаное число тактов. В составе с LOOP можно ставить в программах точки с управляемой задержкой. Тогда как в составе с WAIT эти NOP/1-7 несут свободный смысл и могут уведомлять операционную среду о возможной паузе в прикладном процессе без необходимости возвращаться к нему некоторый период времени или о снижении приоритета задачи в очереди. Особо следует также отметить сочетание WAIT+HLT и LOOP+HLT, означающие явное завершение того или иного действия. Так, LOOP+HLT может означать прекращение работы конкретного процесса. Тогда как WAIT+HLT свидетельствует о необходимости выдачи ожидаемого ресурса от системы за неопределённый период. Как пример, процедура перерисовки указателя мыши требует обязательного прихода уведомления о смене позиции на экране или какого-либо другого действия, что может укладываться в довольно продолжительный интервал времени в периоде ожидания действий пользователя…
Вот краткое и не документабельное изложение сути поставленной мной задачи. Выполняю всё в одиночку, медленно, сердито и скудно. Был бы рад энтузиастам, готовым подключиться к проработке некоторых ньюансов проекта.
Страничка проекта с эмулятором под Chrome - в окне эмулятора:
F1 - 1 шаг эмуляции;
F4 - запуск автоэмуляции;
Alt+1..7 - выбор префикса в таблице команд;
Alt+8..0 - трюковые префиксы INT/LOOP/WAIT;
Alt+K - буфер клавиатуры;
Alt+P,T,R - поле адреса/транслятора/управления соответственно.
P.S.: Принимается адекватная критика.