Bài 7 · Nâng cao · 28 phút· Cập nhật 11/06/2026
Lập trình cho CPU
Biên soạn bởi Nguyễn Anh Tuấn
Tự viết chương trình hợp ngữ (assembly) cho CPU 8-bit: nạp/cộng/ghi, nhảy, rẽ nhánh JZ và vòng lặp - dịch ra mã máy rồi chạy ngay.
Ở Bài “Mô hình CPU 8-bit” bạn đã thấy CPU chạy theo chu trình Nạp → Giải mã → Thực thi, lần lượt từng ô. Vậy “lập trình” chỉ là: viết một danh sách lệnh đúng thứ tự để CPU làm việc bạn muốn. Bộ lệnh của CPU này chỉ gồm 9 lệnh (kèm vài chỉ dẫn trình dịch sẽ giới thiệu ngay sau):
| Lệnh | Opcode | Ý nghĩa |
|---|---|---|
| LDI n | 0x5 | Nạp thẳng số n (0-15) vào thanh ghi A |
| LDA đc | 0x1 | A ← giá trị đọc từ ô nhớ địa chỉ đc |
| ADD đc | 0x2 | A ← A + giá trị ở ô đc |
| SUB đc | 0x3 | A ← A − giá trị ở ô đc |
| STA đc | 0x4 | Ghi giá trị của A vào ô đc |
| JMP đc | 0x6 | Nhảy: cho CPU chạy tiếp từ ô đc |
| JZ đc | 0x7 | Nhảy tới đc NẾU kết quả vừa rồi = 0 |
| OUT | 0xE | Hiện A ra “đèn” kết quả |
| HLT | 0xF | Dừng chương trình |
Tên lệnh không phải chữ ngẫu nhiên - chúng là viết tắt tiếng Anh (các chữ in đậm ghép lại thành tên lệnh):
- LDA LoaD Accumulator - nạp giá trị vào thanh ghi A (accumulator = thanh ghi “tích luỹ”)
- LDI LoaD Immediate - nạp thẳng một số (immediate = số nằm ngay trong lệnh)
- ADD ADD - cộng
- SUB SUBtract - trừ
- STA STore Accumulator - lưu (store) giá trị của A xuống ô nhớ
- JMP JuMP - nhảy
- JZ Jump if Zero - nhảy nếu kết quả bằng 0 (zero)
- OUT OUTput - xuất / hiện A ra ngoài
- HLT HaLT - dừng (halt)
(Ngoài ra còn NOP = No OPeration - “không làm gì”.)
Trong chương trình bạn sẽ còn gặp vài từ khác - ORG, DAT, nhãn (lap:) và dấu ;. Chúng KHÔNG phải lệnh CPU (không có opcode, CPU không chạy chúng) mà là chỉ dẫn cho trình dịch (assembler directive) - mách trình dịch cách bố trí bộ nhớ:
- ORG n ORiGin - đặt mục TIẾP THEO vào ô số n (vd ORG 14 = “từ đây xếp vào ô 14”)
- DAT n DATa - khai báo một ô DỮ LIỆU mang giá trị n (0-255), vd DAT 5
- nhãn: đặt TÊN cho địa chỉ ô hiện tại (vd lap:); trình dịch tự thay nhãn bằng số khi dịch
- ; chú thích (comment) - trình dịch bỏ qua phần sau dấu ;
Mấu chốt: lệnh được dịch thành byte mã máy và CPU CHẠY; còn chỉ dẫn chỉ giúp trình dịch SẮP XẾP bộ nhớ, bản thân nó không trở thành lệnh chạy.
Trung thực: đây là CPU dạy học, KHÔNG phải hợp ngữ chuẩn
Nhớ lại cấu trúc byte lệnh
Cộng hai số rồi hiện kết quả. Vì 5 và 3 đã có sẵn trong hai ô nhớ, ta nạp số thứ nhất vào A, cộng số thứ hai, rồi hiện ra và dừng. Đọc kỹ phần chú thích, sửa thử, và bấm các ví dụ khác:
Mã máy (6 byte / 16 ô):
| [0] | 0001 1110 | LDA 14 |
| [1] | 0010 1111 | ADD 15 |
| [2] | 1110 0000 | OUT |
| [3] | 1111 0000 | HLT |
| [14] | 0000 0101 | DAT 5 |
| [15] | 0000 0011 | DAT 3 |
Kết quả chạy (đèn OUT):
8
A cuối = 8 · 12 bước
Sửa chương trình bên trái - mã máy và kết quả tự cập nhật. Bấm một ví dụ ở trên để nạp nhanh. (Muốn xem từng bước chạy trên bảng mạch? Mở Bài “Mô hình CPU 8-bit”.)
- ▸LDA 14 / ADD 15: làm việc với SỐ đang nằm trong ô nhớ 14, 15.
- ▸OUT hiện A ra “đèn”; HLT dừng máy.
- ▸ORG 14 + DAT đặt dữ liệu vào ô 14, 15 (tách khỏi vùng lệnh).
CPU này chỉ có 16 ô (Bài Bộ nhớ & RAM). Cả lệnh lẫn dữ liệu đều phải nằm trong đó. CPU bắt đầu chạy từ ô 0 nên ta xếp lệnh ở các ô thấp, rồi dùng ORG để đặt dữ liệu ra các ô cao - kẻo CPU “chạy nhầm” dữ liệu như thể nó là lệnh.
- ▸Ô 0,1,2,…: các byte lệnh, CPU chạy lần lượt.
- ▸ORG 14: “từ đây là dữ liệu”, đặt ở ô 14 trở đi.
- ▸DAT 5: một ô dữ liệu mang giá trị 5.
- ▸Hết 16 ô là hết - đó là vì sao chương trình phức tạp không nhét vừa.
Bình thường CPU chạy ô kế tiếp. Lệnh JMP đc đổi điều đó: nó cho CPU nhảy tới ô khác. Nhảy ngược về phía trên tạo thành vòng lặp. Ví dụ “Đếm lên” cộng 1 mãi mãi:
; Đếm lên vô hạn - minh hoạ vòng lặp
; Lưu ý: đếm quá 255 sẽ TRÀN SỐ và CPU dừng
LDI 1 ; A ← 1
lap: ; nhãn cho vòng lặp
OUT ; hiện A
ADD 15 ; A ← A + 1
JMP lap ; quay lại nhãn "lap"
ORG 15
DAT 1 ; ô 15 = 1 Vô hạn… cho tới khi tràn
Muốn máy quyết định, ta cần rẽ nhánh. JZ đc chỉ nhảy khi kết quả vừa tính ra bằng 0 (cờ ZERO bật). Mẹo so sánh hai số: lấy hiệu của chúng - nếu hiệu = 0 thì chúng bằng nhau.
; Hai số có bằng nhau không?
; Hiện 1 nếu bằng, 0 nếu khác.
; Cách làm: lấy hiệu, nếu = 0 thì bằng nhau
LDA 13 ; A ← số X (ô 13)
SUB 14 ; A ← X − Y
JZ bang ; nếu hiệu = 0 → nhảy "bang"
LDI 0 ; khác nhau → A = 0
OUT
HLT
bang:
LDI 1 ; bằng nhau → A = 1
OUT
HLT
ORG 13
DAT 6 ; ← số X (đổi để thử)
DAT 6 ; ← số Y (đổi để thử) - ▸JZ giống một “lối rẽ có điều kiện”: kết quả vừa rồi bằng 0 thì rẽ sang ô khác, không thì đi thẳng.
- ▸Ghép SUB + JZ là cách kiểm tra “hai số có bằng nhau không”.
- ▸Chỉ có JZ (không có “nhảy nếu âm/nhớ”) nên chia chỉ phát hiện được chia hết.
Đến lượt bạn. Dùng sân chơi dưới đây: gõ chương trình, mã máy và kết quả tự cập nhật. Nếu sai, trình dịch sẽ chỉ rõ dòng lỗi. Thử ví dụ “Tổng 1+…+N” - nó gói gọn cả vòng lặp lẫn rẽ nhánh:
Mã máy (6 byte / 16 ô):
| [0] | 0001 1110 | LDA 14 |
| [1] | 0010 1111 | ADD 15 |
| [2] | 1110 0000 | OUT |
| [3] | 1111 0000 | HLT |
| [14] | 0000 0101 | DAT 5 |
| [15] | 0000 0011 | DAT 3 |
Kết quả chạy (đèn OUT):
8
A cuối = 8 · 12 bước
Sửa chương trình bên trái - mã máy và kết quả tự cập nhật. Bấm một ví dụ ở trên để nạp nhanh. (Muốn xem từng bước chạy trên bảng mạch? Mở Bài “Mô hình CPU 8-bit”.)
- ▸Lỗi hay gặp: số quá phạm vi (toán hạng > 15, DAT > 255).
- ▸Lỗi hay gặp: chương trình dài quá 16 ô → “ô bị ghi đè”.
- ▸Mẹo: viết từng phần nhỏ, chạy thử liên tục, đọc kỹ chú thích.
Hết chương phần cứng 🎉
Câu hỏi thường gặp
Vì toán hạng chỉ có 4 bit (nửa thấp của byte lệnh), mà 4 bit đếm được 0-15. Muốn có số lớn hơn, ta đặt nó vào một ô nhớ (DAT) rồi dùng LDA để nạp.
LDA thay thế A bằng giá trị ở ô nhớ (A ← ô). ADD thì cộng thêm vào A (A ← A + ô). Muốn bắt đầu một phép tính, thường LDA trước rồi ADD/SUB sau.
CPU KHÔNG hiểu nhãn; nó chỉ hiểu địa chỉ số. Nhãn là tiện ích cho người viết: trình dịch (assembler) tự thay mỗi nhãn bằng địa chỉ ô tương ứng khi dịch ra mã máy.
CPU chạy lệnh ở ô mà con trỏ lệnh (PC) đang trỏ tới, lần lượt từ ô 0. Ta dùng ORG để đặt dữ liệu ra các ô cao (vd 13-15) để CPU không “chạy nhầm” dữ liệu. Sắp xếp sai là chương trình hỏng.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Khác nhau cốt lõi giữa lệnh CPU (như ADD) và chỉ dẫn trình dịch (như ORG, DAT) là gì?
- 1
Đổi số hạng
Mở công cụ ở Bước 2, sửa chương trình “Cộng 5 + 3” thành 7 + 2 (đổi hai dòng
DAT). Kết quả ra gì?Hoàn thành khi:
DAT 7vàDAT 2→ đèn hiện 9. - 2
Viết phép trừ
Tự viết chương trình tính 10 − 6 rồi hiện kết quả (gợi ý:
LDA,SUB,OUT,HLT,ORG,DAT).Hoàn thành khi:
LDA 14 / SUB 15 / OUT / HLT / ORG 14 / DAT 10 / DAT 6→ ra 4. - 3
Nhân bằng cộng lặp
Viết chương trình tính 5 × 3 bằng cách cộng (không có lệnh nhân).
Hoàn thành khi:
LDI 0rồiADDô-chứa-5 ba lần (hoặcADDô-chứa-3 năm lần) →OUTra 15. - 4
Đọc vòng lặp
Nạp ví dụ “Đếm lên”. Vì sao nó dừng, và số cuối cùng in ra là bao nhiêu?
Hoàn thành khi: Cộng tới 256 thì TRÀN SỐ (vượt 8 bit) nên CPU dừng; số cuối cùng in được là 255.
- 5
Rẽ nhánh với JZ
Nạp ví dụ “So sánh bằng nhau”. Đổi hai số để chúng KHÁC nhau rồi chạy. Kết quả đổi thế nào, vì sao?
Hoàn thành khi: Hai số khác nhau → hiệu ≠ 0 →
JZkhông nhảy → in ra 0 (thay vì 1). - 6
Tự viết có vòng lặp
Dùng ví dụ “Tổng 1+…+N” (hoặc tự viết), tính tổng 1+2+3. Đặt N = 3 và chạy.
Hoàn thành khi: Đặt
DATN = 3 → chương trình lặp cộng dồn →OUTra 6.