D8 Добавьте поддержку RV32E в NPC
Вы уже реализовали NPC, поддерживающий набор инструкций minirv. Однако поскольку minirv содержит только 8 основных инструкций, программы, скомпилированные для minirv, обычно содержат больше инструкций и поэтому выполняются медленнее. Чтобы повысить производительность программ, можно реализовать в NPC больше распространённых инструкций, что позволит компилировать программы для RV32E и тем самым уменьшить количество инструкций в программе. Поэтому нам необходимо «обновить» ISA, используемую NPC, с minirv до RV32E. На этапе A мы ещё раз «обновим» NPC — уже до RV32IMAC.
Следует отметить, что NEMU использует RV32IM, который отличается от RV32E, используемого NPC. Однако RV32IM включает все инструкции, необходимые RV32E, поэтому программы RV32E могут напрямую выполняться на процессорах RV32IM. Таким образом, если программа скомпилирована для RV32E, DiffTest будет работать корректно даже при использовании NEMU с RV32IM в качестве REF.
Создание инфраструктуры для NPC
После выполнения PA вы уже должны понимать важность инфраструктуры. В PA есть четыре основных компонента инфраструктуры: sdb, trace, native и DiffTest. За исключением native, который относится к AM, остальные три компонента можно реализовать и в NPC.
Я уже умею смотреть временные диаграммы. Зачем мне эта инфраструктура?
Чтобы вы не превратились просто в операторов инструментов и не тратили свою жизнь впустую.
Временные диаграммы действительно содержат информацию обо всех сигналах схемы на каждом такте, однако эта информация находится на слишком низком уровне. Она не показывает высокоуровневую семантику, поэтому пользователю приходится самостоятельно просматривать огромное количество данных в поисках ошибки.
На самом деле ошибки, вызванные багами, проявляются на разных уровнях абстракции. Например, неправильно подключённый сигнал в RTL-реализации на уровне выполнения программы может привести к выборке неправильной инструкции, обращению к недопустимой области памяти или возврату из функции по неправильному адресу... Конечно, все эти ошибки в конечном итоге можно обнаружить, анализируя изменения нулей и единиц на временных диаграммах, но разве не лучше сразу увидеть проблему в выводе itrace/mtrace/ftrace? Зачем тратить столько времени на то, с чем инструменты справляются лучше? Более того, если ошибка находится на уровне программного обеспечения, отладка по временным диаграммам лишь создаст дополнительные проблемы.
Научный подход к отладке прежде всего требует понимания того, как программа выполняется на компьютере. Кроме того, необходимо понимать преимущества и недостатки различных инструментов и выбирать подходящий инструмент в зависимости от ситуации. Если рассматривать уровни абстракции компьютерной системы, поведение программы можно наблюдать на разных уровнях:
Программа -> Модуль -> Функция -> Инструкция -> Доступ к памяти -> Шина -> Сигнал
Чем выше уровень, тем легче понять поведение системы, но тем меньше деталей видно; чем ниже уровень, тем точнее доступная информация, но тем сложнее понять общее поведение. Поэтому правильный подход к отладке выглядит следующим образом:
Сначала использовать подходящие программные инструменты, чтобы быстро определить примерное место возникновения ошибки.
Затем использовать временные диаграммы для более детального анализа небольшого участка и найти точное место ошибки.
Реализуйте sdb для NPC
Вам необходимо реализовать в NPC такие функции, как пошаговое выполнение, вывод значений регистров и просмотр памяти. Вычисление выражений и точки наблюдения основаны на возможности выводить регистры и просматривать память. Пошаговое выполнение и просмотр памяти реализовать относительно просто.
Для вывода регистров необходимо получить доступ к регистрам общего назначения в RTL. Это можно сделать двумя способами — выберите любой:
Получить доступ через DPI-C.
Получить доступ к регистрам общего назначения через C++-файлы, сгенерированные Verilator, например
top->rootp->NPC__DOT__isu__DOT__R_ext__DOT__Memory. Конкретное имя C++-переменной зависит от имён модулей и переменных в Verilog. Его можно найти, просмотрев сгенерированные C++ header-файлы. Однако при изменении RTL-кода или версии Verilator имя C++-переменной может измениться, поэтому его придётся вручную синхронизировать.
Добавьте поддержку trace в NPC
Вы уже реализовали itrace, mtrace и ftrace в NEMU; попробуйте реализовать их и в NPC. При реализации itrace обратите внимание на следующие два момента:
Текущую выполняемую инструкцию необходимо получать через DPI-C.
Необходимо подключить библиотеку capstone; подробности можно посмотреть в
nemu/src/utils/filelist.mk.
После того как в среде симуляции будет доступна информация о текущей выполняемой инструкции и обращениях к памяти, реализация mtrace и ftrace не должна вызвать больших трудностей.
Добавьте поддержку DiffTest в NPC
DiffTest — мощный инструмент для отладки процессора. Перед тем как реализовывать в NPC дополнительные инструкции, разумно сначала настроить для него DiffTest. Здесь DUT — это NPC, а REF — NEMU. Для этого необходимо:
Реализовать DiffTest API в
nemu/src/cpu/difftest/ref.c, включаяdifftest_memcpy(),difftest_regcpy()иdifftest_exec(). Кроме того,difftest_raise_intr()предназначена для обработки прерываний и пока не используется.В menuconfig NEMU выбрать shared library в качестве цели компиляции:
Build target (Executable on Linux Native) --->
(X) Shared object (used as REF for differential testing)
Перекомпилировать NEMU. После успешной компиляции будет создан файл динамической библиотеки
nemu/build/riscv32-nemu-interpreter-so.Подключить этот файл динамической библиотеки к среде симуляции NPC с помощью динамической линковки и использовать предоставляемый API для реализации DiffTest. В качестве примера можно обратиться к соответствующему коду NEMU.
Попробуйте корректно запустить программу dummy в NPC с включённым механизмом DiffTest. Чтобы проверить, действительно ли DiffTest работает, можно специально внести ошибку в реализацию инструкции addi в NPC и посмотреть, сможет ли DiffTest обнаружить её.
Обратите внимание, что после этого необходимо снова изменить цель компиляции в menuconfig NEMU и перекомпилировать NEMU в ELF.
Можно ли использовать Spike в качестве REF?
Поскольку реализация NEMU проще, чем Spike, и вы уже лучше знакомы с NEMU, мы всё же рекомендуем использовать собственный NEMU в качестве REF. В будущем вам может понадобиться добавить в REF какие-либо собственные функции для облегчения отладки, и мы не хотим, чтобы код REF воспринимался вами как что-то совершенно постороннее. Однако если вы умеете читать код open-source проектов, можете использовать Spike в качестве REF.
Реализация набора инструкций RV32E
Чтобы компилировать программы для RV32E, необходимо подготовить соответствующую runtime-среду AM. Проект AM уже предоставляет базовый каркас для riscv32e-npc, и вам необходимо немного его доработать. Однако поскольку ранее вы уже настраивали AM для minirv-npc, это не должно быть сложно.
Настройте AM для riscv32e-npc
Необходимо выполнить следующее:
Добавить target
runдляriscv32e-npc, чтобы поддерживались компиляция программы и запуск симуляции одной командой.Реализовать функцию
halt()вriscv32e-npc, чтобы она сообщала среде симуляции NPC о необходимости завершить симуляцию, а также передавала информацию о том, был ли результат выполнения программы корректным.
После подготовки этой инфраструктуры вы сможете удобно реализовывать дополнительные инструкции RV32E в NPC. Вы уже реализовывали эти инструкции в NEMU, однако при их реализации в RTL есть некоторые особенности:
Арифметические и логические инструкции: выполнение этих инструкций в основном осуществляется ALU, с которым вы уже сталкивались в экспериментах по цифровой схемотехнике. В частности:
Сложение и вычитание — при реализации инструкции
addiвы уже реализовали сложение чисел в дополнительном коде. В аппаратуре вычитание в дополнительном коде также можно реализовать через сложение. В RISC-V инструкции сложения и вычитания не должны отдельно определять перенос и переполнение.Логические операции — здесь всё достаточно просто.
Операции сдвига — они также не представляют большой сложности и могут быть реализованы напрямую с помощью операторов.
Операции сравнения — их можно свести к операции вычитания, а результат сравнения определить на основе результата вычитания.
Инструкции перехода: решение о выполнении перехода также можно вычислить через операцию вычитания в ALU.
Как аппаратура различает знаковые и беззнаковые числа?
Попробуйте написать следующую программу:
#include <stdint.h>
int32_t fun1(int32_t a, int32_t b) { return a + b; }
uint32_t fun2(uint32_t a, uint32_t b) { return a + b; }
Затем скомпилируйте её и посмотрите дизассемблированный код:
riscv64-linux-gnu-gcc -c -march=rv32g -mabi=ilp32 -O2 test.c
riscv64-linux-gnu-objdump -d test.o
Чем отличаются эти две функции? Подумайте, почему получается именно так.
Если вы новичок, попробуйте самостоятельно нарисовать архитектурную схему
Если вы только начинаете заниматься проектированием процессоров, попробуйте самостоятельно нарисовать полную архитектурную схему однотактного процессора.
Посмотрите результаты синтеза ALU
Попробуйте синтезировать ALU с помощью проекта yosys-sta, изучите результаты синтеза и ответьте на следующие вопросы:
Мы знаем, что вычитание чисел в дополнительном коде можно реализовать с помощью сумматора, а инструкции сравнения и перехода также по сути используют вычитание. Если напрямую использовать в RTL-коде различные операторы, например
-и<, сможет ли yosys автоматически объединить соответствующие операции вычитания так, чтобы они использовали один и тот же сумматор?В какие схемы yosys синтезирует операторы сдвига
<<и>>?Можно ли улучшить результат, получаемый при прямом синтезе операторов с помощью yosys?
Подсказка: если результаты синтеза для 32-битных данных слишком сложны для анализа, сначала попробуйте посмотреть и проанализировать результаты для 16-, 8- или даже 4-битных данных.
Корректно запустите на NPC все предыдущие тесты
Благодаря созданной инфраструктуре вы должны без особых трудностей реализовать NPC с поддержкой RV32E. После завершения реализации попробуйте перекомпилировать все ранее запускавшиеся тесты для riscv32e-npc, а затем запустить их на NPC.
RV32E не содержит инструкций умножения и деления. Как тогда NPC может корректно выполнять C-программы, содержащие операции умножения и деления?
Причина заключается в модульной структуре набора инструкций RISC-V. gcc может выбирать способ компиляции операций умножения и деления в зависимости от того, присутствует ли в ISA расширение M. Если расширения M нет, gcc преобразует операции умножения и деления в вызовы функций вроде __mulsi3(). Эти функции предоставляют программную реализацию целочисленных арифметических операций, то есть вычисляют результаты умножения и деления с помощью сложения и вычитания. Объявления этих функций можно посмотреть на этой странице, а их реализации находятся в библиотеке libgcc. Обычно libgcc подключается к исполняемому ELF-файлу во время линковки.
Мы уже перенесли в riscv32e-npc некоторые распространённые функции программной реализации операций целочисленного умножения и деления из libgcc. Поэтому можно создавать исполняемые ELF-файлы, которые корректно выполняют умножение и деление даже при отсутствии соответствующих аппаратных инструкций.
Мыслите шире и используйте подходящие инструменты
Некоторые студенты спрашивали: зачем использовать такие инструменты, как Verilator и Makefile, если в ModelSim можно просто нажать кнопку? Потому что отладка исключительно по временным диаграммам — не лучший подход. Для небольших программ вроде cpu-tests вы ещё можете обойтись временными диаграммами, если очень этого хотите. Но по мере увеличения размера программ эффективность такой отладки резко падает: если ошибка возникает после 100 миллионов тактов симуляции, как вы собираетесь искать её на временной диаграмме?
Однако большинство студентов раньше даже не задумывались о том, как повысить эффективность отладки. На самом деле проблема заключается не в недостатке способностей — например, trace по сути можно реализовать обычным printf() — а в ограничивающих представлениях:
Я не учусь на Computer Science, поэтому программное обеспечение меня не касается.
Я занимаюсь аппаратной частью; программную часть можно сделать как-нибудь.
Сейчас компании используют Quartus/Vivado, поэтому использование Verilator в «One Student, One Chip» устарело.
Подобные представления заставляют людей инстинктивно отвергать идеи из области программного обеспечения. Например, в соревновании Loongson Cup успешная загрузка Linux является высшим достижением в части системной демонстрации. Однако, судя по результатам, далеко не каждая участвующая команда способна этого добиться. Мы же считаем, что если научиться использовать правильные инструменты, любой человек сможет за разумное время успешно загрузить Linux на самостоятельно разработанном процессоре. Например, на третьем этапе «One Student, One Chip», который длился 3 месяца, студент электронной специальности, до этого никогда не проектировавший процессоры, самостоятельно смог загрузить Linux Debian на собственном процессоре. На практике даже небольшой скрипт иногда способен значительно повысить эффективность работы. Вместо того чтобы цепляться за традиционные методы, гораздо полезнее понимать, изучать и перенимать передовые подходы из других областей — это сделает вас сильнее.
Если вы новичок, теперь можете посмотреть архитектурные схемы в учебниках
Если вы только начинаете заниматься проектированием процессоров, сравните нарисованную вами архитектурную схему однотактного процессора со схемами из учебников. Подумайте: какая архитектура лучше и почему?
SA
