главы 8–9

RISC-V: настоящий набор команд

Учебная ЭВМ показывает принцип. Настоящий процессор RISC-V устроен так же — выборка, декодирование, выполнение, — только регистров 32, команды 32-битные и форматов шесть. Ниже работающий ассемблер и эмулятор: программы из листингов книги собираются в машинный код и выполняются по шагам, с системными вызовами Linux.

RISC и CISC

RISC (RISC-V, ARM) CISC (x86)
Длина команды фиксированная, 32 бита от 1 до 15 байт
Работа с памятью только load/store почти любая команда
Регистров общего назначения 32 16
Декодер простой сложный, команды разбиваются на микрооперации

Внутри современный x86 тоже превращает сложные команды в простые RISC-подобные микрооперации — как учебная ЭВМ превращает ADD в последовательность микрокоманд.

Форматы команд

Rрегистр–регистр: add, sub, mul…
funct7
rs2
rs1
funct3
rd
opcode
Iс константой и загрузки: addi, lw, jalr
imm[11:0]
rs1
funct3
rd
opcode
Sсохранение: sw, sd
imm[11:5]
rs2
rs1
funct3
imm[4:0]
opcode
Bусловный переход: beq, blt
imm[12|10:5]
rs2
rs1
funct3
imm[4:1|11]
opcode
Uстаршие 20 бит: lui, auipc
imm[31:12]
rd
opcode
Jпрыжок: jal
imm[20|10:1|11|19:12]
rd
opcode

Регистры rs1, rs2 и rd всегда лежат в одних и тех же битах во всех форматах. Поэтому декодер может читать регистры, ещё не зная, что это за команда, — одна из причин простоты RISC.

Ассемблер и эмулятор

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

0 шагов
Исходный текст — можно редактировать
Машинный код после ассемблера
1000000002517 auipc a0, 2← la
1000400050513 addi a0, a0, 0← la
1000800002597 auipc a1, 2← la
1000c02758593 addi a1, a1, 39← la
10010028000ef jal ra, toupper
1001400050613 addi a2, a0, 0← mv
1001800100513 addi a0, x0, 1← li
1001c00002597 auipc a1, 2← la
1002001358593 addi a1, a1, 19← la
1002404000893 addi a7, x0, 64← li
1002800000073 ecall
1002c00000513 addi a0, x0, 0← li
1003005d00893 addi a7, x0, 93← li
1003400000073 ecall
10038ff010113 addi sp, sp, -16
1003c00113423 sd ra, 8(sp)
1004000813023 sd s0, 0(sp)
1004400058413 addi s0, a1, 0← mv
1004800050383 lb t2, 0(a0)
1004c00150513 addi a0, a0, 1
1005007a00e13 addi t3, x0, 122← li
10054007e4863 blt t3, t2, cont← bgt
1005806100e13 addi t3, x0, 97← li
1005c01c3c463 blt t2, t3, cont
10060fe038393 addi t2, t2, ('A'-'a')
1006400758023 sb t2, 0(a1)
1006800158593 addi a1, a1, 1
1006c00000e13 addi t3, x0, 0← li
10070fdc39ce3 bne t2, t3, loop
1007440858533 sub a0, a1, s0
1007800013403 ld s0, 0(sp)
1007c00813083 ld ra, 8(sp)
1008001010113 addi sp, sp, 16
1008400008067 jalr x0, 0(ra)← ret
Текущая инструкция и её 32 бита

auipc a0, 2 формат U

будет выполнено следующей · исходная строка 3· часть псевдоинструкции la

imm[31:12]
rd
opcode
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
0
0
1
0
1
0
0
0
1
0
1
1
1

hex 00002517 · imm[31:12] = 2 · rd = x10 (a0) · opcode = 23

Регистры (оранжевым — изменились на последнем шаге)
zero0
ra0
sp0x7ffff000
gp0
tp0
t00
t10
t20
s00
s10
a00
a10
a20
a30
a40
a50
a60
a70
s20
s30
s40
s50
s60
s70
s80
s90
s100
s110
t30
t40
t50
t60
Стек (8 двойных слов от sp)
sp→7ffff0000x0000000000000000
7ffff0080x0000000000000000
7ffff0100x0000000000000000
7ffff0180x0000000000000000
7ffff0200x0000000000000000
7ffff0280x0000000000000000
7ffff0300x0000000000000000
7ffff0380x0000000000000000

Стек растёт вниз от 0x7ffff000. Пока ничего не положено.

Вывод программы (stdout) и код выхода
пока пусто

Как это запускают по книге: QEMU и тулчейн

$ sudo apt install qemu-system-riscv64 u-boot-qemu opensbi      # раздел 8.3.2
$ qemu-system-riscv64 -machine virt -cpu rv64 -m 2G -nographic \
    -kernel /usr/lib/u-boot/qemu-riscv64_smode/uboot.elf \
    -device virtio-blk-device,drive=hd0 -drive file=ubunturv.img,format=raw,id=hd0 …
# внутри гостевой Ubuntu для RISC-V:
$ as -o HelloWorld.o HelloWorld.S      # ассемблер → объектный файл
$ ld -o HelloWorld HelloWorld.o        # компоновщик → исполняемый ELF
$ ./HelloWorld
Hello RISC-V World!
$ gdb ./HelloWorld   →  break _start, run, stepi, info registers   # листинг 8.11