При отладке обычных программ, точки останова можно ставить почти везде и в достаточно больших количествах. Увы. Когда программа исполняется на контроллере, это правило не действует. Если идёт участок, в котором формируется временная диаграмма, то остановка всё испортит. А проход на низкой и на высокой частоте — это не одно и то же. Граничные условия — бич разработчиков. Или, скажем, шина USB. В NIOS II я с нею не работал, но на STM32 — это ужас. И хочется что-то посмотреть, и при остановке словишь таймаут. В общем, очень часто, несмотря на наличие передовой JTAG отладки, поведение программы в критичных по времени участках покрыто мраком. Как было бы здорово хотя бы после исполнения поглядеть, по какой цепочке прошла программа, какие ветвления сработали, а какие нет!
Ещё важнее знать историю развития исключений. На NIOS II мне этого делать не доводилось, а вот на Cyclone V SoC в ядре ARM — вполне. При отладке всё работает, а стартуешь программу из ПЗУ — получаешь исключение. Как в него вошли — видно, а какое развитие ситуации привело к этому — нет. Трассировка же срывает все покровы.
Введение
Обучающие статьи принято писать с серьёзным лицом, излагая материал спокойно и беспристрастно. Но увы, не всегда это получается. Сейчас по плану должна быть статья на тему оптимизации работы синтезированного процессора в комплексе 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:intelFPGALite17.1University_ProgramMonitor_Programbinjtag_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? Тут всё просто. Я в этом цикле статей продвигаю передовую идею, где компьютерная система находится на верхнем уровне иерархии.
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:WorkPlay2BSPsettings.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-скрипты.
Ну, а в следующих статьях замеры придётся делать по старинке, осциллографом.
Источник