Использование процессорной системы Nios II без процессорного ядра Nios II

В прошлом блоке статей про комплекс для удалённой отладки Redd, я показал, что работа с ним – это не только работа с ПЛИС. Мало того, ПЛИС – это всего лишь очень интересная, но всё-таки весьма специфичная часть комплекса. Основная же его часть – мосты FTDI и прочие USB-шные вещи. Особого интереса тема не вызвала, но тем не менее, теперь все знают, какие именно основные аппаратные средства имеются в комплексе. И мы снова можем вернуться к рассмотрению интересной темы – ПЛИС.

Продолжим традицию предыдущего блока и будем дальше искать необязательные части. Сегодня мы научимся обходиться без процессорного ядра Nios II. Да-да. В процессорной системе Nios II само процессорное ядро – важный, но не обязательный элемент. Мы потренируемся делать систему без него, вынося все управляющие функции на уровень центрального процессора комплекса Redd.

Использование процессорной системы Nios II без процессорного ядра Nios II

Зачем нужно и зачем не нужно процессорное ядро

Как я уже отмечал в теоретической части одной из прошлых статей, при работе с комплексом Redd, всё нестандартное оборудование – это вспомогательная вещь. Оно используется краткосрочно, только на этапе проекта и не отчуждается к Заказчику. Соответственно, вряд ли на это будут выделены отдельные разработчики. И нет смысла ради работы с ним, изучать слишком много дополнительных вещей.

Именно поэтому вместо подхода к разработке через Verilog (или VHDL, или ещё какой-нибудь специфичный язык), я предложил просто создавать Nios II систему на базе готовых устройств, соединяя их шинами. Вместо управляющих автоматов я выбрал процессорное ядро, так как язык разработки под него ближе современным системным программистам. Разумеется, если не хватает кубиков для создания системы, недостающие приходится дописывать. Но потом они могут использоваться повторно в других проектах, так что готовая библиотека наберётся весьма быстро.

У этого подхода множество достоинств. И собственно, этот подход мы уже закрепили на практике. Но когда я продумываю новые примеры, которые хочу вынести на обсуждение, на первый план выходит нехватка памяти ПЛИС. Хочется больше памяти отдать для FIFO, но программу и данные для процессора тоже где-то надо хранить. А у четвёртого Циклона этой самой памяти не так много. Всем может не хватить.

Вторая проблема – мы уже делали систему для связи процессора Nios II и центрального процессора в одной из предыдущих статей. Она тоже отнимает память для своих FIFO, плюс – её использование загромождает рисунки, скрывая суть, описываемую в статье. Если можно отказаться от этого дела, лучше отказаться (правда, этот отказ не всегда возможен).

Но кто будет управлять системой? И как можно перекидывать данные без процессорного ядра? Знакомьтесь! Компонент Altera JTAG-to-Avalon-MM.

Одной стороной он связан со штатной шиной JTAG (именно штатной, не надо ничего внешнего, достаточно работать через штатный USB-Blaster). Другой – подключён к шине AVALON, обеспечивая доступ ко всем устройствам. Точно такой же доступ обеспечивало и процессорное ядро. Исходно, данный блок задуман для первичной отладки собственных ядер, чтобы обращаться к ним на скорую руку с хост-машины. Но никто не запрещает использовать его и для других задач, ведь JTAG адаптер в комплексе Redd присутствует постоянно!

Само собой, система на основе данного блока не может быть производительной для транзакций. Канал JTAG не быстрый сам по себе, а ещё и применённый в комплексе Redd адаптер USB-Blaster на базе микроконтроллера делает его совсем неспешным. Но как сделать быстрый мастер – мы уже знаем. Никто не запрещает под каждый случай выбирать то, что удобнее именно под него.

В проектах, которые планируются под будущие статьи, скорость управления не критична. Там будет анализатор, которому будет дана команда копить данные в SDRAM, после чего он уйдёт в себя до заполнения ОЗУ или до команды прекратить работу. Выполнятся эти команды за микросекунды или за десятки миллисекунд – особой разницы нет.

Ну, и перекачка данных через JTAG будет идти медленнее, чем через FT245. Но опять же, мы уже знаем, как перекачивать всё через FT245. Знание нового варианта совершенно не запрещает нам выбирать старый. Но зато, познакомившись с новым вариантом, мы получим возможность делать систему пусть помедленнее, но попроще в разработке и поэкономичнее с точки зрения ресурсов. Больше методик, хороших и разных! Поэтому приступаем к экспериментам.

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

Про изготовление аппаратной части (в отличие от программной) в сети имеется масса статей. Обычно во всех примерах в систему добавляется внутренняя память ПЛИС, светодиоды и кнопки. Наша организация, как и вся страна, сейчас экстренно ушла на удалёнку, поэтому мне даже некого попросить подключить осциллограф к разъёму комплекса, так что я в примере ограничусь только памятью. Кто будет повторять эксперименты на обычных макетных платах, может добавить GPIO на выход, чтобы поуправлять светодиодами, и GPIO на вход, чтобы посмотреть работу кнопок. Создаём процессорную систему (как это делается, я описывал здесь), но вместо процессора ставим блок Altera JTAG-to-Avalon-MM. Его берут здесь:

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

Обратите внимание, что сигнал Reset скоммутирован весьма нестандартно. Это связано с тем, что мне не хочется тратить время на физическую его выработку.

Собственно, для сегодняшней аппаратуры — это всё. Генерим систему, назначаем ножку clk (другие ножки мы сегодня не используем). Напоминаю, что ножку reset я делаю виртуальной (Virtual Pin), методика описана в этой статье (искать по фразе Virtual Pin). В результате, должно получиться:

Собираем, заливаем в ПЛИС… Приступаем к экспериментам.

Программирование через TCL-скрипты

Дальше все учебники, рассказывающие про работу с блоком Altera JTAG-to-Avalon-MM, говорят, что работу надо вести с помощью TCL-скриптов. Перед тем, как бросаться в бездну непознанного, лучше набить руку на хорошо документированном, поэтому мы не будем исключением и начнём эксперименты тоже с них. Про то, что такое TCL, есть много статей на Хабре. Дам ссылку на одну из них, так как она связана с ПЛИС: https://habr.com/ru/post/308962/. На сайте Intel FPGA (бывшем сайте Альтеры) есть целый онлайн-курс для работы с этим языком. Чтобы научиться подавать команды на нём, в среде разработки можно запустить System Console:

В результате, откроется шикарная среда.

В ней можно творить чудеса, вплоть до разработки GUI-приложений. Но в рамках статьи мы коснёмся этого дела лишь поверхностно (почему – объясню чуть ниже). Мы просто попробуем подать пару команд. Каких команд? Для этого надо найти и скачать документ Analyzing and Debugging Designs with System Console (как всегда, я даю имена, но не ссылки, так как ссылки вечно меняются).

Мы попробуем поэкспериментировать с командами из этой группы документа:

Для начала осмотримся в выданных нам сообщениях. Так как я загрузил «прошивку» удалённо, система сама нашла удалённый JTAG-сервер:

Команды будем подавать вот в это окно:

Для начала попробуем записать в память константу. Для этого надо бы подать команду:

master_write_32

но беда в том, что первый её аргумент —

Поэтому перед подачей полезной команды, надо сначала научиться этот service_path получать. Ищем соответствующий участок в документации:

Прекрасно! Пробуем подать соответствующую команду, пользуясь примером из документа.

% get_service_paths master
/devices/10CL006(Y|Z)|10CL010(Y|Z)|..@1#1-5.4.2.1#192.168.10.146/(link)/JTAG/alt_sld_fab_sldfabric.node_0/phy_0/master_0.master
%

Как-то это сложно вводить вручную. Вариант:

% lindex [get_service_paths master] 0
/devices/10CL006(Y|Z)|10CL010(Y|Z)| ..@1#1-5.4.2.1#192.168.10.146/(link)/JTAG/alt_sld_fab_sldfabric.node_0/phy_0/master_0.master
%

Ничуть не лучше. Он был бы полезен, только если бы устройств было несколько.

Хорошо. Попробуем полный вариант примера из документа:

% set m_path [lindex [get_service_paths master] 0]
/devices/10CL006(Y|Z)|10CL010(Y|Z)|..@1#..@1#1-5.4.2.1#192.168.10.146/(link)/JTAG/alt_sld_fab_sldfabric.node_0/phy_0/master_0.master
%

И теперь – пробуем запись:

% master_write_32 $m_path 0x0 0x01234567
error: master_write_32: /devices/10CL006(Y|Z)|10CL010(Y|Z)|..@1#..@1#1-5.4.2.1#192.168.10.146/(link)/JTAG/alt_sld_fab_sldfabric.node_0/phy_0/master_0.master is not an open master service
    while executing
"master_write_32 $m_path 0x0 0x01234567"
%

Продолжаем штудировать инструкцию…

% open_service master $m_path

%

Теперь-то получится?

% master_write_32 $m_path 0x0 0x01234567

% master_write_32 $m_path 0x4 0x89abcdef

%

Сообщений об ошибках нет. Пробуем читать:

% master_read_32 $m_path 0x0 0x2
0x01234567 0x89abcdef
%

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

Итого, правильная последовательность действий выглядит так:

set m_path [lindex [get_service_paths master] 0] open_service master $m_path
master_write_32 $m_path 0x0 0x01234567
master_write_32 $m_path 0x4 0x89abcdef
master_read_32 $m_path 0x0 0x2

Кому интересны возможности System Console и языка TCL, могут изучить описания, примеры и видеоматериалы, которых в сети достаточно много, а в рамках данной статьи, я перехожу к тому, чего лично мне найти в сети не удалось…

Отходим от языка TCL в сторону C++

TCL – достаточно мощный язык. Его всегда полезно изучить, особенно если вы планируете плотно работать с ПЛИС. В целом, свободное владение ещё одним мощным языком программирования – это большой плюс для разработчика. Но это – в целом. А в частности – необходимость владения ещё одним языком идёт вразрез с принципами, которые были сформулированы в этой статье. Мы считаем, что комплекс Redd – вспомогательный комплекс, которым должны легко владеть системные разработчики на языках широкого профиля. То есть, владение дополнительным языком всегда приветствуется, но не должно быть обязательным условием.

Поэтому я решил найти, как же можно работать с JTAG-мостом из старых добрых C/C++. Возможно, я слепой, однако, потратив уйму времени на упражнения с составлением поисковых запросов, я ничего путного не добился. Самое интересное, что многие запросы выдавали среди первых ссылку на мою же статью про весёлую квартусель, но там показано, как System Console вызывается из программы на Java, причём файл скрипта должен быть создан заранее, что явно будет ещё больше портить быстродействие, и без того не очень высокое из-за последовательного канала JTAG и небыстрого адаптера. Тем не менее, кто работает на Java, может освежить ту статью в памяти и принять методы на вооружение.

Не найдя ничего, я решил воспользоваться принципом, показанным в одной из моих статей из цикла про PSoC. Суть подхода проста: если нет документации – изучаем то, что идёт в комплекте поставки. А в комплекте с Квартусом идёт масса TCL-скриптов. Именно изучая их, я проникся мощью языка. Но с другой стороны, проникся мыслью, сколько времени уйдёт на его освоение. Да, сейчас работы не так много, но, во-первых, она всё равно есть, а во-вторых, когда вы читаете эти строки, я очень надеюсь, что мир уже вернулся к скоростной жизни.

В конце концов, я нашёл очень интересный файл: C:intelFPGA_lite17.1quartusbin64tcl_server.tcl

Судя по его содержимому, он создаёт сервер, через который можно вызывать функции, к которым мы уже обращались. То есть мы вполне можем написать своего клиента, который подключится по сети и начнёт подавать команды… Этот клиент может быть написан на моём любимом C++. То есть, вопрос будет решён. Поэтому приступаем к анализу данного файла.

Эксперименты с файлом tcl_server.tcl на локальной машине

Продолжаем идти от хорошо документированного к скрытой впотьмах цели. Пробуем изучать всё из Windows (на моей домашней машине), правда, уже будучи подключённым к удалённому JTAG-серверу (на комплексе Redd в офисе).

Сначала следствие пошло по ложному пути. В файле есть вот такая интересная конструкция:

if { [info exists env(QUARTUS_ENABLE_TCL_SERVER)]  } {
	if { $env(QUARTUS_ENABLE_TCL_SERVER) == 1 } {
		_q_setup_server $_q_port
	}
}

Само собой, я вбил слово QUARTUS_ENABLE_TCL_SERVER в поисковик. Результатов было не так много. Всего два. Из них только один путный: https://www.intel.com/content/www/us/en/programmable/quartushelp/13.0/mergedProjects/eda/synthesis/synplicity/eda_pro_synplty_setup.htm

Обрадованный, я создал соответствующую переменную окружения, но команда netstat –a показала, что на порту 2589 не появилось никакого сервера.

Хорошо. Тогда я попробовал запустить этот скрипт целиком через System Console, воспользовавшись данным пунктом меню:

И получил ошибку:

Я – человек простой. Не найдя на своей машине описания этого безобразия (но найдя его на github и заподозрив, что перед нами простой вывод сообщения), я сделал файл-копию скрипта и удалил соответствующую строку:

Попутно убрал описанную ранее проверку на наличие переменной окружения QUARTUS_ENABLE_TCL_SERVER, раз всё равно скрипт нам предстоит запускать вручную.

После чего скрипт запустился, в системе появился слушающий порт 2589. К нему можно подключиться через Telnet. Ещё раз напоминаю, что в данный момент я веду эксперименты на домашней машине под Windows. На удалённую машину мы перейдём чуть позже. Итак, подключаемся:

Получаем пустое окно терминала. Стукнув пару раз по клавише , видим, что нас слышат, но не понимают:

Запросим помощи.

help
1 couldn't find help for command . Try help help.

Уже лучше. Уточняем запрос.
Покажу только фрагмент ответа:

help help 
<…>
get_service_paths
get_service_types
get_services_to_add
get_version
<…>
master_read_16
master_read_32
master_read_8
master_read_memory
master_read_to_file
master_write_16
master_write_32
master_write_8

Знакомые слова! Ну-ка, попробуем ввести уже известную нам строку:

get_service_paths master
1 Error: Invalid command

Простите! Но нам же только что разрешили подавать команду get_service_paths!

Проверяем скрипт на наличие строки Invalid command. И она там обнаружена! Примерно в середине вот такой конструкции:

	set ecmd [lindex $line 0]
	if { $ecmd != "project" && $ecmd != "device" && 
	$ecmd != "cmp" && $ecmd != "sim" && $ecmd != "show_main_window"
	&& $ecmd != "hide_main_window" && $ecmd != "get_version" 
	&& $ecmd != "help" && $ecmd != "convert" 
	&& $ecmd != "import_assignments_from_maxplus2" } {
		set res "Error: Invalid command";
	} elseif [catch { set res [eval $line] } result] {
		set res "Error: $result";
	} 
	if { $res == "" } {
		set res " "
	}

Получается, что этот скрипт обрабатывает только небольшое количество команд из терминала. На самом деле, это очень полезная проверка. В будущем мы сможем именно в этом месте сделать обработку скриптовых команд. По мере освоения языка TCL мы сможем всё больше и больше кода выносить на уровень скрипта, а от внешней программы только вызывать его (это явно будет быстрее, чем все строки гнать через сеть). Ну, а те команды, которые не распознаны, по-прежнему просто бросать в интерпретатор напрямую. В идеале, мы потихоньку изучим TCL и постепенно перейдём полностью на него. Если получится.

Пока же остановим исполнение скрипта (закрыв System Console) и заменим указанный участок на такой:

	set ecmd [lindex $line 0]
	if [catch { set res [eval $line] } result] {
		set res "Error: $result";
	} 
	if { $res == "" } {
		set res " "
	}

Запускаем скрипт на исполнение, подключаемся, пробуем прогнать нашу эталонную последовательность команд:

set m_path [lindex [get_service_paths master] 0]
1 /devices/10CL006(Y|Z)|10CL010(Y|Z)|..@1#..@1#1-5.4.2.1#192.168.10.146/(link)/JTAG/alt_sld_fab_sldfabric.node_0/phy_0/master_0.master
open_service master $m_path
1 Error: can't read "m_path": no such variable

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

open_service master /devices/10CL006(Y|Z)|10CL010(Y|Z)|..@1#..@1#1-5.4.2.1#192.168.10.146/(link)/JTAG/alt_sld_fab_sldfabric.node_0/phy_0/master_0.master 

Получили результат 1. Отлично! Продолжаем опыты!

master_write_32 /devices/10CL006(Y|Z)|10CL010(Y|Z)|..@1#..@1#1-5.4.2.1#192.168.10.146/(link)/JTAG/alt_sld_fab_sldfabric.node_0/phy_0/master_0.master  0x0 0x01234567

master_write_32 /devices/10CL006(Y|Z)|10CL010(Y|Z)|..@1#..@1#1-5.4.2.1#192.168.10.146/(link)/JTAG/alt_sld_fab_sldfabric.node_0/phy_0/master_0.master 0x4 0x89abcdef

master_read_32 /devices/10CL006(Y|Z)|10CL010(Y|Z)|..@1#..@1#1-5.4.2.1#192.168.10.146/(link)/JTAG/alt_sld_fab_sldfabric.node_0/phy_0/master_0.master 0x0 0x2

Получаем ответ:

1 0x01234567 0x89abcdef

1 – это результат исполнения функции. Дальше идут считанные данные. То есть доступ через сеть у нас не идеальный, но имеется!

Эксперименты с файлом tcl_server.tcl на удалённой машине

В целом, мы уже можем запустить программу и начинать работать с процессорной системой, но к медлительности аппаратуры JTAG у нас добавляется медлительность сети. Само собой, лучше запускать рабочую программу на удалённой машине, чтобы она там работала с адресом localhost. Так будет явно быстрее. И именно тогда исполнение будет идти на центральном процессоре комплекса Redd, что было изначально и запланировано.

Какой файл запускать? Перерыв некоторое количество документации, выясняем, что quartus_sh. Хорошо. Пока не будем развлекаться со скриптами, а, как и в Windows, попробуем подать команды в интерактивном режиме. Забегая вперёд, скажу, что будет весело… Итак, даём команду:

user@redd:~$ sudo /opt/intelFPGA/18.1/qprogrammer/bin/quartus_sh -s

Нам предлагают подавать tcl-строки:

Но наши любимые команды приводят к ошибке:

tcl> get_service_paths master
invalid command name "get_service_paths"

Оказывается, надо подгрузить недостающую библиотеку, подав две команды:

load_package systemconsole 
initialize_systemconsole

Правда, следующая уже привычная нам команда даст ошибку даже с библиотеками:

Ох и долго я искал причину! Перерыл все библиотеки, перерыл море статей на поисковиках, нет такой команды. Но другие – есть. Но они не работают. Но хотя бы есть. Случайно нашёл неприметный документ с решением. Оказывается, нам нужна команда claim_path. Ещё и ещё раз привет кроссплатформенности.

Вот такая последовательность приведёт нас к успеху:

set m_path [lindex [get_service_paths master] 0] set claim_path [claim_service master $m_path mylib] master_write_32 $claim_path 0x0 0x01234567
master_write_32 $claim_path 0x4 0x89abcdef
master_read_32 $claim_path 0x0 0x2

Проверяем через Telnet

В интерактивном режиме всё работает, проверяем работу через скрипт, создающий сервер. Добавляем в него недостающие строки (те самые две строки, подгружающие библиотеку) и запускаем:
user@redd:~$ sudo /opt/intelFPGA/18.1/qprogrammer/bin/quartus_sh -t tcl_server1.tcl

Он вылетает. Слушающий сокет не появляется. Оказывается, надо добавить строчку, которая не даёт выйти из функции открытия сервера:

То же самое текстом:

proc _q_setup_server {port} {
	global _q_lsock;
	if [catch { set _q_lsock [socket -server _q_accept $port] } emsg] {
	}
	vwait forever
	return;
}

Запускаем правильный скрипт и пробуем подключиться к получившемуся серверу через Telnet:

Подаём такие команды (команды выделены жирным, ответы – обычным текстом, как и в случае с Windows, вместо переменных подставляем фразы из предыдущих ответов):

lindex [get_service_paths master]

1 {/devices/10CL006(Y|Z)|10CL010(Y|Z)|..@1#1-5.4.2.1/(link)/JTAG/(110:132 v1 #0)/phy_0/master}

claim_service master {/devices/10CL006(Y|Z)|10CL010(Y|Z)|..@1#1-5.4.2.1/(link)/JTAG/(110:132 v1 #0)/phy_0/master} mylib

1 /channels/remote1/mylib/master_1

master_write_32 /channels/remote1/mylib/master_1 0x0 0x01234567

1

master_write_32 /channels/remote1/mylib/master_1 0x4 0x89abcdef

1

master_read_32 /channels/remote1/mylib/master_1 0x0 0x2

1 0x01234567 0x89abcdef

Всё работает!

А потом пришёл лесник и всех разогнал… Или не всех?

Пока я работал над статьёй, мне удалось найти интересную методику, не требующую написания скрипта, обеспечивающего доступ через сеть. В языке Си имеется функция popen (в Windows — _popen). Она позволяет открыть из консольной программы дочерний процесс, захватив его поток ввода или вывода. В итоге, в Windows можно открыть системную консоль со следующими аргументами:

C:intelFPGA_lite17.1quartussopc_builderbinsystem-console.exe —disable_readline –cli

И можно подавать команды безо всякой сети. Но беда в том, что я не нашёл, как посылать команды и принимать ответы одновременно. Аргумент функции _popen может быть только r или w, но никак не оба сразу. Есть методика, специфичная для Windows, но она будет далеко не кроссплатформенна. Можно, как в статье с Квартуселью, скрипты подавать в качестве аргумента программы system-console, а ответ принимать в виде потока, но каждый запуск будет очень не быстрым…

В Линуксе инициализация функций системной консоли тоже неспешная, так что лучше её делать единожды. Вроде, Гугль говорит, что в Linux у функции popen допустим аргумент «r+», открывающий двунаправленный канал,. Тогда мы сможем открыть quartus_sh и общаться с ним, но я в Linux не очень силён, а Гугль находит ссылки на форумы, где лежит не решение, а жаркие споры на тему, во всех ли сборках это возможно.

Поэтому приводить программу я сегодня не буду. Реализация функций Telnet может оказаться ненужной, а как сделать идеальную программу, перехватывающую оба потока, я пока не знаю. Если кто-то приведёт в комментариях готовые решения, я буду благодарен. В первую очередь – под Debian, чтобы не гонять большие потоки данных по сети. Пока же – ограничимся работающей концепцией.

Заключение

Мы познакомились с методикой разработки процессорных систем на базе Nios II, не содержащих непосредственно процессорное ядро. Такие системы не могут быть рекомендованы, как универсальные, так как их быстродействие оставляет желать лучшего, но в ряде случаев быстродействие управляющего ядра не является важным фактором. Зато достигаемая экономия внутреннего ОЗУ ПЛИС в таких системах выходит на первый план. Именно для них и рекомендуется описанная методика.

Классический подход к программированию таких систем – TCL-скрипты, но в рамках концепции комплекса Redd был разработан и описан в статье подход для программирования на языке C++.

Исходный скрипт tcl_server.tcl из комплекта поставки среды Quartus версии 17.1 Lite можно скачать здесь. Вариант, получившийся после правки – здесь. Пример проекта для ПЛИС можно взять здесь.

 

Источник

FPGA, Nios II, Redd, ПЛИС, системное программирование

Читайте также