5 December 2014

Ядро PCI-express в ПЛИС Achronix — быстрый старт

High performance


Этот пост написан с целью показать разработчикам дизайна для ПЛИС, как с наименьшими затратами времени и сил начать работать с шиной PCI-express на платформе Ahronix Speedster22i. В статье описывается организация проекта, адаптация которого к конкретным требованиям разработчика сводится к несложной модификации исходного текста всего одного модуля, что позволяет подключиться к шине PCIe хост-компьютера буквально за 1 час. Надеюсь, разработчикам на других платформах эта статья будет так же небезинтересна.


В ПЛИС Speedster22i HD1000 имеется два аппаратных ядра PCIe, сертифицированных PCI-SIG на соответствие спецификации PCIe 3.0, а в отладочной плате Speedster22i HD1000 Development Kit (о которой я писал в предыдущем посте) одно из этих ядер выведено на PCIe разъем. Через интерфейс PCIe очень удобно осуществлять взаимодействие отладочной платы с хост-компьютером. По сути, это единственное высокоскоростное решение для означенной цели. Альтернативой использования PCIe для связи отладочной платы с хост-компьютером может служить лишь встроенный com-порт, который на несколько порядков медленнее. Все остальные решения требуют больших или меньших аппаратных изощрений, как минимум, потребуется применение преобразователей уровня сигнала.
У компании Achronix имеется референс-дизайн, демонстрирующий работу аппаратного ядра PCIe во всей красе – ядро работает в режиме target с доступом как непосредственно CPU, так и через механизм DMA по чтению и записи. Я проверил, все работает отлично. Но этот дизайн оказалось достаточно сложно модифицировать под собственные цели в силу недостаточной модульности и излишней усложненности кода на языке Verilog. Поэтому было принято решение на основе фирменного дизайна создать собственный вариант, убрав из него все, связанное с обменом через DMA, а так же структурировав его таким образом, чтобы явно выделить в нем модули с неизменяемым кодом и модули, код которых требуется модифицировать для адаптации к конкретным задачам разработчика. В результате получился простой, хорошо структурированный проект, адаптация которого под конкретные задачи разработчика сводится к несложному изменению кода всего одного модуля.
Фирменной особенностью ПЛИС Achronix является наличие аппаратно реализованных IP-ядер контроллеров таких интерфейсов, как PCIe, DDR3, 100/40/10G Ethernet и Interlaken. Эти аппаратные ядра обеспечивают все, что необходимо для функционирования указанных интерфейсов, единственно что требуется от разработчика — написать собственные модули сопряжения с этими контроллерами. В результате объем работы драматически сокращается. Кроме того, существенно упрощается достижение требуемого тайминга. В случае дизайна PCIe, понадобилось всего несколько модулей сопряжения, причем большинство из них было взято из фирменного референс-дизайна.


Краткое описание проекта


В проекте реализован доступ к трем 128-разрядным регистрам. PCIe ядро сконфигурировано на 3 BARа: BAR0 – 64KB, BAR1 и BAR2 – по 8 KB. Доступ к регистрам осуществляется через BAR1. Наличие 3х BARов обусловлено требованиями совместимости с используемым драйвером. Описание регистров приводится ниже:

Имя Смещение в АП BAR1 тип Описание
R0 0 RO {4{32’hDEADBEEF}}
R1 20h RW
RW
Биты [7:0] — вывод на линейку светодиодов
Биты [127:8] – не используются
R2 40h RO
RW
Биты [7:0] – чтение линейки выключателей
Биты [127:8] – не используются


При модернизации проекта первое, что было сделано – удален код, связанный с обменом данными через DMA. После этого для подключения к ядру были использованы каналы чтения и записи target_read и target_write. Далее, была определена структура модулей, изображенная на рисунке:


Всего получилось 4 модуля (в некоторые из них входят подмодули)

Состав модулей:
  • pcie_g3x4.v – обертка аппаратного ядра PCIe. Определяет его параметры, такие как VendorID, количество полос (lanes), ширину локальной шины и т. д. Этот модуль генерируется с помощью генератора ядер среды разработки ACE.
  • pci_target_bus_ctrl.v – модуль-обертка, согласующий канал target аппаратного ядра и локальную шину, на которой расположены регистры, доступные через шину PCI. Поскольку канал target состоит из двух независимых подканалов: записи и чтения, этот модуль объединяет в себе два модуля: pci_target_bus_write_ctrl.v и pci_target_bus_read_ctrl.v, реализующие операции записи и чтения соответственно.
  • lbus_registers.v – модуль, содержащий собственно пользовательские регистры. Единственный модуль, требующий модификации кода под конкретный проект.
  • ACX_SNAPSHOT.v – вспомогательный модуль для внутрисхемной отладки. По окончании отладки может быть исключен из проекта.


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


Генерация ядра PCIe


Для генерации ядра можно воспользоваться генератором ядер оболочки ACE. Все заданные параметры сохраняются в файле с расширением .axip, который в любой момент можно отредактировать. Результатом работы генератора являются текстовые файлы на языках Verilog и VHDL. Снимок экрана в процессе генерации ядра показан на рисунке:



Интерфейс target ядра pcie


Аппаратное ядро PCI включает в себя несколько интерфейсов, но нас интересует интерфейс target. Через этот интерфейс подключаются регистры, выступающие как пассивные устройства, а процессор выступает в качестве активного устройства. Интерфейс target состоит из 4х каналов: задания адреса записи, данных записи, задания адреса чтения и чтения данных. Каналы записи и чтения работают независимо друг от друга. Ниже приведены временные диаграммы транзакций записи и чтения. На этих же диаграммах показаны сигналы локальной шины.


Локальная шина


Локальная шина имеет очень простую структуру. Она состоит из двух независимых каналов – записи и чтения и может быть настроена на разную ширину слова. В данном проекте используются слова шириной 128 бит.
Интерфейс локальной шины, реализованный в модуле lbus_registers.v обеспечивает запись в регистры без задержки и чтение с задержкой на 1 такт. Реальные задержки, однако, несколько больше, т.к. подмодули, входящие в модуль pci_target_bus_ctrl.v вносят свой вклад в латентность транзакций записи и чтения.


Имплементация


Имплементация проекта состоит из двух этапов – этапа синтеза и этапа трассировки.

Структура каталогов


Для имплементации была выбрана следующая организация каталогов:
pci_simple
    |--- src
    |--- syn
    |--- tr
    |--- tools


В каталоге src размещены исходные файлы на языке Verilog. В каталоге syn находятся файлы, необходимые для синтеза с помощью программы synplify, а в каталоге tr – файлы, необходимые для этапа трассировки. Так же в этом каталоге по умолчанию находятся сгенерированные ядра. В каталоге tools содержатся драйвера и программа PciExpress, помощью которой можно читать и записывать данные в регистры, подключенные к шине PCIe.

Синтез


В каталоге syn находится файл проекта pcie_simple_design.prj. Этот файл необходимо указать программе синтеза synplify-pro, разработанной компанией Synopsys. Результатом работы этой программы является файл pcie_simple_design.vma в подкаталоге syn/rev_1. Этот файл является входным для следующего этапа – трассировки. Снимок экрана во время выполнения этапа синтеза показан ниже:



Трассировка


Этап трассировки осуществляется программой ACE собственной разработки компании Achronix. В каталоге tr находится файл проекта pci-simple.prj, который надо указать программе ACE. По окончании этапа трассировки в подкаталоге tr/impl_1/output появится файл прошивки pci-simple-design.jam, который загружается непосредственно в ПЛИС. Снимок экрана в процессе выполнения этапа трассировки:



Констрейнты


Имеются всего два файла констрейнтов – один описывает тактовые цепи, а другой определяет используемые пины ввода-вывода. Файлы находятся в каталоге tr и имеют имена pcie_simple_design.sdc и pcie_simple_design.pdc соответственно. Они уже подключены через файлы проектов к программам синтеза и трассировки.


Результаты



Тайминг


Результаты трассировки
Frequency (MHz)
Clock/Group Target Achieved Meets Timing
user_clk 212.5 308.5 yes (+45.2%)
core_clk 212.5 433.5 yes (+104.0%)
sbus_clk 50.0 138.7 yes (+177.5%)
Tck 10.0 175.4 yes (+1653.6%)


Нас интересует тактовая группа user_clk, на которую подключены пользовательские регистры. Как видно, при заданной частоте 212.5 MHz, был достигнут результат 308.5 MHz, т.е. на 45% выше, чем требуется.

Утилизация


Ресурс Занято
RLBs 0.520%
LUT4 Sites 0.410%
DFF Sites 0.520%
MUX2 Sites 0.010%
ALU Sites 0.170%
LRAM Sites 1.280%
BRAM Sites 0.190%
BMULT Sites 0.000%
I/O Pad Sites 1.980%
Data Pads 1.740%
Clock Pads 12.50%
Reset Pads 0.000%



Подключение к хост-компьютеру


Для подключения к хост-компьютеру требуется драйвер. При определенных условиях можно использовать драйвер из фирменного референс-дизайна. С этим драйвером работает приложение PciExpress.exe, через которое можно обращаться к регистрам, подключенным к шине PCIe. Чтобы можно было использовать эти средства, требуется сохранить структуру BARов оригинального дизайна и сохранить значения параметров VendorID и DeviceID.

Чтобы начать работать с хост-компьютером с операционной системой Windows, необходимо выполнить следующие действия:
  • Подключить отладочную плату к компьютеру через шину PCIe. Требуется слот PCIe x8 или шире. Подключение следует производить на выключенных устройствах с соблюдением мер антистатической защиты. Отладочную плату запитать от внешнего источника питания.
  • Включить питание компьютера и платы. Порядок включения питания несущественен.
  • Загрузить в ПЛИС прошивку.
  • С помощью менеджера устройств обнаружить новое устройство на шине PCI и установить для него драйвер.
  • Перезагрузиться
  • После перезагрузки с помощью программы PciExpress можно производить запись/чтение регистров.


На нижеследующем рисунке как раз показан результат чтения регистра со смещением 0 в адресном пространстве BAR1:




Кастомизация модуля lbus_registers.v


Для того, чтобы исходный код можно было использовать в собственных проектах, требуется ввести в дизайн регистры, необходимые разработчику. Все пользовательские регистры находятся в модуле lbus_registers.v и при его кастомизации требуется осуществить следующие простые действия:
  1. Написать код для каждого пользовательского регистра
  2. Задать в списке параметров адрес каждого регистра
  3. Написать код дешифратора адреса для каждого регистра
  4. Подключить каждый регистр к шинам записи и чтения


Покажем, как осуществить эти действия на практике.
• Определяем имя регистра и его длину:
reg     [AXI_DATA_WIDTH-1:0]        my_register;

• Определяем стробы записи и чтения для этого регистра:
wire			selw_my_register;
wire			selr_my_register;


• Пишем always-блок для этого регистра. Это удобно делать с помощью оператора generate.
В самом простом случае код выглядит так:
genvar i;
generate
    for (i = 0; i < AXI_BE_WIDTH; i = i + 1)
	begin: leds_lanes
		always @( posedge clk or negedge rst_n )
		if (!rst_n)	my_register [7+ 8*i: 8*i] <= 8'h0;
			else
				if (selw_my_register && lbus_wr_be[i] )
					my_register[7+ 8*i: 8*i] <= lbus_wr_data[7+ 8*i: 8*i];
				else
					my_register [7+ 8*i: 8*i] <= my_register [7+ 8*i: 8*i];
    end
endgenerate


Если требуется более сложная обработка отдельных разрядов, то, always-блок, естественно усложнится и, возможно, проще будет написать код явно, не используя оператор generate.
• Добавляем в список параметров строчку:
parameter	ADDR_MY_REGISTER	= 32'h1234_5678
,
где – вместо 32'h1234_5678 указываем реальное смещение в байтах в требуемом адресном пространстве
• Пишем формулы для сигналов выбора регистра:
selw_my_register = reg_wr_hit & (lbus_wr_addr[REG_ADDR_WIDTH-1:0] == ADDR_MY_REGISTER [REG_ADDR_WIDTH+AXI_REMAIN_WIDTH-1:AXI_REMAIN_WIDTH]);
selr_my_register = reg_rd_hit & (lbus_rd_addr[REG_ADDR_WIDTH-1:0] == ADDR_MY_REGISTER [REG_ADDR_WIDTH+AXI_REMAIN_WIDTH-1:AXI_REMAIN_WIDTH]);


• В блок always_comb
always_comb
	begin
		case (1'b1)
	                   …
		endcase
	end


добавляем новую веточку внутри оператора case:
selr_my_register:	c_reg_rd_data = my_register;


Вышеописанные действия повторяем для каждого пользовательского регистра.

Интерфейс модуля


Интерфейс модуля определен следующим образом:

module lbus_registers #(
	parameter   BAR_NMB			= 3'd0
	parameter   AXI_DATA_WIDTH		= 128,
	parameter   AXI_BE_WIDTH		= AXI_DATA_WIDTH/8,	// AXI Len Width
	parameter   LBUS_ADDR_WIDTH	= 12,	// 64 KB expected for NWL Reference Design
	parameter   REG_ADDR_WIDTH		= LBUS_ADDR_WIDTH,	// 64 KB expected for NWL Reference Design
	parameter	ADDR_R0		= 32'h000_0000,
	parameter	ADDR_R1		= 32'h000_0020,
	parameter	ADDR_R2		= 32'h000_0040
)
(
	input  wire				rst_n,
	input  wire				clk,
//
	input  wire [7:0]			switches,
	output wire [AXI_DATA_WIDTH-1: 0]	rg1_out,
	output wire [AXI_DATA_WIDTH-1: 0]	rg2_out,
	output wire [71: 0]			debug_bus,
	
// Local Bus channel
	input  wire [LBUS_ADDR_WIDTH-1:0]	lbus_wr_addr,
	input  wire [2:0]			lbus_wr_region,
	input  wire				lbus_wr_en,
	input  wire [AXI_BE_WIDTH-1:0]	lbus_wr_be,
	input  wire [AXI_DATA_WIDTH-1:0]	lbus_wr_data,
//
	input  wire [LBUS_ADDR_WIDTH-1:0]	lbus_rd_addr,
	input  wire  [2:0]			lbus_rd_region,
	output wire [AXI_DATA_WIDTH-1:0]	lbus_rd_data
);


Настройка параметров


Параметры настройки модуля lbus_registers.v перечислены в таблице:
Имя параметра Значение по умолчанию Диапазон значений Описание
BAR_NMB 3'd0 3’d0-3’d7 Номер BARа, на который настроен адресный селектор
AXI_DATA_WIDTH 128 128, 256 Размер шины данных
AXI_BE_WIDTH AXI_DATA_WIDTH/8 Не следует менять вручную
LBUS_ADDR_WIDTH 12 8-15 Задает разрядность локальной шины адреса. Обычно соответствует размеру АП самого большого BARа
REG_ADDR_WIDTH LBUS_ADDR_WIDTH <=LBUS_ADDR_WIDTH Задает разрядность АП локальной шины адреса, соответствующей выбранному BARу
ADDR_R0
ADDR_R1
ADDR_R2
32'h000_0000 Зависит от размера BARа Адрес регистра R0 (R1,R2). Адреса регистров указываются всегда в байтах и соответствуют их смещению в адресном пространстве BARа



Отладка


Отладка осуществляется с помощью внутреннего анализатора сигналов, для чего в проекте используется модуль ACX_SNAPSHOT.v, подключаемый директивой условной компиляции `define USE_SNAPSHOT. Документация по организации внутрисхемной отладки находится на сайте Achronix в файле Snapshot User Guide.pdf.


Заключение и выводы


Даже такая непростая задача, как подключение к шине PCI-express решается на платформе Achronix Speedster22i легко и, главное, быстро. Создать работающий проект на базе аппаратного ядра PCIe оказалось не просто, а очень просто.
Рассказ о других аппаратных ядрах ПЛИС Achronix Speedster22i планируется по мере их освоения. В последующих постах будет рассказано про ядра DDR-3 и 100G Ehernet.


Ссылки


1. Achronix объявляет соответствие своих аппаратных ядер PCI Express в ПЛИС Speedster22i спецификации PCI-SIG (англ.) www.achronix.com/wp-content/uploads/pr/2014_May_PCI-SIG.pdf
2. Схема отладочной платы HD1000 dev kit (англ.) 22iHD1000_Development_Board_Schematic.pdf
3. Руководство по использованию контроллеров PCIe на Speedster22i (англ.) www.achronix.com/wp-content/uploads/docs/Speedster22i_PCIe_User_Guide_UG030.pdf
4. Руководство пользователя Snapshot (англ.) www.achronix.com/wp-content/uploads/docs/Speedster22i_Snapshot_User_Guide_UG016.pdf
5. Оригинальный reference design: Speedster22i_PCIe_Demo_Design.zip
6. Исходные файлы описываемого проекта: drive.google.com/file/d/0B9Gt8fTYH6s-VGhfbk5RQWM4bk0
Tags:ПЛИСFPGAVerilogAchronixSpeedsterPCIePCI-express
Hubs: High performance
+38
18.3k 95
Comments 29