Lập trình hợp ngữ với 8086
...

  • Hợp ngữ (Assembly) là ngôn ngữ lập trình bậc thấp, chỉ cao hơn ngôn ngữ máy
  • Hợp ngữ là ngôn ngữ gắn liền với các dòng vi xử lý
    • Các lệnh dùng trong hợp ngữ là lệnh của VXL
    • Chương trình hợp ngữ viết cho một VXL có thể không hoạt động trên VXL khác
  • Chương trình hợp ngữ khi dịch ra mã máy có kích thước nhỏ gọn, chiếm ít không gian nhớ

#1 - Cú pháp
...

  • Trong chương trình hợp ngữ, mỗi lệnh được đặt trên một dòng - dòng lệnh. Do đó, ta không cần phải ngăn cách mỗi lệnh bằng dấu ;

  • Lệnh có 2 dạng

    • Lệnh thật (VD: MOV, SUB, ADD,..) khi dịch, lệnh gợi nhớ được dịch ra mã máy
    • Lệnh giả: là các hướng dẫn chương trình dịch (VD: MAIN PROC, .DATA, END MAIN,...) khi dịch, lệnh giả không được dịch ra mã máy mà chỉ có tác dụng định hướng cho chương trình dịch
  • Không phân biệt chữ hoa hay chữ thường trong các dòng lệnh hợp ngữ

  • Cú pháp của một lệnh Assembly thường có dạng sau:

    [label] [mnemonic] [operands] ; [comment]

  • Trong đó

    • label (Trường tên): chứa các nhãn, tên biến, hoặc tên thủ tục, là tên đặt cho một vị trí nhất định trong chương trình. Nhãn giúp chúng ta dễ dàng tham chiếu đến các vị trí này khi cần thiết
    • mnemonic: là tên của lệnh (hoặc mã lệnh) cần thực hiện. Trong trường mã lệnh sẽ có lệnh thật hoặc lệnh giả. Đối với các lệnh thật thì trường này chứa các mã lệnh gợi nhớ. Mã lệnh này sẽ được chương trình dịch ra mã máy. Đối với các hướng dẫn chương trình dịch thì trường này chứa các lệnh giả và sẽ không được dịch ra mã máy
    • operands: (Trường toán hạng) là các tham số của lệnh
    • comment: là phần chú thích, bắt đầu bằng dấu chấm phẩy ;

MAIN PROC, MAIN ENDP
...

  • Trong ngôn ngữ lập trình Assembly, MAIN PROCMAIN ENDP đánh dấu bắt đầu và kết thúc của một thủ tục
  • Thủ tục (hay còn gọi là hàm) là một khối code thực hiện một nhiệm vụ cụ thể. Thủ tục có thể được gọi từ nhiều nơi trong chương trình, giúp tái sử dụng code, dễ đọc và dễ bảo trì hơn
  • Khi bắt đầu khởi chạy chương trình thì ta sẽ thực thi lệnh trong MAIN trước
  • Ở đây, MAIN là tên của thủ tục và PROC là từ khóa để bắt đầu một thủ tục. Mọi lệnh nằm giữa MAIN PROCMAIN ENDP sẽ thuộc về thủ tục MAIN
  • Ví dụ về cách sử dụng MAIN PROC trong một chương trình Assembly:
MAIN PROC
	; Các lệnh của thủ tục main ở đây
MAIN ENDP

Trong đoạn mã trên, ta thấy rằng MAIN chính là phần trường tên cụ thể là tên thủ tục. Và ở trường mã lệnh ta có hai lệnh giả PROCENDP

Chương trình con
...

  • Giống như chương trình chính, thì chương trình con cũng được định nghĩa nhờ các lệnh giả PROCENDP theo mẫu sau:
tên_ctc PROC
	;Các lệnh của thân chương trình
	RET
tên_ctc ENDP
  • Trong ngôn ngữ Assembly, không có khái niệm rõ ràng về hàm như trong ngôn ngữ lập trình cao cấp. Thay vào đó, bạn thường sẽ thực hiện nhảy từ một điểm đến một điểm khác trong chương trình bằng cách sử dụng các lệnh nhảy JMP hoặc các lệnh gọi hàm CALL
  • Các nhãn (label) thường được dùng để đánh dấu các vị trí trong chương trình, bao gồm cả vị trí của các hàm. Khi bạn gọi một hàm bằng lệnh CALL, bạn thực sự đang nhảy đến một nhãn được đặt trước đó, thực hiện mã trong phần đó, và sau đó có thể quay lại nơi gọi hàm thông qua lệnh RET (return)
  • Do đó mà khi đặt tên nhãn, ta không được đặt tên nhãn trùng nhau mặc dù các nhãn được đặt trong các hàm khác nhau

#2 - Dữ liệu cho chương trình
...

1. Dữ liệu kiểu số
...
  • Dữ liệu kiểu số có thể được cho dưới dạng số hệ hai, hệ mười, hệ mười sáu hoặc dưới dạng ký tự.
  • Khi cung cấp số liệu cho chương trình, số cho ở hệ nào phải được kèm đuôi của hệ đó
  • Ngoại lệ:
    • Hệ 10 ta không kèm theo đuôi vì là trường hợp ngầm định của Assembly
    • Với hệ 16 nếu số đó bắt đầu bằng các chữ cái thì ta phải thêm 0 ở trước để chương trình dịch có thể hiểu được đó là số hệ 16 chứ không phải là một tên hoặc một nhãn

Ví dụ các số viết đúng

  • 0011B : Số hệ 2
  • 1234 : Số hệ 10
  • 0ABBAH : Số hệ 16
  • 1EF1H : Số hệ 16
2. Dữ liệu dạng ký tự / chuỗi ký tự
...
  • Nếu dữ liệu là ký tự hoặc chuỗi ký tự thì chúng phải được đóng trong cặp dấu trích dẫn đơn hoặc kép, ví dụ 'A' hay "abcd" . Chương trình sẽ dịch ký tự ra mã ASCII tương ứng của nó

#3 - Biến và hằng
...

  • Trong ngôn ngữ lập trình Assembly, một biến là một vùng nhớ được đặt tên, nơi mà chúng ta có thể lưu trữ và truy xuất dữ liệu

  • Một biến phải được định kiểu dữ liệu trước khi sử dụng, nó có thể được khởi tạo hoặc không cần khởi tạo giá trị ban đầu. Để định nghĩa các kiểu dữ liệu ta dùng các lệnh sau:

    • DB (define byte) (1 byte): định nghĩa biến kiểu byte
    • DW (define word) (2 bytes): định nghĩa biến kiểu từ
    • DD (define double word) (4 bytes): định nghĩa biến kiểu từ kép
  • Cú pháp khai báo biến:

    [Tên] [Kiểu dữ liệu] [Giá trị khởi đầu]

Ví dụ các cách khai báo biến đúng

  • B1 DB 4 : Khai báo biến B1 với giá trị ban đầu là 4
  • B2 DB ? : Khai báo biến B2 không có giá trị ban đầu
  • C1 DB 'A
  • C2 DB 34
  • Cú pháp khai báo hằng số:

    [Tên] EQU [Giá trị]

Hằng có thể là kiểu số, kí tự, hoặc chuỗi kí tự

Biến byte
...

Một biến kiểu byte trong Assembly có thể chứa được cả số và ký tự. Biến byte có thể chứa bất kỳ giá trị nào có thể được biểu diễn trong 8 bit, bao gồm cả các số nguyên từ 0 đến 255 (hoặc từ -128 đến 127 nếu được xem là số nguyên có dấu) và các ký tự trong bảng mã ASCII.

Biến Word
...

Biến từ (word) trong Assembly dành 2 byte không gian bộ nhớ. Biến từ có thể chứa các giá trị từ 0 đến 65535 đối với số không dấu (hoặc từ -32768 đến 32767 đối với số có dấu). Nó cũng có thể chứa được tối đa 2 ký tự ASCII, vì mỗi ký tự ASCII chiếm 1 byte

#4 - Khai báo mảng
...

  • Biến mảng là biến hình thành từ một dãy liên tiếp các phần tử cùng loại byte hoặc từ
  • Các cách khai báo mảng:
Cách 1: M1 DB 4,5,6,7,8,9
...
  • Cách trên định nghĩa mảng có tên là M1 gồm 6 byte với các giá trị khởi đầu là 4, 5, 6, 7, 8, 9
  • Phần từ đầu mảng là 4 và có địa chỉ trùng với địa chỉ của M1
  • Phần tử thứ hai là 5 và có địa chỉ là M1 + 1
Cách 2: M2 DB 100 DUP(0)
...
  • Cách trên định nghĩa một mảng tên là M2 gồm 100 byte chứa 100 giá trị khởi đầu là 0
  • Nếu không muốn khởi tạo giá trị ban đầu thì ta có thể viết như sau: M2 DB 100 DUP(?)
Cách 3: M3 DB 4,3,2, 2 DUP(1, 2 DUP(5), 6)
...
  • Cách trên được sử dụng để khai báo mảng chứa các phần tử lặp lại nhiều lần. Cụ thể
    • Lệnh trên khai báo một mảng byte với tên là M3
    • Phần 4, 3, 2 khai báo ba byte đầu tiên của mảng với giá trị lần lượt là 4, 3 và 2
    • Phần 2 DUP(5) tạo ra hai phần từ lặp lại là 5, 5
    • Phần DUP (1, 2 DUP(5), 6) tương ứng với 1, 5, 5, 6
    • Từ đó 2 DUP(1, 2 DUP(5), 6) tương ứng với 1, 5, 5, 6, 1, 5, 5, 6
    • Vì vậy, lệnh trên tạo ra một mảng byte M3 với các giá trị là 4, 3, 2, 1, 5, 5, 6, 1, 5, 5, 6
Biến kiểu xâu ký tự
...

Biến kiểu xâu kí tự là một trường hợp đặc biệt của biến mảng, trong đó các phần tử của mảng là các kí tự, do đó khi khai báo kiểu xâu kí tự ta sẽ sử dụng kiểu dữ liệu DB cho mỗi ký tự là đủ

Ví dụ: STR1 DB 'string' (biến STR1 được cấp 6 byte trong bộ nhớ)

Một xâu kí tự cũng có thể được định nghĩa bằng các kí tự và mã ASCII của nó

Ví dụ: STR2 DB 73h, 74h, 'r', 'i', 6Eh, 67h (Nếu chuyển các giá trị hex sang ký tự ASCII ta sẽ có chuỗi "string")

Để đảm bảo chuỗi được đọc / ghi đúng theo mong đợi thì bạn nên thêm ký tự kết thúc chuỗi $ vào cuối mỗi xâu ký tự

#5 - Cấu trúc của một chương trình Assembly
...

#5.1 - Khai báo quy mô sử dụng bộ nhớ
...
Info

Trong các ngôn ngữ lập trình bậc thấp như Assembly, việc khai báo kích thước bộ nhớ là cần thiết để trình biên dịch biết cách quản lý và sử dụng bộ nhớ. Tuy nhiên, trong một số ngôn ngữ bậc cao như Python, JavaScript hoặc Ruby, việc quản lý bộ nhớ thường được thực hiện tự động thông qua các cơ chế như quản lý bộ nhớ tự động (garbage collection)

Các ngôn ngữ bậc thấp hơn thường yêu cầu lập trình viên phải có kiểm soát chặt chẽ hơn về cách sử dụng bộ nhớ, vì không có cơ chế tự động quản lý bộ nhớ. Trong các ngôn ngữ lập trình bậc cao, trình biên dịch thường có khả năng tự động xác định kích thước của dữ liệu và quản lý bộ nhớ cho bạn, giảm bớt việc phải làm điều này bằng tay. Tuy nhiên điều này có thể làm ảnh hưởng đến hiệu suất của chương trình, đặc biệt là trong các ứng dụng yêu cầu hiệu suất cao hoặc có hạn chế về tài nguyên như lập trình vi xử lý bằng Assembly[1]

  • Ta khai báo quy mô sử dụng bộ nhớ như sau:

    .MODEL [Kiểu kích thước bộ nhớ]

  • Các kiểu kích thước bộ nhớ cho chương trình hợp ngữ[2]

Kiểu kích thướcGiới hạn
Tiny (Hẹp)Sử dụng tối đa 64K bytes cho cả Code và Data (dữ liệu)
Small (Nhỏ)Sử dụng tối đa 64KB cho Code và 64KB cho Data (Code <= 64KB, Data <= 64KB) (Trong chương trình học của mình thì ta sẽ sử dụng kiểu khai báo này nhiều nhất)
Medium (Trung bình)Sử dụng tối đa 64KB cho Data và có thể nhiều hơn 64KB cho code (Code > 64KB, Data <= 64KB)
Compact (Gọn)Sử dụng tối đa 64KB cho Code và có thể nhiều hơn cho 64KB cho Data (Code <= 64KB, Data > 64KB)
Large (Lớn)Cả Code và Data đều có thể vượt quá dung lượng 64KB. Tuy nhiên, khi sử dụng mảng (array) ta không được sử dụng vượt quá 64KB (Code > 64KB, Data > 64KB)
Huge (Đồ sộ)Cả Code và Data đều có thể vượt quá dung lượng 64KB. Thêm nữa là ta cũng có thể sử dụng mảng vượt quá 64KB
#5.2 - Khai báo đoạn ngăn xếp (Stack Segment)
...
  • Cách khai báo đoạn ngăn xếp:

    .STACK [Kích thước]

  • Stack Segment chủ yếu được sử dụng để lưu trữ dữ liệu tạm thời và thông tin về ngữ cảnh của chương trình, như giá trị trả về của các hàm và địa chỉ trở lại sau khi gọi hàm (giống như khi thực hiện quay lui hay đệ quy vậy)

  • Nếu không khai báo kích thước thì chương trình dịch sẽ tự động gán cho kích thước giá trị là 1KB

  • Tuy nhiên, đối với chương trình học của ta thì kích thước ngăn xếp này là quá lớn nên ta sẽ sử dụng 100-256 byte là đủ

  • Trong Assembly, Stack được khai báo trên đầu của chương trình và là Stack duy nhất được sử dụng xuyên suốt chương trình

  • Để sử dụng Stack ta sử dụng hai lệnh là PUSHPOP

#5.3 - Khai báo đoạn dữ liệu (Data Segment)
...
  • Để khai báo và khởi tạo các biến hoặc hằng số mà chương trình sẽ sử dụng, ta sẽ khai báo chúng trong phần .DATA
.DATA
	MSG DB   'hello!$'
	CR  DB   13
	LF  EQU  10
  • Khi bạn khai báo một biến trong phần .DATA của chương trình Assembly, hệ thống sẽ cấp phát một vùng nhớ trong RAM (bộ nhớ chính) để lưu trữ giá trị của biến đó. Địa chỉ của vùng nhớ này sẽ được liên kết với tên biến mà bạn đã khai báo. Chính vì vậy mà sau khi khai báo dữ liệu xong ta cần phải tiếp tục khởi tạo cho thanh ghi DS như sau:
;khoi tao DS
MOV AX, @Data ;khoi dau thanh ghi DS 
MOV DS, AX ;tro thanh ghi ds ve dau doan data
  • Thanh ghi đoạn dữ liệu DS trong Assembly không chứa dữ liệu trực tiếp mà chứa địa chỉ của đoạn dữ liệu. Khi bạn thực hiện một lệnh truy cập dữ liệu, DS sẽ được sử dụng như một cơ sở để tính toán địa chỉ thực sự của dữ liệu cần truy cập. Đây là cách mà các lệnh trên thanh ghi có thể truy cập vào vùng nhớ rộng lớn hơn nhiều so với kích thước của thanh ghi

Để hiểu tại sao lại cần sử dụng 2 lệnh MOV để khởi tạo cho thanh ghi DS bạn hãy đọc nội dung phần #8.1

#6 - Giới thiệu về Emu8086
...

  • Emu8086 là một trình giả lập cho vi xử lý Intel 8086, một trong những vi xử lý đầu tiên của Intel. Trình giả lập này giúp người dùng có thể viết, biên dịch và thử nghiệm các chương trình hợp ngữ cho vi xử lý 8086 mà không cần phải có phần cứng thực
  • Hệ điều hành DOS (Disk Operating System) đóng vai trò quan trọng trong Emu8086. Khi bạn viết chương trình hợp ngữ trên Emu8086, bạn thực sự đang viết chương trình để chạy trên hệ điều hành DOS. Điều này có nghĩa là, bạn có thể sử dụng các lệnh và chức năng của DOS trong chương trình của mình. Ví dụ, bạn có thể sử dụng các lệnh DOS để nhập xuất dữ liệu, đọc và ghi dữ liệu vào tệp,...
#6.1 - Vai trò của lệnh ngắt trong Emu8086
...
  • Trong hệ điều hành DOS, ngắt (interrupt) là một quá trình dừng chương trình chính đang chạy để ưu tiên thực hiện một chương trình khác

  • Khi một thiết bị phần cứng (như bàn phím hoặc chuột) cần sự chú ý của máy tính, nó sẽ gửi một "ngắt" đến bộ xử lý. Bộ xử lý sau đó sẽ dừng chương trình đang chạy để xử lý ngắt, và sau đó quay trở lại tiếp tục chương trình chính

  • Một lệnh ngắt trong DOS được sử dụng thường xuyên cho Emu8086 là ngắt INT 21H. Ngắt này được sử dụng để giao tiếp với bàn phím và màn hình. Dưới đây là cách sử dụng một số hàm của lệnh ngắt INT 21H:

  1. Hàm 1: Dùng để nhập một ký tự từ bàn phím. Nếu ký tự đó không phải là ký tự điều khiển, thì ký tự sau khi nhập sẽ được cất trong thanh ghi AL
MOV AH, 1
INT 21H ; Ngắt để nhập 1 kí tự vào từ bàn phím

Thanh ghi AH đóng vai trò quan trọng trong việc thực hiện ngắt. Trong lệnh ngắt, giá trị được đặt trong thanh ghi AH sẽ giúp xác định hàm cụ thể sẽ được thực hiện

  1. Hàm 2: Dùng để hiển thị một ký tự ra màn hình. Mã ASCII của ký tự muốn hiển thị phải được cất trong thanh ghi DL
MOV AH, 2
MOV DL, MÃ ASCII CỦA KÍ TỰ
INT 21H ; Ngắt để hiển thị kí tự ra màn hình

Ví dụ: Hiển thị kí tự 'A'

MOV AH, 2
MOV DL, 'A'
INT 21H ; Ngắt để hiển thị kí tự ra màn hình
  1. Hàm 9: Thực hiện chức năng xuất một chuỗi ký tự. Chuỗi này phải kết thúc bằng ký tự $. Điều này có nghĩa là, nó sẽ in ra chuỗi ký tự cho đến khi gặp ký tự $
MOV AH, 9
LEA DX, message
INT 21H
...
message DB 'Hello, World!$'
  1. Hàm nhập chuỗi ký tự:
MOV AH, 10
LEA DX, str
INT 21H
  • Hàm DOS INT 21H với AH = 10 được sử dụng để đọc một chuỗi từ bàn phím. Chuỗi này được lưu dưới dạng chuỗi DOS, trong đó byte đầu tiên chứa độ dài tối đa của chuỗi, byte thứ hai chứa độ dài thực sự của chuỗi, và các byte tiếp theo chứa các ký tự của chuỗi
  • Do đó, khi bạn nhập chuỗi, str[0] chứa độ dài tối đa của chuỗi, str[1] chứa độ dài thực sự của chuỗi. Khi thực hiện in chuỗi nhập vào từ bàn phím thì ta sẽ in ra bắt đầu từ str[2]

Ví dụ: chuỗi 'Hello' mà bạn nhập vào bàn phím thực chất là 256,5,H,e,l,l,o

  1. Hàm ngắt chương trình: Đảm bảo rằng chương trình của bạn kết thúc một cách an toàn và kiểm soát
MOVE AH, 4CH
INT 21H
  • Trong một số trường hợp, đúng là khi chương trình chạy hết các lệnh, nó sẽ tự động kết thúc. Tuy nhiên, trong ngôn ngữ Assembly, việc này không đảm bảo
  • Khi chương trình Assembly chạy hết các lệnh, nếu không có lệnh kết thúc chương trình, CPU sẽ tiếp tục thực thi các byte tiếp theo trong bộ nhớ như là mã máy. Điều này có thể dẫn đến các hành vi không mong muốn, bao gồm cả việc chạy mã không hợp lệ hoặc truy cập vào vùng nhớ không hợp lệ
  • Vì vậy, để đảm bảo rằng chương trình kết thúc một cách an toàn và kiểm soát, bạn nên sử dụng lệnh kết thúc chương trình

#7 - Các cấu trúc lập trình hay sử dụng nhất
...

#7.1 - IF THEN
...

Để hiểu cách IF THEN hoạt động ta đi vào một ví dụ sau:

Thực hiện gán giá trị tuyệt đối của AX cho BX

CMP AX, 0
JNL GAN
NEG AX
GAN: MOV BX, AX

Giải thích

  • CMP AX, 0 : So sánh giá trị trong thanh ghi AX với giá trị 0. Kết quả của phép so sánh này sẽ được lưu trong các cờ của bộ xử lý
  • JNL GAN: Đây là một lệnh nhảy có điều kiện. Trong trường hợp này JNL là viết tắt của "Jump if Not Less" (nhảy nếu không nhỏ hơn). Nó kiểm tra cờ Zero (ZF) và cờ Sign (SF). Nếu ZF = 1 (giá trị = 0) hoặc SF = 0 (Giá trị dương), nghĩa là giá trị trong AX không nhỏ hơn 0, thì chương trình sẽ nhảy đến nhãn GAN. Nếu không, chương trình sẽ tiếp tục thực thi lệnh tiếp theo
  • NEG AX: Nếu giá trị trong AX là số âm, lệnh này sẽ đảo dấu của nó
  • GAN: MOV BX, AX: Trong lệnh này GAN là nhãn (label). Lệnh MOV BX, AX sao chép giá trị trong thanh ghi AX vào thanh ghi BX

Các lệnh rẽ nhánh tiêu biểu

InstructionDescriptionFlags Test
JE/JZJump Equal or Jump ZeroZF
JNE/JNZJump Not Equal or Jump Not ZeroZF
JG/JNLEJump Greater or Jump Not Less/EqualOF, SF, ZF
JGE/JNLJump Greater/Equal or Jump Not LessOF, SF
JL/JNGEJump Less or Jump Not Greater/EqualOF, SF
JLE/JNGJump Less/Equal or Jump Not GreaterOF, SF, ZF
#7.2 - FOR DO
...
  • Trong ngôn ngữ Assembly không có cấu trúc vòng lặp for như các ngôn ngữ cấp cao khác. Tuy nhiên, bạn vẫn có thể mô phỏng cấu trúc for bằng cách sử dụng các chỉ thị nhảy có điều kiện
.MODEL SMALL
.STACK 100h

.DATA
    count DB 9  ; Số lần lặp

.CODE
MAIN PROC
    MOV AX, @DATA
    MOV DS, AX

    MOV CH, count  ; Khởi tạo CH bằng giá trị của biến count

    ; Vòng lặp for
    FOR_LOOP:
        ; Thực hiện các lệnh ở đây
        ; Ví dụ: in ra các giá trị của CX
        MOV AH, 2
        MOV DL, CH
        ADD DL, '0'; Thực hiện ép kiểu số sang ký tự trước khi in ra
        INT 21h

        ; Giảm CX đi 1
        DEC CH
        CMP CH, 0
        ; Kiểm tra điều kiện lặp, nếu CH bằng 0 thì dừng lại
        JNE FOR_LOOP

    ; Kết thúc chương trình
    MOV AH, 4Ch
    INT 21h
MAIN ENDP
END MAIN


#8 - Một số lệnh hay được sử dụng trong Assembly
...

#8.1 - Các lệnh trao đổi dữ liệu
...
  1. MOV Dest, Source : dữ liệu được chép từ toán hạng nguồn (Source) tới toán hạng đích (Dest). Lệnh này không làm thay đổi toán hạng nguồn, chỉ làm thay đổi toán hạng đích. Chú ý một số trường hợp không được sử dụng lệnh này bao gồm:

    • Không chuyển dữ liệu trực tiếp giữa hai ô nhớ
    • Không chuyển giá trị tức thời vào thanh ghi đoạn
    • Không chuyển dữ liệu giữa hai thanh ghi đoạn
  2. LEA Dest, Source (Load Effective Address): Lệnh gán địa chỉ của gốc vào đích

#8.2 - Các lệnh thực hiện tính toán
...
  1. INC [Toán hạng đích] : Làm tăng giá trị của Toán hạng đích lên 1 đơn vị
  2. DEC [Toán hạng đích] : Làm giảm giá trị của Toán hạng đích xuống 1 đơn vị
  3. ADD [Toán hạng đích], [Toán hạng nguồn]: Lấy giá trị / nội dung của Toán hạng nguồn cộng vào giá trị / nội dung của Toán hạng đích
  4. SUB [Toán hạng đích], [Toán hạng nguồn] : Lấy giá trị / nội dung của Toán hạng đích trừ đi giá trị / nội dung của Toán hạng nguồn
  5. MUL [Toán hạng nguồn] : Thực hiện phép nhân trên số không dấu
  • Điều đặc biệt về lệnh này là nó chỉ nhận một toán hạng, kết quả của phép nhân sẽ được lưu vào thanh ghi AX. Vì thế bạn có thể tưởng tượng rằng MUL CX có nghĩa là MUL AX, CX
  • Nếu nguồn là 8 bit, kết quả sẽ được lưu trong thanh ghi AX
  • Nếu nguồn là 16 bit, kết quả sẽ được lưu trữ trong thanh ghi DX:AX
    • 16 bit cao nhất của kết quả sẽ được lưu trữ trong thanh ghi DX
    • 16 bit thấp nhất của kết quả sẽ được lưu trữ trong thanh ghi AX
  1. IMUL [Toán hạng nguồn] (Integer Multiply): Tương tự như MUL nhưng là số có dấu
  2. DIV [Toán hạng nguồn] : Thực hiện phép chia trên số không dấu.
  • Nếu Toán hạng nguồn là toán hạng 8 bit thì lệnh sẽ lấy giá tri của thanh ghi AX (số bị chia) chia cho Toán hạng nguồn (số chia). Kết quả là thương số chứa trong thanh ghi AL, số dư chứa trong thanh ghi AH
  • Nếu Toán hạng nguồn là toán hạng 16 bit thì lệnh sẽ lấy giá trị của cặp thanh ghi DX:AX (số bị chia) chia cho Toán hạng nguồn (số chia). Kết quả là thương số chứa trong thanh ghi AX, số dư chứa trong thanh ghi DX
  • Nếu phép chia cho 0 xảy ra hay thương số vượt quá giới hạn của thanh ghi AL (chia 8 bit) hay AX (chia 16 bit) thì CPU sẽ phát sinh lỗi "Divice Overflow". Điều này có nghĩa là, nếu bạn thực hiện phép chia cho toán hạng nguồn 8 bit và kết quả bị tràn, thì kết quả sẽ không tự động chuyển sang thanh ghi AX
  1. IDIV [Toán hạng nguồn] :Tương tự như DIV nhưng là số có dấu
#8.5 - Lệnh Logic: NOT - AND - OR - XOR
...
  1. NOT [Toán hạng đích]
  2. AND [Toán hạng đích], [Toán hạng nguồn]
  3. OR [Toán hạng đích], [Toán hạng nguồn]
  4. XOR [Toán hạng đích], [Toán hạng nguồn]
  • Trong các bài tập ta sẽ sử dụng lệnh XOR nhiều nhất để khởi tạo một thanh ghi với giá trị ban đầu là 0, hoặc có thể được sử dụng để khởi tạo bộ đếm CX về 0 trước khi sử dụng trong một vòng lặp bằng cách viết XOR CX, CX
#8.6 - Lệnh lặp LOOP [Nhãn đích]
...
  • Khi gặp lệnh này chương trình sẽ lặp lại việc thực hiện các lệnh phía sau Nhãn đích đủ n lần, với n được đặt trước trong thanh ghi CX (Thanh ghi CX được sử dụng làm bộ đếm vòng lặp)
  • Sau mỗi lần lặp CX tự động giảm 1 đơn vị (CX = CX - 1) và lệnh lặp sẽ dừng khi CX = 0
Lap:
	;các lệnh cần lặp được viết ở đây
LOOP Lap
#8.7 - Lệnh xử lý chuỗi
...
  1. lodsb (viết tắt của "Load String Byte"): là lệnh di chuyển dữ liệu từ bộ nhớ vào thành ghi AX và tăng con trỏ bộ nhớ sau khi lệnh được thực thi. Cụ thể nó hoạt động như sau
  • lodsb sẽ đọc một byte từ bộ nhớ tại địa chỉ mà con trỏ bộ nhớ (nằm trong thành ghi SI) trỏ tới
  • Byte đó sẽ được đưa vào thanh ghi AL
  • Con trỏ bộ nhớ sẽ được tăng lên 1 byte
  • Nếu cờ Direction (DF) được thiết lập, con trỏ bộ nhớ sẽ giảm thay vì tăng
  1. lodsw cũng giống như lodsb nhưng nó đọc một từ (word) từ bộ nhớ và lưu trữ trong thanh ghi AX.
  • lodsw sẽ đọc hai byte từ bộ nhớ tại địa chỉ mà con trỏ bộ nhớ (nằm trong thành ghi SI) trỏ tới
  • Byte đó sẽ được đưa vào thanh ghi AL
  • Con trỏ bộ nhớ sẽ được tăng lên 2 byte
  • Nếu cờ Direction (DF) được thiết lập, con trỏ bộ nhớ sẽ giảm thay vì tăng
#8.8 - Lệnh CMP
...
  • Cú pháp: CMP Dest, Source
  • Tác dụng: Lệnh CMP (Compare) được sử dụng để so sánh giá trị / nội dung của Toán hạng đích so với Toán hạng nguồn
  • Bản chất của lệnh CMP Dest, Source là lệnh SUB Dest, Source (thực hiện phép tính Dest - Source, nhưng kết quả của phép tính không được lưu vào Des như trong lệnh SUB. Thay vào đó, kết quả của phép trừ được sử dụng để cập nhật trong thanh ghi cờ
  • Các cờ này sau đó có thể được sử dụng bởi các lệnh nhảy có điều kiện (Conditional Jump Instructions) ví dụ như JE (Jump if Equal) để kiểm soát luồng thực thi của chương trình

  1. CPU 8086 thuộc dòng kiến trúc 16 bít, nên rất khó để có thể truy cập được nhiều hơn 64KB bộ nhớ. Do vậy, để truy cập nhiều hơn 64KB bộ nhớ trong CPU 8086, ta cần sử dụng một kỹ thuật gọi là "phân đoạn bộ nhớ" (memory segmentation) giúp cho CPU 8086 có thể quản lý lên đến 1MB bộ nhớ↩︎
  2. Đọc thêm tại: Microsoft Word - Lecture 9 Memory Models, Instruction Operand Notation and Data Transfer Instructions.docx (philadelphia.edu.jo)↩︎