Register dan Instruksi RISC-V

Set inti RV32I: 32 register umum, format instruksi, dan simulator kecil yang dipakai sepanjang modul.

Daftar isi

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.

NamaNomorPeran yang disepakati
zerox0selalu bernilai 0, nilai yang ditulis ke sini dibuang
rax1alamat kembali dari sebuah panggilan fungsi
spx2ujung stack
t0 sampai t6x5-x7 dan x28-x31register sementara untuk perhitungan singkat
s0 sampai s11x8-x9 dan x18-x27register yang isinya wajib dijaga antarpanggilan fungsi
a0 sampai a7x10-x17argumen 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.

asm Contoh: konstanta, penjumlahan, dan salinan antarregister
# 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 s0

Dari 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.

BidangNilai bitArti
funct70000000penanda operasi tambah
rs200111t2, yaitu x7
rs100110t1, yaitu x6
funct3000penanda operasi tambah
rd00101t0, yaitu x5
opcode0110011kelas 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 25=322^5 = 32, 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.

312520151270Rfunct7rs2rs1funct3rdopcodeIimm[11:0]rs1funct3rdopcodeSimm[11:5]rs2rs1funct3imm[4:0]opcodeBimm[12|10:5]rs2rs1funct3imm[4:1|11]opcoderegisterkonstantakode operasipenanda operasi
Empat format instruksi RV32I yang paling sering digunakan

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.

python sim.py
#!/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()
text Keluaran
Pemakaian: python3 sim.py [--trace] program.s

Program 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.

asm program.s
# 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
    ecall

Jalankan programnya dengan perintah berikut.

bash
# menjalankan program contoh dengan simulator
python3 sim.py program.s
text Keluaran
42

Keluarannya hanya satu baris berisi 42, hasil dari 25+4+1325 + 4 + 13. 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.

bash
# menjalankan ulang program dengan jejak per langkah
python3 sim.py --trace program.s
text Keluaran
pc=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
42

Jejak 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, nilai ditolak karena operand memori di RV32I selalu berupa offset(register), sehingga simulator memunculkan pesan operand memori tidak valid. Perbaikannya, muat alamat label terlebih dahulu dengan la t0, nilai, lalu akses memori melalui lw 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 didukung dan programnya berhenti. Perbaikannya, letakkan li a7, 1 atau nomor layanan lain sebelum setiap ecall.
  • Menggunakan instruksi di luar set dasar. Menulis mul t0, t1, t2 menghasilkan pesan instruksi tidak dikenal, karena perkalian bukan bagian RV32I. Perbaikannya, susun perkalian dari slli dan add, 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, serta a0 menandai 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#