Setiap instruksi menjalani urutan pekerjaan yang sama, yaitu fetch untuk pengambilan instruksi dari memori, decode untuk penafsiran kata instruksinya, execute untuk pelaksanaan operasinya, dan write-back untuk penulisan hasil ke register. Urutan inilah siklus fetch-decode-execute, dan pada datapath siklus tunggal seluruhnya selesai dalam satu clock. Topik ini mengurai siklus menjadi langkah mikro yang dapat diamati satu per satu, kemudian menelusurinya instruksi demi instruksi pada program fiktif lima instruksi yang memuat pemuatan, perhitungan, percabangan, dan penyimpanan.
Empat tahap dalam satu clock#
Empat tahap membentuk satu siklus penuh. Tahap fetch membaca kata instruksi pada alamat yang ditunjuk PC dari memori instruksi. Tahap decode memecah kata itu menjadi field, yaitu opcode, field register, dan offset, kemudian membaca dua operand dari register file sesuai field register. Tahap execute menggunakan ALU, baik untuk menghitung hasil penjumlahan, membentuk alamat load atau store, maupun menguji selisih dua register untuk percabangan. Tahap write-back, yaitu penulisan hasil ke register tujuan, menutup siklus dan hanya berlaku bila instruksinya menghasilkan nilai.
Akses memori data menjadi tahap tersendiri yang disisipkan di antara execute dan write-back, karena hanya load dan store yang menyentuh memori data. Pada load, memori dibaca setelah alamatnya terbentuk di ALU, sedangkan pada store, memori ditulis dengan operand yang sudah dibaca sejak tahap decode. Instruksi add dan beq melompati tahap ini begitu saja, karena jalur kabelnya memang tidak melewati memori data. Jejak keluaran nanti menampakkan perbedaan itu sebagai baris memori yang hadir dan hilang sesuai jenis instruksinya.
Urutan yang kaku dan nilai yang mengalir sekaligus#
Keempat tahap berjalan dalam satu clock yang sama, sehingga nilai mengalir sekaligus dan tidak ada tahap yang menunggu clock berikutnya. Meskipun begitu, urutan logisnya tetap kaku, karena operand tidak mungkin dibaca sebelum field register diketahui, dan alamat tidak mungkin terbentuk sebelum operand tersedia. Urutan inilah yang membedakan siklus mikro dari daftar langkah yang boleh dibolak-balik. Memori instruksi dibaca lebih dahulu pada ujung masuk, memori data diakses di tengah jalur, dan register file ditulis paling akhir pada ujung keluar.
Perangkat keras nyata menampung instruksi yang baru diambil pada register khusus, yaitu instruction register, agar instruksi itu tetap tersedia ketika tahap berikutnya bekerja. Pada datapath siklus tunggal, instruksi hanya perlu bertahan selama satu clock di sepanjang kabelnya, sehingga register antartahap itu belum diperlukan. Kebutuhan muncul kembali ketika satu clock menampung beberapa instruksi sekaligus pada pipelining, dan saat itulah setiap batas tahap diberi register penyimpannya sendiri. Urutan yang kaku juga berarti siklus berjalan tanpa jeda satu sama lain, karena PC yang baru sudah diputuskan pada siklus yang sama dan fetch siklus berikutnya langsung dimulai.
Menelusuri siklus pada simulasi#
Program simulasi berisi lima instruksi fiktif 16 bit dengan format opcode 4 bit, field register 3 bit, dan offset 6 bit. Ceritanya sederhana, yaitu memuat nilai 15 yang sudah tersimpan di mem[17], menghitung jumlah yang sama dari R2 dan R3, membandingkan keduanya, lalu menyimpan nilai yang dimuat itu ke mem[18] bila cocok. Instruksi add R5 pada alamat 3 sengaja diletakkan pada jalur yang dilewati ketika cabang diambil, dan akibatnya terlihat dari isi R5 pada akhir jejak. Berkas berikut mencetak setiap siklus beserta tahap-tahapnya.
"""Jejak siklus mikro fetch-decode-execute untuk ISA fiktif 16 bit.
Setiap instruksi dicetak per tahap: fetch, decode, execute, memori
(bila menyentuh memori data), dan write-back.
"""
NAMA_OP = {1: "add", 3: "load", 4: "store", 5: "beq"}
def enc_add(rd, rs1, rs2):
return (1 << 12) | (rd << 9) | (rs1 << 6) | (rs2 << 3)
def enc_load(rd, rs1, off):
return (3 << 12) | (rd << 9) | (rs1 << 6) | (off & 0x3F)
def enc_store(rs, rs1, off):
return (4 << 12) | (rs << 9) | (rs1 << 6) | (off & 0x3F)
def enc_beq(rs1, rs2, off):
return (5 << 12) | (rs2 << 9) | (rs1 << 6) | (off & 0x3F)
def decode(instr):
"""Memecah kata 16 bit menjadi field sesuai posisi bitnya."""
op = (instr >> 12) & 0xF
a = (instr >> 9) & 0x7
b = (instr >> 6) & 0x7
c = (instr >> 3) & 0x7
raw = instr & 0x3F
off = raw - 64 if raw & 0x20 else raw
return op, a, b, c, off
# Keadaan awal mesin: register file dan memori data.
R = [0] * 8
R[2], R[3] = 10, 5
MEM = {17: 15} # mem[17] sudah berisi 15 sebagai sisa perhitungan sebelumnya
# Memori instruksi: lima kata 16 bit. Instruksi pada alamat 3 dilewati
# apabila cabang pada alamat 2 diambil.
PROGRAM = [
enc_load(4, 2, 7), # 0: load R4, R2, 7
enc_add(1, 2, 3), # 1: add R1, R2, R3
enc_beq(1, 4, 1), # 2: beq R1, R4, 1
enc_add(5, 4, 1), # 3: add R5, R4, R1
enc_store(4, 1, 3), # 4: store R4, R1, 3
]
pc = 0
urut = 0
while pc < len(PROGRAM):
urut += 1
instr = PROGRAM[pc]
op, a, b, c, off = decode(instr)
print(f"== Instruksi {urut} | PC={pc} ==")
print(f" fetch : 0x{instr:04X} ({NAMA_OP[op]})")
if op == 1:
print(f" decode : rd=R{a}, rs1=R{b}, rs2=R{c}")
print(f" execute : ALU {R[b]} + {R[c]} = {R[b] + R[c]}")
R[a] = R[b] + R[c]
print(f" write-back: R{a} <- {R[a]}")
elif op == 3:
print(f" decode : rd=R{a}, rs1=R{b}, offset={off}")
alamat = R[b] + off
print(f" execute : ALU {R[b]} + {off} = {alamat} (alamat)")
print(f" memori : mem[{alamat}] = {MEM[alamat]}")
R[a] = MEM[alamat]
print(f" write-back: R{a} <- {R[a]}")
elif op == 4:
print(f" decode : rs2=R{a}, rs1=R{b}, offset={off}")
alamat = R[b] + off
print(f" execute : ALU {R[b]} + {off} = {alamat} (alamat)")
print(f" memori : mem[{alamat}] <- {R[a]}")
MEM[alamat] = R[a]
print(" write-back: tanpa tulis register")
else:
print(f" decode : rs1=R{b}, rs2=R{a}, offset={off}")
hasil = R[b] - R[a]
print(f" execute : ALU {R[b]} - {R[a]} = {hasil} (uji nol)")
print(" write-back: tanpa tulis register")
if hasil == 0:
pc_baru = pc + 1 + off
print(f" PC : {pc} -> {pc_baru} (cabang diambil)")
pc = pc_baru
continue
print(f" PC : {pc} -> {pc + 1}")
pc += 1
print("Keadaan akhir:", ", ".join(f"R{i}={v}" for i, v in enumerate(R) if v), end="")
print(", " + ", ".join(f"mem[{k}]={MEM[k]}" for k in sorted(MEM)))== Instruksi 1 | PC=0 ==
fetch : 0x3887 (load)
decode : rd=R4, rs1=R2, offset=7
execute : ALU 10 + 7 = 17 (alamat)
memori : mem[17] = 15
write-back: R4 <- 15
PC : 0 -> 1
== Instruksi 2 | PC=1 ==
fetch : 0x1298 (add)
decode : rd=R1, rs1=R2, rs2=R3
execute : ALU 10 + 5 = 15
write-back: R1 <- 15
PC : 1 -> 2
== Instruksi 3 | PC=2 ==
fetch : 0x5841 (beq)
decode : rs1=R1, rs2=R4, offset=1
execute : ALU 15 - 15 = 0 (uji nol)
write-back: tanpa tulis register
PC : 2 -> 4 (cabang diambil)
== Instruksi 4 | PC=4 ==
fetch : 0x4843 (store)
decode : rs2=R4, rs1=R1, offset=3
execute : ALU 15 + 3 = 18 (alamat)
memori : mem[18] <- 15
write-back: tanpa tulis register
PC : 4 -> 5
Keadaan akhir: R1=15, R2=10, R3=5, R4=15, mem[17]=15, mem[18]=15Empat siklus tercetak untuk lima instruksi program, karena satu instruksi tidak pernah diambil. Pada siklus ketiga, ALU mengurangkan R1 dengan R4 dan hasilnya 0, sehingga cabang diambil dan PC melompat dari 2 ke 4 tanpa melewati alamat 3. Baris keadaan akhir menutup buktinya, yaitu R5 tidak pernah muncul karena tetap 0, sementara mem[18] berisi 15 hasil store pada siklus keempat. Instruksi yang tidak pernah di-fetch memang tidak meninggalkan jejak apa pun selain ketidakhadirannya.
Pembaruan PC sebagai penutup siklus#
Baris terakhir setiap siklus adalah pembaruan PC, dan baris inilah yang menentukan instruksi mana yang menjalani siklus berikutnya. Untuk add, load, dan store, PC dinaikkan satu alamat begitu saja, karena penambah bekerja tanpa memandang jenis instruksi. Untuk beq, keputusannya bergantung pada hasil uji nol di ALU, yaitu ketika selisihnya nol dan tetap ketika bukan. Pembaruan ini satu-satunya bagian siklus yang memengaruhi urutan pekerjaan berikutnya, bukan isinya.
Siklus berikutnya kemudian berjalan dengan pola yang sama persis, dari fetch pada PC yang baru hingga write-back, dan begitu terus selama PC masih menunjuk instruksi yang sah. Pada simulasi, perulangan berakhir ketika PC keluar dari lima alamat program. Pada mesin nyata, memori berisi instruksi sampai jauh melewati program kecil ini, sehingga eksekusi berhenti karena instruksi khusus atau interupsi, bukan karena alamatnya kehabisan. Offset yang ditafsirkan bertanda juga membuat cabang mundur dapat dinyatakan, dan pola cabang mundur yang syaratnya pernah gagal itulah yang membentuk perulangan pada tingkat mesin.
Kesalahan umum#
- Mengira write-back terjadi pada setiap instruksi. Instruksi store dan beq tidak menghasilkan nilai register, sehingga tahapnya kosong dan RegWrite mati. Jejak keluarannya tetap mencantumkan baris write-back dengan isi tanpa tulis register, dan keberadaan baris itu bukan tanda ada penulisan yang terjadi. Menulis alamat ke register pada store adalah kesalahan yang dihindari persis oleh keputusan mematikan RegWrite ini.
- Menukar urutan execute dengan akses memori. Alamat memori baru ada setelah ALU bekerja, sehingga memori data tidak mungkin diakses lebih dahulu. Urutan pada jejak keluaran bukan pilihan gaya, melainkan ketergantungan data.
- Menganggap tahap fetch menulis sesuatu ke register. Fetch hanya membaca kata instruksi dari memori instruksi, dan satu-satunya penulisan yang menyertainya adalah pembaruan PC oleh penambah, bukan penulisan register file.
- Menghitung target cabang dari PC yang belum dinaikkan. Konvensi pada simulasi adalah PC + 1 + offset, yaitu alamat instruksi sesudah cabang ditambah offsetnya. Target yang dihitung dari PC tanpa + 1 akan melompat satu alamat lebih awal dan melewati instruksi yang seharusnya dieksekusi.
Ringkasan#
- Satu siklus mencakup fetch, decode, execute, akses memori bila instruksinya menyentuh memori data, dan write-back, semuanya dalam satu clock pada datapath siklus tunggal.
- Urutan tahap mengikuti ketergantungan data, yaitu field lebih dahulu baru operand, dan alamat lebih dahulu baru akses memori.
- PC diperbarui pada akhir setiap siklus, biasanya bertambah satu, atau menjadi PC + 1 + offset ketika beq menguji selisih nol.
- Jejak lima instruksi memperlihatkan cabang yang diambil melewati satu instruksi tanpa jejak, karena instruksi yang tidak di-fetch tidak pernah dieksekusi.
Referensi#
- Computer Systems: A Programmer’s Perspective — Carnegie Mellon — Mengurai implementasi prosesor sekuensial menjadi tahap fetch, decode, execute, memory, write back, dan pembaruan PC.
- Computation Structures — MIT OpenCourseWare — Menyusun siklus eksekusi instruksi di atas datapath dan unit kontrol prosesor sederhana.