Mailbox: backpressure, FIFO và identity của transaction được gửi
Tiên quyết
Sau bài này bạn sẽ
- Phân biệt bounded put/get với các try method và peek
- Kiểm thứ tự message và thời điểm producer thoát chặn
- Phân biệt gửi class handle với snapshot object độc lập
Đã gửi handle chưa có nghĩa đã lưu bản chụp dữ liệu
Producer đưa một transaction vào mailbox rồi sửa object để tái sử dụng. Consumer có thể nhận handle trỏ đúng object đó nhưng thấy dữ liệu mới, không phải dữ liệu lúc put. Một FIFO đúng thứ tự vẫn có thể truyền một lịch sử sai nếu ownership của object không rõ.
Bài dùng hai pha: mailbox int có bound 2 để kiểm backpressure/FIFO, rồi mailbox Packet để kiểm identity và một snapshot tạo object riêng. Cần hiểu cả đồng bộ lẫn giá trị thực sự đi qua kênh; không ghép chúng thành một lời hứa “mailbox tự copy transaction”.
Mailbox object và kiểu message là hai thông tin khác
mailbox #(int) channel=new(2) tạo một object mailbox chứa int, tối đa hai message đang chờ. Hai handle cùng trỏ channel dùng chung queue nội bộ. Bound 0 nghĩa không đặt giới hạn message theo cơ chế bounded mailbox, không có nghĩa queue luôn rỗng.
Type parameter giúp kiểm việc đưa/nhận kiểu dữ liệu phù hợp. Nó không đặt ownership cho class object và không thay một comparator semantic. Bài dùng int cho phép tính và Packet chỉ có một field int để việc snapshot rõ; không claim mọi generic mixed-type mailbox hoặc deep-copy graph phức tạp.
Put có thể chặn khi đầy, get có thể chặn khi rỗng
Pha blocking bắt đầu khi mailbox rỗng. Producer put 21 rồi 22 ở 0 ns, làm đầy hai slot. Ở 1 ns, put 23 phải chặn vì chưa có consumer lấy đi. Consumer bắt đầu get ở 3 ns, nhận 21 và mở chỗ để put 23 hoàn tất ở 3 ns.
Consumer lấy 22 ở 4 ns rồi 23 ở 5 ns. Tổng dự kiến 66; main kiểm từng value theo vị trí trước khi cộng. Một producer và một consumer được dùng để thứ tự gửi tường minh. Không suy thứ tự giữa nhiều producer cùng put ở một timestamp từ log của scenario này.
Try method báo kết quả của chính thao tác, không đặt reservation ngầm
Preflight dùng try_get khi rỗng và kiểm target còn 99, rồi try_put7/11 thành công và try_put13 thất bại do đầy. Try_peek nhìn thấy 7 nhưng num vẫn 2; try_get sau đó lần lượt lấy 7/11 và làm num giảm. Sau khi drain, một try_get thất bại phải giữ target 23.
Main kiểm success bằng giá trị trả về dương, không ghim số dương cụ thể thành quy tắc ngôn ngữ cho mọi tool. Num là một snapshot của occupancy, không phải reservation cho lần get tiếp theo: nếu có consumer khác xen vào, “num>0 rồi get” vẫn có thể chặn. Khi cần quyết định không chặn, dùng return code của chính try_get.
Oracle theo vị trí, không chỉ theo tổng
Consumer yêu cầu value bằng 21+k với k 0..2. Nếu producer đổi thứ tự thành 22,21,23, tổng vẫn 66 nhưng FIFO-order oracle phải báo sai. Đây là lý do giữ từng phép so sánh thay vì chỉ tính checksum cuối rồi kết luận thứ tự đúng.
Main cũng kiểm put_release_time=3 và occupancy cuối 0. Những điều kiện này phát hiện một đường thực thi có dữ liệu đúng nhưng không có backpressure mong muốn. Mutant tăng bound từ 2 thành 3 bị preflight/full-capacity oracle bắt; không đổi expected để gọi cấu hình sai là cùng một scenario.
Message là class handle thì identity được chia sẻ
Packet sent được tạo với value 7 và put vào objects. Sender sửa sent.value thành 9 trước khi peek. Main yêu cầu peeked==sent và value 9: mailbox giữ giá trị handle trỏ cùng object, không tự chụp field 7 ở lúc put.
Tiếp theo sửa peeked.value thành 11 rồi get: received vẫn là cùng object với sent và sender cũng thấy 11. Peek không lấy message ra nhưng handle nó trả về vẫn có thể dùng để sửa object. “Chỉ quan sát queue” không đồng nghĩa object được bảo vệ readonly.
Không chuyển kết luận này sang int: với int, giá trị message được truyền theo value, không có một object dùng chung để sửa field qua alias. Kiểu của payload quyết định tầng identity cần review.
Snapshot của field nguyên thủy cần object riêng
Main tạo snapshot=new(sent.value) khi sent.value=11, rồi put snapshot và đổi sent.value thành 13. Consumer phải nhận snapshot khác identity với sent, value 11, trong khi sent giữ 13. Đây là copy một field nguyên thủy vào object mới, không phải deep clone tự động.
Mutant snapshot=sent chỉ tạo alias; sau sửa sender, received cũng thấy 13 và oracle snapshot phải bắt. Nếu Packet có nested handle/dynamic collection, cần một chính sách copy riêng và test thêm từng tầng như bài B5.copy; source hiện tại không được dùng làm proof cho mọi clone transaction.
12 oracle gồm cả kênh int và class
Expected trước chạy: MAILBOX CHECK checks=12 received=3 sum=66 put_release_ns=3 shared_value=11 copied_value=11 time_ns=5. Hai pha class không tiêu thụ thời gian nên kết thúc vẫn 5 ns của pha int.
Ba mutant giữ nguyên oracle: bound 3 thay 2, đảo hai message đầu và alias thay snapshot. Main parse/simulate trên Verilator; không claim nhiều producer, fairness, mọi mixed-type runtime hoặc clone object graph.
Tự kiểm tra
Review kênh, thứ tự và ownership của payload
Khi debug producer/consumer, kiểm họ dùng cùng mailbox object, bound đúng, blocking point đúng và message đúng thứ tự. Sau đó xem payload là value hay handle, ai còn quyền sửa nó và snapshot được tạo ở đâu. Bảo toàn FIFO không thay cho bảo toàn dữ liệu transaction.
Nguồn và mức độ đối chiếu
- SystemVerilog Mailbox — bản chụp resources.zip, SystemVerilog Mailbox vs Queue; Mailbox Methods; Parameterized Mailboxes. Trích dẫn; nguồn đã đọc các mục liên quan.
- SystemVerilog Mailbox — bản chụp resources.zip, try_put(): Checking for Room Without Blocking; peek() and try_peek(): Looking Without Removing. Trích dẫn; nguồn đã đọc các mục liên quan.
- Thí nghiệm nguyên bản ex.b7.mailbox, Source, self-checks, verification.json và concurrency probes. Trích dẫn; nguồn đã đọc toàn văn.
- IEEE 1800-2023 SystemVerilog LRM, Processes, synchronization and IPC methods — chưa đối chiếu LRM. Chưa đối chiếu với văn bản nguồn (chưa mở).