Этот пост написан с целью показать разработчикам дизайна для ПЛИС, как с наименьшими затратами времени и сил начать работать с шиной 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