Mỗi sáng khi ta thức dậy, trí nhớ lấp đầy những khoảng trống. Ta nhớ ra mình đang ở đâu, hôm qua đã làm gì và dự định hôm nay sẽ ra sao. Ký ức có thể ùa về ồ ạt hoặc nhỏ giọt từng chút và có lẽ sau vài phút vẫn còn đâu đó những khoảng không ký ức ("Hài thật, mình mang tất đi ngủ lúc nào vậy ta"), nhưng nhìn chung, ta thường có thể lắp được các mảnh ghép cuộc đời và đạt đủ sự liền mạch để bắt đầu một ngày mới.
Tất nhiên, trí nhớ của con người không trật tự lắm đâu. Cứ thử nhớ lại chút gì đó về môn hình học cấp ba xem, có khi bạn lại bắt đầu nhớ tới cái ngày có diễn tập báo cháy đúng lúc thầy giáo đang chuẩn bị giải thích QED nghĩa là gì.
Trí nhớ cũng chẳng hề hoàn hảo. Thực ra, chữ viết có lẽ đã được phát minh ra chỉ để bù đắp cho sự yếu kém của nó.
Ta viết để sau này đọc. Ta lưu để sau này lấy. Ta cất trữ để sau này truy cập. Chức năng của bộ nhớ là giữ cho thông tin nguyên vẹn giữa hai sự kiện đó. Bất cứ khi nào lưu trữ thông tin, ta đang tận dụng các loại bộ nhớ khác nhau. Chỉ nội trong thế kỷ trước, các phương tiện lưu trữ thông tin đã bao gồm giấy, đĩa nhựa, băng từ, cũng như vô số loại bộ nhớ máy tính khác.
Ngay cả những chiếc rơ-le điện tín—khi được lắp ráp thành cổng logic và sau đó là flip-flop—cũng có thể lưu trữ thông tin. Như chúng ta đã thấy, một flip-flop có khả năng lưu trữ 1 bit. Nhiêu đó không phải gì nhiều nhặn nhưng vạn sự khởi đầu nan và gian nan bắt đầu nản mà. Bởi vì một khi đã biết cách lưu 1 bit, ta có thể dễ dàng lưu 2, 3, hay nhiều bit hơn.
Ở trang 224 của Chương 17, bạn đã bắt gặp flip-flop kiểu D kích mức (level-triggered D-type flip-flop), được làm từ một bộ đảo, hai cổng AND, và hai cổng NOR:
Flip-flop D kích mức
Khi đầu vào Clock là 1, đầu ra Q giống hệt đầu vào Data. Nhưng khi đầu vào Clock chuyển về 0, đầu ra Q sẽ giữ lại giá trị cuối cùng của đầu vào Data. Những thay đổi sau đó của đầu vào Data sẽ chẳng hề hấn gì đến các đầu ra cho đến khi đầu vào Clock trở lại mức 1.
Trong Chương 17, flip-flop này đã góp mặt trong một vài mạch điện khác nhau, nhưng trong chương này nó nhìn chung sẽ chỉ được dùng theo một cách duy nhất—để lưu trữ 1 bit thông tin. Vì lý do đó, tôi sẽ đổi tên các đầu vào và đầu ra để chúng nghe ăn nhập hơn với mục đích này ha:
Flip-flop D kích mức đổi tên
Vẫn là chiếc flip-flop đó thôi, nhưng giờ đầu ra Q được đổi tên thành Data Out (Dữ liệu ra), và đầu vào Clock (thứ từng được gọi là Hold That Bit ở Chương 17) giờ mang tên Write (Ghi). Giống như khi ta ghi lại vài thông tin ra giấy, tín hiệu Write khiến tín hiệu Data In được "ghi" vào, hay được lưu lại, trong mạch. Thông thường, đầu vào Write là 0, và tín hiệu Data In chả có tí tác động nào lên đầu ra cả. Nhưng bất cứ khi nào ta muốn lưu 1 bit dữ liệu vào flip-flop, ta sẽ chuyển đầu vào Write lên 1 rồi lại về 0, như được mô tả trong bảng logic dưới đây, với các đầu vào và đầu ra được viết tắt thành DI, W, và DO:
┌───────────────┬─────────┐
│ Đầu vào │ Đầu ra │
├───────┬───────┼─────────┤
│ DI │ W │ DO │
├───────┼───────┼─────────┤
│ 0 │ 1 │ 0 │
├───────┼───────┼─────────┤
│ 1 │ 1 │ 1 │
├───────┼───────┼─────────┤
│ X │ 0 │ DO │
└───────┴───────┴─────────┘
Như tôi đã đề cập trong Chương 17, loại mạch này còn được gọi là chốt (latch) vì nó "chốt" chặt lấy dữ liệu, nhưng trong chương này mình cứ gọi nó là bộ nhớ (memory) đi. Đây là cách ta có thể biểu diễn 1 bit bộ nhớ mà không cần phải vẽ lại tất tần tật từng linh kiện bên trong:
Bộ nhớ 1 bit
Hoặc nó cũng có thể được xoay dọc thế này nếu bạn thích:
Bộ nhớ 1 bit dọc
Vị trí của các đầu vào và đầu ra không quan trọng lắm đâu.
Đương nhiên, 1 bit bộ nhớ chẳng bõ bèn gì nhưng sẽ khá là dễ để ráp nguyên một byte bộ nhớ bằng cách nối 8 bit bộ nhớ với nhau. Tất cả những gì bạn phải làm là nối 8 tín hiệu Write lại:
Nối 8 bộ nhớ 1 bit
Bộ nhớ 8-bit này có 8 đầu vào và 8 đầu ra, cùng với một đầu vào duy nhất tên là Write thường ở mức 0. Để lưu một byte vào bộ nhớ, hãy chuyển đầu vào Write thành 1 rồi lại về 0. Mạch này cũng có thể được vẽ gọn lại thành một chiếc hộp duy nhất, kiểu thế này:
Hộp bộ nhớ 8 bit
Như thường lệ, các chỉ số dưới giúp phân biệt 8 bit với nhau. Chỉ số 0 biểu thị bit ít quan trọng nhất (least significant bit - nằm ngoài cùng bên phải), và chỉ số 7 là bit quan trọng nhất (most significant bit - nằm ngoài cùng bên trái).
Để đồng bộ hơn với bộ nhớ 1-bit, bộ nhớ 8-bit có thể được biểu diễn bằng cách dùng các đường dẫn dữ liệu (data paths) 8-bit cho cả đầu vào và đầu ra:
Bộ nhớ 8 bit với đường dữ liệu
Có một cách khác để ráp 8 flip-flop nhưng không trực quan bằng cách này. Giả sử ta chỉ muốn có một tín hiệu Data In và một tín hiệu Data Out. Nhưng ta lại muốn có khả năng lưu giá trị của tín hiệu Data In tại 8 thời điểm khác nhau trong ngày, hoặc có thể là 8 thời điểm khác nhau trong phút tới. Và ta cũng muốn sau đó có thể đọc 8 giá trị đó chỉ bằng cách nhìn vào một tín hiệu Data Out.
Nói cách khác, thay vì lưu một giá trị 8-bit, ta muốn lưu 8 giá trị 1-bit riêng biệt.
Lưu trữ 8 giá trị 1-bit riêng biệt đòi hỏi mạch điện phức tạp hơn, nhưng nó lại làm đơn giản hóa bộ nhớ theo những cách khác: Nếu bạn đếm số lượng kết nối cần thiết cho bộ nhớ 8-bit, bạn sẽ thấy có tổng cộng 17 kết nối. Khi 8 giá trị 1-bit riêng biệt được lưu, số lượng kết nối giảm xuống chỉ còn 6.
Cùng xem nó hoạt động thế nào nhé.
Khi lưu 8 giá trị 1-bit, ta vẫn cần 8 flip-flop, nhưng không giống như cấu hình trước đó, tất cả các đầu vào Data giờ được nối chung lại với nhau trong khi các tín hiệu Write lại tách riêng:
Nối chung Data In, tách riêng Write
Mặc dù tất cả các tín hiệu Data In được nối chung, điều này không có nghĩa là tất cả các flip-flop sẽ cùng lưu chung một giá trị Data In. Các tín hiệu Write được tách riêng, nên một flip-flop cụ thể sẽ chỉ lưu giá trị Data In khi tín hiệu Write tương ứng của nó chuyển thành 1. Giá trị mà flip-flop đó lưu chính là giá trị Data In tại thời điểm đó.
Thay vì phải cặm cụi điều khiển 8 tín hiệu Write riêng biệt, ta có thể chỉ cần một tín hiệu Write và dùng một bộ giải mã 3-sang-8 (3-to-8 decoder) để chi phối xem nó sẽ điều khiển flip-flop nào:
Bộ giải mã 3-sang-8
Bạn đã từng thấy các mạch tương tự thế này rồi: Ở trang 114 gần cuối Chương 10, bạn đã thấy một mạch cho phép xác định một số bát phân bằng ba công tắc, trong đó mỗi cổng AND trong số 8 cổng được nối với một bóng đèn. Tùy thuộc vào số bát phân bạn chọn, một (và chỉ một) trong số 8 bóng đèn sẽ sáng lên. Các mạch tương tự ở Chương 18 đóng vai trò quan trọng trong việc hiển thị các chữ số của đồng hồ.
Các tín hiệu S0, S1, và S2 viết tắt cho Select (Chọn). Các đầu vào của mỗi cổng AND bao gồm mỗi tín hiệu Select này hoặc bản đảo ngược của chúng. Bộ giải mã 3-sang-8 này đa năng hơn một chút so với bộ ở Chương 10 vì tín hiệu Write được kết hợp với các đầu vào S0, S1, và S2. Nếu tín hiệu Write là 0, tất cả các cổng AND sẽ có đầu ra là 0. Nếu tín hiệu Write là 1, một và chỉ một cổng AND sẽ có đầu ra là 1 tùy thuộc vào các tín hiệu S0, S1, và S2.
Các tín hiệu Data Out từ 8 flip-flop có thể làm đầu vào cho một mạch gọi là bộ chọn 8-sang-1 (8-to-1 selector), mạch này có tác dụng chọn ra một trong số 8 tín hiệu Data Out từ các flip-flop:
Bộ chọn 8-sang-1
Lần nữa, ba tín hiệu Select và các bản đảo ngược của chúng được đưa vào 8 cổng AND. Dựa trên các tín hiệu S0, S1, và S2, một và chỉ một cổng AND có thể có đầu ra là 1. Nhưng các tín hiệu Data Out từ các flip-flop cũng đồng thời là đầu vào cho 8 cổng AND này. Đầu ra của cổng AND được chọn sẽ chính là tín hiệu Data Out tương ứng từ các flip-flop. Một cổng OR 8-đầu-vào sẽ cung cấp tín hiệu Data Out cuối cùng được chọn lọc từ 8 tín hiệu.
Bộ giải mã 3-sang-8 và bộ chọn 8-sang-1 có thể được kết hợp với 8 flip-flop như thế này:
Kết hợp lại
Hãy để ý rằng ba tín hiệu Select đưa vào bộ giải mã và bộ chọn là y như nhau. Tôi cũng vừa tạo ra một thay đổi quan trọng trong cách dán nhãn các tín hiệu Select. Giờ đây chúng được gắn nhãn là Address (Địa chỉ), vì nó là một con số chỉ định nơi bit này "thường trú" trong bộ nhớ. Nó giống như địa chỉ bưu điện vậy, ngoại trừ việc ở đây chỉ có 8 giá trị địa chỉ 3-bit có thể có: 000, 001, 010, 011, 100, 101, 110, và 111.
Ở phía đầu vào, đầu vào Address sẽ quyết định flip-flop nào mà tín hiệu Write sẽ kích hoạt để lưu đầu vào Data. Ở phía đầu ra (phần dưới cùng của hình), đầu vào Address điều khiển bộ chọn 8-sang-1 để chọn ra đầu ra của một trong số 8 chốt.
Ví dụ ha, cài đặt ba tín hiệu Address thành 010, cài Data In thành 0 hoặc 1, rồi bật Write lên 1 rồi lại về 0. Quá trình đó gọi là ghi vào bộ nhớ (writing to memory), và ta nói rằng giá trị của Data In đã được lưu trong bộ nhớ tại địa chỉ 010.
Đổi ba tín hiệu Address sang một giá trị khác. Giờ sang ngày hôm sau bạn trở lại. Nếu nguồn điện vẫn còn bật, bạn có thể lại thiết lập ba tín hiệu Address về 010, và bạn sẽ thấy Data Out chính là những gì bạn đã cài cho Data In khi bạn ghi nó vào bộ nhớ. Thao tác đó được gọi là đọc từ bộ nhớ (reading from memory) hay truy cập bộ nhớ (accessing memory). Bạn có thể ghi một thứ gì khác vào địa chỉ bộ nhớ đó bằng cách bật tín hiệu Write lên 1 và rồi về 0.
Tại bất kỳ thời điểm nào, bạn cũng có thể thiết lập các tín hiệu Address thành một trong tám giá trị khác nhau, và nhờ vậy bạn có thể lưu 8 giá trị 1-bit khác biệt. Cấu hình này của các flip-flop, bộ giải mã và bộ chọn đôi khi được biết đến là bộ nhớ đọc/ghi (read/write memory) vì bạn có thể lưu trữ các giá trị (tức là ghi chúng) và sau đó xem lại những giá trị đó là gì (tức là đọc chúng). Bởi vì bạn có thể thay đổi tuỳ ý các tín hiệu Address thành bất kỳ giá trị nào trong số tám giá trị, loại bộ nhớ này thường được biết đến với tên gọi phổ biến hơn là bộ nhớ truy cập ngẫu nhiên (random access memory), hay RAM (phát âm giống hệt từ tiếng Anh chỉ con cừu đực).
Không phải bộ nhớ nào cũng là bộ nhớ truy cập ngẫu nhiên đâu! Vào cuối thập niên 1940, trước khi việc chế tạo bộ nhớ từ ống chân không trở nên khả thi và trước khi transistor được phát minh, người ta đã sử dụng các dạng bộ nhớ khác. Một công nghệ khá quái là sử dụng những ống thủy ngân dài để lưu trữ các bit thông tin. Các xung ở một đầu ống lan truyền đến đầu kia giống như những gợn sóng trên mặt hồ, nhưng những xung này phải được đọc một cách tuần tự chứ không phải ngẫu nhiên. Các loại bộ nhớ đường trễ (delay-line memory) khác vẫn được sử dụng cho đến tận thập niên 1960.
Cấu hình RAM cụ thể mà chúng ta vừa chế tạo lưu trữ 8 giá trị 1-bit riêng biệt. Nó có thể được biểu diễn như này:
Mảng RAM
Một cấu hình cụ thể của RAM thường được gọi là một mảng RAM (RAM array). Mảng RAM cụ thể này được tổ chức theo cách thức viết tắt là 8x1 (đọc là tám nhân một). Mỗi giá trị trong tám giá trị của mảng là 1 bit. Bạn có thể tính ra tổng số bit có thể được lưu trữ trong mảng RAM bằng cách nhân hai giá trị đó lại, trong trường hợp này là 8 nhân 1, tức 8 bit.
Hoàn toàn có thể tạo ra các mảng bộ nhớ lớn hơn bằng cách nối các mảng nhỏ lại với nhau. Ví dụ, nếu bạn có 8 mảng RAM 8x1 và bạn nối tất cả các tín hiệu Address với nhau cùng tất cả tín hiệu Write với nhau, bạn có thể tạo thành một mảng RAM 8x8:
Mảng RAM 8x8
Chú ý là các tín hiệu Data In và Data Out giờ đây đều rộng 8 bit. Mảng RAM này lưu trữ 8 byte tách biệt, mỗi byte được tham chiếu bởi một địa chỉ 3-bit.
Tuy nhiên, nếu ta định lắp mảng RAM này từ 8 mảng RAM 8x1, thì tất cả logic giải mã và logic chọn sẽ bị lặp lại thừa thãi. Hơn nữa, hẳn bạn đã nhận ra từ nãy rằng bộ giải mã 3-sang-8 và bộ chọn 8-sang-1 có rất nhiều điểm tương đồng. Cả hai đều dùng 8 cổng AND bốn-đầu-vào, vốn được chọn dựa trên ba tín hiệu Select hoặc Address. Trong một cấu hình bộ nhớ thực tế, bộ giải mã và bộ chọn sẽ dùng chung những cổng AND này.
Cùng xem liệu ta có thể ráp một mảng RAM theo cách hiệu quả hơn đôi chút được không nhé. Thay vì mảng RAM 8x8 chứa 8 byte, hãy nhân đôi bộ nhớ lên thành mảng RAM 16x8 chứa 16 byte. Tựu chung lại, ta sẽ có được một thứ có thể biểu diễn như này:
Mảng RAM 16x8
Địa chỉ cần phải rộng 4 bit để định vị 16 byte bộ nhớ. Tổng số bit có thể được lưu trữ trong mảng RAM này là 16 nhân 8, tức là 64, nghĩa là sẽ cần tới 64 flip-flop riêng biệt. Rõ ràng là sẽ rất khó để nhét trọn vẹn mảng RAM 16x8 vào các trang của cuốn sách này, nên tôi sẽ chia nó ra thành từng phần vậy.
Ở phần trước của chương, bạn đã thấy cách một flip-flop được dùng để lưu 1 bit có thể được ký hiệu bằng một chiếc hộp với các đầu vào Data In, Write và đầu ra Data Out:
Bộ nhớ 1 bit
Một bit bộ nhớ đôi khi được gọi là một ô nhớ (memory cell). Hãy xếp 64 ô nhớ này thành một lưới gồm 8 cột và 16 hàng. Mỗi hàng gồm 8 ô là một byte bộ nhớ. 16 hàng (ở đây chỉ hiển thị ba hàng) tương ứng với 16 byte:
64 ô nhớ
Tạm thời cứ lờ đi phần Data Out đã. Như bạn thấy, với mỗi byte, các tín hiệu Write được nối chung vì toàn bộ một byte sẽ được ghi vào bộ nhớ cùng một lúc. Các tín hiệu Write nối liền này có nhãn nằm bên trái từ W0 đến W15. Chúng tương ứng với 16 địa chỉ có thể có.
Các tín hiệu Data In được nối theo một cách khác. Đối với mỗi hàng, bit quan trọng nhất của byte nằm ở bên trái, và bit ít quan trọng nhất nằm ở bên phải. Các bit tương ứng của mỗi byte được nối chung lại với nhau. Việc mọi byte đều có chung tín hiệu Data In chẳng hề gì, bởi vì byte đó sẽ chỉ được ghi vào bộ nhớ khi tín hiệu Write của nó là 1.
Để ghi vào một trong 16 byte, ta cần một địa chỉ rộng 4 bit vì với 4 bit, ta có thể tạo ra 16 giá trị khác nhau và chọn lấy một trong 16 thứ—những thứ đó chính là các byte được lưu trong bộ nhớ. Như hình minh họa ở trên, đầu vào Address của mảng RAM 16x8 thực sự rộng 4 bit, nhưng ta cần một cách để chuyển đổi địa chỉ đó thành tín hiệu Write thích hợp. Đó chính là lý do bộ giải mã 4-sang-16 (4-to-16 decoder) ra đời:
Bộ giải mã 4-sang-16
Đây là bộ giải mã phức tạp nhất mà bạn sẽ gặp trong cuốn sách này! Mỗi cổng AND trong số 16 cổng có bốn đầu vào, tương ứng với bốn tín hiệu Address và các bản đảo ngược của chúng. Tôi đã đánh số đầu ra của các cổng AND này tương ứng với các giá trị của bốn bit địa chỉ.
Bộ giải mã này giúp tạo ra các tín hiệu Write cho 16 byte của mảng RAM 16x8: Mỗi đầu ra của các cổng AND trong bộ giải mã là một đầu vào cho một cổng AND khác có chứa một tín hiệu Write duy nhất:
Đầu vào cho cổng AND + Write
Đây là các tín hiệu để ghi byte Data In vào bộ nhớ trong hình minh họa (ở phần trước).
Thế là xong phần đầu vào, và tất cả những gì còn lại là các tín hiệu Data Out từ mỗi ô trong số 64 ô nhớ. Tới đây thì khó nè vì mỗi cột trong số tám cột bit phải được xử lý riêng biệt. Ví dụ, đây là một mạch rút gọn xử lý cột ngoài cùng bên trái của mảng RAM 16x8. Nó cho thấy làm thế nào các tín hiệu Data Out của 16 ô nhớ có thể kết hợp với 16 đầu ra của bộ giải mã 4-sang-16 để chỉ chọn ra một trong số các ô nhớ đó:
Mạch xử lý cột ngoài bên trái
16 đầu ra từ bộ giải mã 4-sang-16 được hiển thị ở bên trái. Mỗi đầu ra này là một đầu vào cho một cổng AND. Đầu vào còn lại là Data Out từ một trong 16 ô nhớ thuộc cột đầu tiên trong hình mà ta đã thấy. Đầu ra của 16 cổng AND đó đi vào một cổng OR 16-đầu-vào khổng lồ. Kết quả là DO7, tức bit quan trọng nhất của byte Data Out.
Phần tệ nhất của mạch này là nó cần phải được nhân bản lên cho mỗi bit trong số 8 bit của một byte!
May là còn cách khác ngon ăn hơn.
Bất cứ lúc nào, cũng chỉ có một trong 16 đầu ra của bộ giải mã 4-sang-16 có giá trị 1, tức là ngoài đời nó là một điện áp. Phần còn lại sẽ có đầu ra 0, biểu thị sự nối đất. Nhờ đó, sẽ chỉ có một cổng AND có đầu ra là 1—và cũng chỉ khi Data Out của ô nhớ cụ thể đó là 1—và tất cả phần còn lại sẽ có đầu ra là 0. Lý do duy nhất cho sự tồn tại của cái cổng OR khổng lồ kia là để phát hiện xem có bất kỳ đầu vào nào của nó là 1 hay không.
Chúng ta có thể tống khứ cổng OR khổng lồ đó đi nếu có thể nối chùm tất cả các đầu ra của các cổng AND lại với nhau. Nhưng thông thường, việc trực tiếp nối các đầu ra của cổng logic là không được phép vì điện áp có thể bị kết nối trực tiếp với dây nối đất, và bùm, thế là đoản mạch. Nhưng có một cách để làm được đó là dùng một transistor, như thế này:
Dùng transistor
Nếu tín hiệu từ bộ giải mã 4-sang-16 là 1, thì tín hiệu Data Out từ cực phát (emitter) của transistor sẽ y như tín hiệu DO (Data Out) từ ô nhớ—có thể là một điện áp hoặc một cực đất. Nhưng nếu tín hiệu từ bộ giải mã 4-sang-16 là 0, thì transistor chẳng cho thứ gì lọt qua cả và tín hiệu Data Out từ cực phát của transistor trống không—chẳng phải điện áp cũng chẳng phải cực đất. Điều này có nghĩa là tất cả các tín hiệu Data Out từ một hàng các transistor này có thể được nối với nhau mà không gây ra hiện tượng đoản mạch.
Đây là mảng nhớ rút gọn một lần nữa, nhưng chỉ hiển thị các kết nối Data Out. Các đầu ra của bộ giải mã 4-sang-16 nằm ở bên trái, và các tín hiệu Data Out hoàn chỉnh nằm ở dưới cùng. Không xuất hiện ở đây là những điện trở nhỏ tại các tín hiệu Data Out đó nhằm đảm bảo chúng chỉ có thể là 1 hoặc 0:
Mảng RAM 16x8 hoàn chỉnh
Mảng RAM 16x8 hoàn chỉnh đã được cập nhật trên CodeHiddenLanguage.com.
Những transistor này là nền tảng của một mạch điện được gọi là bộ đệm ba trạng thái (tri-state buffer). Một bộ đệm ba trạng thái có thể có một trong ba đầu ra: nối đất (thể hiện mức logic 0); điện áp (thể hiện mức logic 1); hoặc không có gì—chẳng nối đất cũng chẳng điện áp, hệt như thể nó không được nối với gì cả.
Một bộ đệm ba trạng thái đơn lẻ được ký hiệu như thế này:
Bộ đệm 3 trạng thái
Trông nó giống hệt một bộ đệm (buffer) nhưng lại có thêm một tín hiệu Enable (Kích hoạt). Nếu tín hiệu Enable đó là 1, thì Output sẽ giống hệt Input. Nếu không, Output được cho là đang "lơ lửng" (float) như thể nó không được nối với bất kỳ cái gì.
Bộ đệm ba trạng thái cho phép ta phá luật cấm kết nối các đầu ra của cổng logic. Đầu ra của vô số bộ đệm ba trạng thái có thể được kết nối với nhau mà chẳng lo đoản mạch—chỉ cần đảm bảo tại bất kỳ thời điểm nào cũng chỉ có một trong số chúng được kích hoạt là được.
Thường thì bộ đệm ba trạng thái sẽ phát huy tác dụng hơn nếu được đóng gói để xử lý nguyên một byte bằng một tín hiệu Enable duy nhất:
Xử lý 1 byte
Tôi sẽ ký hiệu cấu hình của các bộ đệm ba trạng thái trên bằng một hộp như này:
Hộp đệm 3 trạng thái
Ở các sơ đồ sau này, nếu không có đủ chỗ để ghi đầy đủ tên của hộp, tôi sẽ chỉ dùng "Tri-State" hoặc "TRI" thôi nhé.
Bạn đã thấy cách các bộ đệm ba trạng thái giúp chọn ra 1 trong số 16 byte bên trong mảng nhớ 16x8. Giờ tôi cũng muốn mảng nhớ 16x8 này có một đầu vào Enable cho riêng nó:
Mảng nhớ 16x8
Nếu tín hiệu Enable đó là 1, thì các tín hiệu Data Out sẽ biểu diễn byte được lưu tại địa chỉ được chỉ định. Nếu tín hiệu Enable là 0, Data Out sẽ trống không.
Bây giờ khi đã chế xong một mạch điện chứa được 16 byte, hãy nhân đôi nó lên. À không, nhân bốn luôn đi. Ấy không không, nhân tám luôn cho máu. Khoan, đã máu đừng hỏi bố cháu là ai, là Mười Sáu. Đúng, nhân 16!
Để làm được chuyện động trời này, bạn sẽ cần 16 mảng nhớ 16x8, được nối dây như thế này:
16 mảng nhớ 16x8
Chỉ có ba trong số 16 mảng RAM được hiển thị. Chúng dùng chung các đầu vào Data In. Còn Data Out của 16 mảng RAM được nối với nhau một cách an toàn vì các đầu ra dùng bộ đệm ba trạng thái. Chú ý hai nhóm địa chỉ 4-bit nhé: Các bit địa chỉ từ A0 đến A3 trỏ tới toàn bộ 16 mảng RAM trong khi các bit địa chỉ từ A4 đến A7 cung cấp đầu vào Select cho một bộ giải mã 4-sang-16. Thứ này được dùng để điều khiển mảng RAM nào trong 16 mảng nhận tín hiệu Write và mảng nào nhận tín hiệu Enable.
Tổng dung lượng bộ nhớ đã được tăng lên gấp 16 lần, nghĩa là ta có thể lưu 256 byte, và ta có thể nhét cái mạch này vào một cái hộp khác với nhãn sau:
Hộp RAM 256x8
Để ý rằng địa chỉ giờ đây rộng 8 bit. Một mảng RAM lưu trữ 256 byte giống như một bưu điện có 256 hòm thư vậy. Mỗi hòm chứa một giá trị 1-byte khác nhau bên trong (cũng chưa biết được có xịn hơn thư rác không).
Làm lại phát nữa nào! Hãy lấy 16 cái mảng RAM 256x8 này rồi dùng một bộ giải mã 4-sang-16 khác để chọn chúng với 4 bit địa chỉ nữa. Dung lượng bộ nhớ tăng vọt lên 16 lần, tổng cộng là 4096 byte. Quả ngọt đây rồi:
Hộp RAM 4096x8
Địa chỉ giờ đã rộng 12 bit.
Làm thêm cú nữa nào. Ta sẽ cần 16 mảng RAM 4096x8 này và một bộ giải mã 4-sang-1. Địa chỉ phình to lên thành 16 bit, và sức chứa của bộ nhớ giờ đã là 65.536 byte:
Hộp RAM 65.536x8
Bạn cứ thoải mái đi tiếp nếu muốn, nhưng tôi thì xin phép dừng lại ở đây.
Có lẽ bạn đã chú ý thấy số lượng giá trị mà một mảng RAM lưu trữ liên quan trực tiếp đến số lượng đầu vào Address. Nếu không có đầu vào Address nào, chỉ một giá trị duy nhất được lưu. Với 4 bit địa chỉ, 16 giá trị được cất giữ, và với 16 bit địa chỉ, ta có tận 65.536 giá trị. Mối quan hệ này được tóm gọn trong một công thức sau:
Số lượng giá trị trong mảng RAM = 2^(Số lượng đầu vào Address)
RAM lưu trữ 65.536 byte cũng được coi là lưu trữ 64 kilobyte, hoặc 64K, hay 64KB, mới thấy lần đầu có thể khiến bạn gãi đầu gãi tai. Thứ ma thuật toán học quái đản nào lại biến 65.536 thành 64 kilobyte?
Giá trị 2^10 là 1024, cũng là giá trị thường được biết tới như một kilobyte. Tiền tố kilo (từ chữ khilioi của Hy Lạp, nghĩa là một ngàn) thường được dùng trong hệ mét. Ví dụ, một kilogram là 1000 gram, và một kilometer là 1000 mét. Nhưng ở đây tôi đang nói là một kilobyte lại bằng 1024 byte—chứ không phải 1000 byte đâu nhé.
Rắc rối nằm ở chỗ hệ mét dựa trên các lũy thừa của 10, còn hệ nhị phân thì bám rễ vào lũy thừa của 2, và đôi bên cứ thế như hai đường thẳng song song chẳng bao giờ gặp. Lũy thừa của 10 là 10, 100, 1000, 10000, 100000, vân vân và mây mây. Lũy thừa của 2 là 2, 4, 8, 16, 32, 64, đại loại thế. Chẳng hề có một lũy thừa nguyên nào của 10 lại bằng với một lũy thừa nguyên của 2 cả.
Nhưng thi thoảng, chúng cũng xích lại khá gần nhau. Đúng vậy, 1000 thực sự khá sát với 1024, hay để nói theo phong cách hàn lâm toán học bằng cách dùng dấu "xấp xỉ bằng":
2^10 ≅ 10^3
Chẳng có chút phép thuật nào trong mối quan hệ này cả. Tất cả những gì nó ám chỉ là một lũy thừa cụ thể của 2 thì xấp xỉ bằng với một lũy thừa cụ thể của 10. Cái sự trùng hợp nhỏ xíu này cho phép mọi người thuận miệng gọi là một kilobyte bộ nhớ trong khi thứ họ thực sự ám chỉ là 1024 byte.
Thứ mà bạn đừng dại mà nói ra là một mảng RAM 64K chứa được 64 ngàn byte. Nó chứa nhiều hơn 64 ngàn—nó là 65.536 cơ. Để ra vẻ là người am hiểu ngọn ngành, bạn hãy nói là "64K" hoặc "64 kilobyte" hoặc "sáu lăm ngàn năm trăm ba mươi sáu."
Mỗi một bit địa chỉ thêm vào sẽ nhân đôi dung lượng bộ nhớ. Mỗi dòng của chuỗi sau đây thể hiện sự nhân lên đó:
Lưu ý rằng những con số chỉ kilobyte hiện diện ở phía bên trái cũng chính là các lũy thừa của 2.
Với chung một đường dây logic cho phép ta gọi 1024 byte là một kilobyte, ta cũng có thể gọi 1024 kilobyte là một megabyte. (Chữ megas trong tiếng Hy Lạp mang nghĩa là vĩ đại.) Megabyte được viết tắt là MB. Và công cuộc nhân đôi bộ nhớ lại cứ thế tiếp diễn:
Từ gigas của người Hy Lạp có nghĩa là khổng lồ, thế nên 1024 megabyte sẽ được gọi là một gigabyte, viết tắt là GB.
Tương tự như vậy, một terabyte (teras nghĩa là quái vật) bằng 2^40 byte (xấp xỉ 10^12), hay 1.099.511.627.776 byte. Terabyte được viết tắt là TB.
Một kilobyte xấp xỉ khoảng một nghìn byte, một megabyte xấp xỉ một triệu byte, một gigabyte xấp xỉ một tỷ byte, và một terabyte xấp xỉ một nghìn tỷ byte.
Hít một hơi và vào cảnh giới hiếm người bước tới, một petabyte bằng 2^50 byte, hay 1.125.899.906.842.624 byte, xấp xỉ 10^15, tức một triệu tỷ (quadrillion). Còn một exabyte thì bằng 2^60 byte, hay 1.152.921.504.606.846.976 byte, xấp xỉ 10^18, tức một tỷ tỷ (quintillion).
Cung cấp cho bạn một chút bối cảnh thực tế nhé, những chiếc máy tính để bàn được mua vào thời điểm xuất bản ấn bản đầu tiên của cuốn sách này, năm 1999, thường sở hữu 32 MB hoặc 64 MB, hay đôi khi là 128 MB bộ nhớ truy cập ngẫu nhiên. Tính đến thời điểm ấn bản thứ hai này được chắp bút, năm 2021, máy tính để bàn thường nghiễm nhiên được trang bị 4, 8, hoặc 16 GB RAM. (Khoan vội rối trí—tôi vẫn chưa hề đả động tới các loại bộ nhớ lưu trữ có thể giữ lại dữ liệu khi ngắt điện đâu, chẳng hạn như ổ cứng (hard drives) hay ổ đĩa thể rắn [SSD]; tôi ở đây chỉ đang thuần túy bàn về RAM mà thôi.)
Mọi người, dĩ nhiên rồi, luôn thích nói tắt cho nhanh. Một người đang sở hữu 65.536 byte bộ nhớ sẽ vỗ ngực bảo, "Tao có tận 64K (và tao là một vị khách du hành đến từ năm 1980 đây)." Ai đó nắm trong tay 33.554.432 byte thì sẽ nói, "Còn tao có 32 meg." Và những dân chơi có tới 8.589.934.592 byte bộ nhớ sẽ nói, "Em có 8 gig (và em chả nói về dung lượng tải nhạc đâu nhé)."
Thỉnh thoảng mọi người sẽ nhắc tới kilobit hoặc megabit (lưu ý là bit chứ không phải byte nhé), nhưng rất hiếm khi họ dùng nó để chém gió về bộ nhớ. Gần như lúc nào hễ bàn về bộ nhớ là người ta đang nói tới số lượng byte, chứ không phải bit. Thường thì khi kilobit hay megabit lọt vào câu chuyện, nó sẽ liên quan đến dữ liệu đang vi vu truyền tải qua dây cáp hay qua không khí, phần lớn là dính líu đến các kết nối internet tốc độ cao được gọi là "băng thông rộng" (broadband), và sẽ xuất hiện trong mấy cụm từ như "kilobit trên giây" hay "megabit trên giây".
Giờ thì bạn đã rành cách lắp ráp RAM thành bất kỳ kích thước mảng nào bạn muốn (ít nhất là trong trí tưởng tượng), nhưng tôi đã dừng lại ở mức 65.536 byte bộ nhớ.
Tại sao lại là 64 KB? Sao không phải là 32 KB hay 128 KB? Là bởi 65.536 là một số tròn trĩnh đáng yêu. Nó chính là 2^16. Mảng RAM này có một địa chỉ 16-bit—chính xác là 2 byte. Ở dạng thập lục phân, địa chỉ sẽ trải dài từ 0000h đến FFFFh.
Như tôi đã ẩn ý ở trên, 64 KB là mức dung lượng bộ nhớ quen mặt trên mấy chiếc máy tính cá nhân được tậu về vào khoảng năm 1980, nhưng nó không hoàn toàn giống với những gì tôi đã biểu diễn ở đây. Bộ nhớ được lắp ráp từ các flip-flop chính xác hơn phải gọi là bộ nhớ chỉ-đọc tĩnh (static read-only memory). Tới khoảng năm 1980, RAM động (dynamic RAM), hay DRAM, bắt đầu lên ngôi và nhanh chóng thống trị sân chơi. DRAM chỉ đòi hỏi một transistor và một tụ điện (capacitor) cho mỗi ô nhớ. Tụ điện là một linh kiện được dùng trong điện tử chứa hai vật dẫn điện tách biệt nhau. Tụ điện có thể lưu trữ điện tích, nhưng không phải là mãi mãi. Chìa khóa để làm cho DRAM hoạt động được là những điện tích này phải được làm mới (refreshed) hàng ngàn lần mỗi giây.
Cả RAM tĩnh và RAM động đều được gọi là bộ nhớ dễ bay hơi (volatile memory). Một nguồn điện liên tục là điều bắt buộc để nó có thể giữ dữ liệu. Khi bị cắt điện, bộ nhớ dễ bay hơi sẽ lập tức quên sạch sành sanh mọi thứ nó từng biết.
Sẽ thật là lợi thế cho ta nếu có một bảng điều khiển cho phép ta quản lý trọn vẹn 64KB bộ nhớ này—để có thể ghi các giá trị vào bộ nhớ hay là lôi chúng ra xem xét. Bảng điều khiển như vậy có 16 công tắc để khai báo địa chỉ, 8 công tắc để xác định một giá trị 8-bit mà ta muốn ghi vào bộ nhớ, một công tắc khác dành riêng cho tín hiệu Write, và 8 bóng đèn để hiển thị một giá trị 8-bit cụ thể:
Bảng điều khiển RAM 64KB
Mọi công tắc đều đang được hiển thị ở vị trí tắt (0). Tôi cũng thêm vào một công tắc dán nhãn Takeover (Tiếp quản). Mục đích của công tắc này là cho phép các mạch điện khác được dùng chung bộ nhớ mà bảng điều khiển đang kết nối. Khi công tắc này được đặt ở mức 0 (như trong hình), toàn bộ mớ công tắc còn lại trên bảng điều khiển đều vô dụng. Nhưng khi công tắc Takeover được bật lên 1, bảng điều khiển sẽ nắm trọn quyền điều khiển độc quyền với bộ nhớ.
Triển khai cái công tắc Takeover đó là nhiệm vụ của một nhóm các bộ chọn 2-sang-1 (2-to-1 selectors), nhìn khá đơn giản nếu đem ra so với những bộ giải mã và bộ chọn cỡ bự trong chương này:
Takeover
Khi tín hiệu Select là 0, đầu ra của cổng OR sẽ rập khuôn y đúc đầu vào A. Còn khi tín hiệu Select là 1, đầu vào B sẽ là kẻ được chọn.
Ta cần 26 bộ chọn 2-sang-1 này—16 cho các tín hiệu Address, 8 cho các công tắc đầu vào Data, và 2 cái nữa chia nhau cho công tắc Write và tín hiệu Enable. Toàn bộ mạch điện trông như này:
Toàn bộ mạch
Khi công tắc Takeover mở, các đầu vào Address, Data, Write, và Enable đi vào mảng RAM 64K x 8 sẽ xuất phát từ những tín hiệu bên ngoài được hiển thị ở góc trên bên trái của các bộ chọn 2-sang-1. Khi công tắc Takeover đóng, các tín hiệu Address, đầu vào Data, và Write vào mảng RAM sẽ được lấy từ các công tắc trên bảng điều khiển, và Enable sẽ được đặt là 1. Dù ở trường hợp nào thì các tín hiệu Data Out từ mảng RAM cũng sẽ quay lại 8 bóng đèn trên bảng điều khiển và có thể ghé một vài nơi nào đó khác.
Khi công tắc Takeover đóng, bạn có thể tự do dùng 16 công tắc Address để chấm bất kỳ địa chỉ nào trong số 65.536 địa chỉ. Các bóng đèn sẽ cho bạn biết giá trị 8-bit hiện đang được cất giữ trong bộ nhớ tại địa chỉ đó. Bạn có thể dùng 8 công tắc Data để định nghĩa một giá trị mới, và ghi luôn giá trị đó vào bộ nhớ bằng cách dùng công tắc Write.
Mảng RAM 64K x 8 cùng với bảng điều khiển chắc chắn có thể kề vai sát cánh giúp bạn để mắt tới bất kỳ giá trị 8-bit nào trong số 65.536 giá trị mà bạn cần có sẵn. Thế nhưng ta cũng hé mở một cánh cửa cơ hội khác cho một thứ gì đó—có lẽ là một vài mạch điện—nhảy vào sử dụng các giá trị được lưu trữ trong bộ nhớ và ghi đè những giá trị mới vào trong đó luôn.
Nếu bạn cho rằng kịch bản này nghe sặc mùi hư cấu, có lẽ bạn sẽ muốn ngó qua ảnh bìa ấn bản tháng 1 năm 1975 lừng danh của tạp chí Popular Electronics, trong đó có một bài viết về chiếc máy tính gia đình đầu tiên, Altair 8800:
Bìa tạp chí
Mặt trước của chiếc máy tính này là một bảng điều khiển chẳng có gì ngoài công tắc và đèn đóm, và nếu bạn chịu khó đếm dải công tắc dài ngoằng ở phía dưới cùng, bạn sẽ phát hiện ra rằng có đúng 16 công tắc tất cả.
Trùng hợp ư? Chưa chắc đâu.
0
Open to Work
Looking for a Fullstack Ruby on Rails developer?
I am open to new remote/hybrid opportunities and contract work.