mirror of
https://github.com/MPSU/APS.git
synced 2025-09-15 09:10:10 +00:00
* Update 06. RISC-V architecture.md * Update 02. Instruments.md * Update 03. Digital arithmetics.md * Update 04. Operations units.md * Update 05. Sequencial logic.md * Update 07. RISC-V programming.md * Update 08. Singlecycle processor.md * Update 09. Multicycle processor.md * Update 10. Pipeline processor.md * Update 11. Pipeline hazards.md * Update Lectures/02. Instruments.md * Update Lectures/04. Operations units.md --------- Co-authored-by: Andrei Solodovnikov <VoultBoy@yandex.ru>
103 lines
13 KiB
Markdown
103 lines
13 KiB
Markdown
# Лекция 7. Программирование RISC-V
|
||
|
||
В подавляющем большинстве случаев, современные программы пишутся на языках высокого уровня. Процессоры не понимают языков высокого уровня, поэтому компиляторы переводят текст (программу), написанный на языке высокого уровня в последовательность простых инструкций языка ассемблера. После этого, программу на языке ассемблера переводят в последовательность машинных команд — то, что понятно процессору.
|
||
|
||
На лекции было разобрано несколько примеров основных синтаксических конструкций языка высокого уровня C. Условный оператор if реализуется за счёт использования инструкций условного перехода.
|
||
|
||
```c
|
||
if (/*условие*/) {
|
||
//тело условия если True
|
||
} else {
|
||
// тело условия если False
|
||
}
|
||
```
|
||
|
||
```assembly
|
||
# условие вычисляется в xN регистр
|
||
beqz xN, else
|
||
#тело условия если True для if
|
||
j endif
|
||
else:
|
||
#тело условия если False для if
|
||
endif:
|
||
```
|
||
|
||
Оператор цикла while также реализуется за счёт применения инструкций условного перехода.
|
||
|
||
```c
|
||
while (/*условие*/) {
|
||
// тело цикла
|
||
}
|
||
```
|
||
|
||
```assembly
|
||
while:
|
||
# условие вычисляется в xN
|
||
beqz xN, endwhile
|
||
# тело цикла
|
||
j while
|
||
endwhile:
|
||
```
|
||
|
||
Процедуры (они же функции или подпрограммы) – это повторно используемые фрагменты кода, реализующие вычисления определённой задачи. Использование процедур позволяет абстрагироваться и повторно использовать один и тот же код, но с разными входными параметрами. Большие программы состоят из подпрограмм, включающих в себя другие подпрограммы и так далее.
|
||
|
||
Программа, которая вызывает подпрограмму называется *вызывающей*. Подпрограмма которую вызывают называется *вызываемой* подпрограммой. Вызывающая программа использует тот же набор регистров, что и вызываемая, поэтому: либо вызывающая, либо вызываемая должна сохранять регистры вызывающей в памяти и восстанавливает их, когда процедура завершает своё выполнение.
|
||
|
||
Вызов подпрограммы означает передачу управления этой подпрограмме, то есть загрузки в PC (program counter, указатель на инструкцию) адреса первой инструкции вызываемой подпрограммы. Чтобы вернуться к месту вызова процедуры, когда выполнение подпрограммы закончится, при её вызове необходимо использовать специальную инструкцию jal (jump and link).
|
||
|
||
Соглашение о вызовах устанавливает правила использования регистров между процедурами. В соглашении о вызовах RISC-V даются символические имена регистров x0 — x31 для обозначения их роли. Вызываемая подпрограмма получает аргументы через регистры a0 — a7. В таблице ниже приводится какие из регистров должны быть сохранены неизменными при возврате из подпрограммы, и, какие регистры следует сохранить перед вызовом подпрограммы, если их содержимое планируется использовать после.
|
||
|
||

|
||
|
||
Каждый вызов процедуры имеет свой собственный экземпляр данных, включающий: аргументы функции, содержимое регистрового файла и адрес возврата, и называемый *активационной записью*. Для хранения активационных записей функций используется стек, занимающий часть основной памяти. Стек — это способ организации памяти, при котором первая запись будет считана в последнюю очередь (LIFO — last-in-first-out). Для поддержания работы стека используется регистр x2, также именуемый sp (Stack Pointer — указатель стека), который указывает на последнюю ячейку памяти помещённую в стек. Далее приводится пример вызова подпрограммы с сохранением сохраняемых регистров на стек.
|
||
|
||
```assembly
|
||
addi sp, sp, -8 # выделить место на стеке для двух элементов
|
||
sw ra, 0(sp) # сохранить регистр ra на стек
|
||
sw a1, 4(sp) # сохранить регистр a1 на стек
|
||
jal ra, func # сохранить в ra адрес возврата PC+4 и перейти к func
|
||
lw ra, 0(sp) # восстановить из стека значение ra
|
||
lw a1, 4(sp) # восстановить из стека значение a1
|
||
addi sp, sp, 8 # освободить место на стеке
|
||
|
||
func: # вызываемая функция
|
||
addi sp, sp, -4 # выделить место на стеке
|
||
sw s0, 0(sp) # сохранить регистр s0 на стеке
|
||
# ...... некий код, выполнение функции, использующей регистр s0
|
||
lw s0, 0(sp) # восстановить из стека значение s0
|
||
addi sp, sp, 4 # освободить место на стеке
|
||
jr ra # вернуться в основную программу
|
||
```
|
||
|
||
Большинство языков программирования (в том числе C) используют три отдельных области памяти для данных:
|
||
|
||
- **Stack**: Содержит данные используемые процедурными вызовами. Регистр sp указывает на вершину стека
|
||
|
||
- **Static**: Содержит глобальные переменные, которые существуют в течении всего времени жизни программы. Регистр gp (Global Pointer) указывает на начало этой области
|
||
|
||
- **Heap**: Содержит динамически-распределяемые данные и растёт в сторону старших адресов. В C программист управляет кучей вручную, размещая новые данные с помощью malloc() и освобождая с помощью free(). В Python, Java, и большинстве современных языков, куча управляется автоматически
|
||
|
||
- **Text**: область памяти содержащая программный код
|
||
|
||

|
||
|
||
Также на лекции затронули вопрос компиляции программ с языков высокого уровня. Этот процесс происходит в несколько этапов. Сначала высокоуровневый код компилируется в код на языке ассемблера, который затем ассемблируется в машинный код и сохраняется в виде объектного файла. Компоновщик, также называемый редактором связей или линкером (linker), объединяет полученный объектный код с объектным кодом библиотек и других файлов, в результате чего получается готовая к исполнению программа. На практике, большинство компиляторных пакетов
|
||
выполняют все три шага: компиляцию, ассемблирование и компоновку. Наконец, загрузчик загружает программу в память и запускает её.
|
||
|
||

|
||
|
||
## Основные материалы лекции
|
||
|
||
1. [Ссылка](https://www.youtube.com/watch?v=y1mNFvm8OZY) на видеозапись лекции
|
||
2. Все материалы лекции можно найти в этом источнике, к сожалению аналога на русском пока не нашел [***Patterson Hennessy***. *Computer organization and design. RISC-V edition* — 2 глава]
|
||
3. Про процесс компиляции можно почитать, например, в этом источнике [***Харрис и Харрис***. *Цифровая схемотехника и архитектура компьютера* — весь параграф 6.6 с подпунктами]
|
||
|
||
## Дополнительные материалы к лекции для саморазвития
|
||
|
||
1. [Полезная информация](https://github.com/riscv/riscv-asm-manual/blob/master/riscv-asm.md) по программированию на языке ассемблера RISC-V, на английском языке
|
||
2. [Здесь](http://skilldrick.github.io/easy6502/) можно познакомиться, разобраться и пописать ассемблерные программки под архитектуру 6502 — классический процессор, на котором работают Бендер, Терминатор и Денди. Давай-давай, не стесняйся, заходи. Прямо на странице есть встроенный симулятор и объясняют как написать простенькую игру — змейку. Если не в курсе, люди соревнуется, у кого она получится меньше по объёму кода. Да и вообще в интернете полно информации по этому процессору. Процитирую автора ресурса: 6502 is *fun*. Nobody ever called x86 *fun*. А [тут](https://museum.netstalking.ru/xaknotdie/02_nes_6502_asm.html) на русском про программирование 6502.
|
||
|
||
## Популярные материалы
|
||
|
||
1. В [этом](https://godbolt.org/) онлайн-компиляторе можно смотреть в какую последовательность ассемблерных инструкций скомпилируется твой код на C++ для самых разных архитектур. Можно, например, сравнить x86, ARM и RISC-V, при том разных версий компиляторов — чем отличается генерируемые инструкции процессору в каждом случае, где код длиннее, где требуется много подготовительных операций и тому подобное. В конце концов можно наглядно посмотреть разницу между программами для CISC и RISC архитектур
|