ЛР10-11. Обновление листинга

This commit is contained in:
Andrei Solodovnikov
2026-07-25 16:40:20 +03:00
parent a64da6312b
commit 4676bf3b77
3 changed files with 170 additions and 64 deletions

116
ERRATA.md
View File

@@ -2,6 +2,122 @@
![http://95.215.8.74:5000/days_since_last_commit.png](http://95.215.8.74:5000/days_since_last_commit.png) ![http://95.215.8.74:5000/days_since_last_commit.png](http://95.215.8.74:5000/days_since_last_commit.png)
**25.07.2026**: В _листинг II.10-1_ (стр. 179-181) были внесены изменения:
1. Адреса программного стека и стека прерываний изменены с `3FFC` и `1FFC` на `1F0` и `F0` соответственно.
Младшая часть адресов изменена с `C` на `0` поскольку верхушка стека должна быть выровнена по границе в 16 байт (таково требование соглашения о вызовах).
Старшая часть адреса была уменьшена с `3F` до `01` и с `1F` до `00`, чтобы оба стека уместились в исходную память данных, чей размер изначально был задан в `memory_pkg` репозитория на значении в 512 байт.
2. Поскольку адреса `1F0` и `F0` умещаются в 12 бит, загрузка этих адресов теперь умещается в одну инструкцию, а не две. Благодаря этому, инструкции `li`, расположенные по адресам `0x00` и `0x18` разворачиваются в одну инструкцию `addi`. Из-за этого изменились адреса всех инструкций в листинге.
3. В листинге была допущена ошибка: верхушка указателя на стек была записана в регистр `x2`, однако в последствии в качестве верхушки стека использовался регистр `x5`. Теперь листинг использует корректный регистр `x2`.
4. В начале обработчика перехвата `trap_handler` между инструкциями с адресами `0x44` и `0x48` была пропущена инструкция поднятия верхушки стека на 16 байт вверх `addi x2, x2, -16`. Аналогично, была пропущена инструкция опускания верхушки стека на 16 байт вниз `addi x2, x2, 16` в конце обработчика `done` между инструкциями с адресами `0x90` и `0x94` соответственно.
<details>
<summary> Исправленная версия листинга </summary>
```asm
_start:
# Инициализируем начальные значения регистров
00: li x2, 0x00001F0 # устанавливаем указатель на верхушку стека
04: li x3, 0x00000000 # устанавливаем указатель на глобальные данные
08: la x5, trap_handler # псевдоинструкция la аналогично li загружает число,
# только в случае la — это число является адресом
# указанного места (адресом обработчика перехвата)
0C: csrw mtvec, x5 # устанавливаем вектор прерывания
10: li x5, 0x000000F0 # готовим адрес верхушки стека прерывания
14: csrw mscratch, x5 # загружаем указатель на верхушку стека прерывания
18: li x5, -1 # подготавливаем маску, разрешающую все источники
# прерывания
1C: csrw mie, x5 # загружаем маску в регистр маски
20: li x5, 1 # начальное значение глобальной переменной
24: sw x5, 0(x3) # загружаем переменную в память
28: li x6, 0 # начальное значение, чтобы в симуляции не было xxx
2C: li x7, 0 # начальное значение, чтобы в симуляции не было xxx
# Вызов ecall исключительно из хулиганских соображений, поскольку в данной
# микроархитектурной реализации это приведет к появлению illegal_instr и
# последующей обработке исключения
30: ecall
# Вызов функции main
main:
34: beq x0, x0, main # бесконечный цикл, аналогичный while (1);
# ОБРАБОТЧИК ПЕРЕХВАТА
# Без стороннего вмешательства процессор никогда не перейдёт к инструкциям ниже,
# однако в случае перехвата в программный счетчик будет загружен адрес первой
# нижележащей инструкции.
# Сохраняем используемые регистры на стек
trap_handler:
38: csrrw x2, mscratch, x2 # меняем местами mscratch и x2
3C: addi x2, x2, -16 # поднимаем верхушку стека на 16 байт вверх
# (указатель на стек всегда должен быть выровнен
# по границе в 16 байт)
40: sw x6, 0(x2) # сохраняем x6 на стек mscratch
44: sw x7, 4(x2) # сохраняем x7 на стек mscratch
# Проверяем произошло ли прерывание
48: csrr x6, mcause # x6 = mcause
4C: li x7, 0x80000010 # загружаем в x7 код того, что произошло прерывание
50: # данная псевдоинструкция будет разбита на две
# инструкции: lui и addi
54: bne x6, x7, exc_handler # если коды не совпадают, переходим к проверке
# на исключение
# Обработчик прерывания
58: lw x7, 0(x3) # загружаем переменную из памяти
5C: addi x7, x7, 3 # прибавляем к значению 3
60: sw x7, 0(x3) # возвращаем переменную в память
64: j done # идем возвращать регистры и на выход
exc_handler: # Проверяем произошло ли исключение
68: li x7, 0x0000002 # загружаем в x7 код того, что произошло исключение
6C: bne x6, x7, done # если это не оно, то выходим
# Обработчик исключения
70: csrr x6, mepc # Узнаём значение PC (адреса инструкции,
# вызвавшей исключение)
74: lw x7, 0x0(x6) # Загружаем эту инструкцию в регистр x7.
# В текущей микроархитектурной реализации это
# невозможно, т.к. память инструкций отделена от
# памяти данных и не участвует в выполнении
# операций load / store.
# Другой способ узнать об инструкции, приведшей
# к исключению — добавить поддержку статусного
# регистра mtval, в который при исключении
# может быть записана текущая инструкция.
# Теоретически мы могли бы после этого
# сделать что-то, в зависимости от этой инструкции.
# Например, если это операция умножения — вызвать
# подпрограмму умножения.
78: addi x6, x6, 4 # Увеличиваем значение PC на 4, чтобы после
# возврата не попасть на инструкцию, вызвавшую
# исключение.
7C: csrw mepc, x6 # Записываем обновленное значение PC в регистр mepc
80: j done # идем восстанавливать регистры со стека и на выход
# Возвращаем регистры на места и выходим
done:
84: lw x6, 0(x2) # возвращаем x6 со стека
88: lw x7, 4(x2) # возвращаем x7 со стека
8C: addi x2, x2, 16 # опускаем верхушку стека обратно на 16 байт вниз
90: csrrw x2, mscratch, x2 # меняем обратно местами x2 и mscratch
94: mret # возвращаем управление программе (pc = mepc)
# что означает возврат в бесконечный цикл
```
</details>
<br>
**30.12.2025**: В Финальном обзоре ЛР№4 (стр. 111) указан неверный тип константы для инструкций переходов: 23-битная `const`, в то время как на самом деле используется 8-битная `offset`. **30.12.2025**: В Финальном обзоре ЛР№4 (стр. 111) указан неверный тип константы для инструкций переходов: 23-битная `const`, в то время как на самом деле используется 8-битная `offset`.
<details> <details>

View File

@@ -237,81 +237,74 @@ _Рисунок 5. Структурная схема контроллера пр
```asm ```asm
_start: _start:
# Инициализируем начальные значения регистров # Инициализируем начальные значения регистров
00: li x2, 0x00003FF0 # устанавливаем указатель на верхушку стека 00: li x2, 0x00001F0 # устанавливаем указатель на верхушку стека
04: # данная псевдоинструкция будет разбита на две 04: li x3, 0x00000000 # устанавливаем указатель на глобальные данные
# инструкции: lui и addi
08: li x3, 0x00000000 # устанавливаем указатель на глобальные данные 08: la x5, trap_handler # псевдоинструкция la аналогично li загружает число,
# только в случае la — это число является адресом
0С: la x5, trap_handler # псевдоинструкция la аналогично li загружает число,
10: # только в случае la — это число является адресом
# указанного места (адресом обработчика перехвата) # указанного места (адресом обработчика перехвата)
# данная псевдоинструкция будет разбита на две
# инструкции: lui и addi
14: csrw mtvec, x5 # устанавливаем вектор прерывания 0C: csrw mtvec, x5 # устанавливаем вектор прерывания
18: li x5, 0x00001FFC # готовим адрес верхушки стека прерывания 10: li x5, 0x000000F0 # готовим адрес верхушки стека прерывания
1С: # данная псевдоинструкция будет разбита на две
# инструкции: lui и addi
20: csrw mscratch, x5 # загружаем указатель на верхушку стека прерывания 14: csrw mscratch, x5 # загружаем указатель на верхушку стека прерывания
24: li x5, 0x00010000 # подготавливаем маску прерывания единственного 18: li x5, -1 # подготавливаем маску, разрешающую все источники
# входа прерываний # прерывания
28: csrw mie, x5 # загружаем маску в регистр маски 1C: csrw mie, x5 # загружаем маску в регистр маски
2С: li x5, 1 # начальное значение глобальной переменной 20: li x5, 1 # начальное значение глобальной переменной
30: sw x5, 0(x3) # загружаем переменную в память 24: sw x5, 0(x3) # загружаем переменную в память
34: li x6, 0 # начальное значение, чтобы в симуляции не было xxx 28: li x6, 0 # начальное значение, чтобы в симуляции не было xxx
38: li x7, 0 # начальное значение, чтобы в симуляции не было xxx 2C: li x7, 0 # начальное значение, чтобы в симуляции не было xxx
# Вызов ecall исключительно из хулиганских соображений, поскольку в данной # Вызов ecall исключительно из хулиганских соображений, поскольку в данной
# микроархитектурной реализации это приведет к появлению illegal_instr и # микроархитектурной реализации это приведет к появлению illegal_instr и
# последующей обработке исключения # последующей обработке исключения
3С: ecall 30: ecall
# Вызов функции main # Вызов функции main
main: main:
40: beq x0, x0, main # бесконечный цикл, аналогичный while (1); 34: beq x0, x0, main # бесконечный цикл, аналогичный while (1);
# ОБРАБОТЧИК ПЕРЕХВАТА # ОБРАБОТЧИК ПЕРЕХВАТА
# Без стороннего вмешательства процессор никогда не перейдет к инструкциям ниже, # Без стороннего вмешательства процессор никогда не перейдёт к инструкциям ниже,
# однако в случае перехвата в программный счетчик будет загружен адрес первой # однако в случае перехвата в программный счетчик будет загружен адрес первой
# нижележащей инструкции. # нижележащей инструкции.
# Сохраняем используемые регистры на стек # Сохраняем используемые регистры на стек
trap_handler: trap_handler:
44: csrrw x5, mscratch, x5 # меняем местами mscratch и x5 38: csrrw x2, mscratch, x2 # меняем местами mscratch и x2
48: addi x5, x5, -16 # поднимаем верхушку стека на 16 байт вверх 3C: addi x2, x2, -16 # поднимаем верхушку стека на 16 байт вверх
# (указатель на стек всегда должен быть выровнен # (указатель на стек всегда должен быть выровнен
# границе в 16 байт) # по границе в 16 байт)
4С: sw x6, 0(x5) # сохраняем x6 на стек mscratch 40: sw x6, 0(x2) # сохраняем x6 на стек mscratch
50: sw x7, 4(x5) # сохраняем x7 на стек mscratch 44: sw x7, 4(x2) # сохраняем x7 на стек mscratch
# Проверяем произошло ли прерывание # Проверяем произошло ли прерывание
54: csrr x6, mcause # x6 = mcause 48: csrr x6, mcause # x6 = mcause
58: li x7, 0x80000010 # загружаем в x7 код того, что произошло прерывание 4C: li x7, 0x80000010 # загружаем в x7 код того, что произошло прерывание
5С: # данная псевдоинструкция будет разбита на две 50: # данная псевдоинструкция будет разбита на две
# инструкции: lui и addi # инструкции: lui и addi
60: bne x6, x7, exc_handler # если коды не совпадают, переходим к проверке 54: bne x6, x7, exc_handler # если коды не совпадают, переходим к проверке
# на исключение # на исключение
# Обработчик прерывания # Обработчик прерывания
64: lw x7, 0(x3) # загружаем переменную из памяти 58: lw x7, 0(x3) # загружаем переменную из памяти
68: addi x7, x7, 3 # прибавляем к значению 3 5C: addi x7, x7, 3 # прибавляем к значению 3
6С: sw x7, 0(x3) # возвращаем переменную в память 60: sw x7, 0(x3) # возвращаем переменную в память
70: j done # идем возвращать регистры и на выход 64: j done # идем возвращать регистры и на выход
exc_handler: # Проверяем произошло ли исключение exc_handler: # Проверяем произошло ли исключение
74: li x7, 0x0000002 # загружаем в x7 код того, что произошло исключение 68: li x7, 0x0000002 # загружаем в x7 код того, что произошло исключение
78: bne x6, x7, done # если это не оно, то выходим 6C: bne x6, x7, done # если это не оно, то выходим
# Обработчик исключения # Обработчик исключения
7С: csrr x6, mepc # Узнаём значение PC (адреса инструкции, 70: csrr x6, mepc # Узнаём значение PC (адреса инструкции,
# вызвавшей исключение) # вызвавшей исключение)
80: lw x7, 0x0(x6) # Загружаем эту инструкцию в регистр x7. 74: lw x7, 0x0(x6) # Загружаем эту инструкцию в регистр x7.
# В текущей микроархитектурной реализации это # В текущей микроархитектурной реализации это
# невозможно, т.к. память инструкций отделена от # невозможно, т.к. память инструкций отделена от
# памяти данных и не участвует в выполнении # памяти данных и не участвует в выполнении
@@ -325,19 +318,19 @@ exc_handler: # Проверяем произошло ли иск
# Например, если это операция умножения — вызвать # Например, если это операция умножения — вызвать
# подпрограмму умножения. # подпрограмму умножения.
84: addi x6, x6, 4 # Увеличиваем значение PC на 4, чтобы после 78: addi x6, x6, 4 # Увеличиваем значение PC на 4, чтобы после
# возврата не попасть на инструкцию, вызвавшую # возврата не попасть на инструкцию, вызвавшую
# исключение. # исключение.
88: csrw mepc, x6 # Записываем обновленное значение PC в регистр mepc 7C: csrw mepc, x6 # Записываем обновленное значение PC в регистр mepc
8С: j done # идем восстанавливать регистры со стека и на выход 80: j done # идем восстанавливать регистры со стека и на выход
# Возвращаем регистры на места и выходим # Возвращаем регистры на места и выходим
done: done:
90: lw x6, 0(x5) # возвращаем x6 со стека 84: lw x6, 0(x2) # возвращаем x6 со стека
94: lw x7, 4(x5) # возвращаем x7 со стека 88: lw x7, 4(x2) # возвращаем x7 со стека
98: addi x5, x5, 16 # опускаем верхушку стека обратно на 16 байт вниз 8C: addi x2, x2, 16 # опускаем верхушку стека обратно на 16 байт вниз
9С: csrrw x5, mscratch, x5 # меняем обратно местами x5 и mscratch 90: csrrw x2, mscratch, x2 # меняем обратно местами x2 и mscratch
A0: mret # возвращаем управление программе (pc = mepc) 94: mret # возвращаем управление программе (pc = mepc)
# что означает возврат в бесконечный цикл # что означает возврат в бесконечный цикл
``` ```

View File

@@ -1,13 +1,10 @@
00004137 1f000113
FF010113
00000193 00000193
04400293 03800293
00028293
30529073 30529073
000022B7 0F000293
FFC28293
34029073 34029073
000102B7 FFF00293
30429073 30429073
00100293 00100293
0051A023 0051A023
@@ -15,10 +12,10 @@ FFC28293
00000393 00000393
00000073 00000073
00000063 00000063
340292F3 34011173
ff028293 FF010113
0062A023 00612023
0072A223 00712223
34202373 34202373
800003B7 800003B7
01038393 01038393
@@ -34,8 +31,8 @@ ff028293
00430313 00430313
34131073 34131073
0040006F 0040006F
0002A303 00012303
0042A383 00412383
01028293 01010113
340292F3 34011173
30200073 30200073