Pull to refresh

Веселая Квартусель, или как процессор докатился до такой жизни

Reading time18 min
Views5.2K


При отладке обычных программ, точки останова можно ставить почти везде и в достаточно больших количествах. Увы. Когда программа исполняется на контроллере, это правило не действует. Если идёт участок, в котором формируется временная диаграмма, то остановка всё испортит. А проход на низкой и на высокой частоте — это не одно и то же. Граничные условия — бич разработчиков. Или, скажем, шина USB. В NIOS II я с нею не работал, но на STM32 — это ужас. И хочется что-то посмотреть, и при остановке словишь таймаут. В общем, очень часто, несмотря на наличие передовой JTAG отладки, поведение программы в критичных по времени участках покрыто мраком. Как было бы здорово хотя бы после исполнения поглядеть, по какой цепочке прошла программа, какие ветвления сработали, а какие нет!

Ещё важнее знать историю развития исключений. На NIOS II мне этого делать не доводилось, а вот на Cyclone V SoC в ядре ARM — вполне. При отладке всё работает, а стартуешь программу из ПЗУ — получаешь исключение. Как в него вошли — видно, а какое развитие ситуации привело к этому — нет. Трассировка же срывает все покровы.

Предыдущие статьи цикла:
  1. Разработка простейшей «прошивки» для ПЛИС, установленной в Redd, и отладка на примере теста памяти
  2. Разработка простейшей «прошивки» для ПЛИС, установленной в Redd. Часть 2. Программный код
  3. Разработка собственного ядра для встраивания в процессорную систему на базе ПЛИС
  4. Разработка программ для центрального процессора Redd на примере доступа к ПЛИС
  5. Первые опыты использования потокового протокола на примере связи ЦП и процессора в ПЛИС комплекса Redd

Введение


Обучающие статьи принято писать с серьёзным лицом, излагая материал спокойно и беспристрастно. Но увы, не всегда это получается. Сейчас по плану должна быть статья на тему оптимизации работы синтезированного процессора в комплексе Redd, в которой надо рассказать про особенности работы с кэшем. Дальше — статья про задержки при обращении к шине. В целом, всё это можно показать на осциллографе. Я уже делал подобное в статье про DMA («DMA: мифы и реальность»). Но хотелось продемонстрировать всё средствами самого процессора, я такие проверки проводил для ARM ядра в ПЛИС Cyclone V SoC. Очень удобно (правда, результаты не публиковались). По этому поводу я решил разобраться с механизмом трассировки, заложенном в блоке JTAG. Жаль, что бесплатно скачиваемыми средствами мне пока так и не удалось отображать, сколько тактов исполнялась та или иная команда, но зато появился материал, при помощи которого программу всегда можно расспросить, как она докатилась до такой жизни. Ну, и кроме того, пока в памяти свежи воспоминания о бурных вечерах, я выплесну их в достаточно эмоциональной манере. Так что сегодня будет много мемуаров и чуть-чуть полезной теории.

Аппаратная часть


Итак, очень часто при отладке программы для микроконтроллера желательно знать пройденный ею путь, причём пройти она его должна на полной скорости. Для случая NIOS II этот механизм имеется и включается вот на этой вкладке свойств процессорного ядра:



Trace Type задаёт тип сохраняемой информации. Можно не сохранять ничего (это сэкономит память кристалла), можно сохранять только историю команд (чаще всего этого достаточно), ну или сохранять историю как команд, так и данных. Стоит отметить, что в документации рассказывается, что в последнем режиме сохранение сведений производится как-то сложно, при этом возможны накладки с историей команд, а память расходуется больше. Так что пользуйтесь этим режимом только при реальной необходимости.

Параметр Trace Storage задаёт тип памяти, в которую будет сохраняться трасса. В документации сказано, что внешняя память не поддерживается штатной средой разработки. Поэтому лучше выбирать только внутреннюю.



Ну, и Onchip Trace Frame Size задаёт объём буфера. Чем буфер больше, тем больше событий удастся в него разместить, тем большую историю можно будет рассмотреть. Но тем меньше останется памяти кристалла под прочие нужды.



Галочку, создающую отдельный порт JTAG, включать не надо. Пусть всё заруливается на штатный.

Программная поддержка


Хорошо. Вот мы произвели настройку процессорного ядра, что дальше? Оооо! Дальше начинается детективная история. Если рисунки выше следует воспринимать, как руководство к действию, то дальше будут идти иллюстрации, показывающие бардак и головотяпство. В ряде документов сказано, что для работы с трассировкой следует пользоваться сторонними дорогими программами и даже специальной JTAG аппаратурой. Но в некоторых документах проскакивает, что можно взять и что-то штатное.

Эксперименты с Eclipse


Прекрасно. Осмотримся в Eclipse. Напоминаю, что я пользуюсь средой разработки Quartus Prime 17.1. Ну, так получилось. Запускаем программу на отладку и идём в пункт меню, позволяющий открыть различные окна:



Там выбираем окно Debug->Trace Control:



И получаем вот такую пустышку:



А ведь отладочная сессия-то запущена. Мало того, в ней даже появились пункты меню Start Tracing и Stop Tracing (до открытия данного окна их не было). Но они заблокированы. И активировать их я не смог.



Напрасно я заваливал Гугля разными запросами. В старых документах эти пункты описываются, как вполне себе работающие, а в современных просто не упоминаются. Не упоминает их никто и на форумах. Может, тут в комментариях кто-нибудь что-нибудь подскажет?

Проблемы с последней версией Eclipse


Хорошо. А что с самой свежей версией среды разработки? Может там всё работает? Ыыыыыы! Это тема для отдельной главы. Самая свежая версия для четвёртых и пятых Циклонов — 18.1.1. То есть, надо сначала скачать версию 18.1, а затем установить обновление 18.1.1. Если кто-то решил, что у меня слишком много свободного времени, что я ради каждой мелочи ПО переставляю, — всё нормально. Основная проверка была на тему проблем с кэшем. Они более серьёзны, хотелось проверить их в новой версии, просто здесь я про них не пишу.

Итак, скачал. Установил. Во-первых, версия 18.1 под WIN7 запускается, а вот 18.1.1 не находит DLL. Хорошо. Выяснил, что надо скачать Redist от Visual Studio 2015. Поставил. Стало запускаться. Создал проект с процессорной системой. Он даже собрался. Иду в Eclipse, создаю на его основе программу с BSP, всё, как мы уже многократно делали… И получаю вот такое дело…



Проект не собирается. Если выйти и войти — открывается только частично.



Видите папку закрытую? Вот так. Почему? Ооооо! У трёх файлов:



вот такие чудесные атрибуты защиты:



Так что их ни открыть, ни изменить… Если дать больше прав, проект откроется, но файлы созданы не полностью, так что собрать его не получится. Пробовал давать права, пока не закрыта Eclipse, а затем сохранить проект. Итог тот же.

Может виновата стремительно устаревающая ОС Windows 7? Не зря же мне не хватало библиотек! Ставлю свежий Квартус на WIN10, получаю полностью идентичный результат создания проекта! Есть у меня знакомый. В силу места его работы он иногда сталкивается с продукцией отечественных производителей. И высказывает ряд мыслей о них самих и об их родственниках. Ну, и о том, что «кто так строит?». Вы знаете, глядя на весёлости в ПО фирмы Intel, я начинаю задумываться, что дело далеко не в стране происхождения… Вообще, при Альтере такого не было… Всякое бывало, но такого не помню.

Выкрутился просто. Скопировал проект на USB флэшку с файловой системой FAT32. Там нет атрибутов безопасности. Открыл проект с неё, вошёл в Eclipse и создал код. Нет атрибутов безопасности — нет проблем. Затем скопировал его обратно на жёсткий диск и… Ну разумеется, получил проблемы при генерации BSP. Потому что файл *.bsp содержит много относительных путей и один абсолютный. Это хорошо, что я флэшку вытащил. Иначе бы и не заметил, что BSP скидывается на неё (так как это место рождения проекта), а проект собирается на жёстком диске. Вот пример такого пути (уже поправленный):



Ну отлично… Всё сгенерилось, собралось… И работает точно так же, как и в версии 17.1… А для SoC контроллеров в новой среде ещё и приходится каждый раз JTAG цепочку пересоздавать в программаторе. В версии 17.1 достаточно было сделать это один раз и сохранить… Эээээх. Ну, да ладно…

Altera Monitor и его проблема


Так или иначе, поиски в сети по слову Start Tracing и Stop Tracing привели меня к интересным документам. Они описывают различные версии забавной программы, которая сегодня называется Altera Monitor (в старых документах имя было другое). Найти её было относительно просто. Надо скачать пакет University Program для своей версии среды разработки. Обратите внимание на лицензионные ограничения. Но так как мы сейчас учимся, нам это не страшно. А вот для коммерческих работ — там всё плохо. Подробности тут: www.intel.com/content/www/us/en/programmable/support/training/university/materials-software.html

Скачиваем, ставим… Там есть документ, привязанный к текущей версии (потому что версии, рассыпанные по Интернету сильно различаются). Я даже попробовал запустить пример для имеющейся у меня макетки DE0-Nano-SoC. Он работает. Но когда я попробовал сделать свой проект, он не заработал. Файл *.sof загружается в ПЛИС, после чего, через некоторое время, выдаётся сообщение:



Только никакой информации в указанном окне нет. Если попытаться загрузить ещё раз: ну, появится как раз в том окне текст:
Could not query JTAG Instance IDs.

Please ensure the FPGA has been configured using the correct .sof file.


Что за беда такая? Поискал по Гуглю. Нашёл несколько форумов с такой же проблемой. На одном сотрудник Intel спрашивал, какая у автора частота у JTAG и предлагал задать стандартную. Хотя, к тому времени я уже понимал, что дело не в частоте: фирменный же пример работает, да и как её задать? На одном форуме автор написал, что как-то само прошло. Он не понял, как. И сказал, что если делать всё внимательно по инструкции, всё заработает. На остальных картина была однотипная. Человек спрашивает. Ему не отвечают. Через полгода-год кто-нибудь пишет, что у него та же ситуация, не появилось ли решения? И тишина-а-а-а-а-а…

Попытки решения опытным путём


Хорошо. Что может быть причиной? Сначала я решил осмотреться в работающем примере. Что за таинственные JTAG ID? Может виной тому наличие в работающей системе System ID?



Добавил к себе, не помогло. Может виной всему мост JTAG to Avalon, к которому дополнительно подключены все периферийные устройства?



Добавил — не помогло. Я пробовал ещё несколько гипотез, но понял, что гадать можно вечно. С горя я даже запросил у Яндекса, у Bing и даже у BaiDu. Все они знают меньше, чем Гугль. Стало ясно, что придётся заниматься декомпиляцией, чтобы выпытать у самой программы, чего же ей надо. Проверил, на каком языке написана программа. Оказалось, что на Яве. Байт-код хранится в файле Altera_Monitor_Program.jar. Ну и чудненько. Если не считать того, что я эту Яву вообще не знаю. На ЯваСкрипте, было дело, баловался с Интернетом Вещей, а вот с настоящей Явой не сталкивался. Но где наша не пропадала!

Анализ JAVA байт-кода для поиска проблемы


Как вскрывать байт-код? Гугль привёл на статью на Хабре, где сказано, что для этого надо использовать JD-GUI. Нашёл его на github, скачал. Проблемный участок я выявил достаточно быстро, так как у JD-GUI замечательная интерактивная навигация. От сообщения до участка я вышел за 10 минут. Этот участок вызывает стороннюю программу, после чего анализирует её ответ. Вызов выглядит так:
         systemConsoleCommand[index] = "system-console";
         systemConsoleCommand[var24++] = "--script=" + Globals.gHost.getMonitorProgramRootDir("bin/jtag_instance_check.tcl", true);
         systemConsoleCommand[var24++] = cable;
         systemConsoleCommand[var24++] = Globals.gProject.system.sofFilename;
         try {
            Process sysConsoleProc = NiosIIShell.executeCommand(systemConsoleCommand).start();
            BufferedReader gdbIn = new BufferedReader(new InputStreamReader(sysConsoleProc.getInputStream()));

Ну, и дальше — разбор ответа, который пока не рассматриваем.

Имея такой код, я попробовал открыть консоль NIOS II:



Там перешёл в каталог, где лежит sof файл и вбил командную строку:
system-console --script=jtag_instance_check.tcl USB-0 test.sof

Правда, для этого пришлось скопировать файл C:\intelFPGALite\17.1\University_Program\Monitor_Program\bin\jtag_instance_check.tcl туда же, где лежит sof, чтобы не мучиться с путём. В итоге я получил вполне приличный отклик:

TYPE_NAME altera_avalon_jtag_uart.jtag FULL_HPATH jtag_uart_0 (INSTANCE_ID:0)

TYPE_NAME altera_nios2_gen2.data_master FULL_HPATH nios2_gen2_0 (INSTANCE_ID:0)


Вроде, всё красиво…

Трассировка JAVA байт-кода


Если бы дело происходило прямо сегодня, этого раздела бы не было. Но оно происходило вчера. Я ещё очень плохо понимал Яву. Что там написано при разборе текста, для меня было тёмным лесом. Правда, ребёнок мой два года посещал курсы олимпиадного программирования у 1Совских франчайзи (сертификаты и раздаточный материал были 1Совские). За эти курсы были отданы бешеные деньги. И учили их там как раз на Яве. Так вот, он тоже не понял, что там дальше в коде написано (держа интригу, я опубликую код чуть ниже, не сейчас). В общем, возникло стойкое ощущение, что пришла пора провести трассировку. Я знаю сбойный участок, я вижу, что там получены строки и они чем-то программе не нравятся. Так чем?

Ребёнок нашёл мне очень замечательную статью www.crowdstrike.com/blog/native-java-bytecode-debugging-without-source-code

Там рассказывается про очень полезный плагин для Eclipse, который позволяет работать с JARами, ставя в них точки останова. Где скачать, я нашёл вот тут: marketplace.eclipse.org/content/bytecode-visualizer/help

Скачал Eclipse, скачал с горем пополам плагин для оффлайн-установки… Начал ставить — не хватает библиотек. Стал читать. Оказывается, есть три версии плагина. Под Eсlipse 4.5 (Mars), 4.4 (Luna) и 4.3 (не помню имя). Ну всё просто. Идём на сайт Eclipse, видим ссылку на скачивание версии Mars для Java… И… Она мёртвая. Не беда! Там около десятка зеркал!.. И все ссылки на них мёртвые. Пробуем Luna для Java, там ссылки на x64 мёртвые, на x86 одна живая нашлась… Как говорит один мой знакомый: «Геморрой, он всеобъемлющий». В общем, Гугль с трудом, но нашёл мне 64-битную Java-сборку версии Mars на каком-то неофициальном сервере. Полчаса качал, но скачал.

Внедрил плагин, создал проект… Ужас! Там трассировка идёт не на уровне исходного кода, а на уровне а-ля ассемблер. Короче, декодированный байтовый код трассируется. Но на самом деле, это не беда! Ведь всегда можно сверяться с декомпилированными исходниками, открытыми в другом окне, плюс очень хорошие комментарии тот плагин показывает… Также выяснилось, что точки останова ставить можно не в любое место, а только на вход в функцию. Но меня уже не остановить! Там не так много и прошагать-то нужно от входа до проблемного участка.

Напомню, что обрабатываемые строки выглядят так:
TYPE_NAME altera_avalon_jtag_uart.jtag FULL_HPATH jtag_uart_0 (INSTANCE_ID:0)

TYPE_NAME altera_nios2_gen2.data_master FULL_HPATH nios2_gen2_0 (INSTANCE_ID:0)


И вот такой Java-код:
if (str.contains("(INSTANCE_ID:")) {
    Pattern getInstance = Pattern.compile("\\(INSTANCE_ID:(\\d+)\\)");
    Matcher idMatcher = getInstance.matcher(str);
    if (idMatcher.find()) {
       String foundstr = idMatcher.group(1);
       instance = Integer.parseInt(foundstr);
    }

прекрасно вычленяет Instance ID. А вот такой код:
            Pattern getHPath = Pattern.compile("FULL_HPATH (.+?)\\|(.+?) \\(");
            Matcher hpathMatcher = getHPath.matcher(str);
            if (hpathMatcher.find()) {
              hpath = hpathMatcher.group(2).replace("|", ".");
            }

переменную hpath не заполняет. Сегодня-то я уже знаю, что регулярное выражение:
"FULL_HPATH (.+?)\\|(.+?) \\("

требует два слова, разделённых вертикальной чертой. Ну, а дальше берётся только то, что находится после черты. А вчера ещё не знал. Интереснее другое. Ребёнок два года изучал работу на Яве и не изучил регулярных выражений! Нет, понятно, что их учили не языку, а олимпиадному программированию средствами языка, но как я понял, регулярные выражения на Яве — в порядке вещей. Такие деньги берут, сертификатами от солидных фирм трясут, а важным вещам не учат… Но я отвлёкся.

Свет в конце тоннеля


Что за вертикальная черта? Берём тот проект, который работал, подаём ему ту же команду и получаем вот такой ответ:

TYPE_NAME altera_avalon_jtag_uart.jtag FULL_HPATH Computer_System:The_System|JTAG_UART (INSTANCE_ID:0)

TYPE_NAME altera_avalon_jtag_uart.jtag FULL_HPATH Computer_System:The_System|JTAG_UART_2nd_Core (INSTANCE_ID:1)

TYPE_NAME altera_avalon_jtag_uart.jtag FULL_HPATH Computer_System:The_System|JTAG_UART_for_ARM_0 (INSTANCE_ID:2)

TYPE_NAME altera_avalon_jtag_uart.jtag FULL_HPATH Computer_System:The_System|JTAG_UART_for_ARM_1 (INSTANCE_ID:3)

TYPE_NAME altera_nios2_gen2.data_master FULL_HPATH Computer_System:The_System|Nios2 (INSTANCE_ID:0)

TYPE_NAME altera_nios2_gen2.data_master FULL_HPATH Computer_System:The_System|Nios2_2nd_Core (INSTANCE_ID:1)


Что за Computer_System:The_System? Тут всё просто. Я в этом цикле статей продвигаю передовую идею, где компьютерная система находится на верхнем уровне иерархии.
А этот пример имеет вот такую Verilog-прослойку:
module DE0_Nano_SoC_Computer (
	////////////////////////////////////
	// FPGA Pins
	////////////////////////////////////

	// Clock pins
	input              CLOCK_50,
	input              CLOCK2_50,
	input              CLOCK3_50,
	
	// ADC
	output             ADC_CONVST,
	output             ADC_SCLK,
	output             ADC_SDI,
	input              ADC_SDO,
	
	// ARDUINO
	inout       [15:0] ARDUINO_IO,
	inout              ARDUINO_RESET_N,
	
	// GPIO
	inout       [35:0] GPIO_0,
	inout       [35:0] GPIO_1,
	
	// KEY
	input       [1:0]  KEY,
	
	// LED
	output      [7:0]  LED,
	
	// SW
	input       [3:0]  SW,
	
	////////////////////////////////////
	// HPS Pins
	////////////////////////////////////
	
	// DDR3 SDRAM
	output      [14:0] HPS_DDR3_ADDR,
	output      [2:0]  HPS_DDR3_BA,
	output             HPS_DDR3_CAS_N,
	output             HPS_DDR3_CKE,
	output             HPS_DDR3_CK_N,
	output             HPS_DDR3_CK_P,
	output             HPS_DDR3_CS_N,
	output      [3:0]  HPS_DDR3_DM,
	inout       [31:0] HPS_DDR3_DQ,
	inout       [3:0]  HPS_DDR3_DQS_N,
	inout       [3:0]  HPS_DDR3_DQS_P,
	output             HPS_DDR3_ODT,
	output             HPS_DDR3_RAS_N,
	output             HPS_DDR3_RESET_N,
	input              HPS_DDR3_RZQ,
	output             HPS_DDR3_WE_N,

	// Ethernet
	output             HPS_ENET_GTX_CLK,
	inout              HPS_ENET_INT_N,
	output             HPS_ENET_MDC,
	inout              HPS_ENET_MDIO,
	input              HPS_ENET_RX_CLK,
	input       [3:0]  HPS_ENET_RX_DATA,
	input              HPS_ENET_RX_DV,
	output      [3:0]  HPS_ENET_TX_DATA,
	output             HPS_ENET_TX_EN,

	// Accelerometer
	inout              HPS_GSENSOR_INT,

	// I2C
	inout              HPS_I2C0_SCLK,
	inout              HPS_I2C0_SDAT,
	inout              HPS_I2C1_SCLK,
	inout              HPS_I2C1_SDAT,
	
	// Pushbutton
	inout              HPS_KEY,

	// LED
	inout              HPS_LED,

	// LTC
	inout              HPS_LTC_GPIO,

	// SD Card
	output             HPS_SD_CLK,
	inout              HPS_SD_CMD,
	inout       [3:0]  HPS_SD_DATA,
	
	// SPI
	output             HPS_SPIM_CLK,
	input              HPS_SPIM_MISO,
	output             HPS_SPIM_MOSI,
	inout              HPS_SPIM_SS,
	
	// UART
	input              HPS_UART_RX,
	output             HPS_UART_TX,
	
	// USB
	inout              HPS_CONV_USB_N,
	input              HPS_USB_CLKOUT,
	inout       [7:0]  HPS_USB_DATA,
	input              HPS_USB_DIR,
	input              HPS_USB_NXT,
	output             HPS_USB_STP
);


//=======================================================
//  REG/WIRE declarations
//=======================================================
wire  hps_fpga_reset_n;




//=======================================================
//  Structural coding
//=======================================================


Computer_System The_System (
	////////////////////////////////////
	// FPGA Side
	////////////////////////////////////

	// Global signals
	.system_pll_ref_clk_clk				(CLOCK_50),
	.system_pll_ref_reset_reset			(1'b0),

	// ADC
	.adc_sclk							(ADC_SCLK),
	.adc_cs_n							(ADC_CONVST),
	.adc_dout							(ADC_SDO),
	.adc_din							(ADC_SDI),

	// Arduino GPIO
	.arduino_gpio_export				(ARDUINO_IO),

	// Arduino Reset_n
	.arduino_reset_n_export				(ARDUINO_RESET_N),

	// Slider Switches
	.slider_switches_export				(SW),

	// Pushbuttons
	.pushbuttons_export					(~KEY),

	// Expansion JP1
	.expansion_jp1_export				({GPIO_0[35:19], GPIO_0[17], GPIO_0[15:3], GPIO_0[1]}),

	// Expansion JP7
	.expansion_jp7_export				({GPIO_1[35:19], GPIO_1[17], GPIO_1[15:3], GPIO_1[1]}),

	// LEDs
	.leds_export						(LED),
	
	////////////////////////////////////
	// HPS Side
	////////////////////////////////////
	// DDR3 SDRAM
	.memory_mem_a			(HPS_DDR3_ADDR),
	.memory_mem_ba			(HPS_DDR3_BA),
	.memory_mem_ck			(HPS_DDR3_CK_P),
	.memory_mem_ck_n		(HPS_DDR3_CK_N),
	.memory_mem_cke			(HPS_DDR3_CKE),
	.memory_mem_cs_n		(HPS_DDR3_CS_N),
	.memory_mem_ras_n		(HPS_DDR3_RAS_N),
	.memory_mem_cas_n		(HPS_DDR3_CAS_N),
	.memory_mem_we_n		(HPS_DDR3_WE_N),
	.memory_mem_reset_n		(HPS_DDR3_RESET_N),
	.memory_mem_dq			(HPS_DDR3_DQ),
	.memory_mem_dqs			(HPS_DDR3_DQS_P),
	.memory_mem_dqs_n		(HPS_DDR3_DQS_N),
	.memory_mem_odt			(HPS_DDR3_ODT),
	.memory_mem_dm			(HPS_DDR3_DM),
	.memory_oct_rzqin		(HPS_DDR3_RZQ),
		  
	// Accelerometer
	.hps_io_hps_io_gpio_inst_GPIO61		(HPS_GSENSOR_INT),

	// Ethernet
	.hps_io_hps_io_gpio_inst_GPIO35		(HPS_ENET_INT_N),
	.hps_io_hps_io_emac1_inst_TX_CLK	(HPS_ENET_GTX_CLK),
	.hps_io_hps_io_emac1_inst_TXD0		(HPS_ENET_TX_DATA[0]),
	.hps_io_hps_io_emac1_inst_TXD1		(HPS_ENET_TX_DATA[1]),
	.hps_io_hps_io_emac1_inst_TXD2		(HPS_ENET_TX_DATA[2]),
	.hps_io_hps_io_emac1_inst_TXD3		(HPS_ENET_TX_DATA[3]),
	.hps_io_hps_io_emac1_inst_RXD0		(HPS_ENET_RX_DATA[0]),
	.hps_io_hps_io_emac1_inst_MDIO		(HPS_ENET_MDIO),
	.hps_io_hps_io_emac1_inst_MDC		(HPS_ENET_MDC),
	.hps_io_hps_io_emac1_inst_RX_CTL	(HPS_ENET_RX_DV),
	.hps_io_hps_io_emac1_inst_TX_CTL	(HPS_ENET_TX_EN),
	.hps_io_hps_io_emac1_inst_RX_CLK	(HPS_ENET_RX_CLK),
	.hps_io_hps_io_emac1_inst_RXD1		(HPS_ENET_RX_DATA[1]),
	.hps_io_hps_io_emac1_inst_RXD2		(HPS_ENET_RX_DATA[2]),
	.hps_io_hps_io_emac1_inst_RXD3		(HPS_ENET_RX_DATA[3]),

	// I2C
	.hps_io_hps_io_i2c0_inst_SDA		(HPS_I2C0_SDAT),
	.hps_io_hps_io_i2c0_inst_SCL		(HPS_I2C0_SCLK),
	.hps_io_hps_io_i2c1_inst_SDA		(HPS_I2C1_SDAT),
	.hps_io_hps_io_i2c1_inst_SCL		(HPS_I2C1_SCLK),

	// Pushbutton
	.hps_io_hps_io_gpio_inst_GPIO54		(HPS_KEY),

	// LED
	.hps_io_hps_io_gpio_inst_GPIO53		(HPS_LED),

	// LTC
	.hps_io_hps_io_gpio_inst_GPIO40		(HPS_LTC_GPIO),

	// SD Card
	.hps_io_hps_io_sdio_inst_CMD		(HPS_SD_CMD),
	.hps_io_hps_io_sdio_inst_D0			(HPS_SD_DATA[0]),
	.hps_io_hps_io_sdio_inst_D1			(HPS_SD_DATA[1]),
	.hps_io_hps_io_sdio_inst_CLK		(HPS_SD_CLK),
	.hps_io_hps_io_sdio_inst_D2			(HPS_SD_DATA[2]),
	.hps_io_hps_io_sdio_inst_D3			(HPS_SD_DATA[3]),

	// SPI
	.hps_io_hps_io_spim1_inst_CLK		(HPS_SPIM_CLK),
	.hps_io_hps_io_spim1_inst_MOSI		(HPS_SPIM_MOSI),
	.hps_io_hps_io_spim1_inst_MISO		(HPS_SPIM_MISO),
	.hps_io_hps_io_spim1_inst_SS0		(HPS_SPIM_SS),

	// UART
	.hps_io_hps_io_uart0_inst_RX		(HPS_UART_RX),
	.hps_io_hps_io_uart0_inst_TX		(HPS_UART_TX),

	// USB
	.hps_io_hps_io_gpio_inst_GPIO09		(HPS_CONV_USB_N),
	.hps_io_hps_io_usb1_inst_D0			(HPS_USB_DATA[0]),
	.hps_io_hps_io_usb1_inst_D1			(HPS_USB_DATA[1]),
	.hps_io_hps_io_usb1_inst_D2			(HPS_USB_DATA[2]),
	.hps_io_hps_io_usb1_inst_D3			(HPS_USB_DATA[3]),
	.hps_io_hps_io_usb1_inst_D4			(HPS_USB_DATA[4]),
	.hps_io_hps_io_usb1_inst_D5			(HPS_USB_DATA[5]),
	.hps_io_hps_io_usb1_inst_D6			(HPS_USB_DATA[6]),
	.hps_io_hps_io_usb1_inst_D7			(HPS_USB_DATA[7]),
	.hps_io_hps_io_usb1_inst_CLK		(HPS_USB_CLKOUT),
	.hps_io_hps_io_usb1_inst_STP		(HPS_USB_STP),
	.hps_io_hps_io_usb1_inst_DIR		(HPS_USB_DIR),
	.hps_io_hps_io_usb1_inst_NXT		(HPS_USB_NXT)
);

endmodule


Я специально привёл её полностью, чтобы подчеркнуть, сколько совершенно ненужного кода приходится писать в этом случае. А если будут добавлены или убраны ножки, этот код придётся ещё и править. Собственно, строки


То же самое текстом:
...
Computer_System The_System (
	////////////////////////////////////
	// FPGA Side
	////////////////////////////////////

	// Global signals
	.system_pll_ref_clk_clk				(CLOCK_50),
...


и дают этот префикс. Авторы считают, что иерархия должна быть такой, только такой и никакой другой. Процессорную систему нельзя убрать глубже и нельзя вынести наверх.

Мы не должны ждать милости от природы, взять её — наша задача!


Неужели мы проделали такую работу, чисто чтобы смириться с этим делом? Как любит говорить один мой знакомый: «ненужная работа — хуже пьянства», а создание такой прослойки — типичный случай ненужной работы. Поэтому попытаемся обойти это ограничение. Помните, при вызове сторонней программы JAVA код подставлял какой-то tcl-скрипт, я его ещё копировал в каталог рядом с файлом sof? В нём наше спасение! Именно он говорит системной консоли, какие действия следует предпринять и именно он форматирует ответ. форматирование идёт вот так:

То же самое текстом:
# PRINT OUT INSTANCE ID INFO FOR EVERYTHING:
set i 0
foreach path [lsort -command compare_node_number [get_service_paths bytestream]] {
    # If this path corresponds to a JTAG UART, incr i
    if {[string match *$cable_name* $path ] && [string match *jtag_uart* [marker_get_type $path] ]} {
        puts "[marker_get_info $path] (INSTANCE_ID:$i)"
        incr i
    }
}
set i 0
foreach path [lsort -command compare_node_number [get_service_paths processor]] {
     # If this path corresponds to a NiosII, incr i
    if {[string match *$cable_name* $path ] && [string match *nios2* [marker_get_type $path] ]} {
        puts "[marker_get_info $path] (INSTANCE_ID:$i)"
        incr i
    }
}


Первый блок форматирует сведения о блоках JTAG_UART, второй — о процессорных ядрах. Вот если бы здесь добавить в выходной поток вертикальные чёрточки! Мой коллега поправил данный участок так:
# PRINT OUT INSTANCE ID INFO FOR EVERYTHING:
set i 0
foreach path [lsort -command compare_node_number [get_service_paths bytestream]] {
    # If this path corresponds to a JTAG UART, incr i
    if {[string match *$cable_name* $path ] && [string match *jtag_uart* [marker_get_type $path] ]} {

	set info [marker_get_info $path]
	if {[string first "|" $info] == -1} {
	    set info [string map {"FULL_HPATH " "FULL_HPATH a:b|"} $info]
	}
	puts "$info (INSTANCE_ID:$i)"

        incr i
    }
}
set i 0
foreach path [lsort -command compare_node_number [get_service_paths processor]] {
     # If this path corresponds to a NiosII, incr i
    if {[string match *$cable_name* $path ] && [string match *nios2* [marker_get_type $path] ]} {
	set info [marker_get_info $path]
	if {[string first "|" $info] == -1} {
	    set info [string map {"FULL_HPATH " "FULL_HPATH a:b|"} $info]
	}
	puts "$info (INSTANCE_ID:$i)"
        incr i
    }
}

Теперь, если чёрточек нет, они будут добавлены. И наконец-то программа заработает не только с ужасной, но и с оптимально написанной процессорной системой!

Настройка проекта в Altera Monitor


Уфффф. Всё. Конец раздолбайству, бездорожью и разгильдяйству (хотя, насчёт бездорожья — это не точно). Теперь снова рисунки в статье отражают инструкцию для работы! У нас есть проект для ПЛИС, а также программа, которая собирается и запускается в Eclipse. Теперь запускаем Altera Monitor и создаём проект.



Создаём каталог с проектом (я кладу его отдельно от проекта для ПЛИС) и даём имя проекту. Также выбираем архитектуру процессора



Систему я выбираю Custom System. При этом надо указать мои файлы *.sof и *.sopcinfo. Я выбираю их в рабочих каталогах. Прелоадер нашей системе не нужен.



Тип программы выбираем Program with Device Driver Support, тогда будет построена библиотека BSP:



Рабочий файл пока что один (он был создан в Eclipse). Вот его и добавляю:



В последнем окне я ничего не меняю:



Соглашаемся с загрузкой sof файла:



Если мы только что поставили ПО, переключились в режим работы с исходными текстами. Потом он уже будет включён (я покажу, как выглядит меню у меня, когда всё уже включено, там же будет и пункт для включения).



Программа на Си у меня простейшая:
#include "sys/alt_stdio.h"

int main()
{ 
  alt_putstr("Hello from Nios II!\n");
  
  volatile int i=0;
  
  i += 1;
  i += 2;
  i += 3;
  i += 4;
  i += 5;
  i += 6;
  i += 7;
  i += 8;
  i += 9;
  i += 10;
  i += 11;
  i += 12;

  /* Event loop never exits. */
  while (1);

  return 0;
}

Пытаюсь её собрать:



Получаю ошибку:
c:/intelfpga/17.1/nios2eds/bin/gnu/h-x86_64-mingw32/bin/../lib/gcc/nios2-elf/5.3.0/../../../../../H-x86_64-mingw32/nios2-elf/bin/ld.exe: region `Code' overflowed by 15888 bytes

Это потому, что я не добавлял SDRAM в систему, ограничился встроенной памятью ПЛИС. Но почему в Eclipse всё поместилось, а тут нет? Потому что BSP там я выбрал с суффиксом Small, а здесь мне автоматически сделали обычный пакет. Поэтому открываем файл: C:\Work\Play2\BSP\settings.bsp

И начинаем ручную донастройку.





Пересобираем BSP:



И снова собираем проект. На этот раз успешно. Теперь загружаем его:



Наконец, реальная трассировка


Вы ещё не забыли, зачем я всё это делаю? Я это делаю для трассировки. Её надо активировать. Для этого переходим на вкладку Trace и в контекстном меню выбираем Enable Trace:



Я поставлю точку останова в конце функции main() на вкладке Disassembly (как приятно выглядит RISC ассемблер после ужасного стекового ассемблера, в который превращается код на Яве!)

Вот начало функции main:



Промотаю чуть вниз и поставлю точку останова сюда:



Запускаем, ждём останова и идём на вкладку Trace.

Вообще, всё не очень хорошо. Сначала явно какое-то ожидание (у нас там был вывод в JTAG, так что вполне законная вещь):



В конце — какой-то ещё код… Но я не вижу кода функции main! Вот конец:



Я даже не знаю, что сказать. Но так или иначе, если поставить не одну, а две точки останова (в начало и конец функции main), то после прогона от первой до второй, картинка будет приличной:



Краткие выводы


Итак, мы выяснили, что допытываться, как работал тот или иной участок вполне можно. А значит, тема статьи («…как процессор докатился до такой жизни») раскрыта. Интереснее, как до такой жизни докатились все те, чьи проблемы пришлось решать, для того чтобы получить результат? И очень жаль, что пока мне не удалось определять, за сколько тактов выполнилась та или иная команда. Из обрывков документации видно, что это, кажется, технически возможно, но какое ПО позволит это сделать, не ясно. Есть мнение, что нам поможет документ Analyzing and Debugging Designs with the System Console, в который пришлось вчитываться при анализе tcl-скрипта. В нём есть интересная табличка Table 10-15: Trace System Commands, но на детальную проработку этого дела лично у меня просто нет времени. Но возможно, кому-то это будет настолько важно, что он реализует всё. Указанные команды включаются в tcl-скрипты.

Ну, а в следующих статьях замеры придётся делать по старинке, осциллографом.
Tags:
Hubs:
+17
Comments4

Articles