Основы цифровой схемотехники. Как логические элементы образуют биты памяти в твоем компьютере
Источник: t.me/Bureau121
Содержание статьи
- В начале был Бит
- Некоторая неопределенность
- Простой RS-триггер
- Практическое применение
- Продвинутый D-триггер
- Практическое применение
- Кодовый замок на регистрах
В начале был Бит
Пару месяцев назад мы приступили к изучению цифровой схемотехники с базового блока NOT
. Не будем изменять традиции и в этот раз, тем более, что блок на самом деле не так прост, как кажется на первый взгляд. Возьмем парочку и соединим их последовательно.
Кто-то может сказать, что это совершенно бессмысленная схема, ведь сигнал на выходе всегда равен входу. Действительно, применяя логическое отрицание дважды, мы получаем исходное утверждение. Если ты не «белый» хакер, ты «черный». Не «не белый» — по-прежнему «белый». Это выглядит как бесполезная словесная эквилибристика, но все меняется, если в схеме выше попробовать вход замкнуть на выход.
Теперь, если в правой половине у нас высокий логический уровень, в левой половине всегда будет низкий (и наоборот). Иначе говоря, схема приобретает свойство бистабильности и принимает лишь одно из двух возможных состояний. Совсем как бит памяти — или переменная bool
в С/С++.
Для наглядности можно собрать схему на макетной плате. Здесь подойдет любой интвертор — например, 74HC04B. Это шесть логических вентилей NOT
в корпусе DIP-14 (целых три бита информации, Карл!). Впрочем, как ты уже понимаешь, одну и ту же функцию можно реализовать несколькими способами, поэтому здесь наш выбор практически неограничен.
Простой пример (как на картинке выше) позволяет на практике убедиться, что микросхема работает, как мы предполагали. Для мигания светодиодами и переключения состояний в такой ячейке памяти достаточно прикасаться концами проводов (от земли или питания) к соответствующим выводам микросхемы. Возможно, само по себе это звучит и не очень увлекательно, но некоторое размышление в процессе должно навести на интересные идеи.
Некоторая неопределенность
В какой-то момент твой пытливый хакерский ум наверняка задался вопросом — а что если подать одно и то же напряжение на обе половинки одновременно? И в каком состоянии окажется наша схема после? В сущности, это сродни делению на ноль в математике (или программировании) — запрещенная операция, результат на выходе которой не определен. Парадоксально, но даже такое свойство может иметь практическое применение! Инженеры в Intel хорошо знают схемотехнику (кто бы сомневался) и встроенный в их процессоры генератор случайных чисел работает именно по такому принципу.
Сейчас процесс работы нашей схемы напоминает электромеханические компьютеры середины 40-х годов XX века, когда коммутация сигналов внутри устройства происходила с помощью реле и переключателей. Самое время добавить новые возможности и перейти от инверторов к элементам с несколькими входами.
Простой RS-триггер
Внесем минимальные изменения в нашу схему и воспользуемся дополнительными входами NAND
. Назовем их nR
и nS
(not RESET
и not SET
соответственно, их назначение прояснится в дальнейшем).
Оба входа могут принимать по два значения, итого предстоит разобрать четыре варианта. Начнем с базового случая nR = 1
и nS = 1
. При этом на выходах Q
и nQ
уже есть какие-то значения. Обрати внимание, что если Q = 1
, то при nS = 1
результатом операции NAND
будет низкий уровень, то есть nQ = 0
. И наоборот, если Q = 0
, то nQ = 1
и оба выхода в нашей схеме действительно принимают противоположные значения. Другими словами, если один из входов вентиля NAND
находится в состоянии логической единицы, то сигнал на выходе определяется как инверсия второго входа — в точности как с инверторами чуть ранее! Таким образом, при nR = 1
и nS = 1
схема сохраняет свое старое состояние и выходы не обновляются.
Теперь рассмотрим вариант с nR = 1
и nS = 0
. Так как на входе верхнего элемента NAND
точно есть хотя бы один ноль, то его выход в любом случае будет равен логической единице. Значит, Q = 1
и, следовательно, nQ = 0
. Аналогично, при nR = 0
и nS = 1
мы можем схожим образом вывести, что состояние схемы будет полностью противоположным (Q = 0
и nQ = 1
).
Остается разобрать заключительный вариант, где оба входа равны нулю одновременно. На интуитивном уровне можно уже предполагать, что тут что-то не так. Действительно, при nR = nS = 0
результат элемента NAND
не может быть положительным ни при каких возможных значениях дополнительного входа (рекомендую проверить по таблице истинности). Следовательно, Q = nQ = 0
и это единственный случай, когда наша схема «сбоит». В дальнейшим мы ее лучшим и обязательно избавимся от этого недостатка.
Но сейчас самое время остановиться и перевести дух. Выше были не самые тривиальные рассуждения и если ты чего-то не понял, то это совершенно нормально. Последовательностные схемы сложнее для восприятия и именно поэтому в предыдущей статье мы начали знакомство именно с комбинационной логики.
Поэтому советую перечитать несколько абзацев выше еще раз. Тем более, что это ключевая схема и далее в статье многие элементы будут основаны именно на ней. А вообще, самый правильный способ разобраться в любой схеме раз и навсегда — это взять ручку и листочек бумаги (или стилус и планшет) и последовательно рассмотреть каждый из возможных вариантов. Я сужу по личному опыту — в твоем случае может сработать что-то еще.
Практическое применение
Надеюсь, теперь принцип работы RS-триггера для тебя больше не секрет и тебе уже не терпится применить полученные знания на практике. Для этого понадобится микросхема IW4011, которая содержит четыре вентиля NAND
(подойдет и любой зарубежный аналог).
Наверняка ты уже знаком с таким неприятным явлением в электронике, как дребезг контактов. Он проявляется всякий раз, когда участки цепи коммутируются механически: при нажатии тактовой кнопки, переключении тумблера, реле и во многих других случаях. Как правило, с дребезгом предпочитают бороться программным способом: кратковременные изменения в сигнале (момент соприкосновения контактов) просто игнорируются устройством.
Это распространенный и достаточно эффективный способ: реализацию подобной функции можно найти и в Arduino (библиотека Bounce2). Однако у такого метода есть и свои минусы: обработку сигнала приходится задерживать на какое-то время (порядка нескольких десятков миллисекунд). Во многих случаях это не становится большой проблемой.
Но что, если нажатие на кнопку обрабатывается в прерывании? Или мы проектируем клавиатуру для геймеров и хотим получить минимальное время отклика? Да, «лишние» миллисекунды конечно не выведут тебя на первые строчки в ладдерах популярных игр, но некоторое преимущество точно обеспечат. Попробуем применить здесь RS-триггер и нарисуем такую схему.
Мы используем два вентиля NAND
, то есть только «половину» микросхемы CD4011 (К561ЛА7). Обрати внимание, это не стандартная тактовая кнопка: тут работает перекидной контакт без фиксации, так что в каждый момент времени коммутируются ровно два вывода. Приятная особенность таких кнопок в том, что при нажатии они издают слабый щелчок, похожий на звук переключателей в механической клавиатуре.
Аппаратные решения хороши своей простотой и безотказностью — программа может зависнуть или исчерпать доступную память, тогда как с триггерами таких проблем нет.
Продвинутый D-триггер
Освоившись с установкой и сбросом внутреннего состояния в простой схеме, перейдем к более интересным вещам. Как и в программировании, теперь мы можем повторно использовать готовые логические блоки, чтобы эффективно абстрагироваться от возрастающей сложности устройств. Это примерно как с кодом — единожды реализовав нужную функцию или алгоритм, ты можешь добавлять их в необходимые места, не заботясь о стеке вызовов и конкретных адресах в памяти.
Как ты помнишь, ключевое неудобство в RS-триггере вызывал тот факт, что при nR = nS = 0
мы получали одинаковое состояние на прямом (Q
) и, казалось бы, инверсном (nQ
) выходе. Попробуем избавиться от этой проблемы. Для этого будем явно блокировать один из сигналов, если другой в этот момент активен.
Теперь состояние входа данных (D
) через пару вентилей NAND
подается на оба входа RS-триггера, а сигнал разрешения (E
) контролирует момент времени, когда состояние правой половины схемы может меняться.
Можно пойти еще дальше и объединить две защелки в один триггер, который будет срабатывать только по переднему фронту тактового сигнала (переход с низкого уровня в высокий). Это наиболее универсальная схема, так как позволяет мгновенно зафиксировать состояние всех сигналов на входных линиях, а уже потом приступить к формированию результата с помощью комбинационной логики.
Практическое применение
В 4000 серии пару D-триггеров реализует микросхема CD4013 (отечественный аналог — К561ТМ2). Назначение ее выводов ты можешь увидеть на схеме ниже. Обрати внимание, что, помимо входов данных и тактового сигнала, тут выведены контакты для асинхронного сброса и установки внутреннего состояния триггера.
Асинхронность означает, что событие происходит мгновенно, не дожидаясь следующего тактового сигнала. И наоборот, обновления с линии данных происходят синхронно с изменениями на линии CLK
.
Таким образом, комбинируя микросхемы CD4011 (561ЛА7) и CD4013 (561ТМ2) можно собрать схему с тактовой кнопкой и светодиодом, которая будет помнить свое состояние. Первое нажатие заставит светодиод гореть, второе нажатие его погасит. При этом нам даже не потребуется микроконтроллер и мы не напишем ни строчки кода!
Кодовый замок на регистрах
Теперь пришло время для более серьезных вещей. Я собрал на макетной плате кодовый замок на микросхемах 561 серии. Часть из них ты уже знаешь, другую часть мы разберем в процессе.
В первую очередь следует определиться с вводом. Здесь шесть кнопок для первых шести цифр 1 - 6 (больше не поместилось) и кнопка сброса. Сигнал с них подается на RS-триггеры, что помогает справиться с дребезгом контактов. Далее у нас есть четыре микросхемы К561ТМ2, это дает нам восемь бит информации о состоянии устройства.
Обрати внимание, что первая группа D-триггеров связана последовательно: инверсный выход через вентиль NAND
блокирует тактовый сигнал следующего. Таким образом, пока не будет верно подобрана первая цифра ключа, остальная схема не будет реагировать на нажатия. Кроме того, на этот же вентиль подается инверсный выход самого триггера, так что как только нужная кнопка будет нажата в первый раз, блок отключается и далее внутреннее состояние D-триггера уже не меняется.
Отдельного рассмотрения заслуживает пара D-триггеров U3A
и U6A
. У них общий тактовый сигнал, а вход данных второго зависит от выхода первого. Так как изначально все триггеры сброшены в ноль, то даже после однократного нажатия нужной кнопки выход второго не изменится и последний из триггеров будет все также заблокирован (иными словами, ноль из U3A
перекочует в U6A
, но глобально это ничего не меняет).
Однако, первый регистр из пары все-таки поменяет свое состояние, так как по приходу тактового сигнала на его входе данных содержится логическая единица от инверсного выхода второго. Значит, на его выходе уже будет высокий уровень и нам потребуется еще один тактовый сигнал (нажатие на кнопку), чтобы протолкнуть «единичку» во второй регистр. Таким образом, можно сделать вывод, что одну из кнопок требуется нажать два раза подряд, после чего разблокируется последний D-триггер.
Кстати, трехвходовые вентили NAND
для разрешения подачи тактового сигнала на D-триггер содержатся в микросхемах К561ЛА9 (по три штучки на корпус).
Разумеется, если взять такую схему отдельно, в ней будет существенный изъян: можно подобрать ключ, просто перебирая все кнопки одна за другой. В какой-то момент времени мы угадаем первую верную цифру, после чего перейдем ко второй, третьей и так далее.
Поэтому нам нужна возможность сброса состояния всех D-триггеров, как только превышено разрешенное количество попыток. Для этого требуется в первую очередь организовать подсчет, регистрируя нажатие любой цифры. Поэтому я взял парочку четырехвходовых вентилей NOR
(К561ЛЕ6).
Логический ноль на их выходе образуется всякий раз, когда нажата какая-либо из подключенных кнопок. Объединим выходы вентилей с помощью операции И-НЕ. При этом сработает такое правило: инвертор на выходе логического элемента можно переместить на все его входы, но при этом поменяется и сам тип элемента (NOR
переходит в NAND
и наоборот). Таким образом, NAND
на выходе можно рассматривать как NOR
, а инверторы на его входах оказываются рядом с инверторами на выходах блоков NOR
и взаимно аннигилируют. В результате мы получаем один большой элемент OR
, что и требовалось для нашего счетчика.
Кстати, схема самого счетчика выглядит так.
Он построен на базе трех D-триггеров, так что у нас три бита и восемь возможных состояний. Инверсный выход первого блока подается на вход, так что по восходящему фронту тактового сигнала BIT0
внутреннего состояния переходит из ноля в единичку (и наоборот). При этом выход соединен с CLK
следующего и каждое второе переключение приводит к изменением для BIT1
.
Далее нам остается только отслеживать комбинацию 101
для пяти разрешенных попыток и подать сигнал сброса, если последний из D-триггеров, хранящих наш секретный код, все еще не оказался в состоянии логической единицы (случай правильной комбинации).
Работу всей схемы можно посмотреть на видео. Успешный код активирует таймер на микросхеме КР1006ВИ1, которая демонстрирует анимацию на светодиодной шкале.