Mengapa kita memerlukan "nop" Yaitu Tidak ada instruksi operasi di mikroprosesor 8085?

19

Dalam instruksi mikroprosesor 8085, ada operasi kontrol mesin "nop" (tidak ada operasi). Pertanyaan saya adalah mengapa kita perlu operasi no? Maksud saya jika kita harus mengakhiri program, kita akan menggunakan HLT atau RST 3. Atau jika kita ingin pindah ke instruksi selanjutnya kita akan memberikan instruksi selanjutnya. Tetapi mengapa tidak ada operasi? Apa yang dibutuhkan?

Demietra95
sumber
5
NOP sering digunakan dalam debugging dan memperbarui program Anda. Jika di kemudian hari, Anda ingin menambahkan beberapa baris ke program Anda, Anda bisa menimpa NOP. Kalau tidak, Anda harus memasukkan garis, dan menyisipkan berarti menggeser seluruh program. Juga instruksi yang salah (salah) dapat diganti (hanya ditimpa) oleh NOP berikut argumen yang sama.
Penyelundup Plutonium
Ohkay. Tetapi menggunakan nop juga meningkatkan ruang. Padahal tujuan utama kami adalah menjadikannya tidak banyak terjadi.
Demietra95
* Maksud saya tujuan kami adalah membuat masalah lebih kecil. Jadi bukankah itu menjadi masalah juga?
Demietra95
2
Itu sebabnya harus digunakan dengan bijak. Kalau tidak, seluruh program Anda hanya akan menjadi sekelompok NOP.
Penyelundup Plutonium

Jawaban:

34

Salah satu penggunaan instruksi NOP (atau NOOP, no-operasi) dalam CPU dan MCU adalah untuk memasukkan sedikit, dapat diprediksi, keterlambatan dalam kode Anda. Meskipun NOP tidak melakukan operasi apa pun, perlu beberapa waktu untuk memprosesnya (CPU harus mengambil dan mendekodekan opcode, sehingga perlu sedikit waktu untuk melakukan itu). Sekurang-kurangnya 1 siklus CPU "terbuang" untuk menjalankan instruksi NOP (biasanya angka yang dapat disimpulkan dari lembar data CPU / MCU), oleh karena itu menempatkan N NOP secara berurutan adalah cara mudah untuk memasukkan penundaan yang dapat diprediksi:

tdelSebuahy=NTclHaickK

di mana K adalah jumlah siklus (paling sering 1) diperlukan untuk pemrosesan instruksi NOP, dan adalah periode jam.TclHaick

Kenapa kamu ingin melakukan itu? Mungkin berguna untuk memaksa CPU untuk menunggu sedikit untuk perangkat eksternal (mungkin lebih lambat) untuk menyelesaikan pekerjaan mereka dan melaporkan data ke CPU, yaitu NOP berguna untuk keperluan sinkronisasi.

Lihat juga halaman Wikipedia terkait di NOP .

Penggunaan lain adalah untuk menyelaraskan kode pada alamat tertentu dalam memori dan "trik perakitan" lainnya, seperti yang dijelaskan juga dalam utas ini pada Programmers.SE dan pada utas lainnya pada StackOverflow .

Artikel menarik lainnya tentang masalah ini .

Tautan ini ke halaman buku Google terutama mengacu pada CPU 8085. Kutipan:

Setiap instruksi NOP menggunakan empat jam untuk mengambil, mendekode dan mengeksekusi.

EDIT (untuk mengatasi masalah yang diungkapkan dalam komentar)

π

Lorenzo Donati mendukung Monica
sumber
3
Banyak hal (terutama output yang mendorong IC eksternal ke UC) tunduk pada batasan waktu seperti 'waktu minimum antara D menjadi stabil dan tepi clock adalah 100 us', atau 'LED IR harus berkedip pada 1 MHz'. Karenanya sering diperlukan penundaan (akurat).
Wouter van Ooijen
6
NOP dapat berguna untuk mendapatkan pengaturan waktu yang tepat ketika menggedor protokol serial. Mereka juga dapat berguna untuk mengisi ruang kode yang tidak digunakan diikuti dengan lompatan ke vektor mulai-dingin untuk situasi yang jarang terjadi jika Program Counter rusak (mis. PSU glitch, terkena dampak peristiwa gamma ray langka, dll) & mulai menjalankan kode dalam jika tidak, kosongkan ruang kode.
Techydude
6
Pada Sistem Komputer Video Atari 2600 (konsol permainan video kedua untuk menjalankan program yang tersimpan pada kartrid), prosesor akan menjalankan dengan tepat 76 siklus setiap baris pemindaian, dan banyak operasi harus dilakukan sejumlah siklus tepat setelah dimulainya suatu garis pindai. Pada prosesor itu, instruksi "NOP" yang terdokumentasi membutuhkan dua siklus, tetapi juga umum bagi kode untuk menggunakan instruksi tiga siklus yang tidak berguna untuk menambah penundaan ke jumlah siklus yang tepat. Menjalankan kode lebih cepat akan menghasilkan tampilan yang benar-benar kacau.
supercat
1
Menggunakan NOP untuk penundaan dapat masuk akal bahkan pada sistem non-real-time dalam kasus di mana perangkat I / O memaksakan waktu minimum antara operasi berturut-turut, tetapi tidak maksimal. Misalnya, pada banyak pengontrol, memindahkan satu byte keluar port SPI akan memakan waktu delapan siklus CPU. Kode yang tidak melakukan apa-apa selain mengambil byte dari memori dan mengeluarkannya ke port SPI bisa berjalan sedikit terlalu cepat, tetapi menambahkan logika untuk menguji apakah port SPI siap untuk setiap byte akan membuatnya lambat tanpa perlu. Menambahkan NOP atau dua dapat memungkinkan kode untuk mencapai kecepatan maksimum yang tersedia ...
supercat
1
... dalam hal tidak ada interupsi. Jika interupsi terjadi, NOP akan membuang-buang waktu dengan sia-sia, tetapi waktu yang terbuang oleh satu atau dua NOP akan kurang dari waktu yang diperlukan untuk menentukan apakah interupsi membuat mereka tidak perlu.
supercat
8

Jawaban lain hanya mempertimbangkan NOP yang sebenarnya dieksekusi di beberapa titik - yang digunakan cukup umum, tetapi itu bukan satu-satunya penggunaan NOP.

The non-melaksanakan NOP juga cukup berguna ketika menulis kode yang dapat ditambal - pada dasarnya, Anda akan pad fungsi dengan beberapa NOP setelah itu RET(atau instruksi serupa). Ketika Anda harus menambal executable, Anda dapat dengan mudah menambahkan lebih banyak kode ke fungsi mulai dari yang asli RETdan menggunakan sebanyak mungkin NOP yang Anda butuhkan (misalnya untuk lompat jauh atau bahkan kode inline) dan menyelesaikan dengan yang lain RET.

Dalam kasus penggunaan ini, noone pernah mengharapkan NOPuntuk mengeksekusi. Satu-satunya titik adalah untuk memungkinkan menambal executable - dalam dieksekusi non-empuk teoretis, Anda harus benar-benar mengubah kode fungsi itu sendiri (kadang-kadang mungkin sesuai dengan batas asli, tetapi cukup sering Anda akan memerlukan lompatan pula ) - itu jauh lebih rumit, terutama mempertimbangkan perakitan yang ditulis secara manual atau kompilator yang mengoptimalkan; Anda harus menghormati lompatan dan konstruksi serupa yang mungkin menunjuk pada beberapa kode penting. Secara keseluruhan, cukup rumit.

Tentu saja, ini jauh lebih banyak digunakan di masa lalu, ketika berguna untuk membuat tambalan seperti ini kecil dan online . Hari ini, Anda hanya akan mendistribusikan biner yang dikompilasi dan selesai dengan itu. Masih ada beberapa yang menggunakan patching NOP (mengeksekusi atau tidak, dan tidak selalu huruf NOPs - misalnya, Windows menggunakan MOV EDI, EDIuntuk patching online - itu adalah jenis di mana Anda dapat memperbarui perpustakaan sistem saat sistem benar-benar berjalan, tanpa perlu restart).

Jadi pertanyaan terakhir adalah, mengapa memiliki instruksi khusus untuk sesuatu yang tidak benar-benar melakukan apa-apa?

  • Ini adalah instruksi aktual - penting saat debugging atau handcoding assembly. Instruksi seperti MOV AX, AXakan melakukan hal yang persis sama, tetapi jangan memberi sinyal maksud dengan jelas.
  • Padding - "kode" yang ada di sana hanya untuk meningkatkan kinerja keseluruhan kode yang tergantung pada perataan. Itu tidak pernah dimaksudkan untuk dieksekusi. Beberapa pengadu hanya menyembunyikan bantalan NOP di pembongkaran mereka.
  • Ini memberi lebih banyak ruang untuk mengoptimalkan kompiler - pola yang masih digunakan adalah Anda memiliki dua langkah kompilasi, yang pertama agak sederhana dan menghasilkan banyak kode perakitan yang tidak perlu, sedangkan yang kedua membersihkan, mengembalikan referensi alamat dan menghapus instruksi asing. Ini sering terlihat dalam bahasa-bahasa yang dikompilasi JIT juga - keduanya .NET's IL dan JVM's byte-code menggunakan NOPcukup banyak; kode rakitan terkompilasi yang sebenarnya tidak lagi memilikinya. Perlu dicatat bahwa itu bukan x86 NOP.
  • Itu membuat debugging online lebih mudah baik untuk membaca (memori pre-zeroed adalah all- NOPs, membuat pembongkaran jauh lebih mudah untuk dibaca) dan untuk hot-patching (meskipun saya sejauh ini lebih suka Edit dan Lanjutkan di Visual Studio: P).

Untuk menjalankan NOP, tentu saja ada beberapa poin lagi:

  • Performa, tentu saja - ini bukan alasannya pada 8085, tetapi bahkan 80486 sudah memiliki eksekusi instruksi pipa, yang membuat "tidak melakukan apa-apa" sedikit lebih rumit.
  • Seperti yang terlihat MOV EDI, EDI, ada NOP lain yang efektif selain literal NOP. MOV EDI, EDImemiliki kinerja terbaik sebagai NOP 2-byte pada x86. Jika Anda menggunakan dua NOPs, itu akan menjadi dua instruksi untuk dieksekusi.

EDIT:

Sebenarnya, diskusi dengan @DmitryGrigoryev memaksa saya untuk memikirkan hal ini sedikit lebih banyak, dan saya pikir ini merupakan tambahan yang berharga untuk pertanyaan / jawaban ini, jadi izinkan saya menambahkan beberapa bit tambahan:

Pertama, poin, jelas - mengapa ada instruksi yang melakukan sesuatu seperti mov ax, ax? Sebagai contoh, mari kita lihat kasus 8086 kode mesin (lebih tua dari 386 kode mesin):

  • Ada instruksi NOP khusus dengan opcode 0x90. Ini masih saat ketika banyak orang menulis dalam pertemuan, ingatlah. Jadi, bahkan jika tidak ada NOPinstruksi khusus , NOPkata kunci (alias / mnemonic) akan tetap berguna dan akan memetakannya.
  • Instruksi seperti MOVbenar - benar memetakan ke banyak opcode yang berbeda, karena menghemat waktu dan ruang - misalnya, mov al, 42adalah "pindahkan byte langsung ke alregister", yang diterjemahkan menjadi 0xB02A( 0xB0menjadi opcode, 0x2Amenjadi argumen "langsung"). Jadi itu membutuhkan dua byte.
  • Tidak ada opcode pintas untuk mov al, al(karena pada dasarnya itu hal yang bodoh untuk dilakukan), jadi Anda harus menggunakan mov al, rmboverload (rmb "register or memory"). Itu sebenarnya membutuhkan tiga byte. (walaupun mungkin akan menggunakan yang kurang spesifik mov rb, rmb, yang seharusnya hanya membutuhkan dua byte mov al, al- byte argumen digunakan untuk menentukan sumber dan register target; sekarang Anda tahu mengapa 8086 hanya memiliki 8 register: D). Bandingkan dengan NOP, yang merupakan instruksi byte tunggal! Ini menghemat memori dan waktu, karena membaca memori pada 8086 masih cukup mahal - belum lagi memuat program itu dari kaset atau disket atau sesuatu, tentu saja.

Jadi dari mana datangnya xchg ax, ax? Anda hanya perlu melihat opcodes dari xhcginstruksi lainnya . Anda akan melihat 0x86, 0x87dan akhirnya, 0x91- 0x97. Jadi nopdengan itu 0x90sepertinya cocok untuk xchg ax, ax(yang, sekali lagi, bukan xchg"kelebihan" - Anda harus menggunakan xchg rb, rmb, pada dua byte). Dan pada kenyataannya, saya cukup yakin ini adalah efek samping yang bagus dari arsitektur mikro saat itu - jika saya ingat dengan benar, mudah untuk memetakan seluruh jajaran 0x90-0x97"xchg, bertindak atas register axdan ax- di" ( operan menjadi simetris, ini memberi Anda jangkauan penuh, termasuk nop xchg ax, ax; perhatikan bahwa pesanannya adalah ax, cx, dx, bx, sp, bp, si, di- bxsetelah dx,ax; ingat, nama register adalah mnemonik, bukan nama yang dipesan - akumulator, penghitung, data, basis, penunjuk tumpukan, penunjuk dasar, indeks sumber, indeks tujuan). Pendekatan yang sama juga digunakan untuk operan lain, misalnya mov someRegister, immediateset. Di satu sisi, Anda bisa menganggap ini seolah-olah opcode sebenarnya bukan byte penuh - beberapa bit terakhir adalah "argumen" untuk operan "nyata".

Semua ini dikatakan, pada x86, nopdapat dianggap sebagai instruksi nyata, atau tidak. Mikro-arsitektur asli memang memperlakukannya sebagai varian xchgjika saya ingat dengan benar, tetapi sebenarnya disebutkan nopdalam spesifikasi. Dan karena xchg ax, axtidak benar-benar masuk akal sebagai instruksi, Anda dapat melihat bagaimana para perancang 8086 menghemat transistor dan jalur dalam decoding instruksi dengan mengeksploitasi fakta yang 0x90memetakan secara alami ke sesuatu yang sepenuhnya "noppy".

Di sisi lain, i8051 memiliki opcode yang sepenuhnya dirancang untuk nop- 0x00. Agak praktis. Desain instruksi pada dasarnya menggunakan nibble tinggi untuk operasi dan nibble rendah untuk memilih operan - misalnya, add aadalah 0x2Y, dan 0xX8berarti "register 0 direct", demikian 0x28juga add a, r0. Menghemat banyak pada silikon :)

Saya masih bisa melanjutkan, karena desain CPU (belum lagi desain kompiler dan desain bahasa) adalah topik yang cukup luas, tapi saya pikir saya telah menunjukkan banyak sudut pandang berbeda yang masuk ke dalam desain dengan cukup baik.

Luaan
sumber
Sebenarnya, NOPbiasanya merupakan alias untuk MOV ax, ax, ADD ax, 0atau instruksi serupa. Mengapa Anda merancang instruksi khusus yang tidak melakukan apa-apa ketika ada banyak di luar sana.
Dmitry Grigoryev
@DmitryGrigoryev Itu benar-benar masuk ke dalam desain bahasa CPU (well, micro-architecture) itu sendiri. Kebanyakan CPU (dan kompiler) akan cenderung untuk mengoptimalkannya MOV ax, ax; NOPakan selalu memiliki jumlah siklus yang tetap untuk eksekusi. Tapi saya tidak melihat bagaimana itu relevan dengan apa yang saya tulis dalam jawaban saya.
Luaan
CPU tidak dapat benar-benar mengoptimalkan MOV ax, axjauh, karena pada saat mereka tahu itu sudah MOVinstruksi dalam pipa.
Dmitry Grigoryev
@DmitryGrigoryev Itu benar-benar tergantung pada jenis CPU yang sedang Anda bicarakan. CPU desktop modern melakukan banyak hal, bukan hanya pemipaan instruksi. Sebagai contoh, CPU tahu itu tidak harus membatalkan jalur cache dll, ia tahu itu tidak harus benar-benar melakukan apa-apa (sangat penting untuk HyperThreading, dan bahkan untuk beberapa pipa yang terlibat secara umum). Saya tidak akan terkejut jika itu juga mempengaruhi prediksi cabang (meskipun itu mungkin akan sama untuk NOPdan MOV ax, ax). CPU modern jauh lebih kompleks daripada kompiler oldschool C :))
Luaan
1
+1 untuk mengoptimalkan siapa pun ke noone! Saya pikir kita semua harus berbicara dan kembali ke gaya ejaan ini! Z80 (dan 8080) memiliki 7 LD r, rinstruksi di mana rada satu register, mirip dengan MOV ax, axinstruksi Anda . Alasannya 7 dan bukan 8 adalah karena salah satu instruksi kelebihan beban menjadi HALT. Jadi 8080 dan Z80 memiliki setidaknya 7 instruksi lain yang melakukan hal yang sama NOP! Cukup menarik, meskipun instruksi ini tidak secara logis terkait dengan pola bit, mereka semua mengambil 4 T Negara untuk dieksekusi sehingga tidak ada alasan untuk menggunakan LDinstruksi!
CJ Dennis
5

Kembali di akhir 70-an, kami (saya adalah seorang mahasiswa peneliti muda saat itu) memiliki sistem dev kecil (8080 jika memori berfungsi) yang berjalan di 1024 byte kode (yaitu UVEPROM tunggal) - hanya memiliki empat perintah untuk memuat (L ), simpan (S), cetak (P), dan hal lain yang saya tidak ingat. Itu didorong dengan teletype dan punch tape nyata. Itu dikodekan dengan ketat!

Salah satu contoh penggunaan NOOP adalah dalam layanan rutin interrupt (ISR), yang ditempatkan dalam interval 8 byte. Rutin ini berakhir dengan 9 byte yang berakhir dengan lompatan (panjang) ke alamat yang sedikit lebih jauh ke ruang alamat. Ini berarti, mengingat urutan byte endian kecil, bahwa byte alamat tinggi adalah 00j, dan ditempatkan ke byte pertama dari ISR ​​berikutnya, yang berarti bahwa itu (ISR berikutnya) dimulai dengan NOOP, supaya 'kita' dapat masuk kode di ruang terbatas!

Jadi NOOP berguna. Plus, saya curiga paling mudah bagi intel untuk mengkodekannya seperti itu - Mereka mungkin memiliki daftar instruksi yang ingin mereka terapkan dan dimulai pada '1', seperti semua daftar yang dilakukan (ini adalah hari-hari FORTRAN), jadi nol Kode NOOP menjadi rontok. (Saya belum pernah melihat artikel yang menyatakan bahwa NOOP adalah bagian penting dari teori Ilmu Komputasi (sama seperti Q: apakah matematikawan memiliki nul op, berbeda dari nol teori grup?)

Philip Oakley
sumber
Tidak semua CPU memiliki NOP disandikan ke 0x00 (meskipun 8085, 8080 dan CPU saya yang paling akrab dengan, Z80, semua lakukan). Namun, jika saya merancang prosesor di situlah saya meletakkannya! Hal lain yang berguna adalah bahwa memori biasanya diinisialisasi ke semua 0x00 sehingga menjalankan ini sebagai kode tidak akan melakukan apa-apa sampai CPU mencapai memori non-zeroed.
CJ Dennis
@ CJDennis Saya telah menjelaskan mengapa CPU x86 tidak digunakan 0x00untuk nopjawaban saya. Singkatnya, menghemat instruksi decoding - xchg ax, axmengalir secara alami dari cara instruksi decoding bekerja, dan ia melakukan sesuatu yang "noppy", jadi mengapa tidak menggunakannya dan menyebutnya nop, kan? :) Digunakan untuk menghemat sedikit pada silikon untuk instruksi decoding ...
Luaan
5

Pada beberapa arsitektur, NOPdigunakan untuk menempati slot tunda yang tidak digunakan . Misalnya, jika instruksi cabang tidak menghapus pipa, beberapa instruksi setelah itu dieksekusi:

 JMP     .label
 MOV     R2, 1    ; these instructions start execution before the jump
 MOV     R2, 2    ; takes place so they still get executed

Tetapi bagaimana jika Anda tidak memiliki instruksi yang berguna untuk dicocokkan setelah JMP? Dalam hal ini Anda harus menggunakan NOPs.

Slot keterlambatan tidak terbatas pada lompatan. Pada beberapa arsitektur, bahaya data dalam pipa CPU tidak diselesaikan secara otomatis. Ini berarti bahwa setelah setiap instruksi yang memodifikasi register ada slot di mana nilai baru register belum dapat diakses. Jika instruksi berikutnya membutuhkan nilai itu, slot harus ditempati oleh NOP:

 ADD     R1, R1
 NOP               ; The new value of R1 is not ready yet
 ADD     R1, R3

Juga, beberapa instruksi pelaksanaan bersyarat ( Jika-Benar-Salah dan serupa) menggunakan slot untuk setiap kondisi, dan ketika kondisi tertentu tidak memiliki tindakan yang terkait dengannya, slotnya harus ditempati oleh NOP:

CMP     R0, R1       ; Compare R0 and R1, setting flags
ITF     GT           ; If-True-False on GT flag 
MOV     R3, R2       ; If 'greater than', move R2 to R3
NOP                  ; Else, do nothing
Dmitry Grigoryev
sumber
+1. Tentu saja, itu hanya cenderung muncul pada arsitektur yang tidak peduli tentang kompatibilitas ke belakang - jika x86 mencoba sesuatu seperti itu ketika memperkenalkan instruksi pipa, hampir semua orang akan menyebutnya salah (setelah semua, mereka hanya meningkatkan CPU mereka, dan mereka aplikasi berhenti bekerja!). Jadi x86 harus memastikan aplikasi tidak melihat kapan peningkatan seperti ini ditambahkan ke CPU - sampai kita tetap ke multi-core CPU ...: D
Luaan
2

Contoh lain penggunaan untuk NOP dua byte : http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx

Instruksi MOV EDI, EDI adalah NOP dua byte, yang hanya cukup ruang untuk menambal dalam instruksi lompatan sehingga fungsinya dapat diperbarui dengan cepat. Tujuannya adalah bahwa instruksi MOV EDI, EDI akan diganti dengan instruksi JMP $ -5 dua byte untuk mengarahkan kontrol ke lima byte ruang tambalan yang muncul tepat sebelum dimulainya fungsi. Lima byte cukup untuk instruksi lompatan penuh, yang dapat mengirim kontrol ke fungsi penggantian yang dipasang di tempat lain di ruang alamat.

pjc50
sumber