Tasks và functions: tái sử dụng code mà không chia sẻ nhầm state

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

Sau bài này bạn sẽ

  • Chọn function cho phép tính trả giá trị và task cho chuỗi hành động có thể chờ
  • Định nghĩa rõ kiểu trả về, input copy-in và output copy-out
  • Dự đoán hai lời gọi automatic chồng lấn có local state riêng
  • Phân biệt lifetime của local với quyền sở hữu biến dùng chung

Tái sử dụng hành vi, không sao chép lỗi

Một testbench tính parity ở nhiều nơi và có nhiều luồng cần gửi dữ liệu sau khoảng chờ. Sao chép thân code khiến sửa lỗi khó; nhưng gom thành helper rồi vô tình dùng chung local giữa các lời gọi lại tạo lỗi mới.

Bài tách hai hợp đồng: function nhận byte và trả parity ngay trong cùng simulation time; task giữ một số, đợi rồi trả gấp đôi. Hai task được gọi cùng lúc với dữ liệu và thời gian khác nhau để kiểm chúng có thực sự độc lập.

Function là một biểu thức có kiểu trả về

parity(byte_value) có thể nằm trong phép so sánh vì lời gọi trả về một giá trị. Function trong TB khai báo rõ một bit; thân reset kết quả 0 rồi XOR lần lượt 8 bit. Mọi đường đi của function đều gán kết quả.

Kiểu trả về là một phần của thuật toán. Một helper cộng hai byte nhưng chỉ trả 8 bit sẽ mất carry, dù biến nhận bên ngoài rộng hơn. Các dạng khai báo Verilog cũ còn có mặc định cần chú ý; cách an toàn khi đọc code là tìm kiểu/range ngay ở khai báo, không suy từ tên hàm. [1]

Tính tức thời khác chờ một hành động hoàn tất

Function parity của ví dụ không có delay, event control hay wait;256 lần kiểm đều xong tại t=0. Điều đó không có nghĩa 256 phép tính không tốn thời gian CPU, hoặc logic parity ngoài silicon có độ trễ 0.

Task có thể chứa timing control. delayed_double dùng delay theo input, nên lời gọi 3 ns chỉ trả về sau 3 ns. Task cũng có thể không chờ gì; từ khóa task không tự làm thời gian tăng. Với helper cần kết quả trong một biểu thức, dùng function; với hành động có chờ hoặc nhiều output, xem xét task theo hợp đồng cụ thể. [2]

Đối số được lấy và trả lúc nào?

Task của bài có input value, input delay_ns và output result. Input được lấy vào lúc gọi. Output được chép về biến caller khi lời gọi hoàn tất, khác với một kênh chia sẻ liên tục.

Slow ban đầu-1. Hai ns sau khi bắt đầu, fast đã thành 14 nhưng slow vẫn-1; checker kiểm ngay mốc này để bắt lỗi output về sớm. Khi slow hoàn tất ở 3 ns, caller mới nhận 6.

Bài không dùng ref hoặc inout. Không suy cơ chế copy-in/copy-out này thành ngữ nghĩa mọi kiểu đối số SystemVerilog. Các quy tắc chuẩn đầy đủ còn [3]. [4]

Automatic tạo local riêng cho mỗi lời gọi

task automatic delayed_double có biến held. Một call giữ 3, call kia giữ 7. Vì local thuộc từng activation, call thứ hai không ghi đè held của call đầu trong khoảng nó đang chờ.

Các task có lifetime static trong code module cũ có thể chia sẻ formal/local giữa lời gọi. Đừng suy lifetime chỉ từ việc task nằm gần một initial hay được gọi từ fork. Kiểm khai báo và scope; dùng automatic khi cần các activation độc lập như thí nghiệm này. [5]

Lời gọi bắt đầu cùng lúc không nhất thiết xong cùng lúc

Fork bắt đầu hai lời gọi tại 0: value 3/delay 3 và value 7/delay 1. Fast trả 14 tại 1 ns, slow trả 6 tại 3 ns; join chờ nhánh chậm nhất và hoàn tất 3 ns.

Một nhánh kiểm tại 2 ns nằm giữa hai mốc ấy. Nó giúp phân biệt “hai output cuối cùng có đúng không” với “chúng được công bố đúng lúc không”. Nếu các input giống nhau và delay giống nhau, lỗi chia sẻ state có thể bị giấu; vì vậy hai call cố ý khác cả value lẫn thời điểm kết thúc.

256 byte parity và hai task chồng lấn

Tính trước chạy: trong 256 byte có 128 byte parity lẻ. Oracle dùng bảng parity nibble, khác cấu trúc loop XOR 8 bit của function. Tổng thời gian sau kiểm function vẫn 0; task fast 14 ở 1 ns, slow 6 ở 3 ns, join 3 ns.

Function tức thời và task automatic chồng lấn

Kỳ vọng trước chạy: parity 256 byte có 128 odd, không tiến thời gian; hai task tự giữ 3/7, chờ 3/1 ns ->6/14; output slow còn-1 ởt 2, join 3.

tb_reuse.sv testbench
module tb_reuse;  timeunit 1ns;  timeprecision 1ps;  integer n, odd = 0, slow = -1, fast = -1;  reg [7:0] byte_value;  time fast_at = 0, slow_at = 0;  function automatic reg parity(input reg [7:0] value);    integer i;    begin      parity = 0;      for (i = 0; i < 8; i = i + 1) parity = parity ^ value[i];    end  endfunction  function automatic reg nibble_parity(input reg [3:0] value);    // Independent lookup for an odd number of ones in four bits.    case (value)      1, 2, 4, 7, 8, 11, 13, 14: nibble_parity = 1;      default: nibble_parity = 0;    endcase  endfunction  task automatic delayed_double(input integer value, input time delay_ns, output integer result);    integer held;    begin      held = value;      #(delay_ns);      result = 2 * held;    end  endtask  initial begin    for (n = 0; n < 256; n = n + 1) begin      byte_value = n[7:0];      if (parity(byte_value) !== (nibble_parity(byte_value[7:4]) ^ nibble_parity(byte_value[3:0])))        $fatal(1, "parity mismatch at=%0d", n);      if (parity(byte_value)) odd = odd + 1;    end    if (odd != 128 || $time != 0) $fatal(1, "function count or time mismatch");    $display("FUNCTION CHECK inputs=256 odd=%0d at=%0d", odd, $time);    fork      begin delayed_double(3, 3, slow); slow_at = $time; end      begin delayed_double(7, 1, fast); fast_at = $time; end      begin        #2;        if (fast != 14 || slow != -1 || fast_at != 1)          $fatal(1, "output copy-out or early slow result mismatch");      end    join    if (slow != 6 || fast != 14 || slow_at != 3 || fast_at != 1 || $time != 3)      $fatal(1, "overlapping automatic calls mismatch");    $display("TASK CHECK fast=%0d at=%0d slow=%0d at=%0d join=%0d", fast, fast_at, slow, slow_at, $time);    $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: FUNCTION CHECK inputs=256 odd=128 at=0TASK CHECK fast=14 at=1 slow=6 at=3 join=3

Không claim synthesis, mọi static/automatic lifetime rule hoặc ref argument; chỉ input copy-in/output copy-out và automatic locals đã viết.

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.

Parser/Icarus thực chạy các assertions, kể cả output chưa trả ở 2 ns. Bằng chứng không claim recursion, synthesis hay toàn bộ lifetime trong class/package. [6]

Automatic không khóa biến global

  • Cho rằng automatic làm mọi biến module mà task truy cập cũng thành local.
  • Hai call cùng ghi một biến output của caller rồi mong chúng độc lập.
  • Bỏ qua kiểu trả về và mất carry hoặc sign trước khi gán ra ngoài.
  • Chỉ kiểm giá trị cuối mà không kiểm lúc output được trả.
  • Gọi helper có chờ từ vị trí cần phép tính không chờ.
  • Dùng chung file descriptor hoặc hàng đợi mà không có quy tắc ownership.

Trong TB này mỗi call có output đích riêng; local riêng chưa đủ để bảo vệ một resource global. Khi cần chia sẻ, hãy thiết kế giao thức truy cập thay vì coi automatic là một khóa đồng bộ.

Tự kiểm tra

Tự kiểm tra: tasks-functions

  1. Hai task automatic nhận 3/chờ 3 ns và 7/chờ 1 ns, cùng bắt đầu 0. Kết quả nào đúng?
  2. 256 lần gọi function parity không có timing control bắt đầu tại 0. Simulation time sau đó là gì?
  3. Tại 2 ns trong TB, slow output của caller bằng gì?
  4. Automatic có làm hai call cùng ghi một biến module trở nên an toàn không?
  5. Function cộng hai byte chỉ trả 8 bit; biến caller 9 bit có tự phục hồi carry đã mất không?

Ghi hợp đồng dữ liệu và thời gian cho mỗi helper

Mô tả rõ input được lấy khi nào, output có hiệu lực khi nào, kiểu/width của kết quả và lifetime của local. Kiểm trường hợp hai call chồng lấn với dữ liệu khác nhau trước khi đưa helper vào regression lớn.

Nguồn đào tạo hỗ trợ cách phân loại; bài giữ draft/LRM needs-spec-check và bằng chứng chỉ phủ code đã chạy.

Nguồn và mức độ đối chiếu

  1. Verilog Functions — bản chụp trong resources.zip, Function declarations và return value. Trích dẫn; nguồn đã đọc các mục liên quan.
  2. Verilog Task — bản chụp trong resources.zip, Difference between function and task. Trích dẫn; nguồn đã đọc các mục liên quan.
  3. 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ở).
  4. Verilog Task — bản chụp trong resources.zip, Task arguments. Trích dẫn; nguồn đã đọc các mục liên quan.
  5. Verilog Task — bản chụp trong resources.zip, Automatic Task. Trích dẫn; nguồn đã đọc các mục liên quan.
  6. Thí nghiệm nguyên bản ex.a7.tasks-functions, Thử nghiệm nguyên bản. Trích dẫn; nguồn đã đọc toàn văn.
  7. IEEE 1800-2023 SystemVerilog LRM, tasks-functions language and system task semantics. Chưa đối chiếu với văn bản nguồn (chưa mở).
  8. Thí nghiệm nguyên bản ex.a7.tasks-functions, Source, assertions và verification.json của TB nguyên bản. Trích dẫn; nguồn đã đọc toàn văn.
  9. IEEE 1800-2023 SystemVerilog LRM, Tasks/functions; argument passing; static/automatic lifetime. Chưa đối chiếu với văn bản nguồn (chưa mở).
  10. Verilog Functions — bản chụp trong resources.zip, Syntax; Function declarations; Returning a value from a function; Calling a function. Trích dẫn; nguồn đã đọc các mục liên quan.
  11. Verilog Task — bản chụp trong resources.zip, Syntax; Automatic Task; Difference between function and task. Trích dẫn; nguồn đã đọc các mục liên quan.
  12. Thí nghiệm nguyên bản ex.a7.tasks-functions, tb_reuse.sv và verification.json. Trích dẫn; nguồn đã đọc toàn văn.