Найти тему
12,2K подписчиков

От аппаратных решений к программным. Часть 2. Триггеры, обратные связи и порядок вычислений

544 прочитали

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

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

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

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

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

Но тема сегодняшней статьи - триггеры. На примере которых я и проиллюстрирую некоторые возникающие сложности. Для этого сначала рассмотрим простейший RS-триггер на двух элементах 2И-НЕ.

RS-триггер и его работа в аппаратном виде

Входы R и S являются инверсными, что не показано на иллюстрации
Входы R и S являются инверсными, что не показано на иллюстрации

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

Речь идет о конечной скорости переключения логических элементов. По этой причине сигналы на прямом и обратном выходе триггера изменятся не одновременно. Предположим, что время переключения элемента равно 10 нс (длительности фронтов учитывать не будем).

Задержки при переключении RS-триггера. Иллюстрация моя
Задержки при переключении RS-триггера. Иллюстрация моя

Пусть в начальный момент времени триггер был сброшен (Q=0). При поступлении на вход S импульса на выходе Q установится уровень логической 1. Но между переходом 1->0 на входе S и переходом 0->1 на выходе Q пройдет 10 нс. Выход Q соединен с одним из входов второго логического элемента, что вызовет и изменение уровня на выходе Q1. Но время переключения составит еще 10 нс.

Таким образом, полное переключения триггера займет 20 нс, из которых в течении 10 нс на его выходах будет "запрещенная" комбинация сигналов (Q=1 Q1=1).

RS-триггер и его работа в виде программной эмуляции

Состояние триггера описывает система двух логических уравнений

В предыдущей статье я рассмотрел в качестве простого примера фрагмент схемы с не менее простой программной эмуляцией.-3

Пока забудем о запрещенном состоянии входов (R=0 S=0). Каждое уравнение соответствует логической функции выполняемой соответствующим логическим элементом. И порядок вычисления этих уравнений оказывается важным при программной эмуляции триггера.

Задержке переключения логического элемента при программной эмуляции соответствует длительно одного шага эмуляции. По той простой причине, что состояние элементов схемы (переменных состояния) непрерывно циклически перевычисляется. И измениться состояние может только во время пересчета. Точно так же, как работает динамическая синхронная логика.

Давайте посмотрим на программную эмуляцию работы такого триггера

Пример программной эмуляции работы RS-триггера. Иллюстрация моя
Пример программной эмуляции работы RS-триггера. Иллюстрация моя

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

Порядок вычисления в этом фрагменте программы такой же, как в записи системы уравнений. При импульсе на входе S у нас будет корректно вычислено состояние выхода Q, так как состояние переменных S и Q1 известно и корректно. Вычисление Q1 в следующей строке тоже не вызовет проблем.

Но давайте посмотрим, что произойдет при поступлении сигнала сброса на вход R. По прежнему сначала будет вычисляться состояние выхода Q. Но теперь это вычисление никак не учитывает того факта, что состояние выхода Q1 стало не корректным, так еще не учтено влияние сигнала сброса. В результате, у нас не изменится, на данном шаге эмуляции, состояние выхода Q, что является ошибкой.

Вычисление состояния Q1 уже учтет изменение состояния входа R и установит Q1=1, что является верным. Таким образом, к окончанию первого шага эмуляции у нас оказывается ошибочное состояние выхода Q и корректное выхода Q1. В целом для триггера такой результат эмуляции является неверным.

На следующем цикле эмуляции состояние выхода Q тоже станет верным, так как при вычислении Q состояние Q1 уже корректное. По большому счету, такое поведение программной модели примерно соответствует поведению аппаратного триггера, когда оба выхода принимали на 10 нс состояние логической 1. Но длительность такого некорректного состояния гораздо больше и равна длительности цикла эмуляции.

Временная диаграмма программной эмуляции работы RS-триггера. Иллюстрация моя
Временная диаграмма программной эмуляции работы RS-триггера. Иллюстрация моя

Обратите внимание, что для аппаратной реализации триггера временные диаграммы установки и сброса показывали бы одинаковое время задержки. А вот при программной эмуляции поведение стало несимметричным по причине влияния порядка вычисления.

Такое поведение "программного" триггера может являться недопустимым и привести к неработоспособности модуля (который мы и заменяем программным вариантом) в целом.

Что же делать? Признать программную эмуляции неработоспособной? Безусловно это не так. Данный пример показывает те проблемы, которые я обозначил в начале статьи, но для эмуляции компонентов схемы есть общее и довольно простое решение. Мы просто будем описывать поведение сложных компонентов схемы как поведение функциональных блоков. То есть, в целом, а не как набор отдельных элементов.

Ну а раз начали мы с RS-триггера, то давайте для него первого и используем этот подход.

RS-триггер как функциональный блок в программной эмуляции

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

Пример программной эмуляции RS-триггера как функционального объекта. Иллюстрация моя
Пример программной эмуляции RS-триггера как функционального объекта. Иллюстрация моя

Теперь мы можем просто установить состояния сигналов на входах и вызвать функцию rs_trigger, которая и будет эмулировать поведение триггера как функционального объекта.

D-триггер как функциональный блок в программной эмуляции

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

Дело в том, что реальный D-триггер может быть прозрачным при определенном уровне на входе стробирования (фактически, входе разрешения загрузки) и фиксировать состояние выходов по перепаду. Примером такого триггера является 155ТМ5 (74HC77). Или непрозрачным, когда состояние выхода может измениться только по перепаду на входе строба. Примером такого триггера является микросхема 1554ТМ2 (74HC74).

Пример временной диаграммы работы прозрачного D-триггера (155ТМ5). Иллюстрация моя
Пример временной диаграммы работы прозрачного D-триггера (155ТМ5). Иллюстрация моя

Хорошо видно, что для прозрачного триггера сигнал на выходе фиксируется на уровне входного сигнала по отрицательному перепаду на входе С и остается неизменным, пока на входе С низкий уровень. А при высоком уровне на входе С сигнал проходит без изменения на выход. Такой вариант D-триггера еще называют защелкой.

Пример временной диаграммы работы не прозрачного D-триггера (155ТМ2). Иллюстрация моя
Пример временной диаграммы работы не прозрачного D-триггера (155ТМ2). Иллюстрация моя

Хорошо видно, что для непрозрачного триггера сигнал на выходе фиксируется равным сигналу на входе D по положительному перепаду на входе С. Все остальное время сигнал на выходе остается неизменным.

И мы должны уметь эмулировать оба варианта. При этом можно заметить, что на первый взгляд состояние триггера на момент начала вычисления опять не имеет значения. Но вот точно ли не имеет? Для прозрачного триггера предыдущее состояние действительно не имеет значения, а вот для не прозрачного мы должны как то отслеживать переход 0->1 сигнала С. А для этого мы должны запоминать предыдущее состояние сигнала C.

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

Пример программной реализации прозрачного D-триггера. Иллюстрация моя
Пример программной реализации прозрачного D-триггера. Иллюстрация моя

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

Для непрозрачного триггера программная реализация будет немного сложнее, из-за необходимости отслеживания именно перехода 0->1 на входе С.

Пример программной реализации не прозрачного D-триггера. Иллюстрация моя
Пример программной реализации не прозрачного D-триггера. Иллюстрация моя

Как видите, усложнение совсем не большое. В этом примере я не стал устанавливать начальное состояние для _C (предыдущее состояние на входе С), понадеявшись на компилятор, который вставит код очистки переменных перед началом кода программы.

Т-триггер (счетный триггер) как функциональный блок в программной эмуляции

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

Пример программной реализации счетного Т-триггера. Иллюстрация моя
Пример программной реализации счетного Т-триггера. Иллюстрация моя

Здесь тоже нет ничего сложного. По положительному перепаду на входе С мы меняем состояние выхода на противоположное. То есть, в точности так, как работал бы D-триггер с подключенным к инверсному выходу входу D.

JK триггер

JK-триггер похож по своему поведению на синхронный RS-триггер, но при высоком уровне одновременно на входах J и K превращается в счетный триггер.

Пример программной реализации JK-триггера. Иллюстрация моя
Пример программной реализации JK-триггера. Иллюстрация моя

Этот пример уже посложнее, поскольку и поведение триггера более сложное. В данном примере триггер отрабатывает положительный перепад на входе С. Дополнительно, мы обрабатываем состояние входов J и K.

Заключение

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

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

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

До новых встреч!