![8-битный компьютер, выполняющий программу 8-битный компьютер, выполняющий программу](https://habrastorage.org/getpro/habr/upload_files/7ec/239/f09/7ec239f09d165aa683cb541413517ed0.png)
Привет! Я всегда хотел собрать свой компьютер — не только в теории понять как «бегают» биты, складываются числа, работают прерывания, как программный код превращается в нули и единицы. У меня получилось и я хотел бы поделиться своим опытом. Это заняло у меня 140 часов и $400 на все компоненты и их доставку. Если вам интересно узнать о проекте, спускайтесь под кат.
У меня нет цели научить читателя компьютерной электронике, но есть цель немного о ней рассказать и заинтересовать для самостоятельного изучения. Поэтому в статье упущено много базовой информации, нет деталей реализации различных компонентов, упрощены схемы — я не хочу перегружать материал. Если вас заинтересует статья, в конце есть раздел со всеми ссылками на видео и книгу для детального ознакомления.
Содержание статьи:
Видеоиллюстрация
На видео снизу я разбираю программу для вывода на экран чисел Фибоначчи, написанную на языке C. Из кода на языке С, я генерирую код на языке ассемблера, чтобы лучше понять принципы выполнение программы на компьютере. Так как компьютер из статьи не понимает язык ассемблера, я перевожу его на язык, который он понимает.
Вы можете посмотреть первые 10 секунд видео, в котором демонстрируется выполнение программы, вернуться на статью и дочитать ее, а потом с бóльшим контекстом досмотреть видео.
Архитектура
Компьютер построен на архитектуре SAP-1 simpleaspossible. SAP-1 — это архитектура для начинающих, главная цель — понять базовые идеи и концепции построения компьютера без углубления в детали. Дизайн специально разработан для академических целей.
Большинство деталей в проекте — это 7400 серия интегральных микросхем от Texas Instruments, американской компании-производителя полупроводниковых изделий.
Компоненты
Компьютер состоит из следующих компонентов:
Тактовый генератор.
Оперативная память.
Регистр адреса оперативной памяти.
Буферные регистры A и B.
Арифметико-логическое устройство.
Регистр ввода-вывода и дисплей.
Счётчик команд.
Регистр инструкций.
Шина для адреса и данных.
Устройство управления.
Схема
Схема расположения компонентов выглядит следующим образом:
![](https://habrastorage.org/getpro/habr/upload_files/733/6d5/bf4/7336d5bf49e9258c92e0e4e39abece10.png)
Компоненты
Тактовый генератор
Тактовый генератор координирует работу всех компонентов в компьютере. Он подключен почти к каждому компоненту отдельно и раз в определенное время выдает напряжение. Это нужно для того, чтобы синхронизировать выполнение программы разными частями компьютера.
В основе тактового генератора лежит чип LM555CN — это таймер, устройство для формирования повторяющихся импульсов тактовых сигналов. С помощью резисторов и конденсатора можно контролировать частоту импульсов. Так, например, у Intel Core i9-7980XE базовая тактовая частота — 2.60 GHz. Это значит, что за одну секунду выдается 2.6 миллиарда импульсов.
![](https://habrastorage.org/getpro/habr/upload_files/26d/c13/bb2/26dc13bb28c491a0f046005cdd2b181b.gif)
Частота импульса складывается из времени наличия напряжения и его отсутствия как проиллюстрировано на рисунке ниже. По формуле ниже, она из документации к таймеру, при резисторе А — 100 Ом, резисторе B — 100K Ом, конденсаторе С — 2 микрофарад, получается, что один такт занимает — 0.693 * 201000 * 0.000002 = 0.278 секунды. За одну секунду получится — 1 / 0.278 = 3.59 такта.
![](https://habrastorage.org/getpro/habr/upload_files/a2d/6bb/a9a/a2d6bba9aaa089ad87285a58b1b1eea4.jpeg)
Пример использования тактового генератора — внизу на картинке на макетной плате находится чип SN74LS173, это 4-битный D flip-flop — он нужен для того, чтобы хранить 4 битовых значения. Таким образом можно хранить 16 комбинаций значений, от 0000 до 1111. У чипа 16 ножек с помощью которых он вставляется в плату. Каждая из которых отвечает за свою часть работы. Чтобы не вдаваться в подробности, если на M и N разрешение на запись, и 1D подать напряжение, мы ожидаем, что чип сохранит значение как напряжение и отобразит это в 1Q, выход которого ведет к диоду красная лампочка — но ничего не произойдет. Для сохранения значения нам нужно также подать напряжение на вход CLK clock signal — тактовый сигнал, который исходит из тактового генератора.
![](https://habrastorage.org/getpro/habr/upload_files/9fc/e5d/a9b/9fce5da9bea6ad2c4f7269ab00461d7a.png)
В проекте тактовый генератор чуть-чуть сложнее:
Вместо резистора на 100К Ом там находится потенциометр, это «резистор с крутилкой», его можно поворачивать за и против часовой стрелки и динамически изменять сопротивление от 0 ОМ до 1М Ом. Таким образом можно увеличить количество тактов в секунду и компьютер будет работать быстрее, и наоборот.
Вместо одного таймера, там три, переключатель и кнопка. Это позволяет переключаться между двумя режимами — ручной и автоматический. В ручном режиме такт совершается при нажатии кнопки — это позволяет дебагать работу компьютера, а автоматический вы уже видели.
Оперативная память
Оперативная память нужна компьютеру, чтобы хранить определенный набор данных по определенным адресам. Оперативная память используется для хранения команд компьютера (например, сложить два числа), адресов (сложить число по какому-то адресу) и данных (записать какое-то число по какому-то адресу).
Знакомый нам чип SN74LS173 может сохранить 4 бита информации, чтобы сохранить 8 бит информации — нужно взять два SN74LS173. Таким образом, мы можем хранить значения от 0000 0000 до 1111 1111, что равно 256 возможных комбинаций, 2 в степени 8.
На схеме ниже к двум SN74LS173 подключен DIP-переключатель на 8 переключателей, которыми можно задавать 8 бит информации. Так как переключатели подсоединены к питанию, если переключить один из них, он выдаст напряжение. При подаче сигнала от тактового генератора, это значение сохранится в чипе и соответствующий диод загорится.
![](https://habrastorage.org/getpro/habr/upload_files/7fe/15c/e99/7fe15ce99723f13b14ef4f9ef5b619d2.png)
На самом деле, мы хотим контролировать когда производить запись. Без этого в памяти может оказаться любое значение – например, мы начинаем переключать переключатели, не переключили до конца, а тактовый сигнал сработал и память обновилась.
Для этого мы соединяем входы M и N с кнопкой. Кнопка подключена к напряжению, если на нее нажать, она передаст напряжение по перемычке. Нажав на кнопку и дождавшись тактового сигнала, мы получим запись значения.
![](https://habrastorage.org/getpro/habr/upload_files/1f7/f7f/654/1f7f7f654869cf8d325b4c6e90648185.png)
Таким образом, схематически, можно выразить масштабируемость оперативной памяти как наличие одной кнопки, которая контролирует запись 8 бит. Если мы хотим иметь 128 бит оперативной памяти, а именно столько памяти в проекте, нам нужно 16 кнопок, каждая из которых отвечает за свои 8 ячеек оперативной памяти 16 * 8 = 128.
![](https://habrastorage.org/getpro/habr/upload_files/f57/801/61a/f5780161aad530f75e84469435db74ac.png)
Если бы мы горизонтально подключили все ячейки между собой все первые ячейки каждой колонки, все вторые, третьи и так далее, соединив с одним переключателем на 8 переключателей, мы могли бы контролировать в какую именно колонку записать переданное через переключатели значение нажатием кнопки. Нажали на 16-ю кнопку — значение записалось только в последнюю колонку ячеек.
Кнопки получились бы репрезентацией адресов оперативной памяти. Но это сложно масштабировать, легче масштабировать бинарное представление 16 кнопок. То есть 4 бита, от 0000 до 1111 — в сумме 16 комбинаций, что равно количеству кнопок и, соответственно, колонок ячеек. С этим поможет DIP-переключатель на 4 переключателя.
Если значение переключателей будет 0000 — выбираем первый ряд, если 0001 — второй ряд, 0011 — четвертый ряд, и так далее до 1111 — 16 ряд. Раз кнопки превратились в переключатели, а переключатели превратились в перенаправление на определенную колонку ячеек, мы потеряли кнопку на запись — которую тоже надо добавить.
Таким образом, мы изобретаем декодер адресов. На вход декодера подается 4 сигнала, отвечающих за адрес в памяти, и 1 сигнал, отвечающий за запись.
![](https://habrastorage.org/getpro/habr/upload_files/203/e9c/26a/203e9c26a06c74e0b95e59f1e6ab900e.png)
Мы не будем разбирать устройство декодера. Внутри декодера находится комбинационная логика — логические вентили И AND и инверторы NOT. Иллюстрация работы в коротком видео здесь.
![](https://habrastorage.org/getpro/habr/upload_files/0a4/828/c69/0a4828c69a45e4155d573a3559b47f04.png)
Таким образом, мы имеем 4 переключателя для адресов, 8 переключателей для значений ячеек, 1 кнопка на запись значений.
Арифметико-логическое устройство
Арифметико-логическое устройство (АЛУ) — компонент, который выполняет арифметические и логические операции. Например, АЛУ в проекте умеет суммировать и вычитать два числа, каждое из которых представлено 8 битами. Вид операции зависит от положения тумблера: замкнутый тумблер даст сигнал АЛУ сложить числа, разомкнутый вычесть одно число из другого.
![](https://habrastorage.org/getpro/habr/upload_files/efd/070/d84/efd070d8467e36f792067dace39781c2.png)
Результат операции сразу сохраняется в отдельный 8-битный регистр, чтобы позже выполнить другие функции над ним — например, положить в оперативную память по какому-то из адресов. Этот регистр называется регистр для суммы.
Но на самом деле, АЛУ не принимает произвольные значения из переключателей. Каждое значение хранится в отдельном регистре — A и B. Эти регистры являются буферными регистрами. Буферные регистры предназначены для временного хранения данных и напрямую подключены ко входам АЛУ.
Регистры A и B почти идентичны по строению 4 знакомых нам чипа SN74LS173, но у них разные задачи. Регистр A призван сохранять промежуточный результат вычислений — один операнд, а регистр B призван хранить другой операнд.
Ниже в коде чуть более наглядно о задачах регистров на примере счётчика с инкрементом. Изначально, мы инициализируем переменную a и регистр А значением 0, переменную b регистр B значением 2. Cуммируем a и b, сохраняя в переменную sum регистр суммы в АЛУ. Значение из sum перезаписывается в a. Повторяем в цикле пока a меньше 255.
def increment():
a = 0
b = 2
while a < 255:
sum = a + b
a = sum
return a
Схема архитектуры, которую вы уже видели, и пример задачи показывает, что:
Регистр суммы в АЛУ нужен для сохранения результата операции между регистрами A и B — значение можно передать в другие компоненты через шину данных. Например, в регистр A и решить этим задачу с инкрементом.
Регистр B нужен для хранения вспомогательных значений — в него можно только записать через шину данных.
Регистр A нужен для временного хранения значений — его можно передать в другие компоненты через шину данных.
![](https://habrastorage.org/getpro/habr/upload_files/831/3d8/d6e/8313d8d6e5d8371f63033d9863f1ade3.png)
Также АЛУ не подключен к тактовому генератору, это видно на схеме выше, — это ассинхронный компонент. Это значит, что он отрабатывает сразу как только меняются значения в A и/или B. Это достигается за счет того, что в состав АЛУ включены только комбинационные схемы, как у декодера адресов.
![Схема SN74LS181— 4-битного АЛУ Схема SN74LS181— 4-битного АЛУ](https://habrastorage.org/getpro/habr/upload_files/78d/056/86b/78d05686bd574eff734b8feb286c1de6.png)
Регистр ввода-вывода и дисплей
Внизу на картинке изображен семисегментный индикатор — он может отображать цифры и буквы. Он состоит из семи сегментов, включающихся и выключающихся по отдельности — с помощью подачи питания на опредленные ножки.
Чтобы отобразить букву F, нужно подать питание на 1, 2, 4 и 6 ножки слева направо, сверху вниз. Чтобы отобразить цифру 1, нужно подать питание на 5 и 9 ножки. Вместо порядкового номера ножки, можно использовать буквы на схеме — для цифры 1 это B и C.
![](https://habrastorage.org/getpro/habr/upload_files/a28/067/ea4/a28067ea48548a1966ed1321e7a75daa.jpg)
Если мы хотим отобразить число, состоящее из нескольких цифр, мы можем использовать несколько индикаторов.
![](https://habrastorage.org/getpro/habr/upload_files/f1a/514/83c/f1a51483c26d02188850918b44491972.jpeg)
В проекте таких индикаторов 3 — они используются для отображения чисел в диапазоне от 0 00000000 до 255 11111111, один индикатор на одно число 0 отображается как 000, 1 как 001. Также в индикаторе есть ножка десятичного знака DP на схеме на случай, если нужно отображать числа с дробной частью (например, 17.3) — но такой функциональности в проекте нет, поэтому эта ножка не используется. Как вы поняли, шестнадцатеричная система счисления в проекте не используется, вместо F (15), используется два дисплея с 1 и 5.
Снизу проиллюстрированы все возможные варианты отображения одного десятичного числа на дисплее.
![](https://habrastorage.org/getpro/habr/upload_files/16f/581/0ba/16f5810ba0ee561cab672f2f76ef6c2b.png)
Теперь нужно понять как «соединить» 4-битное значение в диапазоне от 0000 0 до 1111 9 с входами дисплея от A до G. Например, если значение 0011, то на B и C нужно подать напряжение, а на A, D, E, F, G не нужно. С этим поможет таблица истинности ниже.
![](https://habrastorage.org/getpro/habr/upload_files/5ff/51d/d63/5ff51dd639b27e3c54fa66b90730636b.png)
Вы могли заметить, что эта таблица похожа на структуру данных ассоциативный массив. Такой массив может хранить пары вида ключ-значение и поддерживает операции добавления пары, поиска и удаления.
binary_to_7_segment_display_map = {
'0000': '1111110',
'0001': '0110000',
...
}
binary_to_7_segmnet_display_map.get('0000')
del binary_to_7_segment_display_map['0001']
Что еще похоже на ассоциативный массив? Оперативная память — адрес на вход и значения на выход. Мы можем сохранять по адресам, которые соответствуют двоичным числам 1001, значения для входов дисплея, которые соответствуют десятичному отображению 9.
![](https://habrastorage.org/getpro/habr/upload_files/a99/80f/0bf/a9980f0bfa2c8fbc44a1f23e92d7f245.png)
Но оперативная память нам не подойдет потому, что не может хранить свои данные «вечно», только при наличии питания. Это значит, что нам придется каждый раз заново записывать в оперативную память значения из таблицы истинности.
Кроме оперативной памяти есть еще другие, которые сохраняют ее свойства (адреса, значения, запись и так далее):
ROM (read-only memory) — на заводе изготовителя «заливают» значения по адресам, а после значения изменить нельзя. Если надо что-то изменить, необходимо менять чип целиком.
PROM (programmable read-only memory) — изготавливается с «чистыми» значениями, которые можно запрограммировать один раз.
EPROM (erasable programmable read-only memory) — память можно перезаписывать, но чтобы стереть значения нужно несколько часов держать специальное окошко на чипе под светом ультрафиолетовой лампы. Окошко заклеивается наклейкой или изолентой.
EEPROM (electrically erasable programmable read-only memory) — память можно перезаписывать сколько угодно раз с помощью электрических импульсов — напряжения.
В проекте используется EEPROM — AT28C16. У него 11 входов для адресов (от 00000000000 до 11111111111) — это 2^11 комбинаций, то есть 2048 адресов и 8 ячеек памяти на каждый адрес. В сумме это память на 16384 бит (2048 байт, ~2 килобайта). С помощью входа OE (output enable), подавая напряжение, можно регулировать — выводить ли на выходы I/O то, что хранится в памяти или нет. С помощью входа WE (write enable), подавая напряжение, при «выключенном» OE, можно сделать из выходов I/O входы для записи в ячейки памяти.
![](https://habrastorage.org/getpro/habr/upload_files/232/abf/4dc/232abf4dc7043c5806e825ae6f902d7c.png)
Плата, некий пульт управления памятью, с помощью которой можно менять значения в ячейках и смотреть, что хранится по тому или иному адресу, выглядит вот так:
![](https://habrastorage.org/getpro/habr/upload_files/bb2/cca/d02/bb2ccad02890c0355dcd06ccddccf134.jpg)
С помощью DIP-переключателей задается адрес, с помощью диодов выводится значение в ячейке. С помощью перемычек (проводков над диодами) задаются новые значения.
Как отобразить число от 0 до 9 на дисплее мы разобрались. Теперь нужно понять как отобразить трехзначное число (например, 123) на 3 дисплеях. Сложность здесь в том, что на регистр ввода-вывода передается одно 8-битное значение (для 255 это 1111011), а на выходе не 1 дисплей, а 3.
Есть много решений этой задачи, в проекте используется сложный, но менее затратный по ресурсам (нужно меньше чипов). О сложном решении рассказать текстом не просто, поэтому обсудим простое решение.
Для каждой цифры из трехзначного числа — свой отдельный дисплей, а для каждого дисплея — свой отдельный EEPROM. Все адреса EEPROM-ов соединены между собой — это значит, что если на вход в регистр ввода-вывода попадет адрес — он попадет на все EEPROM-ы.
![](https://habrastorage.org/getpro/habr/upload_files/51e/ab7/7eb/51eab77eb035f0c77be4db5307039143.png)
Фишка здесь в том, что каждый из EEPROM-в отображает только часть числа. Первый — первую цифру, второй — вторую, третий — третью. Это достигается за счет того, что каждый из них имеет свою таблицу истинности — различную друг от друга.
На каждый из EEPROM-ов приходит адрес 1111011 (число 123). Первый должен отобразить 1 — значит, в ячейке памяти по адресу 1111011 лежит — 0110000 (значения для дисплея). У второго в памяти лежит — 2 (1101101), у третьего — 3 (1111101).
![](https://habrastorage.org/getpro/habr/upload_files/8c4/f1f/b8b/8c4f1fb8b58e512c92e28486bdffbace.png)
Счётчик команд
В проекте, как и в современных компьютерах, в оперативной памяти хранятся не только значения, но и инструкции. Инструкции — это команды для компьютера сделать то или иное действие.
Оператор компьютера человек заполняет оперативную память инструкциями — одна инструкция в одной ячейке оперативной памяти, а компьютер выполняет эти инструкции одну за одной — для этого ему нужен счётчик команд.
Какие есть инструкции и как они работают, мы разберем чуть позже — сейчас о порядке выполнения программы:
Как только компьютер подключается к питанию, его надо поставить на паузу через специальную кнопку на плане тактового процессора.
Оперативная память заполняется вручную через DIP-переключатели, связанные с чипами SN74LS173, о которых рассказывалось выше.
Пауза отжимается и компьютер выполняет инструкцию из ячейки памяти по адресу 0000, потом инструкцию 0001, 0002 и так далее.
Адрес | Значение | |
0000 | 0001 | 0001 |
0001 | 0010 | 1111 |
... | ... | ... |
1111 | 0000 | 0001 |
В любой момент времени можно поставить компьютер на паузу, изменить оперативную память и отжать паузу — что-то вроде ручной операционной системы.
Счётчик команд выполняет две задачи:
Хранит адрес оперативной памяти — из какой ячейки взять инструкции для выполнения.
Последовательно считает следующий адрес оперативной памяти — начинает от 0000 и до 1111 всего16адресов.
![](https://habrastorage.org/getpro/habr/upload_files/aa6/a5a/fd3/aa6a5afd3ee8ca11072e694952b7dfa4.gif)
Также в счётчик команд можно записать любой адрес — например, 0004, тогда счётчик начнет считать с этого значения — 0005, 0006 и так далее. Записать адрес можно и уже после того как счетчик заработал — то есть прервать его на, например, адресе 1101.
Такая функциональность нужна для условий в программах. Например, в программе ниже, написанной на псевдокоде, мы хотим инициализировать регистр A единицей и инкрементировать его до тех пор, пока он меньше 255. Для этого нам не надо ~250 строк кода, нам надо инструкция для компьютера, которая может «перебрасывать» выполнение программы — менять адрес в счётчике команд.
LOAD 1 TO REGISTER A
ADD 1 TO REGISTER A
IF REGISTER A < 255: JUMP TO 2TH INSTRUCTION
ELSE: HOLD
Шина адреса и данных
Почти каждый компонент соединен с шиной адреса и данных. Она так называется потому, что по ней передаются и адреса (например, ячеек памяти) и данные (например, для загрузки в регистры).
На схеме ниже видно стрелочки с цифрами.
Если стрелочка идет только от компонента к шине, значит, компонент выдает что-то на выход — как счётчик команд, который ничего не хранит, а выдает следующий адрес.
Если стрелочка идет только от шины к компоненту, значит, компонент что-то хранит — как регистр адреса оперативной памяти, в который записывается адрес оперативной памяти.
Если стрелочка идет от компонента к шине и от шины к компоненту, значит, это более функциональный компонент — как регистр A, в который можно записать значение и потом из него его получить (например, для сохранения в оперативную память).
![](https://habrastorage.org/getpro/habr/upload_files/e14/d07/f4b/e14d07f4b027a424c1d03f6fadf16a80.png)
Раз все компоненты соеденены с помощью шины, теперь можно управлять передачей значений в шине между компонентами. Например, отобразить значение из оперативной памяти на дисплее:
Дожидаемся нужного адреса на счётчике команд, передаем его в регистр адреса оперативной памяти.
По адресу из оперативной памяти получаем значение и передаем его в регистр ввод-вывода.
Регистр ввода-вывода обрабатывает переданное значение и передает в регистр ввода-вывода, чтобы отобразить на дисплее.
![](https://habrastorage.org/getpro/habr/upload_files/acf/09c/c07/acf09cc070f8be1fa2042339171b5da2.png)
Но не все так просто — на данном этапе все компоненты подключены к шине. Это значит, что передав значение из одного компонента, оно попадет на все остальные, которые направлены на получение значений из шины. То есть на примере выше, из счётчика команд адрес попадет не только в регистр адреса оперативной памяти, но и в регистр A, регистр B, регистр инструкций.
Эту проблему решит SN74LS245 — это микросхема, обеспечивающая двустороннюю связь между компонентами. В нашем случае, эта микросхема находится между каждым из компонентов компьютера, который соединен с шиной, и непосредственно шиной.
8 входов для данных с одной стороны и 8 выходов с другой. Если на вход DIR подать напряжение, ножки B — входы, а ножки A — выходы, если не подать, то наоборот. Ко входу OE подключена кнопка — она отвечает за то, чтобы разрешить передачу данных через микросхему или нет отBкA.
![](https://habrastorage.org/getpro/habr/upload_files/95d/598/eb8/95d598eb8e1333aa5c2b35a1cae9a7ac.png)
Ножки A дальше подключены к любым другим микросхемам. Так, с помощью SN74LS245, можно решить проблему нежелательного попадания данных из шины во все компоненты, подключенные к шине — теперь это регулируется кнопками.
Устройство управления
Раз у нас есть такие кнопки на всех компонентах (какие-то подключены к SN74LS245, какие-то к другим микросхемам, которым не нужен SN74LS245, как, например, тактовый процессор), мы можем сделать некий пульт управления компьютером, разместив их рядом на одной плате вместо на каждом компоненте отдельно:
![](https://habrastorage.org/getpro/habr/upload_files/5ab/bbc/fbb/5abbbcfbb88a3ebd4721756870f308d4.png)
Каждая кнопка отвечает за свой компонент:
HLT — останавливает тактовый процессор — выполняющую программу можно поставить на паузу.
MI — записывает значение из шины в регистр адреса оперативной памяти.
RI — записывает значение из шины в оперативную память по адресу из регистра выше.
RO — выдает значение из оперативной памяти на шину.
II — записывает значение из шины в регистр инструкций.
IO — выдает 4 последних бита из значения из регистра инструкций на шину.
AI — записывает значение из шины в регистр A.
AO — выдает значение из регистра A на шину.
ΣO — выдает сумму значений регистров A и B из АЛУ.
SU — регулирует, суммировать значения из регистров A и B или вычитать.
BI — записывает значение из шины в регистр B.
OI — записывает значение из шины в регистр ввода-вывода, и соответственно, выводит на дисплей.
CE — регулирует, работает ли счётчик команд или стоит на паузе.
CO — выдает значение счётчика команд на шину.
J — записывает значение из шины в счётчик команд.
Теперь нам легко написать какую-то программу, например, сложить два числа и вывести на дисплей. Помните, на тактовом процессоре есть ручной режим, который генерирует таковые сигналы по нажатию кнопки.
Изначально, запустим и остановим компьютер, запрограммируем оперативную память через DIP-переключатели:
По адресу 0 0000 — число 1 0001.
По адресу 1 0001 — число 2 0010.
Так как это первый запуск счётчика команд, он выдаст значение 0000.
Загрузим первое число в регистр A:
Выдадим значение счётчика команд на шину CO.
Запишем значение из шины в регистр адреса оперативной памяти MI — по адресу 0 0000 хранится число 1 0001.
Выдадим число из оперативной памяти на шину RO — как только меняется адрес, регистр оперативной памяти готов выдавать сохраненное число.
Запишем значение из шины в регистр A AI.
Увеличим адрес в счётчике команд CE — он выдаст значение 0001.
Загрузим второе число в регистр B:
Выдадим значение счётчика команд на шину CO.
Запишем значение из шины в регистр адреса оперативной памяти MI — по адресу 1 0001 хранится число 2 0010.
Выдадим число из оперативной памяти на шину RO.
Запишем значение из шины в регистр B BI.
Увеличим адрес в счётчике команд CE — он выдаст значение 0002.
Выведем сумму на дисплее:
АЛУ автоматически посчитало сумму значений из регистров A и B 3,0001+0010=0011.
Выдадим число из АЛУ на шину ΣO.
Запишем значение из шины в регистр ввода-вывода OI и, соответственно, вывeдем на дисплей.
![](https://habrastorage.org/getpro/habr/upload_files/59c/3d0/2e7/59c3d02e70f53273a477eae426f0f990.png)
По сути, мы изобрели настоящие команды инструкции, состоящие из микрокоманд микроинструкций:
Загрузить load значение по адресу оперативной памяти в регистр A: CO, MI, RO, AI, CE.
Сложить add к загруженному значению другое, загрузив его по адресу оперативной памяти в регистр B: CO, MI, RO, BI, CE.
Вывести out результат из АЛУ на дисплей: ΣO, OI.
Наша программа выглядеть так (сами придумали синтаксис):
LDA 0
ADD 1
OUT
Комбинируя различные микроинструкции, можно создавать свои инструкции.
Еще важный момент состоит в том, что часть микроинструкций нужно выполнять одновременно. Например, чтобы передать значение из счётчика команд в регистр адреса оперативной памяти, нужно выполнить одновременно CO, MI — потому что если один из компонентов прекращает передавать значение на шину, другой компонент не сможет его из шины считать.
Таким образом, набор микроинструкций для инструкций выше выглядит так:
Шаг | LDA | ADD | OUT |
1 | CO, MI | CO, MI | ΣO, OI |
2 | RO, AI, CE | RO, BI, CE |
В компьютере, для хранения шага инструкции, есть регистр микроинструкций. В основе лежат теже микросхемы, что и в счётчике команд и 3-8 декодер. Декодер принимает на вход значения от 000 до 111 красные диоды из счётчика и выдает последовательность шагов в диапазоне 01111, 10111, 11011, 11101, 11110. На картинке 6 шагов 6 зеленых диодов, но в проекте используется 5 шагов.
![](https://habrastorage.org/getpro/habr/upload_files/e45/715/d10/e45715d107046c8d0fbb08f083a8eec7.gif)
В программе выше мы записывали в оперативную память и манипулировали только числами. Чтобы компьютер автоматически выполнял программу, вместе с числами нужно записывать инструкции.
Инструкции делятся на несколько видов:
Сделать что-то со значением. Например, загрузить число 2 в регистр A. Это инструкция, которая принимает число как аргумент.
Сделать что-то с адресом оперативной памяти. Например, сложить число из адреса 15 с числом из регистра A. Это инструкция, которая принимает адрес оперативной памяти как аргумент.
Сделать что-то. Например, вывести на дисплей число из регистра A. Это инструкция, которая не принимает аргументов.
Инструкция и аргумент вместе занимают 8 бит — это одна ячейка памяти. 4 бита слева — это инструкция, 4 бита справа — аргумент. Например, загрузить число 2 в регистр A — 0001 0010, а сложить число из адреса 15 с числом из регистра A — 0010 1111.
![](https://habrastorage.org/getpro/habr/upload_files/cef/36c/3a3/cef36c3a3d7bebdc13b79ef992204f8e.png)
Регистр инструкций получает инструкцию и аргумент из оперативной памяти через шину. Потом передает левые 4 бита в устройство управления, а правые 4 бита на шину. Устройство управления, в свою очередь, основываясь на переданной инструкции, определяет, что делать с аргументом на шине.
![](https://habrastorage.org/getpro/habr/upload_files/6bd/4ad/489/6bd4ad489c41d14aecd8d38b13dada7d.png)
Если расширить нашу программу, получится, что теперь значения из оперативной памяти не записываются напрямую в регистр A — как мы делали вручную, а идут через регистр инструкций. И что каждую инструкцию теперь нужно достать из оперативной памяти.
Шаг | LDA | ADD | OUT |
1 | CO, MI | CO, MI | CO, MI |
2 | RO, II | RO, II | RO, II |
3 | IO, AI, CE | IO, BI, CE | ΣO, OI, CE |
Теперь, если мы не управляем компонентами вручную, это должно делать устройство управления. Это компонент, который принимает на вход инструкции из регистра инструкций и шаги из регистра микроинструкций, а на выход отдает сигналы другим компонентами. Вместо нажатия на кнопку, оно выдает напряжение, которое ранее генерировалось нажатием кнопки.
Это проиллюстрировано на GIF-ке внизу 6 секунд на кадр: через устройство управления отдельный шаг каждой инструкции генерирует на выход напряжение для компонентов, которые нужно активизировать.
![](https://habrastorage.org/getpro/habr/upload_files/5ea/241/5fa/5ea2415faa66345f635a696da1643d6a.gif)
Как вы уже могли догадаться, сделать устройство управления можно через EEPROM: шаг и инструкция будут адресом, а значения памяти будут выражать активацию компонентов (тотже принцип, что и с дисплеем).
Таблица истинности выглядит так:
![](https://habrastorage.org/getpro/habr/upload_files/7b1/adb/aa7/7b1adbaa7c54d48183ba8826d77556c9.png)
Получится, что по адресу 0001 01111 хранится 01000000000010, а по адресу 1110 11011 хранится 000000001001100. Раз мы определяем по какому адресу какие хранятся значения, которые «управляют» компонентами, это значит, что мы сами можем определять какое 4-битное значение относится к той или иной инструкции. Вместо 0001 для LOAD могло быть что-угодно, например, 1111 — в оперативную память и EEPROM нужно уже записывать 1111 вместо 0001.
Заключение
Если вас заинтересовала статья и вы хотите погрузиться в компьютерную электронику глубже, посмотрите ссылки ниже:
Изначально, книга Digital Computer Electronics от Albert P. Malvino, в которой разбирается базовая электроника по типу резистров и транзисторов, устройство логических вентелей и микросхем, и строится компьютер на архитектуре SAP-1 (как и проект из статьи).
Следом, видеоплейлист от Ben Eater, который строит компьютер из книги и записывает это на YouTube. Также, есть канал BitFlip, который делает тоже самое, но на русском языке.
Последнее, магазин, в котором можно заказать все необходимые детали или другой, на случай, если нужно будет что-то дозаказать (по причине брака, например). Эти магазины из Америки (таможня, пошлина и так далее), но там есть все необходимое, в СНГ может быть трудно найти такие-же детали и придется искать аналоги.
Больше про проект можно почитать у меня в Telegram-канале: пост, пост и пост. Там я также делюсь своим мнением про разработку и IT-индустрию в целом.
Спасибо за внимание. Если у вас есть вопросы или вы хотите что-то обсудить, жду в комментариях или личных сообщениях.