Русский
Русский
English
Статистика
Реклама

Разработка логического анализатора на базе Redd проверяем его работу на практике

В прошлой статье мы сделали аппаратуру, реализующую логический анализатор на базе комплекса Redd. Статья разрослась так, что рассмотрение программной поддержки мы отложили на потом. Пришла пора разобраться с тем, как мы будем получать и отображать данные, которые анализатор копит в ОЗУ.



Предыдущие статьи цикла
  1. Разработка простейшей прошивки для ПЛИС, установленной в Redd, и отладка на примере теста памяти.
  2. Разработка простейшей прошивки для ПЛИС, установленной в Redd. Часть 2. Программный код.
  3. Разработка собственного ядра для встраивания в процессорную систему на базе ПЛИС.
  4. Разработка программ для центрального процессора Redd на примере доступа к ПЛИС.
  5. Первые опыты использования потокового протокола на примере связи ЦП и процессора в ПЛИС комплекса Redd.
  6. Веселая Квартусель, или как процессор докатился до такой жизни.
  7. Методы оптимизации кода для Redd. Часть 1: влияние кэша.
  8. Методы оптимизации кода для Redd. Часть 2: некэшируемая память и параллельная работа шин.
  9. Экстенсивная оптимизация кода: замена генератора тактовой частоты для повышения быстродействия системы.
  10. Доступ к шинам комплекса Redd, реализованным на контроллерах FTDI
  11. Работа с нестандартными шинами комплекса Redd
  12. Практика в работе с нестандартными шинами комплекса Redd
  13. Проброс USB-портов из Windows 10 для удалённой работы
  14. Использование процессорной системы Nios II без процессорного ядра Nios II
  15. Практическая работа с ПЛИС в комплекте Redd. Осваиваем DMA для шины Avalon-ST и коммутацию между шинами Avalon-MM
  16. Разработка простейшего логического анализатора на базе комплекса Redd


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

Вывод из всего этого прост. Чтобы в статье не бегать от модуля к модулю, пришлось разобраться, как сделать на чистом Tcl. И знаете, не такой это оказался страшный язык. Вообще, всегда можно перейти в каталог C:\intelFPGA_lite и задать поиск интересующих слов по файлам *.tcl. Решения почти всегда найдутся. Так что он мне нравится всё больше и больше. Ещё и ещё раз советую приглядеться к этому языку.

Я уже говорил, что при запуске Квартусового Tcl скрипта под Windows и под Linux, тексты должны немного различаться в районе инициализации. Запуск под Linux непосредственно на центральном процессоре Reddа должен давать большее быстродействие. Но зато запуск под Windows во время опытов более удобен для меня, так как я могу редактировать файлы в удобной для себя среде. Поэтому все дальнейшие файлы я писал под запуск из System Console в Windows. Как их переделать в Линуксовый вариант, мы разбирались в той самой статье по ссылке выше.

Обычно я даю фрагменты кода с пояснениями, а затем уже справочно полный код. Но это хорошо, когда во фрагментах каждый видит что-то знакомое. Так как для многих, кто читает эти строки, язык Tcl считается экзотикой, сначала я приведу для справки полный текст своего первого пробного скрипта.
Полный текст находится здесь.
variable DMA_BASE 0x2000000variable DMA_DESCR_BASE 0x2000020# Чтение регистра блока DMA.#proc dma_reg_read { address } {  variable DMA_BASE  variable m_path  set address [expr {$address * 4 + $DMA_BASE}]  return [master_read_32 $m_path $address 1]}# Запись регистра блока DMAproc dma_reg_write { address data } {  variable DMA_BASE  variable m_path  set address [expr {$address * 4 + $DMA_BASE}]  master_write_32 $m_path $address $data}# Запись регистра дескрипторов блока DMAproc dma_descr_reg_write { address data } {  variable DMA_DESCR_BASE  variable m_path  set address [expr {$address * 4 + $DMA_DESCR_BASE}]  master_write_32 $m_path $address $data}proc prepare_dma {sdram_addr sdram_size} {# Остановили процесс, чтобы всё понастраивать# Да, мне лень описывать все константы, # я делаю всё на скорую рукуdma_reg_write 1 0x20# На самом деле, тут должно быть ожидание фактической остановки,# но в рамках теста, оно не нужно. Точно остановимся.# Добавляем дескриптор в FIFO# Адрес источника (вообще, это AVALON_ST, но я всё# с примеров списывал, а там он зануляется)dma_descr_reg_write 0 0# Адрес приёмника. dma_descr_reg_write 1 $sdram_addr# Длинаdma_descr_reg_write 2 $sdram_size# Управляющий регистр (взводим бит GO)dma_descr_reg_write 3 0x80000000# Запустили процесс, не забыв отключить прерыванияdma_reg_write 1 4}puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_pathprepare_dma 0 0x100puts [master_read_32 $m_path 0 16]



Разбираем фрагменты скрипта


При работе со скриптом нам надо знать базовые адреса блоков. Обычно я их беру из заголовочных файлов, сделанных для BSP, но сегодня мы не создали никаких проектов. Однако адреса всегда можно посмотреть в Platform Designer хоть на привычной нам структурной схеме (я покажу только пример одного адреса):



Хоть там же на специальной вкладке, где всё собрано в виде таблиц:



Нужные мне адреса я вписал в начало скрипта (а нулевой начальный адрес буферного ОЗУ я буду подразумевать всегда, чтобы облегчить код):
variable DMA_BASE 0x2000000variable DMA_DESCR_BASE 0x2000020


Функции доступа к DMA в позапрошлой статье я специально написал в простейшем виде. Сегодня же я просто взял и аккуратно перенёс этот код из C++ в Tcl. Эти функции требуют доступа к аппаратуре, а именно чтения и записи порта самого блока DMA и функция записи порта блока дескрипторов DMA. Читать дескрипторы пока не нужно. Но понадобится допишем по аналогии. Мы уже тренировались работать с аппаратурой здесь, поэтому делаем такие функции:
# Чтение регистра блока DMA.#proc dma_reg_read { address } {  variable DMA_BASE  variable m_path  set address [expr {$address * 4 + $DMA_BASE}]  return [master_read_32 $m_path $address 1]}# Запись регистра блока DMAproc dma_reg_write { address data } {  variable DMA_BASE  variable m_path  set address [expr {$address * 4 + $DMA_BASE}]  master_write_32 $m_path $address $data}# Запись регистра дескрипторов блока DMAproc dma_descr_reg_write { address data } {  variable DMA_DESCR_BASE  variable m_path  set address [expr {$address * 4 + $DMA_DESCR_BASE}]  master_write_32 $m_path $address $data}

Теперь уже можно реализовывать логику работы с DMA. Ещё и ещё раз говорю, что я специально в позапрошлой статье не обращался к готовому API, а сделал как можно более простые собственные функции. Я знал, что мне придётся эти функции портировать. И вот так я портировал инициализацию DMA (аргументы начальный адрес и длина в байтах для буфера, в который будет идти приём):
proc prepare_dma {sdram_addr sdram_size} {# Остановили процесс, чтобы всё понастраивать# Да, мне лень описывать все константы, # я делаю всё на скорую рукуdma_reg_write 1 0x20# На самом деле, тут должно быть ожидание фактической остановки,# но в рамках теста, оно не нужно. Точно остановимся.# Добавляем дескриптор в FIFO# Адрес источника (вообще, это AVALON_ST, но я всё# с примеров списывал, а там он зануляется)dma_descr_reg_write 0 0# Адрес приёмника. dma_descr_reg_write 1 $sdram_addr# Длинаdma_descr_reg_write 2 $sdram_size# Управляющий регистр (взводим бит GO)dma_descr_reg_write 3 0x80000000# Запустили процесс, не забыв отключить прерыванияdma_reg_write 1 4}

Ну, собственно, всё. Дальше идёт основное тело скрипта. Какое я там сделал допущение? Я не жду окончания работы DMA. Я же знаю, что таймер тикает весьма быстро. Поэтому запрошенные мною 0x100 байт заполнятся весьма шустро. И все мы знаем, что JTAG работает очень неспешно. В реальной жизни, разумеется, надо будет добавить ожидание готовности. А может и отображение текущего адреса. И ещё чего-нибудь И тогда код станет точно непригодным для статьи, из-за того, что все эти мелочи станут закрывать суть. А пока простейший код выглядит так:
puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_pathprepare_dma 0 0x100puts [master_read_32 $m_path 0 16]

То есть, настроились на работу с аппаратурой, приняли 0x100 байт в буфер с адресом 0, отобразили первые 16 байт.

Заливаем прошивку, запускаем System Console, исполняем скрипт, наблюдаем такую красоту:



Таймер 0, счётчик 0. Таймер 0x18, счётчик 1. Таймер 0x5EB, счётчик 2. Ну, и так далее. В целом, мы видим то, что хотели. На этом можно было бы и закончить, но текстовое отображение не всегда удобно. Поэтому продолжаем.

Как отобразить результаты в графическом виде


То, что мы получили это вполне себе замечательно, но временные диаграммы обычного логического анализатора часто удобнее смотреть в графическом виде! Надо бы написать программу, чтобы можно было это делать Вообще, написать свою программу всегда полезно, но время ресурс ограниченный. А в рамках работы с комплексом Redd, мы придерживаемся подхода, что все эти работы вспомогательные. Мы должны тратить время на основной проект, а на вспомогательный нам никто его не выделит. Можно этим заняться в свободное от работы время, но лично мне это не даёт делать балалайка. Она тоже много времени отнимает. Так что свободное время лично у меня тоже занято. К счастью, уже существуют решения, которые могут облегчить нам жизнь. Я узнал о нём, когда читал статьи про Icarus Verilog. Там строят графическую визуализацию времянок через некие файлы VCD. Что это такое?

В стандарте Verilog (я нашёл его здесь ) смотрим там раздел 18. Value change dump (VCD) files. Вот так! Оказывается, можно заставить тестовую систему на языке Verilog генерировать подобные файлы. И они будут стандартизированы, то есть, едины, независимо от среды, в которой производится моделирование. А где стандарт, там и единство логики систем отображения. А что, если мы тоже будем формировать такой же файл на основании того, что пришло из анализатора? Причём заставим Tcl скрипт делать это Осталась самая малость: выяснить, что в файл следует записать, а также как это сделать.

Радостный, я попросил Гугля показать мне пример vcd-файла. Вот, что он мне дал. Я выделил всё, что там было в таблице и сохранил в файл tryme.vcd. Если к моменту чтения статьи страничка перестанет существовать, вот содержимое этого файла ниже.
Содержимое файла tryme.vcd
$commentFile created using the following command:vcd files output.vcd $dateFri Jan 12 09:07:17 2000$end$versionModelSim EE/PLUS 5.4$end$timescale1ns$end$scope module shifter_mod $end$var wire 1 ! clk $end$var wire 1 " reset $end$var wire 1 # data_in $end$var wire 1 $ q [8] $end$var wire 1 % q [7] $end$var wire 1 & q [6] $end$var wire 1 ' q [5] $end$var wire 1 ( q [4] $end$var wire 1 ) q [3] $end$var wire 1 * q [2] $end$var wire 1 + q [1] $end$var wire 1 , q [0] $end$upscope $end$enddefinitions $end#0$dumpvars0!1"0#0$0%0& 0'0(0)0*0+0,$end#1001!#1500!#2001!$dumpoffx!x"x#x$x%x&x'x(x)x*x+x,$end#300$dumpon1!0"1#0$0% 0&0'0(0)0*0+1,$end#3500!#4001!1+#4500!#5001!1*#5500!#6001!1)#6500!#7001!1(#7500!#8001!1'#8500!#9001!1&#9500! #10001!1%#10500!#11001!1$#11500!1"0$0%0&0'0(0)0*0+0,#12001!$dumpall1!1"1#0$0%0&0'0(0)0*0+0,$end


Какое-то Юстас Алексу. Можно, конечно, разобраться, глядя в стандарт, но больно там много всего. Попробуем всё сделать на практике, загрузив файл в какую-нибудь среду, визуализирующую его содержимое, и сопоставив текст с полученной картинкой. Несмотря на то, что заголовок web-страницы гласит, что всё это пример от среды ModelSim, мне не удалось открыть данный файл в этой среде. Возможно, в комментариях кто-то подскажет, как это сделать. Однако не МоделСимом единым жив человек. Существует такая кроссплатформенная система с открытым исходным кодом GtkWave. Сборку под Windows я взял тут . Запускаем её, выбираем в меню File->Open New Tab:



Указываем наш файл и получаем такую картинку:



Выделяем все сигналы и через меню правой кнопки Мыши выбираем Recurse Import->Append:



И вот результат:



Видя такое дело, вполне можно разобраться, что в файле зачем добавлено.
$timescale1ns$end

Ну, тут всё понятно. Это в каких единицах время будет задаваться.
$scope module shifter_mod $end$var wire 1 ! clk $end$var wire 1 " reset $end$var wire 1 # data_in $end$var wire 1 $ q [8] $end$var wire 1 % q [7] $end$var wire 1 & q [6] $end

Это объявляются переменные с весьма экзотическими именами Восклицательный знак, Кавычки, решётка и т.п. Шина в найденном примере объявляется побитово. Идём дальше:
#0$dumpvars0!1"0#0+0,$end

Время равно нулю. Дальше значения переменных. Зачем ключевое слово $dumpvars? Придётся заглянуть в стандарт. Как я и думал, какое-то непонятное занудство. Но создаётся впечатление, что нам сообщают, что эти данные получены при помощи директивы языка $dumpvars. Давайте попробуем убрать это слово и соответствующую ему строку $end. Загружаем обновлённый файл и видим результат:



Как говорится, найдите десять отличий Никакой разницы. Значит, нам это добавлять на выход не нужно. Идём дальше.
#1001!#1500!

Мы видим, что в моменты 100 и 150 нс тактовый сигнал переключился, а остальные нет. Поэтому мы можем добавлять только изменившиеся значения сигналов. Идём дальше.
#2001!$dumpoffx!x"x,$end#300$dumpon1!0"

Теперь мы умеем задавать состояние X. Проверяем, нужны ли ключевые слова $dumpoff и $dumpon, выкинув их из файла (не забываем про парные им $end)

Добавлять ещё один рисунок, полностью идентичный предыдущим, я не буду. Но можете проверить это же у себя.

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


То же самое текстом:
vcd_declaration_vars ::=$var var_type size identifier_code reference $endvar_type ::=event | integer | parameter | real | realtime | reg | supply0 | supply1 | time| tri | triand | trior | trireg | tri0 | tri1 | wand | wire | worsize ::=decimal_numberreference ::=identifier| identifier [ bit_select_index ]| identifier [ msb_index : lsb_index ]index ::=decimal_number


Прекрасно! Мы можем задавать и вектора! Пробуем добавить одну переменную, дав ей имя звёздная собака, так как односимвольные варианты уже израсходованы авторами оригинального примера:
$var reg 32 *@ test [31:0] $end

И добавим ей таких присвоений Обратите внимание на пробелы перед именем переменной! Без них не работает, но и стандарт требует их наличия в отличие от однобитных вариантов:
#0b00000001001000110100010101100111 *@#100b00000001XXXX0011010001010110zzzz *@

Смотрим результат (я в нём ради интереса раскрыл вектор)



Ну и замечательно. У нас есть вся теория, чтобы подготовить файл для просмотра в GtkWave. Наверняка, можно будет его посмотреть и в ModelSim, просто я пока не понял, как это сделать. Давайте займёмся формированием данного файла для нашего супер-мега-анализатора, который фиксирует одно единственное число.

Делаем Tcl-скрипт, создающий файл VCD


Если весь предыдущий опыт я брал из файлов *.tcl, идущих в комплекте с Квартусом, то здесь всё плохо. Слово file есть в любом из них. Пришлось спросить у Гугля. Он выдал ссылку на замечательный справочник.

Функции не трогаем, а основное тело скрипта переписываем так.
Основное тело скрипта.
puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_pathprepare_dma 0 0x100set fileid [open "ShowMe.vcd" w]puts $fileid "\$timescale"puts $fileid "1ns"puts $fileid "\$end"puts $fileid "\$scope module megaAnalyzer \$end"puts $fileid "\$var reg 32 ! data \[31:0] \$end"puts $fileid "\$upscope \$end"puts $fileid "\$enddefinitions \$end"# А зачем рисовать где-то справа?# Будем рисовать от первой точки# Для этого запомним базуset startTime [lindex [master_read_32 $m_path 0 1] 0]for { set i 0}  {$i < 20} {incr i} {   set cnt [lindex [master_read_32 $m_path [expr {$i * 8}]  1] 0]   set data [lindex [master_read_32 $m_path [expr {$i * 8 + 4}]  1] 0]   # Таймер тикает с частотой 100 МГц   # Один тик таймера - это 10 нс   # поэтому на 10 и умножаем   puts $fileid "#[expr {($cnt - $startTime)* 10}]"   puts $fileid "b[format %b $data] !"}close $fileid


Здесь я не стал учитывать перескоки счётчика через ноль. Перед нами простейший демонстрационный скрипт, так что не будем его перегружать. Запускаем Дааааа. Это именно то, о чём я предупреждал. Оно работает весьма и весьма долго. Сколько оно будет работать в случае заполненных мегабайтных буферов страшно даже предположить. Но зато мы не потратили много времени на разработку! Опять же, кто работал с анализатором BusDoctor, тот не даст соврать: этот фирменный анализатор отдаёт данные больших объёмов тоже очень и очень неспешно.
Итак, получаем файл:
$timescale1ns$end$scope module megaAnalyzer $end$var reg 32 ! data [31:0] $end$upscope $end$enddefinitions $end#0b0 !#240b1 !#15150b10 !#25170b11 !#26510b100 !#26690b101 !#31180b110 !#31830b111 !#35540b1000 !#35630b1001 !#36940b1010 !#39890b1011 !#46130b1100 !#55540b1101 !#60820b1110 !#71270b1111 !#71480b10000 !#76270b10001 !#77990b10010 !#91000b10011 !


Случайные промежутки есть. Нарастающее двоичное число есть. Правда, с отброшенными незначащими нулями. Визуализируем:



Счётчик щёлкает! В случайные моменты времени. Что хотели, то и получили

Заключение


Мы сделали простейший логический анализатор, потренировались принимать данные с него и производить их визуализацию. Чтобы довести этот простейший пример до совершенства, разумеется, придётся ещё потрудиться. Но путь, по которому идти, понятен. Мы получили опыт изготовления реальных измерительных устройств на базе ПЛИС, установленной в комплексе Redd. Теперь каждый сможет доработать эту основу так, как сочтёт нужным (и как ему позволит время), а в следующей серии статей я начну рассказ о том, как заменить этому анализатору голову, чтобы сделать шинный анализатор для USB 2.0.

Те, у кого нет настоящего Redd, смогут воспользоваться вот такой свободно доставаемой платой, которая имеется у массы продавцов на Ali Express.


Искать следует по запросу WaveShare ULPI. Подробнее о ней на странице производителя.
Источник: habr.com
К списку статей
Опубликовано: 30.06.2020 18:10:31
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Компьютерное железо

Программирование микроконтроллеров

Системное программирование

Fpga

Плис

Redd

Логический анализатор

Tcl

Vcd

Systemconsole

Gtkwave

Категории

Последние комментарии

© 2006-2020, personeltest.ru