Tại sao chúng ta cần hướng dẫn sử dụng không cần hướng dẫn trong bộ vi xử lý 8085?


19

Trong lệnh vi xử lý 8085, có một thao tác điều khiển máy "nop" (không hoạt động). Câu hỏi của tôi là tại sao chúng ta cần một hoạt động không? Ý tôi là nếu chúng tôi phải kết thúc chương trình, chúng tôi sẽ sử dụng HLT hoặc RST 3. Hoặc nếu chúng tôi muốn chuyển sang hướng dẫn tiếp theo, chúng tôi sẽ đưa ra các hướng dẫn tiếp theo. Nhưng tại sao không hoạt động? Nhu cầu là gì?


5
NOP thường được sử dụng để gỡ lỗi và cập nhật chương trình của bạn. Nếu vào một ngày sau đó, bạn muốn thêm một số dòng vào chương trình của mình, bạn chỉ cần ghi đè lên NOP. Nếu không, bạn sẽ phải chèn các dòng và chèn có nghĩa là dịch chuyển toàn bộ chương trình. Ngoài ra các hướng dẫn bị lỗi (không chính xác) có thể được thay thế (đơn giản là ghi đè) bằng NOP theo cùng các đối số.
buôn lậu Plutonium

Ohkay. Nhưng sử dụng nop cũng làm tăng không gian. Trong khi mục tiêu chính của chúng tôi là làm cho nó mất ít vị trí.
Demietra95

* Ý tôi là mục tiêu của chúng tôi là làm cho một vấn đề nhỏ hơn. Vì vậy, nó cũng không trở thành một vấn đề?
Demietra95

2
Đó là lý do tại sao nó nên được sử dụng một cách khôn ngoan. Nếu không, toàn bộ chương trình của bạn sẽ chỉ là một loạt các NOP.
buôn lậu Plutonium

Câu trả lời:


34

Một cách sử dụng lệnh NOP (hoặc NOOP, không hoạt động) trong CPU và MCU là chèn một chút, có thể dự đoán được, độ trễ trong mã của bạn. Mặc dù các NOP không thực hiện bất kỳ thao tác nào, nhưng phải mất một thời gian để xử lý chúng (CPU phải tìm nạp và giải mã opcode, vì vậy cần một ít thời gian để làm điều đó). Chỉ cần 1 chu kỳ CPU là "lãng phí" để thực hiện lệnh NOP (thường có thể suy ra số chính xác từ bảng dữ liệu CPU / MCU), do đó, đặt N NOP theo trình tự là một cách dễ dàng để chèn độ trễ có thể dự đoán được:

tdetôimộty= =NTctôiockK

Trong đó K là số chu kỳ (thường là 1) cần thiết cho việc xử lý lệnh NOP và là chu kỳ đồng hồ.Tctôiock

Tại sao bạn lại làm vậy? Có thể hữu ích khi buộc CPU phải chờ một chút để các thiết bị bên ngoài (có thể chậm hơn) hoàn thành công việc và báo cáo dữ liệu cho CPU, tức là NOP hữu ích cho mục đích đồng bộ hóa.

Xem thêm trang Wikipedia liên quan trên NOP .

Một cách sử dụng khác là căn chỉnh mã tại một số địa chỉ nhất định trong bộ nhớ và các "thủ thuật lắp ráp" khác, như được giải thích trong luồng này trên Lập trình viên.SE và trong luồng khác này trên StackOverflow .

Một bài viết thú vị về chủ đề này .

Liên kết này đến trang sách của Google đặc biệt đề cập đến CPU 8085. Trích đoạn:

Mỗi lệnh NOP sử dụng bốn đồng hồ để tìm nạp, giải mã và thực thi.

EDIT (để giải quyết một mối quan tâm thể hiện trong một bình luận)

π


3
Rất nhiều thứ (đặc biệt là các đầu ra điều khiển IC bên ngoài uC) phải chịu các ràng buộc về thời gian như 'thời gian tối thiểu giữa D ổn định và cạnh đồng hồ là 100 us' hoặc 'Đèn LED IR phải nhấp nháy ở 1 MHz'. Do đó (chính xác) sự chậm trễ thường được yêu cầu.
Wouter van Ooijen

6
NOP có thể hữu ích để lấy đúng thời gian khi đập bit giao thức nối tiếp. Chúng cũng có thể hữu ích để lấp đầy không gian mã không sử dụng, sau đó chuyển sang vectơ khởi động nguội trong các tình huống hiếm gặp nếu Bộ đếm chương trình bị hỏng (ví dụ: trục trặc PSU, tác động bởi sự kiện tia gamma hiếm, v.v.) & bắt đầu thực thi mã trong một mặt khác, phần trống của không gian mã.
Techydude

6
Trên Hệ thống máy tính video Atari 2600 (bảng điều khiển trò chơi video thứ hai để chạy các chương trình được lưu trên hộp mực), bộ xử lý sẽ chạy chính xác 76 chu kỳ cho mỗi dòng quét và nhiều thao tác sẽ cần được thực hiện một số chu kỳ chính xác sau khi bắt đầu đường quét. Trên bộ xử lý đó, lệnh "NOP" được ghi lại có hai chu kỳ, nhưng thông thường mã sẽ sử dụng một lệnh ba chu kỳ vô dụng khác để làm chậm trễ một số chu kỳ chính xác. Chạy mã nhanh hơn sẽ mang lại một màn hình bị cắt xén hoàn toàn.
supercat

1
Sử dụng NOP cho sự chậm trễ có thể có ý nghĩa ngay cả trên các hệ thống không theo thời gian thực trong trường hợp thiết bị I / O áp đặt thời gian tối thiểu giữa các hoạt động liên tiếp, nhưng không phải là tối đa. Ví dụ, trên nhiều bộ điều khiển, việc chuyển một byte ra một cổng SPI sẽ mất tám chu kỳ CPU. Mã không làm gì ngoài việc lấy byte từ bộ nhớ và xuất chúng sang cổng SPI có thể chạy hơi nhanh, nhưng việc thêm logic để kiểm tra xem cổng SPI đã sẵn sàng cho mỗi byte có làm cho nó chậm không. Thêm một hoặc hai NOP có thể cho phép mã đạt được tốc độ tối đa có sẵn ...
supercat

1
... trong trường hợp không có ngắt. Nếu một cú đánh bị gián đoạn, các NOP sẽ không lãng phí thời gian, nhưng thời gian bị lãng phí bởi một hoặc hai NOP sẽ ít hơn thời gian cần thiết để xác định xem một ngắt có khiến chúng không cần thiết hay không.
supercat

8

Các câu trả lời khác chỉ đang xem xét một NOP thực sự thực thi tại một số điểm - được sử dụng khá phổ biến, nhưng đó không phải là cách sử dụng NOP duy nhất.

Các NOP không thực hiện cũng khá hữu ích khi viết mã có thể được vá - về cơ bản, bạn sẽ pad chức năng với một vài NOPs sau sự RET(hoặc hướng dẫn tương tự). Khi bạn phải vá tệp thực thi, bạn có thể dễ dàng thêm nhiều mã hơn vào hàm bắt đầu từ bản gốc RETvà sử dụng bao nhiêu NOP mà bạn cần (ví dụ: để nhảy dài hoặc thậm chí mã nội tuyến) và hoàn thiện bằng mã khác RET.

Trong trường hợp sử dụng này, noöne từng mong đợi NOP thực thi. Điểm duy nhất là cho phép vá lỗi thực thi - trong một thực thi không đệm về mặt lý thuyết, bạn phải thực sự thay đổi mã của hàm (đôi khi nó có thể phù hợp với các ranh giới ban đầu, nhưng thường thì bạn sẽ cần một bước nhảy ) - điều đó phức tạp hơn nhiều, đặc biệt là xem xét lắp ráp bằng tay hoặc trình biên dịch tối ưu hóa; bạn phải tôn trọng các bước nhảy và các cấu trúc tương tự có thể đã chỉ ra một số đoạn mã quan trọng. Tất cả trong tất cả, khá khó khăn.

Tất nhiên, điều này đã được sử dụng nhiều hơn rất nhiều vào thời xa xưa, khi nó rất hữu ích để tạo ra các bản vá như nhỏtrực tuyến . Hôm nay, bạn sẽ chỉ phân phối một nhị phân được biên dịch lại và được thực hiện với nó. Vẫn còn một số người sử dụng các bản vá NOP (thực thi hay không, và không phải lúc nào cũng bằng chữ NOP- ví dụ, Windows sử dụng MOV EDI, EDIđể vá trực tuyến - đó là loại mà bạn có thể cập nhật thư viện hệ thống trong khi hệ thống thực sự đang chạy mà không cần khởi động lại).

Vì vậy, câu hỏi cuối cùng là, tại sao có một hướng dẫn chuyên dụng cho một cái gì đó không thực sự làm gì?

  • Đây là một hướng dẫn thực tế - quan trọng khi gỡ lỗi hoặc lắp ráp mã hóa. Các hướng dẫn như MOV AX, AXsẽ thực hiện chính xác như vậy, nhưng không báo hiệu ý định khá rõ ràng.
  • Đệm - "mã" đó chỉ để cải thiện hiệu suất tổng thể của mã phụ thuộc vào căn chỉnh. Nó không bao giờ có nghĩa là để thực thi. Một số trình gỡ lỗi chỉ đơn giản là ẩn NOP đệm trong quá trình tháo gỡ của chúng.
  • Nó cung cấp thêm không gian để tối ưu hóa trình biên dịch - mẫu vẫn được sử dụng là bạn đã có hai bước biên dịch, bước đầu tiên khá đơn giản và tạo ra nhiều mã lắp ráp không cần thiết, trong khi cách thứ hai làm sạch, tua lại các tham chiếu địa chỉ và xóa hướng dẫn ngoại lai. Điều này cũng thường thấy trong các ngôn ngữ do JIT biên dịch - cả mã byte của IL và JVM của .NET đều sử dụng NOPkhá nhiều; mã lắp ráp được biên dịch thực tế không còn những cái đó nữa. Tuy nhiên, cần lưu ý rằng những cái đó không phải là x86 NOP.
  • Nó giúp việc gỡ lỗi trực tuyến dễ dàng hơn cho cả việc đọc (bộ nhớ trước sẽ là tất cả NOP, giúp cho việc phân tách dễ đọc hơn rất nhiều) và để vá nóng (mặc dù tôi rất thích Chỉnh sửa và Tiếp tục trong Visual Studio: P).

Để thực hiện NOP, tất nhiên còn một vài điểm nữa:

  • Hiệu suất, tất nhiên - đây không phải là lý do tại sao nó là vào năm 8085, nhưng ngay cả 80486 cũng đã có một lệnh thực thi theo đường ống dẫn đến việc "không làm gì" một chút khó khăn hơn.
  • Như đã thấy MOV EDI, EDI, có những NOP hiệu quả khác ngoài nghĩa đen NOP. MOV EDI, EDIcó hiệu suất tốt nhất là NOP 2 byte trên x86. Nếu bạn đã sử dụng hai NOPs, đó sẽ là hai hướng dẫn để thực thi.

CHỈNH SỬA:

Trên thực tế, cuộc thảo luận với @DmitryGrigoryev buộc tôi phải suy nghĩ về vấn đề này nhiều hơn một chút và tôi nghĩ đó là một bổ sung có giá trị cho câu hỏi / câu trả lời này, vì vậy hãy để tôi thêm một số bit:

Đầu tiên, điểm, rõ ràng - tại sao sẽ có một hướng dẫn làm một cái gì đó như thế mov ax, axnào? Ví dụ: chúng ta hãy xem trường hợp mã máy 8086 (cũ hơn mã máy thậm chí là 386):

  • Có một hướng dẫn NOP chuyên dụng với opcode 0x90. Đây vẫn là thời gian mà nhiều người viết trong hội đồng, nhớ bạn. Vì vậy, ngay cả khi không có NOPhướng dẫn chuyên dụng , NOPtừ khóa (bí danh / ghi nhớ) vẫn sẽ hữu ích và sẽ ánh xạ tới đó.
  • Các hướng dẫn như MOVthực sự ánh xạ tới nhiều opcode khác nhau, bởi vì điều đó giúp tiết kiệm thời gian và không gian - ví dụ, mov al, 42là "di chuyển byte ngay lập tức đến thanh alghi", nghĩa là 0xB02A( 0xB0là opcode, 0x2Alà đối số "ngay lập tức"). Vì vậy, cần hai byte.
  • mov al, alVề cơ bản, không có opcode cho (vì đó là một việc ngu ngốc), vì vậy bạn sẽ phải sử dụng quá tải mov al, rmb(rmb là "đăng ký hoặc bộ nhớ"). Điều đó thực sự mất ba byte. (mặc dù nó có thể sẽ sử dụng ít đặc hiệu hơn mov rb, rmbthay vào đó, chỉ nên lấy hai byte cho mov al, al- byte đối số được sử dụng để chỉ định cả nguồn và thanh ghi đích; bây giờ bạn biết tại sao 8086 chỉ có 8 thanh ghi: D). So sánh với NOP, đó là một lệnh byte đơn! Điều này giúp tiết kiệm bộ nhớ và thời gian, vì đọc bộ nhớ trong 8086 vẫn còn khá tốn kém - chưa kể đến việc tải chương trình đó từ băng hoặc đĩa mềm hoặc một cái gì đó, tất nhiên.

Vậy xchg ax, axnguồn gốc từ đâu? Bạn chỉ cần nhìn vào opcodes của các xhcghướng dẫn khác . Bạn sẽ thấy 0x86, 0x87và cuối cùng, 0x91- 0x97. Vì vậy, nop0x90vẻ như nó khá phù hợp với xchg ax, ax(một lần nữa, không phải là xchg"quá tải" - bạn phải sử dụng xchg rb, rmb, ở hai byte). Và trên thực tế, tôi khá chắc chắn rằng đây là một hiệu ứng phụ tuyệt vời của kiến ​​trúc vi mô thời bấy giờ - nếu tôi nhớ lại một cách chính xác, thật dễ dàng để ánh xạ toàn bộ phạm vi của 0x90-0x97"xchg, hành động qua các thanh ghi axax- di" ( toán hạng là đối xứng, điều này cung cấp cho bạn phạm vi đầy đủ, bao gồm cả nop xchg ax, ax, lưu ý rằng thứ tự là ax, cx, dx, bx, sp, bp, si, di- bxlà saudx ,ax; hãy nhớ rằng, tên đăng ký là ghi nhớ, không được đặt hàng tên - bộ tích lũy, bộ đếm, dữ liệu, cơ sở, con trỏ ngăn xếp, con trỏ cơ sở, chỉ mục nguồn, chỉ mục đích). Cách tiếp cận tương tự cũng được sử dụng cho các toán hạng khác, ví dụ như mov someRegister, immediatetập hợp. Theo một cách nào đó, bạn có thể nghĩ về điều này như thể opcode thực sự không phải là một byte đầy đủ - một vài bit cuối cùng là "một đối số" cho toán hạng "thực".

Tất cả điều này đã nói, trên x86, nopcó thể được coi là một hướng dẫn thực sự hoặc không. Kiến trúc vi mô ban đầu đã coi nó như một biến thể xchgnếu tôi nhớ chính xác, nhưng nó thực sự được đặt tên noptrong đặc tả. Và vì xchg ax, axkhông thực sự có ý nghĩa như một hướng dẫn, bạn có thể thấy các nhà thiết kế của 8086 đã lưu trên các bóng bán dẫn và đường dẫn trong việc giải mã hướng dẫn bằng cách khai thác thực tế rằng 0x90ánh xạ tự nhiên vào một thứ hoàn toàn "mềm mại".

Mặt khác, i8051 có opcode được thiết kế hoàn toàn cho nop- 0x00. Thực tế. Việc thiết kế hướng dẫn về cơ bản là sử dụng nibble cao đối với hoạt động và nibble thấp để lựa chọn các toán hạng - ví dụ, add a0x2Y, và 0xX8có nghĩa là "đăng ký 0 trực tiếp", do đó 0x28add a, r0. Tiết kiệm rất nhiều trên silicon :)

Tôi vẫn có thể tiếp tục, vì thiết kế CPU (không đề cập đến thiết kế trình biên dịch và thiết kế ngôn ngữ) là một chủ đề khá rộng, nhưng tôi nghĩ rằng tôi đã thể hiện nhiều quan điểm khác nhau đi vào thiết kế khá độc đáo.


Trên thực tế, NOPthường là một bí danh MOV ax, ax, ADD ax, 0hoặc hướng dẫn tương tự. Tại sao bạn sẽ thiết kế một hướng dẫn chuyên dụng mà không làm gì khi có rất nhiều ngoài kia.
Dmitry Grigoryev

@DmitryGrigoryev Điều đó thực sự đi vào thiết kế ngôn ngữ CPU (tốt, kiến ​​trúc vi mô). Hầu hết các CPU (và trình biên dịch) sẽ có xu hướng tối ưu hóa MOV ax, axđi; NOPsẽ luôn có một số lượng chu kỳ cố định để thực hiện. Nhưng tôi không thấy nó liên quan đến những gì tôi đã viết trong câu trả lời của mình.
Luaan

CPU thực sự không thể tối ưu hóa một MOV ax, axcách xa, bởi vì vào thời điểm chúng biết đó là một MOVhướng dẫn đã có sẵn.
Dmitry Grigoryev

@DmitryGrigoryev Điều đó thực sự phụ thuộc vào loại CPU mà bạn đang nói đến. CPU máy tính để bàn hiện đại làm rất nhiều thứ, không chỉ hướng dẫn đường ống. Ví dụ, CPU biết rằng nó không phải làm mất hiệu lực các dòng bộ đệm, v.v. Tôi sẽ không ngạc nhiên nếu nó cũng ảnh hưởng đến dự đoán chi nhánh (mặc dù điều đó có thể giống với NOPMOV ax, ax). CPU hiện đại phức tạp hơn nhiều so với trình biên dịch C
cũs

1
+1 để tối ưu hóa không ai thành noöne! Tôi nghĩ rằng tất cả chúng ta nên hợp tác và quay trở lại phong cách đánh vần này! Z80 (và đến lượt 8080) có 7 LD r, rhướng dẫn trong đó rlà bất kỳ thanh ghi đơn lẻ nào, tương tự như MOV ax, axhướng dẫn của bạn . Lý do là 7 chứ không phải 8 là vì một trong những hướng dẫn bị quá tải để trở thành HALT. Vì vậy, 8080 và Z80 có ít nhất 7 hướng dẫn khác thực hiện tương tự NOP! Thật thú vị, mặc dù các hướng dẫn này không liên quan về mặt logic theo mẫu bit, tất cả đều mất 4 T States để thực thi nên không có lý do gì để sử dụng LDhướng dẫn!
CJ Dennis

5

Trở lại vào cuối những năm 70, chúng tôi (lúc đó tôi là một sinh viên nghiên cứu trẻ tuổi) có một hệ thống dev nhỏ (8080 nếu bộ nhớ phục vụ) chạy trong 1024 byte mã (tức là một UVEPROM) - nó chỉ có bốn lệnh để tải (L ), lưu (S), in (P) và những thứ khác tôi không thể nhớ. Nó được điều khiển với một teletype và băng đục lỗ thực sự. Nó đã được mã hóa chặt chẽ!

Một ví dụ về việc sử dụng NOOP là trong một thói quen dịch vụ ngắt (ISR), được đặt cách nhau trong các khoảng 8 byte. Thường trình này kết thúc là 9 byte kết thúc bằng một bước nhảy (dài) đến một địa chỉ xa hơn một chút so với không gian địa chỉ. Điều này có nghĩa, với thứ tự byte cuối nhỏ, byte địa chỉ cao là 00h và được đặt vào byte đầu tiên của ISR tiếp theo, có nghĩa là nó (ISR tiếp theo) bắt đầu bằng NOOP, vì vậy 'chúng ta' có thể phù hợp mã trong không gian hạn chế!

Vì vậy, NOOP là hữu ích. Thêm vào đó, tôi nghi ngờ việc intel mã hóa theo cách đó là dễ nhất - Họ có thể có một danh sách các hướng dẫn họ muốn thực hiện và nó bắt đầu từ '1', giống như tất cả các danh sách đã làm (đây là thời của FORTRAN), vì vậy số 0 Mã NOOP trở thành một rơi ra. (Tôi chưa bao giờ thấy một bài báo lập luận rằng NOOP là một phần thiết yếu của lý thuyết Khoa học máy tính (giống như Q: các nhà toán học có một nul op, khác với số 0 của lý thuyết nhóm không?)


Không phải tất cả các CPU đều được mã hóa NOP thành 0x00 (mặc dù 8085, 8080 và CPU mà tôi quen thuộc nhất, Z80, tất cả đều làm như vậy). Tuy nhiên, nếu tôi đang thiết kế một bộ xử lý mà tôi sẽ đặt nó! Một điều khác rất hữu ích là bộ nhớ thường được khởi tạo cho tất cả 0x00, do đó, thực thi điều này dưới dạng mã sẽ không làm gì cho đến khi CPU đạt tới bộ nhớ khác không.
CJ Dennis

@CJDennis Tôi đã giải thích lý do tại sao CPU x86 không sử dụng 0x00cho nopcâu trả lời của tôi. Nói tóm lại, nó tiết kiệm cho việc giải mã lệnh - xchg ax, axchảy tự nhiên từ cách giải mã lệnh hoạt động, và nó làm một cái gì đó "gọn gàng", vậy tại sao không sử dụng nó và gọi nó nop, phải không? :) Được sử dụng để tiết kiệm khá nhiều trên silicon để giải mã hướng dẫn ...
Luaan

5

Trên một số kiến ​​trúc, NOPđược sử dụng để chiếm các khe trễ không sử dụng . Ví dụ: nếu lệnh rẽ nhánh không xóa đường ống, một số lệnh sau khi được thực thi bằng mọi cách:

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

Nhưng nếu bạn không có bất kỳ hướng dẫn hữu ích nào để phù hợp với thì JMPsao? Trong trường hợp đó, bạn sẽ phải sử dụng NOPs.

Khe trễ không giới hạn để nhảy. Trên một số kiến ​​trúc, các mối nguy dữ liệu trong đường ống CPU không được giải quyết tự động. Điều này có nghĩa là sau mỗi lệnh sửa đổi một thanh ghi, có một vị trí mà giá trị mới của thanh ghi chưa thể truy cập được. Nếu hướng dẫn tiếp theo cần giá trị đó, vị trí sẽ được chiếm bởi NOP:

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

Ngoài ra, một số hướng dẫn thực hiện có điều kiện ( If-True-false và tương tự) sử dụng các vị trí cho từng điều kiện và khi một điều kiện cụ thể không có hành động liên quan đến nó, vị trí của nó sẽ bị chiếm bởi 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

+1. Tất nhiên, những thứ đó chỉ có xu hướng hiển thị trên các kiến ​​trúc không quan tâm đến khả năng tương thích ngược - nếu x86 đã thử một cái gì đó tương tự khi giới thiệu hướng dẫn đường ống, hầu như mọi người sẽ gọi nó là sai (sau tất cả, họ chỉ nâng cấp CPU và các ứng dụng đã ngừng hoạt động!). Vì vậy, x86 để đảm bảo các ứng dụng không thông báo khi cải tiến như thế này được bổ sung vào CPU - cho đến khi chúng tôi tới các CPU đa lõi anyway ...: D
Luaan

2

Một ví dụ khác về việc sử dụng NOP hai byte : http://bloss.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx

Lệnh MOV EDI, EDI là NOP hai byte, chỉ đủ không gian để vá trong lệnh nhảy để chức năng có thể được cập nhật nhanh chóng. Ý định là lệnh MOV EDI, EDI sẽ được thay thế bằng lệnh JMP $ -5 hai byte để chuyển hướng điều khiển đến năm byte không gian vá xuất hiện ngay trước khi bắt đầu chức năng. Năm byte là đủ cho một lệnh nhảy đầy đủ, có thể gửi điều khiển đến chức năng thay thế được cài đặt ở một nơi khác trong không gian địa chỉ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.