[Из песочницы] Поднимаем SOC: ARM + FPGA

f17cd045d9c3411c8dc81ac58b65f55d.jpgНа днях ко мне в руки попала EBV SoCrates Evaluation Board. В двух словах — это плата с SoC от фирмы Altera, на борту которой есть двухъядерный ARM и FPGA Cyclone V.

ARM и FPGA на одном чипе — это должно быть очень интересно! Но для начала всё это добро нужно «поднять».Об этом процессе я и поведаю в данной статье.

Если вам в руки попала такая или подобная плата и вы не до конца уверены, что же с ней нужно делать. Если вы всегда думали, что FPGA — это что-то сложное и непонятно, как к этому подступиться. Или вы просто любопытный инженер. Тогда заходите. Мы всем рады.

А в качестве маленького бонуса измерим пропускную способность между CPU и FPGA.

План работНаш план состоит из следующих пунктов: Получение прошивки FPGA Сборка ядра Сборка U-Boot и Preloader Сборка rootfs Написание тестовых программ Создание SD-карты Запуск платы и измерение пропускной способности Поехали! Создание прошивки FPGA Первым делом нам нужно получить прошивку FPGA.Из инструментов для этого понадобится САПР Quartus, скачать его можно на официальном сайтеОписывать установку не буду — там всё достаточно очевидно.Создание проекта Запускаем Quartus, идём в File → New Project Wizard, жмём Next, заполняем директорию и название проекта: Название проекта b629b5fdce6045b8b42aa3c839c6a2e7.png Следующую страницу пропускаем, потом идёт выбор семейства и типа ПЛИС.Выбор ПЛИС 1cddfbdde3554992ab7618bf47325682.png Остальные настройки для нас не важны, жмём Finish.Проект Qsys Qsys — отличный инструмент для начинающих. Позволяет получить прошивку, не написав ни строчки кода. Вместо этого разработчик собирает конструктор из заранее заданных кубиков (IP-корок). Требуется только правильно настроить каждую корку и соединить их должным образом.Итак, Tools → Qsys, в левом окне (IP Catalog) нам потребуются две IP-корки:

Processors and Peripherals → Hard Processor Systems → Arria V / Cyclone V Hard Processor System Basic Functions → On Chip Memory → On Chip Memory (RAM or ROM) Hard Processor System (HPS) — это наш ARM. С его настроек и начнем.На первой вкладке нас интересует HPS-to-FPGA interface width, чтобы мы имели доступ из CPU ко внутренней памяти FPGA:

FPGA Interfaces 3131ea70bc80426baa9fbfb2da3de9bd.png Дальше идёт куча настроек для различных интерфейсов — в каких режимах работают, какие пины используются: Peripheral Pins b6a3bbedd93e4bdd9665e076ccdd2219.png Следующая вкладка — настройка клоков. В Inputs Clocks оставляем всё без изменений: Input Clocks d83ec9d891bf4ba1b54510ed19d5f9f1.png В Output Clocks ставим галку на Enable HPS-to-FPGA user 0 clock: Output clocks ab21cef5e9014c659639fdb095a08561.png Потом идёт большой подраздел с различными настройками для DDR3 памяти.DDR3 PHY Setting 20fa85e7d7c94318a611f4d0dd75feb0.png DDR3 Memory Parameters 9d0b00db688a4ed89a3e5967dd2ca2aa.png DDR3 Memory Timing 1136cb1cd7ad4198a4efa3e038e989db.png DDR3 Board Settings 35bcf6ac13ed4502ab9bc21841e35044.png С HPS мы разобрались, переходим к настройке On-Chip памяти. Это память, которая расположена непосредственно внутри ПЛИС.Настроек тут значительно меньше: On-Chip Memory c481c064d74148e990b57f0aea1202bd.png Теперь нужно соединить блоки между собой. Всё достаточно интуитивно (обратите внимание на значение базового адреса напротив s1): Qsys Connections 399f2310cf2640fca549e97da4115747.png Готово. Сохраняем (File → Save) под именем soc.Осталось сгенерировать файлы. Кнопка Generate HDL, в появившемся окне опять жмём Generate, ждём, Finish.

Компиляция проекта Теперь нужно добавить сгенерённые файлы в проект: Assignments → Settings вкладка Files, добавляем файл soc/synthesis/soc.qipНужно применить настройки для DDR пинов. Но перед этим нужно выполнить первую стадию компиляции: Processing → Start → Start Analysis & Synthesis

Запускаем скрипт для настройки пинов: Tools → Tcl Scripts. В появившемся окне выбираем Project → soc → synthesis → submodules → hps_sdram_p0_pin_assignments.tcl, Run.

Финальная компиляция проекта: Processing → Start Compilation

Мы получили файл soc.sof c прошивкой FPGA. Но мы хотим прошивать ПЛИС прямо из CPU, поэтому нам понадобится другой формат. Выполним конвертацию. Это можно делать и из GUI, но в консоле проще. Да и вообще, пора уже отвыкать от GUI:).

Для конвертации надо запустить терминал и перейти в директорию с нашим проектом. Далее перейти в output_files и выполнить команду (не забываем, что директория с утилитами Quartus дожна быть в переменной PATH):

quartus_cpf -c soc.sof soc.rbf Ура! Мы получили прошивку FPGA.Сборка ядра Теперь соберём ядро для нашего ARM.Из инструментов потребуется Altera SoC EDS. Отсюда мы будет брать компилятор arm-linux-gnueabihf- для кросс-компиляции.Выкачиваем ядро:

git clone https://github.com/coliby/terasic_MTL.git Запускаем скрипт, который добавит в PATH директории с компилятором и запустит bash: /opt/altera/quartus14.0/embedded/embedded_command_shell.sh Устанавливаем переменные окружения: export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- export LOADADDR=0×8000 Переходим в директорию с ядром и выполняем конфигурацию: cd terasic_MTL/ make socfpga_defconfig Cобираем образ ядра для U-Boot: make -j 4 uImage Теперь нам нужно получить так называемый .dtb (Device Tree Blob) файл. Это бинарный файл, содержащий информацию о платформе — интерфейсы, пины, тактовые сигналы, адресное пространство и т.д. Ядро читает этот файл во время инициализации и вносит в неё изменения. Это позволяет использовать одно собранное ядро на нескольких аппаратных платформах.Итак, получаем .dtb файл: make socfpga_cyclone5.dtb Но этот файл не для нашей платформы, поэтому нам придётся внести в него небольшие изменения. Для этого конвертируем файл в текстовый формат .dts (Device Tree Source): ./scripts/dtc/dtc -I dtb -O dts -o soc.dts arch/arm/boot/dts/socfpga_cyclone5.dtb Теперь в soc.dts нужно удалить блок bridge@0xff200000. Это можно сделать либо руками, либо наложив патч: patch soc.dts dts.patch dts.patch 942,966d941 < bridge@0xff200000 { < compatible = "altr,h2f_lw_bridge-1.0", "simple-bus"; < reg = <0xff200000 0x200000>; < #address-cells = <0x1>; < #size-cells = <0x1>; < ranges = <0x200 0xff200200 0x80 0x100 0xff200100 0x80>; < < tsc@0x200 { < compatible = "terasic,mlt_touch_screen"; < reg = <0x200 0x80>; < width_pixel = <0x320>; < height_pixel = <0x1e0>; < interrupts = <0x0 0x28 0x4>; < }; < < vip2@0x100 { < compatible = "ALTR,vip-frame-reader-13.0", "ALTR,vip-frame-reader-9.1"; < reg = <0x100 0x80>; < max-width = <0x320>; < max-height = <0x1e0>; < mem-word-width = <0x100>; < bits-per-color = <0x8>; < }; < }; < Теперь конвертируем файл обратно в .dtb: ./scripts/dtc/dtc -I dts -O dtb -o soc.dtb soc.dts Итого, нас интересует два файла:arch/arm/boot/uImage soc.dtb Сборка U-Boot и Preloader Процесс запуска SoC выглядит следующим образом:Boot ROM Preloader Bootloader OS Boot ROM — это первая стадия загрузки, которая выполняется сразу после поднятия питания. Её основная функция — определить и выполнить вторую стадию, Preloader.Функциями Preloader чаще всего являются инициализация SDRAM интерфейса и конфигурация пинов HPS. Инициализация SDRAM позволяет выполнить загрузку следующей стадии из внешней памяти, так как её код может не поместиться в 60 КБ доступной встроенной памяти.

Bootloader может участвовать в дальнейшей инициализации HPS. Также эта стадия выполняет загрузку операционной системы либо пользовательского приложения. Обычно (и в нашем случае) в качестве Bootloader выступает U-Boot.

OS — тут всё просто. Это наш любимый Linux. Ядро для него у нас уже есть, корневую файловую систему получим чуть позже.А в сейчас мы займемся Preloader и U-Boot

Открываем терминал, запускаем уже знакомый нам скрипт:

/opt/altera/quartus14.0/embedded/embedded_command_shell.sh Заходим в директорию с нашим проектом: cd ~/src/soc_test/ После компиляции там должна появиться директория hps_isw_handoff, переходим в неё: cd hps_isw_handoff Запускаем генерацию необходимых файлов: bsp-create-settings --type spl --bsp-dir build --preloader-settings-dir soc_hps_0 --settings build/settings.bsp --set spl.boot.WATCHDOG_ENABLE false После этого дожна появиться директория build.Собираем Preloader: make -C build Собираем U-boot: make -C build uboot Теперь нам нужно настроить переменные для U-Boot. Вначале создаем текстовый файл u-boot-env.txt.u-boot-env.txt console=ttyS0 baudrate=115200 bootfile=uImage bootdir=boot bootcmd=run mmcboot bootdelay=3 fdt_file=soc.dtb fdt_addr_r=0xf00000 ethaddr=00:01:02:03:04:05 kernel_addr_r=0×10000000 mmcroot=/dev/mmcblk0p2 mmcpart=2 con_args=setenv bootargs ${bootargs} console=${console},${baudrate} misc_args=setenv bootargs ${bootargs} uio_pdrv_genirq.of_id=generic-uio mmc_args=setenv bootargs ${bootargs} root=${mmcroot} rw rootwait mmcboot=mmc rescan; ext2load mmc 0:${mmcpart} ${kernel_addr_r} ${bootdir}/${bootfile}; ext2load mmc 0:${mmcpart} ${fdt_addr_r} ${bootdir}/${fdt_file}; run mmc_args con_args misc_args; bootm ${kernel_addr_r} — ${fdt_addr_r} verify=n Затем конвертируем его в бинарный формат, не забыв указать размер области, содержащей переменные — 4096 байт нам вполне хватит. Даже если реальный размер превысит заданный, mkenvimage сообщит об этом. ./build/uboot-socfpga/tools/mkenvimage -s 4096 -o u-boot-env.img u-boot-env.txt Нас интересуют три файла: build/uboot-socfpga/u-boot.img u-boot-env.img build/preloader-mkpimage.bin Сборка rootfs Это раздел написан для тех, кто использует Debian (или в если Вашем дистрибутиве тоже есть debootstrap). Если Вы не среди них — можете воспользоваться Yocto или любым другим удобным для Вас методом.Устанавливаем необходимые пакеты:

sudo apt-get install debootstrap qemu-user-static binfmt-support Создаем директорию и выкачивает туда необходимые файлы: mkdir rootfs sudo debootstrap --arch armel --foreign wheezy rootfs http://ftp.debian.org/debian Чтобы запускать приложения, собранные под ARM-архитектуру, будем использовать qemu static. Для этого скопируем файл в нашу rootfs: sudo cp /usr/bin/qemu-arm-static rootfs/usr/bin/ Переходим в нашу новую файловую систему: sudo chroot rootfs /bin/bash Если приглашение интерпретатора изменилось на «I have no name!@hostname:/#», значит всё прошло успешно.Заканчиваем процесс установки: /debootstrap/debootstrap --second-stage В /etc/inittab оставляем следующие строки:/etc/inittab id:5: initdefault:

si: sysinit:/etc/init.d/rcS

~~: S: wait:/sbin/sulogin

l0:0: wait:/etc/init.d/rc 0 l1:1: wait:/etc/init.d/rc 1 l2:2: wait:/etc/init.d/rc 2 l3:3: wait:/etc/init.d/rc 3 l4:4: wait:/etc/init.d/rc 4 l5:5: wait:/etc/init.d/rc 5 l6:6: wait:/etc/init.d/rc 6

z6:6: respawn:/sbin/sulogin S:2345: respawn:/sbin/getty 115200 console Устанавливаем пароль: passwd Создаём архив: tar -cpzf rootfs.tar.gz --exclude=rootfs.tar.gz / Написание тестовых программ Если говорить в двух словах, то почти всё взаимодействие между компонентами SoC происходит при помощи отображения адресного пространства одного компонента в адресное пространство другого.Рассмотрим на примере. В нашем проекте при помощи Qsys мы указали, что на интерфейсе HPS-to-FPGA начиная с адреса 0 расположен блок On-Chip памяти размером 262144 байт. Сам интерфейс HPS-to-FPGA отображается в адресное пространство CPU по адресу 0xC0000000 (см. документацию на Cyclone V). В итоге обращение CPU по адресам от (0xC0000000 + 0) до (0xC0000000 + 262143) будет приводить к обращению ко внутренней памяти FPGA.Поэтому для работы нам потребуется утилита, с помощью которой можно читать/писать про произвольным адресам памяти. Вот её исходный код:

mem.c #include #include #include #include #include #include #include #include #define MAP_SIZE (4096) #define MAP_MASK (MAP_SIZE-1)

int main (int argc, char *argv[]) { int fd;

if (argc < 2 ) { printf( "Usage:\n" ); printf( "%s byte_addr [write_data]\n", argv[ 0 ] ); exit( -1 ); }

// /dev/mem это файл символьного устройства, являющийся образом физической памяти. fd = open (»/dev/mem», O_RDWR | O_SYNC); if (fd < 0 ) { perror( "open" ); exit( -1 ); }

void *map_page_addr, *map_byte_addr; off_t byte_addr; byte_addr = strtoul (argv[ 1 ], NULL, 0);

// Выполняем отображение файла /dev/mem в адресное пространство нашего процесса. Получаем адрес страницы. map_page_addr = mmap (0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, byte_addr & ~MAP_MASK); if (map_page_addr == MAP_FAILED) { perror («mmap»); exit (-1); }

// Вычисляем адрес требуемого слова (адрес при этом байтовый) map_byte_addr = map_page_addr + (byte_addr & MAP_MASK);

uint32_t data;

// Если аргументов три, значит записываем данные, иначе — читаем и выводим на экран. if (argc > 2) { data = strtoul (argv[ 2 ], NULL, 0); *((uint32_t *) map_byte_addr) = data; } else { data = *((uint32_t *) map_byte_addr); printf («data = 0x%08x\n», data); }

// Убираем отображение. if (munmap (map_page_addr, MAP_SIZE)) { perror («munmap»); exit (-1); }

close (fd); return 0; } Теперь нужно собрать её с использованием кросс-компилятора. Для этого запускаем скрипт: /opt/altera/quartus14.0/embedded/embedded_command_shell.sh И выполняем компиляцию: arm-linux-gnueabihf-gcc -o mem.o mem.c Также нам нужна утилита для измерения пропускной способности: memblock.c #include #include #include #include #include #include #include #include

// Валидные коды операций #define COP_WRITE (0) #define COP_READ (1) #define COP_CHECK (2)

int main (int argc, char *argv[ 0 ]) { int fd; void *map_addr; if (argc < 5 ) { printf( "Usage:\n" ); printf( "%s

\n», argv[ 0 ]); exit (-1); }

// /dev/mem это файл символьного устройства, являющийся образом физической памяти. fd = open (»/dev/mem», O_RDWR | O_SYNC); if (fd < 0 ) { perror( "open" ); exit( -1 ); }

uint8_t cop; off_t addr; uint32_t word_cnt; uint32_t cycle_cnt;

// Код операции cop = strtoul (argv[ 1 ], NULL, 0); // Начальный адрес addr = strtoul (argv[ 2 ], NULL, 0); // Количество слова для записи/чтения word_cnt = strtoul (argv[ 3 ], NULL, 0); // Количество циклов повторения cycle_cnt = strtoul (argv[ 4 ], NULL, 0);

// Выполняем отображение файла /dev/mem в адресное пространство нашего процесса. map_addr = mmap (0, word_cnt * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr); if (map_addr == MAP_FAILED) { perror («map»); exit (-1); }

uint32_t cycle; uint32_t word; uint32_t data;

// В зависимости от кода операции switch (cop) {

// Записываем в память «счётчик». case (COP_WRITE): for (cycle = 0; cycle < cycle_cnt; cycle++ ) { for( word = 0; word < word_cnt; word++ ) { *( ( uint32_t *) map_addr + word ) = word; } } break; // Читаем данные и выводим на экран. case( COP_READ ): for( cycle = 0; cycle < cycle_cnt; cycle++ ) { for( word = 0; word < word_cnt; word++ ) { data = *( ( uint32_t *) map_addr + word ); printf( "idx = 0x%x, data = 0x%08x\n", word, data ); } } break;

// Читаем данные и сравниваем с «гипотетически записанными». case (COP_CHECK): for (cycle = 0; cycle < cycle_cnt; cycle++ ) { for( word = 0; word < word_cnt; word++ ) { data = *( ( uint32_t *) map_addr + word ); if( data != word ) { printf( "Error! write = 0x%x, read = 0x%x\n", word, data ); exit( -1 ); } } } break;

default: printf («Error! Unknown COP\n»); exit (-1); } if (munmap (map_addr, word_cnt * 4)) { perror («munmap»); exit (-1); }

close (fd); return 0; } Компилируем: arm-linux-gnueabihf-gcc -o memblock.o memclock.c Соответственно, интересующие нас файлы:

mem.o memblock.o Создание SD-карты Настало время собрать всё воедино. На текущий момент у нас должны быть следующие файлы: soc.rbf uImage soc.dtb preloader-mkpimage.bin u-boot.img u-boot-env.img rootfs.tar.gz mem.o memblock.o Если какого-то из них нет — значит Вы что-то пропустили :)Создадим директорию и скопируем все указанные файлы в неё. Далее нам нужно найти и подключить MicroSD карту.В последующих командах предполагается, что карта определилась как устройство /dev/sdb. Мы создадим на ней два раздела:

/dev/sdb1 — для Preloader и U-Boot /dev/sdb2 — для файловой системы Если карта определилась под другим именем, внесите соответствующие изменения.На всякий случай затираем всё нулями.Внимание! Eще раз проверьте, что /dev/sdb — это карта, а не Ваш второй жёсткий диск.

sudo dd if=/dev/zero of=/dev/sdb bs=10M Для того, чтобы создать разделы, воспользуемся утилитой fdisk: sudo fdisk /dev/sdb Далее нужно ввести следующие команды (пустая строка — ввод Enter): Команды для fdisk o n p 1 2048 +1M n p 2

t 1 a2 t 2 83 w Можно проверить, что у нас получилось: sudo fdisk -l /dev/sdb Должно быть что-то похожее на: Вывод fdisk -l Disk /dev/sdb: 1966 MB, 1966080000 bytes 61 heads, 62 sectors/track, 1015 cylinders, total 3840000 sectors Units = sectors of 1×512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0×02be07e5

Device Boot Start End Blocks Id System /dev/sdb1 2048 4095 1024 a2 Unknown /dev/sdb2 4096 3839999 1917952 83 Linux Теперь скопируем на карту образ с переменными U-Boot: sudo dd if=u-boot-env.img of=/dev/sdb bs=1 seek=512 После этого копируем Preloader: sudo dd if=preloader-mkpimage.bin of=/dev/sdb1 И сам U-Boot: sudo dd if=u-boot.img of=/dev/sdb1 bs=64k seek=4 Создаём файловую систему ext3: sudo mkfs.ext3 /dev/sdb2 Монтируем её: sudo mount /dev/sdb2 /mnt/ И разворачиваем в неё нашу rootfs: sudo tar xvf rootfs.tar.gz -C /mnt/ Далее копируем образ ядра, dtb, прошивку FPGA и тестовые программы: sudo cp uImage /mnt/boot/ sudo cp soc.dtb /mnt/boot/ sudo cp soc.rbf /mnt/boot/ sudo cp mem.o /mnt/root/ sudo cp memblock.o /mnt/root/ Отмонтируем файловую систему: sudo umount /dev/sdb2 Всё, карта готова! Запуск платы и измерение пропускной способности Наконец-то всё готово для работы. Вставляем карту, подключаем USB и питание.Заходим по консоли: minicom -D /dev/ttyUSB0 -b 115200 -s Первым делом прошьём FPGA.Для это необходимо установить переключатель P18 на плате в положение «On On On On On» (выключатели с 1 по 5).Смотрим текущее состояние FPGA: cat /sys/class/fpga/fpga0/status Мы должны увидеть configuration phaseЗаливаем прошивку: dd if=/boot/soc.rbf of=/dev/fpga0 bs=4096 И смотри состояние еще раз: cat /sys/class/fpga/fpga0/status Состояние должно смениться на user mode. Это означает, что ПЛИС сконфигурирована и готова к работе.Теперь проверяем наши утилиты. Но перед этим ещё немного «работы напильником».У нашего кросс-компилятора и у Debian разные названия динамического линкера. Поэтому для того, чтобы утилиты работали, нам необходимо создать ссылку на правильный линкер:

ln -s /lib/ld-linux.so.3 /lib/ld-linux-armhf.so.3 Итак, запускаем утилиту (пояснение, что это за адрес, будет чуть ниже): ./mem.o 0xFFD0501C Если в результате Вы видите строку data = 0×00000007, значит всё в порядке.Как я уже писал выше, внутренняя память ПЛИС у нас будет отображена в адресное пространство начиная с адреса 0xC0000000. Но перед тем, как мы сможем работать с этой памятью, нам нужно сделать еще два действия.

Первое — так как по умолчанию все интерфейсы между CPU и FPGA находятся в ресете, то мы должны его снять. За это отвечает блок Reset Manager (rstmgr), с базовым адресом 0xFFD05000, и конкретно его регистр brgmodrst со смещением 0×1C. Итоговый адрес регистра — 0xFFD0501C. В нём задействованы только три младших бита:

0-й — сброс интерфейса HPS-to-FPGA 1-й — сброс интерфейса LWHPS-to-FPGA 2-й — сброс интерфейса FPGA-to-HPS Логика работы всех битов одинакова — если там записана единица, значит соответствующий интерфейс находится в ресете. В итоге, значение по умолчанию для этого регистра — это 0×7, что мы и видели, когда читали из него при помощи нашей утилиты. Нам требуется снять ресет с интерфейса HPS-to-FPGA, значит мы должны записать в регистр число 0×6: ./mem.o 0xFFD0501C 0×6 После этого вновь прочитаем регистр, чтобы убедиться, что данные записались корректно: ./mem.o 0xFFD0501C Второе — мы должны включить отображение интерфейса HPS-to-FPGA в адресное пространство CPU. За это отвечает блок L3 (NIC-301) GPV (l3regs) с базовым адресом 0xFF800000, и конкретно его регистр remap со смещением 0. За HPS-to-FPGA отвечает бит под номером 3. В итоге, нам нужно записать в регистр число 0×8: ./mem.o 0xFF800000 0×8 К сожалению, этот регистр доступен только для записи, поэтому прочитать для проверки данные у нас не получится.Теперь мы можем читать и писать в память FPGA. Проверим это. Читаем:

./mem.o 0xC0000000 Естественно, там должны быть нули. Теперь запишем туда что-нибудь: ./mem.o 0xC0000000 0×12345678 И снова прочитаем: ./mem.o 0xC0000000 Должно совпасть с записанным.Ура! Мы наконец-то сделали это! Мы получили работающую SoC с FPGA и организовали доступ к её памяти из CPU.Но просто читать/писать — это как-то совсем скучно. Давайте хотя бы измерим пропускную способность нашего интерфейса. Тем более это займет совсем немного времени.

Для этого нам потребуется наша вторая утилита memblock:

root@desktop:~# ./memblock.o Usage: ./memblock.o

Она работает следующим образом: если первый аргумент cop равен 0, то в word_count 32-битных слов, начиная с адреса address, будет записана последовательность чисел от 0 до word_count-1. Вся процедура будет произведена cycles раз (это сделано для более точного измерения пропускной способности).Если cop равен 1, то эти же слова будут считаны и выведены на экран.Если cop равен 2, то слова будут считаны, а их значения будут сравниваться с теми, что гипотетически были записаны.Проверим. Запишем немного данных:

./memblock.o 0 0xC0000000 10 1 Теперь считаем их: ./memblock.o 1 0xC0000000 10 1 Результат должен быть следующим: Вывод memblock.o data = 0×00000000 data = 0×00000001 data = 0×00000002 data = 0×00000003 data = 0×00000004 data = 0×00000005 data = 0×00000006 data = 0×00000007 data = 0×00000008 data = 0×00000009 Теперь попробуем сравнить данные, специально задав чуть большее количество слов: ./memblock.o 2 0xC0000000 11 1 Должны получить такую строку: Error! write = 0xa, read = 0×0 Теперь запускаем запись по всему объему памяти в количестве 1000-ти повторений и замеряем время записи: time ./memblock.o 0 0xC0000000 0×10000 1000 Среднее значение по 5 запускам равно 11.17 секунд. Считаем пропускную способность: 1000 раз * 65536 записей * 4 байта * 8 бит/в_байте / (11.17×10^6) = 187.75 Мбит/c Не очень густо. А что у нас с чтением: time ./memblock.o 2 0xC0000000 0×10000 1000 Среднее время 10.5 секунд. Что выливается в: 1000×65536 * 4×8 / (10.5×10^6) = 199.73 Мбит/c Примерно то же самое. Естественно, на время выполнения любой из этих операций одно из двух ядер загружается на 100%.Если при компиляции добавить флаг -O3, то пропускная способность на запись и на чтение станет 212 Мбит/c и 228 Мбит/c соответственно. Чуть лучше, но тоже не метеор.

Но это и не удивительно — мы же ничего не делали, чтобы эту самую пропускную способность увеличить. Неплохо было бы поиграться с более хитрой оптимизацией, посмотреть в сторону ядра, или, на худой конец, хотя бы прикрутить DMA, чтобы разгрузить процессор.Но это уже в следующей статье, если, конечно, кому-то это будет интересно.

Спасибо тем, кто добрался до конца! Удачи!

Полезные ссылки Официальная документация на Cyclone VRocketboards.org — много разных статей про платы с SoCИнформация конкретно по EBV SoCrates Evaluation Board

© Habrahabr.ru