RV32I adalah set instruksi dasar RISC-V untuk prosesor 32 bit, dan topik ini membahas dua bagiannya yang paling sering disentuh saat menulis assembly, yaitu register umum dan format instruksi. Pembahasan mencakup 32 register beserta nama konvensinya, format R, I, S, dan B, pseudo-instruksi, layanan ecall, serta simulator kecil bernama sim.py yang digunakan sepanjang modul ini. Setelah topik ini selesai dibaca, setiap baris program contoh pada modul dapat dibaca sambil menunjuk register dan bidang yang dimaksudnya.
Register umum RV32I#
RV32I menetapkan 32 register umum berukuran 32 bit yang diberi nomor x0 sampai x31, dan setiap register menampung satu nilai bilangan bulat 32 bit. Register x0 menempati posisi istimewa karena nilainya selalu 0, dan penulisan apa pun ke register ini dibuang begitu saja. Fitur itu berguna untuk menyediakan konstanta 0 tanpa instruksi pemuatan, misalnya beq t0, zero, selesai memeriksa apakah t0 bernilai 0. Di luar 32 register umum ini, program counter tidak terjangkau oleh instruksi aritmetika biasa, sehingga alur program hanya dapat diubah melalui instruksi lompatan.
Menulis nomor register sepanjang waktu tidak menyampaikan maksud program, sehingga assembler menerima nama konvensi sebagai pengganti nomor, sesuai konvensi pemanggilan (calling convention), yaitu kesepakatan pembagian peran register.
| Nama | Nomor | Peran yang disepakati |
|---|---|---|
zero | x0 | selalu bernilai 0, nilai yang ditulis ke sini dibuang |
ra | x1 | alamat kembali dari sebuah panggilan fungsi |
sp | x2 | ujung stack |
t0 sampai t6 | x5-x7 dan x28-x31 | register sementara untuk perhitungan singkat |
s0 sampai s11 | x8-x9 dan x18-x27 | register yang isinya wajib dijaga antarpanggilan fungsi |
a0 sampai a7 | x10-x17 | argumen fungsi dan nilai kembalinya |
Pembagian peran ini terasa wajib ketika program mulai memanggil fungsi, karena nilai yang dipegang register s harus tetap utuh meskipun fungsi dipanggil, sedangkan isi register t boleh ditimpa. Simulator modul ini menerima nama konvensi maupun nomor x, sehingga add t0, t1, t2 dan add x5, x6, x7 adalah instruksi yang sama persis. Empat baris berikut memperlihatkan penggunaan yang khas.
# tidak-dijalankan
li t0, 25 # muat konstanta 25 ke t0
li t1, 4 # muat konstanta 4 ke t1
add t2, t0, t1 # t2 = t0 + t1
mv s0, t2 # salin nilai t2 ke s0Dari mnemonic ke pola bit#
Teks seperti add t0, t1, t2 adalah assembly, yaitu notasi teks instruksi mesin yang mudah dibaca manusia. Prosesor tidak mengeksekusi teks, melainkan pola bit, sehingga setiap instruksi RV32I memiliki bentuk biner tetap berukuran 32 bit. Assembler menerjemahkan mnemonic dan operandnya menjadi pola bit itu ketika berkas sumber disusun menjadi program biner. Kata mnemonic berarti pengingat, karena add jauh lebih mudah diingat daripada deretan bitnya.
Instruksi add t0, t1, t2 diubah menjadi enam bidang dalam 32 bit, dan tabel berikut membacanya bidang demi bidang.
| Bidang | Nilai bit | Arti |
|---|---|---|
funct7 | 0000000 | penanda operasi tambah |
rs2 | 00111 | t2, yaitu x7 |
rs1 | 00110 | t1, yaitu x6 |
funct3 | 000 | penanda operasi tambah |
rd | 00101 | t0, yaitu x5 |
opcode | 0110011 | kelas operasi register-register |
Dibaca dari bit tertinggi ke terendah, ke enam bidang itu tersambung menjadi 00000000011100110000001010110011, yang dalam notasi heksadesimal ditulis 0x007302B3. Nilai 32 bit inilah kontrak yang sesungguhnya, karena prosesor hanya melihat pola bit tersebut. Bidang opcode memilih kelas instruksi, sedangkan funct3 dan funct7 memilih operasi di dalam kelasnya. Instruksi sub t0, t1, t2 hanya berbeda pada funct7 yang menjadi 0100000, sehingga bidang lainnya tetap sama persis.
Format instruksi R, I, S, dan B#
Semua instruksi RV32I berukuran sama, tetapi kebutuhan operandnya berbeda. Instruksi aritmetika membutuhkan tiga register, instruksi dengan konstanta membutuhkan dua register dan sebuah immediate, yaitu konstanta yang disimpan di dalam instruksi itu sendiri, sedangkan instruksi percabangan membutuhkan dua register dan alamat tujuan. RV32I mengaturnya melalui format, yaitu pola pembagian 32 bit ke dalam bidang-bidang. Setiap bidang register diberi lebar 5 bit karena , tepat cukup untuk memilih satu dari 32 register.
Diagram berikut menggambar empat format yang paling sering muncul, yaitu R untuk operasi register-register, I untuk konstanta, load memori, dan jalr, S untuk penyimpanan ke memori, serta B untuk percabangan bersyarat. Dua format lain, U untuk lui dan auipc serta J untuk jal, tidak digambar karena polanya lebih sederhana.
Keputusan desain ini terasa pada siklus eksekusi. Instruksi diambil dari memori pada alamat program counter, opcode terbaca pada posisi tetap, dan bidang-bidangnya diurai secara sejajar. Ukuran tetap 32 bit membuat program counter cukup bergerak maju 4 byte untuk mencapai instruksi berikutnya. Kesederhanaan ini menjadi salah satu ciri gaya reduced instruction set computer (RISC) yang diusung RISC-V.
Pseudo-instruksi, direktif, dan ecall#
Set instruksi dasar yang kecil tetap nyaman ditulis berkat pseudo-instruksi, yaitu mnemonic tambahan yang tidak memiliki pola bit sendiri dan selalu diperluas assembler menjadi instruksi dasar. Contoh yang paling sering digunakan adalah li untuk memuat konstanta, mv untuk menyalin register, ret untuk kembali dari fungsi, dan j untuk lompatan tak bersyarat. Instruksi mv s0, t2 sebenarnya adalah addi s0, t2, 0, sedangkan li untuk konstanta besar disusun dari lui dan addi. Program tetap dieksekusi sebagai instruksi dasar, dan pseudo-instruksi hanya memperpendek baris sumbernya.
Direktif adalah perintah untuk assembler, bukan instruksi untuk prosesor. Direktif .text menandai bagian kode, .data menandai bagian data, .word dan .byte meletakkan nilai, sedangkan .asciz meletakkan string yang diakhiri byte 0. Label pada bagian data memegang alamat, dan alamat itu dimuat ke register melalui la t0, nama_label sebelum datanya diakses.
Keluaran program diperoleh melalui ecall, yaitu instruksi yang meminta layanan kepada lingkungan eksekusinya. Nomor layanan disiapkan di a7, sedangkan argumennya diletakkan di a0. Simulator modul ini mengikuti konvensi RARS, yaitu layanan 1 mencetak bilangan bulat bertanda dari a0, layanan 4 mencetak string pada alamat a0, layanan 11 mencetak satu karakter, dan layanan 10 mengakhiri program. Simulator juga menyiapkan register sp dengan nilai 0x10000, sehingga program yang menggunakan stack, yaitu area memori untuk data sementara, memiliki titik awal yang pasti.
RV32I tidak memiliki instruksi perkalian dan pembagian karena kedua operasi itu hadir melalui ekstensi M. Perkalian bulat pada set dasar karenanya disusun dari pergeseran bit seperti slli dan penjumlahan seperti add.
Simulator yang digunakan sepanjang modul#
Modul ini menjalankan seluruh programnya pada sim.py, yaitu simulator RV32I kecil yang ditulis dengan Python dan disertakan utuh pada topik ini. Simulator menerima instruksi inti RV32I, pseudo-instruksi umum, dan direktif .text, .data, .word, .asciz, serta kawan-kawannya. Dijalankan tanpa argumen, simulator hanya mencetak baris bantuan berikut.
#!/usr/bin/env python3
"""sim.py — simulator RV32I subset untuk pembelajaran arsitektur komputer.
Pemakaian:
python3 sim.py [--trace] program.s
python3 sim.py (tanpa argumen: cetak bantuan)
Cakupan:
- Instruksi dasar RV32I (aritmetika, logika, shift, branch, jump,
load/store, lui, auipc) beserta pseudo-instruksi umum.
- Direktif: .text, .data, .globl, .align, .word, .byte, .asciz, .space.
- ecall gaya RARS: a7=1 cetak bilangan bulat (a0), a7=4 cetak string
di alamat a0, a7=11 cetak karakter (a0), a7=10 keluar.
- sp (x2) diinisialisasi ke 0x10000; memori di-nol-kan; pc mulai 0.
"""
from __future__ import annotations
import re
import sys
MASK32 = 0xFFFFFFFF
STACK_TOP = 0x10000
STEP_LIMIT = 1_000_000
REGS = {"zero": 0, "ra": 1, "sp": 2, "gp": 3, "tp": 4,
"t0": 5, "t1": 6, "t2": 7, "s0": 8, "fp": 8, "s1": 9,
"a0": 10, "a1": 11, "a2": 12, "a3": 13, "a4": 14,
"a5": 15, "a6": 16, "a7": 17,
"s2": 18, "s3": 19, "s4": 20, "s5": 21, "s6": 22,
"s7": 23, "s8": 24, "s9": 25, "s10": 26, "s11": 27,
"t3": 28, "t4": 29, "t5": 30, "t6": 31}
for _i in range(32):
REGS[f"x{_i}"] = _i
INT_RE = re.compile(r"^[+-]?(0[xX][0-9a-fA-F]+|0[bB][01]+|\d+)$")
MEMOP_RE = re.compile(r"^([+-]?(?:0[xX][0-9a-fA-F]+|0[bB][01]+|\d+))\s*\(\s*([a-zA-Z0-9]+)\s*\)$")
class SimError(Exception):
pass
def reg(name: str) -> int:
key = name.strip().lower()
if key not in REGS:
raise SimError(f"register tidak dikenal `{name}`")
return REGS[key]
def to_int(text: str) -> int:
s = text.strip().replace("_", "")
if not INT_RE.match(s):
raise SimError(f"angka tidak valid `{text}`")
neg = s.startswith("-")
s = s.lstrip("+-")
value = int(s, 0)
return -value if neg else value
def signed(value: int) -> int:
value &= MASK32
return value - (1 << 32) if value & 0x80000000 else value
def u32(value: int) -> int:
return value & MASK32
class Program:
def __init__(self) -> None:
self.labels: dict[str, int] = {}
self.instructions: list[tuple[int, str, str]] = [] # (pc, teks, mnemonic)
self.data: dict[int, int] = {} # alamat byte -> nilai byte
def parse_source(text: str) -> Program:
prog = Program()
pc = 0
dataptr = None # None = sedang tidak menandai alamat data berikutnya
pending: list[str] = []
def flush_labels() -> None:
for name in pending:
if name in prog.labels:
raise SimError(f"label duplikat `{name}`")
prog.labels[name] = dataptr if dataptr is not None else pc
for lineno, raw in enumerate(text.split("\n"), start=1):
line = raw.split("#", 1)[0].strip()
if not line:
continue
pending = []
while True:
m = re.match(r"^([A-Za-z_.][\w.]*)\s*:\s*(.*)$", line)
if not m:
break
pending.append(m.group(1))
line = m.group(2).strip()
if not line:
flush_labels()
continue
parts = line.split(None, 1)
head = parts[0].lower()
rest = parts[1].strip() if len(parts) > 1 else ""
if head.startswith("."):
flush_labels()
if head in (".text", ".data", ".globl", ".global", ".section"):
if head == ".text":
dataptr = None
elif head == ".data":
dataptr = pc
continue
if head == ".align":
align = 1 << to_int(rest)
if dataptr is not None:
while dataptr % align:
prog.data[dataptr] = 0
dataptr += 1
else:
pad = (align - pc % align) % align
pc += pad
continue
if head in (".word", ".byte", ".asciz", ".string", ".space", ".zero"):
addr = pc if dataptr is None else dataptr
addr_end = addr
if head == ".space" or head == ".zero":
addr_end = addr + to_int(rest)
elif head in (".asciz", ".string"):
s = rest.strip()
if not (s.startswith('"') and s.endswith('"')):
raise SimError(f"baris {lineno}: .asciz butuh string berkutip")
body = s[1:-1].encode("latin-1", "backslashreplace").decode("latin-1")
for ch in body + "\x00":
prog.data[addr_end] = ord(ch) & 0xFF
addr_end += 1
else:
width = 4 if head == ".word" else 1
items = [t.strip() for t in rest.split(",") if t.strip()]
for item in items:
value = to_int(item) & (MASK32 if width == 4 else 0xFF)
for k in range(width):
prog.data[addr_end] = (value >> (8 * k)) & 0xFF
addr_end += 1
if dataptr is None:
pc = addr_end
else:
dataptr = addr_end
continue
raise SimError(f"baris {lineno}: direktif tidak didukung `{head}`")
# instruksi
flush_labels()
if dataptr is not None:
raise SimError(f"baris {lineno}: instruksi setelah label data")
prog.instructions.append((pc, line, head))
pc += 4
return prog
def operands(rest: str) -> list[str]:
if not rest:
return []
return [t.strip() for t in rest.split(",") if t.strip()]
class Sim:
def __init__(self, prog: Program, trace: bool) -> None:
self.prog = prog
self.trace = trace
self.regs = [0] * 32
self.regs[2] = STACK_TOP
self.mem: dict[int, int] = dict(prog.data)
self.pc = 0
self.out: list[str] = []
def read_byte(self, addr: int) -> int:
return self.mem.get(addr & MASK32, 0)
def read(self, addr: int, width: int) -> int:
if addr % width:
raise SimError(f"akses tidak sejajar: alamat 0x{addr & MASK32:08x} lebar {width}")
value = 0
for k in range(width):
value |= self.read_byte(addr + k) << (8 * k)
return value
def write(self, addr: int, width: int, value: int) -> None:
if addr % width:
raise SimError(f"akses tidak sejajar: alamat 0x{addr & MASK32:08x} lebar {width}")
for k in range(width):
self.mem[(addr + k) & MASK32] = (value >> (8 * k)) & 0xFF
def label(self, name: str) -> int:
key = name.strip()
if key not in self.prog.labels:
raise SimError(f"label tidak dikenal `{name}`")
return self.prog.labels[key]
def emit(self, text: str) -> None:
self.out.append(text)
def do_ecall(self) -> bool:
service = signed(self.regs[17])
a0 = self.regs[10]
if service == 1:
self.emit(str(signed(a0)))
elif service == 4:
chars = []
addr = a0
while True:
byte = self.read_byte(addr)
if byte == 0:
break
chars.append(chr(byte))
addr += 1
self.emit("".join(chars))
elif service == 11:
self.emit(chr(a0 & 0xFF))
elif service == 10:
return True
else:
raise SimError(f"ecall layanan {service} tidak didukung")
return False
def step(self) -> bool:
"""Jalankan satu instruksi; kembalikan True bila program selesai."""
index = self.pc // 4
if index < 0 or index >= len(self.prog.instructions):
raise SimError(f"pc 0x{self.pc & MASK32:08x} berada di luar instruksi "
"(program berakhir tanpa ecall exit?)")
pc, text, mnem = self.prog.instructions[index]
if self.trace:
print(f"pc=0x{pc & MASK32:08x} {text}")
args = operands(text.split(None, 1)[1] if len(text.split(None, 1)) > 1 else "")
r = self.regs
def imm_load(rd: int, value: int) -> None:
r[rd] = u32(value)
if mnem in ("addi", "slti", "sltiu", "xori", "ori", "andi", "slli", "srli", "srai"):
rd, rs1 = reg(args[0]), reg(args[1])
imm = to_int(args[2])
if mnem == "addi":
imm_load(rd, r[rs1] + imm)
elif mnem == "slti":
imm_load(rd, 1 if signed(r[rs1]) < imm else 0)
elif mnem == "sltiu":
imm_load(rd, 1 if r[rs1] < u32(imm) else 0)
elif mnem == "xori":
imm_load(rd, r[rs1] ^ imm)
elif mnem == "ori":
imm_load(rd, r[rs1] | imm)
elif mnem == "andi":
imm_load(rd, r[rs1] & imm)
elif mnem == "slli":
imm_load(rd, r[rs1] << (imm & 31))
elif mnem == "srli":
imm_load(rd, r[rs1] >> (imm & 31))
else:
imm_load(rd, signed(r[rs1]) >> (imm & 31))
elif mnem in ("add", "sub", "sll", "slt", "sltu", "xor", "srl", "sra", "or", "and"):
rd, rs1, rs2 = reg(args[0]), reg(args[1]), reg(args[2])
if mnem == "add":
imm_load(rd, r[rs1] + r[rs2])
elif mnem == "sub":
imm_load(rd, r[rs1] - r[rs2])
elif mnem == "sll":
imm_load(rd, r[rs1] << (r[rs2] & 31))
elif mnem == "slt":
imm_load(rd, 1 if signed(r[rs1]) < signed(r[rs2]) else 0)
elif mnem == "sltu":
imm_load(rd, 1 if r[rs1] < r[rs2] else 0)
elif mnem == "xor":
imm_load(rd, r[rs1] ^ r[rs2])
elif mnem == "srl":
imm_load(rd, r[rs1] >> (r[rs2] & 31))
elif mnem == "sra":
imm_load(rd, signed(r[rs1]) >> (r[rs2] & 31))
elif mnem == "or":
imm_load(rd, r[rs1] | r[rs2])
else:
imm_load(rd, r[rs1] & r[rs2])
elif mnem == "lui":
imm_load(reg(args[0]), to_int(args[1]) << 12)
elif mnem == "auipc":
imm_load(reg(args[0]), pc + (to_int(args[1]) << 12))
elif mnem in ("beq", "bne", "blt", "bge", "bltu", "bgeu"):
rs1, rs2 = reg(args[0]), reg(args[1])
target = self.label(args[2])
taken = {"beq": r[rs1] == r[rs2], "bne": r[rs1] != r[rs2],
"blt": signed(r[rs1]) < signed(r[rs2]),
"bge": signed(r[rs1]) >= signed(r[rs2]),
"bltu": r[rs1] < r[rs2], "bgeu": r[rs1] >= r[rs2]}[mnem]
if taken:
self.pc = target
return False
elif mnem == "jal":
if len(args) == 1:
rd, target = 1, self.label(args[0])
else:
rd, target = reg(args[0]), self.label(args[1])
imm_load(rd, pc + 4)
self.pc = target
return False
elif mnem == "jalr":
if len(args) == 1:
rd, base, off = 1, reg(args[0]), 0
else:
rd, base = reg(args[0]), reg(args[1])
off = to_int(args[2]) if len(args) > 2 else 0
dest = (r[base] + off) & MASK32 & ~1
imm_load(rd, pc + 4)
self.pc = dest
return False
elif mnem in ("lb", "lh", "lw", "lbu", "lhu"):
rd, m = reg(args[0]), MEMOP_RE.match(args[1])
if not m:
raise SimError(f"operand memori tidak valid `{args[1]}`")
addr = u32(r[reg(m.group(2))] + to_int(m.group(1)))
width = {"lb": 1, "lbu": 1, "lh": 2, "lhu": 2, "lw": 4}[mnem]
value = self.read(addr, width)
if mnem in ("lb", "lh") and value & (1 << (width * 8 - 1)):
value -= 1 << (width * 8)
imm_load(rd, value)
elif mnem in ("sb", "sh", "sw"):
rs2, m = reg(args[0]), MEMOP_RE.match(args[1])
if not m:
raise SimError(f"operand memori tidak valid `{args[1]}`")
addr = u32(r[reg(m.group(2))] + to_int(m.group(1)))
self.write(addr, {"sb": 1, "sh": 2, "sw": 4}[mnem], r[rs2])
elif mnem == "ecall":
if self.do_ecall():
return True
elif mnem == "ebreak":
return True
else:
# pseudo-instruksi
if mnem == "nop":
pass
elif mnem == "li":
imm_load(reg(args[0]), to_int(args[1]))
elif mnem == "la":
imm_load(reg(args[0]), self.label(args[1]))
elif mnem == "mv":
imm_load(reg(args[0]), r[reg(args[1])])
elif mnem == "not":
imm_load(reg(args[0]), ~r[reg(args[1])])
elif mnem == "neg":
imm_load(reg(args[0]), -r[reg(args[1])])
elif mnem == "seqz":
imm_load(reg(args[0]), 1 if r[reg(args[1])] == 0 else 0)
elif mnem == "snez":
imm_load(reg(args[0]), 1 if r[reg(args[1])] != 0 else 0)
elif mnem == "sltz":
imm_load(reg(args[0]), 1 if signed(r[reg(args[1])]) < 0 else 0)
elif mnem == "j":
self.pc = self.label(args[0])
return False
elif mnem == "jr":
self.pc = r[reg(args[0])] & MASK32 & ~1
return False
elif mnem == "ret":
self.pc = r[1] & MASK32 & ~1
return False
elif mnem == "call":
target = self.label(args[0])
imm_load(1, pc + 4)
self.pc = target
return False
elif mnem in ("beqz", "bnez", "bgez", "bltz", "bgtz", "blez"):
rs1 = reg(args[0])
taken = {"beqz": r[rs1] == 0, "bnez": r[rs1] != 0,
"bgez": signed(r[rs1]) >= 0, "bltz": signed(r[rs1]) < 0,
"bgtz": signed(r[rs1]) > 0, "blez": signed(r[rs1]) <= 0}[mnem]
if taken:
self.pc = self.label(args[1])
return False
elif mnem in ("bgt", "ble", "bgtu", "bleu"):
rs1, rs2 = reg(args[0]), reg(args[1])
taken = {"bgt": signed(r[rs1]) > signed(r[rs2]),
"ble": signed(r[rs1]) <= signed(r[rs2]),
"bgtu": r[rs1] > r[rs2], "bleu": r[rs1] <= r[rs2]}[mnem]
if taken:
self.pc = self.label(args[2])
return False
else:
raise SimError(f"instruksi tidak dikenal `{mnem}`")
self.pc = u32(self.pc + 4)
return False
def run(self) -> None:
for _ in range(STEP_LIMIT):
if self.step():
return
raise SimError("batas langkah terlampaui (kemungkinan perulangan tak berujung)")
def main() -> None:
argv = sys.argv[1:]
trace = "--trace" in argv
argv = [a for a in argv if a != "--trace"]
if not argv:
print("Pemakaian: python3 sim.py [--trace] program.s")
return
try:
with open(argv[0], encoding="utf-8") as fh:
source = fh.read()
except OSError as exc:
print(f"sim: {exc}", file=sys.stderr)
raise SystemExit(1)
try:
prog = parse_source(source)
if not prog.instructions:
raise SimError("tidak ada instruksi untuk dijalankan")
sim = Sim(prog, trace)
sim.run()
for line in sim.out:
print(line)
except SimError as exc:
print(f"sim: {exc}", file=sys.stderr)
raise SystemExit(1)
if __name__ == "__main__":
main()Pemakaian: python3 sim.py [--trace] program.sProgram diberikan sebagai berkas .s, dan simulator hanya mencetak keluaran yang diminta program melalui ecall. Berkas program.s berikut menjumlahkan tiga konstanta melalui register, lalu mencetak hasilnya sebelum berhenti.
# tidak-dijalankan
# program.s — demonstrasi register dan instruksi dasar RV32I
# Menjumlahkan tiga konstanta melalui register, lalu mencetak hasilnya.
.text
.globl main
main:
li t0, 25 # muat konstanta 25 ke t0
li t1, 4 # muat konstanta 4 ke t1
li t2, 13 # muat konstanta 13 ke t2
add a0, t0, t1 # a0 = t0 + t1, yaitu 29
add a0, a0, t2 # a0 = a0 + t2, yaitu 42
mv s0, a0 # simpan salinan hasil di s0
li a7, 1 # layanan ecall 1: cetak bilangan bulat
ecall # cetak isi a0
li a7, 10 # layanan ecall 10: keluar dari program
ecallJalankan programnya dengan perintah berikut.
# menjalankan program contoh dengan simulator
python3 sim.py program.s42Keluarannya hanya satu baris berisi 42, hasil dari . Angka itu dihasilkan ecall layanan 1 yang mencetak isi a0, dan programnya berhenti pada ecall layanan 10. Opsi –trace menambahkan satu baris per langkah, berisi alamat program counter dan instruksinya, sehingga jalannya program dapat ditelusuri.
# menjalankan ulang program dengan jejak per langkah
python3 sim.py --trace program.spc=0x00000000 li t0, 25
pc=0x00000004 li t1, 4
pc=0x00000008 li t2, 13
pc=0x0000000c add a0, t0, t1
pc=0x00000010 add a0, a0, t2
pc=0x00000014 mv s0, a0
pc=0x00000018 li a7, 1
pc=0x0000001c ecall
pc=0x00000020 li a7, 10
pc=0x00000024 ecall
42Jejak ini memperlihatkan alamat program counter yang bertambah 4 per instruksi karena setiap instruksi RV32I berukuran 4 byte, serta pseudo-instruksi yang tampil dalam bentuk teks asalnya karena simulator menafsirkannya secara langsung. Keluaran 42 dicetak di akhir karena simulator mengumpulkan hasil ecall selama eksekusi dan menampilkannya setelah programnya berhenti.
Kesalahan umum#
Tiga error berikut paling sering muncul pada sesi pertama bersama simulator.
- Mengakses data berlabel tanpa memuat alamatnya. Bentuk
lw t1, nilaiditolak karena operand memori di RV32I selalu berupaoffset(register), sehingga simulator memunculkan pesanoperand memori tidak valid. Perbaikannya, muat alamat label terlebih dahulu denganla t0, nilai, lalu akses memori melaluilw t1, 0(t0). - Menjalankan ecall tanpa menyiapkan a7. Nilai a7 yang masih 0 bukan layanan yang dikenal, sehingga simulator memunculkan pesan
ecall layanan 0 tidak didukungdan programnya berhenti. Perbaikannya, letakkanli a7, 1atau nomor layanan lain sebelum setiapecall. - Menggunakan instruksi di luar set dasar. Menulis
mul t0, t1, t2menghasilkan pesaninstruksi tidak dikenal, karena perkalian bukan bagian RV32I. Perbaikannya, susun perkalian darisllidanadd, atau jalankan program pada implementasi yang menambahkan ekstensi M.
Simulator membantu karena pesan errornya selalu menyebut penyebabnya, misalnya register yang tidak dikenal, nomor layanan yang belum disiapkan, atau mnemonic yang tidak dikenal, sehingga perbaikannya dapat ditunjuk langsung dari pesannya.
Ringkasan#
- RV32I menyediakan 32 register umum berukuran 32 bit dengan x0 selalu bernilai 0, dan nama konvensi seperti
t0,s0, sertaa0menandai peran register di dalam program. - Setiap instruksi memiliki notasi assembly dan pola bit tetap 32 bit, dengan opcode sebagai penentu kelas serta funct3 dan funct7 sebagai penanda variasi operasi.
- Format R, I, S, dan B membagi 32 bit ke dalam bidang register dan immediate pada posisi yang tetap, sehingga pengambilan dan penguraian instruksi berjalan sejajar.
- Pseudo-instruksi dan direktif merapikan sumber program, sedangkan sim.py mengeksekusi set dasar RV32I beserta ecall gaya RARS dan opsi –trace.
Referensi#
- RISC-V ISA Manual — RISC-V International — Spesifikasi resmi yang menetapkan register, format instruksi, dan encoding RV32I.
- RARS — TheThirdOne — Simulator RISC-V pendidikan yang konvensi ecall-nya diikuti oleh sim.py.
- Computer Systems: A Programmer’s Perspective — Bryant dan O’Hallaron — Buku yang membahas register dan format instruksi prosesor nyata pada bab machine-level programming.