Initial, always và fork: đọc nhiều process cùng chạy

Khoảng 30 phútMức 1/3Bản nháp: một số khẳng định chưa đối chiếu spec

Tiên quyết

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.

Begin cộng thời gian, fork/join chờ nhánh lâu nhất

Kiểm initial một lần, always lặp có delay/event và thời gian hoàn tất block tuần tự/song song bằng Icarus.

tb_processes.sv testbench
module tb_processes;  timeunit 1ns;  timeprecision 1ps;  logic clk = 0;  int edges = 0, initial_runs = 0;  int seq_first = -1, seq_done = -1;  int fork_first = -1, fork_second = -1, join_done = -1;  initial initial_runs++;  always #2 clk = ~clk;  always @(posedge clk) edges++;   initial begin : sequential_flow    #3;    seq_first = int'($time);    #5;    seq_done = int'($time);  end  initial begin : parallel_flow    fork      begin        #3;        fork_first = int'($time);      end      begin        #5;        fork_second = int'($time);      end    join    join_done = int'($time);  end  initial begin : check_flow    #1;    if (seq_first != -1 || fork_first != -1 || edges != 0 || initial_runs != 1)      $fatal(1, "process ran too early");    #8; // t=9ns, away from clock edges and all completion times under test.    if (seq_first != 3 || seq_done != 8 || fork_first != 3 || fork_second != 5 ||        join_done != 5 || edges != 2 || initial_runs != 1)      $fatal(1, "process scheduling mismatch");    $display("PROCESS RESULT seq_first=%0d seq_done=%0d fork_first=%0d fork_second=%0d join_done=%0d edges=%0d",             seq_first, seq_done, fork_first, fork_second, join_done, edges);    $display("PROCESS STOP at=%0d initial_runs=%0d", int'($time), initial_runs);    $finish;  endendmodule

Đã kiểm chứng thế nào

  • Phân tích và elaborate theo LRM (pyslang 12.1.0)
  • Mô phỏng, exit 0 và output khớp kỳ vọng tính tay (Icarus Verilog 11.0, 4-state)

Output phải khớp: PROCESS RESULT seq_first=3 seq_done=8 fork_first=3 fork_second=5 join_done=5 edges=2PROCESS STOP at=9 initial_runs=1

Testbench dùng delay/fork/finish để mô phỏng, không claim synthesis hoặc clock vật lý.

Các process ghi biến riêng; checkt 9 tránh cùng timestep với mốc 3/5/8 và posedge 2/6. Không chứng minh thứ tự giữa các process cùng thời điểm.

Chỉ kiểm begin/end và fork/join; join_any/join_none, disable fork và toàn bộ event-region rules ngoài phạm vi ví dụ.

Các kiểm tra chạy lúc xây dựng nội dung (2026-11-06), không chạy trong trình duyệt. Kỳ vọng được tính tay trước khi chạy.

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

Theo từng process, đừng cộng nhầm thời gian

  1. Khối begin của ví dụ bắt đầu t=0, chờ #3 rồi #5. Mốc cuối bằng bao nhiêu ns?
  2. Hai nhánh fork cùng bắt đầu t=0, chờ 3 và 5 ns rồi kết thúc. Join của bài hoàn tất ở đâu?
  3. Clock ban đầu 0, always #2 đảo clock; tại t=9 ns đã có bao nhiêu cạnh lên?
  4. Một initial có #delay tới t=8 ns có vi phạm ý nghĩa chạy một lần không?
  5. Vì sao luồng kiểm của bài đọc tổng kết ở t=9 thay vì cùng t=5 với nhánh fork?

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

  1. 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ở).
  2. 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.
  3. 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ở).
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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ở).
  10. 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.