Шауэрман Александр А . shamrel@yandex.ru
Первая статья цикла:  Архитектура ПЛИС. Часть 1. Логический элемент 
Для многих проектов на ПЛИС значительную часть используемых логических ячеек занимают мультиплексоры (Multiplexer  – MUX). Всем известно, что мультиплексор представляет собой комбинационное устройство, коммутирующее несколько входов на один выход. Выбор входа определяют управляющие сигналы. При оптимизации логики вашего мультиплексора, вы сможете максимально эффективно реализовать его в ПЛИС Altera. В этом разделе рассмотрим основные проблемы и дадим решения для достижения оптимального использования ресурсов.
Для тех, кто перешел на язык описания схем после проектирования на дискретной логике, мультиплексор представляется неким фиксированным электронным компонентом: такой микросхемой, переключающей цифровые потоки. Однако при HDL описании схемы, особенно если это Verilog HDL , роль мультиплексора значительно шире и его можно встретить в не всегда ожидаемых местах. Если вы в своем коде используете оператор case if  
При аппаратном синтезе в ПЛИС выделяют несколько типов мультиплексоров: бинарные мультиплексоры, мультиплексоры селекторы и приоритетные мультиплексоры. Понимание того, как мультиплексоры создаются из HDL кода, и как они могут быть реализованы в ходе синтеза, является первым шагом на пути к оптимизации структуры мультиплексора для получения наилучших результатов.
Б инарные мульт и плексоры Бинарные мультиплексоры – это мультиплексоры, которые содержат дешифратор; они выбирают вход, основываясь на значении управляющих сигналов, представленных в виде двоичного позиционного кода. Простейший такой мультиплексор имеет два информационных входа и один управляющий.
Рисунок 1 – Таблица истинности и обозначение  мультиплексора 2: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 
Таким образом, для ПЛИС, у которых LE основан на 4-LUT, мультиплексор 4:1 эффективно строится на двух LUT. Мультиплексор с большим количеством входов при синтезе разбивается на блоки 4-х входовых мультиплексоров, при необходимости в завершении ставится мультиплексор 2:1.
Пример Verilog модуля бинарного мультиплексора 4:1 (занимает две LUT):
Бинарный мультиплексор 4:1. Verilog 
module  multiplexer
( 
	input 		d3, d2, d1, d0,  	// data	 
	input 		c1, c0, 		// control 
	output 	reg  y
) ; 
 
	always  @* 
	case  ( { c1,  c0} ) 
		2'b00 :  y =  d0; 
		2'b01 :  y =  d1; 
		2'b10 :  y =  d2; 
		2'b11 :  y =  d3; 
	endcase 
 
endmodule  
Язык Verilog позволяет обратится отдельно к каждому биту в векторе по его номеру. Эта возможность даст еще одну реализацию бинарного мультиплексора.
 Бинарный мультиплексор 4:1. Verilog. Реализация с объединением входов 
module  binary_mux 
( 
	input 	[ 3 : 0 ] 	din,  // Вход 
	input 	[ 1 : 0 ] 	sel,  // Управление 
	output 		dout // Выход 
) ; 
 
	assign  dout =  din[ sel] ; 
 
endmodule  
В отличии от предыдущих реализаций информационные и управляющие входы объединены в вектор. При использовании такого модуля в экземпляре используется конкатенация (объединение):
	binary_mux  mux_inst
	( 
		.din( { d3,  d2,  d1,  d0} ) , 
		.sel( { c1,  c0} ) , 
		.dout( y) 
	) ;   На практике удобно использовать универсальные модули, где основные свойства задаются через параметр. Это позволяет использовать одну реализацию в различных задачах. В последнем примере бинарного мультиплексора легко перейти к параметризируемой модели и управлять числом коммутируемых входов:
 Бинарный мультиплексор 4:1. Verilog. Параметризируемая модель 
module  binary_mux 
# (  
	parameter  SEL_WIDTH =  3 , 		// разрядность управляющего вектора 
	parameter  TOTAL_DAT =  1  <<  SEL_WIDTH	// максимальное число входов 
) 
( 
	input 	[ TOTAL_DAT- 1 : 0 ] 	din, 
	input 	[ SEL_WIDTH- 1 : 0 ] 	sel, 
	output 					dout
) ; 
 
	assign  dout =  din[ sel] ; 
 
endmodule  
При использовании модуля параметр SEL_WIDTH
	binary_mux # ( .SEL_WIDTH( 2 ) )  mux_inst
	( 
		.din( { d3,  d2,  d1,  d0} ) , 
		.sel( { c1,  c0} ) , 
		.dout( y) 
	) ;  Легко убедится, что при одинаковом числе коммутируемых входов на уровне RTL все варианты описания мультиплексора дают одинаковую схему.
Мультиплексор-селектор 
Мультиплексор-селектор не имеет дешифратора: для каждого входа предназначена отдельная управляющая линия. Одновременно только на одной линии управления может быть установлена единица. Несмотря на простоту реализации на дискретных элементах на каждый вход ставится элемент 2-И, с выхода которых сигнал подается на элемент ИЛИ. При реализации в ПЛИС такой мультиплексор намного менее эффективен, чем двоичный. Из-за того, что на каждый вход требуется управляющая линия, для 4-х канального мультиплексора понадобится уже три 4-LUT.
 Мультиплексор-селектор 4:1. Verilog 
module  selector_multiplexer
( 
	input 		d3, d2, d1, d0,  	// data	 
	input 		c3, c2, c1, c0, 	// control 
	output 	reg  y
) ; 
 
	always  @* 
	case  ( { c3,  c2,  c1,  c0} ) 
		2'b0001 :  y =  d0; 
		2'b0010 :  y =  d1; 
		2'b0100 :  y =  d2; 
		2'b1000 :  y =  d3; 
		default :  y =  0 ; 
	endcase 
 
endmodule  
Однако в некоторых случаях управляющие сигналы – это выход декодера, тогда компилятор попытается объединить селектор и дешифратор в двоичный мультиплексор.
Приоритетный мультиплексор 
Судя по названию, в приоритетном мультиплексоре логика выбора подразумевает приоритет. Варианты для выбора правильного элемента должна быть проверена в определенном порядке на основе приоритета сигнала. Такая структура в Verilog в основном определяется операторами if else casex  ?: 
Например, такой модуль:
 Приоритетный мультиплексор 4:1. Verilog. Реализация на if 
module  priority_multiplexer
( 
	input 		d3, d2, d1, d0,  	// data	 
	input 		c2, c1, c0, 		// control 
	output 	reg  y
) ; 
	always  @* 
	if  ( c0) 
		y =  d0; 
	else  if  ( c1) 
		y =  d1; 	
	else  if  ( c2) 
		y =  d2; 
	else  
		y =  d3; 
 
endmodule  
на уровне RTL (Register transfer level – уровень регистровых взаимодействий) будет иметь вид:
Рисунок 4 – Структура приоритетного мультиплексора на уровне RTL 
Очевидно, что путь прохождения сигнала со входа d3yd0y
Рисунок 5 – Структура приоритетного мультиплексора 4:1 на уровне Technology Map 
Из рисунка 5 видно, что по прежнему самые длинные пути это d3→yd2→ycase 
И напоследок, точно такой же по функционалу и реализации модуль может быть записан с использованием оператора casex 
 Приоритетный мультиплексор 4:1. Verilog. Реализация на casex 
module  priority_multiplexer
( 
	input 		d3, d2, d1, d0,  	// data	 
	input 		c2, c1, c0, 	// control 
	output 	reg  y
) ; 
 
	always  @* 
	casex  ( { c2,  c1,  c0} ) 
		3 'b?? 1 :  y =  d0; 
		3 'b? 10 :  y =  d1; 
		3'b100 :  y =  d2; 
		3'b000 :  y =  d3; 
	endcase 
 
endmodule  
У такой записи помимо наглядности есть еще одно маленькое преимущество, в конструкции casex  
Часто бывает, что применение оператора if case if if else Latch .
Рассмотрим пример приоритетного мультиплексора, но "упростим" схему, "забудем" последнюю ветвь else 
 Приоритетный мультиплексор 4:1 с ошибкой. Verilog 
module  priority_multiplexer_bad
( 
	input 		d3, d2, d1, d0,  	// data	 
	input 		c2, c1, c0, 	// control 
	output 	reg  y
) ; 
	always  @* 
	if  ( c0) 
		y =  d0; 
	else  if  ( c1) 
		y =  d1; 	
	else  if  ( c2) 
		y =  d2; 
//	else  
//		y = d3; 
endmodule  
При компиляции такой модуль вместо 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. Что-то пошло не так. Вход c0c0d0c0c1d1c0c1c1d2
Рисунок 6 – Структура полученного мультиплексора на уровне RTL 
Рисунок 7 – Структура полученного мультиплексора на уровне Technology Map 
На технологической карте последняя (крайняя правая) LUT охвачена петлей обратной связи – это и есть Latch. В отечественной литературе выделяют целое семейство таких устройств, их называют асинхронными триггерами, одноступенчатыми триггерами , или триггерами-защелками. Такие устройства имеют ограниченную поддержку в инструментах проверки и анализа, например, TimeQuest  (встроенная в Quartus утилита для временного анализа, оценивает задержки распространения) интерпретирует Latch, как синхронный триггер, работающий по спадающему фронту, что является весьма грубым допущением. Поэтому схем с защелками нужно избегать, а использование такого мультиплексора является крайне неудачным решением.
При описании сложного мультиплексора с разветвленной иерархией на операторах if else case 
Например так:
 Бинарный мультиплексор с ошибкой. Verilog 
module  multiplexer_bad
( 
	input 		d3, d2, d1, d0,  	// data	 
	input 		c1, c0, 		// control 
	output 	reg  y
) ; 
 
	always  @* 
	case  ( { c1,  c0} ) 
		2'b00 :  y =  d0; 
		2'b01 :  y =  d1; 
		2'b10 :  y =  d2; 
	//	2'b11: y = d3; 
	endcase 
 
endmodule  
Как результат, при синтезе получим увеличение числа логических элементов, Latch и плохую предсказуемость при временном и функциональном анализе. Когда в мультиплексоре используется всего 4 варианта выбора, то, в принципе, легко проследить все варианты, но задача усложняется экспоненциально при увеличении числа коммутируемых входов, тем более, если не все сочетания управляющих сигналов функционально возможны или достаточно редко происходят. Спецификация Verilog имеет решение на этот случай, оператор case default 
 Бинарный мультиплексор с default. Verilog 
module  multiplexer_bad
( 
	input 		d3, d2, d1, d0,  	// data	 
	input 		c1, c0, 		// control 
	output 	reg  y
) ; 
 
	always  @* 
	case  ( { c1,  c0} ) 
		2'b00 :  y =  d0; 
		2'b01 :  y =  d1; 
		2'b10 :  y =  d2; 
	//	2'b11: y = d3; 
		default  :  y =  0 ; 
	endcase 
 
endmodule  
Даже если будет закомментирован любой другой случай, или два случая, компилятор реализует корректную схему без защелок. Поэтому хорошим тоном программирования является добавления default 
Описание ветви default default 
Многоразрядный мультиплексор 
Под многоразрядными мультиплексорами мы будем подразумевать мультиплексоры, коммутирующие многоразрядные данные (вектора). Так как в подобном устройстве все разряды не связаны между собой, при построении в ПЛИС на уровне логического блока не будут задействованы цепи переноса, а устройство в целом представляет собой несколько одноразрядных мультиплексоров, включенных параллельно, у которых управляющие входы объединены. Поэтому все показанные выше методы описания мультиплексоров актуальны и при многоразрядных значениях входных данных. Например, для того, чтобы превратить бинарный мультиплексор в N-разрядный, достаточно информационные входы и выход объявить как N-разрядные вектора, управляющие сигналы останутся без изменения:
 N-разрядный бинарный мультиплексор. Verilog 
module  multiplexer
# (  parameter 	 N =  8 ) 
( 
	input 		[ N- 1 : 0 ] 	d3, d2, d1, d0,  	// data	 
	input 				c1, c0, 			// control 
	output 	reg 	[ N- 1 : 0 ]  y
) ; 
 
	always  @* 
	case  ( { c1,  c0} ) 
		2'b00 :  y =  d0; 
		2'b01 :  y =  d1; 
		2'b10 :  y =  d2; 
		2'b11 :  y =  d3; 
	endcase 
 
endmodule  
При такой реализации единственный настраиваемый параметр – это N, разрядность входных и выходных данных. Количество входов остается фиксированным, что неудобно, в сложном проекте придется держать множество модулей. Гораздо большим потенциалом в плане параметризации обладает модель бинарного мультиплексора с объединением входов. Ниже приведен модуль многоразрядного настраиваемого мультиплексора, построенный в соответствии с рекомендациями Altera.
 Настраиваемый модуль мультиплексора. Verilog 
module  bus_mux 
# (  
	parameter  DAT_WIDTH =  2 ,  
	parameter  SEL_WIDTH =  3 , 
	parameter  TOTAL_DAT =  DAT_WIDTH <<  SEL_WIDTH
) 
( 
	input 	[ TOTAL_DAT- 1 : 0 ] 	din, 
	input 	[ SEL_WIDTH- 1 : 0 ] 	sel, 
	output 	[ DAT_WIDTH- 1 : 0 ] 	dout
) ; 
 
	localparam  NUM_WORDS =  ( 1  <<  SEL_WIDTH) ; 
 
	genvar  i, k; 
	generate 
		for  ( k= 0 ;  k <  DAT_WIDTH;  k= k+ 1 ) 
		begin  :  out
			wire  [ NUM_WORDS- 1 : 0 ]  tmp; 
			for  ( i= 0 ;  i <  NUM_WORDS;  i= i+ 1 ) 
			begin  :  mx
				assign  tmp [ i]  =  din[ k+ i* DAT_WIDTH] ; 
			end 
			assign  dout[ k]  =  tmp[ sel] ; 
		end 
	endgenerate 
endmodule  
Рисунок поясняет принцип работы модуля. Для примера выбрана ширина входных данных 4, количество входов 4, а ширина управляющего вектора – 2. В цикле генерации формируем временный вектор tmp
Рисунок 8 – Принцип работы модуля многоразрядного коммутатора 
Экспериментальное исследование 
Несмотря на то, что рассмотренные в этой статье модели достаточно простые и для того, чтобы оценить корректность работы достаточно взглянуть на схему в RTL Viewer  и Technology Map , можно провести экспериментальное исследование непосредственно на ПЛИС. С некоторого времени у меня под рукой отладочная плата LESO2 , версия с Cyclone IV на борту. Восьми тумблеров хватит для формирования информационных и управляющих сигналов одноразрядного мультиплексора. Ниже приведен листинг модуля верхнего уровня.
 Модуль верхнего уровня. Verilog 
module  demo_mux
( 
	( *  chip_pin =  "65"  * )  input 	d0, 		//sb8 тумблер	 
	( *  chip_pin =  "64"  * )  input 	d1, 		//sb7 тумблер 
	( *  chip_pin =  "60"  * )  input 	d2, 		//sb6 тумблер 
	( *  chip_pin =  "59"  * )  input 	d3, 		//sb5 тумблер 
	( *  chip_pin =  "52"  * )  input 	d4, 		//sw кнопка (с инверсией) 
 
	( *  chip_pin =  "58"  * )  input 	c0, 		//sb4 
	( *  chip_pin =  "55"  * )  input 	c1, 		//sb3 
	( *  chip_pin =  "54"  * )  input 	c2, 		//sb2 
	( *  chip_pin =  "53"  * )  input 	c3, 		//sb1 
 
	( *  chip_pin =  "11, 10, 8, 7, 6, 3, 2, 1"  * ) 
	output 			[ 7 : 0 ] 		led_o
) ; 
	wire  y; 
 
	// Экземпляр исследуемого модуля: 
	multiplexer mult_inst
	( 
		.d3( d3) ,  .d2( d2) ,  .d1( d1) ,  .d0( d0) ,  // data	 
		.c1( c1) ,  .c0( c0) , 		    // control 
		.y( y) 
	) ; 
 
	// назначаем выход модуля на светодиод 
	assign  led_o [ 0 ]  =  y; 
endmodule  
В листинге для примера установлен экземпляр одноразрядного бинарного мультиплексора. При исследовании многоразрядных мультиплексоров на информационные входы можно подать константы. В качестве дополнительного информационного входа (в листинге обозначен как 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. Логический элемент