Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Industrial programming >> VHDL

Как сделать AXI FIFO в блочной ОЗУ, используя готовое/действующее рукопожатие

Меня немного раздражали особенности интерфейса AXI, когда мне впервые пришлось создавать логику для интерфейса модуля AXI. Вместо обычных управляющих сигналов «занят/действителен», «полный/действителен» или «пустой/действителен» интерфейс AXI использует два управляющих сигнала с именами «готов» и «действителен». Мое разочарование вскоре сменилось благоговением.

Интерфейс AXI имеет встроенное управление потоком без использования дополнительных управляющих сигналов. Правила достаточно просты для понимания, но есть несколько подводных камней, которые необходимо учитывать при реализации интерфейса AXI на ПЛИС. В этой статье показано, как создать AXI FIFO в VHDL.

AXI решает проблему задержки на один цикл

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

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

К сожалению, интерфейсный модуль не может останавливаться во времени, пока он использует только синхронизированную логику. FIFO поднимает full флаг точно на переднем фронте часов. Одновременно интерфейсный модуль пытается записать следующий элемент данных. Он не может сэмплировать и реагировать на full сигнализируй, пока не поздно.

Одним из решений является добавление дополнительных almost_empty сигнал, мы сделали это в учебнике Как создать кольцевой буфер FIFO в VHDL. Дополнительный сигнал предшествует empty сигнал, давая интерфейсному модулю время среагировать.

Готовое/действительное рукопожатие

Протокол AXI реализует управление потоком, используя только два управляющих сигнала в каждом направлении, один из которых называется ready. и другие valid . ready сигнал контролируется приемником, логический '1' значение этого сигнала означает, что приемник готов принять новый элемент данных. valid сигнал, с другой стороны, контролируется отправителем. Отправитель должен установить valid до '1' когда данные, представленные на шине данных, пригодны для выборки.

А вот и важная часть: передача данных происходит только тогда, когда оба ready и valid '1' в том же такте. Получатель сообщает, когда он готов принять данные, а отправитель просто помещает данные туда, когда ему есть что передать. Передача происходит, когда оба согласны, когда отправитель готов отправить, а получатель готов получить.

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

Реализация

Есть много способов реализовать AXI FIFO в VHDL. Это может быть регистр сдвига, но мы будем использовать структуру кольцевого буфера, потому что это самый простой способ создать FIFO в блочной ОЗУ. Вы можете создать все это в одном гигантском процессе, используя переменные и сигналы, или вы можете разделить функциональность на несколько процессов.

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

Объект

Объявление объекта включает общий порт, который используется для установки ширины входных и выходных слов, а также количества слотов для резервирования места в ОЗУ. Емкость FIFO равна глубине оперативной памяти минус один. Один слот всегда остается пустым, чтобы различать полный и пустой FIFO.

09

Первые два сигнала в объявлении порта — это входы синхронизации и сброса. Эта реализация использует синхронный сброс и чувствительна к переднему фронту тактового сигнала.

Существует входной интерфейс в стиле AXI, использующий готовые/действительные управляющие сигналы и сигнал входных данных общей ширины. Наконец, появляется выходной интерфейс AXI с такими же сигналами, как и на входе, только с обратными направлениями. Сигналы, принадлежащие интерфейсу ввода и вывода, имеют префикс in_. или out_ .

Выход одного AXI FIFO можно было подключить напрямую ко входу другого, интерфейсы идеально подходили друг к другу. Хотя лучшим решением, чем складывать их друг в друга, было бы увеличение ram_depth универсальный, если вы хотите увеличить FIFO.

Объявления сигналов

Первые два оператора в декларативной области VHDL-файла объявляют тип RAM и его сигнал. Объем ОЗУ динамически изменяется на основе общих входных данных.

18

Второй блок кода объявляет новый целочисленный подтип и четыре сигнала от него. index_type имеет размер, точно отражающий глубину оперативной памяти. head сигнал всегда указывает слот ОЗУ, который будет использоваться в следующей операции записи. tail signal указывает на слот, к которому будет осуществляться доступ при следующей операции чтения. Значение count signal всегда равен количеству элементов, хранящихся в настоящий момент в FIFO, и count_p1 является копией того же сигнала, задержанного на один такт.

26

Затем идут два сигнала с именами in_ready_i. и out_valid_i . Это просто копии выводов сущности in_ready и out_valid . _i постфикс просто означает внутренний , это часть моего стиля написания кода.

39

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

48

Подпрограммы

После сигналов мы объявляем функцию для увеличения нашего пользовательского index_type . next_index функция смотрит на read и valid параметры, чтобы определить, есть ли текущая транзакция чтения или чтения/записи. В этом случае индекс будет увеличен или завернут. Если нет, возвращается неизменное значение индекса.

56

Чтобы избавить нас от повторного ввода, мы создаем логику для обновления head и tail сигналы в процедуре, а не как два идентичных процесса. update_index процедура принимает часы и сигналы сброса, сигнал index_type , ready сигнал и valid сигнал в качестве входных данных.

69

Этот полностью синхронный процесс использует next_index функция для обновления index сигнал, когда модуль вышел из состояния сброса. При сбросе index signal будет установлено наименьшее значение, которое он может представлять, которое всегда равно 0 из-за того, как index_type и ram_type объявляется. Мы могли бы использовать 0 в качестве значения сброса, но я стараюсь максимально избегать жесткого кодирования.

Копировать внутренние сигналы на выход

Эти два параллельных оператора копируют внутренние версии выходных сигналов в фактические выходы. Нам нужно работать с внутренними копиями, потому что VHDL не позволяет нам читать сигналы объектов с режимом out. внутри модуля. Альтернативой было бы объявить in_ready и out_valid с режимом inout , но большинство стандартов кодирования компаний ограничивают использование inout сигналы сущности.

70

Обновите голову и хвост

Мы уже обсуждали index_proc процедура, которая используется для обновления head и tail сигналы. Сопоставляя соответствующие сигналы с параметрами этой подпрограммы, мы получаем эквивалент двух идентичных процессов, один для управления вводом FIFO и один для вывода.

87

Поскольку оба head и tail установлены на одно и то же значение логикой сброса, FIFO изначально будет пустым. Вот как работает этот кольцевой буфер, когда оба указывают на один и тот же индекс, это означает, что FIFO пуст.

Вычислить ОЗУ блока

В большинстве архитектур ПЛИС блочные примитивы ОЗУ являются полностью синхронными компонентами. Это означает, что если мы хотим, чтобы инструмент синтеза выводил блочную ОЗУ из нашего кода VHDL, нам нужно поместить порты чтения и записи внутрь синхронизированного процесса. Кроме того, не может быть значений сброса, связанных с блочной ОЗУ.

97

Нет разрешения чтения или разрешить запись здесь это было бы слишком медленно для AXI. Вместо этого мы непрерывно записываем в слот ОЗУ, на который указывает head индекс. Затем, когда мы определяем, что произошла транзакция записи, мы просто продвигаем head чтобы зафиксировать записанное значение.

Аналогично, out_data обновляется каждый такт. tail указатель просто перемещается на следующий слот, когда происходит чтение. Обратите внимание, что next_index Функция используется для вычисления адреса порта чтения. Мы должны сделать это, чтобы убедиться, что оперативная память достаточно быстро реагирует после чтения и начинает выводить следующее значение.

Подсчитать количество элементов в FIFO

Подсчет количества элементов в оперативной памяти — это просто вычитание head из tail . Если head завернуто, мы должны компенсировать это на общее количество слотов в оперативной памяти. У нас есть доступ к этой информации через ram_depth константа из общего ввода.

105 

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

112

Обновите готово вывод

in_ready сигнал должен быть '1' когда этот модуль готов принять другой элемент данных. Так и должно быть до тех пор, пока FIFO не заполнен, и это именно то, о чем говорит логика этого процесса.

127

Обнаружение одновременного чтения и записи

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

132

Обновить действительный вывод

out_valid сигнал указывает нижестоящим модулям, что данные, представленные на out_data действителен и может быть использован в любое время. out_data сигнал поступает непосредственно с выхода RAM. Реализация out_valid сигнал немного сложен из-за дополнительной задержки тактового цикла между вводом и выводом блока ОЗУ.

Логика реализована в комбинационном процессе, так что она может без задержки реагировать на изменяющийся входной сигнал. Первая строка процесса — это значение по умолчанию, которое устанавливает out_valid сигнал на '1' . Это будет преобладающее значение, если ни одно из двух последующих операторов If не будет запущено.

144

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

Рассмотрим форму волны ниже. Первоначально FIFO пуст, что обозначается кодом count. сигнал 0 . Затем запись происходит на третьем такте. Слот ОЗУ 0 обновляется в следующем такте, но требуется дополнительный цикл, прежде чем данные появятся на out_data выход. Назначение or count_p1 = 0 заявление, чтобы убедиться, что out_valid остается '0' (обведено красным), пока значение распространяется по ОЗУ.

Последний оператор If защищает от другого крайнего случая. Мы только что говорили о том, как справиться с особым случаем записи в пустой буфер, проверив текущий и предыдущий уровни заполнения FIFO. Но что произойдет, если и мы выполним одновременное чтение и запись, когда count уже 1 ?

На графике ниже показана такая ситуация. Первоначально в FIFO присутствует один элемент данных D0. Он был там некоторое время, так что оба count и count_p1 0 . Затем в третьем такте происходит одновременное чтение и запись. Один элемент покидает FIFO, а в него поступает новый, оставляя счетчики без изменений.

В момент чтения и записи в ОЗУ нет очередного значения, готового к выводу, как было бы, если бы уровень заполнения был выше единицы. Мы должны подождать два такта, прежде чем входное значение появится на выходе. Без какой-либо дополнительной информации было бы невозможно обнаружить этот угловой случай, и значение out_valid в следующем тактовом цикле (отмечен сплошным красным цветом) будет ошибочно установлено значение '1' .

Вот почему нам нужен read_while_write_p1 сигнал. Он обнаруживает, что имели место одновременные операции чтения и записи, и мы можем принять это во внимание, установив out_valid на '0' в этом такте.

Синтез в Vivado

Чтобы реализовать дизайн как автономный модуль в Xilinx Vivado, мы сначала должны задать значения для общих входных данных. Этого можно добиться в Vivado с помощью НастройкиОбщиеОбщие параметры/параметры меню, как показано на изображении ниже.

Общие значения были выбраны в соответствии с примитивом RAMB36E1 в архитектуре Xilinx Zynq, которая является целевым устройством. Использование ресурсов после внедрения показано на изображении ниже. AXI FIFO использует один блок ОЗУ и небольшое количество LUT и триггеров.

AXI более чем готов/действителен

AXI расшифровывается как Advanced eXtensible Interface и является частью стандарта ARM Advanced Microcontroller Bus Architecture (AMBA). Стандарт AXI — это намного больше, чем рукопожатие чтения/действия. Если вы хотите узнать больше об AXI, я рекомендую следующие ресурсы для дальнейшего чтения:

  • Википедия:AXI
  • Введение в ARM AXI
  • Введение в Xilinx AXI
  • Спецификация AXI4


VHDL

  1. Облако и как оно меняет мир ИТ
  2. Как максимально использовать свои данные
  3. Как инициализировать RAM из файла с помощью TEXTIO
  4. Как подготовиться к использованию ИИ с помощью Интернета вещей
  5. Как промышленный Интернет меняет управление активами
  6. Рекомендации по отслеживанию активов:как максимально использовать данные об активах, заработанных с трудом
  7. Как мы можем лучше понять Интернет вещей?
  8. Как максимально эффективно использовать Интернет вещей в ресторанном бизнесе
  9. Как данные позволяют использовать цепочку поставок будущего
  10. Как сделать данные цепочки поставок надежными