Рабочий процесс Генерации Ядра IP с процессором MicroBlaze: Xilinx Kintex-7 KC705

Этот пример показывает, как использовать Рабочий процесс Генерации Ядра IP HDL Coder™, чтобы разработать исходный дизайн для частей Xilinx® без встроенного существующего процессора ARM®, но которые все еще используют сгенерированный интерфейс AXI HDL Coder™, чтобы управлять DUT. А именно, этот пример будет использовать плату Xilinx Kintex-7 KC705, и мягкий процессор MicroBlaze™, запускающий LightWeightIP, (lwIP) базировался, сервер встроенного программного обеспечения TCP/IP в исходном проекте, чтобы получить доступ к HDL Coder™ сгенерировал регистры DUT отовсюду в связанной сети. Далее, этот пример также подсветит различие между доступом к данным из набора регистров, реализованных как несколько скалярных портов и набором регистров, реализованных как один векторный порт.

Требования

  • Комплект Проекта Xilinx Vivado, с поддерживаемой версией, перечисленной в документации HDL Coder

  • Макетная плата Xilinx Kintex-7 KC705

  • Пакет поддержки HDL Coder™ для Советов FPGA Xilinx

  • Соединение Ethernet

Макетная плата Xilinx Kintex-7 KC705

Исходные проекты в качестве примера

MicroBlaze является простым, универсальным ненавязчивым процессором, который может использоваться в FPGA Xilinx только платформы, такие как Kintex-7, чтобы выполнить функциональность законченный процессор, или как гибкий, программируемый IP. Когда программы являются маленькими, ELF может находиться в BRAM, и проект становится полностью сам содержавшимся в FPGA. Существует много приложений, хорошо подходящих для того, чтобы быть реализованным на MicroBlaze. Здесь мы перечисляем только некоторых:

  1. Удаленное сетевое управление развернутого Основного алгоритма IP

  2. Встроенный веб-сервер для управления и отображения данных

  3. Интегрирование существующих алгоритмов программного обеспечения на платформы только для оборудования

Следующее является схемой уровня системы для этой системы MicroBlaze:

Исходный проект, "Xilinx MicroBlaze TCP/IP AXI4-облегченному Ведущему устройству", использует Vivado™ MicroBlaze IP, чтобы перевести пакеты TCP/IP в AXI4-облегченные чтения и записи. Ниже блок-схема полной системы, включая все периферийные устройства, требуемые управлять сервером TCP/IP и отладкой через последовательную консоль UART.

MicroBlaze Setup

В порядке управлять сервером TCP/IP, MicroBlaze IP нужны несколько основных периферийных устройств:

  • локальная память (BRAM) для данных/инструкций

  • Ядро Ethernet для передачи и приема кадров

  • Ядро UART для отправки сообщений отладки

  • Ядро таймера для генерации прерываний тайм-аута

  • Контроллер прерываний для обработки прерываний от всех этих периферийных устройств.

Как видно в редакторе адреса ниже, все эти периферийные устройства соединяются с MicroBlaze через интерфейсы AXI4.

Сумма локальной памяти, выделенной с помощью BRAM, составляет 1 МБ. Эта сумма необходима, чтобы запустить стек lwIP. Преимущество определения BRAM для локальной памяти - то, что исполняемый ELF может быть включен в поток битов, который упрощает программирование и позволяет предназначаться для существующих плат FPGA, которые не могут иметь внешней памяти DRAM. Однако это удобство прибывает в стоимость увеличенного использования:

Если использование BRAM является беспокойством, и ресурсы DRAM доступны, можно решить заменить локальную память BRAM на внешнюю память DRAM. Дополнительную информацию см. в примечании "xapp1026" к приложению Xilinx по альтернативным настройкам и информации приложений.

Исходный проект в качестве примера plugin_rd.m

plugin_rd.m для этого исходного проекта показывают ниже:

function hRD = plugin_rd()
% Reference design definition
%   Copyright 2014-2018 The MathWorks, Inc.
% Construct reference design object
hRD = hdlcoder.ReferenceDesign('SynthesisTool', 'Xilinx Vivado');
hRD.ReferenceDesignName = 'Xilinx MicroBlaze TCP/IP to AXI4-Lite Master';
hRD.BoardName = 'Xilinx Kintex-7 KC705 development board';
% Tool information
hRD.SupportedToolVersion = {'2017.2','2017.4'};
%% Add custom design files
% add custom Vivado design
hRD.addCustomVivadoDesign( ...
  'CustomBlockDesignTcl', 'system_top.tcl',...
  'VivadoBoardPart',      'xilinx.com:kc705:part0:1.1');
% add custom files, use relative path
hRD.CustomFiles         = {'mw_lwip_tcpip_axi4.elf'};
%% Add interfaces
% add clock interface
hRD.addClockInterface( ...
  'ClockConnection',      'clk_wiz_0/clk_out1', ...
  'ResetConnection',      'proc_sys_reset_0/peripheral_aresetn',...
  'DefaultFrequencyMHz',  100,...
  'MinFrequencyMHz',      100,...
  'MaxFrequencyMHz',      100,...
  'ClockNumber',          1,...
  'ClockModuleInstance',  'clk_wiz_0');
% add AXI4 and AXI4-Lite slave interfaces
hRD.addAXI4SlaveInterface( ...
  'InterfaceConnection', 'microblaze_0_axi_periph/M04_AXI', ...
  'BaseAddress',         '0x44A00000',...
  'MasterAddressSpace',  'microblaze_0/Data',...
  'InterfaceType',       'AXI4-Lite',...
  'InterfaceID',         'MicroBlaze AXI4-Lite Interface');
hRD.EmbeddedCoderSupportPackage = hdlcoder.EmbeddedCoderSupportPackage.None;  %None

Обратите внимание на то, что исходный проект включает исполняемый файл MicroBlaze mw_lwip_tcpip_axi4.elf в свойство CustomFiles исходного проекта. Это скопирует исполняемый файл от исходного проекта до проекта Vivado так, чтобы это могло быть сопоставлено с MicroBlaze IP.

Дополнительный код в 'system_top.tcl', чтобы присоединить ELF к uBlaze

'system_top.tcl' файл, включенный в наиболее исходные проекты, используется, чтобы создать верхний уровень блок-схема Интегратора IP Vivado, содержащая большую часть IP исходного проекта. Здесь мы добавляем некоторый дополнительный код Tcl в этот файл после того, как блок-схема была создана, чтобы сопоставить автономный исполняемый файл MicroBlaze ELF с MicroBlaze IP.

   import_files -norecurse mw_lwip_tcpip_axi4.elf
   generate_target all [get_files system_top.bd]
   set_property SCOPED_TO_REF system_top [get_files -all -of_objects [get_fileset sources_1] {mw_lwip_tcpip_axi4.elf}]
   set_property SCOPED_TO_CELLS { microblaze_0 } [get_files -all -of_objects [get_fileset sources_1] {mw_lwip_tcpip_axi4.elf}]

Выполнение этого позволяет ELF быть группированным с потоком битов и запрограммированным в память MicroBlaze BRAM в то же время, что и FPGA.

Выполните рабочий процесс ядра IP

Используя вышеупомянутый исходный проект вы сгенерируете Ядро IP HDL, которое мигает светодиодами на плате KC705. Вы будете затем использовать tcpclient, чтобы отправить/получить отформатированные пакеты в MicroBlaze, чтобы выпустить чтения/записи по интерфейсу AXI4-Lite к сгенерированному Ядру IP HDL. Файлы, используемые на следующей демонстрации, расположены в:

  • matlab/toolbox/hdlcoder/hdlcoderdemos/customboards/KC705

1. Добавьте файлы исходного проекта MicroBlaze в путь MATLAB с помощью команды:

>> addpath(fullfile(matlabroot,'toolbox','hdlcoder','hdlcoderdemos','customboards','KC705'));

2. Создайте путь к инструменту Xilinx Vivado™ при помощи следующей команды:

>> hdlsetuptoolpath('ToolName', 'Xilinx Vivado', 'ToolPath', 'C:\Xilinx\Vivado\2017.4\bin\vivado.bat');

Используйте свой собственный путь к установке Xilinx Vivado™ при выполнении команды.

3. Откройте модель Simulink, которая реализует мигание LED, а также векторные выходные порты для сравнения со скалярными портами, с помощью команды:

open_system('hdlcoder_led_vector')

4. Запустите HDL Workflow Advisor от подсистемы hdlcoder_led_vector/DUT путем щелчка правой кнопкой по подсистеме DUT и выбора HDL Code> HDL Workflow Advisor.

5. Выберите исходный проект из выпадающего на шаге 1.2

6. Присвойте порты регистра "MicroBlaze AXI4-облегченный Интерфейс". Они затем будут доступны при шестнадцатеричном смещении, показанном в таблице.

7. Запустите остающиеся шаги в рабочем процессе, чтобы сгенерировать поток битов и программировать целевое устройство.

Определение адресов из отчета ядра IP

Базовый адрес для Ядра IP HDL Coder™ задан в исходном проекте plugin_rd.m со следующей командой:

% add AXI4 and AXI4-Lite slave interfaces
hRD.addAXI4SlaveInterface( ...
  'InterfaceConnection', 'microblaze_0_axi_periph/M04_AXI', ...
  'BaseAddress',         '0x44A00000',...
  'MasterAddressSpace',  'microblaze_0/Data',...
  'InterfaceType',       'AXI4-Lite',...
  'InterfaceID',         'MicroBlaze AXI4-Lite Interface');

Для этого проекта базовым адресом является 0x44A0_0000. Смещения могут быть найдены в таблице IP Core Report Register Address Mapping:

Векторное Чтение-запись Данных с Синхронизацией Строба

Векторные данные поддерживаются на AXI4/AXI4-Lite интерфейсы с R2017a. В отличие от набора скалярных портов, все элементы векторных данных обработаны как синхронные к логике Основного алгоритма IP. Дополнительные регистры строба, добавленные для каждого векторного порта ввода и вывода, поддерживают эту синхронизацию через несколько последовательных чтений/записей AXI4.

Для входных портов регистр строба управляет разрешением на наборе теневых регистров, позволяя логике ядра IP видеть все обновленные векторные элементы одновременно. Для выходных портов регистр строба управляет синхронным получением векторных данных, которые будут считаны. Ниже схема логики синхронизации, сгенерированной с векторными данными:

Соединитесь с сервером TCP/IP

Чтобы начать взаимодействовать с сервером TCP/IP, работающим на MicroBlaze, сначала соедините последовательную консоль UART, чтобы просмотреть сообщения отладки, которые помогут гарантировать, что вещи работают как ожидалось. Сначала найдите последовательный порт, который соединяется с UART на плате:

Затем используйте этот порт, чтобы соединить использование программы, такой как PuTTY™:

После того, как соединенный с последовательной консолью UART, запустите скрипт hdlworkflow_ProgramTargetDevice.m, чтобы повторно программировать плату.

   >>hdlworkflow_ProgramTargetDevice
   ### Workflow begin.
   ### Loading settings from model.
   ### ++++++++++++++ Task Program Target Device ++++++++++++++
   ### Generated logfile: hdl_prj\hdlsrc\hdlcoder_led_vector\workflow_task_ProgramTargetDevice.log
   ### Task "Program Target Device" successful.
   ### Workflow complete.

В консоли необходимо видеть следующий заголовок, отображая IP-адрес и номер порта, с которым соединяется сервер.

ПРИМЕЧАНИЕ: Если плата будет соединена с сетью с включенным сервером DHCP, информация о IP будет отличаться, чем показанный выше. В этом случае необходимо будет изменить строку 43 из скрипта read_write_test.m, чтобы соединиться с правильным IP-адресом платы:

t = tcpclient('192.168.1.10',7);

Отправка AX4-облегченных транзакций к MicroBlaze из MATLAB с помощью tcpipclient

В порядке выпустить чтения и записи к Ядру IP через TCP/IP и AXI4-облегченный, адрес, данные и команда, которая будет выполняться, должны быть закодированы в пакете, отправленном в сервер TCP/IP. В данном примере мы используем следующий формат пакета:

               [--Address--]  32-bits
               [----Cmd----]  lower byte of 32-bit word (READ = 0, WRITE = 1, DEBUG = 2)
               [---Length---]  lower byte of 32-bit work (N<255)
               [----Data---]  32-bits, used only for WRITE cmd

Например, пакет, выпуская чтение 3 последовательных значений, запускающихся в адресе 0x44a0010c:

               [44 a0 01 0c]
               [00 00 00 00]
               [00 00 00 03]

Это возвратит данные при смещениях 0x10c, 0x110,0x114.

Например, пакет, выпуская запись 0x0, чтобы сместить 0x04, был бы:

               [44 a0 00 04]
               [00 00 00 01]
               [00 00 00 01]
               [00 00 00 00]

Изменить уровень отладки раньше распечатывало к консоли, отправляло отладку cmd пакет:

               [xx xx xx xx]
               [00 00 00 03]
               [00 00 00 01] %0 = no msg, 1 = READ|WRITE, 2 = full pkt

Запустите read_write_test.m скрипт

Этот пример включает скрипт, который установит связь с сервером TCP/IP, работающим на MicroBlaze, создаст команды, чтобы позволить/запретить DUT, считать 6 скалярных портов и те же данные как векторный порт и сравнить результаты.

1. Чтобы запустить этот скрипт, сначала скопируйте его в свою локальную директорию

>> copyfile(fullfile(matlabroot,'toolbox','hdlcoder','hdlcoderdemos','ublaze_lwip_read_write_vector_test.m'),'ublaze_test.m');

и откройте скрипт в редакторе:

>> edit('ublaze_test.m');

Скрипт имеет три раздела. Первый раздел, соединяется с платой и настраивает команды, которые будут использоваться. При необходимости обновите IP-адрес платы на строке 41.

2. Выполните раздел 1. Вы сгенерируете следующие команды как массивы типов uint32:

read6_cmd      = uint32([hex2dec('44a0010c') 0 6]);   %read 6 32-bit regs
read_vec_cmd   = uint32([hex2dec('44a00140') 0 6]);   %read 6 elements of vec
strobe_vec_cmd = uint32([hex2dec('44a00160') 1 1 1]); %write strobe for vec
enable_cmd     = uint32([hex2dec('44a00004') 1 1 1]); %enable ip core
disable_cmd    = uint32([hex2dec('44a00004') 1 1 0]); %disable ip core
debug0_cmd     = uint32([hex2dec('00000000') 3 0]);   %disable all debug printfs
debug1_cmd     = uint32([hex2dec('00000000') 3 1]);   %enable READ|WRITE printfs
debug2_cmd     = uint32([hex2dec('00000000') 3 2]);   %enable pkt printfs

ПРИМЕЧАНИЕ: эти массивы хранят данные в формате порядка байтов, используемом локальной машиной, которая для многих x86 систем является форматом с прямым порядком байтов. Однако сервер TCP/IP ожидает значения в формате с обратным порядком байтов (сетевой порядок байтов). В результате, если система, вы включены, является прямым порядком байтов, байты в каждом элементе должны быть подкачаны с помощью swapbytes.

3. Выполните раздел 2, чтобы отключить логику DUT и считать одно встречное значение, соединенное с 6 скалярными портами, а также всеми 6 элементами в векторном порте. Заметьте, что все встречные значения соответствуют. Это вызвано тем, что те же данные управляются ко всем портам, и DUT отключен, таким образом, асинхронный доступ через интерфейс AXI4 не очевиден.

   Scalar port (top) vs vector port (bottom) access with DUT disabled:
      7e8aec14   7e8aec14   7e8aec14   7e8aec14   7e8aec14   7e8aec14
      7e8aec14   7e8aec14   7e8aec14   7e8aec14   7e8aec14   7e8aec14

4. Выполните раздел 3, чтобы повторно включить логику DUT и считать те же встречные значения назад. Заметьте, что 6 скалярных портов, которые все показывают различным значениям, в то время как 6 элементов векторного порта являются всеми одинаковыми. Это происходит из-за последовательного доступа, который должен произойти через интерфейс AXI4 и отсутствие регистра синхронизации в скалярном случае порта и присутствии явного регистра синхронизации в векторном случае порта.

   Scalar port (top) vs vector port (bottom) access with DUT enabled:
      7f7796dc   7fc70e4b   8016860a   8065fdce   80b5758d   8104ed4d
      815964dd   815964dd   815964dd   815964dd   815964dd   815964dd

Соответствующая отладка вывод на последовательной консоли будет:

Сводные данные

Эта демонстрация подсветила использование MicroBlaze, который только разрабатывает ненавязчивый процессор в FPGA. MicroBlaze хорошо подходит функционировать как законченный процессор или как гибкий IP рабочий устаревший код С как микропрограммное приложение. Эта демонстрация также показала различие между набором скалярных портов и векторным портом в отношении синхронизации данных через интерфейс AXI4.

Приложение A: Создание и редактирование приложения SDK Xilinx

Этот раздел покажет, как создать новый проект SDK Xilinx и включить код от этого примера, чтобы затем изменить или расширить.

1. Откройтесь проект Xilinx Vivado путем щелчка по ссылке на шаге 4.1 HDL Workflow Advisor "Создают Проект":

2. Экспортируйте существующий проект, включая сгенерированный поток битов, к локальной папке. Если это сделано, разрешение и "SDK Запуска" также.

3. Из SDK создайте новый проект приложения

4. У вас затем будет опция именования проекта и создания нового bsp

Затем можно выбрать из нескольких предварительно сконфигурированных проектов примера/шаблона начать. Этот пример создается прочь "lwIP проект" Сервера Эха, так выбор это теперь.

5. Используя сервер эха как шаблон, можно заменить следующие 3 метода на фрагмент кода ниже, чтобы изменить поведение сервера

Приложение B: Скопируйте содержимое файла C в проект

#define IPCOREBASE 0x44a00000
#define WRITE  0x01
#define READ   0x00
#define DEBUG  0x03

int transfer_data() {
    return 0;
}

void print_app_header()
{
    xil_printf("\n\r\n\r-----MathWorks HDL Coder AXI4-Lite IP Core Read/Write Server ------\n\r");
    xil_printf("  TCP packets sent to port 7 will be issued as AXI4-Lite Read/Writes\n\r");
    xil_printf("\n\r");
    xil_printf("  [ 32-bit address ] (Base Address = 0x44a0_0000)\n\r");
    xil_printf("  [ 32-bit   cmd   ] (read = 0x00, write =0x01, debug = 0x03)\n\r");
    xil_printf("  [ 32-bit   len   ] ( N<255)\n\r");
    xil_printf("  [ 32-bit   data  ] (N 32-bit data values for write cmd)\n\r");
    xil_printf("------------------------------------------------------------ ------\n\r");

}

void print_packet(struct pbuf *p) {
    u16 ii;
    u8 *pktPtr;

    pktPtr = p->payload;
    xil_printf("DEBUG | packet payload:\r\n");
    for (ii=0;ii<p->len;ii+=4) {
        xil_printf("%02x %02x %02x %02x\r\n",*(pktPtr+ii),*(pktPtr+ii+1),*(pktPtr+ii+2),*(pktPtr+ii+3));
    }


}
err_t recv_callback(void *arg, struct tcp_pcb *tpcb,
                               struct pbuf *p, err_t err)
{
    u8 *pktPtr,*pktEnd;
    volatile u32 *addr;
    u32 data[255],cmd;
    u16 len;
    int ii;
    static u8 debug = 3;

    /* do not read the packet if we are not in ESTABLISHED state */
    if (!p) {
        tcp_close(tpcb);
        tcp_recv(tpcb, NULL);
        return ERR_OK;
    }

    /* indicate that the packet has been received */
    tcp_recved(tpcb, p->len);
    if (debug > 1) print_packet(p);

    //[ 32 bits address ]
    //[ 32 bits read = 0x00, write =0x01]
    //[ 32 bits length ]
    //[ 32 bits write data]
    pktPtr = p->payload;
    pktEnd = pktPtr+p->len;


    /* could be multiple commands per packet */
    while ( pktPtr < pktEnd) {
        addr = (u32*) (pktPtr[0]<<24 | pktPtr[1]<<16 | pktPtr[2]<<8 | pktPtr[3]);
        cmd  = (u32) pktPtr[7];      // cmd is 32 bits, but only 1st byte used, ignore rest
        pktPtr += 8;

        switch(cmd) {
        case WRITE :
            len  = (u32) pktPtr[3];  // len is 32 bits, but only 1st byte used, ignore rest
            pktPtr += 4;
            for (ii=0;ii<len; ii++) {
                data[0] = (u32) (pktPtr[0]<<24 | pktPtr[1]<<16 | pktPtr[2]<<8 | pktPtr[3]);
                *addr = data[0];
                if (debug > 0) xil_printf("WRITE | address: 0x%08x, data[0]: 0x%08x\r\n",addr,data[0]);
                addr++;
                pktPtr += 4;
            }
            break;
        case READ :
            len  = (u32)   pktPtr[3]; // len is 32 bits, but only 1st byte used, ignore rest
            pktPtr += 4;
            for (ii=0;ii<len; ii++) {
                data[ii] = *addr;
                if (debug > 0) xil_printf("READ  | address: 0x%08x, data[%d]: 0x%08x\r\n",addr,ii,data[ii]);
                addr++;
            }
            /* send the packet back */
            if (tcp_sndbuf(tpcb) > p->len)
                err = tcp_write(tpcb, data, 4*len, 1);
            else
                xil_printf("no space in tcp_sndbuf\n\r");
            break;
        case DEBUG:
            debug = pktPtr[3]; // only need the low byte
            pktPtr += 4;
            xil_printf("Debug level set to : 0x%02x\r\n",debug);
            break;
        default :
            xil_printf("INVALID | cmd: 0x%08x\r\n",cmd);

        }
    }
        /* free the received pbuf */
        pbuf_free(p);

        return ERR_OK;
}

6. Сохраните измененный echo.c файл, и приложение будет восстановлено.

Приложение C: Программируйте FPGA с ELF и потоком битов

Теперь, можно программировать FPGA с помощью экспортируемого потока битов и недавно созданного файла ELF.