Комментарии от COKPOWEHEU
- (например, для перехода на подпрограмму), можно организовать скрытый аппаратный стек
Именно для подпрограмм это может быть неудобно. При переключении контекстов придется разматывать этот стек и пересохранять его в память.
У WCH реализована похожая штука - аппаратное сохранение несохраняемых регистров (t*, a*) при входе в прерывание. Благодаря этому прерывание выглядит как обычная функция: те регистры, о которых обычно заботится caller, сохраняются аппаратно. Остальные - самим обработчиком. Но и тут я умудрился наступить на грабли. При реализации многозадачности этот аппаратный стек надо вручную отключать чтобы сохранить регистры туда, куда мне надо, и восстановить потом из нужного места, а не из стека.
Но, повторю, с прерываниями это проще, чем с функциями. У них почти всегда нулевая вложенность.
- можно использовать т. н. «подсказки» — инструкции, которые чего-то не делают
Проще выделить лишний блок в MMIO или CSR, чем вот так извращаться. И в принципе, именно так и делают.
Георгий Курячий Тем не менее в стандарте прописаны именно hints, а CSR таких нет, всё отдано на откуп производителю
- Тому же служит карта памяти, в которой задано расположение .text, .data,
Это не конвенция, а аппаратное ограничение. Секцию .text бесполезно располагать в области MMIO, в секцию .data - во флеш-памяти. Переназначить-то, конечно, можно, но работать не будет.
Георгий Курячий Это договорённость о том, где какая память размещена, причём вариантов этой договорённости несколько. как по мне — конвенция по определению.
- RISC: все вычисления производятся только на регистрах, операции с памятью всего две: прочитать и записать
- ⇒ Нужно больше регистров
Похоже, что 32 или даже 16 регистров как раз и оказалось оптимальным. Все данные туда так и так не загонишь, а в алгоритмах слишком часто встречаются задачи либо провести с переменной какую-то простую операцию и положить на место, либо наоборот работа с огромными массивами данных, которые в регистры все равно не влезут. Плюс ограниченное количество битов на хранение адреса, плюс необходимость сохранять - восстанавливать.
Георгий Курячий Это вы просто компиляторов мало писали).
- Регистр gp указывает на область данных, доступных всем нитям одновременно
Смысл в том, что, допустим, .data располагается прямо с начала оперативной памяти - 0x2000 0000. Этот адрес слишком велик, чтобы кодировать его в imm-части инструкции. Придется сначала грузить старшие биты в какой-нибудь РОН, а потом через imm добавлять смещение. Но можно задокументировать gp (или tp, наверное) на 0x2000 0000 + 2048, тогда доступ к первым 4 кБ будет на целую инструкцию проще.
- Плоская модель памяти RARS
Для общего ознакомления было бы неплохо показать хотя бы одну альтернативную модель памяти. Вот, например: https://niiet.ru/wp-content/uploads/2025/10/%D0%A0%D0%9F-%D0%9A1921%D0%92%D0%93015.pdf (сама железка https://niiet.ru/product/%D0%BA1921%D0%B2%D0%B3015/) глава 6 "организация памяти". Видно, что секция .text располагается с 0x8000 0000, секция .data с 0x4000 0000, секция MMIO c 0x2000 0000 ну и так далее.
- Резервированная память (до 0x400000)
Как следствие - акцент на специфичных для RARS адресах и костылях стоило бы убрать. Операционная система сама назначает права доступа тем областям памяти, которым хочет. А при наличии MMU - может их еще и перетасовать.
- Память ядра ОС. Начиная с адреса 0x80000000 идёт область, недоступная программе пользователя. Это область кода и данных ядра. Безотносительно к тому, запущена программа под управлением ОС или «на голом железе», для исполнения кода и доступа к памяти требуется особый режим работы процессора. Чтение, запись и переход с использованием адресов ядра пользовательской программе запрещены.
Вот эти костыли времен MIPS уж точно стоит убрать.
Георгий Курячий Из Rars? Welcome. Я бы не взялся.
Можно ли в RARS прочитать байт из раздела .text по нечётному адресу? (Почему
?)
А в чем подвох? Вроде все нормально читается. В **некоторых** ядрах нельзя прочитать по нечетному адресу **слово**. Но уж с байтами-то даже там проблем нет. А другим вообще плевать на выравнивание.
- Уже введение второго уровня доступа (kernel space вдобавок к user space при помощи MMU)
Уже введение второго **уровня привилений**, M-mode вдобавок к U-mode. Исправляем терминологию. MMU тут вообще никакого отношения не имеет. Насколько я слышал, даже полноценный Linux можно запустить на машине без MMU. С определенными огранияениями (fork не работает, например), но все же.
Георгий Курячий Кажется, замечание мимо кассы. Привилегии — это привилегии, какие-от команды на каком-то уровне недоступны, вот и всё. Вопрос именно в том. В каких случаях в памяти не надо заранее явно выделять место, куда загружено ядро.
- TODO Слишком много!
- 0x0 - 0x003fffff — также специфика плоской модели памяти RARS
Вот потому и много, что зачем-то идет рассмотрение тонкостей эмулятора, которые потом в курсе нигде не используются.
Георгий Курячий Rars используется в курсе чуть менее, чем всегда.
- Такой подход называется позиционно-независимым кодированием. (про lui / auipc)
Это иногда приводит к другим проблемам. Например, адреса меток можно загружать только через la. Даже если нужен абсолютный адрес начала ОЗУ (инициализация регистров gp, sp, инициализация переменных из флеша и т.д.). Как заставить программу внутри .text обращаться к себе через la, а между .text и .data через li я так и не понял. А ведь .text, в отличие от .data, может перемещаться.
- 12 битов хватает на то, чтобы сделать переход на 4 килобайта кода (4, а не 2
Но 2^12 это и так 4096. Отдельно стоит отметить, что адрес кратен 2, а не 4. Расширение С учитывается даже тут.
- Цикл с постусловием — штука ненадёжная
Скорее, неинтуитивная.
- Добавим, что ситуация, когда целочисленное переполнение в вычислениях необходимо проверять, встречается в основном в особых случаях («длинная арифметика» и т. п.).
"Ситуации, когда целочисленное переполнение в вычислениях необходимо проверять, были типичны для 8- или хотя бы 16-битных машин". RISCV изначально 32-битный, его еще поди переполни.
- в отличие от большинства архитектур, в RISC-V нет возможности задать два регистра-приёмника (это привело бы к усложнению и замедлению логики).
И к необходимости программисту использовать div/rem в правильном порядке
Георгий Курячий Наоборот, избавило бы его от этой необходимости, см. далее
- Алгоритмы умножения и деления требуют на порядок больше тактов, чем сложение
Разве что речь идет о двоичных порядках. Умножение все же относительно простая операция, которая выполняется всего раза в два медленнее. Вот деление - да, десятки тактов на него это норма.
Георгий Курячий Вы так и не показали мне алгоритм / схемотехнику, при помощи которой умножение можно сделать за два такта
- В конвенции также сказано, что регистр приёмник1 не должен совпадать с регистрами делимое или делитель (то есть вторая инструкция должна работать с теми же операндами, что и первая)
ЕМНИП, там в другом прикол. Он не должен совпадать, если нас интересуют И div, И rem. По вполне очевидной причине - аргументы второй инструкции должны быть теми же, что у первой, иначе и результат будет другой.
- Непонятно, как конвейер справляется со «стеной переносов»
А при чем здесь конвейер, если за умножение отвечает отдельный аппаратный блок? Он вообще может работать в комбинационном режиме, и второй такт нужен для того, чтобы дождаться завершения всех переходных процессов.
Георгий Курячий Если за какую-то операцию (хоть за преобразование Фурье) отвечает «отдельный аппаратный блок», она действительно может пройти за два такта — загрузить, выгрузить. Тогда почему деление медленное?
