Современные устройства и вычислительные системы сталкиваются с возрастающими требованиями к производительности при обработке больших потоков данных, особенно это критично в таких областях, как машинное и глубокое обучение, компьютерное зрение, обработка сигналов, научные вычисления.
Одной из ключевых операций в этих задачах является умножение матриц, которое относится к числу наиболее ресурсоемких вычислений. Последовательная реализация этой операции занимает огромное количество времени для больших матриц. Решение задачи по определению имеет вычислительную сложность $ O(n^3) $ для матриц размера $n×n$. По этой причине разрабатываются различные алгоритмы для более эффективного решения данной задачи.
Традиционно для вычисления умножения матриц использовались центральные процессоры на основе архитектуры фон Неймана. Однако узкое место данной архитектуры в виде медленного доступа к памяти затрудняло эффективное выполнение вычислений. Графические процессоры использовались для борьбы с этим эффектом, позволяя выполнять параллельные вычисления. Однако, как отмечают Капра и др., GPU по-прежнему реализуются по архитектуре фон Неймана и поэтому страдают от аналогичных проблем с доступом к памяти, особенно в приложениях глубокого обучения с высокими требованиями по быстродействию. Совсем недавно тензорные процессоры (TPU) от Google стали самым эффективным способом выполнения операций над матрицами. Данные процессоры используют систолические массивы, которые по своей архитектуре не ограничены узкими местами фон Неймана.
Систолическая структура
Систолическая вычислительная система — это набор взаимосвязанных процессорных элементов (ПЭ), каждый из которых выполняет некоторую заранее определенную и зафиксированную простую операцию. Поскольку простые, регулярные структуры связи имеют существенные преимущества при проектировании и реализации, ячейки в систолической системе обычно соединяются между собой, образуя систолический луч или систолическое дерево. Информация в систолической системе передается между ячейками конвейерным способом, а связь с внешним миром происходит только на граничных ячейках.

В классических фон-неймановских вычислителях данные, считанные из памяти, однократно обрабатываются в процессорном элементе (ПЭ), после чего снова возвращаются в память. Доступ к памяти является узким местом таких архитектур, что критично для многих задач, в частности для задачи умножения матриц. Авторы идеи систолической матрицы Кунг и Лейзерсон предложили заменить один элемент обработки на массив таких процессорных элементов. Вычисления при таком раскладе будут организованы так, что данные на своем пути от считывания из памяти до возвращения обратно пропускаются через как можно большее число процессорных элементов. Этот трюк позволяет добиться более высокой производительности вычислений без увеличения пропускной способности памяти. Суть подхода заключается в том, чтобы гарантировать, что после извлечения данных из памяти, эти данные будут эффективно использованы в каждой ячейке, которую они проходят, будучи “перекаченными” из одной ячейки в другую ячейку по массиву
Такой подход напоминает механизм циркуляции крови в организме. Функция памяти на схеме аналогична функции сердца, множество процессорных элементов выполняет роль тканей, а поток данных, проходящий через все вычислительные ячейки, можно рассматривать как циркулирующие кровяные клетки. Отсюда и происходит название данного типа структур.
Возможность использовать каждый элемент данных несколько раз (и, таким образом, достигать высокой производительности вычислений при скромной пропускной способности памяти) – лишь одно из многих особенностей систолического подхода.
К прочим характеристикам данной структуры можно отнести следующие моменты:
- Процессорные элементы, которые формируют систолическую структуру, имеют одинаковый тип. Каждый из процессорных элементов выполняет определенные вычисления, в отличие от обычных процессоров в многопроцессорных системах, которые могут выполнять одновременно сразу несколько различных задач. Операция, которую будут реализовывать процессорные элементы, выбирается исходя из требований задачи, в соответствии со структурой связей;
- Входные данные извлекаются из памяти один раз и используются до тех пор, пока эти данные необходимы для вычисления задачи. Операции ввода и вывода происходят через граничные процессорные элементы.
- Вычисления в систолических структурах происходят в виде пересылки потока данных из одного вычислительного блока к другому.
- Для большей производительности вычислений можно реализовать сразу несколько систолических матриц. Это позволяет совместить параллелизм с конвейерным подходом к обработке данных, используемым в систолических архитектурах. Кроме того, если процессорные элементы вычисляют сложные выражения или в них был обнаружен критический путь, никто не мешает конвейеризовать вычисления и в процессорных элементах. Это увеличит количество тактов, которые будут необходимы для завершения вычислений, но позволит увеличить тактовую частоту, на которой будет возможна работа вычислителя;
- Производительность системы можно линейно улучшать добавлением большего числа вычислительных блоков.
Использование систолической архитектуры возможно для широкого класса связанных с вычислениями операций, когда над каждым элементом данных выполняется множество повторяющихся операций.
Классификация систолических структур
Систолические структуры обычно разделяют на три категории в зависимости от степени их гибкости
Специализированные: Данные структуры заточены на выполнение конкретного алгоритма. При их проектировании важно понимать, что такие структуры обладают специфической геометрией, статичными связями между процессорными элементами, определенным числом этих процессорных элементов, а также особым типом операции, выполняемые всеми вычислительными блоками этой структуры. В качестве примеров, использующие такой тип структур, можно назвать быстрое преобразование Фурье для заданного числа точек, матричные преобразования (умножение матрицы на вектор, умножение матриц и т.д.), реализация фильтров;
Алгоритмически ориентированные: Эти структуры, как можно понять по названию, не привязаны к конкретному алгоритму, а заточены на определенный класс алгоритмов. В основном подобные структуры используются для выполнения однотипных операций над векторами, матрицами или иными числовыми множествами. В подобных структурах настройка на алгоритм может осуществляться несколькими способами. В одних случаях это достигается путем частичного перепрограммирования процессорных элементов. Обычно они содержат набор возможных операций, которые они могут вычислить, и при настройке на алгоритм необходимо выбрать конкретную операцию. В других случаях это достигается путем изменения внутренних связей между вычислительными блоками. Это позволяет гибко адаптироваться к различным наборам задач;
Программируемые: В этих структурах имеется возможность программирования как процессорных элементов, так и связей между ними. Процессорные элементы в этом типе структур могут иметь локальную память программ, что позволяет выбирать им различные операции из заданного набора без дополнительной реконфигурации. Команды или управляющие сигналы, хранящиеся в памяти программ таких вычислительных блоков, могут так же и изменять направления передачи операндов в структуре, что значительно увеличивает гибкость и функциональность таких структур.
Систолические структуры также можно разбить на классы по разрядности процессорных элементов:
Одноразрядные структуры: В таких матрицах каждый процессорный элемент в каждый момент времени работает только с одним двоичным разрядом. Это существенно ограничивает применение таких структур, но может быть полезно для задач, где требуется высокая скорость обработки отдельных битов информации;
Многоразрядные структуры: В многоразрядных матрицах процессорные элементы выполняют операции над словами фиксированной длины. Это делает их более подходящими для задач, где требуется работа с большим объемом информации, например, для задач численных вычислений, обработки, фильтрации изображений или сигналов.
Выбор конкретных структур зависит от задачи и от вида обрабатываемой информации.
Топологии систолических структур
На рисунке представлена линейная структура систолического массива.

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

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

Данный вид структуры подходит для выполнения операций обращения матриц, а также действий над матрицами специального вида.
Кроме пространственного расположения процессорных элементов важно рассмотреть и возможные направления потоков данных, которые применяются в различных реализациях систолических архитектур. По данному критерию систолические структуры в основном разделяют на три категории: ULA, BLA и TLA.
ULA (Unidirectional Linear Array) — это систолический массив, где потоки данных через процессорные элементы двигаются лишь в одном направлении, при этом количество потоков данных может быть любым.

BLA (Bidirectional Linear Array) — это систолическая структура, в которой два потока данных перемещаются в противоположных направлениях, т.е. навстречу друг другу.

Массив типа BLA, где один из потоков является выходным, называется регулярным.
TLA (Three-path communication Linear Array) – это линейный процессорный массив с тремя коммуникационными трактами, в котором по разным направлениям перемещаются три потока данных

Процессорные элементы в такой схеме выполняют две операции и обычно называются сдвоенными элементами.
Также TLA часто называют сдвоенным конвейером, поскольку он может быть разделен на два линейных конвейера типа BLA. Соответственно, TLA можно получить объединением двух BLA с одним общим потоком данных.
Применение систолического массива для умножения матриц
Рассмотрим умножение матриц $A$ и $B$.
Для простоты будем считать, что матрицы – квадратные, так как прямоугольные матрицы можно достроить до квадратных добавлением нужного количества нулевых строк или столбцов.
Кроме того, для простоты примера ограничим размер матриц до $n\ = \ 3$, это существенно сократит объем математических выкладок, но при этом никак не отразится на корректности размышлений, ведь идеи для матриц размера $3 \times 3$ можно легко обобщить и для матриц произвольной размерности.
Пусть
$$A\ = \begin{bmatrix} a_{00} & a_{01} & a_{02} \\ a_{10} & a_{11} & a_{12} \\ a_{20} & a_{21} & a_{22} \end{bmatrix};\ B\ = \begin{bmatrix} b_{00} & b_{01} & b_{02} \\ b_{10} & b_{11} & b_{12} \\ b_{20} & b_{21} & b_{22} \end{bmatrix};$$Тогда произведением этих матриц будет матрица
$$C\ = \ A \times B\ = \begin{bmatrix} a_{00} & a_{01} & a_{02} \\ a_{10} & a_{11} & a_{12} \\ a_{20} & a_{21} & a_{22} \end{bmatrix} \times \begin{bmatrix} b_{00} & b_{01} & b_{02} \\ b_{10} & b_{11} & b_{12} \\ b_{20} & b_{21} & b_{22} \end{bmatrix}\ = \ \begin{bmatrix} c_{00} & c_{01} & c_{02} \\ c_{10} & c_{11} & c_{12} \\ c_{20} & c_{21} & c_{22} \end{bmatrix};$$Вычислим значения элементов итоговой матрицы по определению умножения матриц (строка на столбец):
$${с_{00} = a_{00} \bullet b_{00} + \ a_{01} \bullet b_{10}\ + \ a_{02} \bullet b_{20}},\\ {с_{01} = a_{00} \bullet b_{01} + \ a_{01} \bullet b_{11}\ + \ a_{02} \bullet b_{21}}, \\\ {с_{02} = a_{00} \bullet b_{02} + \ a_{01} \bullet b_{12}\ + \ a_{02} \bullet b_{22}}, \\\ {с_{10} = a_{10} \bullet b_{00} + \ a_{11} \bullet b_{10}\ + \ a_{12} \bullet b_{20}}, \\\ {с_{11} = a_{10} \bullet b_{01} + \ a_{11} \bullet b_{11}\ + \ a_{12} \bullet b_{21}}, \\\ {с_{12} = a_{10} \bullet b_{02} + \ a_{11} \bullet b_{12}\ + \ a_{12} \bullet b_{22}}, \\\ {с_{20} = a_{20} \bullet b_{00} + \ a_{21} \bullet b_{10}\ + \ a_{22} \bullet b_{20}}, \\\ {с_{21} = a_{20} \bullet b_{01} + \ a_{21} \bullet b_{11}\ + \ a_{22} \bullet b_{21}}, \\\ {с_{22} = a_{20} \bullet b_{02} + \ a_{21} \bullet b_{12}\ + \ a_{22} \bullet b_{22}.}$$Таким образом, операции, необходимые для умножения матриц, сводятся к определенным комбинациям умножения с последующим сложением. Процессорный элемент должен уметь выполнять такую операцию.

Такой блок умножает два входа, соответствующие элементам из двух матриц, и добавляет результат к промежуточной сумме (частичной сумме). Эта операция повторяется с течением времени для накопления результатов для вывода. Элемент обработки также имеет регистровую память для хранения входящих данных и частичных сумм.
Для того, чтобы реализовать при помощи таких процессорных элементов операцию умножения матриц, структура массива должна представлять собой топологию ULA, заключающуюся в том, что потоки данных направлены в одну сторону.

Таким образом, алгоритм работы систолического массива будет следующий:
- На каждом такте процессорный элемент с индексами $i$, $j$ получает два входа $a$ и $b$;
- Он вычисляет произведение этих входов;
- Добавляет результат к данным, хранящимся во внутреннем регистре элемента, и сохраняет результат в регистр;
- Отправляет вход $a$ в следующий процессорный элемент справа;
- Отправляет вход $b$ в следующий процессорный элемент снизу.
Кроме схемы, представленной выше, для реализации умножения матриц можно использовать топологию BLA.
Процессорный элемент такой архитектуры представлен на рисунке 26.

Систолическая матрица представлена на рисунке ниже. В данной схеме элементы второй строки матрицы $B$ поступают со сдвигом на один такт относительно первой строки. Элементы третьей строки на один такт запаздывают относительно элементов второй строки. Элементы матрицы $А$ поступают вниз в порядке, указанном на рисунке.

Заметим, что при такой организации вычислительного процесса для каждого ПЭ такты выполнения операции чередуются с тактами простоя. Таким образом, каждый выходной результат формируется за два такта. В общем случае для выполнения умножения матриц в соответствии с приведенной структурой потребуется $2 \times (2n - 1) + n - 1 = 5n - 3$ тактов (размер матриц $n \times n$).
Разработка умножителя матриц с использованием систолического массива
Схема rtl-дизайна
Опишем проект, реализующий умножение матриц при помощи систолического массива. Проект написан на языке описания аппаратуры уровня регистровых передач SystemVerilog. Для разработки проекта был выбран структурный подход, суть которого заключается в том, что логика работы различных блоков устройства разбита на отдельные подмодули, которые затем объединяются в едином модуле верхнего уровня. В качестве подмодулей выделим блок процессорного элемента и блок систолического массива. Ниже приведено описание отдельных структурных элементов.
Описание модуля процессорного элемента
Данный модуль реализует процессорный элемент. Он производит 8-битную операцию умножения с накоплением.

В обоих случаях на вход ПЭ подаются два операнда i_first_element, i_second_element, а выходят операнды o_first_element, o_second_element и частичная сумма mac_q. На n-ом шаге работы систолической системы ПЭ выполняет операцию
$${mac\_ q}^{n}\ = \ {mac\_ q}^{n - 1}\ + \ {i\_ first\_ element}^{n - 1} \times {i\_ second\_ element}^{n - 1}$$на основе операндов, полученных на (n – 1)-м шаге, при этом операнды на входе и выходе ПЭ одинаковы:
$${o\_ first\_ element}^{n} = \ {i\_ first\_ element}^{n - 1};$$ $${o\_ second\_ element}^{n} = \ {i\_ second\_ element}^{n - 1}.$$Частичная сумма поступает на вход ПЭ с данного процессорного элемента (штриховая линия).
Интерфейс общения с этим модулем достаточно прост. Помимо тактового сигнала i_clk и сигнала асинхронного сброса i_arst, имеем два входных порта для входных значений i_first_element и i_second_element, и управляющий сигнал i_ready. На выходе имеем два порта для o_first_element и o_second_element для распространения входных данных процессорного элемента дальше по систолическому массиву. Для чтения результата присутствует порт o_result.
Логика работы модуля следующая: данные из входных портов защелкиваются, когда устанавливается входной порт i_ready. Входные элементы перемножаются между собой при активном уровне сигнала i_ready. К данному произведению добавляется результат предыдущего шага mac_q. Полученный результат помещается в mac_d, который защелкивается в mac_q на следующем такте. Входные операнды передаются на выход схемы. Данные из mac_q поступают на результирующий выход схемы o_result.
Описание модуля систолического массива
Данный модуль соединяет процессорные элементы для формирования систолического массива в соответствии с выбранной структурой. Ниже на рисунке представлена схема соединений процессорных элементов в систолическом массиве 3x3. Горизонтальные линии представляют соединения строк, вертикальные - столбцов. Стрелки на линиях указывают направление перемещения потока данных.

Интерфейс данного модуля:
Для задания размерности итоговой систолической матрицы модуль имеет входной параметр N. На вход модуля поступает тактовый сигнал i_clk и сигнал асинхронного сброса i_arst. Помимо этих входных сигналов, есть входные порты i_row и i_col, представляющие собой подготовленные матрицы с необходимым временным смещением, получаемыми из модуля верхнего уровня. Для вывода итоговой матрицы существует порт o_result.
Логика работы модуля: модуль реализует интерконект между необходимым числом процессорных элементов. Так же реализует логику соединений для передачи данных из матриц строк и столбцов на входы процессорных элементов первого столбца и первой строки соответственно.
Описание модуля верхнего уровня
Данный модуль реализует умножение матриц размера $n \times n$ с использованием систолического массива. Параметр $n$ задается в описании модуля.
Интерфейс общения с этим модулем достаточно прост. Помимо тактового сигнала i_clk и сигнала асинхронного сброса i_arst, имеем два входных порта для управления матрицами i_first_matrix и i_second_martix. Данные из этих портов защелкиваются, когда устанавливается входной порт i_ready. Затем входные матрицы преобразуются и передаются в систолический массив для умножения. Когда умножение завершено, выставляется выходной порт o_valid и можно брать значения из порта o_result.
Элементы входных матриц представляют собой 8-битные целые числа. В задачах глубокого обучения и машинного обучения приходится выполнять огромное количество вычислений, однако, как правило, повышенные требования к точности этих вычислений к моделям машинного обучения или нейросетям не предъявляется. При этом возможность использовать в качестве входного типа данных 8-битные целые числа позволяет существенно упростить логику и сократить размер дизайна схемы. Для перехода к целым числам применяется квантование весов модели. При этом произвольные значения между минимальным и максимальным аппроксимируются с помощью целого 8-битного числа.
Для контроля за длительностью вычислений модуль имеет управляющий счетчик. Он рассчитывает $3n - 2$ тактов, сколько требуется для завершения полного матричного умножения. Почему тактов необходимо $3n - 2$? Это классическая формула для систолического массива: $n$ тактов для загрузки данных в массив, $n - 1$ тактов для распространения результатов и еще $n - 1$ тактов для завершения вычислений. Итого: $n\ + \ (n - 1)\ + \ (n - 1)\ = \ 3n - 2$.
Кроме того, благодаря счетчику, генерируется сигнал завершения o_valid. Так же он помогает управлять потоком данных.
Для корректного вычисления входные матрицы должны быть преобразованы. Для этого в модуле описан блок подготовки и преобразования входных данных. Когда устанавливается сигнал i_ready, входные матрицы преобразуются в матрицы row и col, как показано на рисунке 30. Для каждой строки матрицы i_first_matrix положение элементов инвертируется, добавляется нулевое заполнение, и происходит сдвиг строк вправо, чтобы сформировать матрицу row с правильным временным выравниванием. Каждый столбец матрицы i_second_matrix транспонируется (преобразуется в строку) и инвертируется, добавляется нулевое заполнение, и происходит сдвиг строк вправо, чтобы сформировать матрицу col с правильным временным смещением.

Таким образом, итоговая структура модуля представлена на рисунке ниже.

Верификация дизайна
Перед синтезом устройства необходимо проверить корректность его работы. Для этого сначала проведем верификацию модуля процессорного элемента, который реализует операцию умножения со сложением, так как корректность работы этого узла напрямую влияет на результат вычислений. После успешной проверки модуля процессорного элемента будет проведено совместное моделирование всего устройства, дабы проверить взаимодействие всех блоков, управляющую логику и передачу данных между модулями. Это позволит убедиться в правильности дизайна и готовности системы к синтезу.
Для верификации работы процессорного элемента был написан тестбенч на языке SystemVerilog. Для проверки используется подход квазислучайного моделирования. Этот подход состоит в использовании случайных воздействий. Они позволяют обнаружить ошибки, которые могли быть неожидаемы при составлении тестов. Для задания количества случайных проверок в тестовом модуле есть параметр N. В середине проверки устройство сбрасывается при помощи асинхронного сброса. Это позволяет проверить корректность сброса и возможность корректной работы после этого.
Моделирование работы процессорного элемента было проведено с использованием средства для симуляции и отладки Icarus Verilog с открытым исходным кодом, позволяющим быстро проверить работу небольших и средних проектов. Визуализация временных диаграмм, получаемая после работы симулятора Icarus Verilog, сгенерирована при помощи инструмента GTKWave с открытым исходным кодом. Временная диаграмма проверки и вывод тестовой программы представлены на рисунках ниже.


Как видим из рисунков выше, результаты работы процессорного элемента совпадают с ожидаемыми результатами, асинхронный сброс работает корректно, входные данные перемещаются дальше, что соответствует ожидаемому поведению модуля. Таким образом, можно заключить, что модуль процессорного элемента работает корректно.
После проверки корректности работы вычислительного модуля, можно проверить работу всего умножителя в целом. Для верификации работы устройства был создан базовый тестовый стенд на языке С++, состоящий из нескольких функций для реализации проверки умножения квадратных матриц произвольного размера. Схема проверки представляет собой создание случайных матриц размера $N \times N$, подачу этих матриц на тестируемое устройство, ожидание конца вычислений устройства, вычисления матрицы корректного результата и проверки эквивалентности полученных результирующих матриц. Все это повторяется некоторое количество раз, заданное в параметре модуля. Программа должна выводить на экран входные матрицы и ожидаемую матрицу результата. Если произошла ошибка, полученная выходная матрица также будет распечатана, и симуляция завершится.
Моделирование работы процессорного элемента было проведено с использованием средства для симуляции и отладки Verilator с открытым исходным кодом. Этот инструмент преобразует HDL-код в оптимизированную модель на языке С++ для быстрой симуляции. Кроме того, Verilator один из немногих открытых проектов, который имеет довольно полную поддержку стандарта SystemVerilog. Визуализация временных диаграмм, получаемая после работы симулятора Icarus Verilog, сгенерирована при помощи инструмента GTKWave с открытым исходным кодом. Временная диаграмма проверки и вывод тестовой программы для размерности матриц $N\ = \ 3$ представлены на рисунках ниже.


Как видно из рисунков выше, результат работы умножителя совпадают с ожидаемыми результатами.
Временная диаграмма проверки и вывод тестовой программы для размерности матриц $N\ = \ 4$ представлены на рисунках ниже.


Исходя из полученных результатов проведенных моделирований можно заключить, что все модули устройства работают корректно, устройство верно вычисляет произведение двух квадратных матриц различных размеров.
Синтез дизайна на ПЛИС
В данном разделе синтезируем устройство в САПР Quartus II. Также в рамках эксперимента создадим и настроим проект, при помощи которого проверим работу устройства.
Для проверки синтеза установим значение размерности матриц $N\ = \ 100$. На рисунке представлен результат синтеза устройства в САПР Quartus II.

Из рисунка видно, что синтез прошел успешно, указано количество используемых логических и комбинационных элементов на ПЛИС, количество используемых регистров и пинов, а также встроенных 9-битных умножителей.
Рассмотрим максимальную тактовую частоту, с которой возможно запустить эту схему. Для этого воспользуемся инструментом TimeQuest Timing Analyzer, который позволяет анализировать временные характеристики проектов на ПЛИС. Этот инструмент предоставляет несколько моделей, нам интересен наихудший случай с максимальными задержками, поэтому рассмотрим модель Slow 1200mV 85C Model. Оценка максимально возможной тактовой частоты схемы представлена ниже.

Перейдем к эксперименту по проверке работы схемы на отладочной плате с ПЛИС Cyclone IV E EP4CE15F23C8. Для этого создадим проект с soft-процессором NiosII и подключим к нему устройство умножения матриц на основе систолического массива.
Сборка компонентов системы на кристалле для Cyclone IV осуществлена при помощи среды системной интеграции Qsys, предоставляемой в комплекте САПР Quartus II.
Основные компоненты:
- clk_0 – Задает частоту внешнего сигнала синхронизации для системы на кристалле
- sysid_qsys_0 – Обеспечивает хранение и выдачу уникального идентификатора версии, использующегося программой управления при инициализации системы
- оnchip_memory2_0 – Блок, представляющий описание всей доступной оперативной памяти в системе
- jtag_uart_0 – Блок, обеспечивающий прием и передачу информации для отладки SoC
- nios2_qsys_0 – Soft-процессор NiosII
Дополнительными компонентами системы считаются порты входа-выхода, необходимые для передачи данных в модуль верхнего уровня умножителя и обратно.
Результат сборки всех перечисленных элементов в системе Qsys представлен на рисунке ниже.

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

Следующим шагом необходимо написать тестовую программу, которую будет выполнять soft-процессор. Для этого необходимо создать проект во встроенной IDE NiosII Software Build Tools for Eclipse. После создания проекта и генерации BSP файлов, была написана тестовая программа для проверки работы систолического массива.
Для проверки корректности вычислений провели ряд запусков системы. На рисунках ниже представлены результаты выполнения этих запусков.



Как видно из рисунков, систолический массив правильно вычисляет значения итоговой матрицы. Можем сделать вывод, что устройство работает корректно.