Initial, always và fork: đọc nhiều process cùng chạy
Tiên quyết
- Module A6: Verilog cấu trúc: Đang tải
Sau bài này bạn sẽ
- Phân biệt initial chạy một lần với always lặp và process chờ event
- Tính thời gian tuần tự của begin/end và thời gian chờ fork/join
- Đọc clock generator theo half-period và đếm cạnh đúng thời điểm
- Viết TB tránh suy ra thứ tự giữa các process cùng timestep
Một file không phải một luồng chạy từ trên xuống
Trong phần cứng, nhiều khối có thể hoạt động cùng lúc. HDL dùng các process để mô tả hành vi đồng thời đó. Đọc hết initial này rồi mới chạy initial viết phía dưới là một mô hình suy nghĩ dễ sai.
Bài dùng các process ghi vào biến riêng để quan sát thời gian mà không tạo tranh chấp. Ta chưa cần thuộc mọi event region; trước tiên vẽ được những luồng nào chạy, luồng nào đang chờ và sự kiện nào cho chúng tiếp tục.
Các kết quả được kiểm bằng Icarus và pyslang. Quy tắc scheduling tổng quát vẫn [1]; bài không tuyên bố một lần mô phỏng xác minh mọi thứ tự concurrent execution.
Initial chạy một lần, có thể kéo dài nhiều thời điểm
initial initial_runs++; tăng bộ đếm một lần trong TB. Một initial khác có thể chứa delay và kéo dài tới 8 ns; “chạy một lần” không có nghĩa mọi lệnh của nó phải xong tại thời điểm 0.
Các initial của ví dụ là các process riêng. Chúng có thể bắt đầu rồi chờ những delay khác nhau. Thứ tự đặt các khối trong file không phải một handshake giữa chúng. Nếu process B cần dữ liệu do A tạo, phải có quan hệ đồng bộ hoặc thời điểm sample phù hợp.
Khởi tạo các biến đo mốc bằng -1 giúp TB phát hiện một mốc chưa được ghi. Không dùng giá trị mặc định của simulator rồi giả định process đã chạy đúng.
Always lặp, event hoặc delay làm tiến thời gian
Clock của TB được viết always #2 clk = ~clk, bắt đầu từ clk=0. Nó chờ 2 ns, đảo clock rồi lặp. Vì mỗi lần đảo chỉ là nửa chu kỳ, period là 4 ns, không phải 2 ns. Cạnh lên trước thời điểm 9 ns nằm tại 2 và 6 ns.
Một process khác always @(posedge clk) edges++ chờ cạnh lên rồi tăng bộ đếm, sau đó quay lại chờ cạnh tiếp theo. Nó không liên tục tăng edges trong mỗi bước scheduler.
Nếu viết một vòng always không delay, không event và không điều kiện chờ, nó có thể không nhường để thời gian mô phỏng tiến lên. Bài không thực thi một vòng treo để minh hoạ; thay vào đó giữ timing control rõ ràng trong code đã kiểm.
Clock generator là mô hình TB, không phải cách tạo clock vật lý được bảo đảm synthesis. Không claim timing, FF hoặc một mạch oscillator từ đoạn always #delay này.
Begin/end: hai delay cộng dồn
Trong một process, begin/end nhóm các statement tuần tự. Khối sequential_flow bắt đầu ở t=0, chờ #3 rồi ghi seq_first=3; tiếp tục chờ #5 nên ghi seq_done=8.
Delay thứ hai bắt đầu khi process tới statement đó, không bắt đầu lại từ thời điểm 0. Vì vậy 3+5=8 là dự đoán đúng cho luồng này. Begin/end tự nó không tạo một process độc lập cho mỗi statement.
Tên khối sequential_flow giúp định vị trong source/scope; việc đặt tên không biến luồng tuần tự thành song song. Khi đọc block lồng nhau, trước tiên theo từng process và đường điều khiển của process đó.
Fork/join: chờ nhánh lâu nhất
Trong parallel_flow, fork tạo hai nhánh: một nhánh chờ 3 ns, nhánh kia chờ 5 ns. Cả hai được khởi động trong cùng lần fork ở t=0, nên mốc hoàn tất là 3 và 5, không phải 3 và 8.
Join của ví dụ chờ cả hai nhánh hoàn tất rồi process cha mới ghi join_done=5. Thời gian chờ ở tình huống này là max(3,5)=5 ns.
“Song song” ở đây là mô hình process của simulator, không có nghĩa máy chủ phải dùng hai CPU hoặc hai luồng hệ điều hành cho hai nhánh. Các process còn tương tác với event scheduler; nếu cùng ghi/đọc một biến tại cùng thời điểm, cần phân tích race riêng.
Bài chỉ chạy fork/join. Không áp dụng kết luận “chờ tất cả” cho join_any/join_none, disable fork hoặc thread lifetime khác; đó là những cấu trúc cần bằng chứng riêng.
Dự đoán rồi kiểm các mốc 3, 5, 8 và 9 ns
Tính tay trước khi chạy:
- Sequential: mốc đầu 3, mốc cuối 8 ns.
- Fork: hai nhánh xong ở 3 và 5 ns; join xong ở 5 ns.
- Clock period 4 ns: tại t=9 đã có hai posedge (2,6).
- Initial tăng bộ đếm đúng một lần; TB kết thúc ở t=9.
TB còn kiểm ở t=1 rằng các mốc chưa xảy ra và chưa có cạnh lên clock. Kiểm tổng kết ở t=9 tránh trùng thời điểm các sự kiện đang được thử. Sai mốc hoặc số lượt thì $fatal; exit 0 và output khớp mới có simulated.
Output thật PROCESS RESULT seq_first=3 seq_done=8 fork_first=3 fork_second=5 join_done=5 edges=2. Dòng dừng xác nhận t=9 và initial_runs=1. Không claim lint/synthesis cho TB dùng delay/fork/finish này. [2]
Vì sao TB không kiểm ngay ở t=5?
Giả sử một initial khác cũng chờ #5 rồi đọc fork_second. Cả process đọc và process ghi có thể được lên lịch trong cùng timestep. Nếu không có quan hệ thứ tự phù hợp, việc thấy một giá trị trong một lần chạy không chứng minh code không có race.
Ví dụ tránh câu hỏi đó: các process đo mốc ghi biến riêng, còn luồng kiểm đọc sau mọi mốc liên quan, ở t=9. Đây là cách tạo thí nghiệm xác định cho nội dung của bài, không phải giải pháp chung “thêm delay tùy ý để chữa mọi race”.
Ở bài blocking/non-blocking và scheduling, cần đọc vùng cập nhật và hợp đồng lấy mẫu sâu hơn. Đừng lấy cách viết #1 ở một TB cụ thể làm quy tắc đồng bộ duy nhất của cả dự án.
Lỗi thường gặp khi tạo stimulus và clock
- Cho rằng mọi initial chạy nối đuôi theo vị trí trong file.
- Hiểu “initial một lần” thành “chỉ được làm việc ở t=0”.
- Nhầm #2 toggle với clock period 2 ns.
- Cộng delay của hai nhánh fork như thể chúng nằm trong begin tuần tự.
- Cho rằng begin/end tự tạo concurrency.
- Không có $finish trong TB có clock chạy mãi, rồi tưởng simulator sẽ tự biết lúc nào đủ test.
- Dùng một kết quả scheduler để phủ nhận race mà chưa kiểm quan hệ đọc/ghi.
Tự kiểm tra
Vẽ process trước khi đọc thời gian
Vẽ mỗi initial/always thành một luồng, đặt delay/event trên luồng đó, rồi vẽ các nhánh fork và điểm join. Khi nhiều luồng chạm cùng một tín hiệu, hỏi tiếp quan hệ lấy mẫu và scheduling.
Bài kiểm các mốc và cấu trúc đã nêu, chưa phủ mọi parallel-block variant hoặc toàn bộ event-region rules. Hoàn thành bài này chỉ đáp ứng tiên quyết riêng của blocking/non-blocking; không tự đánh dấu xong A6 hoặc cả module A7.
Nguồn và mức độ đối chiếu
- IEEE 1800-2023 SystemVerilog LRM, cần đối chiếu LRM. Chưa đối chiếu với văn bản nguồn (chưa mở).
- Thử nghiệm process: content/code/ex.a7.procedural/verification.json, Bằng chứng process. Trích dẫn; nguồn đã đọc toàn văn.
- IEEE 1800-2023 SystemVerilog LRM, Initial and always procedures; sequential and parallel blocks; timing controls. Chưa đối chiếu với văn bản nguồn (chưa mở).
- Thử nghiệm process: content/code/ex.a7.procedural/verification.json, PROCESS RESULT seq_first=3 seq_done=8. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm process: content/code/ex.a7.procedural/verification.json, PROCESS RESULT join_done=5. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm process: content/code/ex.a7.procedural/verification.json, clock generator và PROCESS RESULT edges=2. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm process: content/code/ex.a7.procedural/verification.json, sequential_flow và initial_runs=1. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm process: content/code/ex.a7.procedural/verification.json, check_flow sampling t=1 và t=9. Trích dẫn; nguồn đã đọc toàn văn.
- IEEE 1800-2023 SystemVerilog LRM, Initial and always procedures; sequential/parallel blocks; timing controls. Chưa đối chiếu với văn bản nguồn (chưa mở).
- Thử nghiệm process: content/code/ex.a7.procedural/verification.json, tb_processes.sv và verification.json. Trích dẫn; nguồn đã đọc toàn văn.