Nhiều tác vụ thường ngày của máy tính là di chuyển mấy thứ linh tinh, và bằng "thứ linh tinh" tôi đang ám chỉ các byte. Chúng ta cảm nhận byte di chuyển mỗi khi tải hoặc lưu một tệp, khi xem phim, nghe nhạc trực tuyến, hay gọi video. Đôi khi, nếu những byte đó không di chuyển đủ nhanh, âm thanh hoặc hình ảnh có thể bị đứng hoặc giật lag. Ai cũng gặp đúng không?
Ở cấp độ vi mô hơn, các byte cũng di chuyển qua lại ngay bên trong bản thân bộ xử lý trung tâm (CPU). Các byte đi từ bộ nhớ vào CPU, rồi vào bộ logic và số học (ALU). Kết quả từ ALU đôi khi lại quay ngược trở lại ALU để thực hiện thêm các phép tính số học hoặc logic khác trước khi đưa về lưu trong bộ nhớ.
Việc các byte dịch chuyển bên trong CPU nghe có vẻ không hào nhoáng bằng những màn cày nát số liệu nặng đô của ALU, nhưng nó lại đóng vai trò cực kỳ thiết yếu.
Khi các byte này được truyền đi bên trong CPU, chúng được lưu trữ trong một tập hợp các chốt. Khái niệm này có lẽ đã quen thuộc từ Bộ Tích Lũy Ba-Byte ở Chương 20. Cỗ máy đó chứa bốn chốt 8-bit, một để lưu mã lệnh và ba chốt còn lại để lưu byte dữ liệu.
CPU mà tôi đang tạo dựa trên bộ vi xử lý Intel 8080, và nó cần nhiều hơn bốn chốt. Vội gì mà cho bạn xem hết ngay. Tôi muốn tập trung trước vào bảy chốt 8-bit đặc biệt có thể được điều khiển trực tiếp bởi các lệnh của CPU. Các chốt này được gọi là thanh ghi (register) và một mục đích chính của các thanh ghi này là lưu trữ byte khi chúng được xử lý bởi ALU.
Cả bảy thanh ghi này đều quan trọng nhưng có một cái đặc biệt hơn và nó được gọi là bộ tích lũy (accumulator).
Bộ tích lũy đặc biệt ra sao? Như bạn còn nhớ từ chương trước, ALU có hai đầu vào A và B. Trong Intel 8080 (cũng như trong CPU mà tôi sắp chế tạo), đầu vào đầu tiên trong hai đầu vào này luôn luôn là giá trị được lưu trong bộ tích lũy, và đầu ra của ALU luôn luôn được nạp ngược trở lại vào bộ tích lũy. Từ bộ tích lũy, nó có thể được dời sang một trong các thanh ghi khác hoặc đem cất vào bộ nhớ.
Bảy thanh ghi này được nhận diện bằng các chữ cái. Bộ tích lũy còn được gọi là thanh ghi A. Bốn thanh ghi khác bị gán cho những cái tên khá nhàm chán là B, C, D và E. Nhưng hai thanh ghi cuối cùng không phải là F và G đâu nhé. Như bạn sẽ thấy, hai thanh ghi này thường hay bắt cặp với nhau để tạo ra một địa chỉ 16-bit dùng truy cập bộ nhớ. Vì lẽ đó, chúng được gọi là H và L, viết tắt của "high byte" (byte cao) và "low byte" (byte thấp).
Tóm lại, Intel 8080 (và CPU của tôi) định nghĩa bảy thanh ghi, được đặt tên là A, B, C, D, E, H, và L.
Ở trang 318 trong chương trước, tôi đã chỉ ra mã lệnh 3Eh là "Mã để đưa byte tiếp theo vào CPU." Nói chính xác hơn, đó là mã để đưa byte tiếp theo trong bộ nhớ vào bộ tích lũy, hay còn gọi là thanh ghi A. Mã 3Eh đó là một thành viên của một họ các mã tương tự được 8080 thực thi:
Mã lệnh | Ý nghĩa
--------+-----------------------------------------------
06h | Đưa byte tiếp theo vào thanh ghi B
0Eh | Đưa byte tiếp theo vào thanh ghi C
16h | Đưa byte tiếp theo vào thanh ghi D
1Eh | Đưa byte tiếp theo vào thanh ghi E
26h | Đưa byte tiếp theo vào thanh ghi H
2Eh | Đưa byte tiếp theo vào thanh ghi L
36h | Đưa byte tiếp theo vào bộ nhớ tại địa chỉ [HL]
3Eh | Đưa byte tiếp theo vào thanh ghi A
Mã 3Eh nằm cuối bảng. Vâng, đúng là thứ tự của những mã này không hề theo thứ tự bảng chữ cái của các thanh ghi, nhưng sự đời vốn vậy mà.
Hãy chú ý đến mã 36h. Mã này khác biệt với phần còn lại trong bảng. Nó không đưa byte nằm sau mã lệnh vào một trong bảy chốt. Mà đưa byte đó vào bộ nhớ tại địa chỉ 16-bit gộp bởi các thanh ghi H và L, ký hiệu là [HL].
Tôi vẫn chưa chế xong một CPU hiểu được mấy lệnh này đâu, nhưng giả dụ ta có một CPU như thế (hay một máy tính dùng Intel 8080), ta có thể cho chạy một đoạn chương trình máy tính nhỏ sử dụng ba trong số các mã lệnh này:
+-------+
0000h: | 26h | Mã đưa byte tiếp theo vào H
+-------+
| 00h |
+-------+
| 2Eh | Mã đưa byte tiếp theo vào L
+-------+
| 08h |
+-------+
| 36h | Mã đưa byte tiếp theo vào [HL]
+-------+
| 55h |
+-------+
| 76h | Mã dừng
+-------+
| 00h |
+-------+
0008h: | 00h | ← Nơi lưu byte 55h
+-------+
Ba trong số mã này—26h, 2Eh, và 36h—đều nằm trong bảng trên. Mã đầu tiên đưa byte tiếp theo trong bộ nhớ (là 00h) vào thanh ghi H. Mã thứ hai đưa byte 08h vào thanh ghi L. Giờ các thanh ghi H và L hợp lại tạo thành địa chỉ bộ nhớ 0008h. Mã lệnh thứ ba là 36h, ra lệnh lưu byte tiếp theo (là 55h) vào bộ nhớ tại [HL], tức là địa chỉ được tạo ra bởi các thanh ghi H và L. Cuối cùng, bắt gặp mã 76h, nó sẽ ra lệnh cho CPU dừng lại.
Việc dùng thanh ghi H và L để đúc ra một địa chỉ bộ nhớ 16-bit được gọi là địa chỉ gián tiếp (indirect addressing), và mặc dù lúc này trông nó không đặc biệt mấy nhưng hóa ra lại vô cùng lợi hại đấy.
Nếu bạn phân tích các bit của tám mã lệnh trong bảng trước, bạn sẽ phát hiện ra một quy luật. Tất cả tám mã này đều được tạo thành từ các bit:
0 0 D D D 1 1 0
trong đó DDD là một mã 3-bit chỉ định đích đến (destination) của byte, như trong bảng sau:
Mã | Thanh ghi hoặc Bộ nhớ
----+----------------------
000 | B
001 | C
010 | D
011 | E
100 | H
101 | L
110 | [HL] hoặc M
111 | A
Mã 110 trong bảng này chỉ định một vị trí bộ nhớ được định vị bởi tổ hợp 16-bit của thanh ghi H và L. Cá nhân tôi thích ký hiệu vị trí đó là [HL], trong khi tài liệu của Intel về 8080 lại gọi nó là M (đương nhiên là viết tắt của memory - bộ nhớ), cứ như thể M chỉ đơn giản là một thanh ghi khác vậy.
Nếu mọi mã lệnh mà một bộ vi xử lý 8-bit hỗ trợ đều dài 1 byte, thì có thể có bao nhiêu mã lệnh tất cả? Hiển nhiên là 256 rồi. Hóa ra Intel 8080 chỉ định nghĩa 244 mã lệnh, chừa lại 12 giá trị 8-bit không được định nghĩa. Cho đến giờ, bạn đã thấy hai bảng gồm tám mã lệnh mỗi bảng, bảng đầu tiên ở Chương 21 trang 329, và tám mã nữa trong bảng ở trang 336. Ở trang 318 của Chương 21, bạn cũng đã được giới thiệu mã 32h (lưu một byte vào địa chỉ bộ nhớ theo sau mã lệnh) và mã 76h (dừng bộ vi xử lý).
Giữ chặt mũ nón nhé, vì bảng tiếp theo chứa tới 64 mã lệnh lận:
Bảng này chứa hơn một phần tư toàn bộ mã lệnh được Intel 8080 thực thi. Những phép toán này tạo thành phần lõi của các chức năng số học và logic mà CPU hỗ trợ.
Ở trên cùng của tám cột mã lệnh, bạn sẽ thấy các chữ viết tắt ba chữ cái cho Add (Cộng), Add with Carry (Cộng có nhớ), Subtract (Trừ), Subtract with Borrow (Trừ có mượn); các phép toán logic AND, XOR, và OR; và Compare (So sánh). Đây là những từ viết tắt mà Intel dùng trong tài liệu về bộ vi xử lý 8080. Bạn có thể coi chúng như những từ gợi nhớ (mnemonics)—tức là những từ đơn giản để giúp bạn nhớ các phép toán dài hơn—nhưng chúng cũng đóng một vai trò quan trọng khi viết chương trình cho 8080.
Mỗi phép toán trong số tám phép toán số học và logic này có thể được kết hợp với một nguồn nằm ở cột ngoài cùng bên trái, liệt kê bảy thanh ghi và bộ nhớ được truy cập bởi [HL].
Những từ viết tắt này cung cấp một cách tiện lợi để gọi tên các mã lệnh. Ví dụ, thay vì nói "mã để cộng nội dung của thanh ghi E vào bộ tích lũy" hay "mã lệnh 83h", bạn chỉ cần phán gỏn lọn:
ADD E
Tổng của thanh ghi E và bộ tích lũy được lưu ngược lại vào bộ tích lũy.
Thay vì nói dài dòng "Thực hiện phép toán exclusive OR giữa bộ tích lũy và byte bộ nhớ lưu tại [HL]" hay "mã lệnh AEh", bạn chỉ cần nói:
XRA M
Kết quả cũng lại được lưu vào bộ tích lũy.
Những từ viết tắt này chính thức được gọi là lệnh hợp ngữ(assembly language instructions). Thuật ngữ này ra đời vào đầu những năm 1950 và ám chỉ quá trình "lắp ráp" (assembling) một chương trình máy tính. Các từ gợi nhớ là một cách súc tích để gọi tên một mã lệnh cụ thể mà CPU thực thi. Lệnh hợp ngữ XRA M cũng chính là mã thao tác AEh, và ngược lại.
Toàn bộ 64 lệnh trong bảng trước đều tuân theo quy luật bit sau:
1 0 F F F S S S
trong đó FFF là mã cho chức năng số học hoặc logic được thực hiện trong Bộ logic số học (ALU) ở Chương 21. Mã SSS ám chỉ thanh ghi hoặc bộ nhớ nguồn, đó chính là các mã trong bảng trước.
Hồi nãy, bạn đã thấy một bảng mã lệnh làm nhiệm vụ di chuyển byte theo sau mã lệnh đó vào một trong các thanh ghi hoặc bộ nhớ. Chuyên ngành gọi chúng là các lệnh di chuyển tức thời (move immediate), viết tắt là MVI. Giờ thì hãy dẹp mấy cái mô tả dài dòng bằng tiếng Việt đi và bắt đầu dùng các lệnh hợp ngữ chính thức thôi:
Lệnh hợp ngữ | Opcode
-------------+-------
MVI B, data | 06h
-------------+-------
MVI C, data | 0Eh
-------------+-------
MVI D, data | 16h
-------------+-------
MVI E, data | 1Eh
-------------+-------
MVI H, data | 26h
-------------+-------
MVI L, data | 2Eh
-------------+-------
MVI M, data | 36h
-------------+-------
MVI A, data | 3Eh
Tôi cũng đã đẩy mã thao tác sang cột cuối cùng để nhấn mạnh sự quan trọng của lệnh hợp ngữ. Từ data ám chỉ byte theo sau mã thao tác.
Mã 32h được giới thiệu ở Chương 21 lưu nội dung của bộ tích lũy vào bộ nhớ tại vị trí địa chỉ đi kèm ngay sau mã lệnh. Đây là một nửa của cặp mã tương tự. Mã 3Ah nạp byte tại địa chỉ đó vào bộ tích lũy:
Lệnh hợp ngữ | Opcode
-------------+-------
STA addr | 32h
-------------+-------
LDA addr | 3Ah
Chữ viết tắt STA nghĩa là "store accumulator" (lưu bộ tích lũy), còn LDA là "load accumulator" (nạp bộ tích lũy). Chữ viết tắt addr ám chỉ một địa chỉ 16-bit được cung cấp trong 2 byte ngay sau mã thao tác.
Từ gợi nhớ HLT tương ứng với opcode 76h, dùng để dừng CPU.
Đây là một chương trình máy tính nhỏ sử dụng vài lệnh này, cho bạn thấy các mã thao tác tương ứng với các lệnh hợp ngữ ra sao:
Lệnh LDA nạp giá trị tại địa chỉ 2044h vào bộ tích lũy. Giá trị đó là 66h. Lệnh MVI sau đó nạp giá trị 33h vào thanh ghi B. Lệnh ADD cộng giá trị trong thanh ghi B với giá trị trong bộ tích lũy. Lúc này bộ tích lũy chứa giá trị 99h. Cuối cùng, lệnh STA lưu tổng đó vào bộ nhớ tại địa chỉ 2044h, ghi đè lên giá trị 66h bằng giá trị 99h.
Tám mã thao tác trong bảng ở Chương 21 thực hiện các phép toán số học và logic sử dụng byte đi sau mã lệnh. Đám lệnh này được trình bày ở đây với các từ gợi nhớ chính thức của Intel 8080:
Lệnh hợp ngữ | Opcode
-------------+-------
ADI data | C6h
-------------+-------
ACI data | CEh
-------------+-------
SUI data | D6h
-------------+-------
SBI data | DEh
-------------+-------
ANI data | E6h
-------------+-------
XRI data | EEh
-------------+-------
ORI data | F6h
-------------+-------
CPI data | FEh
Tôi có nhắc đến ở Chương 21 rằng mấy lệnh này được gọi là lệnh tức thời (lập tức) vì chúng thực hiện phép toán số học hoặc logic sử dụng ngay byte nằm liền kề sau opcode. Chúng có thể được đọc là "cộng ngay lập tức", "cộng có nhớ ngay", "trừ ngay lập tức" đại loại vậy. Phép toán luôn liên quan tới bộ tích lũy, và kết quả cũng quay về lưu trong bộ tích lũy.
Chương trình nhỏ vừa hiển thị ở trên có thể được đơn giản hóa như thế này:
Bây giờ thay vì phải nạp giá trị 33h vào thanh ghi B rồi mới cộng nó vào bộ tích lũy, giá trị 33h được cộng thẳng vào bộ tích lũy bằng lệnh ADI luôn.
Intel 8080 cũng định nghĩa 63 lệnh để di chuyển các byte từ thanh ghi này sang thanh ghi khác, từ địa chỉ bộ nhớ [HL] sang một thanh ghi, hoặc từ một thanh ghi sang địa chỉ bộ nhớ đó:
Opcode cho MOV đích,nguồn
------+-----------------------------------------------
| Đích
------+-----------------------------------------------
Nguồn | B | C | D | E | H | L | M | A
------+-----+-----+-----+-----+-----+-----+-----+-----
B | 40h | 48h | 50h | 58h | 60h | 68h | 70h | 78h
------+-----+-----+-----+-----+-----+-----+-----+-----
C | 41h | 49h | 51h | 59h | 61h | 69h | 71h | 79h
------+-----+-----+-----+-----+-----+-----+-----+-----
D | 42h | 4Ah | 52h | 5Ah | 62h | 6Ah | 72h | 7Ah
------+-----+-----+-----+-----+-----+-----+-----+-----
E | 43h | 4Bh | 53h | 5Bh | 63h | 6Bh | 73h | 7Bh
------+-----+-----+-----+-----+-----+-----+-----+-----
H | 44h | 4Ch | 54h | 5Ch | 64h | 6Ch | 74h | 7Ch
------+-----+-----+-----+-----+-----+-----+-----+-----
L | 45h | 4Dh | 55h | 5Dh | 65h | 6Dh | 75h | 7Dh
------+-----+-----+-----+-----+-----+-----+-----+-----
M | 46h | 4Eh | 56h | 5Eh | 66h | 6Eh | | 7Eh
------+-----+-----+-----+-----+-----+-----+-----+-----
A | 47h | 4Fh | 57h | 5Fh | 67h | 6Fh | 77h | 7Fh
Chúng được gọi là lệnh di chuyển (move instructions), viết tắt bằng từ gợi nhớ của 8080 là MOV. 63 lệnh này được viết kèm với cả thanh ghi đích và thanh ghi nguồn. Mã 69h là
MOV L,C
Thanh ghi đích xuất hiện trước, và thanh ghi nguồn theo sau. Coi chừng! Quy ước này lúc đầu có thể hơi khó hiểu đấy. Lệnh này có nghĩa là di chuyển byte trong thanh ghi C sang thanh ghi L. Bạn có thể mường tượng nó với một mũi tên nhỏ chỉ hướng di chuyển của byte:
Move L ← C
Nội dung trước đó của thanh ghi L bị thay thế bằng giá trị từ thanh ghi C. Nội dung của thanh ghi C không đổi, và sau khi thực hiện thì cả C và L đều có chung một giá trị.
Hãy để ý rằng có bảy lệnh trong đám này thực chất chẳng làm gì cả vì thanh ghi đích và nguồn là một, ví dụ như:
MOV C,C
Di chuyển nội dung của một thanh ghi vào chính nó thì không làm thay đổi gì.
Tuy nhiên, không có lệnh MOV M,M nào cả. Nếu có thì nó sẽ là mã thao tác 76h, nhưng mã này đã được dùng cho lệnh dừng (halt), viết tắt là HLT.
Đây là một cách khác để viết lại chương trình nhỏ thực hiện phép cộng một giá trị vào byte nằm ở vị trí bộ nhớ 2044h:
Phiên bản này phô diễn sự tiện lợi của việc định địa chỉ gián tiếp khi dùng thanh ghi H và L. Chúng chỉ cần được cài đặt một lần duy nhất với các giá trị tạo thành địa chỉ bộ nhớ 2044h. Lệnh MOV đầu tiên di chuyển giá trị tại địa chỉ đó vào bộ tích lũy. Bộ tích lũy lúc này chứa giá trị 66h. Sau đó giá trị 33h được cộng thêm vào. Lệnh MOV thứ hai đẩy giá trị của bộ tích lũy trở lại vị trí bộ nhớ 2044h mà chẳng cần phải chỉ định lại địa chỉ đó lần nữa.
Nếu bạn phân tích các bit tạo nên 63 lệnh MOV này, bạn sẽ khám phá ra quy luật sau:
0 1 D D D S S S
trong đó DDD là thanh ghi đích và SSS là thanh ghi nguồn. Đây vẫn là cái bảng mà bạn đã thấy lúc nãy:
Mã | Thanh ghi hoặc Bộ nhớ
----+----------------------
000 | B
001 | C
010 | D
011 | E
100 | H
101 | L
110 | M
111 | A
Một cách để thiết kế CPU là trước tiên quyết định xem bạn muốn CPU thi hành những lệnh nào, rồi sau đó mới nghĩ xem cần mạch điện gì để làm được điều đó. Về cơ bản, đó chính là những gì tôi đang làm ở đây. Tôi đang chọn một tập con của các lệnh Intel 8080 và sau đó xây dựng mạch điện cho chúng.
Để thi hành mọi mã lệnh liên quan tới bảy thanh ghi này, CPU cần một cách để lưu các byte vào bảy chốt và truy xuất các byte đó dựa trên các mã 3-bit này. Tạm thời, tôi sẽ ngó lơ mã 110 vì nó là trường hợp đặc biệt. Bảy mã còn lại có thể dùng làm đầu vào cho các bộ giải mã 3-sang-8.
Mạch điện dưới đây chứa bảy chốt và bảy bộ đệm ba trạng thái. Một bộ giải mã 3-sang-8 được dùng để chốt giá trị đi vào một trong các thanh ghi, và một bộ giải mã 3-sang-8 khác được dùng để kích hoạt một trong các bộ đệm ba trạng thái nhằm chọn ra một giá trị từ một trong các thanh ghi:
Mảng thanh ghi
Cấu trúc này được gọi là mảng thanh ghi(register array), và nó chính là mạch quan trọng nhất mà bạn sẽ gặp trong chương này. Tôi biết thoạt nhìn nó hơi phức tạp, nhưng thực ra nó lại khá dễ hiểu.
Trên cùng của mạch là một đường dẫn dữ liệu 8-bit mang nhãn "In". Đó là một byte cần được lưu vào mảng thanh ghi. Bảy hộp dán nhãn chữ cái chính là các chốt 8-bit. Dù không được ghi rõ ràng, mỗi chốt này đều có một đầu vào Clock bên trái để lưu giá trị vào trong chốt.
Ở phía trên sơ đồ, bên trái và bên phải là hai bộ giải mã 3-sang-8 với các đầu vào Select dán nhãn S2, S1, và S0. Giá trị của các đầu vào Select này tương ứng với các mã cho bảy chốt như đã hiển thị trong bảng trước. Đó là lý do tại sao đầu ra số 6 lại không được sử dụng: Đầu ra đó tương ứng với giá trị chọn là 110, vốn trỏ tới vị trí bộ nhớ chứ không phải chốt.
Bộ giải mã 3-sang-8 trên cùng bên trái điều khiển các đầu vào Clock trên các chốt này. Tín hiệu Clock đó được dẫn tới một trong bảy chốt tùy thuộc vào giá trị của S0, S1, và S2. Quá trình này sẽ lưu byte đầu vào vào một trong các chốt.
Nằm bên dưới mỗi một trong bảy chốt là một bộ đệm ba trạng thái. Dù không được xác định rõ ràng, mỗi bộ đệm này đều có một đầu vào Enable. Ở phía trên bên phải lại có một bộ giải mã 3-sang-8 khác. Bộ giải mã này kích hoạt một trong bảy bộ đệm ba trạng thái, và byte được lưu trong chốt đó sẽ xuất hiện ở đường dẫn dữ liệu "Out" dưới cùng.
Cũng nên lưu ý là bộ tích lũy được xử lý hơi đặc biệt một chút ở đây: Giá trị được lưu trong bộ tích lũy lúc nào cũng có sẵn ở đầu ra Acc nằm dưới cùng.
Các giá trị 8-bit đi vào mảng thanh ghi này có thể đến từ vài nguồn khác nhau: Chúng có thể đến từ bộ nhớ, hoặc từ một trong những thanh ghi khác, hoặc từ ALU. Các giá trị 8-bit đi ra khỏi mảng thanh ghi này có thể được lưu vào bộ nhớ, lưu vào một trong những thanh ghi khác, hoặc đi vào ALU.
Để bạn không quên cả khu rừng khi đi lang thang quanh những cái cây, đây là một sơ đồ khối đơn giản hóa hiển thị các đường dẫn dữ liệu này:
Sơ đồ khối đường dẫn dữ liệu
Rất nhiều thứ đã lược bỏ khỏi sơ đồ này. Nó chỉ hiển thị các đường dẫn dữ liệu 8-bit chính. Ngoại trừ đường dẫn từ đầu ra Acc của mảng thanh ghi đến đầu vào A của bộ logic số học, tất cả các đầu vào và ra khác đều được kết nối với nhau. Thậm chí Data Output của RAM cũng kết nối với Data Input của RAM!
Khả thi là vì mọi đầu ra của các linh kiện này đều đi qua các bộ đệm ba trạng thái, và tại bất kỳ thời điểm nào cũng chỉ có đúng một bộ đệm được kích hoạt. Giá trị được kích hoạt đó sau đấy có thể được lưu vào bộ nhớ bằng tín hiệu Write, hoặc lưu vào một trong bảy thanh ghi trong mảng thanh ghi, hoặc kết quả của một phép tính số học hay logic có thể được lưu vào ALU.
Sự kết nối giữa tất cả các đầu vào và đầu ra này được gọi là một bus dữ liệu. Nó là một đường dẫn dữ liệu dùng chung cho cả đầu vào lẫn đầu ra của mọi linh kiện. Khi một bộ đệm ba trạng thái nối với bus dữ liệu được kích hoạt, byte đó sẽ khả dụng trên toàn bộ bus dữ liệu, và bất kỳ linh kiện nào khác trên bus này cũng có thể sử dụng nó.
Bus dữ liệu này chỉ dành cho dữ liệu 8-bit. Sẽ có một bus khác dành cho địa chỉ bộ nhớ 16-bit vì địa chỉ đó cũng có thể xuất phát từ nhiều nguồn khác nhau. Bus 16-bit đó mang tên bus địa chỉ và bạn sẽ được thấy nó sớm thôi.
Bây giờ là đến vài chi tiết hơi rườm rà. Tôi thực lòng mong mảng thanh ghi này cứ đơn giản như cái mạch điện tôi vừa hiển thị cho bạn ở trang trước, nhưng nó sẽ cần thêm vài nâng cấp nữa.
Mảng thanh ghi tôi vừa cho bạn xem hoạt động rất tuyệt với các lệnh MOV liên quan đến việc di chuyển một byte từ thanh ghi này sang thanh ghi khác. Thật ra, các bộ giải mã 3-sang-8 được thiết kế trong mảng thanh ghi chính là nhằm phục vụ cho lệnh MOV đó: Nội dung của một thanh ghi có thể được kích hoạt trên bus dữ liệu, và giá trị đó có thể được lưu sang một thanh ghi khác.
Nhưng mảng thanh ghi này lại không hoạt động với các lệnh STA và LDA. Những lệnh này lưu một giá trị từ bộ tích lũy vào bộ nhớ, và nạp một giá trị từ bộ nhớ vào bộ tích lũy. Các lệnh khác cũng liên quan tới bộ tích lũy. Tất cả các lệnh số học và logic đều lưu kết quả vào bộ tích lũy.
Vì lý do đó, phần bộ tích lũy của mảng thanh ghi cần phải được nâng cấp đôi chút để cho phép một giá trị được lưu vào bộ tích lũy, và sau đó truy xuất từ bộ tích lũy mà không cần phụ thuộc vào các bộ giải mã 3-sang-8. Việc này có thể được hoàn thành chỉ với một chút logic bổ sung trong mảng thanh ghi liên quan tới chốt bộ tích lũy và bộ đệm ba trạng thái:
Thêm cổng OR
Hai tín hiệu bổ sung bên trái đã được thêm vào làm đầu vào cho hai cổng OR. Hai tín hiệu này cho phép một giá trị từ bus dữ liệu có thể được lưu vào bộ tích lũy (chốt có nhãn "A") độc lập với bộ giải mã Input Select (Chọn Đầu Vào), và cũng có thể xuất hiện trên bus dữ liệu độc lập với bộ giải mã Output Select (Chọn Đầu Ra).
Một nâng cấp khác cho mảng thanh ghi là cần thiết để định địa chỉ RAM bằng sự kết hợp giữa thanh ghi H và L. Nhưng nâng cấp này phức tạp hơn nhiều, nên chúng ta hãy cứ hoãn nó lại càng lâu càng tốt để dồn sức cho những việc cấp bách khác đã nhé!
Ba chốt 8-bit bổ sung phải được kết nối vào bus dữ liệu. Đây là các chốt lưu trữ các byte lệnh:
Chốt lưu byte lệnh
Mã thao tác luôn được lưu trữ trong Chốt Lệnh 1 (Instruction Latch 1). Sau khi mã thao tác được lưu trong chốt, nó có thể được sử dụng để tạo ra tất cả các tín hiệu khác điều khiển CPU. Bạn sẽ thấy cách việc này hoạt động trong chương tiếp theo.
Chốt Lệnh 2 (Instruction Latch 2) được sử dụng cho các lệnh có thêm một byte. Ví dụ, các mã thao tác dịch chuyển tức thời (MVI) được theo sau bởi một byte, byte này sau đó sẽ được di chuyển vào một trong các thanh ghi. Các lệnh số học và logic tức thời, chẳng hạn như ADI, cũng được theo sau bởi một byte. Trong trường hợp của ADI, ví dụ, byte đó được cộng vào bộ tích lũy. Do đó, giá trị trong Chốt Lệnh 2 phải được kích hoạt để hiển thị trên bus dữ liệu, đó cũng chính là mục đích của bộ đệm ba trạng thái.
Chốt Lệnh 2 và 3 được sử dụng cùng nhau cho các lệnh có độ dài 3 byte—ví dụ như, STA và LDA. Đối với các lệnh này, byte thứ hai và thứ ba tạo thành một giá trị 16-bit dùng để định địa chỉ bộ nhớ.
Ngoài bus dữ liệu 8-bit, CPU còn yêu cầu một bus địa chỉ 16-bit để có thể truy cập lên đến 64K bộ nhớ. Từ những gì bạn đã biết cho đến nay, địa chỉ dùng để truy cập bộ nhớ có thể đến từ ba nguồn khác nhau:
Một giá trị gọi là bộ đếm chương trình (program counter). Đây là giá trị 16-bit dùng để truy cập các lệnh. Nó bắt đầu từ 0000h và tăng tuần tự cho đến khi gặp lệnh HLT.
2 byte theo sau các mã thao tác STA hoặc LDA. Chúng kết hợp lại tạo thành một địa chỉ 16-bit.
Các thanh ghi H và L tạo thành một địa chỉ 16-bit—ví dụ, trong lệnh MOV A,M. Khi được sử dụng theo cách này, HL được gọi là một cặp thanh ghi (register pair).
Trong Bộ Tích Lũy Ba-Byte, bộ nhớ được truy cập tuần tự bằng một bộ đếm 16-bit. Tôi sẽ không sử dụng bộ đếm cho CPU mà tôi đang xây dựng. Như bạn sẽ thấy ở Chương 24, một vài lệnh có thể đặt bộ đếm chương trình tới một địa chỉ khác. Vì lý do đó, bộ đếm chương trình sẽ là một chốt 16-bit mà giá trị của nó, nhìn chung, sẽ được tăng thêm một sau khi một byte lệnh được truy xuất từ bộ nhớ.
Đây là toàn bộ bộ đếm chương trình:
Bộ đếm chương trình
Hãy lưu ý rằng các đường dẫn dữ liệu này rộng hơn rõ rệt so với những đường dẫn trong các sơ đồ trước đó bởi vì chúng đại diện cho các giá trị 16-bit. Cả đầu vào ở trên và đầu ra ở dưới đều được kết nối với bus địa chỉ. Bất kỳ giá trị nào trên bus địa chỉ đều có thể được lưu vào chốt bằng đầu vào Clock, và giá trị trong chốt có thể được đưa lên bus địa chỉ bằng cách sử dụng tín hiệu Enable.
Đối với lệnh STA hay LDA, 2 byte theo sau mã thao tác sẽ được lưu trong Chốt Lệnh 2 và 3. Điều này có nghĩa là hai chốt này cũng phải được kết nối với bus địa chỉ thông qua một bộ đệm ba trạng thái.
Với lệnh MOV liên quan đến địa chỉ bộ nhớ với cặp thanh ghi HL, các giá trị từ chốt H và L cũng phải được kết nối sang bus địa chỉ. Khi xây dựng mảng thanh ghi, tôi hoàn toàn không nghĩ tới yêu cầu này. Trong thiết kế mảng thanh ghi của tôi, các thanh ghi H và L chỉ mới tiếp xúc với bus dữ liệu mà thôi.
Ngoài ra, tôi muốn giới thiệu thêm hai lệnh nữa:
Lệnh | Mô tả | Opcode
-------+-----------------------------------+-------
INX HL | Tăng giá trị của cặp thanh ghi HL | 23h
-------+-----------------------------------+-------
DCX HL | Giảm giá trị của cặp thanh ghi HL | 2Bh
Lệnh INX cộng thêm 1 vào giá trị 16-bit trong cặp thanh ghi HL. Còn lệnh DCX thực hiện việc giảm bằng cách trừ 1 khỏi giá trị đó.
Những lệnh này hóa ra lại rất hữu ích, đặc biệt là lệnh INX. Ví dụ, giả sử có 5 byte được lưu tuần tự trong bộ nhớ bắt đầu từ địa chỉ 1000h, và bạn muốn cộng chúng lại. Bạn chỉ cần thiết lập thanh ghi H và L một lần và sau đó tăng giá trị đó lên sau khi truy cập mỗi byte:
Bạn chắc hẳn còn nhớ ALU có một số cờ để cho biết khi nào kết quả là 0, hay nếu nó bị âm, hoặc nếu có nhớ sinh ra từ phép cộng hoặc trừ. Không có cờ nào bị ảnh hưởng bởi các lệnh INX và DCX.
(Intel 8080 cũng triển khai các lệnh INX và DCX cho các cặp thanh ghi BC và DE, nhưng chúng ít hữu dụng hơn, và tôi sẽ không đưa chúng vào CPU của mình. Intel 8080 còn triển khai các phép toán tăng giảm 8-bit, viết tắt là INR và DCR, cho tất cả bảy thanh ghi và bộ nhớ được định địa chỉ bởi HL, nhưng tôi cũng sẽ không triển khai những lệnh đó.)
Các lệnh INX và DCX ngầm chỉ ra rằng ta cần thêm một số mạch điện để thực hiện việc tăng và giảm 16-bit.
Trước đó tôi đã đề cập rằng bộ đếm chương trình—chốt dùng để lưu giá trị 16-bit nhằm truy cập các lệnh từ bộ nhớ—cũng phải được tăng lên sau khi mỗi byte lệnh được đọc từ bộ nhớ. Một bộ tăng 16-bit cũng cần thiết ở đây.
Một mạch tăng và giảm thì đơn giản hơn một chút so với bộ cộng và bộ trừ vì nó chỉ phải cộng và trừ số 1. Đây là phiên bản 8-bit chỉ để bạn hình dung xem nó trông như thế nào. Các chữ I có chỉ số dưới là đầu vào; các chữ O có chỉ số dưới là đầu ra. Tín hiệu đầu vào Dec được gán là 0 để tăng giá trị, và 1 để giảm giá trị:
Mạch tăng giảm
Hãy gom tổ hợp các cổng XOR và AND này vào một linh kiện lớn hơn, bao gồm một chốt 16-bit để lưu giá trị cần được tăng hoặc giảm đi 1, và một bộ đệm ba trạng thái để cung cấp giá trị đã được tăng hoặc giảm:
Bộ tăng/giảm
Hãy gọi linh kiện này là bộ tăng/giảm (incrementer-decrementer). Giống như bộ đếm chương trình, đầu vào 16-bit của chốt và đầu ra 16-bit từ bộ đệm ba trạng thái đều được kết nối với bus địa chỉ. Cả tín hiệu đầu vào Decrement (Giảm) hoặc Increment (Tăng) đều có thể kích hoạt bộ đệm ba trạng thái, nhưng tín hiệu Decrement thì giảm giá trị trong chốt, trong khi tín hiệu Increment thì tăng nó lên.
Bus địa chỉ 16-bit chủ yếu dùng để cung cấp địa chỉ cho RAM, nhưng nó cũng phải có khả năng di chuyển các giá trị 16-bit giữa các linh kiện của CPU.
Ví dụ, bộ đếm chương trình được sử dụng để định địa chỉ RAM khi truy cập các lệnh từ bộ nhớ. Sau khi giá trị trong bộ đếm chương trình truy cập một lệnh, giá trị đó phải được chuyển vào bộ tăng/giảm để được tăng lên, và sau đó lưu ngược trở lại vào chốt của bộ đếm chương trình.
Thêm một ví dụ khác: Lệnh MOV A,M sử dụng cặp thanh ghi HL để truy cập một byte từ bộ nhớ. Nhưng thường thì ngay sau đó là một lệnh INX HL, di chuyển giá trị của HL vào bộ tăng/giảm để tăng lên 1, sau đó lưu ngược lại vào cặp thanh ghi HL.
Cuối cùng, vấn đề mà tôi cứ lảng tránh nãy giờ đã đến lúc không thể tránh được nữa. Mảng thanh ghi mà tôi đã cho bạn xem ở trên rất gọn gàng và thanh lịch, nhưng nó lại không cung cấp cách nào để đưa các giá trị của H và L lên bus địa chỉ 16-bit. Đây là bản sửa lỗi cho vấn đề đó:
Đưa giá trị H, L lên bus địa chỉ
Lưu ý các đầu vào và đầu ra 16-bit ở tận cùng bên phải. Cả hai sẽ được kết nối với bus địa chỉ để cho phép lưu một giá trị 16-bit vào các thanh ghi H và L và truy xuất nó.
Sơ đồ nâng cấp các thanh ghi H và L đó cho thấy một vài tín hiệu và linh kiện mới đã được thêm vào mảng thanh ghi:
Tín hiệu HL Select ở bên phải điều khiển hàng trên cùng của các bộ đệm ba trạng thái mới. Tín hiệu này quyết định xem đầu vào của các thanh ghi H và L đến từ đầu vào bình thường của mảng thanh ghi hay từ đầu vào 16-bit.
Tín hiệu HL Clock ở bên phải đi vào hai cổng OR đã được thêm vào các đầu vào Clock của các chốt để cho phép lưu giá trị từ bus địa chỉ.
Tín hiệu HL Enable ở bên phải kích hoạt một bộ đệm ba trạng thái 16-bit mới, nhờ đó đầu ra tổng hợp của các chốt H và L có thể xuất hiện trên bus địa chỉ.
Một mớ bòng bong! Nhưng có ai bảo là tạo ra máy tính là dễ đâu.
Một phiên bản tương tác của mảng thanh ghi hoàn chỉnh đã có sẵn trên trang web CodeHiddenLanguage.com.
Giờ thì hãy kết nối các linh kiện 16-bit này vào một bus địa chỉ 16-bit. Sơ đồ khối sau đây cho thấy một phần của bus dữ liệu ở trên cùng đóng vai trò là đầu vào cho Chốt Lệnh 2 và 3, đầu ra của chúng được kết hợp trong một bộ đệm ba trạng thái kết nối với bus địa chỉ:
Sơ đồ khối bus địa chỉ
Chính cái bus địa chỉ 16-bit với kích thước rộng hơn rõ rệt này đi vòng quanh các linh kiện ở dưới cùng, vừa làm đầu vào vừa làm đầu ra, và nó cũng cung cấp địa chỉ cho RAM.
Giống như sơ đồ khối của bus dữ liệu trước đó, sơ đồ khối của bus địa chỉ này đang thiếu một vài tín hiệu cốt lõi: các tín hiệu để kích hoạt các bộ đệm ba trạng thái, và các tín hiệu để lưu giá trị trên bus này vào các chốt khác nhau.
Chính những tín hiệu này—khi được điều phối và đồng bộ hóa hợp lý—sẽ khiến các lệnh được CPU thực thi. Chính những tín hiệu này là mạch đập bên trong máy tính, và chúng hoàn toàn xứng đáng có một chương riêng. Đó sẽ là nội dung tiếp theo.