Mode Pengalamatan

Cara operand ditemukan: immediate, register, dan akses memori dengan offset pada RISC-V.

Daftar isi

Setiap instruksi RISC-V harus menemukan nilai yang dikerjakannya, dan cara penemuan itu disebut mode pengalamatan (addressing mode). Topik ini membahas tiga mode yang disediakan RV32I, yaitu immediate, register, dan memori dengan base dan offset, lalu menjalankan satu program contoh yang memakai ketiganya. Setelah membacanya, setiap operand pada instruksi RV32I dapat dibaca dan alamat data yang dihitungnya dapat diperiksa sebelum program dijalankan.

Tiga tempat operand berada#

Instruksi RV32I mengambil operand dari tiga tempat saja. Konstanta kecil tertanam langsung di dalam instruksi sebagai immediate, data dibaca dari salah satu register, atau data diambil dari memori melalui alamat yang tersimpan pada register. Semua operasi aritmetika dan logika hanya bekerja antarregister, sedangkan memori hanya disentuh oleh instruksi load, yaitu pembacaan memori ke register, dan store, yaitu penulisan register ke memori. Rancangan semacam ini disebut arsitektur load-store, yakni arsitektur yang memisahkan perhitungan dari akses memori secara ketat.

Batasan itu membuat bentuk setiap instruksi tetap kecil dan seragam. Mesin tidak perlu menyiapkan jalur data untuk banyak kombinasi sumber operand, sehingga perancangannya sederhana. Tabel berikut merangkum ketiga mode beserta bentuk operandnya.

ModeBentuk operandContohNilai yang dipakai
immediatekonstanta pada instruksiaddi t0, zero, 7angka 7
registernama registeradd t2, t0, t1isi t0 dan t1
memorioffset(base)lw t5, 0(t4)isi alamat t4 + 0

Ketiga mode dapat muncul dalam satu instruksi sekaligus. Pada addi t0, zero, 7, operand pertama dan kedua berupa register, sedangkan operand ketiga berupa immediate. Instruksi load menggabungkan register dan memori, karena lw t5, 0(t4) membaca memori pada alamat hasil penjumlahan register t4 dan immediate 0.

Immediate sebagai konstanta di dalam instruksi#

Immediate adalah konstanta yang tertanam langsung di dalam instruksi, sehingga nilainya tersedia seketika tanpa akses register maupun memori. Pada RV32I, immediate aritmetika berukuran 12 bit bertanda dengan rentang -2048 sampai 2047. Ukuran itu dipakai karena 32 bit instruksi juga harus memuat kode operasi dan nomor register. Baris addi t0, t1, 10 menambahkan konstanta 10 ke isi t1, lalu menaruh hasilnya di t0.

Konstanta di luar rentang 12 bit tidak muat dalam satu instruksi. Pseudo-instruksi, yaitu instruksi praktis yang diterjemahkan menjadi satu atau lebih instruksi dasar, menutup kebutuhan itu. Pseudo-instruksi li memasang konstanta apa pun pada register tanpa perlu menulis rinciannya, dan pada perangkat keras sungguhan konstanta besar diterjemahkan menjadi pasangan lui dan addi, sedangkan simulator dalam modul ini menjalankannya sebagai satu langkah.

asm Contoh: konstanta immediate pada instruksi
# tidak-dijalankan
addi t0, zero, 7        # konstanta 7 tertanam pada instruksi
addi t1, t0, -3         # immediate negatif juga sah
li   t2, 100000         # konstanta besar melalui pseudo-instruksi li

Immediate juga dipakai pada operasi logika. Instruksi andi, ori, dan xori menerima konstanta dengan bentuk yang sama seperti addi, sehingga bitmask dapat ditulis langsung tanpa menyiapkan register terlebih dahulu.

Register sebagai operand tercepat#

Operand register dibaca dari register file, yaitu kumpulan 32 register umum berukuran 32 bit di dalam prosesor. Instruksi aritmetika tiga operand seperti add t2, t0, t1 membaca dua register sumber dan menulis satu register tujuan dalam satu langkah tanpa menyentuh memori. Akses ini paling cepat di antara ketiga mode karena register berada persis di sisi jalur perhitungan.

Register zero memperluas gaya penulisan program. Nilainya selalu nol dan tidak dapat diubah, sehingga zero dipakai sebagai sumber konstanta nol maupun tempat membuang hasil yang tidak dibutuhkan. Pseudo-instruksi mv t0, t1 sesungguhnya adalah addi t0, t1, 0, yakni penyalinan lewat penjumlahan nol. Dengan pola yang sama, addi t0, zero, 7 merupakan cara baku memasang konstanta 7 pada register.

Dalam program nyata, nilai yang sering dipakai dijaga di register selama mungkin. Setiap kali nilai dikirim ke memori, instruksi berikutnya harus menanggung biaya pembacaannya kembali. Prinsip kerjanya satu kalimat, yaitu perhitungan berlangsung di register dan memori dipakai untuk penyimpanan jangka panjang.

Memori: alamat base dengan offset#

Akses memori RV32I selalu berbentuk offset(base). Register base menampung alamat awal, sedangkan offset, yakni jarak tetap dalam byte dari alamat awal tersebut, ditulis sebagai konstanta pada instruksi. Alamat efektif dihitung sebagai base+offset \text{base} + \text{offset} , lalu lw membaca satu word, yaitu data berukuran 4 byte, dari alamat itu. Sebaliknya, sw menulis isi register ke alamat yang dihitung dengan cara yang sama.

Alamat data berlabel tidak perlu dihitung dengan tangan. Instruksi la t4, nilai menghitung alamat label nilai dan menaruhnya di t4, sehingga elemen pertama array dibaca dengan lw t5, 0(t4), elemen kedua dengan lw t5, 4(t4), dan seterusnya. Array adalah kumpulan nilai yang disimpan berurutan di memori, sehingga geseran offset per elemennya tetap, yaitu 4 byte untuk word. Offset negatif juga sah, misalnya lw t5, -4(t4) membaca word tepat sebelum alamat yang ditunjuk t4.

Offset pada load dan store berukuran 12 bit bertanda, sama seperti immediate addi. Untuk data yang lebih kecil, RV32I menyediakan lb dan lbu untuk satu byte serta lh dan lhu untuk halfword, yaitu data 2 byte, dengan pasangan sb dan sh untuk menulisnya. Word harus berada pada alamat kelipatan 4 dan halfword pada kelipatan 2, sehingga akses yang tidak sejajar menimbulkan error.

Menjalankan program contoh#

Simulator RV32I yang dipakai sepanjang modul ini disertakan kembali di bundle topik ini sebagai sim.py. Berkas program memakai ekstensi .s dan dijalankan dengan python3 sim.py program.s, sedangkan tanpa argumen simulator hanya mencetak baris pemakaiannya. Layanan keluarannya mengikuti konvensi RARS, yaitu simulator pendidikan RISC-V, dengan nomor layanan pada a7 dan argumen pada a0, misalnya layanan 1 untuk mencetak bilangan bertanda dan layanan 10 untuk keluar.

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 berikut memakai ketiga mode dalam satu alur. Bagian immediate menampilkan konstanta 7, bagian register menggandakannya menjadi 14, dan bagian memori menjumlahkan array empat bacaan suhu ruangan lalu menyimpan hasilnya ke label hasil sebelum dicetak. Direktif .data menandai bagian data, .text menandai bagian instruksi, dan .space 4 menyediakan satu word kosong untuk hasil.

asm program.s
# tidak-dijalankan
# program.s - contoh tiga mode pengalamatan dalam satu program

        .data
nilai:  .word 12, 7, 20, 5      # array empat suhu ruangan
hasil:  .space 4                # satu word untuk hasil penjumlahan

        .text
        .globl main
main:
        # mode immediate: konstanta tertanam di dalam instruksi
        addi t0, zero, 7        # t0 = 7
        mv a0, t0
        li a7, 1                # layanan cetak bilangan
        ecall                   # cetak 7

        # mode register: operand berasal dari register
        add t1, t0, t0          # t1 = 7 + 7 = 14
        mv a0, t1
        li a7, 1
        ecall                   # cetak 14

        # mode memori: alamat base ditambah offset
        la t4, nilai            # alamat array ke t4
        li t3, 4                # jumlah elemen
        li t2, 0                # penjumlah mulai dari nol
ulang:
        lw t5, 0(t4)            # baca elemen pada offset 0 dari t4
        add t2, t2, t5          # jumlahkan ke penjumlah
        addi t4, t4, 4          # geser base satu word ke elemen berikutnya
        addi t3, t3, -1
        bnez t3, ulang
        la t6, hasil
        sw t2, 0(t6)            # simpan penjumlah ke memori
        lw a0, 0(t6)            # baca kembali dari memori
        li a7, 1
        ecall                   # cetak 44

        li a7, 10               # layanan keluar
        ecall
bash
# menjalankan program contoh
python3 sim.py program.s
text Keluaran
7
14
44

Keluarannya tiga baris, yaitu 7, 14, dan 44. Jumlah 12, 7, 20, dan 5 memang 44, sehingga perhitungan offset pada perulangannya mudah diperiksa ulang. Perhatikan bahwa penjumlahan array tidak pernah memakai immediate selain offset 0(t4) dan geseran 4, karena nilai elemennya tinggal di memori.

Melihat jejak eksekusi dengan –trace#

Sekali dijalankan, program hanya menampilkan hasil akhirnya. Opsi --trace membuat simulator mencetak program counter, yaitu register penunjuk alamat instruksi yang sedang dijalankan, beserta teks instruksinya untuk setiap langkah. Program pendek berikut tidak mencetak apa pun dan langsung keluar, tetapi jejaknya memperlihatkan satu hal penting, yaitu teks instruksi memuat immediate-nya, sehingga operand konstanta memang tersimpan di dalam instruksi.

asm trace.s
# tidak-dijalankan
# trace.s - program pendek yang dijejak dengan --trace

        li t0, 7
        li t1, 5
        add t2, t0, t1
        mv a0, t2
        li a7, 10               # layanan keluar
        ecall
bash
# menjalankan program pendek dengan jejak eksekusi
python3 sim.py --trace trace.s
text Keluaran
pc=0x00000000  li t0, 7
pc=0x00000004  li t1, 5
pc=0x00000008  add t2, t0, t1
pc=0x0000000c  mv a0, t2
pc=0x00000010  li a7, 10
pc=0x00000014  ecall

Program counter bergerak empat byte per instruksi, yaitu 0x00000000, 0x00000004, dan seterusnya, karena setiap instruksi RV32I berukuran tetap 32 bit. Tidak ada lompatan pada jejak itu, sehingga keenam baris dieksekusi berurutan tanpa satu langkah pun yang dilewati. Opsi ini menambah satu baris keluaran per langkah, sehingga praktis hanya untuk program pendek, dan jejak program berperulangan akan memanjang seiring jumlah iterasinya.

Keluaran trace tidak dimaksudkan untuk dibaca sekilas, melainkan untuk dicocokkan dengan berkasnya. Setiap baris menampilkan alamat terlebih dahulu, lalu teks instruksi persis seperti yang ditulis pada berkas .s dengan komentarnya yang sudah dibuang. Dengan kebiasaan itu, jejak dapat dibandingkan baris demi baris tanpa alat tambahan apa pun.

Kesalahan umum#

  • Mengakses label tanpa la. Instruksi load menuntut register base, sehingga lw t1, nilai ditolak sebagai operand memori yang tidak valid. Perbaikannya, muat alamatnya terlebih dahulu dengan la t1, nilai.
  • Offset di luar rentang 12 bit. Konstanta seperti 8192 tidak muat pada immediate, sehingga alamat yang jauh dibangun di register terlebih dahulu, misalnya dengan li lalu add.
  • Akses tidak sejajar. lw menuntut alamat kelipatan 4 dan lh kelipatan 2, sehingga offset yang dihitung keliru menimbulkan error akses tidak sejajar.
  • Mengira array memiliki indeks seperti pada bahasa tingkat tinggi. Tingkat mesin tidak menyediakannya, sehingga elemen ke-i dihitung dengan menambahkan offset 4 sebanyak i kali ke register base sebelum lw dijalankan. Instruksi slli t6, t3, 2 mengalikan indeks dengan empat lewat geseran bit, lalu hasilnya ditambahkan ke base dengan add.
  • Menganggap memori berisi nol dengan sendirinya. Simulator menginisialisasi memori ke nol, tetapi kebiasaan yang aman adalah mengisi setiap byte yang dipakai sebelum dibaca, misalnya melalui sw pada setiap slot yang disediakan .space.

Ringkasan#

  • RV32I hanya mengenal tiga sumber operand, yaitu immediate yang tertanam pada instruksi, register, dan memori melalui bentuk offset(base).
  • Immediate aritmetika berukuran 12 bit bertanda, dan konstanta di luar rentang itu dipasang melalui pseudo-instruksi li.
  • Akses memori berlabel dimulai dari la untuk menghitung alamat, lalu lw dan sw dengan offset dari register base, dengan syarat alamat sejajar ukuran datanya.
  • Setiap instruksi berukuran 4 byte sehingga program counter bergerak empat byte per langkah, dan opsi --trace memperlihatkan pergerakan itu langkah demi langkah.

Referensi#