mirror of
https://github.com/MPSU/APS.git
synced 2025-09-15 17:20:10 +00:00
Синхронизация с правками публикуемого издания (#101)
* СП. Обновление предисловия * СП. Обновление введения * СП. Обновление лаб * СП. Обновление доп материалов * СП. Введение * СП. Введение * СП. ЛР№4, 15 * СП. Базовые конструкции Verilog * Update Implementation steps.md * СП. ЛР 4,5,7,8,14 * СП. ЛР№8 * Синхронизация правок * СП. Финал * Исправление ссылки на рисунок * Обновление схемы * Синхронизация правок * Добавление белого фона .drawio-изображениям * ЛР2. Исправление нумерации рисунка
This commit is contained in:
committed by
GitHub
parent
d251574bbc
commit
9739429d6e
@@ -31,13 +31,13 @@
|
||||
|
||||
> — Но разве в процессе компиляции исходного кода на языке Си мы не получаем программу, написанную на языке ассемблера? Получится ведь тот же код, что мы могли написать и сами.
|
||||
|
||||
Штука в том, что ассемблерный код, который писали ранее вы отличается от ассемблерного кода, генерируемого компилятором. Код, написанный вами, обладал, скажем так... более тонким микро-контролем хода программы. Когда вы писали программу, вы знали какой у вас размер памяти, где в памяти расположены инструкции, а где данные (ну, при написании программ вы почти не пользовались памятью данных, а когда пользовались — просто лупили по случайным адресам и все получалось). Вы пользовались всеми регистрами регистрового файла по своему усмотрению, без ограничений. Однако, представьте на секунду, что вы пишете проект на ассемблере вместе с коллегой: вы пишите одни функции, а он другие. Как в таком случае вы будете пользоваться регистрами регистрового файла? Ведь если вы будете пользоваться одними и теми же регистрами, вызов одной функции может испортить данные в другой. Поделите его напополам и будете пользоваться каждый своей половиной? Но что будет, если к проекту присоединится ещё один коллега — придётся делить регистровый файл уже на три части? Так от него уже ничего не останется. Для разрешения таких ситуаций было разработано [соглашение о вызовах](#соглашение-о-вызовах) (calling convention).
|
||||
Штука в том, что ассемблерный код, который писали ранее вы отличается от ассемблерного кода, генерируемого компилятором. Код, написанный вами, обладал, скажем так... более тонким микро-контролем хода программы. Когда вы писали программу, вы знали какой у вас размер памяти, где в памяти расположены инструкции, а где данные (ну, при написании программ вы почти не пользовались памятью данных, а когда пользовались — просто использовали случайные адреса и всё получалось). Вы пользовались всеми регистрами регистрового файла по своему усмотрению, без ограничений. Однако, представьте на секунду, что вы пишете проект на ассемблере вместе с коллегой: вы пишите одни функции, а он другие. Как в таком случае вы будете пользоваться регистрами регистрового файла? Ведь если вы будете пользоваться одними и теми же регистрами, вызов одной функции может испортить данные в другой. Поделите его напополам и будете пользоваться каждый своей половиной? Но что будет, если к проекту присоединится ещё один коллега — придётся делить регистровый файл уже на три части? Так от него уже ничего не останется. Для разрешения таких ситуаций было разработано [соглашение о вызовах](#соглашение-о-вызовах) (calling convention).
|
||||
|
||||
Таким образом, генерируя ассемблерный код, компилятор не может так же, как это делали вы, использовать все ресурсы без каких-либо ограничений — он должен следовать ограничениям, накладываемым на него соглашением о вызовах, а также ограничениям, связанным с тем, что он ничего не знает о памяти устройства, в котором будет исполняться программа — а потому он не может работать с памятью абы как. Работая с памятью, компилятор следует некоторым правилам, благодаря которым после компиляции компоновщик сможет собрать программу под ваше устройство с помощью специального скрипта.
|
||||
|
||||
### Соглашение о вызовах
|
||||
|
||||
Соглашение о вызовах [устанавливает](https://github.com/riscv-non-isa/riscv-elf-psabi-doc/releases/download/v1.0/riscv-abi.pdf) порядок вызова функций: где размещаются аргументы при вызове функций, где находятся указатель на стек и адрес возврата и т.п.
|
||||
Соглашение о вызовах [устанавливает](https://github.com/riscv-non-isa/riscv-elf-psabi-doc/releases/download/v1.0/riscv-abi.pdf) порядок вызова функций: где размещаются аргументы при вызове функций, где находятся указатель на стек, адрес возврата и т.п.
|
||||
|
||||
Кроме того, соглашение делит регистры регистрового файла на две группы: оберегаемые и необерегаемые регистры.
|
||||
|
||||
@@ -71,7 +71,7 @@ _Таблица 1. Ассемблерные мнемоники для целоч
|
||||
|
||||
Несмотря на то, что указатель на стек помечен как Callee-saved регистр, это не означает, что вызываемая функция может записать в него что заблагорассудится, предварительно сохранив его значение на стек. Ведь как вы вернёте значение указателя на стек со стека, если в регистре указателя на стек лежит что-то не то?
|
||||
|
||||
Запись `Callee` означает, что к моменту возврата из вызываемой функции, значение Callee-saved регистров должно быть ровно таким же, каким было в момент вызова функций. Для s0-s11 регистров это осуществляется путём сохранения их значений на стек. При этом, перед каждым сохранением на стек, изменяется значение указателя на стек таким образом, чтобы он указывал на сохраняемое значение (обычно он декрементируется). Затем, перед возвратом из функции все сохранённые на стек значения восстанавливаются, попутно изменяя значение указателя на стек противоположным образом (инкрементируют его). Таким образом, несмотря на то что значение указателя на стек менялось в процессе работы вызываемой функции, к моменту выхода из неё, его значение в итоге останется тем же.
|
||||
Запись `Callee` означает, что к моменту возврата из вызываемой функции, значение Callee-saved регистров должно быть ровно таким же, каким было в момент вызова функций. Для s0-s11 регистров это осуществляется путём сохранения их значений на стек. При этом, перед каждым сохранением на стек, изменяется значение указателя на стек таким образом, чтобы он указывал на сохраняемое значение (обычно он декрементируется). Затем, перед возвратом из функции все сохранённые на стек значения восстанавливаются, попутно изменяя значение указателя на стек противоположным образом (инкрементируют его). Таким образом, несмотря на, то что значение указателя на стек менялось в процессе работы вызываемой функции, к моменту выхода из неё, его значение в итоге останется тем же.
|
||||
|
||||
### Скрипт для компоновки (linker_script.ld)
|
||||
|
||||
@@ -85,7 +85,7 @@ _Таблица 1. Ассемблерные мнемоники для целоч
|
||||
|
||||
Кроме того, вы в любой момент можете изменить значение счетчика адресов. Например, если адресное пространство памяти поделено на две части: под инструкции отводится 512 байт, а под данные 1024 байта. Таким образом, выделенный диапазон адресов для инструкций: `[0:511]`, а для данных: `[512:1535]`. Предположим при этом, что общий объем секций `.text` составляет 416 байт. В этом случае, вы можете сперва разместить секции `.text` так же, как было описано в предыдущем примере, а затем, выставив значение на счетчике адресов равное `512`, описать размещение секций данных. Тогда, между секциями появится разрыв в 96 байт, а данные окажутся в выделенном для них диапазоне адресов.
|
||||
|
||||
В нашей процессорной системе гарвардская архитектура. Это значит, что память инструкций и данных у нас независимы друг от друга. Это физически разные устройства, с разными шинами и разным адресным пространством. Однако обе эти памяти имеют общие значения младших адресов: самый младший имеет адрес ноль, следующий адрес 1 и т.д. Таким образом, происходит наложение адресных пространств памяти инструкций и памяти данных. Компоновщику трудно работать в таких условиях: "как я записать что по этому адресу будет размещаться секция данных, когда здесь уже размещена секция инструкций".
|
||||
В нашей процессорной системе гарвардская архитектура. Это значит, что память инструкций и данных у нас независимы друг от друга. Это физически разные устройства, с разными шинами и разным адресным пространством. Однако обе эти памяти имеют общие значения младших адресов: самый младший имеет адрес ноль, следующий адрес 1 и т.д. Таким образом, происходит наложение адресных пространств памяти инструкций и памяти данных. Компоновщику трудно работать в таких условиях: "как я могу разместить по этому адресу секцию данных, когда здесь уже размещена секция инструкций".
|
||||
|
||||
Есть два механизма для решения этого вопроса. Первый: компоновать секции инструкций и данных по отдельности. В этом случае будет два отдельных скрипта компоновщика. Однако, компоновка секций инструкций зависит от компоновки секций данных (в частности, от того по каким адресам будут размещены стек и .bss-секция, а также указатель на глобальную область данных), поскольку в часть инструкций необходимо прописать конкретные адреса. В этом случае, придётся делать промежуточные операции в виде экспорта глобальных символов в отдельный объектный файл, который будет использован при компоновке секции инструкций, что кажется некоторым переусложнением.
|
||||
|
||||
@@ -100,7 +100,7 @@ _Таблица 1. Ассемблерные мнемоники для целоч
|
||||
|
||||
Помимо прочего, в скрипте компоновщика необходимо прописать, каков [порядок следования байт](https://en.wikipedia.org/wiki/Endianness), где будет находиться стек, и какое будет значение у указателя на глобальную область памяти.
|
||||
|
||||
Все это с подробными комментариями описано в файле `linker_script.ld`.
|
||||
Всё это с подробными комментариями описано в файле `linker_script.ld`.
|
||||
|
||||
```ld
|
||||
OUTPUT_FORMAT("elf32-littleriscv") /* Указываем порядок следования байт */
|
||||
@@ -301,11 +301,12 @@ SECTIONS
|
||||
|
||||
_Листинг 1. Пример скрипта компоновщика с комментариями._
|
||||
|
||||
Обратите внимание на указанные размеры памяти инструкций и данных. Они отличаются от размеров, которые использовались ранее в пакете `memory_pkg`. Дело в том, что пока система и исполняемые ей программы были простыми, в большом объеме памяти не было нужды и меньший размер значительно сокращал время синтеза системы. Однако в данный момент, чтобы обеспечить программе достаточно места под инструкции, а также программный стек и стек прерываний, необходимо увеличить объемы памяти инструкций и памяти данных. Для этого необходимо обновить значения параметров `INSTR_MEM_SIZE_BYTES` и `DATA_MEM_SIZE_BYTES` на 32'd1024 и 32'd2048 соответственно. В зависимости от сложности вашего проекта, в будущем вам может снова потребоваться изменять размер памяти в вашей системе. Помните, все изменения в memory_pkg должны отражаться и в скрипте компоновщика для вашей системы.
|
||||
> [!IMPORTANT]
|
||||
> Обратите внимание на указанные размеры памяти инструкций и данных. Они отличаются от размеров, которые использовались ранее в пакете `memory_pkg`. Дело в том, что пока система и исполняемые ей программы были простыми, в большом объеме памяти не было нужды и меньший размер значительно сокращал время синтеза системы. Однако в данный момент, чтобы обеспечить программе достаточно места под инструкции, а также программный стек и стек прерываний, необходимо увеличить объемы памяти инструкций и памяти данных. Для этого необходимо обновить значения параметров `INSTR_MEM_SIZE_BYTES` и `DATA_MEM_SIZE_BYTES` на 32'd1024 и 32'd2048 соответственно. В зависимости от сложности вашего проекта, в будущем вам может снова потребоваться изменять размер памяти в вашей системе. Помните, все изменения в memory_pkg должны отражаться и в скрипте компоновщика для вашей системы.
|
||||
|
||||
### Файл первичных команд при загрузке (startup.S)
|
||||
|
||||
В стартап-файле хранятся инструкции, которые обязательно необходимо выполнить перед началом исполнения любой программы. Это инициализация регистров указателей на стек и глобальную область данных, контрольных регистров системы прерывания и т.п.
|
||||
В стартап-файле хранятся инструкции, которые обязательно необходимо выполнить перед началом исполнения любой программы. Это инициализация регистров указателей на стек и глобальную область данных, контрольных регистров системы прерывания, инициализация .bss-секции и т.п.
|
||||
|
||||
По завершению инициализации, стартап-файл выполняет процедуру передачи управления точке входа в запускаемую программу.
|
||||
|
||||
@@ -456,7 +457,8 @@ _int_handler:
|
||||
|
||||
_Листинг 2. Пример содержимого файла первичных команд с поясняющими комментариями._
|
||||
|
||||
Обратите внимание на строки `call main` и `call int_handler`. Компоновка объектного файла, полученного после компиляции `startup.S` будет успешной только в том случае, если в других компонуемых файлах будут функции именно с такими именами.
|
||||
> [!IMPORTANT]
|
||||
> Обратите внимание на строки `call main` и `call int_handler`. Компоновка объектного файла, полученного после компиляции `startup.S` будет успешной только в том случае, если в других компонуемых файлах будут функции именно с такими именами.
|
||||
|
||||
## Практика
|
||||
|
||||
@@ -487,7 +489,7 @@ _Листинг 2. Пример содержимого файла первичн
|
||||
|
||||
### Компоновка объектных файлов в исполняемый
|
||||
|
||||
Далее необходимо выполнить компоновку объектных файлов. Это можно выполнить командной следующего формата:
|
||||
Далее необходимо выполнить компоновку объектных файлов. Это можно сделать командой следующего формата:
|
||||
|
||||
```text
|
||||
<исполняемый файл компилятора> <флаги компоновки> <входные объектные файлы> -o <выходной объектный файл>
|
||||
@@ -551,9 +553,9 @@ FF5FF06F 04400293 FFF00313 30529073
|
||||
...
|
||||
```
|
||||
|
||||
Обратите внимание что байты не просто склеились в четверки, изменился так же и порядок следования байт. Это важно, т.к. в память данные должны лечь именно в таком (обновленном) порядке байт (см. первую строчку скрипта компоновщика). Когда-то `objcopy` содержал [баг](https://sourceware.org/bugzilla/show_bug.cgi?id=25202), из-за которого порядок следования байт не менялся. В каких-то версиях тулчейна (отличных от представленного в данной лабораторной работе) вы все ещё можете столкнуться с подобным поведением.
|
||||
Обратите внимание что байты не просто склеились в четверки, изменился так же и порядок следования байт. Это важно, т.к. в память данные должны лечь именно в таком (обновленном) порядке байт (см. первую строчку скрипта компоновщика). Когда-то `objcopy` содержал [баг](https://sourceware.org/bugzilla/show_bug.cgi?id=25202), из-за которого порядок следования байт не менялся. В каких-то версиях тулчейна (отличных от представленного в данной лабораторной работе) вы всё ещё можете столкнуться с подобным поведением.
|
||||
|
||||
Вернемся к первой строке: `@00000000`. Как уже говорилось, число, начинающееся с символа `@` говорит САПР, что с этого момента инициализация идет начиная с ячейки памяти, номер которой совпадает с этим числом. Когда вы будете экспортировать секции данных, первой строкой будет: `@20000000`. Так произойдет, поскольку в скрипте компоновщика сказано, указано инициализировать память данных с `0x80000000` адреса (значение которого было поделено на 4, чтобы получить номер 32-битной ячейки памяти). Это было сделано, чтобы не произошло наложения адресов памяти инструкций и памяти данных (см раздел [скрипт для компоновки](#скрипт-для-компоновки-linker_scriptld)). **Чтобы система работала корректно, эту строчку необходимо удалить.**
|
||||
Вернемся к первой строке: `@00000000`. Как уже говорилось, число, начинающееся с символа `@` говорит САПР, что с этого момента инициализация идет начиная с ячейки памяти, номер которой совпадает с этим числом. Когда вы будете экспортировать секции данных, первой строкой будет: `@20000000`. Так произойдет, поскольку в скрипте компоновщика сказано инициализировать память данных с `0x80000000` адреса (значение которого было поделено на 4, чтобы получить номер 32-битной ячейки памяти; когда-то в objcopy был ещё один [баг](https://sourceware.org/bugzilla/show_bug.cgi?id=27214), в результате которого деление на 4 не производилось). Это было сделано, чтобы не произошло наложения адресов памяти инструкций и памяти данных (см параграф [скрипт для компоновки](#скрипт-для-компоновки-linker_scriptld)). **Чтобы система работала корректно, эту строчку необходимо удалить.**
|
||||
|
||||
### Дизассемблирование
|
||||
|
||||
@@ -616,7 +618,7 @@ _Листинг 3. Пример дизасемблированного файл
|
||||
|
||||
Следующая за адресом строка, записанная в шестнадцатеричном виде — это та инструкция (или данные), которая размещена по этому адресу. С помощью этого столбца вы можете проверить, что считанная инструкция на временной диаграмме (сигнал `instr`) корректна.
|
||||
|
||||
В правом столбце находится ассемблерный (человекочитаемый) аналог инструкции из предыдущего столбца. Например, инструкция `00001197` — это операция `auipc gp,0x1`, где `gp` — это синоним (ABI name) регистра `x3` (см. раздел [Соглашение о вызовах](#соглашение-о-вызовах)).
|
||||
В правом столбце находится ассемблерный (человекочитаемый) аналог инструкции из предыдущего столбца. Например, инструкция `00001197` — это операция `auipc gp,0x1`, где `gp` — это синоним (ABI name) регистра `x3` (см. параграф [Соглашение о вызовах](#соглашение-о-вызовах)).
|
||||
|
||||
Обратите внимание на последнюю часть листинга: дизасм секции `.data`. В этой секции адреса могут увеличиваться на любое число, шестнадцатеричные данные могут быть любого размера, а на ассемблерные инструкции в правом столбце и вовсе не надо обращать внимание.
|
||||
|
||||
@@ -627,7 +629,7 @@ _Листинг 3. Пример дизасемблированного файл
|
||||
Для того, чтобы произвести дизасемблирование, необходимо выполнить следующую команду:
|
||||
|
||||
```text
|
||||
<исполняемый файл дизасемблера> -D (либо -d) <входной исполняемый файл> > <выходной файл на языке ассемблер>
|
||||
[исполняемый файл дизасемблера] -D (либо -d) [входной исполняемый файл] > [выходной файл на языке ассемблер]
|
||||
```
|
||||
|
||||
Для нашего примера, командной будет
|
||||
@@ -642,7 +644,7 @@ _Листинг 3. Пример дизасемблированного файл
|
||||
|
||||
## Задание
|
||||
|
||||
Вам необходимо написать программу для вашего [индивидуального задания](../04.%20Primitive%20programmable%20device/Индивидуальное%20задание#индивидуальные-задания) к 4-ой лабораторной работе на языке C или C++ (в зависимости от выбранного языка необходимо использовать соответствующий компилятор: gcc для C, g++ для C++).
|
||||
Написать программу для вашего [индивидуального задания](../04.%20Primitive%20programmable%20device/Индивидуальное%20задание#индивидуальные-задания) к 4-ой лабораторной работе на языке C или C++ (в зависимости от выбранного языка необходимо использовать соответствующий компилятор: gcc для C, g++ для C++).
|
||||
|
||||
Для того чтобы ваша программа собралась, необходимо описать две функции: `main` и `int_handler`. Аргументы и возвращаемые значения могут быть любыми, но использоваться они не смогут. Функция `main` будет вызвана в начале работы программы (после исполнения .boot-секции startup-файла), функция `int_handler` будет вызываться автоматически каждый раз, когда ваш контроллер устройства ввода будет генерировать запрос прерывания (если процессор закончил обрабатывать предыдущий запрос).
|
||||
|
||||
@@ -651,7 +653,7 @@ _Листинг 3. Пример дизасемблированного файл
|
||||
- При вводе данных с клавиатуры, отправляется скан-код клавиши, а не значение нажатой цифры (и не ascii-код нажатой буквы). Более того, при отпускании клавиши, генерируется скан-код `FO`, за которым следует повторная отправка скан-кода этой клавиши.
|
||||
- Работая с uart через программу Putty, вы отправляете ascii-код вводимого символа.
|
||||
|
||||
Таким образом, для этих двух устройств ввода, вам необходимо продумать протокол, по которому вы будете вводить числа в вашу программу. В простейшем случае можно обрабатывать данные "как есть". Т.е. в случае клавиатуры, нажатие на клавишу `1` в верхнем горизонтальном ряду на клавиатуры со скан-кодом 0x16 интерпретировать как число `0x16`. А в случае отправки по uart символа `1` с ascii-кодом `0x31` интерпретировать его как `0x31`. Однако вывод в Putty осуществляется в виде символов принятого ascii-кода, поэтому высок риск получить непечатный символ.
|
||||
Для этих двух устройств ввода, вам необходимо продумать протокол, по которому вы будете вводить числа в вашу программу. В простейшем случае можно обрабатывать данные "как есть". Т.е. в случае клавиатуры, нажатие на клавишу `1` в верхнем горизонтальном ряду на клавиатуры со скан-кодом 0x16 интерпретировать как число `0x16`. А в случае отправки по uart символа `1` с ascii-кодом `0x31` интерпретировать его как `0x31`. Однако вывод в Putty осуществляется в виде символов принятого ascii-кода, поэтому высок риск получить непечатный символ.
|
||||
|
||||
Функция main может быть как пустой, содержать один лишь оператор return или бесконечный цикл — ход работы в любом случае не сломается, т.к. в стартап-файле прописан бесконечный цикл после выполнения main. Тем не менее, вы можете разместить здесь и какую-то логику, получающую данные от обработчика прерываний через глобальные переменные.
|
||||
|
||||
@@ -676,7 +678,7 @@ _Листинг 3. Пример дизасемблированного файл
|
||||
#include "platform.h"
|
||||
|
||||
/*
|
||||
Создаем заголовочном файле "platform.h" объявлены collider_ptr — указатель на
|
||||
В заголовочном файле "platform.h" объявлены collider_ptr — указатель на
|
||||
структуру SUPER_COLLIDER_HANDLE и collider_obj — экземпляр аналогичной
|
||||
структуры.
|
||||
Доступ к полям этой структуры через указатель можно осуществлять посредством
|
||||
@@ -723,11 +725,27 @@ extern "C" void int_handler()
|
||||
|
||||
_Листинг 4. Пример кода на C++, взаимодействующего с выдуманным периферийным устройством через указатели на структуру и массив, объявленные в platform.h._
|
||||
|
||||
При написании программы на языках высокого уровня может возникнуть соблазн использовать все блага высокоуровневого программирования: использовать `scanf`/`cin` для консольного ввода, `printf`/`cout` для консольного вывода, а также использовать динамические массивы, или контейнеры библиотеки STL. Вам следует избегать подобных позывов, поскольку "из коробки" весь этот функционал вам не будет доступен по следующим причинам:
|
||||
|
||||
1. **Консольный ввод-вывод**. Для того чтобы `printf` мог вывести сообщение в ваше устройство вывода, необходимо "рассказать" ему как это сделать. Для этого необходимо переопределить функцию `write`, используемую функцией `printf`.
|
||||
2. **Динамическая память**. Использование динамических массивов и контейнеров, неявно использующих динамическую память ограничено сложной механикой контроля за этой памятью. В системах общего назначения за это отвечает операционная система, однако в нашей встраиваемой системе её не будет. Для использования динамической памяти, вам потребуется написать **аллокатор** — специальную функцию, или класс, обеспечивающий выделение и освобождение памяти.
|
||||
3. **Размер стандартных библиотек**. Даже если вы реализуете весь требуемый функционал, любая из перечисленных выше функций стандартных библиотек C/C++ потянет за собой включение огромного числа кода, который в итоге попросту не уместится в вашу память инструкций.
|
||||
|
||||
Однако, если вы всё ещё горите желанием писать по-настоящему высокоуровневый код, вы можете воспользоваться сторонними библиотеками, написанными специально для встраиваемых систем. К примеру, для `printf` можно использовать следующий репозиторий: https://github.com/mpaland/printf. Для использования этой библиотеки вам потребуется реализовать одну лишь функцию со следующим прототипом:
|
||||
|
||||
```C
|
||||
void _putchar(char character);
|
||||
```
|
||||
|
||||
В этой функции вам необходимо рассказать, как вывести один ASCII-символ в ваше устройство вывода.
|
||||
|
||||
Динамических массивов вам всё же стоит избегать, заменяя их статическими массивами заведомо большего размера. Однако, не всегда можно решить проблему одним лишь статическим массивом. К примеру, вы можете захотеть использовать словарь (ассоциативный массив). В этом случае вы можете использовать следующий репозиторий: https://github.com/mpaland/avl_array. Он не будет использовать динамическую память, в основе данного контейнера будет также лежать статический массив, размер которого вам необходимо будет задать вручную.
|
||||
|
||||
---
|
||||
|
||||
### Порядок выполнения задания
|
||||
|
||||
1. Внимательно изучите разделы теории и практики.
|
||||
1. Внимательно изучите параграфы теории и практики.
|
||||
2. Разберите принцип взаимодействия с контрольными и статусными регистрами периферийного устройства на примере _Листинга 4_.
|
||||
3. Обновите значения параметров `INSTR_MEM_SIZE_BYTES` и `DATA_MEM_SIZE_BYTES` в пакете `memory_pkg` на 32'd1024 и 32'd2048 соответственно. Поскольку пакеты не являются модулями, вы не увидите их во вкладке `Hierarchy` окна исходников, вместо этого вы сможете найти их во вкладках `Libraries` и `Compile order`.
|
||||
4. Напишите программу для своего индивидуального задания и набора периферийных устройств на языке C или C++. В случае написания кода на C++ помните о необходимости добавления `extern "C"` перед определением функции `int_handler`.
|
||||
@@ -749,10 +767,10 @@ _Листинг 4. Пример кода на C++, взаимодействую
|
||||
|
||||
## Список источников:
|
||||
|
||||
1. [ISC-V ABIs Specification, Document Version 1.0', Editors Kito Cheng and Jessica
|
||||
1. [RISC-V ABIs Specification, Document Version 1.0, Editors Kito Cheng and Jessica
|
||||
Clarke, RISC-V International, November 2022](https://github.com/riscv-non-isa/riscv-elf-psabi-doc/releases/download/v1.0/riscv-abi.pdf);
|
||||
2. [Using LD, the GNU linker — Linker Scripts](https://home.cs.colorado.edu/~main/cs1300/doc/gnu/ld_3.html#IDX338);
|
||||
3. [Google Gropus — "gcc gp (global pointer) register"](https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/60IdaZj27dY/m/s1eJMlrUAQAJ?pli=1);
|
||||
3. [Google Groups — "gcc gp (global pointer) register"](https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/60IdaZj27dY/m/s1eJMlrUAQAJ?pli=1);
|
||||
4. [Wikipedia — .bss](https://en.wikipedia.org/wiki/.bss).
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user