Несколько слов об отладке 1Gb Ethernet-проектов на ПЛИС

Часть первая. Несколько слов об интерфейсах.

Сегодня использование девайсов на ПЛИС с сетью Ethernet (или как острят некоторые мои знакомые, «Азернет») – общее место. Особенно если речь идёт о высокоскоростной передачи данных (АЦП/ЦАП с сетевым выходом, обработка видео, «сырца» с радиолокаторов и гидроакустических комплексов, сбора данных с большой сети (решётки датчиков и т.д. и т.п.). Когда я вижу, как люди, покрывшись испариной, пытаются упихать поток отсчётов с квадратурного демодулятора SDR в USB 3.0, мне их становится откровенно жалко.

Отдельная огромная тема — это на чём это реализовывать. Вариантов несколько, но, в большинстве своём, плисоводы приходят к выводу, что лучший вариант — это «рукопашный».

В начале стоит внешняя «физическая» микросхема (PHY), которая выполняет работу нулевого уровня (если строго следовать модели OSI), т.е. преобразование аналоговых сигналов в цифровые, битовую и кадровую синхронизации. Затем следует MAC-уровень, т.е. всё, начиная от проверки контрольной суммы (КС) пакета до разборки и вытаскивания «полезной нагрузки» из многочисленных IP-протоколов: начиная от TCP, UDP и ARP и заканчивая экзотикой типа H.248.


Ну, и наконец, высший (7ой уровень) — уровень приложения, где уже начинается разгребание той самой полезной информации (будь то стандартный HTTP, или поток отсчётов с АЦП, завёрнутый в придуманный вами протокол).
Так вот основная работа плисовода по части стыковки с Ethernet'ом — это MAC-уровень.

В И-нете, да и здесь на Easyelectronics описывались проекты, которые вообще напрямую стыкуют микроконтроллер с сетью. Но такие решения — это максимум, что бы раз в минуту послать пакетик с какого-нибудь датчика температуры со скоростью 10Мбит/с. Для скоростей выше — внешняя микросхема PHY с соответствующей обвеской. Пару слов (а то это — тоже обширная тема) об интерфейсах PHY. 1Gb PHY-микросхемы наследуют от своих более тихоходных предшествнников, но и превносят новое (а не только изменение частоты оцифровки). Управляются они также.
Во-первых, это — интерфейс настройки MDIO. Это — некоторая помесь SPI и I2C, эдакий двухпроводный SPI. По этому интерфейсу задаются режимы работы микросхемы путём записи/чтения в определённые регистры. Список основных регистров (адреса и содержимое) стандартизован IEEE 802.3. И, в целом, производители этого стандарта придерживаются, но добавляют обычно много своих регистров с более тонкими настройками. А в некоторых микросхемках, например, в популярной PHY 88E1111 от Марвелла, даже добавляют вторую «страницу» регистров (дело в том, что эта микросхема может работать и по витой паре и по оптике и каждая линия имеет свой набор регистров «страницу»).

Самые основные настройки (типа выбора интерфейсов и скорости работы) обычно могут быть выбраны внешними перемычками (которые обычно спарены с выводами светодиодов). Т.е., в принципе, можно не заворачиваться с написанием MDIO-блока, но только в принципе, ибо тогда будут не доступны многие реально полезные вещи (в частности, жёсткое задание скорости и внутренний ФАПЧ для сдвига тактовой частоты (см. далее).

Теперь об основном интерфейсе. В отличии от 100Мбит-прородителей в 1Гбит-микросхемах появились новые форматы: это SGMII, GMII и RGMII.

SGMII — это последовательный интерфейс, который хорош тем, что ему требуется всего одна пара проводов (в одну сторону, есс-но дифференциальных, конкретнее — LVDS), если работа идёт без опорного генератора. А огромный минус его в том, что работа идёт на частоте аж 625МГц по обоим фронтам. Далее этот поток (если речь о приёме) следует преобразовать в параллельный код. В принципе, топовые микросхемы, типа того же Stratix'а IV и V с максимальными суффиксами в состоянии выполнить такое преобразование «на себе», т.е. на схеме, реализованной на внутреннем массиве логических элементов (ЛЭ). Но, такая схема будет весьма нестабильна (ибо работать будет почти на пределе возможностей, когда, по-сути, цифровая схема превращается в «полу-аналоговую» и всё, что показывают имитаторы типа Modelsim'а — никакого отношения к действительности не имеет), да и ставить мегадорогущие каменюги ради стыковки с PHY — не очень серьёзно. Поэтому, во многие ПЛИСины втискивают аппаратные приёмопередатчики SGMII. Но настройка этих блоков — отдельная и непростая штука. Кроме того, нужно помнить про сложности разводки печатной платы, ибо даже исходная частота (625МГц), мягко говоря, не детская, а с учётом требований к фронтам и спадам (типично, максимум — 0,2нС) — можете легко посчитать требуемую широкополосность линий. Поэтому, мой совет — с SGMII связываться только в крайнем случае!

Гораздо проще ситуация с GMII и RGMII-интерфейсами. Это — параллельные (8 и 4, соответственно) линии связи. Тактовая частота снижена до разумных 125МГц, хотя даже такая частота может представлять определённую проблему для дешёвых вариантов современных ПЛИС, особенно если нужно «выжать» всё, что можно из 1Гбит-интерфейса и, соотвественно, пакеты должны формироватся «на лету». Поскольку, для RGMII-интерфейса при тактировании по одному фронту понадобилась бы тактовая частота в 250МГц (что уже совсем тяжеловато для большинства современных ПЛИС), здесь также производится тактирование по фронту и спаду тактового сигнала.



Если очень страшно связываться с двойным тактированием, то можно использовать GMII-интерфейс и тут, фактически, разработчик почти спокойно может использовать наработки, взятые из MII-интерфейса, разве что не забыв, что по GMII передаются байты, а не полубайты(нибблы) и, есс-но, подняв скорость передачи в 5 раз.

Но лишние выводы в современных ПЛИС'инах весьма дороги и RGMII-интерфейс предоставляет возможность разумной экономии (между GMII и SGMII) ценой некоторого (на самом деле, небольшого) усложнения схемы. И, кстати, если ваше устройство должно иметь возможность работать и с 1Гбит и с 100Мбит-устройствами, то, на самом деле, наиболее простой переход с MII обеспечивает RGMII.

Как стыковать RGMII с ПЛИС. Достаточно просто. Теоретически двухтактный буфер можно создать в основном массиве ЛЭ, используя (лучше всего) графику, или поизвращавшись (вообще, убогость этих языков — отдельная тема) в VHDL или Verilog'е (не забыв указать *useioff* у соответствующих выводов). Но, как показала практика, работать это всё будет весьма убого (ибо, с учётом требований к крутизне фронтов, широкополосность должна достигать 400МГц!), правда, даст подсмотреть генирируемые сигнал в СигналТапе(ЧипСкоупе).

Более правильный путь — использовать специализированные двухтриггерные буфера, например, в альтеровском случае — это ALTDDIO в соответствии с документом AN477 «Designing RGMII Interfaces».




Строб пакета tx_en, как показывает практика, лучше тоже протаскивать через ALTDDIO (что и сделано под именем dataout[4]).

Показанная схема будет работать только при условии, что тактовая частота gtx_clk сдвигается на 90 градусов на внутреннем ФАПЧ микросхемы PHY (см. выше диаграмку с RGMII-сигналами). Такая весьма удобная опция предусмотрена в большинстве 1Гбит-PHY, но она включается в соответствующем регистре через MDIO. Например, для 88E1111 — это бит 1 регистра 20. Если же MDIO-приёмопередатчик писать лень, то тогда сдвинутую последовательность нужно сгенерить в ПЛИС с помощью ФАПЧ(PLL) и подать её на выход GTX_CLK. К сожалению, такой вариант часто не обеспечивает нужной стабильности и приводит к редким пропаданием отдельных пакетов.

В качестве дополнения к посту прилагается работающий модуль-шаблон для RGMII-шины как для 100Мбит/с, так и для 1Гбит/с режимов (задаётся параметром SPEED). Код тщательно комментирован, но некоторые пояснения сделаем. Модуль предназначен для проверки скорости работы сетевой системы. Поэтому он шлёт длинные UDP-пакеты с заданной паузой. Длина полезной нагрузки (не пакета!) задаётся параметром DATA_LENGTH. Контрольная сумма (КС) IP прописывается руками, КС UDP не вычисляется (нули). А вот CRC32 считается на лету с помощью модуля CRC32, но в данном примере она тоже была высчитана заранее (благо, не меняется :)) В качестве полезной нагрузки идут просто постоянные байты (в версии с расчётом CRC32 — в начале идёт счётчик байт). Собственно и всё.

Модуль вполне можно использовать как заготовку для своих целей. В этом случае, просьба, оставить в комментах к коду ссылку на этот пост и автора. Кстати, при сборке модуля нужно проверять максимальные задержки в «ТаймКвесте» или аналогичном инструменте. Дело в том, что из-за двух гигантских case'ов код легко читаем, но приводит к генерации огромного массива переключателей, которые, соответственно, могут легко не пойти по задержке для данной ПЛИС. Если такое случилось, то нужно либо подрезать кол-во ветвлений, либо генерить часть данных во внешней ПЗУ. Короче, дальше — всё в ваших руках. С помощью данного модуля удавалось «выжать» полезную (т.е. уже за вычетом всех заголовков) скорость передачи до почти 970Мбит/с.

В качестве сигнала reset в простейшем случае можно использовать выход locked ФАПЧ.

Ещё один момент. Во многих микросхемах, в частности, семейства Cyclone IV, Сигнал Тап не имеет возможности снять сигнал после модуля DDIO_out. Да-да! Как известно, СигналТап часто при компиляции «не может» дотянутся до нужных вам сигналов, даже если на них навешен LCELL (или атрибут *keep* в Verilog'е). Это происходит по разным причинам, это — тема для отдельной статьи.

Но, среди плисоводов (даже весьма опытных) бытует «городская легенда», что, что бы 100% увидеть нужный сигнал в СигналТапе достаточно его повесить на какую-либо ногу микросхемы. Так вот — ничего подобного! Вот вам пример. Это хорошо видно по схемотехнике выходных цепей соответствующей микросхемы. Так что сигналы на RGMII-шине штатной смотрелкой подсмотреть скорее всего не удастся. А при попытке их завернуть на какой-нибудь вход ПЛИС всё закончится, скорее всего, разбалансировкой линий и соответствующими проблемами.

Продолжение следует…

(C) 2015, Kluwert, имею право (и регулярно :))

module PacketGenerator
(
	input i_clk,
	input i_rst,
	
	output [7:0] o_data,
	output reg   TX_EN
);

`define FAST (1'b0)
`define HIGH (1'b1)

parameter SPEED = `FAST; // Determines that to send bytes or nibbles

reg [7:0] clk_counter; // Number of bits determines pause time
reg [7:0] byte; 	   // Current output byte

wire [7:0] clk_bytes;

assign clk_bytes = (SPEED == `FAST) ? clk_counter[9:1] : clk_counter; 
assign o_data    = (SPEED == `FAST) ? (clk_counter[0] ? byte[3:0] : byte[7:4]) : byte;

always @(posedge i_clk)
	begin
		if (i_rst)
			clk_counter <= 0;
		else
		 begin
			clk_counter <= clk_counter + 1'b1;
			
			if ((clk_bytes >= 9'h0) && (clk_bytes < 9'd72))
				TX_EN <= 1'b1; // Transmission is enabled
			else
				TX_EN <= 1'b0;
			
		case (clk_bytes)
			// Sending the preambule and asserting TX_EN
			0: byte <= 8'h55;
			1: byte <= 8'h55; 
			2: byte <= 8'h55; 
			3: byte <= 8'h55; 
			4: byte <= 8'h55; 
			5: byte <= 8'h55; 
			6: byte <= 8'h55; 
			7: byte <= 8'hd5;
				
			default: 
			
				case (clk_bytes-8)
					// Sending the UDP/IP-packet itself
					0: byte <= 8'hd8;
					1: byte <= 8'hd3; 
					2: byte <= 8'h85; 
					3: byte <= 8'h26; 
					4: byte <= 8'hc5; 
					5: byte <= 8'h78; 
					6: byte <= 8'h00; 
					7: byte <= 8'h23; 
		
					8: byte <= 8'h54; 
					9: byte <= 8'h3c; 
					10: byte <= 8'h47; 
					11: byte <= 8'h1b; 
					12: byte <= 8'h08; 
					13: byte <= 8'h00; 
					14: byte <= 8'h45; 
					15: byte <= 8'h00; 
		
					16: byte <= 8'h00; 
					17: byte <= 8'h2e; 
					18: byte <= 8'h00; 
					19: byte <= 8'h00; 
					20: byte <= 8'h00; 
					21: byte <= 8'h00; 
					22: byte <= 8'hc8; 
					23: byte <= 8'h11;
		
					24: byte <= 8'hd6; 
					25: byte <= 8'h73; 
					26: byte <= 8'hc0; 
					27: byte <= 8'ha8; 
					28: byte <= 8'h4d; 
					29: byte <= 8'h21; 
					30: byte <= 8'hc0; 
					31: byte <= 8'ha8;
		
					32: byte <= 8'h4d; 
					33: byte <= 8'hd9; 
					34: byte <= 8'hc3; 
					35: byte <= 8'h50; 
					36: byte <= 8'hc3; 
					37: byte <= 8'h60; 
					38: byte <= 8'h00; 
					39: byte <= 8'h1a; 
		
					40: byte <= 8'h00; 
					41: byte <= 8'h00; 
					42: byte <= 8'h01; 
					43: byte <= 8'h02; 
					44: byte <= 8'h03; 
					45: byte <= 8'h04; 
					46: byte <= 8'h01; 
					47: byte <= 8'h01;
		
					48: byte <= 8'h01; 
					49: byte <= 8'h01; 
					50: byte <= 8'h01; 
					51: byte <= 8'h01; 
					52: byte <= 8'h01; 
					53: byte <= 8'h01; 
					54: byte <= 8'h01; 
					55: byte <= 8'h01;

					56: byte <= 8'h01; 
					57: byte <= 8'h01; 
					58: byte <= 8'h01; 
					59: byte <= 8'h01;
					
					// The CRC32 control sum (checked in Matlab programm)
					60: byte <= 8'he3;
					61: byte <= 8'h8e;
					62: byte <= 8'hdf;
					63: byte <= 8'h1f;
			
					default: byte <= 0; // Pause 
				endcase
		endcase
	  end	
	end
	
endmodule
  • +9
  • 01 сентября 2015, 19:54
  • Kluwert
  • 4
Файлы в топике: Output_buffer.jpg, RGMII_signals.jpg, SGMII.jpg, ALTDIO.jpg

Комментарии (1)

RSS свернуть / развернуть
Открыл комменты, по мере возможности буду отвечать.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.