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

Risc-v

Отечественный микроконтроллер К1986ВК025 на базе процессорного ядра RISC-V для приборов учета электроэнергии

21.10.2020 12:13:27 | Автор: admin
Добро пожаловать в эпоху RISC-V!

Решения на базе открытого стандарта системы команд RISC-V всё чаще появляются на рынке. Уже в серийном производстве микроконтроллеры от китайских коллег, интересные решения предлагает Microchip с FPGA на борту. Растет экосистема ПО и средств разработки для данной архитектуры. Кажущиеся ранее непоколебимыми лидеры все чаще встречаются в объявлениях о перепродаже, а молодые стартапы привлекают многомиллионные инвестиции. Компания Миландр так же ввязалась в эту гонку и сегодня начала поставку заинтересованным предприятиям инженерных образцов своего нового микроконтроллера К1986ВК025 на базе процессорного ядра RISC-V для приборов учета электроэнергии. В общем картинки, характеристики и прочая информация, а также немного хайпа под катом.



Что такое RISC-V? Слава великому Хабру и его пользователям, которые уже написали много статей по данной теме, и избавили меня от этой участи. Посмотрите RISC-V с нуля. Так же можно ознакомиться с уже серийными изделиями от GigaDevice. А также с критикой и описанием недостатков RISC-V архитектуры со стороны сотрудников ARM.

Микроконтроллер К1986ВК025 на базе ядра RISC-V это второе поколение Миландровских микроконтроллеров для счетчиков электроэнергии. Первое поколение микросхемы К1986ВК2x разработаны на базе процессорного ядра ARM Cortex-M0 и выпускаются уже более 5 лет. На их основе сейчас выпускаются счетчики Милур.

Но время не стоит на месте и новые правила рынка диктуют новые требования к микросхемам. Нужно сделать больше функций новые требования по минимальному функционалу приборов учета. Нужно попасть в требуемую цену, так как конкурировать придется с монстрами типа TI и NXP. Также, если вы следите за этим направлением, то знаете, что новые интеллектуальные счетчики электроэнергии могут отключать электричество ограничивать потребление электроэнергии. А это значит новая система должна обеспечивать и информационную безопасность, чтобы злоумышленники не могли отключить или наоборот включить электричество по своему усмотрению. Все это привело к появлению новой микросхемы К1986ВК025, о которой сейчас и расскажем.

1. Основные характеристики К1986ВК025



Процессорное ядро RISC-V (BМ-310S CloudBEAR)
Тактовая частота 60 МГц
Напряжение питания (основное) 2,2...3,6В
Напряжение питания (АЦП) 3,0...3,6В
Напряжение питание (батарейное) 1,8...3,6В
Объем памяти программ Flash 256+8 Кбайт
Объем памяти ОЗУ 112 Кбайт
Объем однократно-программируемой ПЗУ 16 Кбайт
Метрологический измерительный АЦП сигма-дельта 24 бита, 7 каналов
Аппаратный блок вычисления показаний потребляемой энергии
Инструментальный АЦП 10 бит с датчиком температуры
Интерфейсы 5xUART, 3xSPI, 1хI2C
Число пользовательских выводов 55
Батарейный домен объемом 512 байт с часами реального времени и детектором фиксации проникновения
4 блока 32-разрядных таймеров с 4 каналами захвата событий и ШИМ
Сторожевой таймер
Блок подсчета CRC с изменяемым полиномом
Блок поддержки вычисления симметричных криптографических алгоритмов
Блок генератора случайных чисел
Блок детектора изменения тактовой частоты
Блок детектора изменения напряжения питания (основного и батарейного)
Блок оптического детектора
Блок генерации шума в цепи питания
Защитная экранная сетка
Интерфейс отладки JTAG
Тип корпуса QFN88 (10 х 10 мм)
Рабочая температура от минус 50С до +85С

Структурная схема
image

2. Процессорное ядро


Как уже было отмечено, сердцем микросхемы является 32 битное процессорное ядро RISC-V (в конфигурации RV32IMC) с обозначением BM-310, которое было разработано нашими хорошими друзьями из компании CloudBEAR. Это не единственное их процессорное ядро. Ребята предлагают целую линейку ядер, начиная от небольших микроконтроллерных ядер и заканчивая высокопроизводительными 64 битными многопроцессорными кластерами.



Компания Миландр ведет разработку новых продуктов на базе ядер из всех весовых категорий портфолио CloudBEAR. В кремнии сейчас доступны только К1986ВК025 на базе ядра BM-310. Но и другие продукты, в том числе на базе 64 битных ядер скоро увидят свет. Ядро BM-310 это 32 битное RISC-V ядро с трехстадийным конвейером, с возможностью выполнения операции умножения за два такта. Поддержка операций с плавающей запятой в данной версии микросхемы не была реализована (хотя ядро позволяет это реализовать). Производительность ядра BM-310 на тесте CoreMark составляет 3,0 CoreMark/МГц. Таким образом его можно сравнить с ARM Cortex-M3. При этом ядро в новой микросхеме занимает всего 0,3 мм2. А с учетом того, что большинство задач по вычислению параметров потребления электроэнергии выполняются аппаратно контроллером метрологического АЦП, то основная вычислительная мощность ядра может использоваться для коммуникационных задач в приборе учета.

3. Цена


Цена один из основных критериев для микросхемы. Основной вклад в стоимость микросхемы вносит кристалл. Чем больше его площадь тем дороже, чем более сложная технология изготовления тем он тоже дороже. Но при этом, чем более сложная технология, тем на меньшей площади можно реализовать требуемый функционал, а значит будет дешевле. В общем выбор технологии не всегда очевиден. Микросхемы первого поколения К1986ВК2x были разработаны на 180 нм. Примерно треть кристалла относилось к аналоговой части (собственно сами сигма-дельта АЦП, питание, IO) и при переходе на более тонкие процессы эта часть практически не уменьшается. Но в соответствии с новыми требованиям объем функционала должен быть увеличен практически в 4 раза. В новой микросхеме реализовано 256 Кбайт Flash против 64К, ОЗУ 112Кбайт против 16К, 5 блоков UART против 2, 3 блока SPI против 1 и еще криптография В общем, после долгих прикидок и расчетов было принято решение, что если уделить особое внимание площади, то на технологии 90 нм можно попасть в требуемый коридор стоимости, на технологиях 65 нм или ниже было бы легче, но бОльшие затраты на разработку в целом сделали бы проект более рискованным по финансам.

В результате мы получили действительно компактный кристалл, который по площади более чем в 2 раза меньше предыдущего. И хотя изначально были и более амбициозные планы уменьшить кристалл до 8 мм2, полученный результат тоже очень хорош. Именно из-за амбиций форма кристалла получилась прямоугольной, а не квадратной. Верхняя часть кристалла отдана под аналоговые блоки, и ее топологию прорисовывали отдельно под изначально планируемые размеры, а когда стало понятно, что цифра не влезет, то кристалл стал расти в прямоугольник. Та же картина была и с микросхемами первого поколения. Постоянство признак мастерства.

4. Метрологический АЦП


Если процессорное ядро это сердце микроконтроллера, то метрологическое АЦП это его мозг, так как именно он определяет предназначение микросхемы. В микроконтроллере реализован блок из 7 каналов 24 битных АЦП. Все каналы разбиты на три пары F0-F2 (канал напряжения и канал тока) для 3-х фазной сети и еще одного независимого канала тока (отнесен к F0). Каждый из 7 каналов оцифровывает входной сигнал с выходной частотой отсчетов до 16 кГц. Кроме этого, в каждой паре каналов F0-F2 реализована возможность рассчитывать среднеквадратические значения тока/напряжения, вычислять активную и реактивную мощности, вычислять потребленную активную и реактивную энергию, частоту сигнала в каналах напряжения, превышение пикового значения, падение сигнала ниже установленного уровня. Эти дополнительные блоки позволяют снизить нагрузку на процессор, что в свою очередь снижает потребляемую мощность всего кристалла. Так же каждый АЦП имеет независимый канал DMA, обеспечивая возможность сохранения данных в ОЗУ без участия процессора.

Список основных параметров и возможностей блока сигма-дельта АЦП:
7 независимых АЦП с выходной частотой отсчетов 4/8/16 кГц (4 канала тока и 3 канала напряжения). Эти каналы образуют 3 блока для измерения параметров каждой фазы F0-F2.
В блоке каналов F0 реализуем автоматический выбор канала тока (который имеет максимальное значение) для последующих расчетов мощностных характеристик. Если разница токов превышает 6%, то формируется прерывание. Кроме этой функции в остальном блоки F0-F2 идентичны.
Все каналы АЦП имеют независимые калибровочные коэффициенты наклона характеристики.
Каждый канал тока имеет независимый интегратор.
В каждом блоке АЦП (F0-F2) независимо рассчитывается период сигнала по каналу напряжения. Количество периодов, в течение которого рассчитывается эта величина, можно задавать равным 1/2/4/8/16/32/64/128 периодам.
В каждом блоке есть проверка на пропадание периодического сигнала в канале напряжения.
В каждом блоке проверяется просадка напряжения ниже заданного уровня, а также превышения сигнала в каналах тока и напряжения установленного лимита.
Есть возможность скорректировать фазы сигналов в каналах напряжения с точностью до 0,02%.
Вычисляются среднеквадратические, квадрат среднеквадратических значений токов и напряжений, а также их независимая калибровка.
При вычислении активной и реактивной энергии значение накопленной энергии в течение периода сохраняется в отдельных регистрах (для положительной и отрицательной энергии).
Вычисляются полная мощность и полная энергия.
Вычисляется сдвиг фаз по отношению к фазе 0.

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

5. Информационная безопасность


Как уже было отмечено ранее новые интеллектуальные счетчики электроэнергии должны уметь отключать потребителя от сети по соответствующей команде. Так же передача показаний потребленной энергии осуществляется автоматически по различным каналам связи. А это значит что вопросы информационной безопасности будут стоять особенно остро. В Постановление Правительства РФ от 19.06.2020 N 890 "О порядке предоставления доступа к минимальному набору функций интеллектуальных систем учета электрической энергии (мощности)" (вместе с Правилами предоставления доступа к минимальному набору функций интеллектуальных систем учета электрической энергии (мощности)) сформулированы минимальные функциональные требования для приборов учета электроэнергии. Но в части информационной безопасности они пока не сформулированы (ожидаются до конца 2020 года). Поэтому при разработке микросхемы мы опирались на требования ФСБ к средствам криптографической зашиты информации для некорректируемых регистраторов (СКЗИ НР). Реализованный функционал на наш взгляд избыточен, а если где-то и что-то и не предусмотрели, то можно реализовать это программно. Но в любом случае в части обеспечения специальных требований микросхема находится на уровне карточных и паспортных чипов. Поэтому в данной микросхеме реализуются все требования необходимые для бытовых электросчетчиков как в части собственно задач измерения,, она может найти свое применение и в других областях тахографах, электронных пломбах, идентификации и аутентификации элементов IoT. Всю спецуху в микросхеме можно разделить на две части криптография и инженерная защита.

5.1. Криптография


На микросхеме реализовано всё для защиты информации по ГОСТ Р 58940-2020 (СПОДЭС), в том числе:
Блоки сопроцессоров для поддержки блочных шифров Кузнечик, Магма и AES;
Блок генератора случайных чисел;
Блок вычисления CRC по произвольному полиному;
Блок специальной энергозависимой памяти ключевой информации с батарейным питанием
Однократно программируемая ПЗУ первоначального загрузчика, в которой реализуется уникальная идентификация каждой микросхемы.
Симметричная криптография реализуется на базе сопроцессоров, которые ускоряет самые тяжёлые операции. Это позволяет получить скорость шифрования с пиковой производительностью до 10 Мбит/с. При необходимости алгоритмы ассиметричной криптографии реализуется программным путем. В общем про идентификацию, аутентификацию шифрование, обновление прошивок нужно писать отдельную статью, что и сделаем потом.

5.2. Инженерная защита


Для затруднения различных инженерных методов воздействия как на сам счетчик электроэнергии, так и на отдельно микросхему реализованы специальные методы инженерной защиты:
3 вывода для детекторов проникновения (электронные пломбы)
Блок детектора изменения тактовой частоты
Блок детектора изменения напряжения питания
Блок оптического детектора
Блок генерации шума в цепи питания
Защитная экранная сетка
Защита от несанкционированного считывания памяти

Выводы для детекторов проникновения это три отдельных вывода микроконтроллера через которые фиксируются сигналы от внешних электронных пломб, даже в случае отсутствия питания. Таким образом, какие-либо попытки механического воздействия на счетчик будут обнаружены и зафиксированы. Кроме механизмов защиты корпуса прибора реализуется методы защиты уже самой микросхемы от более изощренных атак.
Для исключения ситуации с разгоном или наоборот торможением тактовой частоты процессора реализован детектор изменения частоты, который непрерывно сравнивает внешний источник тактирования с внутренним RC генератором. И при возникновении отклонений выходящих за заданные границы фиксирует это событие или может переключить микросхему на внутренний (реализован на кристалле) аварийный источник тактирования. Гличивание сигнала тактирования парируется системой фильтрации входного сигнала. Для корректного запуска микросхем по включению основного питания, а так же обнаружения факта выхода питания из допустимых диапазонов реализован блок детектора напряжения. Также блок обеспечивает контроль уровня заряда батарейки, на которой работает микросхема при отсутствии основного питания. Таким образом исключаются какие-либо манипуляции или попытки сбить работу микросхемы через питающие напряжения. Для исключения утечки ключевой информации по паразитным каналам, например цепям питания (подробнее можно почитать тут ) в микросхеме реализован блок зашумления основного питания с помощью псевдослучайного генератора. Блок формирует случайным образом изменяемое дополнительное потребление, маскируя основной профиль потребления. Для противодействия более серьезным полу-инвазивным и инвазивным атакам (атака на микросхему когда есть доступ к кристаллу микросхемы или даже отдельным цепям кристалла) реализован оптический детектор. Так как обычно кристалл микросхемы в корпусе, то доступ света к нему невозможен. Если же корпус микросхемы вскрыт и на кристалл попадает свет, то оптический датчик сформирует сигнал тревоги. Если же злоумышленник прошел еще дальше, то для ограничения доступа к внутренним цепям микросхемы в верхних слоях топологии кристалла реализована защитная стека, в которой перемежаются цепи земли, питания и информационная цепь, по которой передается случайный полином. Кстати именно по этому в верхних картинках приведен рисунок топологии из САПР, а не реальная фотография кристалла.
Так как реальная фотография кристалла выглядит вот так.



Или чуть поближе



Для сравнения как защитная сетка выглядит в чипе для карточек от ST (серия ST23).


В случае физического нарушения целостности сетки так же формируется сигнал тревоги. Программное обеспечение должно контролировать все сигналы тревоги и в случае их возникновения фиксировать факт атаки. Так же микросхемы может быть настроена так, что бы криптографическая ключевая информация была автоматически стерта, при обнаружении фактов атаки. Все это требует обеспечения доверенности ПО. Для пользовательской программы в микросхеме реализовано 16 Кбайт однократно программируемой ПЗУ (по принципу анти-фьюз) и 256+8 Кбайт перепрограммируемой Flash. Запуск микросхемы происходить из встроенной однократно программируемой памяти. Изначально при изготовлении микросхем, данная память чистая, и для серийный изделий загрузка стартового доверенного загрузчика будет происходит при тестировании и отбраковке. Это позволяет обеспечить уникальную идентификацию каждой микросхемы, гарантировать целостность метрологического и криптографического ПО и являющаяся корнем доверия для пользовательской программы. А так же при необходимости обеспечить защиту от считывания пользовательской программы во Flash. На время исследования для инженерных образцов в стартовую память записан типовой загрузчик для серии 1986ВЕ. При необходимости может быть выпущена отдельная партия микросхем с стартовым загрузчиком адаптированым под задачи потребителя.

6. Средства разработки и документация


Предварительный вариант спецификации доступен по ссылке.

Для первоначального знакомства с микроконтроллером разработана демонстрационная плата. image

Это плата предназначена для прототипирования приборов учета, большое число ВЧ разъемов предназначено для подключения измерительных трансформаторов. Это позволяет не завязываться на какой-то конкретный их тип, и легко подключить именно те трансформаторы или шунты, которые применяют разработчики приборов учета. Так же это позволяет подключить различные измерительные приборы для снятия точностных характеристик АЦП. Кроме того отсутствие высокого напряжения (220В) на плате снимает с нас ответственность, если кого то стукнет. Для более массового продвижения микросхемы ведется разработка миниатюрной тестовой платы в формате arduino uno или mega.

6.1. Eclipse+GCC


Разработка программ может осуществляться в среде на базе Eclipce с компилятором GCC и отладкой через OpenOCD, GDB. Для отладки в среде Eclipse подходят классические отладчики J-Link и их клоны. Так же подходят отладчики производства компании Миландр.

6.2. IAR


Так же разработку можно осуществлять в более привычной для многих среде IAR Embedded Workbench, которая включает средства отладки, компиляции и оптимизации. Для отладки используются либо фирменный шнурок I-JET for RISC-V, либо если заказать Evaluation Kit for RISC-V, то вместе с ним вы получите I-jet Lite debug probe (который поддерживает в том числе и ARM), а так же 30 дневную лицензию на саму среду.

Более подробную информацию по настройке и запуску сред IAR и Eclipse для работы с микроконтроллером К1986ВК025 можно найти тут.

7. Что нужно для получения образцов


Это посылочки, тем кто ждет эти микросхемы, а так же тем, кто наоборот


Если вы хотите получить образцы и платы для ознакомления, то необходимо обратиться в отдел маркетинга Миландра. В приоритете, конечно фирмы из электроэнергетического сектора, но в любом случае попытаемся всем помочь. После проведения всего комплекса испытания серийный выпуск микроконтроллеров будет запущен в 21 году, так же данная микросхема будет включена в реестр продукции соответствующей 719 Постановлению Правительства.

Заключение


Вчерашние смелые идеи уже сегодня воплощаются в технических решениях и становятся нормой жизни. Мир, в котором мы с Вами живем, не позволяет стоять на месте. Появление описанной выше микросхемы имеет огромное значение для всего сообщества RISC-V. Я поздравляю всех, кто причастен к разработке данной микросхемы с ее рождением. А так же, надеюсь, что потребители (разработчики приборов учета) воспримут ее положительно, и невзирая на скепсис по отношению к отечественным микросхемам, я уверен мы можем конкурировать с иностранными компаниями.

Добро пожаловать в эпоху RISC-V!
Подробнее..

Перевод Новый высокопроизводительный одноплатный компьютер BeagleV построенный на открытой архитектуре RISC-V за 119

16.01.2021 02:07:06 | Автор: admin
BeagleV SBC

До недавнего времени платы построенные на процессоре RISC-V такие как Kendryte K210, невозможно было использовать для высоко ресурсоемких задач, т.к. производительность конечных была очень низкой. В частности одноплатный компьютер XuanTie C906 на базе Allwinner RISC-V можно было использовать только для нишевых небольших задач, например в качестве камеры из-за отсутствия аппаратного графического ускорителя и наличия только 256 МБ оперативной памяти. Можно использовать одноплатные компьютеры такие как SiFive HiFive Unmatched или PolarBerry, но стоимость конечной системы составит сотни, а то и более тысячи долларов, что является весьма дороговато.

Таким образом, очевидна необходимость в недорогом одноплатном компьютере на Linux, построенным на высокопроизводительном процессоре RISC-V. И есть отличная новость, фонд BeagleBoard.org, Seeed Studio и китайский фабричный производитель микросхем Starfive объединились для разработки и производства одноплатного компьютера BeagleV (Beagle Five) на базе двухъядерного процессора StarFive JH7100 (ядро SiFive U74 RISC-V) с цифровым сигнальным процессором(DSP) для компьютерного зрения, ускорителем машинного обучения (Deep learning) NVDLA и нейронной сети для искусственного интеллекта (AI).

Технические характеристики BeagleV:
  • Процессор: SoC StarFive JH7100 Vision SoC с:
    • двухядерным ядром RISC-V U74 с 2MB L2 cache @ 1.5 GHz
    • Vision DSP Tensilica-VP6 для компьютерного зрения
    • NVDLAEngine 1 core (configuration 2048 MACs @ 800MHz 3.5 TOPS)
    • Ускоритель нейронной сети (1024MACs @ 500MHz 1 TOPS)
    • VPU H.264/H.265 decoder up to 4Kp60, dual-stream decoding up to 2Kp30
    • JPEG encoder/decoder
    • Цифровым сигнальным процессором(DSP) для обработка аудио потока

  • Оперативная память: 4GB или 8GB LPDDR4
  • Пользовательская память: слот MicroSD
  • Видео:
    • 1x HDMI порт с поддержкой разрешения до 1080p30
    • 1x MIPI DSI интерфейс с поддержкой разрешения до 4Kp30
    • MIPI-CSI TX для вывода видео после обработки модуля искусственного интеллекта (AI)

  • Камера:Двухканнальный процессор обработки изображений (ISP) с поддержкой разрешения до 4K @ 30FPS,2 x MIPI-CSI Rx
  • Звук: аналоговый вывод (3.5 mm TRRS jack)
  • Связь: 1x Gigabit Ethernet, 2.4 GHz 802.11b/g/n WiFi 4, и Bluetooth 4.2
  • USB: 4 порта USB 3.0
  • Expansions Header: 40-контактный разъем совместимый с Raspberry Pi 3, включает GPIO с 28 x GPIO, I2C, I2S, SPI, UART
  • Безопасность: поддержка TRNG и OTP
  • Разное: кнопка питания и сброса
  • Питание: 5V/3A через порт USB Type-C
  • Размер: 9070 (приблизительно) мм.


BeagleV block diagram

Основываясь на сведениях о ядре SiFive U74, производительность должна быть эквивалентна ядру Cortex-A55. Cortex-A55 является приемником Cortex-A53 на котором построен процессор Allwinner A64. По заявлению компании ARM, ядро Cortex-A55 имеет на 18% большую производительность и на 15% большую энергоэффективность. Плата Banana Pi BPI-M64 содержит 4-ядра Cortex-A53 с частотой 1.2 GHz, а BeagleV будет содержать два ядра RISC-V U74 с частотой 1.5 GHz, что говорит о практически идентичной производительности.

Поэтому производительность одноплатного компьютера BeagleV будет не такой высокой по сравнению с платами на процессоре Arm, но за счет наличия модуля ускорителя нейронной сети и цифрового зрения, BeagleV будет конкурентоспособным по сравнению с другими платами с искусственными интеллектом, такими как Coral Dev Board mini.

Первая партия поставок плат запланированная на март будет без графического процессора, но следующая которая должна будет выпущена в сентябре будет с графическим процессором от Imagination Technologies GPU.

Поддержка BeagleV будет в основной ветки mainline Linux, образ ОС будет построен на версии Debian. Так будет поддержка Fedora и FreeRTOS. Одноплатные компьютеры на RISC-V являются открытым аппаратным обеспечением(open-source hardware) как и другие платы организации BeagleBoard.org, это означает что файлы дизайна оборудования(hardware design files), прошивки(firmware) и программное обеспечение будут общедоступными.

Конечная цена BeagleV составит $119 с 4GB RAM и $149 с 8GB RAM, но первая партия будет выпущена только с 8GB RAM. Энтузиасты и гики могут заполнить заявку на получение платы на сайте. Так же можно оформить предзаказ платы в Seeed Studio. Дополнительную информацию можно найти на странице продукта.

Ссылки:


Подробнее..

Новостной дайджест событий из мира FPGAПЛИС 007 (2020_12)

31.12.2020 12:04:32 | Автор: admin

Здравствуйте друзья.


Продолжаем публиковать новости из мира FPGA/ПЛИС.


В 2020 опубликовано 238 новостей, запущены FPGA стримы и проведена первая отечественная FPGA конференция. Подборка за декабрьи ссылки на все упоминания под катом.




Ссылки:


  • Краткий 90-минутный видео-обзор прошедших событий в FPGA отрасли здесь
  • Материалы прошедшей FPGA конференции и ссылки на материалы здесьи отчет на хабре
  • Записи FPGA стримов здесь


PS: Стримы проводят ребята вот с этого проекта. Подкидывайте им идеи для стримов или присоединяйтесь в качестве гостя, если надумаете рассказать что-то интересное из мира FPGA/ПЛИС



FPGA декабрь новости



Зимний хакатон от QuickSilicon


Выполнение MicroBlaze приложений на PSU DDR в Vitis


Инструменты FPGA с открытым исходным кодом и поддержка Renode для MCU Core-V


Решаем проблему разбиения чисел на PYNQ


Следующий уровень светодиодного стрима


Небольшой бесплатный курс по VHDL на Udemy


Модельное проектирование ПЛИС и ASIC в контексте функциональной безопасности


Школа FPGA/SoC для применения в атомной промышленности и связанной с ней приборостроении


UVM обновилась согласно стандарту IEEE 1800.2-2020


Доступны материалы конференции Synopsys Verification Day 2020


Silexica запускает первый коммерческий плагин для Vitis HLS


Реализация интерпретатора CHIP-8 на Verilog


Установка Cocotb на Windows 10 для повышения производительности проверки проектов ПЛИС


ECPIX-5 современная отладочная плата на Lattice ECP5


Релиз Sigasi Studio 4.10


Глубокое обучение на FPGA


Конкурс от QuickLogic совместно с SensiML


Qomu MCU + eFPGA Development Kit, который помещается внутри USB-порта


Вебинар: Верификация с использованием OSVVM


Светодиодно-ленточный релакс FPGA стрим сегодня в 20:00 Мск


Ускорения отладки RTL для ПЛИС


О разработке на Плис в соответствии с DO254


Самоконфигурируемая трехмерная мультиПЛИСовая адаптивная платформа


Microchips Анонсировала доступность PolarFire для космического применения


Опубликованы материалы конференции Nokia и Intel


FPGA конференция в Сколково


Intel Open FPGA Stack Простая разработка пользовательских платформ


Изучаем Vivado Methodology Report


Все воркшопы Адама Тейлора в этом году


HBM2 и тензорные блоки ключевые особенности Intel FPGA


Запись субботнего стрима GoWin первое знакомство доступна на Youtube


Обзор научных работ, связанных с FPGA


Компания Samsung разработала прототип голографического дисплея с использованием FPGA


Повышение производительности разработки с Vivado и SystemVerilog


Новые возможности VHDL2019


FPGA конференция и хакатон от Intel и Nokia


Саммит разработчиков oneAPI 2020


Вебинар: Accelerating Data Channels to 112 Gbps PAM4: A Case Study in Real-World FPGA Implementation


Освоение DPC++ для программирования гетерогенных систем с использованием C++ и SYCL


Реализация глубоких нейронных сетей на ПЛИС


Делаем UART на HLS


QuickLogic присоединяется к партнерской программе Samsung SAFE IP



Это был заключительный новостной дайджест в этом году. Надеюсь, что среди опубликованных подборок за год вы смогли найти для себя что-то новое. Мы продолжим делать новостные дайджесты по тематике FPGA, давая вам возможность быть в курсе последних новинок и событий в мире программируемой логики.


Подробнее..
Категории: Новости , Дайджест , Intel , Fpga , Gowin , Плис , Rtl , Lattice , Risc-v , Xilinx , Hdl , Migen , Litex

Перевод Сравнение векторных расширений ARM и RISC-V

20.05.2021 22:11:13 | Автор: admin

Сравнение векторного расширения RISC-V (RVV) и масштабируемого векторного расширения ARM (SVE/SVE2).

Микропроцессоры с векторными командами ожидает большое будущее. Почему? Беспилотные автомобили, распознавание речи, распознавание образов, всё это основано на машинном обучении, а машинное обучение на матрицах и векторах.

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


Увеличение производительности остановилось, что делает необходимым распараллеливать вычисления различными способами, либо с помощью многоядерности, либо с помощью векторизации, либо с помощью исполнения не в порядке очереди (out-of-order).Увеличение производительности остановилось, что делает необходимым распараллеливать вычисления различными способами, либо с помощью многоядерности, либо с помощью векторизации, либо с помощью исполнения не в порядке очереди (out-of-order).

Сейчас мы придумали тысячи умных способов получить большую производительность, будь то создание многоядерных процессоров, внеочередное (out-of-order) исполнение, более совершенное предсказание переходов и SIMD-команды.

Все эти способы основаны на одной центральной идее: пытаться различными способами распараллеливать работу. Когда вы выполняете какие-то вычисления в цикле над массивом элементов, у вас появляется возможность параллелизма данных. Этот цикл при достаточно умном компиляторе может быть превращён в последовательность SIMD или векторных команд.

SIMD-инструкции, в отличие от SISD-инструкций, каждая инструкция (зелёный цвет) обрабатывает множество независимых потоков данных (синий цвет).SIMD-инструкции, в отличие от SISD-инструкций, каждая инструкция (зелёный цвет) обрабатывает множество независимых потоков данных (синий цвет).

SIMD-команды, такие, как Neon, MMX, SSE2 и AVX замечательно сработали в мультимедийных приложениях, в таких вещах, как кодирование видео и т.п. Но нам нужно получить большую производительность во многих областях. Векторные команды предлагают большую гибкость в превращении почти любого цикла в векторные команды. Однако есть много различных способов это сделать.

Я описал векторные команды RISC-V здесь:RISC-V Vector Instructions vs ARM and x86 SIMD.

Позже я описал векторные команды ARM:ARMv9: What is the Big Deal?.

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

Это заставило меня обнаружить, что ARM и RISC-V следуют принципиально разным стратегиям. Стоит написать об этом, потому что это одна из моих любимых тем. Я люблю простые, элегантные и эффективные технологии:The Value of Simplicity.

Векторное расширение RISC-V по сравнению с ARM SVE это элемент элегантности и простоты.

Проблема с масштабируемыми векторными командами ARM (Scalable Vector Instructions, SVE)

В процессе изучения SVE, было неочевидно, почему это так трудно для понимания, но когда я взял книгу по RISC-V и перечитал главу по векторному расширению, это стало ясно.

Честно говоря, ARM является большим шагом вперёд по сравнению с большим сложным и беспорядочным ассемблером Intel x86. Давайте не будем про это забывать. Также мы не можем пройти мимо того факта, что ARM не молодая платформа, и содержит много легаси. Когда мы имеем дело с ARM, у нас есть три различных набора команд: ARM Thumb2, ARM32 и ARM64. Когда вы гуглите руководства и пытаетесь их читать, возникает ряд проблем. Люди не всегда понимают, какой набор команд изучать.

Инструкции Neon SIMD имеют две разновидности: 32- и 64-битные. Проблема, конечно, не в ширине слова, а в том, что для 64-битной архитектуры ARM перепроектировал весь набор команд и изменил многие вещи, даже соглашение об именовании регистров.

Вторая проблема в том, что ARM большой. Система команд содержит свыше 1000 команд. Сравните с базовым набором RISC-V, в котором всего лишь 48 команд. Это означает, что читать ассемблер ARM не так просто. Посмотрим на команду SVE:

LD1D z1.D, p0/Z, [x1, x3, LSL #3]

Здесь делается много. Если у вас есть опыт в ассемблере, вы можете догадаться, что префиксLDозначаетLoaD. Но что означает1D? Вы должны это выяснять. Дальше вы должны выяснить, что означают странные суффиксы имён регистров, такие как .Dand/Z. Дальше вы видите скобки[]. Вы можете догадаться, что они составляют адрес, зачем там странная запись LSL #3, что означает логический сдвиг влево (Logic Shift Left)три раза. Что сдвигается? Все данные? Или только содержимое регистраx3? Это снова нужно смотреть в справочнике.

Команды ARM SVE содержат множество концепций, не являющихся очевидными, от которых голова идёт кругом. Мы сделаем глубокое сравнение, но сначала скажем несколько слов о RISC-V.

Красота векторного набора команд RISC-V

Обзор всех команд векторных расширений RISC-V (RVV) помещается на одной странице. Команд немного, и, в отличие от ARM SVE, они имеют очень простой синтаксис. Вот команда загрузки вектора в RISC-V:

VLD v0, x10

Команда загружает векторный регистрvданными, находящимися по адресу, который хранится в обычном целочисленном регистреx10. Но сколько данных загружается? В наборе команд SIMD, таком, как ARM Neon это определяется именем векторного регистра.

LD1 v0.16b, [x10]  # Load 16 byte values at address in x10

Есть другой способ сделать это. Такой же результат достигается таким образом:

LDR d0, [x10]    # Load 64-bit value from address in x10

Эта команда загружает младшую 64-битную часть 128-битного регистраv0. Для SVE2 у нас есть другой вариант:

LD1D z0.b, p0/z, [x10] # Load ? number of byte elementsLD1D z0.d, p0/z, [x10] # Load double word (64-bit) elements

В этом случае регистр предикатаp0определяет в точности, сколько элементов мы загружаем. Еслиp0 = 1110000, мы загружаем три элемента.v0 это 128-битная младшая частьz0.

Регистры имеют одинаковые имена?

Причина этому в том, что регистры d,vиz находятся в одной ячейке. Давайте поясним. У вас есть блок памяти, называемый регистровый файл в каждом CPU. Или, если быть более точным, в CPU расположено много регистровых файлов. Регистровый файл, это память, в которой расположены регистры. Вы не можете получить доступ к ячейкам памяти в регистровом файле, так же как в обычной памяти, вместо этого вы обращаетесь к области памяти, используя имя регистра.

ARM floating point registers are overlapping in the same register file (memory in CPU holding registers).ARM floating point registers are overlapping in the same register file (memory in CPU holding registers).

Различные регистры могут отображаться на одну и ту же область регистрового файла. Когда мы используем скалярную операцию с плавающей точкой, мы используем часть векторных регистров. Рассмотрим 4-й векторный регистр, и рассмотрим, как ещё он используется:

  • z3 регистр SVE2 переменной длины.

  • v3 младшие 128 бит z3. Регистр Neon.

  • d3 младшие 64 битаv3.

  • s3 младшие 32 битd3

RISC-V, однако, устроен не так. Векторные регистры RISC-V находятся в отдельном регистровом файле, не разделяемом с регистрами скалярной плавающей точки.

  • x0-x31 скалярные целочисленные регистры.

  • f0-f31 скалярные регистры с плавающей точкой.

  • v0-v31 векторные регистры. Длина не зависит от ISA.

Сложность векторных команд ARM

Я только поцарапал поверхность векторных команд ARM, потому что их очень много. Просто найти, что делает команда загрузки Neon и SVE2, занимает много времени. Я просмотрел много документации ARM и записей в блогах. Сделать то же самое для RISC-V очень просто. Практически все команды RISC-V можно разместить на двойном листе бумаги. У него есть только три команды загрузки вектораVLD,VLDSиVLDX.

Я не могу сказать, сколько этих команд у ARM. Их очень много, и я не собираюсь становиться профессионалом по программированию ARM. на ассемблере.

Как ARM и RISC-V обрабатывают вектора переменной длины

Это довольно интересный вопрос, так как ARM и RISC-V используют существенно различные подходы и я считаю, что простота и гибкость решения RISC-V просто блестящая.

Вектора переменной длины в RISC-V

Чтобы начать обработку векторов, вы делаете две вещи:

  • VSETDCFG Vector SET Data ConFiGuration. Устанавливает битовый размер каждого элемента, тип, который может быть вещественным, знаковым целым или беззнаковым целым. Также конфигурация определяет, сколько векторных регистров используется.

  • SETVL SET Vector Length. Устанавливает, сколько элементов содержит вектор. Максимальное количество элементов, которое вы не можете превысить MVL(max vector length).

Регистровый файл RISC-V может быть скофигурирован так, чтобы иметь меньше 32 регистров. Может быть, например, 8 регистров или 2 регистра большего размера. Регистры могут занимать весь объём регистрового файла.Регистровый файл RISC-V может быть скофигурирован так, чтобы иметь меньше 32 регистров. Может быть, например, 8 регистров или 2 регистра большего размера. Регистры могут занимать весь объём регистрового файла.

И здесь всё становится интереснее. В отличие от ARM SVE, я могу разделить файл векторных регистров именно так, как я хочу. Пусть регистровый файл имеет размер 512 байт. Я могу теперь объявить, что я хочу иметь два векторных регистра, по 256 байт каждый. Далее я могу сказать, что я хочу использовать 32-битные элементы, другими словами, элементы по 4 байта. Получаем следующее:

Два регистра: 512 байт / 2 = 256 байт на регистр256 байт / 4 байта на элемент = 128 элементов

Это означает, что я могу складывать или умножать 128 элементов просто одной командой. В ARM SVE вы этого сделать не можете. Количество регистров фиксировано, и память аллоцирована для каждого регистра. И RISC-V, и ARM позволяют вам использовать максимум 32 векторных регистра, но RISC-V позволяет вам отключать регистры и отдавать используемую ими память оставшимся регистрам, увеличивая их размер.

Вычисление максимальной длины вектора (Max Vector Length, MVL)

Давайте посмотрим, как это работает на практике. Процессор, конечно, знает размер регистрового файла. Программист этого не знает, и не предполагается, что знает.

Когда программист использует VSETDCFG, чтобы установить типы элементов и количество используемых регистров, процессор использует эту информацию, чтобы вычислить максимальную длину вектора Max Vector Length (MVL).

LI        x5, 2<<25  # Load register x5 with 2<<25VSETDCFG  x5         # Set data configuration to x5

В примере выше происходят две вещи:

  • Включаем два регистра:v0иv1.

  • Устанавливаем тип элементов в 64-битные вещественные числа. Давайте сравним это с ARM Neon, в котором каждый регистр имеет длину 128 бит. Это означает, что Neon может обрабатывать два таких числа параллельно. Но в RISC-V 16 таких регистров можно объединить в один. Это позволяет обрабатывать 32 значения параллельно.

На самом деле это не буквально так. За сценой у нас есть конечное число вещественных умножителей, блоков АЛУ и т.п., что ограничивает число параллельных операций. Однако всё это уже детали реализации.

Итак, мы получили значениеMVL, равное 32. Разработчик не должен напрямую знать это число. Команда SETVLработает так:

SETVL rd, sr  ; rd  min(MVL, sr), VL  rd

Если вы попытаетесь установить Vector Length (VL)в значение 5, это сработает. Однако, если вы попытаетесь установить значение 60, вы получите вместо этого значение 32. Итак, величина Max Vector Length (MVL) важна, она не фиксирована конкретным значением при изготовлении процессора. Она может быть вычислена исходя из конфигурации (типа элементов и количества включенных регистров).

Вектора переменной длины в ARM

В ARM вы не устанавливаете длину вектора явным образом. Вместо этого вы устанавливаете длину вектора косвенно, используя предикатные регистры. Они являются битовыми масками, которыми вы включаете и выключаете элементы в векторном регистре. Регистры предикатов также существуют в RISC-V, но не имеют центральной роли, как в ARM.

Чтобы получить эквивалентSETVLна ARM , используйте командуWHILELT, что является сокращением отWhile Less Than:

WHILELT p3.d, x1, x4

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

i = 0while i < M   if x1 < x4      p3[i] = 1   else      p3[i] = 0  end  i += 1  x1 += 1end

Концептуально, мы переворачиваем биты в регистре предиката p3в зависимости от того, меньше лиx1,чемx4. В данном случаеx4содержит длину вектора. Еслиp3выглядит так, то длину вектора можно считать равной 3.

1110000

То есть вектор переменной длины реализуется за счёт того, что все операции используют предикат. Рассмотрим эту операцию сложения. Представьте, чтоv0[p0]извлекает из v0только те элементы, для которыхp0истинно.

ADD v4.D, p0/M, v0.D, v1.D ; v4[p0]  v0[p0] + v1[p0]

Итак, мы сделали некоторое вступление. Сейчас рассмотрим более полный пример кода, чтобы увидеть, как эти наборы команд работают на практике.

Пример кода DAXPY

Рассмотрим сейчас, как функции C могут быть реализованы различными векторными командами:

void daxpy(size_t n, double a, double x[], double y[]) {        for (int64_t i = 0; i < n; ++i) {                y[i] = x[i] * a + y[i];        }}

Почему такое странное имя daxpy? Это простая функция в библиотеке линейной алгебры BLAS, популярной в научной работе. В BLAS эта функция называется daxpyи она очень популярна для демонстрации примеров работы разнообразных SIMD и векторных команд. Она реализует такую формулу:

aX + Y

гдеa скаляр, а XиY вектора. Без векторных команд нужно было бы обрабатывать все элементы в цикле. Но с умным компилятором, эти команды могут быть векторизованы в код, который выглядит на RISC-V так, как показано ниже. Комментарии показывают, какой регистр какой переменной соответствует:

daxpy(size_t n, double a, double x[], double y[]) n - a0  int   register (alias for x10) a - fa0 float register (alias for f10)  x - a1  (alias for x11)  y - a2  (alias for x12

Код:

LI       t0, 2<<25    VSETDCFG t0             # enable two 64-bit float regsloop:    SETVL  t0, a0           # t0  min(mvl, a0), vl  t0    VLD    v0, a1           # load vector x    SLLI   t1, t0, 3        # t1  vl * 2 (in bytes)    VLD    v1, a2           # load vector y    ADD    a1, a1, t1       # increment pointer to x by vl*8    VFMADD v1, v0, fa0, v1  # v1 += v0 * fa0 (y = a * x + y)    SUB    a0, a0, t0       # n -= vl (t0)    VST    v1, a2           # store Y    ADD    a2, a2, t1       # increment pointer to y by vl*8    BNEZ   a0, loop         # repeat if n != 0    RET    

Это код, скопированный из примера. Отметим, что мы не используем именаf иxдля целочисленных и вещественных регистров. Чтобы помочь разработчикам лучше помнить соглашения, ассемблер RISC-V определяет ряд псевдонимов. Например, аргументы функции передаются в регистрахx10-x17. Но нет необходимости запоминать эти номера, для аргументов предусмотрены псевдонимыa0-a7.

t0-t6 псевдонимы регистров временных переменных. Они не сохраняются между вызовами.

Для сравнения мы приведём ниже код ARM SVE. Пометим, какой регистр какую переменную содержит.

daxpy(size_t n, double a, double x[], double y[]) n - x0  register a - d0  float register x - x1  register  y - x2  register i - x3  register for the loop counter

Код:

daxpy:        MOV z2.d, d0            // a        MOV x3, #0              // i        WHILELT p0.d, x3, x0    // i, nloop:        LD1D z1.d, p0/z, [x1, x3, LSL #3] // load x        LD1D z0.d, p0/z, [x2, x3, LSL #3] // load y        FMLA z0.d, p0/m, z1.d, z2.d        ST1D z0.d, p0, [x2, x3, LSL #3]        INCD x3                 // i        WHILELT p0.d, x3, x0    // i, n        B.ANY loop        RET

Код ARM немного короче, так как команды ARM делают больше, чем одно действие. Это является причиной того, что код RISC-V гораздо проще читать. Команды в RISC-V делают что-то одно, и не требуют специального сложного синтаксиса. Такая простая вещь, как загрузка векторного регистра в ARM выглядит сложно:

LD1D z1.d, p0/z, [x1, x3, LSL #3]

Часть в квадратных скобках вычисляет адрес, из которого происходит загрузка:

[x1, x3, LSL #3] = x1 + x3*2 = x[i * 8]

Итак, здесь видно, чтоx1представляет базовый адрес переменнойx.x3 счётчикi. Сдвигом влево на три бита мы умножаем на 8, то есть на количество байт в 64-битном вещественно числе.

Заключение

Как начинающий в векторном кодинге, я должен сказать, что ARM переусложнён. Это не значит, что ARM плохой. Я также изучал систему команд Intel AVX, и она в 10 раз хуже. Я совершенно определённо не хочу тратить время на изучение AVX, принимая во внимание, сколько усилий отняли SVE и Neon.

Для меня совершенно ясно, что любой, кто хочет изучать кодинг на ассемблере, должен начать с RISC-V. Для начинающих это на порядки проще в освоении. И это не удивительно. Эта система команд специально разработана для преподавания в университете.

Архитектура сложна, потому что это легаси. Эта архитектура развивалась десятилетиями, и пыталась сохранять обратную совместимость. ARM имеет более ясный дизайн, но он усложнён просто потому, что дизайн продиктован в первую очередь требованиями отрасли, а не дружественностью к новичкам.

Если для вас, как и для меня, это хобби, и вы просто хотите понимать, как развивается технология, и как работают вещи, такие, как векторная обработка, сберегите свои усилия и просто прочитайте книгу по RISC-V.

Люди могут поспорить, что ARM или Intel или что-то ещё проще, потому что по ним больше книг и больше ресурсов. Ничего подобного! Я могу сказать вам на своём собственном опыте, что документация часто представляет собой препятствие, а не помощь. Это означает, что вам нужно раскопать больше материала. Вы найдёте много противоречий, корни которых лежат в устаревших принципах работы.

Если вы хотите погрузиться в ассемблер, вы можете прочитать некоторые из моих статей и руководств:

Подробнее..

Как начать путь к работе по проектированию электроники FPGA космического корабля Blue Origin

30.07.2020 10:07:11 | Автор: admin


Вы хотите узнать, как получить работу по проектированию электроники космического корабля? Мне надавно пришло предложение поинтервьироваться на позицию FPGA designer для Blue Origin (см. выше). Лично мне такая позиция не нужна (у меня уже есть позиция ASIC designer-а в другой компании), но я отметил, что технические требования к претендентам в Blue Origin точно совпадают с содержанием семинара для школьников и младших студентов, который пройдет 15-17 сентября на выставке ChipEXPO в Сколково, с поддержкой от РОСНАНО. Хотя разумеется на семинаре мы коснемся технологий Verilog и FPGA только на самом начальном уровне: базовые концепции и простые, но уже интересные, примеры. Чтобы устроится после этого в Blue Origin, вам все-же потребуется несколько лет учебы и работы.

Из-за короновируса семинар будет удаленный, поэтому принять участие смогут не только школьники и студенты Москвы, но и всей России, Украины, Казахстана, Калифорнии и других стран и регионов. Физически проводить лекции и удаленно помогать участникам будут преподаватели и инженеры МИЭТ, ВШЭ МИЭМ, МФТИ, Черниговского Политеха, Самарского университета, IVA Technologies и fpga-systems.ru.

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

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

Инструкция как получить плату есть на сайте STEMford. STEMford это образовательные программы для школьников от eNano, дочернего предприятия Фонда инфраструктурных образовательных программ РОСНАНО. Вот их офис, куда нужно будет зайти:



До практического занития вам нужно будет установить на ваш компьютер среду Intel Quartus Prime Lite Edition. Инструкция как это сделать, есть в бесплатном фрагменте книги Цифровой синтез: практический курс под общей редакцией А. Ю. Романова, Ю. В. Панчула. ДМК Пресс, 2020.

Вообще Цифровой синтез полезная книжка для семинара, там описываются упражнения, которые будут с некоторыми вариациями представлены и на семинаре, хотя например вместо учебного процессора schoolMIPS будет использоваться более новомодный schoolRISCV:



В конце книжки Цифровой синтез есть довольно занятное интервью пары молодоженов Владислава и Елены Шаршиных, который вместе с их коллегой Андреем Папушиным компания Intel привезла в Калифорнию за серебряную победу на конкурсе InnovateFPGA:



Вот одна из плат, которая будет использоваться на семинаре. Если плат на вас не хватит, или если вы не школьник, вы можете заказать плату на AliExpress и в других местах: 1, 2, 3, 4:



Что будет на семинаре? Вот части программы:

15 сентября. Из чего строится современная цифровая схема.
Модератор дня: Александр Михайлович Силантьев, преподаватель Национального исследовательского университета Московский институт электронной техники (МИЭТ).

15.00. Открытие мероприятия, приветствие от организаторов.
15.15-15.30. Мини-лекция: От Клода Шеннона до Apple iPhone: как появилось проектирование цифровых схем и как оно выглядит в современных компаниях.
Юрий Владимирович Панчул, проектировщик сетевых микросхем и микропроцессорных ядер. Саннивейл, Калифорния.

15.30-16.00. Лекция: Комбинационная логика и ее описание на языке Verilog. Теоретический материал переплетается с демонстрацией синтеза для ПЛИС/FPGA в среде Intel Quartus Prime Lite Edition. Александр Михайлович Силантьев.

16.00-16.30. Упражнение с логическими элементами
И/ИЛИ/НЕ/ИСКЛЮЧАЮЩЕЕ-ИЛИ, входы которых подсоединены к кнопкам, а выходы к светодиодам платы c ПЛИС.
16.30-17.00. Упражнение с выводом буквы на семисегментный индикатор.
17.00-17.30. Лекция: Последовательностная логика, которая вводит в схемы память и повторения.
17.30-18.00. Упражнение со сдвиговым регистром.
18.00-19.00. Упражнение для плат ZEOWAA и OMDAZZ с Intel FPGA Cyclone IV: Комбинируем сдвиговый регистр и вывод на семисегментный индикатор буквы: получаем вывод на многоразрядный динамический семисегментный индикатор слова (например имени ученика). Упражнение для платы Terasic DE10-Lite с Intel FPGA MAX10: Комбинируем сдвиговый регистр и вывод букв на статический семисегментный индикатор: получаем вывод бегущей строки (например имени ученика).
19.00-21.00. Дополнительные упражнения и индивидуальные проекты учеников, с помощью от студентов и аспирантов микроэлектроники от участвующих университетов: МИЭТ, ВШЭ МИЭМ, Черниговского НТУ,
Самарского Университета.


Первый день основан на опыте проведения прошлогоднего семинара в Москве, который описан в статье на Хабре:



Второй день основан на опыте проведения летней школы в Зеленограде в прошлом году и семинарах в Самаре:

16 сентября. Приемы и примеры цифрового проектирования на уровне регистровых передач.
Модератор дня: Сергей Анатольевич Иванец, декан факультета электронных и информационных технологий, Черниговский национальный технологический университет, Украина.

15.00-15.15. Мини-лекция: Как из простых схем строить сложные: параллельность, конвейерность и конечные автоматы.
Юрий Владимирович Панчул.

15.15-15.30. Предисловие к примеру игры: рассказ про генерацию графики на VGA.
Сергей Анатольевич Иванец.

15.30-16.00. Упражнение с рисованием на экране разноцветных квадратов и других статических изображений.

16.00-16.30. Презентация примера графической игры с параллельно вычисляемыми спрайтами и конечными автоматами для сценария игры. Демонстрация запуска игры на плате Digilent Basys3 с Xilinx FPGA Artix-7. Обсуждение модификации игры с помощью добавления новых спрайтов и изменения сценария.
Михаил Коробков, fpga-systems.ru.

16.30-17.00. Упражнение с запуском игры на платах ZEOWAA, OMDAZZ и Terasic DE10-Lite.
Сергей Анатольевич Иванец.

17.00-17.30. Лекция: Использование Linear Feedback Shift Registers (LFSR) для передачи данных и генераторов случайных чисел. Сравнение Verilog и VHDL на основе кода LFSR Фибоначчи и Галуа. Демонстрация использования LFSR для генерации изображения на экране VGA движущегося звездного неба из случайных звезд.
Илья Александрович Кудрявцев, декан Факультета электроники и приборостроения Самарского Университета.

17.30-18.00. Упражнение с запуском примера движущегося звездного неба на платах ZEOWAA, OMDAZZ и Terasic DE10-Lite.
Сергей Анатольевич Иванец.

18.00-19.00. Более подробная лекция про моделирование и использование генераторов случайных чисел для глубоко заинтересовавшихся.
Илья Александрович Кудрявцев.

18.00-21.00. Дополнительные упражнения и индивидуальные проекты учеников по изменению игры, в том числе с помощью добавления в нее фона из движущегося звездного неба. С помощью от студентов и аспирантов микроэлектроники от участвующих университетов.






Третий день будет основан на опыте ликбеза по процессорам от Станислава Жельнио и Александра Романова. Только раньше они разработали и использовали учебный процессор schoolMIPS, а теперь мы будем использовать schoolRISCV. Архитектура RISC-V очень похожа на MIPS и другие RISC-архитектуры (SPARC, ARM, POWER итд), но очищена от их костылей, которые имели смысл на простых процессорах, но мешают на сложных (регистровые окна в SPARC, которые обессмысливаются в софтвере с большим стеком; branch delay slots в MIPS до Rev6, которые хороши на простом статическом конвейере с последовательной выборкой инстркций, но превращаются в головную боль в динамическом конвейере итд).

Так как в России есть минимум три компании, которые проектируют процессоры на архитектуре RISC-V, и это хороший кандидат на будущий high-end российский встроеный и может быть даже десктопный и серверный процессор, и так как RISC-V уверенно заменяет MIPS в качестве рабочей лошадки для архитектурного и микроархитектурного экспериментирования в мировых университетах, то школьников лучше учить на нем.

17 сентября. Первый шаг в архитектуру и микроархитектуру современных процессоров.
Модератор дня Александр Юрьевич Романов, к.т.н., доцент Московского института электроники и математики им. А.Н. Тихонова (МИЭМ), Национальный исследовательский университет Высшая школа экономики (НИУ ВШЭ).

15.00-15.15. Мини-лекция: От ENIAC и МЭСМ, через IBM/360 и Cray-1 до Intel, ARM и RISC-V: как появились, эволюционизировали и к чему пришли программируемые процессоры общего назначения.
Юрий Владимирович Панчул.

15.15-16.30. Архитектура: вид процессора с точки зрения программиста. Лекция об ассемблере RISC-V с одновременными упражнениями на симуляторе процессора на уровне инструкций.
Никита Поляков, проектировщик микропроцессоров и преподаватель Московского физико-технического института.

16.30-17.30. Микроархитектура: вид процессора с точки зрения схемотехника. Лекция по аппаратной организации процессора schoolRISCV, с вариантами одноцикловой и конвейерной микроархитектуры. Демонстрация синтеза процессора и запуск его на платах.
Станислав Жельнио, разработчик микросхем в IVA Technologies.

17.30-17.30. Упражнение по добавлению в процессор инструкции и верификации с помощью программного теста. Измерение максимальной тактовой частоты получившегося варианта процессора.
Станислав Жельнио.

17.30-18.00. Заключительная лекция: Следущие шаги в освоении проектирования цифровых схем, а также применение этих знаний для проектирования встроенных систем и ускорителей вычислений искуственного интеллекта.
Александр Юрьевич Романов.

18.00-21.00. Дополнительные упражнения и индивидуальные проекты учеников по изменению процессора и интеграции его с периферийными устройствами. С помощью от студентов и аспирантов микроэлектроники от участвующих университетов.


Вот Станислав Жельнио обучает школьников прошлым летом в Зеленограде:







До встречи на семинаре на виртуальном ChipEXPO в Сколково! Мы постараемся провести там часть мероприятия и вживую, силами преподавателей МИЭТ, ВШЭ МИЭМ и МФТИ, но если вирус не позволит, проведем распределенно.
Подробнее..

On commutativity of addition

27.04.2021 22:20:31 | Автор: admin
Does an assembly change, if we write (b + a) instead (a + b)?
Let's check out.

Let's write:
__int128 add1(__int128 a, __int128 b) {    return b + a;}

and compile it with risc-v gcc 8.2.0:

add1(__int128, __int128):
.LFB0:
.cfi_startproc
add a0,a2,a0
sltu a2,a0,a2
add a1,a3,a1
add a1,a2,a1
ret


Now write the following:

__int128 add1(__int128 a, __int128 b) {    return a + b;}

And get:

add1(__int128, __int128):
.LFB0:
.cfi_startproc
mv a5,a0
add a0,a0,a2
sltu a5,a0,a5
add a1,a1,a3
add a1,a5,a1
ret

The difference is obvious.

Now do the same using clang (rv64gc trunk). In both cases we get the same result:
add1(__int128, __int128): # @add1(__int128, __int128)
add a1, a1, a3
add a0, a0, a2
sltu a2, a0, a2
add a1, a1, a2
ret

The result is the same we got from gcc in the first case. Compilers are smart now, but not so smart yet.

Let's try to find out, what happened here and why. Arguments of a function __int128 add1(__int128 a, __int128 b) are passed through registers a0-a3 in the following order: a0 is a low word of a operand, a1 is a high word of a, a2 is a low word of b and a1 is the high word of b. The result is returned in the same order, with a low word in a0 and a high word in a1.

Then high words of two arguments are added and the result is located in a1, and for low words, the result is located in a0. Then the result is compared against a2, i.e. the low word of b operand. It is necessary to find out if an overflow has happened at an adding operation. If an overflow has happened, the result is less than any of the operands. Because the operand in a0 does not exist now, the a2 register is used for comparison. If a0 < a2, the overflow has happened, and a2 is set to 1, and to 0 otherwise. Then this bit is added to the hight word of the result. Now the result is located in (a1, a0).

Completely similar text is generated by Clang (rv32gc trunk) for the 32-bit core, if the function has 64-bit arguments and the result:

long long add1(long long a, long long b) {    return a + b;}

The assembler:
add1(long long, long long): # @add1(long long, long long)
add a1, a1, a3
add a0, a0, a2
sltu a2, a0, a2
add a1, a1, a2
ret

There is absolutely the same code. Unfortunately, a type __int128 is not supported by compilers for 32-bit architecture.

Here there is a slight possibility for the core microarchitecture optimization. Considering the RISC-V architecture standard, a microarchitecture can (but not has to) detect instruction pairs (MULH[[S]U] rdh, rs1, rs2; MUL rdl, rs1, rs2) and (DIV[U] rdq, rs1, rs2; REM[U] rdr, rs1, rs2) to process them as one instruction. Similarly, it is possible to detect the pair (add rdl, rs1, rs2; sltu rdh, rdl, rs1/rs2) and immediately set the overflow bit in the rdh register.
Подробнее..
Категории: C , Assembler , Компиляторы , Llvm , Risc-v , Optomization

Перевод Разговор с техническим директором RISC-V Марком Химельштейном

14.01.2021 14:20:31 | Автор: admin
image Летом 2020 RISC-V International (организация-член группы RISC-V) объявила о назначении 16 членов правления в новую компанию с штаб-квартирой в Швейцарии в связи с упразднением старого подразделения RISC-V Foundation.

В июне 2020 года в компанию пришел новый CTO Марк Химельштейн. Мы взяли у него онлайн-интервью, в котором поговорили о его роли в компании, проблемах (если они имеются) внедрения RISC-V и влиянии геополитики на различные процессы.

EE Times: Расскажите нам о своем бекграунде и почему вы решили присоединиться к RISC-V.

Марк Химельштейн: Думаю, можно выделить три основных пункта моей биографии:
1) Я работал в MIPS и занимался компиляторами, ПО для оптимизации, ОС и помогал с разработкой ISA;
2) Затем я пять лет работал в Sun Microsystems и руководил разработкой Solaris;
3) Я был сооснователем и CTO стартапа, занимавшегося разработкой системой параллельных вычислений для работы с флеш-памятью (Graphite Systems, купленная EMC в 2015 году).

Я занимался всем от ISA до приложений. Одна из причин, почему Калиста [Калиста Редмонд, CEO RISC-V International] пригласила меня заключается в том, что и ISA и экосистема имеют равную и очень высокую важность. Компании нужен был человек, приближенный к разработке ПО потому что именно в этой сфере ей нужно расти и работать, чтобы обеспечить RISC-V успешное будущее.

Некоторые из моих коллег в MIPS говорили о том, что я стану CTO RISC-V, хотя я тогда об этом не думал. В конечном итоге я выбрал эту работу потому что она показалась мне интересной с технической точки зрения, а также мне понравились люди, которые меня собеседовали. Это основные аспекты, на которые я обращаю внимание при выборе работы.

EE Times: Учитывая ваш многолетний опыт работы в этой отрасли, как вы думаете, какие возможности есть в RISC-V?

Марк Химельштейн: В компании работают над первым чипом такого масштаба, работа над которым была изначально открытой. Он не был дарован нам большой корпорацией, следившей за его созданием. Работа над этим чипом не велась за закрытыми дверями, хотя участие в разработке принимали и крупные игроки. Таким образом, в RISC-V к своему продукту относятся так, как сообщество Linux относится к своему.

Если бы сейчас были 90-е и мы говорили о Linux, то мы бы обсуждали AIX, Solaris и так далее. Сейчас в этой теме все вопросы решены и говорить не о чем. Нам кажется, что с RISC-V мы можем добиться того же самого. В будущем все будет проще, потому что мир ПО с открытым исходным кодом дал людям понять, что отсутствие контроля со стороны больших корпораций открывает свободу действий можно делать то, что вам хочется.

С нами работает просто невероятное множество умных людей. Одна из причин гибкости RISC-V, позволяющей внедрять его как в устройства интернета вещей, так и в суперкомпьютеры, заключается в том, что у нас есть инженеры, и они думают об этом. Мы знаем какие именно аспекты важны в этой области, потому что наблюдали за ее развитием от MIPS и SPARC до Arm и x86.

Благодарю этому опыту нам удалось создать столь гибкую ISA. Из 16-битных инструкций, регистров для чисел с плавающей точкой, целочисленных регистров и так далее. Все сделано так, чтобы даже в простых приложениях (таких как устройства интернета вещей) не приходилось вводить отдельные наборы регистров для вещественных и чисел и операции с пониженной точностью, достаточные для решения их задач. Мы много думали о сжатых инструкциях и подобных вещах а также об их размещении в ограниченном пространстве.

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

EE Times: Какие проблемы возникают при разработке RISC-V прямо сейчас?

Марк Химельштейн: Когда дело доходит до реального развертывания и интеграции, многое зависит от целевой платформы и ее приложений. Здесь не все похоже на Linux, где можно просто выпустить ОС и не подвязываться под циклы разработки ПО. При работе над ПО, можно выпускать продукты под существующее железо, а если выйдет новое просто собирать для него отдельные версии. Мы же занимаемся железом, и мы должны быть уверены в том, что новое ПО будет с ним работать. Впрочем, как вы понимаете, для промышленных компьютеров общего назначения (например, многоядерных или компьютеров с несколькими сокетами) нужна продуманная экосистема.

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

Конечно, Java, Hadoop и Spark облегчают жизнь при переходе на новые платформы. Но даже с ними возникают определенные ситуации, когда необходимо воспользоваться преимуществами оборудования и его особых возможностей.

EE Times: Каковы проблемы на пути внедрения RISC-V? Почему несмотря на все преимущества этой архитектуры, она не используется повсеместно?

Марк Химельштейн: У нас 600 сотрудников, и, вероятно, 120 из них разрабатывают свои ядра и аппаратные компоненты, которые в конечном итоге окажутся в машинах. Но важно понимать, что все делается в соответствии с рабочими процессами. Цикл разработки железа больше, чем цикл разработки ПО. Вы выпускаете прототип, клиент его тестирует и таких итераций может быть несколько. Все это требует намного больше времени, чем исправление бага в Linux.

В интернете вещей, с точки зрения архитектуры, есть заметное развитие. Там уже есть системы на кристаллах, там уже есть встроенные компоненты. А еще на этом рынке много поставщиков инструментов. На нашем веб-сайте есть полный список доступных программ и основных компонентов, а также советов по разработке. В этой области развивается просто невероятная инфраструктура.

Впрочем, если вы хотите заменить какой-то чип в автомобиле, то вы должны учитывать, что жизненный цикл обновлений в автомобилях составляет пять лет, а значит нужно понимать, что продукту предстоит пережить значительное время. Впрочем, я не думаю, что проблемы будут возникать на ранних этапах.

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

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

EE Times: Что делает RISC-V International, чтобы развить экосистему?

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

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

Должен сказать, что пользовательские истории это здорово. При разработке продукта важно знать кто будет его использовать, почему его будут его использовать как люди будут извлекать из него пользу. У нас есть целый ряд аспектов, которые нас волнуют, а также множество областей, в которых мы хотим преуспеть, будь то автомобильная промышленность, телекоммуникации, финансы, нефть, газ RISC-V будет полезна везде.

EE Times: Как вы сотрудничаете с другими организациями в рамках этого плана?

Марк Химельштейн: С разработкой RISC-V нам помогают просто невероятные компании: OpenHW Group, Chips Alliance, OpenTitan. Они помогают нам не забывать, что люди хотят работать с открытыми решениями, они хотят держать все под контролем и иметь возможность влиять на свои продукты.

Мы вместе создаем ISA и все, что ее окружает экосистему. Разработка ISA это лишь треть работы. Остальные две трети разработка экосистемы. Загрузчики, файлы конфигурации, порты Linux, компиляторы, руководства по безопасности все это экосистема.

Конечно, есть и другие аспекты. Когда дело доходит до обмена системами на кристаллах, опытом, средствами безопасности и многим другим, то выясняется, что всем этим занимается множество людей, и всем им нравится RISC-V. Всегда нужно понимать что вы можете сделать самостоятельно, а что можно взять у партнеров.

Когда люди будут создавать какие-либо решения будут опираться на сторонние ядра либо созданные нашими партнерами, либо на одно из 35 опубликованных на нашем сайте. А затем им нужно будет решить какие инновации они хотят в него внедрить.

EE Times: Что вы можете сказать о представлениях, что RISC-V все еще остается в академической сфере?

Марк Химельштейн: Давайте будем честны, пока мы работаем над спецификациями. Но есть компании-поставщики решений, который оказывают нам значительную помощь. Не буду приводить название, но мы сотрудничаем с одной из групп, поставляющей решения для автопроизводителей. Автомобильные компании сами этим не занимаются, они нанимают исполнителей. Walmart от этого мира обратится к поставщику решений и скажет, что ему нужен RISC-V по тем или иным причинам.

EE Times: Как вы сообщаете людям о том, как RISC-V может помочь в их разработках?

Марк Химельштейн: Я главный технический евангелист RISC-V. Моя главная цель внедрение и развертывание продуктов с ядрами RISC-V. Это единственное, что имеет значение.

EE Times: Что будет дальше с RISC-V, теперь, когда вы присоединились к команде?

Марк Химельштейн: Я думаю, что моя роль состоит из трех частей. Первая рабочая. Разработка спецификаций, анализ пробелов. Вторая связана с планированием. Организация работы над определенным аспектами, проверка наличия чеклистов для внедрения и проверка отсутствия препятствий для внедрения. Третья часть общение с внешним миром. Общение со СМИ, посещение конференций и продвижение продукта.

Поскольку я заполняю разрыв между ISA и приложениями, у меня весьма целостное представление о том, над чем мы работаем. Я могу быть на собрании и сказать Ребята, а вы обсуждали этот момент с разработчиками ПО?, а затем организовать это обсуждение.

EE Times: Вы сказали, что не можете охватить все отрасли и должны сосредоточиться на нескольких ключевых. На каких именно?

Марк Химельштейн: Мы работаем с теми отраслями, где меньше сложностей с внедрением. Мы работаем с интернетом вещей, но не только с ним непосредственно, но и с его средой и потребностями. То же самое мы делаем применительно к автомобилям, высокопроизводительным вычислениям и облачным серверам. Вот четыре области, в которых я вижу потенциал к быстрому росту. И я хочу быть уверенным в том, что мы за ними поспеваем и даем им все, что нужно.

Одна из общих проблем, связанных с проектами такого типа, заключается в том, что люди могут постоянно выполнять работу, не имеющую собственной ценности. Часть моей работы объединять результаты и обобщать их, выделять аспекты, которыми можно делиться и поощрять обсуждения, в которых может участвовать сообщество.

EE Times: Расскажите нам, что вы думаете о геополитических проблемах, учитывая, что RISC-V International стала швейцарской организацией.

Марк Химельштейн: Я вам хочу сказать, что со времен экспортного контроля 90-ых ничего не изменилось. Все было очень строго, я руководил командой разработки Solaris, когда мы открыли исходный ход PKI, чтобы продавать средства безопасности в Китай. Все дело в том, что продавать части проприетарной ОС было нельзя, а часть проекта с открытым исходным кодом можно.

Думаю, со временем все устаканится, станет ясно как вести бизнес и так далее. Я не замечал агрессии со стороны каких либо стран или компаний по отношению к RISC-V. Все хотят помогать сообществу, потому что все хотят воспользоваться преимуществами и плодами совместной работы. Мне нравится, что у наших решений открыт исходный код, мы ничего не скрываем как Linux или Hadoop. И как у них, у нас есть пользователи из Китая, Пакистана и других стран это нормально.

Конечно, нельзя забывать о геополитических силах, которые все усложняют даже сильнее, чем 80-ые и 90-ые. Мы должны учитывать это и быть уверены в том, что соблюдаем все требования и не делаем ничего, что нарушает закон какой-либо страны.

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

EE Times: Спасибо, Марк.





image


Вакансии
НПП ИТЭЛМА всегда рада молодым специалистам, выпускникам автомобильных, технических вузов, а также физико-математических факультетов любых других высших учебных заведений.

У вас будет возможность разрабатывать софт разного уровня, тестировать, запускать в производство и видеть в действии готовые автомобильные изделия, к созданию которых вы приложили руку.

В компании организован специальный испытательный центр, дающий возможность проводить исследования в области управления ДВС, в том числе и в составе автомобиля. Испытательная лаборатория включает моторные боксы, барабанные стенды, температурную и климатическую установки, вибрационный стенд, камеру соляного тумана, рентгеновскую установку и другое специализированное оборудование.

Если вам интересно попробовать свои силы в решении тех задач, которые у нас есть, пишите в личку.



О компании ИТЭЛМА
Мы большая компания-разработчик automotive компонентов. В компании трудится около 2500 сотрудников, в том числе 650 инженеров.

Мы, пожалуй, самый сильный в России центр компетенций по разработке автомобильной электроники. Сейчас активно растем и открыли много вакансий (порядка 30, в том числе в регионах), таких как инженер-программист, инженер-конструктор, ведущий инженер-разработчик (DSP-программист) и др.

У нас много интересных задач от автопроизводителей и концернов, двигающих индустрию. Если хотите расти, как специалист, и учиться у лучших, будем рады видеть вас в нашей команде. Также мы готовы делиться экспертизой, самым важным что происходит в automotive. Задавайте нам любые вопросы, ответим, пообсуждаем.

Подробнее..

Легким движением руки MIPS Technologies превращается в RISC-V

09.03.2021 14:21:03 | Автор: admin

Итак, компания MIPS Technologies прекращает разработку архитектуры MIPS, переключившись на работу с системами на базе архитектуры RISC-V. И уже восьмое по счету поколение архитектуры MIPS решили построить на наработках открытого проекта RISC-V.

Стоит напомнить, что компания не раз переходила из рук в руки. В 2017 году она стала работать под началом стартапа Wave Computing, который разрабатывает ускорители для систем машинного обучения. В этих устройствах использовались процессоры MIPS. Дело шло не очень, так что в прошлом году стартап стал банкротом. Правда, процедуру банкротства он так и не завершил, поскольку получил инвестиции от венчурного фонда Tallwood. В итоге была проведена масштабная реорганизация, включая смену названия теперь компания называется MIPS.

Ну и изменилось не только название, бизнес-модель руководство также решило поменять, расширив сферу деятельности.

Ранее MIPS Technologies занималась развитием архитектуры и лицензированием интеллектуальной собственности, которая связана с процессорами MIPS. Сами чипы компания не производила. Но вот перерожденная организация займется выпуском процессоров, но уже на базе архитектуры RISC-V.

Последняя развивается силами некоммерческой структуры RISC-V International при участии всего сообщества разработчиков. По целому ряду причин компания MIPS приняла решение не продолжать развитие собственной архитектуры, а подключиться к совместной с RISC-V International работе. Интересный момент MIPS уже несколько лет является участником RISC-V International, причем технический директор RISC-V International экс-сотрудник MIPS Technologies.

Разработки RISC-V в большинстве своем относятся к открытой и гибкой системе машинных инструкций, которая позволяет создавать микропроцессоры для самых разных отраслей, без необходимости лицензионных выплат и наложения жестких условий на использование. Благодаря такой концепции процессоры на базе RISC-V полностью открытые.

На основе этой архитектуры под разными свободными лицензиями, включая BSD, MIT, Apache 2.0, развивается несколько десятков вариантов ядер микропроцессоров, SoC и уже производимых чипов. Чипы с этой архитектурой поддерживаются разными проектами, начиная с Glibc 2.27, binutils 2.30, gcc 7 и ядра Linux 4.15.

Архитектура MIPS была одним из главных участников MIPS-революции в 80-х годах прошлого века, вместе с SPARC, Alpha, PA-RISC. Эта революция довольно сильно напугала Intel так сильно, что компания потратила несколько миллиардов на разработку Itanium. Процессоры с архитектурой MIPS использовались в мини-ПК DEC, рабочих станциях Silicon Graphics, видеоиграх Nintendo и многих других системах.

Публичной компания стала в 1989 году, после этого Microsoft перенесла Windows на MIPS. Еще чуть позже Silicon Graphics выкупила бизнес компании, выделила его как собственное подразделение, которое стало публичным в 1998 году. Тогда MIPS была прямым конкурентом ARM, причем более сильным конкурентом.

Подробнее..

Перевод Гениальность микропроцессоров RISC-V

23.12.2020 12:19:27 | Автор: admin
image

Войны между RISC и CISC, проходившие в конце 1990-х, уже давно отгремели, и сегодня считается, что разница между RISC и CISC совершенно не имеет значения. Многие заявляют, что наборы команд несущественны.

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

Недавно я подробнее изучил информацию об архитектуре набора команд (instruction-set architecture, ISA) RISC-V и вот некоторые из аспектов, которые по-настоящему впечатлили меня в ISA RISC-V:

  1. Это небольшой и простой в изучении набор команд RISC. Очень предпочтителен для тех, кому интересно получать знания о микропроцессорах.
  2. Благодаря своей простоте, открытости и связи с университетскими профессорами он с большой вероятностью будет доминировать как архитектура, выбираемая для обучения процессорам в вузах.
  3. Его продуманная структура позволяет разработчикам CPU создавать высокопроизводительные микропроцессоры на основе ISA RISC-V.
  4. Благодаря отсутствую лицензионных отчислений и нацеленности на простую аппаратную реализацию увлечённый любитель может, в принципе, создать за приемлемое время собственную конструкцию процессора RISC-V.

Месть RISC


Когда я начал понимать RISC-V лучше, то осознал, что RISC-V оказался радикальным возвратом к тому, что многие считали давно прошедшей эпохой вычислений. С точки зрения конструкции, RISC-V подобен перемещению на машине времени к классическому Reduced Instruction Set Computer (RISC, компьютеру с набором коротких команд) начала 80-х и 90-х.

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

RISC-V же, напротив, является действительно хардкорным представителем процессоров RISC. Если вы почитаете в Интернете обсуждения RISC-V, то найдёте людей, утверждающих, что RISC-V был разработан какими-то олдскульными RISC-радикалами, отказывающимися двигаться в ногу со временем.

Бывшая инженер ARM Эрин Шеперд несколько лет назад написала интересную критику RISC-V:

ISA RISC-V слишком стремился к минимализму. В нём есть сильный упор на минимизацию количества команд, нормализацию кодирования и т.д. Это стремление к минимализму привело к ложным ортогональностям (например, использованию одной и той же команды для ветвления, вызовов и возвратов) и требованию избыточных команд, влияющих на плотность кода с точки зрения размера и количества команд.

Вкратце приведу немного контекста. Малый размер кода даёт преимущество в производительности, поскольку так проще хранить выполняемый код внутри высокоскоростного кэша процессора.

Критика здесь заключается в том, что проектировщики RISC-V слишком сосредоточились на обеспечении малого набора команд. В конце концов, это ведь одна из исходных целей RISC.

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

Традиционно долгие годы считалось, что в процессор RISC нужно добавить больше команд, чтобы он стал более похожим на CISC. Идея заключается в том, что более специализированные команды могут заменить использование множественных общих команд.

Сжатие команд и Macro-Operation Fusion


Однако в архитектуре процессоров присутствуют две инновации, из-за которых эта стратегия добавления более сложных команд во многих смыслах оказывается избыточной:

  • Сжатые команды команды сжимаются в памяти и распаковываются на первой стадии процессора.
  • Macro-operation Fusion две или несколько простых команд считываются процессором и сливаются в одну более сложную команду.

На самом деле, в ARM уже используются обе эти стратегии, а в процессорах x86 применяется вторая, поэтому RISC-V не проделывает здесь каких-то новых трюков.

Однако тут есть тонкость: RISC-V получает из этих двух стратегий гораздо больше выгод по двум важным причинам:

  1. Сжатые команды были добавлены изначально. В других архитектурах, например, в ARM, об этом подумали позже, и прикрутили их довольно поспешным образом.
  2. Здесь оправдывает себя одержимость RISC малым количеством уникальных команд. Для добавления сжатых команд просто остаётся больше места.

Второй пункт требует некоторых пояснений. В архитектурах RISC команды обычно имеют ширину 32 бита. Эти биты нужно использовать для кодирования различной информации. Допустим, у нас есть такая команда (после точки с запятой идут комментарии):

ADD x1, x4, x8    ; x1  x4 + x8

Она складывает содержимое регистров x4 и x8, сохраняя результат в x1. Требуемое для кодирования этой команды количество битов зависит от количества имеющихся регистров. У RISC-V и ARM есть 32 регистра. Число 32 можно выразить 5 битами:

2 = 32

Так как в команде нужно указать три разных регистра, то для кодирования операндов (входящих данных для операции сложения) требуется в сумме 15 бит (3 5).

Следовательно, чем больше возможностей мы хотим поддерживать в наборе команд, тем больше битов мы займём из доступных нам 32 бит. Разумеется, мы можем перейти к 64-битным командам, но при этом потратится слишком много памяти, а значит, пострадает производительность.

Агрессивно стремясь к сохранению малого количества команд, RISC-V оставляет больше места для добавления битов, обозначающих, что мы используем сжатые команды. Если процессор видит, что в команде заданы определённые биты, то он понимает, что её нужно интерпретировать как сжатую.

Это означает, что вместо засовывания внутрь 32 бит одной команды мы можем уместить две команды по 16 бит шириной каждая. Естественно, не все команды RISC-V можно выразить в 16-битном формате. Поэтому подмножество 32-битных команд выбирается на основании их полезности и частоты использования. Если несжатые команды могут получать 3 операнда (входящих данных), то сжатые команды только 2 операнда. То есть сжатая команда ADD будет выглядеть так:

C.ADD x4, x8     ; x4  x4 + x8

В ассемблерном коде RISC-V используется префикс C., сообщающий, что команду нужно преобразовать ассемблером в сжатую команду.

По сути, сжатые команды уменьшают количество операндов. Три регистра-операнда заняли бы 15 бит, оставив на указание операции всего 1 бит! Таким образом, при использовании двух операндов для указания опкода (выполняемой операции) у нас остаётся 6 бит.

На самом деле это близко к тому, как работает ассемблер x86, когда зарезервировано недостаточно битов для использования трёх регистров-операндов. Процессор x86 при этом тратит биты, чтобы позволить, например, команде ADD считывать входящие данные и из памяти, и из регистров.

Однако истинную выгоду мы получаем, объединив сжатие команд с Macro-operation fusion. Когда процессор получает 32-битное слово, содержащее две сжатые 16-битные команды, он может слить их в одну более сложную команду.

Звучит, как чушь мы что, вернулись к тому, с чего начинали?

Нет, поскольку мы минуем необходимость заполнения спецификации ISA кучей сложных команд (то есть стратегии, которой придерживается ARM). Вместо этого мы, по сути, выражаем целое множество сложных команд косвенно, через различные сочетания простых команд.

В обычных условиях Macro-fusion вызвало бы проблему: хотя две команды заменяются одной, они всё равно занимают в два раза больше памяти. Однако при сжатии команд мы не занимаем никакого лишнего места. Мы пользуемся преимуществами обеих архитектур.

Давайте рассмотрим один из примеров, приведённых Эрин Шеперд. В своей критической статье о ISA RISC-V она показывает простую функцию на C. Чтобы было понятнее, я взял на себя смелость переписать её:

int get_index(int *array, int i) {     return array[i];}

На x86 это скомпилируется в следующий ассемблерный код:

mov eax, [rdi+rsi*4]ret

При вызове функции в языке программирования аргументы обычно передаются функции в регистре согласно установленному порядку, который зависит от используемого набора команд. На x86 первый аргумент помещается в регистр rdi, второй в rsi. По стандарту возвращаемые значения должны помещаться в регистр eax.

Первая команда умножает содержимое rsi на 4. Он содержит переменную i. Почему умножает? Потому что array состоит из элементов integer, разделённых друг от друга 4 байтами. Следовательно, третий элемент массива находится в смещении 3 4 = 12 байт.

Далее мы прибавляем это к rdi, который содержит базовый адрес array. Это даёт нам окончательный адрес i-того элемента array. Мы считываем содержимое ячейки памяти по этому адресу и сохраняем его в eax: задача выполнена.

На ARM всё происходит похожим образом:

LDR r0, [r0, r1, lsl #2]BX  lr                    ;return

Здесь мы не умножаем на 4, а сдвигаем регистр r1 на 2 бита влево, что эквивалентно умножению на 4. Вероятно, это более верное описание и того, что происходит на x86. Сомневаюсь, что можно умножать на что-либо, не являющееся кратным 2, поскольку умножение это довольно сложная операция, а сдвиг малозатратен и прост.

Из моего описания x86 об остальном можно только догадываться. Теперь давайте перейдём к RISC-V, где начинается настоящее веселье! (точкой с запятой начинаются комментарии)

SLLI a1, a1, 2     ; a1  a1 << 2ADD  a0, a0, a1    ; a0  a0 + a1LW   a0, a0, 0     ; a0  [a0 + 0]RET

На RISC-V регистры a0 и a1 являются просто псевдонимами x10 и x11. Именно в них помещаются первый и второй аргументы вызова функции. RET это псевдокоманда (сокращение):

JALR x0, 0(ra)     ; sp  0 + ra                   ; x0  sp + 4  ignoring result

JALR выполняет переход к адресу, хранящемуся в ra, который относится к адресу возврата. ra это псевдоним x1.

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

Это действительно выглядит плохо. Именно поэтому Эрин Шеперд чрезвычайно критически отнеслась к проектировочным решениям, сделанным разработчиками RISC-V. Она пишет:

Упрощения RISC-V делают более простым декодер (т.е. фронтенд процессора), однако за это приходится расплачиваться большим количеством команд. Однако масштабирование ширины конвейера это сложная задача, в то время как декодирование немного (или сильно) необычных команд хорошо изучено (основные сложности возникают, когда определение длины команды нетривиально из-за своих бесконечных префиксов особо запущенным случаем является x86).

Однако благодаря сжатию команд и macro-op fusion можно изменить ситуацию к лучшему.

C.SLLI a1, 2      ; a1  a1 << 2C.ADD  a0, a1     ; a0  a0 + a1C.LW   a0, a0, 0  ; a0  [a0 + 0]C.JR   ra

Теперь команды занимают ровно столько же места в памяти, что и пример для ARM.

Так, а теперь давайте выполним Macro-op fusion!

Одно из условий RISC-V для разрешения слияния операций в одну это совпадение целевого регистра. Это условие выполняется для команд ADD и LW (load word, загрузить слово). Поэтому процессор превратит их в одну команду.

Если бы это условие выполнялось и для SLLI, то мы могли бы слить в одну все три команды. То есть процессор бы увидел нечто, напоминающее более сложную команду ARM:

LDR r0, [r0, r1, lsl #2]

Но почему мы не могли прописать эту сложную макро-операцию непосредственно в коде?

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

Однако если вместо этого мы будем изготавливать эти длинные полусложные команды внутри процессора, то никаких проблем не возникает. У процессора никогда не бывает одновременно в наличии более нескольких сотен команд. Поэтому если мы потратим на каждую команду, допустим, 128 бит, то это не создаст затруднений. Кремния по-прежнему будет хватать на всё.

Когда декодер получает обычную команду, он обычно превращает её в одну или несколько микро-операций. Такие микро-операции и есть команды, с которыми на самом деле работает процессор. Они могут быть очень широкими и содержат множество дополнительной полезной информации. Приставка микро звучит иронично, ведь они оказываются шире. Однако на самом деле микро означает, что они имеют ограниченное количество задач.

Macro-operation fusing немного переворачивает работу декодера вниз головой: вместо превращения одной команды в несколько микро-операций, мы берём много операций и превращаем их в одну микро-операцию.

То есть происходящее в современном процессоре может выглядеть довольно странно:

  1. Сначала он объединяет две команды в одну с помощью сжатия.
  2. Затем он разделяет их на две с помощью распаковки.
  3. Далее комбинирует их обратно в одну операцию при помощи macro-op fusion.

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

Ключевым аспектом перехода к микро-операциям является нужный уровень сложности:

  • Не слишком сложные, потому что в противном случае они не смогут завершиться за фиксированное количество тактов, выделенных на каждую команду.
  • Не слишком простые, потому что иначе мы просто потратим ресурсы процессора впустую. Выполнение двух микро-операций займёт в два раза больше времени, чем выполнение всего одной.

Всё началось с процессоров CISC. Intel стала разделять свои сложные команды CISC на микро-операции, чтобы их проще было уместить в конвейеры процессоров, как команды RISC. Однако в последующих конструкциях разработчики осознали, что многие команды CISC можно слить в одну умеренно сложную команду. Если команд для выполнения становится меньше, то работа будет закончена быстрее.

Получаемые преимущества


Мы обсудили множество подробностей, поэтому сейчас вам, должно быть, трудно понять, в чём смысл всех этих трудов. Зачем нужны все эти сжатия и слияния? Похоже, что из-за них выполняется много лишней работы.

Во-первых, сжатие команд совершенно не походит на сжатие zip. Слово сжатие немного неверное, потому что мгновенное сжатие или распаковка команды выполняются абсолютно просто. На это не тратится времени.

То же самое относится к macro-operation fusion. Хотя этот процесс может казаться сложным, подобные системы уже используются в современных микропроцессорах. Поэтому затраты, которые добавляет вся эта сложность, уже были оплачены.

Однако в отличие от проектировщиков ARM, MIPS и x86, приступая к проектированию своего ISA, создатели RISC-V знали о сжатии команд и macro-ops fusion. Благодаря различным тестам с первым минимальным набором команд они сделали два важных открытия:

  1. Программы RISC-V обычно занимают примерно столько же или меньше места в памяти, чем любая другая процессорная архитектура. В том числе и x86, который должен эффективно использовать память, учитывая, что это ISA CISC.
  2. Ему требуется выполнять меньше микро-операций, чем другим ISA.

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

Это заставило коллектив разработчиков RISC-V удворить усилия по реализации macro-operation fusion как фундаментальной стратегии RISC-V. В руководстве по RISC-V есть множество примечаний о том, с какими операциями можно выполнять слияние. Также в него внесены правки, упрощающие слияние команд, встречающихся в частых паттернах.

Благодаря малому ISA его проще изучать студентам. А это означает, что изучающему процессорные архитектуры студенту проще спроектировать собственный процессор, работающий на командах RISC-V. Стоит помнить, что и сжатие команд, и macro-op fusion использовать необязательно.

RISC-V имеет небольшой фундаментальный набор команд, реализация которого обязательна. Однако все остальные команды реализуются как части расширений. Сжатые команды это просто дополнительное расширение.

Macro-op fusion это просто оптимизация. Она не меняет поведения в целом, а поэтому её необязательно реализовывать в собственном процессоре RISC-V.

Стратегия проектирования RISC-V


RISC-V взял всё, что мы знаем сегодня о современных процессорах, и использовал эти знания в проектировании процессоров ISA. Например, мы знаем, что:

  • Сегодня у процессорных ядер есть сложная система прогнозирования ветвления.
  • Процессорные ядра суперскалярны, то есть выполняют множество команд параллельно.
  • Для обеспечения суперскалярности используется выполнение команд с изменением очерёдности (Out-of-Order execution).
  • Они имеют конвейеры.

Это означает, что такие особенности, как поддерживаемое ARM условное выполнение, больше не требуется. Поддержка этой функции в ARM отъедает биты от формата команд. RISC-V может сэкономить эти биты.

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

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

Именно из-за этого RISV-C не имеет и регистров состояния, ведь они создают зависимости между командами. Чем более независима каждая команда, тем проще выполнять её параллельно с другой командой.

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



На правах рекламы


Наша компания предлагает серверы не только с CPU от Intel, но и серверы с процессорами AMD EPYC. Как и для других типов серверов, огромный выбор операционных систем для автоматической установки, есть возможность установить любую ОС с собственного образа. Попробуйте прямо сейчас!

Подробнее..

Из песочницы Еще немного RISC-V

02.09.2020 20:13:34 | Автор: admin
Я занимаюсь программированием микроконтроллеров. И не только пишу для них программы, а по большей части программы для программаторов. И хотел поделиться небольшой радостью заработавшего мк. Вдруг кто-то сейчас мучается с gd32vfxx.

МК программирую самые разные. Это и всем известные stm, lattice, microchip, nuvoton, altera и т.д и экзотика типа azoteq, silicon works или freescale какого-нибудь 2003 года выпуска. Все они (ну, или почти все) разрабатывают свой собственный протокол, уникальный, которого нет больше ни у кого и он самый-самый лучший. Хотя и проходит под лозунгом универсального. И все это множество универсальных протоколов радует своим разнообразием. Если здесь и есть преувеличение, то совсем чуть.

Но сподвигла меня сюда написать вот эта недавняя статья. Ко мне как раз пришли образцы по разным семействам gd32 и, написав программы для программирования всех младших семейств, я пыхтел над семейством RISC-V. И увидел эту статью. Ну я и подумал, если кому-то интересен пересказ даташита, то может будет интересен и даташит по программированию программирования.

Младшие семейства программируются через JTAG и SWD ( за мелким исключением, где есть только SWD. gd32f130xx, например ). gd32vf, наоборот, имеет только JTAG. Но JTAG или SWD это только инструмент. Вколачивание этими инструментами происходит совсем по-разному. Если про gd32f можно почитать ARM Debug Interface Architecture Specification и там все довольно ясно описано, то про gd32vf размазано в RISC-V External Debug Support и The RISC-V Instruction Set Manual. И эти два труда с полпинка не осилишь. У последнего еще есть подзаголовок Volume II. Это насторожило. Значит есть еще свинья с номером 1 и, возможно, с номерами 3 и 4.

Согласно RISC-V External Debug Support доступ к регистрам можно осуществить двумя способами:

  1. Using Abstract Command
  2. Using Program Buffer

К памяти тремя:

  1. Using System Bus Access (у GD32VF отсутствует)
  2. Using Program Buffer
  3. Using Abstract Memory Access

Так как System Bus Access у GD32VF нет ( биты system bus accesses are supported в регистре sbcs ), а использовать программный буфер и писать туда assembler нет никакой радости, то очень удачно есть прекрасный способ обойтись командами. Сначала мне было совершенно непонятно, откуда эти чертовы умники берут номер регистра (Number of the register to access). В описании значения regno гордо значится: Number of the register to access, as described in Table 3.3.

image

Но где какой конкретно. Но потом я их обнаружил в соседнем даташите, упомянутом выше (The RISC-V Instruction Set Manual. Том 2) и солнце засияло.

Итак все заработало. Все действительно оказалось удобнее, чем у gd32f. С точки зрения залить в него код. Но это было только начало. Теперь мне нужно было написать для него монитор. Чтобы было еще быстрее, чем быстро. IAR не дал мне скачать с него свой WORKBENCH, хотя хвастался им вовсю. Я так и не нашел место, где нажать кнопку и скачать. Главный Китайский Менеджер, который присылал образцы, сказал, что они пользуют NucleiStudio и я скачал это и поставил. Мне казалось, что самая неудобная вещь, которой я пользовался это коромысло. Я не понимаю, как бырышни прошлого носили в нем воду и ходили потом сухими. Я был мокрый по самую макушку. Но NucleiStudio перещеголяло коромысло. В общем, после долгих мучений я умудрился распихать данные и функцию в нужные места памяти. Я уверен, что можно сделать это более изящно, но меня уже не хватило.

Выглядит это так.

Нужно найти оригинальный скрипт (я его нашел где-то в недрах этого eсlipse'а, будь он неладен), который содержит кучу непонятных каракулей и добавить туда описание нужных адресов

  . = 0x20000000;  .data_sect : { *(.data_sect) }  . = 0x20000400;  .a_sect : { *(.a_sect) }  . = 0x20000500;  .f_sect : { *(.f_sect) }

Все-таки насколько IAR'овский *.icf файл выглядит куда понятнее и проще! Но это дело привычки, наверное. Когда я искал этот злосчастный файл, Гугл меня все время уверял, что нет ничего мощнее и крепче и круче и гибче, чем линк скрипт. Возможно, Но я намучился. Ну да ладно. Распихано.

Тепер мне нужно только переставить PC и все заработает со страшной скоростью. Но монитор все равно не работал. И теперь абзац, ради которого я все это затеял. Который поможет будущим исследователям RISC-V-чуда.

Вот он.

Чтобы переставить PC нужно просто написать его значение в регистр dpc и потом при старте hart'а значение скопируется в PC. До этого места программа уже написала и прочитала кучу других регистров. С большим успехом, надо признать. А монитор не работает. Как оказалось, чтобы написать в dpc нужно туда записать, а потом оттуда прочитать. Почему это так, я не знаю. Я прочел еще раз все документацию и с волнением следил за судьбой героев. Но так и не нашел почему это так.

После этого все заработало. Единственное неудобство, чтобы прочитать что-то из памяти, нужно останавливать hart, а потом опять запускать. Это не очень удобно, но не страшно. Например, чтобы узнать, как там себя чувствует монитор и закончил ли он работу, нужно остановить hart, прочитать нужную память и, если нужно, запустить снова.

Надеюсь, что параграф про dpc сэкономит кому-нибудь пару часов времени и можно будет ничего не делать все это сэкономленное время.
Подробнее..

Изучаем RISC-V с нуля, часть 1 Ассемблер и соглашения

16.12.2020 02:21:17 | Автор: admin


Издеваться мы будем над микросхемой GD32VF103CBT6, являющейся аналогом широко известной STM32F103, с небольшим, но важным отличием: вместо ядра ARM там используется ядро RISC-V. Чем это грозит нам, как программистам, попробуем разобраться.
Кратко перечислю характеристики контроллера:
Напряжение питания: 2.6 3.6 В
Максимальная тактовая частота: 108 МГц
Объем ПЗУ (flash): 128 кБ
Объем ОЗУ (ram): 32 кБ
Объем Backup регистров (сохраняемых после сброса): 42 х 16 бит = 84 байта.
АЦП+ЦАП: 2 штуки АЦП по 10 каналов и 12 бит каждый плюс 2 ЦАП по 12 бит.
Разумеется, куча прочей периферии вроде таймеров, SPI, I2C, UART и т.д.


Итак, разбираться с контроллером предлагаю с самого нуля разработки платы и программирования ячеек памяти наэлектризованной иглой Хотя нет, это уж слишком, обойдемся ассемблером.
Все исходники, включая схему отладочной платы и примеры кода, можно найти здесь: https://github.com/COKPOWEHEU/GD32VF103_tutor


-1. Печатная плата



Первым делом почему именно самодельная плата. Во-первых, из спортивного интереса: если заказывать разработку, сборку и все остальное в Китае, то где же твой собственный вклад? Да и просто удовольствие от ручной работы никто не отменял. Во-вторых, на самодельной плате можно вывести все нужные разъемы и элементы управления. Скажем, очень удобно когда в наличии всегда есть хотя бы пара кнопок и пара светодиодов плюс отладочный разъем UART. А вот сложная периферия вроде энкодеров, датчиков или дисплеев на подобной плате не нужна, ее лучше подключать к разъемам.
В целом ничего особенно нового моя плата не представляет разведены кварцы, кнопки, светодиоды, разъемы (на угловом висят PA0 PA7 плюс пятивольтовое питание и земля, на двухрядном PB8 PB15 плюс трехвольтовое питание и земля). Наличие пятивольтового питания на разъеме позволяет как запитывать плату от внешнего источника в обход usb (например, от переходника usb-uart), так и наоборот, запитывать от самой платы внешние схемы, которым недостаточно 3.3 В.
Некоторое внимание заострю на разъеме UART, точнее на его распиновке. В отличие от большинства фирменных плат он у меня симметричный, то есть в середине земля, а по краям Rx и Tx. Таким образом можно не запоминать единственно верную распиновку, и соединять любую пару устройств простым шлейфом без необходимости перекрещивать провода в нем.
Естественно, ноги Boot0 и Boot1 выведены на джамперы.
Больше ничего интересного на плате нет.


0. Настройка программного окружения


Разработчики данного контроллера предлагают скачать с их сайта некую IDE. Но мы этого делать не будем: только консоль, текстовый редактор и хардкор.
Вот краткий список используемого софта. Что приятно, весь софт присутствует в репозитории, ничего качать с сайта GigaDevice не пришлось.


софт описание
gcc-riscv64-unknown-elf компилятор
stm32flash, dfu-util Прошивальщики через bootloader
kicad Трассировка плат
screen Отладка по UART

Отдельно остановлюсь на прошивке контроллера. Основных способов три:


(1). JTAG теоретически, самый правильный способ. Вот только подобрать правильное заклинание для него мне так и не удалось
(2). Bootloader.UART замыкаем вывод Boot0 на питание, ресетим контроллер (можно по питанию, можно вывести кнопку), после чего через stm32flash (да, прошивать можно утилитой, предназначенной для другого семейства!) прошиваем


$ stm32flash /dev/ttyUSB0 -w firmware.bin

Ну и наконец притягиваем Boot0 обратно к земле, снова ресетим и смотрим как работает (или как именно не работает) программа
(3). Bootloader.USB аналогичный предыдущему вариант, только вместо stm32flash используется dfu-util:


$ dfu-util -a 0 -d 28e9:0189 -s 0x08000000 -D firmware.bin

Только надо помнить, что для USB важна стабильность тактовой частоты, поэтому если для наших первых опытов хватит встроенного RC-генератора, для USB придется поставить внешний кварц.
Внимательный читатель может заметить, что утилите dfu-util передается некий адрес. Он соответствует началу реальной флеш-памяти контроллера. В нормальном режиме работы этот адрес отображается также и на нулевой адрес, и оттуда же начинается выполнение кода. Если же замкнуть Boot0 на питание, то на тот же нулевой адрес отображается либо Bootloader, либо оперативная память в зависимости от Boot1. В результате работать с контроллером можно вообще не задействуя его флеш, только из оперативки.


0,5. Как можно обойтись без небольшого извращения?


Совместимость с stm32f103 по выводам и части периферии дает некую надежду на возможность портирования кода оттуда без полной переработки. И действительно, простая периферия вроде SPI или DMA (без прерываний!) вполне успешно запустилась после небольших танцев с бубном.
В работе с регистрами стоит отметить, что GigaDevice предпочитают использовать макросы, в отличие от STMicroelectronics, которые использовали структуры. Плюс нумерация с нуля вместо единицы. Приведу пару примеров:


GD32VF103 STM32F103
RCU_APB2EN |= RCU_APB2EN_SPI0EN; RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
SPI_DATA(SPI_NAME) = data; SPI1->DR = data;
DMA_CHCNT(LCD_DMA, LCD_DMA_CHAN) = size; DMA1_Channel3->CNDTR = size;

Здесь сразу бросается в глаза что в RISCV регистр SPI_DATA представлен макросом, в который можно подставить номер используемого модуля SPI. И это очень классно! Можно где-нибудь в заголочнике объявить что используем SPI0, что он висит на DMA0 на канале 2 и препроцессор сам все подставит без всяких накладных расходов.
В результате на основе вот этого проекта (http://personeltest.ru/aways/habr.com/ru/post/496046/ исходный код тут: https://github.com/COKPOWEHEU/stm32f103_ili9341_models3D) получилась такая демка:



Исходный код тут: https://github.com/COKPOWEHEU/RISCV-ili9341-3D


1. Первый проект


Стартует контроллер сразу после подачи питания, но за неимением стандартных средств ввода-вывода, привычных любому программисту экрана, клавиатуры или хотя бы терминала придется приложить некоторые усилия чтобы вообще определить живой камень или нет. Для этого традиционно используется мигание светодиодом (привет отладочным платам, на которых его нет!).
Однако и это не так просто. В целях экономии энергии сразу при включении вся периферия, включая логику портов ввода-вывода, отключена. Чтобы ее включить надо выставить в регистре RCU_APB2EN (адрес 0x40021018) бит RCU_APB2EN_PxEN, где x буква порта. В моем случае, поскольку светодиоды висят на PB5 PB7 это бит RCU_APB2EN_PBEN (3-й бит, он же битовая маска 0x8). Причем было бы неплохо сохранить состояния всех остальных битов.


la  a5, 0x40021018lw  a4, 0(a5)  ori   a4, a4, 8sw  a4, 0(a5)

Регистры a4, a5 взял просто от балды, никакого хитрого умысла тут нет. Можно было взять любые другие.
Но если вы покажете такой код программисту, он тут же захочет дать вам по рукам за использование магических констант. Чтож, исправим это:


.equ RCU_APB2EN, 0x40021018.equ RCU_APB2EN_PBEN, (1<<3)//RCU_APB2EN |= RCU_APB2EN_PBEN  la a5, RCU_APB2EN  lw    a4, 0(a5)    ori a4, a4, RCU_APB2EN_PBEN  sw    a4, 0(a5)

Вот теперь код вполне пригоден для чтения. Но он по-прежнему не делает ничего полезного, ведь недостаточно логику портов ввода-вывода включить, ее еще нужно настроить. Согласно документации, режим работы порта задается четырьмя битами регистра GPIOх_CTL. Но поскольку ножек ввода-вывода у нас 16, получается 64 бита, а регистры всего 32-битные. Поэтому вслед за STmicroelectronics разработчики нашего контроллера разбили эту группу битов на два регистра. Для порта B это будут GPIOB_CTL0 и GPIOB_CTL1: в первом настраиваются порты PB0 PB7, во втором PB8 PB15. Четыре бита соответствуют 16 возможным состояниям, из которых нас пока интересует только обычный выход на максимальной скорости (на отладочной плате нет смысла экономить энергию). Также сразу укажем, что светодиоды висят на 5 7 выводах, а кнопки на 0 и 1:


.equ GPIOB_CTL0,        0x40010C00.equ GPIO_MASK,     0b1111.equ GPIO_PP_50MHz, 0b0011.equ RLED, 5.equ YLED, 6.equ GLED, 7.equ SBTN, 0.equ RBTN, 1

Как было сказано раньше, нулевому выводу соответствуют 4 бита регистра GPIOB_CTL0: [0, 1, 2, 3], первому биту [4, 5, 6, 7]. Соответственно, за интересующий нас 5-й бит, на котором висит красный светодиод, отвечают [20, 21, 22, 23]. Ну а чтобы не высчитывать биты вручную, заставим заниматься этим препроцессор. Если бы мы писали на Си, этот код выглядел бы так:


GPIOB_CTL0 = (GPIOB_CTL0 &~(0b1111<<(RLED*4))) | 0b0011 << (RLED*4);

То есть сначала нужные нам 4 биты затираются нулями, а потом на их место побитовым ИЛИ записывается новое значение. Но мы пока пишем не на Си, а на ассемблере, тут эта строчка получается чуть длиннее:


la a5, GPIOB_CTL0lw  a4, 0(a5)  la  a6, ~(GPIO_MASK << (RLED*4))  and a3, a4, a6  la  a4, (GPIO_PP_50MHz << (RLED*4))  or    a4, a4, a3sw  a4, 0(a5)

Но и этого пока недостаточно для мигающего диода. Мы включили порт, настроили его. Осталось записать туда 0 или 1, подождать какое-то время и записать другое значение и так по кругу. За выходное значение порта отвечает регистр GPIOB_OCTL, который мы будем читать, XOR`ить 5-й бит и записывать обратно. Ну а задержку реализуем тупо вычитанием единицы из регистра счетчика.


Собственно, вот весь код:
.equ RCU_APB2EN, 0x40021018.equ RCU_APB2EN_PBEN, (1<<3).equ GPIOB_CTL0, 0x40010C00.equ GPIO_MASK, 0b1111.equ GPIO_PP_50MHz, 0b0011.equ GPIOB_OCTL, 0x40010C0C.equ RLED, 5.equ YLED, 6.equ GLED, 7.equ SBTN, 0.equ RBTN, 1.text.global _start_start:  //RCU_APB2EN |= RCU_APB2EN_PBEN  la a5, RCU_APB2EN  lw    a4, 0(a5)    ori a4, a4, RCU_APB2EN_PBEN  sw    a4, 0(a5)  //GPIOB_CTL0 = (GPIOB_CTL0 & (0b1111<<RLED*4)) | 0b0011 << (RLED*4)  la a5, GPIOB_CTL0  lw    a4, 0(a5)    la  a6, ~(GPIO_MASK << (RLED*4))    and a3, a4, a6    la  a4, (GPIO_PP_50MHz << (RLED*4))    or    a4, a4, a3  sw    a4, 0(a5)MAIN_LOOP:  //GPIO_OCTL(GPIOB) ^= (1<<RLED)  la a5, GPIOB_OCTL  lw    a4, 0(a5)    xori    a4, a4, (1<<RLED)  sw    a4, 0(a5)  //sleep  la a5, 200000sleep:  addi  a5, a5, -1  bnez a5, sleep  j MAIN_LOOP

Число 200000 в цикле задержки было подобрано экспериментально. Именно столько итераций нужно чтобы светодиод мигал не слишком быстро и не слишком медленно.
Сразу же отмечу, что при ручном управлении ножками порта использовать OCTL не рекомендуется, поскольку работа с ним возможна только в режиме чтение-модификация-запись. Плюс в середину может вклиниться прерывание (о чем поговорим позже), но для отладочной мигалки сойдет. Правильным же способом является использование регистра GPIOx_BOP: старшие 16 бит отвечают за стирание битов OCTL в 0, а младшие за выставление в 1. Есть еще регистр GPIOx_BC, эквивалентный старшим битам BOP, так что я не слишком понимаю зачем он нужен. Для оптимизации разве что. Причем важно отметить, что влияние на эти регистры оказывает только запись единиц. Запись нулей ни на что не влияет. То есть если мы запишем


.equ GPIOB_BOP, 0x40010C10la a5, GPIOB_BOP  la a4, (1<<YLED) | (1<<RLED*16)sw a4, 0(a5)

то желтый светодиод загорится, а красный погаснет.
Но повторюсь, в ассемблерных примерах мы этого делать не будем, тут хватит OCTL`а.
Скомпилировать полученный код можно стандартным компилятором gcc:


$ iscv64-unknown-elf-gcc -march=rv32imac -mabi=ilp32 -mcmodel=medany -nostdlib main.S -o main.elf

Флаги в начале указывают точную архитектуру и расширения нашего ядра, флаг -nostdlib специфичен для компилятора Си (а не ассемблера) и говорит не подставлять стандартный Си`шный код инициализации памяти. Теперь полученного эльфа надо сконвертировать в бинарный формат чтобы утилита прошивки могла с ним работать. Также дизассемблируем его чтобы посмотреть, как препроцессор развернет наши инструкции, адреса и константы (это более актуально для кода на Си, но и нам сгодится), ну и собственно прошить:


$ riscv64-unknown-elf-objcopy -O binary main.elf main.bin$ riscv64-unknown-elf-objdump -D -S main.elf > main.lss$ stm32flash /dev/ttyUSB0 -w main.bin

Для удобства я позволил себе оформить все эти команды в общий makefile.
Не забывайте, что в современных системах доступ к COM и USB портам считается опасным действием и разрешен только руту. Впрочем, повседневное написание прошивок для платки под рутом еще опаснее, поэтому лучше добавить своего пользователя в группу dialout. Если же вы предпочтете пользоваться прошивкой по USB через dfu-utils, нужно прописать правило udev для устройства 28e9:0189.


2. Работа с кнопкой


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


Заголовок спойлера
.equ GPIO_MASK,     0b1111 # маска для стирания ненужных битов#input.equ GPIO_ANALOG,       0b0000 # аналоговый вход.equ GPIO_HIZ,      0b0100 # цифровой вход.equ GPIO_PULL,     0b1000 # вход с подтяжкой к питанию или земле.equ GPIO_RESERVED, 0b1100 # зарезервировано, не использовать#output, GPIO, ручное управление.equ GPIO_PP10,     0b0001 # push-pull выход, максимальная частота 10 МГц.equ GPIO_PP2,      0b0010 # -//- частота 2 МГц.equ GPIO_PP50,     0b0011 # -//- частота 50 МГц.equ GPIO_OD10,     0b0101 # open-drain выход, максимальная частота 10 МГц.equ GPIO_OD2,      0b0110 # -//- частота 2 МГц.equ GPIO_OD50,     0b0111 # -//- частота 50 МГц#output, AFIO  альтернативная функция, портом управляют аппаратные модули.equ GPIO_APP10,        0b1001 # push-pull выход, максимальная частота 10 МГц.equ GPIO_APP2,     0b1010 # -//- частота 2 МГц.equ GPIO_APP50,        0b1011 # -//- частота 50 МГц.equ GPIO_AOD10,        0b1101 # open-drain выход, максимальная частота 10 МГц.equ GPIO_AOD2,     0b1110 # -//- частота 2 МГц.equ GPIO_AOD50,        0b1111 # -//- частота 50 МГц

push-pull это режим порта, при котором внутренняя схема замыкает вывод либо на землю, либо на питание. То есть на выходе порта всегда либо 0, либо 1 в зависимости от содержимого регистра GPIOx_OCTL.open-drain это режим, при котором внутренняя схема может замыкать только на землю, но не на питание. То есть на выходе либо 0, либо неизвестно что. Такой режим используется для соединения выводов в монтажное И либо, скажем, для I2C шины. Управляется регистром OCTL.pull-up, pull-down это дополнительные подтягивающие резисторы, подключаемые либо между выводом и питанием (pull-up), либо между выводом и землей (pull-down). Они также управляются регистром GPIOx_OCTL.На что влияет частота порта я не особенно особенно. Наверное, на максимальную частоту переключения и соответственно на потребляемый ток. То есть если хотите сделать устройство более экономичным, частоту снижаем. Я же здесь этого делать не буду.

3. Регистры и функции


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


псевдоним имя назначение сохранение
zero x0 Вечный и неизменный ноль n/a
ra x1 Адрес возврата нет
sp x2 Stack pointer, указатель стека да
gp, tp x3, x4 Регистры для нужд компилятора. Лучше их вообще не использовать n/a
t0-t6 x5-x7, x28-x31 Временные регистры нет
s0-s11 x8, x9, x18-x27 Рабочие регистры да
a0-a7 x10-x17 Аргументы функции нет
a0, a1 x10, x11 Возвращаемое значение функции нет

Регистр zero предназначен для получения нуля, либо для сбрасывания в него результата вычисления, которое нам не нужно. Аналог /dev/zero и /dev/null в одном лице
О регистрах ra и sp поговорим чуть-чуть позже.
Глубокий смысл регистров gp и tp я так и не понял. Вроде бы используются компиляторами или даже транслятором ассемблера для оптимизаций, плюс для многопоточных задач и разделения прав доступа. В общем, лучше их не трогать.
Временные регистры t0 t6 предназначены для хранения промежуточных результатов вычислений и не обязаны сохраняться при вызове функций. Это сделано для того чтобы простые функции не заморачивались сохранением всех регистров на стеке, а потом еще и восстановлением.
Рабочие регистры s0 s11 напротив сохраняются при вызове функций. Они нужны для обратной задачи воспользоваться ранее вычисленным значением и как-то объединить его с результатом функции и при этом опять же обойтись без лишнего использования стека.
Регистры обмена a0 a7 используются для передачи параметров в функцию и обратно. Очевидно, функция их должна менять, так что после вызова функции их значения не сохраняются. Интересно, что для возвращаемого значения используются только a0 и a1, а портить функции разрешено все.


О соглашениях использования регистров поговорили, пора функцию написать, а потом и вызвать. Примером функции будет задержка в 200`000 циклов, которая пока что вписана прямо в основной цикл программы. Давайте ее оформим как функцию. Принимать она должна время (в циклах) и ничего не возвращать. Отлично, значит аргумент будет храниться в a0. Помимо него можно как угодно портить a1 a7 а также t0 t6, но нам это пока без надобности. А вот остальные регистры портить нельзя, не забываем об этом.
Главное особенностью функций является то, что их код находится только в одном месте, но может вызываться из разных. Как же нам узнать в какую именно из точек вызова вернуться? Для этого соглашением предусмотрен специальный регистр ra, в который при выполнении соответствующей инструкции (jal, jalr или псевдоинструкции call, которая разворачивается в одну из предыдущих) происходит сохранение текущего адреса выполнения, после чего выполнение переходит на функцию. Соответственно, когда функция завершается, ей достаточно перейти по адресу, хранящемуся в ra при помощи инструкции jr ra или обертки ret. Так и запишем, не забыв заменить в теле функции регистр a5 на a0:


...    la a0, 200000  call sleep...sleep:  addi  a0, a0, -1  bnez a0, sleepret

4. Стек


Но что же делать если мы пишем рекурсивную функцию, которая должна вызывать сама себя? Ведь все копии будут пользоваться одними и теми же регистрами, что явно не пойдет алгоритму на пользу. Или что делать если функция использует большой объем временных данных, который в регистрах просто не помещается?
Для решения обеих этих проблем умными людьми была придумана концепция стека, то есть специальным образом организованной оперативной памяти, в которой каждой функции передается начало свободного участка оперативки, и она резервирует непрерывный кусок данных под свои потребности. Когда она вызывает другую функцию, она передает ей начало оставшейся свободной памяти, та резервирует что-то себе и так далее. В результате распределение памяти напоминает матрешку: внешней функции доступна вся память, функциям первого уровня вложенности вся, кроме блока, занятого внешней, функциям второго то, что осталось от первых и так далее. После завершения работы каждая функция возвращает использованную память вызывающей стороне. Такой подход очень эффективен по скорости и памяти, но обладает рядом ограничений, которые нам пока мешать не будут.
Помимо абстрактных данных на стек можно сбрасывать регистры, которые могут быть испорчены вызываемыми функциями.
Из соображений стандартизации была выработана определенная логика работы стека: начинаться он должен с максимально доступного адреса и расти вниз, то есть в сторону меньших адресов. Адрес последнего элемента хранится в специальном регистре sp. В нашем GD32VF103 оперативная память начинается с адреса 0x2000'0000 и насчитывает 32 килобайта, то есть максимально доступный адрес 0x2000'8000, от него и будет расти наш стек.
Предположим, мы хотим положить в него байты 0x12, потом 0x34 и 0x56, а потом снять последний элемент со стека. Тогда логика их распределения будет следующей:


адрес Шаг 0 Шаг 1 Шаг 2 Шаг 3 Шаг 4
0x2000`8000 sp
0x2000`7FFF 0x12 sp 0x12 0x12 0x12
0x2000`7FFE 0x34 sp 0x34 0x34 sp
0x2000`7FFD 0x56 sp 0x56

Обратите внимание, что на 4 шаге значение 0x56 никуда не делось, просто оно вывалилось за пределы стека и стало обычным мусором.
Примерно так работает стек в идеальном мире, но реальность накладывает свои ограничения. Так, у RISC-V имеются проблемы в работе с невыровненными данными, то есть адрес каждой ячейки должен быть кратен ее размеру. Нельзя, например, записать 4-байтное число по адресу 0x2000'0002 только 0x2000'0000 или 0x2000'0004. Впрочем, основное для чего используется стек хранение регистров при вызове функций, а регистры у нас как раз 4-байтные, так что просто сделаем так чтобы и стек принимал только 4-байтные значения.
Вторая проблема прерывания. Мы до них пока не добрались, но рано или поздно доберемся и не хотелось бы получить граблями по лбу на ровном месте. Дело в том, что прерывания, как следует из названия, прерывают нормальный ход программы и заставляют контроллер в спешном порядке прыгать на специальную функцию обработчика прерываний. А произойти это может в любой момент времени, например между записью значения на стек и изменением sp. Что хуже всего, в системах без разделения прав доступа (а мы занимаемся именно такой) стек у основного кода и обработчиков прерываний общий. Это значит, что основной код должен быть написан так, чтобы прерывание, где бы оно ни возникло, не помешало его работе. В случае стека для этого достаточно всего лишь правильно определить порядок операций: мы сначала резервируем место на стеке (уменьшаем sp) и только потом записываем туда данные. С чтением аналогично: сначала данные читаем и только потом освобождаем память увеличиваем sp.
Поскольку операции это частые, оформим их в виде макросов:


.macro push val  addi sp, sp, -4  sw \val, 0(sp).endm.macro pop val  lw \val, 0(sp)  addi sp, sp, 4.endm

Ах да, и не забудем в начале программы инициализировать значение sp верхней границей памяти:


la sp, 0x20008000

Теперь, если мы хотим оформить нашу функцию sleep совсем по фун-шую, можно сделать так:


sleep:  push ra  push s0  mv s0, a0sleep_loop:  addi  s0, s0, -1    bnez s0, sleep_loop  pop s0  pop raret

Правда, смысла в этом именно для функции sleep немного: ей хватает регистра a0. Поэтому для демонстрации давайте сделаем вычисление через рекурсию факториа НЕТ, нормальные люди факториал через рекурсию не вычисляют! Вместо этого сделаем какой-нибудь световой эффект на доступных нам диодах. Честно говоря, я сам не знаю по какому именно алгоритму они будут переключаться, но тем не менее рекурсия там используется. Пример получился немного странный, так что приводить его здесь я не буду.
Если развернуть макросы push и pop, можно увидеть, что в начале и конце функции регистр sp меняется по 4 раза. Налицо бесполезный расход машинного времени! Еще больше он станет если мы захотим положить на стек какой-нибудь большой массив данных. Но ведь нас никто не обязывает пользоваться именно этими макросами, мы можем сразу зарезервировать нужный объем памяти, даже с запасом, а потом класть туда данные, не заботясь об sp. Например, это можно сделать так:


func:  addi sp, sp, -16  sw ra, 12(sp)  sw s0, 8(sp)  sw s1, 4(sp)  sw s2, 0(sp)  lw s2, 0(sp)  lw s1, 4(sp)  lw s0, 8(sp)  lw ra, 12(sp)  addi sp, sp, 16ret

Стоит отметить, что в отличие от идеального стека, которому можно либо положить значение на вершину, либо снять оттуда, но нельзя влезть в середину, наш стек реализован поверх обычной оперативной памяти, то есть мы можем там хранить обычные локальные переменные. Единственная проблема их адрес зависит от значения sp на момент вызова функции, да плюс еще сама функция этот sp меняет. Чтобы уменьшить риск ошибки при подобном относительном доступе, соглашением предусматривается еще один специальный регистр fp frame pointer (он же s0, так что сохранять его придется). При входе в функцию в него сохраняют значение sp, после чего больше не трогают. Сам sp, как и раньше, используется для работы со стеком, а вот fp служит опорной точкой, относительно которой вычисляются адреса локальных переменных.
Допустим, нам нужно выделить на стеке 5 переменных плюс регистры ra, fp и, например, s1, s2 и s3. Тогда код сохранения и восстановления может выглядеть так:


func:  addi sp, sp, -10*4  sw fp, 0(sp)  addi fp, sp, 10*4  sw ra, -9*4(fp)  sw s1, -8*4(fp)  sw s2, -7*4(fp)  sw s3, -6*4(fp)  sw zero, -5*4(fp) #  data[0]  sw zero, -4*4(fp) #  data[1]  sw zero, -3*4(fp) #  data[2]  sw zero, -2*4(fp) #  data[3]  sw zero, -1*4(fp) #  data[4]  lw s3, -6*4(fp)  lw s2, -7*4(fp)  lw s1, -8*4(fp)  lw ra, -9*4(fp)  addi sp, fp, -10*4  lw fp, 0(sp)  addi sp, sp, 10*4ret

Обратите внимание что не-регистровые данные мы просто инициализировали нулями (в реальном коде вместо этого можно использовать более осмысленные числа или не инициализировать их вообще), но явным образом освобождать не стали. Вместо этого мы сначала сохранили значение sp в fp, а в конце восстановили. Приятным бонусом оказывается то, что количество push'ей может даже не равняться количеству pop'ов и, если повезет, никто не пострадает.
Впрочем, работа через fp полезна скорее для людей. Компилятору же не составляет никакого труда все высчитывать через sp, а регистр fp использовать как обычный s0.


5. Храним данные на флешке


Использование стека отлично подходит для локальных переменных, которые не будут сохраняться между вызовами функций. Но иногда возникает необходимость использовать глобальные переменные и константы, доступные любой функции.
Начнем с простого хранения массива констант. Для этого используется та же флеш-память, что и для исполняемого кода. Для удобства ее иногда выделяют в отдельный сегмент .rodata, но пока мы этим заниматься не будем. Просто объявим в конце нашей программы массив из 4 значений:


.textled_arr:  .short (0<<GLED | 0<<YLED | 1<<RLED)  .short (0<<GLED | 1<<YLED | 0<<RLED)  .short (1<<GLED | 0<<YLED | 0<<RLED)  .short (0<<GLED | 1<<YLED | 0<<RLED)led_arr_end:

Директива .short означает, что элемент памяти короткое целое размером 2 байта. О других директивах резервирования места я расскажу чуть позже.
Ну и заменяем предыдущую рекурсивную мигалку на последовательное чтение из этого массива с выводом на светодиоды:


MAIN_LOOP:  la s0, GPIOB_OCTL  lh s1, 0(s0)  la s2, ~(1<<GLED | 1<<YLED | 1<<RLED)  la s3, led_arr  la s4, led_arr_endled_loop:  lh t0, 0(s3)  and s1, s1, s2  or s1, s1, t0    sh s1, 0(s0)  la a0, 300000  call sleep  addi s3, s3, 2  bltu s3, s4, led_loop  j MAIN_LOOP

Здесь стоит отметить две вещи. Во-первых, замена lw на lh при работе с GPIOB_OCTL. Поскольку элементы данных в массиве 2-байтные, как и регистр GPIOB_OCTL, старшие байты вполне можно не писать, это немного сэкономит память. Во-вторых, увеличение адреса в массиве не на 1, а на размер элемента. Если бы мы использовали 32-битные константы, увеличивать пришлось бы на 4 байта, а если байтовые то на 1.


6. Переход к оперативке


В прошлой главе я обмолвился о сегменте .rodata, еще раньше без объяснений ввел сегмент .text. Теперь введем еще два сегмента: .data и .bss. Они оба предназначены для хранения глобальных переменных, но первый инициализируется при включении заранее заданными данными, а второй нет. Причем с .bss есть еще некая неопределенность: в некоторых источниках его инициализировать и не надо вообще, в других надо обязательно, причем нулями. Хотя и не хочется заниматься бесполезным копированием нулей, для совместимости с Си сделать это придется.
Итак, берем предыдущий пример и вместо .text указываем .data, но не спешим прошивать контроллер. Для начала заглянем в дизассемблерный файл res/firmware.lss чтобы убедиться что массив начинается именно из начала оперативной памяти, 0x2000'0000:


000110ea <__DATA_BEGIN__>:   110ea:   0020

Упс, что-то пошло не так. Очевидно, ассемблер не знает где у нашего контроллера начало оперативной памяти. Чтобы ему это указать, создадим файл lib/gd32vf103cbt6.ld, в котором пропишем следующее:


MEMORY{    flash (rxai!w) : ORIGIN = 0x00000000, LENGTH = 128K    ram (wxa!ri) : ORIGIN = 0x20000000, LENGTH = 32K}SECTIONS{  .text : {  } > flash  .data : {  } > ram  .bss : {  } > ram}

То есть сначала мы указываем начало определенной памяти и ее размер, а потом принадлежность секций к той или иной памяти. Теперь этот файл нужно подсунуть компилятору (точнее, линкеру) при помощи ключа -T:


riscv64-unknown-elf-gcc -march=rv32imac -mabi=ilp32 -mcmodel=medany -nostdlib -T lib/gd32vf103cbt6.ld src/main.S -o res/main.elf

Вот теперь данные попали именно туда, куда надо:


20000000 <led_arr>:20000000:   0020

Но прошивать полученным кодом контроллер все еще рано, ведь мы знаем, что оперативная память тем и отличается от постоянной, что может не сохраняться при отключении питания. Это значит, что перед работой основного кода нам в эту память надо сначала скопировать данные. Для этого компилятор заботливо сохранил наши константы в безымянном сегменте сразу после .text, это можно увидеть если посмотреть непосредственно res/firmware.hex файл.
:08 0000 00 2000 4000 8000 4000 D8
Для большего удобства доступа к этим данным добавим в .ld-файл немного магии


Заголовок спойлера
MEMORY{    flash (rxai!w) : ORIGIN = 0x00000000, LENGTH = 128K    ram (wxa!ri) : ORIGIN = 0x20000000, LENGTH = 32K}SECTIONS{  .text : {    *(.text*)    *(.rodata*)    . = ALIGN(4);  } > flash  .data : AT(ADDR(.text) + SIZEOF(.text)){    _data_start = .;    *(.data*)    . = ALIGN(4);    _data_end = .;  } > ram  .bss : {    _bss_start = .;    *(.bss*)    . = ALIGN(4);    _bss_end = .;  } > ram}PROVIDE(_stack_end = ORIGIN(ram) + LENGTH(ram));PROVIDE(_data_load = LOADADDR(.data));

Теперь мы можем использовать область флеш-памяти начиная с _data_load чтобы инициализировать собственно оперативку. Ах да, раз уж у нас есть внешний файл с адресами памяти, вынесем туда же стек:


_start:  la sp, _stack_end#copy data section  la a0, _data_load  la a1, _data_start  la a2, _data_end  bgeu a1, a2, copy_data_endcopy_data_loop:  lw t0, (a0)  sw t0, (a1)  addi a0, a0, 4  addi a1, a1, 4    bltu a1, a2, copy_data_loopcopy_data_end:# Clear [bss] section  la a0, _bss_start  la a1, _bss_end  bgeu a0, a1, clear_bss_endclear_bss_loop:  sw zero, (a0)  addi a0, a0, 4    bltu a0, a1, clear_bss_loopclear_bss_end:

Вот теперь наконец наш массив будет корректно читаться из оперативной памяти.
При создании переменных в секции .bss было бы странно присваивать им какие-то значения (хотя никто не запрещает, просто использованы они не будут). Вместо этого можно использовать директиву-заполнитель .comm arr, 10 (для переменной arr размером 10 байт). Стоит отметить, что использовать ее можно в любой секции, причем резервировать данные она будет только в .bss. Ниже приведены еще примеры объявления переменных различных размеров:


.byte 1, 2, 3 # три однобайтные переменные со значениями 0x01, 0x02 и 0x03.short 4, 5 # две двухбайтные переменные со значениями 0x0004 и 0x0005.word 6, 7 # две четырехбайтные переменные 0x0000'0006 и 0x0000'0007.quad 100500 # одна восьмибайтная переменная 0x0000'0000'0001'8894.ascii "abcd", "efgh" # две переменные по 4 символа (обратите внимание! Терминирующий ноль не добавляется).asciz "1234" # строка "1234\0" - с терминирующим нулем на конце. Обратите внимание что в имени директивы только одна буква 'i'.space 10, 20 # ОДНА переменная размером 10 байт, каждый из которых равен 20. Если второй аргумент опущен, переменная по умолчанию заполняется нулями

Внезапный конец


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

Подробнее..

Изучаем RISC-V с нуля, часть 2 прерыванияи стыковка с Си

16.12.2020 20:13:35 | Автор: admin


Продолжаем погружаться в строение контроллера GD32VF103CBT6. Теперь рассмотрим как он может обрабатывать прерывания работать под управлением высокоуровневого кода.
Первая часть здесь


7. Подключение UART


При выборе задачки на рекурсию я столкнулся с проблемой, что трех светодиодов немного недостаточно для отладки сложных алгоритмов. Поэтому давайте добавим полноценный отладочный интерфейс, к которому можно было бы прицепиться терминальной программой вроде screen и общаться с контроллером при помощи обычного текста. Для этого воспользуемся тем же USART0, по которому происходит прошивка.
Небольшое отступление, связанное с терминологией: USART (универсальный синхронно-асинхронный приемо-передатчик), как следует из названия, умеет работать как в синхронном, так и в асинхронном режимах. А еще в куче других, но они нам пока не интересны. На практике я ни разу не видел его работу в синхронном режиме. Поэтому наравне с USART буду использовать обозначение UART, подразумевая именно асинхронный режим.
Как и с портами, первым делом надо разрешить работу данного модуля. Смотрим в документации, какому биту RCU он соответствует и видим 14-й бит RCU_APB2EN_USART0EN. Следующая особенность GD32VF103 вслед за STM, это необходимость переключения режима работы ножки вывода с обычного GPIO на альтернативную функцию, активируемую значением GPIO_APP50 = 0b1011. Причем только на выход: входная ножка остается обычным GPIO_HIZ. Ах да, в RCU саму возможность работы альтернативных функций тоже придется включить. Делается это 0-м битом, он же RCU_APB2EN_AFEN.
А вот сама настройка UART не представляет ничего сложного: в регистре USART0_CTL0 мы просто разрешаем его работу (USART_CTL0_UEN), включаем передатчик (USART_CTL0_TEN) и приемник (USART_CTL0_REN), после чего в регистре USART0_BAUD задаем скорость обмена как делитель тактовой частоты. Если точнее, не тактовой частоты, а только частоты шины APB2, но пока мы не разбирались с тактированием, частоты всех шин у нас одинаковые и равны 8 МГц:


  la t0, USART0_BASE    li t1, 8000000 / 9600  sw t1, USART_BAUD_OFFSET(t0)    li t1, USART_CTL0_UEN | USART_CTL0_REN | USART_CTL0_TEN  sw t1, USART_CTL0_OFFSET(t0)  la t0, USART0_BASE    li t1, 'S'  sb t1, USART_DATA_OFFSET(t0)

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


$ screen /dev/ttyUSB0 9600

чтобы убедиться что буква 'S' принимается. Для выхода из screen, надо нажать ctrl+a, потом k, потом y.


8. Обмен строками


Обычно передача данных между устройствами не ограничивается одним байтом. А в случае UART можно даже еще больше конкретизировать: обмен идет строками. Значит, и функции обмена будем затачивать на работу со строками. Проблема тут в том, что UART штука медленная: в предыдущем примере мы выставляли всего 9600 бит в секунду, в жизни еще часто встречается 115200. По сравнению даже с 8 МГц частоты ядра, а тем более с 108 МГц это очень долго, значит нам придется подождать пока модуль передаст один байт, чтобы тут же подложить ему следующий. Для этого служит флаг USART_STAT_TBE (Transmit data buffer empty) регистра USART0_STAT.
Таким образом псевдокод на Си будет выглядеть следующим образом:


void uart_puts(char *str){  while(str[0] != '\0'){    while(! (USART0_STAT & USART_STAT_TBE) ){}    USART0_DATA = str[0];    str++;  }}

На ассемблере он выглядит аналогично, единственное что займет немножко больше места, у меня вышло 14 строк.
Таким же способом пишется функция чтения строки, только флаг будем проверять USART_STAT_RBNE (Read data buffer not empty), а конец ввода определять по одному из символов '\r' или '\n' либо по переполнению буфера.
В качестве примера работы с UART можно ввести с терминала строку, преобразовать все строчные буквы с прописные, после чего отправить обратно.


9. Прерывания


Проблема только что описанного способа работы с периферией в том, что на время передачи или приема блокируется вообще все. Причем если на передачу мы можем хотя бы предсказать, какое время это займет, то на прием не можем даже этого. Мало ли, вдруг пользователь уснул за клавиатурой?
Простейший способ это исправить переписать функции, чтобы они при неготовности интерфейса возвращали управление основной программе, а не крутились в цикле. Такой подход называется опросом (polling) и вполне себе используется для медленных и низкоприоритетных устройств. То есть тех, которые могут потерпеть пока до них дойдет очередь опроса. Реализация такого подхода для UART сложности не представляет, поэтому я ее приводить не буду, а вместо этого рассмотрю более сложный способ использование прерываний.
Этот способ заключается в том, что при наступлении определенного события, от ошибки деления на ноль до получения здоровенного пакета по USB, генерируется сигнал прерывания. И если это прерывание разрешено локально, разрешено глобально, разрешено на данной периферии и не перекрыто более приоритетным прерыванием (да-да, выполнены должны быть все эти условия), то контроллер спешно бросает выполнение основного кода и переходит к выполнению обработчика прерываний. К чему это может привести?
Самый очевидный плюс скорость реакции на внешнее событие, которая может составлять единицы тактов. Но рядом со вкусным сыром прячутся грабли: если прерываний слишком много, у контроллера просто не останется ресурсов на выполнение основного кода. Впрочем, такая ситуация в любом случае не должна происходить в грамотно спроектированной системе.
Особенность RISC-V в отличие от многих других контроллеров: поскольку ядро знать не знает что такое стек, оно не может аппаратно сохранить туда контекст выполнения, то есть адрес возврата (нам же нужно знать откуда продолжить работу после завершения обработки прерывания), используемые регистры и все остальное. Поэтому программисту приходится самому думать, куда это все сохранить и при этом не попортить данные основной программы. По большому счету варианта три: либо использовать общий стек, либо раздельный, либо специальные регистры. Специальных регистров архитектурой не предусмотрено, вводить еще одну область памяти и способы работы с ней сложно, поэтому будем пользоваться общим стеком. Нет, в более сложных системах с разделением прав доступа, с планировщиком задач и вообще операционной системой, выделение специального ядерного стека встречается часто. Ну правда, если кривая пользовательская программа убьет себе стек и данные, это плохо, но только для нее. Но если она убьет стек и данные не только себе, но и ядру, пострадают все.
Еще раз напоминаю, что при использовании общего стека надо следить за его целостностью, то есть чтобы запись и чтение никогда не шли ниже sp.
Переходим собственно к прерываниям, а также локушкам и другим исключительным ситуациям. В контроллере GD32VF103 за них отвечает модуль eclic (Enhanced Core Local Interrupt Controller). Он позволяет гибко настроить способ обработки прерываний, приоритеты и многое другое. Мы начнем, как всегда, с простого варианта, но не упустим шанс все себе усложнить. Собственно исключительные ситуации делятся на три типа: немаскируемые (NMI), ловушки (traps) и прерывания (interrupts). Немаскируемые исключения это слишком страшная штука, трогать мы их не будем. Ловушки срабатывают либо если они были заранее поставлены в нужном месте (точка останова breakpoint или специальные инструкции ecall и ebreak), либо если ядро попыталось выполнить невыполнимое и допустить недопустимое. Скажем, прочитать или записать по невыровненному адресу (не кратному размеру переменной) или выполнить незнакомую инструкцию. Ну а прерывания это события от внешних устройств вроде того же UART`а.
Настройка контроллера прерываний eclic осуществляется при помощи кучи специальных регистров. Причем они настолько специальные, что даже не отображаются на память. Доступ к ним возможен при помощи специальных команд вроде scrr (чтение) или scrw (запись). Первым из таких регистров, необходимых для настройки, является mtvec. Старшие 26 битов его отвечают за хранение адреса обработчика прерывания, а младшие 6 за режим работы: волшебное число 3 включает eclic, а любое другое не включает, то есть заставляет использовать предыдущую версию обработчика прерываний clic (для совместимости ее оставили что ли?):


  la t0, trap_entry    andi t0, t0, ~(64-1) #выравнивание адреса должно быть минимум на 64 байта    ori t0, t0, CSR_MTVEC_ECLIC  csrw CSR_MTVEC, t0

Зануление младших битов означает, что при размещении обработчика исключений в памяти надо удостовериться, что в шести младших битах его реального адреса также находятся нули, то есть он выровнен по 64-битной сетке. Это делается директивой ассемблера .align 6:


.align 6trap_entry:  push t0  push t1  push a0  la t0, GPIOB_OCTL  lh t1, 0(t0)    xori t1, t1, (1<<GLED)  sh t1, 0(t0)  la t0, USART0_BASE    la t1, USART_CTL0_UEN | USART_CTL0_REN | USART_CTL0_TEN  sw t1, USART_CTL0_OFFSET(t0)    la t1, 'I'  sw t1, USART_DATA_OFFSET(t0)  la a0, 100000  call sleep  pop a0  pop t1  pop t0mret

Пока мы хотим работать только с прерываниями от UART`а, можно не заморачиваться с разбором что же произошло на самом деле. Считаем, что в обработчик мы могли попасть только из-за него. Прерывание, которое проще всего проверить по опустошению буфера передатчика. За его работу в модуле UART отвечает бит USART_CTL0_TBEIE, а как видно из кода, в прерывании мы его снимаем. То есть при входе в прерывание оно тут же запрещает само себя чтобы не уйти в бесконечный цикл. Ну и отправляет символ 'I' чтобы изобразить бурную деятельность. И зеленым светодиодиком мигнет с той же целью. В реальной жизни, естественно, программисты вписывают туда что-то более осмысленное.
Но если мы просто выставим бит USART_CTL0_TBEIE в регистре USART0_CTL0, мы выполним только одно из условий срабатывания прерывания. Помимо этого надо разрешить это же прерывание внутри контроллера eclic и разрешить обработку прерываний глобально. Это делается следующим кодом:


  # Локальное разрешение прерывания USART0 (eclic_int_ie[i] = 1)  la t0, (ECLIC_ADDR_BASE + ECLIC_INT_IE_OFFSET + USART0_IRQn*4)    la t1, 1  sb t1, 0(t0)  #глобальное разрешение прерываний  csrrs zero, CSR_MSTATUS, MSTATUS_MIE

Рассмотрим что здесь происходит. По адресам ECLIC_ADDR_BASE + ECLIC_INT_IP_OFFSET находится массив четверок регистров, отвечающих за каждый номер прерывания. В виде псевдокода на Си это можно представить так:


struct{  uint8_t clicintip; //interrupt pending  uint8_t clicintie; //interrupt enable  uint8_t clicintattr; //attributes  uint8_t clicintctl; //level and priority}eclic_interrupt[ECLIC_NUM_INTERRUPTS];

  • clicintip флаг наличия прерывания. Он взводится и сбрасывается автоматически при возникновении соответствующего события. Иногда его можно сбросить и программно.
  • clicintie флаг разрешения прерывания. Пока он сброшен, состояние clicintip игнорируется. Пока что нас будет интересовать только этот регистр.
  • clicintattr настройка режима прерывания. Тут можно установить чтобы флаг clicintip выставлялся по уровню или по фронту (изменению сигнала 0->1 или 1->0) а также включить векторный режим. Пока не трогаем.
  • clicintctl настройка уровней и приоритетов. Тоже не трогаем.

Обратите внимание, что каждый из регистров 8-битный, то есть записывать в него нужно инструкцией sb. Ну то, что в коде это написано как-то сложно связано с тем, как это описано в документации. Это я так понял, что данные регистры прекрасно ложатся на структуру, но у разработчиков, похоже, другое мнение. В любом случае, подобная настройка происходит только во время инициализации, так что неоптимальный подход тут вполне простителен.
За прерывание USART0_IRQ отвечает 56-й элемент массива, точнее его поле clicintie, которое нужно выставить в 1.
Остается только добавить обработчик кнопки, чтобы по нажатию выставлялся флаг USART_CTL0_TBEIE, который разрешит прерывание. И, естественно, сам обработчик прерывания, который этот флаг сбросит.


10. Обработка ловушек


Поскольку прерывания провоцируются периферией, а не конкретными инструкциями, возврат из них должен осуществляться ровно в то же место, где им довелось возникнуть. Другое дело ловушки. Они срабатывают обычно не по асинхронным внешним событиям, а по вполне конкретным командам вроде упоминавшегося ранее ecall. И если в конце обработчика ловушки вернуться на ту же инструкцию, ловушка тут же сработает снова. Поэтому сначала адрес возврата нужно увеличить на длину команды, что в нашем случае непросто, ведь команды бывают как 16-битные, так и 32-битные. Но пока не будем заострять на этом внимание, все равно ловушками пользоваться практически не придется. Пока что считаем любую команду 32-битной. Это значит что надо считать значение из местного аналога регистра ra, который тут называется mepc, увеличить на 4 и записать обратно.
Но ведь этот же обработчик служит и для прерываний, где увеличивать регистр не надо. Значит придется сначала определить, что послужило причиной нашего попадания сюда. Для этого служит регистр mcause, у которого есть 31-й бит, ровно за это отвечающий. Если он равен 1, то мы в прерывании, если 0 в ловушке. Не менее эффективно он рассказывает и подробности исключения. Биты 0-11 хранят код ловушки (если 31-й бит равен 0) либо прерывания (если 1). Кодов ловушек не слишком много:


0 instruction address misaligned, ошибка выравнивания инструкции
1 instruction access fault, ошибка доступа к инструкции
2 illegal instruction, незнакомая инструкция
3 breakpoint, инструкция ebreak
4 load address misaligned, ошибка выравнивания памяти
5 load address fault, ошибка доступа к памяти
6 store/AMO misaligned, ошибка выравнивания памяти на запись
7 store/AMO access fault, ошибка доступа к памяти на запись
8 enviroment call from U-mode, инструкция ecall, вызванная из пользовательского кода
9 ?
10 ?
11 Enviroment call from M-mode, инструкция ecall, вызванная из ядерного кода

Из них проще всего проверить коды 2, 3 и 11.
Для выполнения неверной инструкции (код 2) достаточно всего лишь встроить кусок данных между инструкциями. Например, константу 0xFFFF'FFFF (в коде примера закомментирована).
Команда ebreak (код 3) не так проста как кажется. Я в начале сказал что размер инструкции полагаю 32-битным. Так вот, команда ebreak занимает всего 2 байта. Можно обрабатывать ее отдельно, но я предпочту просто ей не пользоваться.
Инструкция ecall в нашем случае генерирует 11-ю ловушку, а не 8-ю как можно было ожидать. Дело в том, что мы не настраивали разграничение доступа, так что фактически весь наш код считается ядерным. То есть нам можно все и отовсюду.
Для проверки напишем обработчик кнопки чтобы по нажатии программа выполняла либо несуществующую инструкцию (ту самую 0xFFFF'FFFF), либо нормальную ecall. А в обработчике ловушки будем мигать красным светодиодом и правильно двигать адрес возврата.


С прерываниями все аналогично. Помните, как мы настраивали биты разрешения прерываний в eclic? Этот же номер нам приедет в младших битах регистра mcause. Достаточно обрезать его по маске и сравнить с интересующим нас номером. В результате мы почти корректно обрабатываем прерывание от UART`а и никак не реагируем на остальные. "Почти" потому что обрабатываем только опустошение буфера передачи, а всего событий на этом векторе висит немного больше. В общем, вот код обработчика ловушек:


Скрытый текст
.align 6trap_entry:  push t0  push t1  push a0  csrr a0, CSR_MCAUSE  la t1, (1<<31)  and t1, a0, t1 #t1 - interrupt / trap    beqz t1, trap_exception #interrupt  la t0, GPIOB_OCTL  lh t1, 0(t0)    xori t1, t1, (1<<GLED)  sh t1, 0(t0)  la t0, 0xFFF  and a0, a0, t0  la t0, USART0_IRQn    bne t0, a0, trap_end  la t0, USART0_BASE    la t1, USART_CTL0_UEN | USART_CTL0_REN | USART_CTL0_TEN  sw t1, USART_CTL0_OFFSET(t0)    la t1, 'I'  sw t1, USART_DATA_OFFSET(t0)trap_end:  la a0, 100000  call sleep  pop a0  pop t1  pop t0mrettrap_exception:  la t0, GPIOB_OCTL  lh t1, 0(t0)    xori t1, t1, (1<<RLED)  sh t1, 0(t0)  csrr t0, CSR_MEPC  addi t0, t0, 4  csrw CSR_MEPC, t0j trap_end

11. Разделение ловушек и прерываний


В предыдущем примере мы анализировали один бит регистра чтобы разделить две принципиально разные ситуации: сбой алгоритма и внешнее событие. К счастью, контроллер прерываний умеет делать это за нас. Для этого воспользуемся регистром mtvt2: его 30 старших битов хранят адрес обработчика прерываний, 1-й бит не отвечает ни за что, а младший бит является переключателем между раздельными обработчиками (mtvt2 + mtvec) при равенстве 1, либо совмещенном при равенстве нулю. Как и в предыдущем случае, использование для адреса обработчика только старших битов намекает нам на выравнивание по 4-байтной сетке. Так что инициализируем:


  la t0, irq_entry  csrw CSR_MTVT2, t0 #выравнивание минимум на 4 байта  csrs CSR_MTVT2, 1

и пишем обработчик. Как и раньше, номер прерывания хранится в младших битах регистра mcause:


Скрытый текст
align 2irq_entry:  push t0  push t1  push a0  csrr a0, CSR_MCAUSE  la t0, 0xFFF  and a0, a0, t0  la t0, USART0_IRQn    bne t0, a0, irq_end  la t0, USART0_BASE    la t1, USART_CTL0_UEN | USART_CTL0_REN | USART_CTL0_TEN  sw t1, USART_CTL0_OFFSET(t0)    la t1, 'I'  sw t1, USART_DATA_OFFSET(t0)  la t0, GPIOB_OCTL  lh t1, 0(t0)    xori t1, t1, (1<<YLED)  sh t1, 0(t0)  la a0, 100000  call sleepirq_end:  pop a0  pop t1  pop t0mret

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


12. Векторный режим работы прерываний


И вот наконец пришел момент окончательно облениться и заставить контроллер самостоятельно выбирать обработчик в зависимости от произошедшего события. Для этого используется такая структура как таблица векторов прерываний. По не вполне очевидным причинам ее адрес должен быть выровнен на 64, 128, 256, 512, 1024, 2048, 4096, 8192 или 16384 байта в зависимости от размера таблицы. Вероятнее всего, это сделано для упрощения арифметики: номер прерывания просто-напросто побитово складывается (OR) с адресом начала таблицы. У нашего контроллера 86 прерываний, по 4 байта на каждое, то есть 344 байта. Ближайшая степень двойки, в которую они влезают 512, этому соответствует выравнивание .align 9. В любом случае таблицу прерываний обычно располагают в самом начале кода, то есть по нулевому адресу, так что проблем не возникает. Но на всякий случай пропишем выравнивание явно. Приводить таблицу прерываний полностью я здесь не буду: она очень длинная. Кому надо, посмотрит в документации или примере кода. Отмечу только, что первые 4 байта в ней разработчики предусмотрительно зарезервировали для размещения инструкции прыжка в начало исполняемого кода:


.text.section .init....align 9vector_base:  j _start  .align    2  .word     0  .word     0  .word     eclic_msip_handler...  .word     RTC_IRQHandler...  .word     SPI1_IRQHandler  .word     USART0_IRQHandler....align 2.text.global _start_start:  la sp, _stack_end...

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


USART0_IRQHandler:  push t0  push a0  la t0, USART0_BASE    la a0, USART_CTL0_UEN | USART_CTL0_REN | USART_CTL0_TEN  sw a0, USART_CTL0_OFFSET(t0)    la a0, 'U'  sw a0, USART_DATA_OFFSET(t0)  la t0, GPIOB_OCTL  lh a0, 0(t0)    xori a0, a0, (1<<GLED)  sh a0, 0(t0)  la a0, 100000  call sleep  pop a0  pop t0mret

Адрес таблицы нужно положить в регистр mtvt:


  la t0, vector_base  csrw CSR_MTVT, t0

И не забываем настроить поле clicintattr уже знакомого нам массива. Оно состоит из двух частей: биты 1 и 2 отвечают за фронты прерывания:
0b00, 0b01 по уровню, то есть флаг clicintip выставляется постоянно когда на проводке события высокий уровень
0b10 по фронту, то есть флаг выставляется при переходе проводка из состояния 0 в 1
0b11 по спаду, то есть при переходе из 1 в 0.
Насколько я понял, это актуально только для внешних прерываний EXTI, когда проводок события непосредственно связан с ножкой контроллера. Для внутренней периферии это безразлично.
Ну а бит 0 отвечает за векторный или не-векторный режим работы. Когда он равен 0 (по умолчанию) прерывание работает в не-векторном режиме, а когда 1 в векторном.


# Использование векторизованного режима обработки (eclic_int_attr[i] = 1)  la t0, (ECLIC_ADDR_BASE+ECLIC_INT_ATTR_OFFSET+USART0_IRQn*4)    la t1, 1  sw t1, 0(t0)

В общем-то вот и все, отдельное прерывание для UART работает.
Для полного фен-шуя можно еще прописать обработку немаскируемых прерываний, хотя я понятия не имею как их проверять.


  la t0, nmi_entry  csrs CSR_MNVEC, t0  li t0, (1<<9)  csrs CSR_MMISC_CTL, t0

13. Переходим на Си


Смею надеяться, с основами ассемблера и архитектуры контроллера мы познакомились. Теперь можно перейти к языкам высокого уровня. Первым делом разберемся с иерархией. При программировании на Си основной код пишется именно на нем, а ассемблер используется лишь для служебных целей вроде инициализации памяти или прерываний, ну плюс низкоуровневые функции или вставки. Следовательно, наш ассемблерный код переименовывается в startup.S, а на его место создаем main.c. В этот Си`шный файл переносим инициализацию портов, UART'а и все остального, оставляем только копирование секций памяти, стек и инициализацию контроллера прерываний. Также я предлагаю переработать работу с отдельными линиями ввода-вывода. На ассемблере это было сделать сложно, но Си и его препроцессоре несколько удобнее. На них я написал файл pinmacro.h чтобы можно было работать с выводами например так:


#define RLED B, 5, 1, GPIO_PP50#define SBTN B, 0, 0, GPIO_HIZ...GPIO_config( RLED );GPIO_config( SBTN );GPO_ON( RLED );if( GPI_ON( SBTN ) )GPIO_OFF( RLED);

В конце ассемблерного файла нужно вызвать main как обычную функцию. Можно даже передать ей argc и argv. Но после нее добавим бесконечный цикл ну случай если кто-то по глупости сделает return.


  li a0, 0  li a1, 0  call mainINF_LOOP:.weak UnhandledInterruptHandlerUnhandledInterruptHandler:  j INF_LOOP

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


.weak IRQHandlerIRQHandler:.weak NMIHandlerNMIHandler:.weak TrapHandlerTrapHandler:j UnhandledInterruptHandler

Как видно, любое внешнее событие приводит к бесконечному зависанию. Хорошо бы добавить сюда еще и индикацию ошибки, но в общем случае мы не знаем какая периферия подключена к контроллеру и как через нее вывести код ошибки. Поэтому предоставим возможность обработки высокоуровневому коду. Пусть программист объявит обработчик прерываний UnhandledInterruptHandler и сам разбирается что же пошло не так.
Директивы .weak, с которыми вы могли познакомиться и раньше, если анализировали ассемблерный код таблицы векторов прерываний означают, что если данный символ (адрес, константа, ...) объявлен где-то еще, то использовать надо именно то, другое объявление, а если ничего подобного в коде нет, то здешнее, "слабое".
Если присмотреться к выхлопу дизассемблера, станет понятно, что наш код инициализации может оказаться после Си'шного. Это не проблема, поскольку и туда и обратно мы перемещаемся прыжками по адресам, но для красоты выделим специальную подсекцию .start, которая будет располагаться после таблицы прерываний, но перед основным кодом.
Также если в ассемблерном коде мы сами прописывали адреса всех регистров и битов, то в Си'шном коде принято использовать готовые наработки производителя. Я не уверен что нашел правильные, но пока что особых ошибок там не видел. Пусть лежат рядом с нашим скриптом линковщика в каталоге lib.


14. Прерывания на Си


Поскольку мы уже знаем какие регистры задействовать, можно было все это написать самостоятельно. Но, как я уже говорил раньше, в Си так не принято. Поэтому воспользуемся готовым кодом, который лежит в lib/Firmware/RISCV/drivers/n200_func.c. Он отвечает за настройку контроллера прерываний и предоставляет функции eclic_set_vmode (переклюить в векторный режим) и eclic_enable_interrupt (разрешить данное прерывание). А вот глобального разрешения и запрета прерываний там почему-то нет. Ладно, пишем вручную:


#define eclic_global_interrupt_enable() set_csr(mstatus, MSTATUS_MIE)#define eclic_global_interrupt_disable() clear_csr(mstatus, MSTATUS_MIE)

Для самого файла n200_func.c имеет смысл описать персональное правило в makefile чтобы не копировать его в src. Если немного его поизучать, можно найти функцию eclic_init, отвечающую за инициализацию настроек всех прерываний (но не их адресов!). Не то чтобы вызывать ее было обязательно, но ведь и лишним не будет. Довольно неприятным побочным эффектом оказалось то, что этот файл требует наличия глобальной переменной SystemCoreClock. Ничего не поделаешь, придется ее объявить.
Как мы видели раньше, внутреннее устройство обработчиков прерываний отличается от обычных функций: приходится сохранять вообще все регистры (в том числе t0-t6), а возврат происходит инструкцией mret вместо обычной ret. Чтобы подсказать Си'шному компилятору что вот эта функция является именно прерыванием, используется специальный атрибут, например так:


__attribute__((interrupt)) void USART0_IRQHandler(void)

Прерываний у нас много, а писать этот атрибут каждый раз лень, поэтому вынесем в файл lib/interrupt_util.h все прототипы обработчиков прерываний с этим атрибутом. Туда же унесем код разрешения и запрета прерываний, про который говорили раньше.
Если посмотреть в дизассемблерный код еще раз, можно увидеть, что main ведет себя не как главный цикл программы, а как обычная функция сохраняет регистры на стеке, при выходе восстанавливает. Поскольку нам это не нужно, объявим и ее прототип перед обработчиками прерываний, только с другим атрибутом:


__attribute__((naked)) int main();

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


Вот и все, что я хотел себе рассказать о первых шагах в освоении данного контроллера.


Заключение


RISC-V довольно интересная архитектура, изучать которую после тесного общения с AVR и поверхностного с ARM было необычно. Некоторые технические решения показались странными, но не лишенными внутренней логики, как, например, отказ от стека или от статусных регистров. Красиво решена работа с прерываниями: хочешь используй один общий обработчик, а хочешь каждому устройству свой. Таблицу можно положить не только в начало основного кода или бутлоадер, а вообще куда угодно, лишь бы влезла.
Решение с макросами вроде USART_DATA( USART0 ) или вскользь упомянутых в начале DMA гораздо удобнее, чем структуры в stm32. Нумерация периферии с нуля мне тоже понравилась больше.
Хорошо бы сравнить ассемблер RISC-V с ассемблером ARM, но с последним я почти не знаком, так что много сказать не смогу. Разве что RISC-V несколько проще для понимания: там почти нет инструкций-комбайнов (если не считать li, которая, согласно документации, разворачивается в "Myriad sequences"). С академической точки зрения это прекрасно, но для реальной работы может обернуться небольшим замедлением кода.
Рекомендую ли я эту архитектуру вообще и GD32VF103 в частности к практическому применению? И да и нет. Основной аргумент против это малая распространенность подобных контроллеров. Те же GigaDevice производят по сути только клон stm32f103, то есть довольно слабого контроллера с высоким потреблением. На мой взгляд, лучше бы скопировали stm32l151. Ну а из плюсов опять же приятный ассемблер и получение общего представления об устройстве контроллеров, которое пригодится и в работе с контроллерами вообще, и особенно с теми же stm32f103. Опять же существуют компьютерные процессоры, основанные на RISC-V и еще неизвестно за кем будущее x86, ARM, RISC-V или еще какой-то архитектурой.


Список источников


http://personeltest.ru/aways/habr.com/ru/post/516006/
https://www.youtube.com/watch?v=M0dEugoU8PM&list=PL6kSdcHYB3x4okfkIMYgVzmo3ll6a9dPZ
https://www.youtube.com/watch?v=LyQcTmNcSpY&list=PL6kSdcHYB3x6KOBxEP1YZAzR8hkMQoEva
https://doc.nucleisys.com/nuclei_spec/isa/eclic.html
http://www.gd32mcu.com/en/download/0?kw=GD32VF1

Подробнее..

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru