[Описание и оглавление серии постов ] В этой серии будем "пробовать на зуб" азбучные истины плисоводства, раз за разом понимая: они не просто так появились, позволяют избежать множества ошибок и не закапываться чересчур глубоко, но если быть аккуратным, можно их и нарушить :)
Все части на этот момент: Генератор зла - об использовании "логики общего назначения" для генерации тактовой частоты; Великая перезагрузка - нужен ли аппаратный Reset и как без него обойтись; Зазеркалье отрицательных фронтов
Как выстрелить себе в ногу на VHDL: Вы аккуратно описываете компоненты «нога», «рука», «пистолет» и «пуля». Любовно их отлаживаете и моделируете по отдельности. После синтеза всей системы обнаруживается, что нога и пистолет активируются на чётных тактах сигнала синхронизации, а рука и пуля на нечётных. (автор неизвестен, гуляет с незапамятных времён)
Очередная прописная истина: все переключения в схеме должны происходить по ФРОНТУ тактового импульса. То, что в verilog называется posedge clk (т.е positive edge, положительный фронт), в новом VHDL: rising_edge(clk), а в более старом VHDL (единственный, который поддерживается в Quartus II 9.0) это называется (clk'event AND clk = '1').
В коде можно прописать и СПАД тактового импульса, negedge clk в верилоге, falling_edge(clk) в новом VHDL, (clk'event AND clk = '0') в более старом, но куда ни глянь, просят ТАК НЕ ДЕЛАТЬ. Дескать, да, оно даже отсинтезируется (в большинстве ПЛИС) всеми правдами и неправдами, но ВАМ ЭТОГО НЕ НАДО, А ЕСЛИ ВЫ СЧИТАЕТЕ, ЧТО ЭТО ВАМ НАДО, ЗНАЧИТ ВЫ ЧТО-ТО НЕ ПОНИМАЕТЕ. Или вы решили подключить память DDR, что означает Double Data Rate, то есть как раз-таки переключение данных как по фронту, так и по спаду (это когда-то позволило по тем же проводам и на той же тактовой частоте передавать вдвое больше данных, что для памяти компьютера очень пользительно), но тогда "не мне вас учить, сами всё знаете". Хотя сейчас и в этой ситуации скорее посоветуют приобрести IP-блок, заточенный под общение с требуемым устройством, а дальше общение с этим IP-блоком уже будет происходить по фронтам.
Я сам до недавнего времени пребывал в заблуждении относительно того, как работают триггеры внутри ПЛИС, и на основе этого заблуждения приходил к выводу, что "смешивать" фронты и спады - вещь нетривиальная, и лучше этого не делать.
Но на этой неделе наконец-то разобрался, и обнаружил, что не так уж это и страшно, и иногда позволяет решить задачу проще и элегантнее. Обеспечить все тайминги OLED-экранчика, инициализировать его по инструкции и впридачу вывести текст (хранящийся здесь же), и всё это на 16 макроблоках? Легко:
А для начала немножко поговорим о триггерах, в институте и учебниках зачастую излагают немножко устаревшую информацию... В общем-то правильную, шибко нового я мало скажу, но расставлю акценты по-своему...
Разумеется, начинают всегда с этого друга, RS-триггера на двух элементах И-НЕ:
Пока на оба входа подаётся лог. "1", схема вообще "превращается" в два инвертора, закольцованные друг на друга, а два инвертора - это просто буфер. Когда его вход соединяешь с выходом, у такой штуки два устойчивых состояния: лог "1" и лог "0", и он способен сохранять 1 бит информации. А заменив инверторы на элементы И-НЕ, мы можем установить его (в лог "1") и сбросить (в лог "0").
Дальше к входу этого триггера пририсовывают ещё немножко логики, превращая его в D-триггер-защёлку:
В общем, да, в ТТЛ логике именно так всё и делалось. Базовый элемент ТТЛ-логики (Транзисторно-Транзисторной Логики на биполярных транзисторах NPN) - это именно И-НЕ, он наиболее просто реализуется, и всё остальное стараются построить на нём. Ну и в целом, известный факт, что на И-НЕ можно реализовать вообще любую логику! Как и на ИЛИ-НЕ, а вот на других - проблематично...
Но сейчас ТТЛ логики почти не осталось, все перешли на КМОП, Комплементарные транзисторы структуры Металл-Окисел-Полупроводник, они же CMOS. В них тоже базовыми элементами можно назвать инвертор, И-НЕ и ИЛИ-НЕ (там они очень симметрично получаются), и вполне можно было бы триггеры строить по тем же схемам. Но там есть ещё один офигительный элемент: аналоговый ключ!
Он реализуется на одном или двух транзисторах, и позволяет просто по сигналу коммутировать между собой две точки. Поставить два таких ключа - и будет уже переключатель. Так вот, насколько мне известно, сейчас простейший D-триггер собирается по такой схеме:
Это не так уж и важно, работают они в итоге одинаково, но в такой схеме разобраться проще! Тут вся сущность этого триггера понятна почти без слов. Когда переключатель стоит в позиции по умолчанию (как нарисовано), выход буфера соединён с его входом, поэтому, если там хранилась единица - она так и останется единицей, хранился нолик - останется ноликом. А вот когда подаёшь clk=1, схема превращается в самый простой буфер: любой входной сигнал дублируется на выход. Важно, что в момент переключения clk назад в ноль схема сохранит самое последнее значение, которое "через него проходило".
Такой триггер имеет много имён у нас и "у них": триггер-защёлка (latch), прозрачный триггер (поскольку при clk=1 пропускает сигнал с входа на выход), триггер, срабатывающий по УРОВНЮ.
Применение у них, безусловно, есть. Скажем, к нам идёт шина адреса и шина данных. Нам назначен адрес. Как только увидели, что данные адресуют НАМ - мы их ЗАЩЁЛКИВАЕМ ровно таким триггером, и где-то их используем. Хотя бы светодиодики включаем...
Но как только мы пытаемся реализовать более сложную логику, у нас возникает желание "соединить выход со входом". Что такое счётчик - он берёт старое своё значение, прибавляет к нему единичку - и сохраняет новое значение. Хотя бы даже простейший однобитный счётчик (который на одном такте переключается из нуля в единицу, на следующем - из единицы в ноль, и так далее) на триггере-защёлке исполнить невозможно! Это выглядело бы примерно так, к выходу триггера подключили инвертор, и его выход подали на вход:
Да, пока clk=0, он будет хранить какое-то значение, но как только включится clk=1, у нас вход соединится с выходом через инвертор. И тут ничего хорошего не выйдет. В части Генератор зла мы уже выясняли, что когда у ОДНОГО инвертора соединяешь вход с выходом, он просто выйдет "в линейный режим", аккурат между нулём и единицей. Если же у нас кольцо из ТРЁХ инверторов (а буфер - это обычно два инвертора друг за дружкой), начнётся генерация на высокой частоте! Так что вместо того, чтобы КОНТРОЛИРУЕМО переключиться ровно один раз, пока clk=1, эта хреновина самовозбудится, и когда clk снова вернётся к нулю, сохранится произвольное значение, то ли ноль, то ли единица. кстати, по такому принципу можно на ПЛИС генерировать ДЕЙСТВИТЕЛЬНО СЛУЧАЙНЫЕ числа, но нам пока не до них, научиться бы счётчик собирать :)
Самым универсальным и надёжным способом избавиться от такого нехорошего поведения оказалось соединение двух защёлок друг за другом:
Как-то так вышло, что я даже не пытался вникать в подробности, как оно работает, ОБЩИЙ СМЫСЛ был ясен. Один продолжает выдавать старое значение, в другой мы заносим преобразованное значение, а затем второй защёлкиваем в первый, и вот тогда, наконец, всё заработает!
Можно сравнить со шлюзовой камерой: нельзя открывать СКВОЗНОЙ ПРОХОД, он соединит входы и выходы комбинаторной логики! Поэтому такой триггер уже НЕПРОЗРАЧНЫЙ.
А раз уж тактовая частота называется clock, он же clk, т.е "часы", то ещё можно вспомнить анкер:
Ведь его назначение примерно то же самое: обеспечить КОНТРОЛИРУЕМОЕ приращение на одну позицию на каждый "период тактовой частоты", в данном случае её обеспечивает маятник. Правда, анкер участвует ещё и в генерации самих колебаний, сообщая маятнику энергию от спускающегося грузика. Но смысл в том, что с одной собачкой мы бы не справились, стоило бы ей отойти, как храповик начал бы неконтролируемо разгоняться!
У такого триггера тоже множество названий. "У них" его называют Flip-Flop или просто FF, а ещё это триггер СРАБАТЫВАЮЩИЙ ПО ФРОНТУ (а не по уровню), и этот факт подчёркивают на схемах, рисуя треугольничек у вывода clk. Ещё его могут назвать двухступенчатым триггером, или триггером "мастер-помощник", а "у них" это было master-slave, но сейчас, наверное, уже нет. ВСЕ ТРИГГЕРЫ В ПЛИС ИМЕННО ТАКИЕ! Хотя половинку триггера можно отключить, и использовать как защёлку, но довольно редко это нужно.
Однажды я почему-то решил, что "полное срабатывание" такого триггера занимает половину такта. И это правда: по фронту тактовой частоты у нас данные защёлкиваются в первую половинку триггера, и только по спаду - во вторую. А если так, то использовать в одном проекте триггеры, работающие по фронту и работающие по спаду - так себе идея, ведь у одного только-только данные на выходе появились, и МГНОВЕННО они должны появиться на входе другого, без какой-либо паузы!!!
Но если мы наконец-то посмотрим внимательно, как такой триггер работает, то обнаружим, всё проще:
Итак, только начинается clk=0. Ключи в двухступенчатом триггере, как изображено сверху рисунка. Первая половинка выступает буфером, пропуская всё "насквозь". Идут переходные процессы (распространение сигналов по комбинаторной логике, которая бывает весьма и весьма протяжённое), и пускай идут. Главное, чтобы к фронту они все уже завершились. Именно когда происходит переключение в clk=1, последнее значение на входе "защёлкивается" в первой половинке триггера.
Дальше смотрим вниз рисунка. Значение хранится в первой половинке триггера, а вторая половинка уже "забыла" старое значение и сейчас ПРОСТО ТРАНСЛИРУЕТ НОВОЕ ЗНАЧЕНИЕ! Вот этот факт я в своё время не уяснил, что правильное значение на выходе появляется буквально через несколько наносекунд после фронта тактовой частоты! Спад тактового импульса выполняет очень важную роль - в этот момент хранение данных передаётся с первой на вторую половинку триггера, но НА ВЫХОДЕ ЭТОГО ВООБЩЕ НЕ ЗАМЕТНО!
А значит, взять данные с выхода одного триггера, работающего по фронту - и подать их на вход другого, работающего по спаду - вполне нормальное явление, не являющееся криминалом "как таковое". Да, при этом на распространение сигнала отводится вдвое меньше времени, лишь половинка такта. Но почему бы и нет... Скажем, у нас в схеме есть 32-битный сумматор с обычным переносом, идущим через все биты от младшего к самому старшему, и он определяет максимальную частоту, на которой наш проект способен работать. Там мы, разумеется, будет работать исключительно по фронтам. А какая-нибудь шустрая логика при этом вполне способна успеть сделать свои делишки за половину такта.
Обещанный пример использования: подключение OLED-экранчика winstar weh001602, хотя интерфейс практически один-в-один и с ЖК-экранчиками, см. QuatCore: подключаем ЖК-экранчик. Идёт 8 бит данных DB0..DB7 (data bit), плюс 1 бит RS выбора между данными и командами, плюс выбор RW "чтение/запись" (который мы опять заземлили, чтобы всегда была запись) и ещё один провод E - "строб". Вот тайминги, которые мы должны соблюдать:
"Алгоритм" примерно такой: 1. устанавливаем RS, 2. через заданный интервал (или дольше) ставим E=1, 3. устанавливаем данные DB0..DB7, 4. через заданный интервал (или дольше) ставим E=0, 5. выдерживаем ещё некоторую паузу, и только после этого можем устанавливать новые значения RS и DB0..DB7.
Паузы там совсем небольшие, по 10..20 нс, но НАМ ОТ ЭТОГО НЕ ЛЕГЧЕ. Это означает, что КАКУЮ-ТО ЗАДЕРЖКУ МЫ ВНЕСТИ ОБЯЗАНЫ!
классический подход: работать строго по тактам. Первый такт: загрузили RS и DB0..DB7, ждём. Второй такт: переключили E=1. Третий такт: переключили E=0. И далее по накатанной. Тут уже впору целый конечный автомат припахать, с тремя состояниями, я такие реализации видел :)
А минимальный отрезок, который мы можем отсчитать - это половина такта. Т.е, мы можем: 1. по фронту clk установить RS и тут же DB0..DB7, "чтобы два раза не ходить", 2. спустя полтакта (по спаду clk) установить E=1, 3. ещё спустя полтакта снять E=0. Это уже следующий фронт clk, и по нему мы устанавливать RS и DB0..DB7 НЕ ИМЕЕМ ПРАВА. Значит, в один такт мы не вписались, НУЖНО МИНИМУМ 2 ТАКТА! 4. выжидаем ещё целый такт - и начинаем всё сначала!
Этого можно было бы достичь, объединяя по AND значение регистра и тактовую частоту, и тогда мы всё сможем сделать исключительно по фронту:
module stupidLCDinterface (input clk, output reg ena = 1'b0, output E, output reg [addrWidth-1:0] adr = {addrWidth{1'b0}});
Как будто бы нас всё устраивает, но: - мы истратили 1 ЛЭ на регистр ena и ещё один ЛЭ, чтобы объединить его выход с clk, - рискованно подавать на выход комбинаторный сигнал, в нём может быть дребезг. Здесь нам на симуляции его не показали:
Здесь первым переключился clk, а только потом ena. Если бы сигнал clk "запоздал" к логическому элементу AND, было бы хреново, возник бы очень короткий пик, и кто знает, вдруг экранчик среагировал бы на него. Хотя, именно цепи clk должны быть самыми шустрыми в ПЛИС, так что как будто бы всё нормально идёт. Но переживать за эти считанные наносекунды как-то совсем не хочется.
Благо, есть решение, лучшее по всем показателям! Вот оно:
Выход строба E теперь регистровый, так что точно никакого дребезга. И весь этот модуль занимает 5 ЛЭ. 4 из них - счётчик от 0 до 15, это чтобы отправить последовательность байт из "ПЗУ". И пятый - этот самый E, причём от него также уйти было невозможно. Если есть ножка, значит, и логический элемент должен быть, который ей управляет! То есть это абсолютный минимум, на который вообще можно было рассчитывать!
Правда, мы видим одну подлянку, которая здесь возникает: мы "ноль" прохлопали, не передали! Чтобы он передался, тактовая частота должна была начаться со спада.
Наконец, представлю своё "ПЗУ":
module HelloBigWolf (input [addrWidth-1:0] adr, output [7:0] Q, output RS);
И всё вместе: генератор зла + светодиодик, и пока, для наглядности, тактовую частоту берём прямо с него, поразительные 0,6 герца:
Инвертор поставил ровно для того, чтобы "ноль" не пропустить, передать тоже. Смотрим на симуляции (только не на 0,6 Гц всё же-таки, это было бы издевательством):
Синтезируется это дело ровно в 32 макроблока из 32 возможных, заполнение 100% :) 1 макроблок мы используем для генерации тактовой частоты. Ещё 14 - это делитель частоты для светодиода. Ещё 1 мы пока что впустую истратили на ножку RW. Она тупо "заземлена", но CPLD так устроены, макроблок на это всё равно тратится! Так что на хранение байтов в нашей самопальной "ПЗУ" и на всю логику инициализации и формирования сигналов ушло ровно 16 макроблоков, как я и обещал!
Наконец, покажу, как оно работает на 0,6 Гц, это интересно:
(тут ещё не весь текст я ввёл. Как все 2 строчки набираются, тоже заснял, но уже стемнело к тому времени, уж больно насыщаться начало, не так красиво)
Каждая вспышка светодиода - это СПАД тактовой частоты. Можно прямо проследить, как экранчик реагирует на каждую команду. Сначала "молчит", потом появляется "мусор", потом (на очистке экрана) он пропадает. И наконец, ввод каждого символа проходит В ДВА ЭТАПА. Когда мы только определили, что будем выдавать символ, а не команду (т.е RS = 1) и выставили E=1, экран уже на это реагирует, сдвигая курсор! А когда мы выставляем E=0, уже защёлкивается очередной символ!
Мне кажется, формирование правильных таймингов "малой кровью" - это не единственное применение отрицательного фронта, но сходу назвать другие применения пока не могу, не успел об этом громко подумать, разработать "философию".