Прогулка по уровням абстракции

Современные системы настолько сложны, что без абстракций невозможно создать что-либо сколько-нибудь интересное. Если бы вы при создании компьютерной игры думали о том, как выполнить простейшую математическую операцию на физическом уровне, то, вероятно, к написанию игры вы так и не приступили бы.

В начале компьютерной эры игры создавались на «жесткой логике». Яркий пример — игра Breakout от компании Atari: она была создана на дискретных компонентах, без применения микропроцессора. Задача, прямо сказать, не из простых — однако Стиву Возняку она оказалась по силам. Каково же было его изумление, когда вместо месяцев работы над подобным проектом он мог потратить всего одну ночь на создание того же самого, используя микропроцессор и язык программирования.

Сооснователь Apple Inc., один из создателей персональных компьютеров, выдающийся инженер-электронщик. Ознакомиться с его биографией можно в книге «iWoz: Computer Geek to Cult Icon: How I Invented the Personal Computer, Co-Founded Apple, and Had Fun Doing It» (на русском «Стив Джобс и я: подлинная история Apple»)

Процессор состоит из транзисторных цепочек: различных логических вентилей, триггеров и т.д. Рассматривать их все мы не будем, как и устройство транзисторов с физикой полупроводников. Для этого стоит обратиться к специализированной литературе. Однако провести параллели между физическим и более высокими уровнями абстракции для понимания необходимо.

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

Если не вдаваться глубоко, транзисторы состоит из трех частей, из двух типов материала, сложенных в виде сэндвича. Отличительной характеристикой одного вещества (N, англ. negative) является то, что в кристаллической решетке имеется избыток электронов, а во втором веществе (P, англ. positive), наоборот, их недостаток (образовавшееся вакантное место называют «дыркой», т.е. эдаким виртуальным положительным зарядом). В зависимости от «укладки» слоев бывают NPN- и PNP-транзисторы.

Электрод, подключенный к середине, называют базой (англ. base), другие два — коллектором (англ. collector) и эмиттером (англ. emitter). Стрелка на эмиттере указывает направление тока.

В цифровой технике усилительные свойства транзисторов не так интересны, а вот «ключевой» режим работы нашел широкое применение. При помощи малого тока, поданного на базу, можно управлять большим током (полевой транзистор управляется напряжением, а не током!), проходящим через коллектор-эмиттер. Проводя параллели с реальной жизнью, можно привести в пример использование крана. Небольшим усилием поворота ручки вы управляете большим потоком воды.

Хорошее описание того, как устроен процессор и как осуществляются различные операции в нём, можно найти в книге «Цифровая схемотехника и архитектура компьютера», Дэвид М. Хэррис, Сара Л. Хэррис.

В итоге при подаче тока на базу транзистор открывается (резистор ограничивает ток базы). Если тока нет, т.е. напряжение между базой и эмиттером равно 0 В, то транзистор закрыт, и ток через К-Э не бежит.

Ток, который может отдавать ножка микроконтроллера, обычно не превышает 20 мА, поэтому подобную схему можно часто встретить там, где необходимо управлять более прожорливой нагрузкой, такой как реле или двигатель.

На основе предложенной схемы можно собирать логические вентили, т.е. осуществлять логические операции. Существует три базовых операции:

  • логическое «И» (англ. and), или конъюнкция, или логическое умножение, обозначается ∧ (или & в языке Си);
  • логическое «ИЛИ» (англ. or), или дизъюнкция, или логическое сложение, обозначается ∨ (или | в языке Си);
  • логическое «НЕ» (англ. not), или изменение значения, или инверсия, или отрицание, обозначается ¬ (или ~ в языке Си).

Операция «НЕ»

Видоизменив схему — заменив реле на сопротивление — легко получить инвертирование сигнала.

Операция НЕ унарная, т.е. требует всего одно значение. Если мы подадим на базу низкое напряжение, то транзистор будет закрыт. Снимая напряжение на коллекторе, мы получим высокий уровень. И наоборот, открыв транзистор (высокое напряжение на базе), мы замкнем цепь на землю, т.е. напряжение на коллекторе будет низким.

Для описания логических операций удобно использовать так называемую таблицу истинности:

A¬A
01
10

Операция «И»

Следующая операция — конъюнкция, т.е. логическое «И», или, как его еще называют, логическое умножение. Она является бинарной, т.е. требует два значения. Очевидно, что «умножая» любое число на ноль, мы получим ноль. Другими словами, высокое напряжение мы можем получить только в том случае, если оба входных сигнала являются высокими. Составим таблицу истинности.

ABA∧B
000
010
100
111

Реализовать такую операцию достаточно просто. Возьмем за основу предыдущую схему, поставим второй транзистор последовательно (не забываем о падении напряжения К-Э!) и переместим резистор с коллектора на эмиттер.

Операция «ИЛИ»

Последняя операция, бинарная, называется дизъюнкцией, или логическим сложением. Получить высокий уровень на выходе можно в случае, если хотя бы один из входов имеет высокий уровень. Составим таблицу истинности.

ABA∨B
000
011
101
111

Физически такую операцию легко реализовать, соединив транзисторы параллельно, а не последовательно, как в случае с операцией И.

Часто для построения схем используют «совмещенные операции», такие как NAND (NOT + AND) или NOR (NOT + OR). В случае, когда нужно переключить состояние из 0 в 1 или из 1 в 0, удобно использовать исключающее ИЛИ (XOR).

Такое построение называется транзисторно-транзисторной логикой, или ТТЛ.

Если поэкспериментировать с такими схемами, то можно собрать триггер. Рассматривать его (их) мы не станем, однако отметим, что такие схемы способны запоминать состояние, т.е. хранить высокий или низкий логический уровень продолжительное время. На основе таких цепочек построена память. Собрав в группу 32 триггера, можно получить «слово» (об этом чуть позже).

Рассмотрим небольшую часть некоторого устройства: светодиод индикации подключен к одной из ножек микроконтроллера, которая привязана к выходному регистру (обозначим reg), точнее, к его третьему биту. Соседние биты отвечают за другие части устройства. В таком случае мы не можем просто взять и записать в регистр «нужное число», чтобы включить светодиод, так как другие биты могут хранить определенные значения.

// wrong!
reg = 0b0100;

Предположим, что в регистре уже записано 0b1001, а нам необходимо значение 0b1101. Изменить состояние одного-единственного бита можно при помощи логических операций.

Для начала необходимо создать вспомогательную переменную, маску (обозначим mask), хранящую единицу в нужной нам позиции.

// reg == 0b1001
mask = 0b0100;

Применив побитовую операцию ИЛИ, в нужное положение регистра мы запишем единицу, сохранив состояние всех остальных бит.

result = reg OR mask
reg     0b1001
mask    0b0100
result  0b1101

Затереть единицу в определенной позиции можно, использовав две операции. Сначала инвертируется маска, а затем производится логическое умножение:

result = reg AND (NOT mask)
reg     0b1101
mask    0b0100
mask`   0b1011
result  0b1001

Таким незамысловатым образом программы управляют микроконтроллером.

Часто для управления той или иной периферией внутри регистра выделяется не один бит, а несколько. Путь второй и третий бит (начиная с нуля) отвечают за выбор режима работы определённой ножки (в нашем случае первой, начиная с нуля):

  • 00 — ножка работает как вход, т.е. может считывать значение (1);
  • 01 — ножка работает как выход, высокоимпедансное состояние (2);
  • 10 — ножка работает как выход, с push-pull (3);
  • 11 — ножка используется как вход/выход другого периферийного блока такого как UART или SPI (4).

Теперь одну сущность, режим работы, описывает не один бит, а два. По этой причине создадим три маски: по одной для описания каждого бита и ещё одну для описания двух бит сразу. Так как мы говорим о первой ножке — пусть у неё будет постфикс 1, затем символ земли и позиция внутри виртуальной сущности (поле конфигурации).

mask1_0 = 0b0100
mask1_1 = 0b1000

Маску описывающую оба бита можно составить из mask1_0 и mask1_1:

mask1   = mask1_0 OR mask1_1

Переключиться в режим (4) можно двумя способами:

result = reg OR mask1                // result = 0b1100
result = reg OR (mask1_0 OR mask1_1) // result = 0b1100

Для перехода в режим (3) нужно записать ноль во второй бит (используя маску mask1_0) и единицу в третий бит (используя маску mask1_1):

result = (reg AND (NOT mask1_0)) OR mask1_1 // result = 0b1000

Аналогично для перехода в режим (2):

result = (reg AND (NOT mask1_1)) OR mask1_0 // result = 0b0100

В режим (1) можно, как и в режим (4) прийти двумя способами:

result = reg AND (NOT mask1)                // result = 0b0000
result = reg AND (NOT (mask1_0 OR mask1_1)) // result = 0b0000

Изменено: