Архитектура ПЛИС. Часть 2. Мультиплексор

Шауэрман Александр А. shamrel@yandex.ru

Первая статья цикла:  Архитектура ПЛИС. Часть 1. Логический элемент

Для многих проектов на ПЛИС значительную часть используемых логических ячеек занимают мультиплексоры (Multiplexer – MUX). Всем известно, что мультиплексор представляет собой комбинационное устройство, коммутирующее несколько входов на один выход. Выбор входа определяют управляющие сигналы. При оптимизации логики вашего мультиплексора, вы сможете максимально эффективно реализовать его в ПЛИС Altera. В этом разделе рассмотрим основные проблемы и дадим решения для достижения оптимального использования ресурсов.

Для тех, кто перешел на язык описания схем после проектирования на дискретной логике, мультиплексор представляется неким фиксированным электронным компонентом: такой микросхемой, переключающей цифровые потоки. Однако при HDL описании схемы, особенно если это Verilog HDL, роль мультиплексора значительно шире и его можно встретить в не всегда ожидаемых местах. Если вы в своем коде используете оператор case, if или тернарный оператор выбора, будьте уверены, вы используете мультиплексор. Поэтому разберемся с основными типами мультиплексоров, встречающихся в Verilog описании схем и некоторыми особенностями их синтеза.

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

Бинарные мультиплексоры

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

Рисунок 1 – Таблица истинности и обозначение  мультиплексора 2:1 на функциональных схемах

Аналитически такой мультиплексор описывается формулой:

y(c_0, d_1, d_0) = \bar{c_0}\cdot d_0 + c_0\cdot d_1 (1)

Такой мультиплексор создается при однократном использовании оператора if или тернарного оператора выбора:

always @ *            // Если y – это reg
    if(c0) y = d1;
    else y = d0;

Или так:

assign y = c0 ? d1 : d0;    // Если y – это wire

Для синтеза этого мультиплексора понадобится одна ячейка 4-х входового LUT. Однако бинарный мультиплексор 4 в 1 имеет уже 6 входов (4 информационных и два управляющих) и не может быть синтезирован непосредственно на 4-LUT. В этом отношении серии ПЛИС с 6-и входовыми LUT находятся в более выигрышном положении, но в нашем Cyclone IV для увеличения коммутируемых каналов (входов) придется использовать каскадную структуру.

Рисунок 2 – Каскадное объединение мультиплексоров

Если реализовывать каскадное включение из трех мультиплексоров "в лоб", то для этого понадобится 3 LUT, при этом у каждого LUT останется неиспользованный вход. На практике для полного использования ресурса применяется взаимное вложение мультиплексоров. Такой подход интересен тем, что на выходе первой LUT (сигнал z, рисунок 3) в зависимости от значения c1 коммутируются либо информационные входы d0, d1, либо управляющий сигнал c0. В результате достигается полная утилизация ресурсов.

Рисунок 3 – Взаимное вложение мультиплексоров для синтеза на LUT

  (2)

      (3)

Таким образом, для ПЛИС, у которых LE основан на 4-LUT, мультиплексор 4:1 эффективно строится на двух LUT. Мультиплексор с большим количеством входов при синтезе разбивается на блоки 4-х входовых мультиплексоров, при необходимости в завершении ставится мультиплексор 2:1.

Пример Verilog модуля бинарного мультиплексора 4:1 (занимает две LUT):

Язык Verilog позволяет обратится отдельно к каждому биту в векторе по его номеру. Эта возможность даст еще одну реализацию бинарного мультиплексора.

В отличии от предыдущих реализаций информационные и управляющие входы объединены в вектор. При использовании такого модуля в экземпляре используется конкатенация (объединение):

	binary_mux  mux_inst
	(
		.din({d3, d2, d1, d0}),
		.sel({c1, c0}),
		.dout(y)
	); 

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

При использовании модуля параметр SEL_WIDTH можно установить в заголовке экземпляра модуля:

	binary_mux #(.SEL_WIDTH(2)) mux_inst
	(
		.din({d3, d2, d1, d0}),
		.sel({c1, c0}),
		.dout(y)
	);

Легко убедится, что при одинаковом числе коммутируемых входов на уровне RTL все варианты описания мультиплексора дают одинаковую схему.

Мультиплексор-селектор

Мультиплексор-селектор не имеет дешифратора: для каждого входа предназначена отдельная управляющая линия. Одновременно только на одной линии управления может быть установлена единица. Несмотря на простоту реализации на дискретных элементах на каждый вход ставится элемент 2-И, с выхода которых сигнал подается на элемент ИЛИ. При реализации в ПЛИС такой мультиплексор намного менее эффективен, чем двоичный. Из-за того, что на каждый вход требуется управляющая линия, для 4-х канального мультиплексора понадобится уже три 4-LUT.

Однако в некоторых случаях управляющие сигналы – это выход декодера, тогда компилятор попытается объединить селектор и дешифратор в двоичный мультиплексор.

Приоритетный мультиплексор

Судя по названию, в приоритетном мультиплексоре логика выбора подразумевает приоритет. Варианты для выбора правильного элемента должна быть проверена в определенном порядке на основе приоритета сигнала. Такая структура в Verilog в основном определяется операторами if, else, casex и ?: .

Например, такой модуль:

на уровне RTL (Register transfer level – уровень регистровых взаимодействий) будет иметь вид:

Рисунок 4 – Структура приоритетного мультиплексора на уровне RTL

Очевидно, что путь прохождения сигнала со входа d3 на выход y будет отличаться от пути с входа d0 на выход y. И в зависимости от числа мультиплексоров в цепи временная задержка при прохождении через такую цепь может стать слишком большой, особенно для ПЛИС с 4-LUT. Однако все не настолько плохо: при компиляции Quartus II стремится преобразовать структуру к древовидному виду, при этом использует прием взаимного вложения, как это было при синтезе бинарного мультиплексора 4:1. В результате технологическая карта (Technology Map) модуля из примера будет иметь вид:

Рисунок 5 – Структура приоритетного мультиплексора 4:1 на уровне Technology Map

Из рисунка 5 видно, что по прежнему самые длинные пути это d3→y и d2→y, но включают в себя не три, а два LUT. В этом случае, быстродействие схемы будет таким же, как и в аналогичном бинарном мультиплексоре, но при этом используется три LUT против двух у бинарного. Очевидно, что при увеличении числа коммутируемых входов у бинарного мультиплексора выигрыш в быстродействии и в занимаемых ресурсах будет увеличиваться, поэтому стоит избегать приоритетных мультиплексоров там, где приоритет не требуется. Если порядок выбора не важен, можно использовать case для построения бинарного мультиплексора. Если в проекте избежать приоритетного выбора нельзя, а задержки становятся критичными, можно постараться изменить логику работы, что бы уменьшить число логических уровней, особенно вдоль критического пути.

И напоследок, точно такой же по функционалу и реализации модуль может быть записан с использованием оператора casex следующим образом:

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

Неявный default

Часто бывает, что применение оператора if удобнее чем case. Однако, использование if может привести в результате к сложному дереву мультиплексоров, которые не могут быть легко оптимизированы инструментами синтеза. В частности, каждый if имеет неявный else, даже когда он не указан и компилятор будет обязан отработать эту ветвь. Такое неявное умолчание может стать причиной дополнительного усложнения синтеза мультиплексора, а в худших случаях привезти к синтезу Latch.

Рассмотрим пример приоритетного мультиплексора, но "упростим" схему, "забудем" последнюю ветвь else:

При компиляции такой модуль вместо 3 LUT занимает уже 4 LUT, а Quartus выдал следующие предупреждения:

Warning (332060): Node: c0 was determined to be a clock but was found without an associated clock assignment.
Warning (335093): TimeQuest Timing Analyzer is analyzing 1 combinational loops as latches.

Что-то пошло не так. Вход c0 компилятор воспринял как вход тактирования (это в комбинационной-то схеме!) и добавил петлю обратной связи, создав Latch. При этом компилятор реализовал лишь то, что хотел от него программист. Если значение переменной c0 равно единице, то на вход поступает d0; если c0 равно нулю, а c1 равно единице, то на выходе будет d1; если и c0,  и c1 – нули, а c1 – единица, то на выходе – d2. Но что должно быть на выходе, если все управляющие сигналы равны нулю? Компилятор делает единственно верный вывод – нужно сохранить предыдущее значение, а для этого синтезировать асинхронный (одноступенчатый) триггер, или, как его по другому называют, защелку (Latch).

Рисунок 6 – Структура полученного мультиплексора на уровне RTL

Рисунок 7 – Структура полученного мультиплексора на уровне Technology Map

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

При описании сложного мультиплексора с разветвленной иерархией на операторах if-else бывает трудно отследить все ветви, а код становится трудным для восприятия. В этом случае можно рекомендовать изменить структуру мультиплексора и попытаться реализовать его в качестве бинарного, используя оператор case. Однако и в бинарном мультиплексоре легко допустить ошибку. Достаточно "забыть" указать один из вариантов выбора.

Например так:

Как результат, при синтезе получим увеличение числа логических элементов, Latch и плохую предсказуемость при временном и функциональном анализе. Когда в мультиплексоре используется всего 4 варианта выбора, то, в принципе, легко проследить все варианты, но задача усложняется экспоненциально при увеличении числа коммутируемых входов, тем более, если не все сочетания управляющих сигналов функционально возможны или достаточно редко происходят. Спецификация Verilog имеет решение на этот случай, оператор case включает в себя обработку значения по умолчанию, для этого используется ключевое слово default. Перепишем предыдущий модуль следующим способом:

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

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

Многоразрядный мультиплексор

Под многоразрядными мультиплексорами мы будем подразумевать мультиплексоры, коммутирующие многоразрядные данные (вектора). Так как в подобном устройстве все разряды не связаны между собой, при построении в ПЛИС на уровне логического блока не будут задействованы цепи переноса, а устройство в целом представляет собой несколько одноразрядных мультиплексоров, включенных параллельно, у которых управляющие входы объединены. Поэтому все показанные выше методы описания мультиплексоров актуальны и при многоразрядных значениях входных данных. Например, для того, чтобы превратить бинарный мультиплексор в N-разрядный, достаточно информационные входы и выход объявить как N-разрядные вектора, управляющие сигналы останутся без изменения:

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

Рисунок поясняет принцип работы модуля. Для примера выбрана ширина входных данных 4, количество входов 4, а ширина управляющего вектора – 2. В цикле генерации формируем временный вектор tmp, из которого одноразрядным мультиплексором выбираем нужное значение.

Рисунок 8 – Принцип работы модуля многоразрядного коммутатора

Экспериментальное исследование

Несмотря на то, что рассмотренные в этой статье модели достаточно простые и для того, чтобы оценить корректность работы достаточно взглянуть на схему в RTL Viewer и Technology Map, можно провести экспериментальное исследование непосредственно на ПЛИС. С некоторого времени у меня под рукой отладочная плата LESO2, версия с Cyclone IV на борту. Восьми тумблеров хватит для формирования информационных и управляющих сигналов одноразрядного мультиплексора. Ниже приведен листинг модуля верхнего уровня.

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

Создаем проект в Quartus II, настраиваем на генерацию rbf-файла. Подробнее о создании и настройки проекта можно узнать из статьи  "Пишем "демку" для LESO2 на Verilog". Использовать Pin Planer или Assigment Editor нет необходимости, все порты модуля верхнего уровня назначены непосредственно в тексте программы при объявлении портов с помощью специальной директивы. Компилируем проект, с помощью утилиты l2flash загружаем rbf-файл в ПЛИС. Исследуем схему.

Рекомендации и выводы

1. На ПЛИС с 4-х входовыми LUT оптимальным образом строятся бинарные мультиплексоры 4:1. При описании мультиплексоров с большим числом входов для сохранения общего быстродействия схемы рекомендуется использовать конвейер.

2. Мультиплексор-селектор не дает преимущества ни по скорости, ни по занимаемым ресурсам. Можно рекомендовать использовать его только совместно с дешифратором.

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

4. Все ветви мультиплексора должны быть описаны. Для каждого if должен быть свой else, если использование case (или casex) сложно, то следует использовать default для ветви, исполняемой по умолчанию.

Литература

Quartus Prime Standard Edition Handbook Volume 1: Design and Synthesis

Advanced Synthesis Cookbook

Первая статья цикла:  Архитектура ПЛИС. Часть 1. Логический элемент

21 апреля 2016
Орфографическая ошибка в тексте:
Чтобы сообщить об ошибке автору, нажмите кнопку "Отправить сообщение об ошибке". Вы также можете отправить свой комментарий.