Этот пост написан с целью показать разработчикам дизайна для ПЛИС, как с наименьшими затратами времени и сил начать работать с шиной 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 и при его кастомизации требуется осуществить следующие простые действия:
- Написать код для каждого пользовательского регистра
- Задать в списке параметров адрес каждого регистра
- Написать код дешифратора адреса для каждого регистра
- Подключить каждый регистр к шинам записи и чтения
Покажем, как осуществить эти действия на практике.
• Определяем имя регистра и его длину:
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
Автор: fpgaFAE