Parameter và generate: cấu hình phần cứng trước khi chạy

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

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

  • Phân biệt parameter cấu hình với tín hiệu đầu vào thay đổi theo thời gian
  • Dùng named parameter override và localparam dẫn xuất độ rộng
  • Đọc for-generate, if-generate và đường dẫn block có tên
  • Nhận diện defparam trong code cũ và kiểm tra cấu hình biên

Chọn cấu hình, rồi mới đưa dữ liệu vào

Bài trước dùng hai instance của cùng bộ cộng. Nhưng nếu mỗi sản phẩm cần số bit khác nhau, bạn không muốn duy trì nhiều bản module chỉ khác độ rộng. Parameter cho phép chọn cấu hình cho từng instance.

Trong bài này, lane_map xử lý một bus chia thành nhiều lane. W chọn số bit mỗi lane; LANES chọn số lane; INVERT chọn giữ nguyên hay đảo bit. Dữ liệu đi qua cổng data có thể thay đổi ở mỗi lần test. Ba parameter được chọn khi thiết kế được elaborate, không phải các nút điều khiển thay đổi theo từng lần đưa dữ liệu vào.

Hãy tưởng tượng đặt làm một bảng mạch: chọn số hàng và số cột khi thiết kế bảng, rồi mới thay đổi những tín hiệu đi trên bảng đó. Đây là trực giác cho ví dụ, không phải khẳng định parameter tương ứng một linh kiện vật lý cụ thể.

Ví dụ dùng SystemVerilog (.sv), trong đó có localparam ở parameter port list. Các quy tắc ngôn ngữ vẫn cần đối chiếu [1]; phần kiểm chứng của bài chỉ xác nhận các cấu hình cụ thể đã chạy.

Parameter, named override và localparam

Cấu hình mặc định của lane_map là W=4, LANES=3, INVERT=1. TOTAL là localparam dẫn xuất bằng W*LANES, dùng làm độ rộng cổng. Với mặc định, bus có 4*3=12 bit. Khi đổi một instance thành W=2, LANES=2, bus của instance đó có 4 bit.

lane_map #(.W(4), .LANES(3), .INVERT(0)) u_named
  (.data(data), .mapped(pass_data));

Có hai danh sách khác nhau:

  • #(...) chọn giá trị parameter cho instance.
  • (...) cuối dòng nối cổng với tín hiệu phía parent.

.W(4) cấu hình độ rộng; .data(data) nối một bus mang dữ liệu. Cả hai đều dùng cú pháp có tên, nhưng không cùng vai trò. Không đọc #(...) ở đây thành lệnh đợi thời gian như #1 trong testbench.

localparam TOTAL là giá trị nội bộ tính từ cấu hình, không phải một knob riêng cho bên ngoài override. Nếu công thức độ rộng là W*LANES, chỉ để người dùng chọn W/LANES giúp tránh ba giá trị bị cấu hình mâu thuẫn. Nó cũng không phải một thanh ghi lưu tích của hai số ở runtime.

Vòng generate tạo gì?

Trong lane_map, vòng for-generate tạo các block g_lane[0] đến g_lane[LANES-1]. Mỗi block có một value rộng W bit và xử lý một lát của bus.

for (lane = 0; lane < LANES; lane = lane + 1) begin : g_lane

Với LANES=3, có ba block lane. Nó không lấy một lane rồi đợi clock để xử lý lane tiếp theo. Thiết kế này thậm chí không có cổng clock. Số block được quyết định khi elaborate.

Bên trong mỗi lane, if-generate chọn một trong hai nhánh theo INVERT: g_invert gán giá trị đảo bit; g_pass nối nguyên lát dữ liệu. Đây là lựa chọn cấu hình, không phải một mux được điều khiển bằng tín hiệu input mode trong ví dụ.

Lát data[lane*W +: W] lấy W bit bắt đầu ở chỉ số lane*W theo hướng tăng chỉ số. Với W=4, ba lane là các lát bit 3:0, 7:4, 11:8. Nếu đổi W, phải tính lại ranh giới, không giữ các con số 4/8/12 ở những chỗ còn lại.

Bốn cấu hình được kiểm tra thật

Trước khi chạy, tính tay với data = 12'ha5c:

  • Cấu hình mặc định đảo 12 bit: ~a5c trong độ rộng 12 bit là 5a3.
  • Named override INVERT=0 giữ nguyên a5c.
  • Instance legacy có W=2, LANES=2; đầu vào là bốn bit thấp c, đảo thành 3.
  • Cấu hình biên W=1, LANES=1 nhận bit thấp bằng 0, nên output đảo là 1.
  • Lane 2 của mặc định lấy nibble cao của 5a3, bằng 5.

Số giá trị 12 bit cần thử là 2^12 = 4096. Testbench duyệt toàn bộ và so sánh tất cả output với phép toán độc lập ở TB; chỉ in số case hoàn tất sau khi không có mismatch nào.

Parameter chọn kích thước và nhánh generate

So sánh cấu hình mặc định, named override, defparam và biên W=1/LANES=1; kiểm tra generate-if/for bằng vét cạn 4096 giá trị 12 bit.

lane_map.sv RTL
`timescale 1ns/1psmodule lane_map #(  parameter integer W = 4,  parameter integer LANES = 3,  parameter integer INVERT = 1,  localparam integer TOTAL = W * LANES) (  input  wire [TOTAL-1:0] data,  output wire [TOTAL-1:0] mapped);  genvar lane;  generate    for (lane = 0; lane < LANES; lane = lane + 1) begin : g_lane      wire [W-1:0] value;      if (INVERT != 0) begin : g_invert        assign value = ~data[lane*W +: W];      end else begin : g_pass        assign value = data[lane*W +: W];      end      assign mapped[lane*W +: W] = value;    end  endgenerateendmodule
map_passthrough.sv RTL
`timescale 1ns/1psmodule map_passthrough (  input  wire [11:0] data,  output wire [11:0] mapped);  lane_map #(.W(4), .LANES(3), .INVERT(0)) u_map    (.data(data), .mapped(mapped));endmodule
tb_params.sv testbench
module tb_params;  timeunit 1ns;  timeprecision 1ps;  logic [11:0] data;  wire [11:0] inv_data, pass_data;  wire small_result;  wire [3:0] legacy_result;  int checked = 0;  lane_map u_default (.data(data), .mapped(inv_data));  lane_map #(.W(4), .LANES(3), .INVERT(0)) u_named    (.data(data), .mapped(pass_data));  lane_map #(.W(1), .LANES(1)) u_small    (.data(data[0]), .mapped(small_result));  lane_map u_legacy (.data(data[3:0]), .mapped(legacy_result));  defparam u_legacy.W = 2;  defparam u_legacy.LANES = 2;   initial begin    for (int i = 0; i < 4096; i++) begin      data = 12'(i);      #1;      if (inv_data !== (data ^ 12'hfff) || pass_data !== data)        $fatal(1, "bad generated map at %0d", i);      if (small_result !== ~data[0] || legacy_result !== (data[3:0] ^ 4'hf))        $fatal(1, "bad parameter override at %0d", i);      checked++;      if (i == 12'ha5c) begin        $display("MAP RESULT inv=%0h pass=%0h legacy=%0h small=%0d",                 inv_data, pass_data, legacy_result, small_result);        $display("LANE RESULT value=%0h", u_default.g_lane[2].value);      end    end    $display("PARAMS RESULT cases=%0d default_bits=%0d legacy_bits=%0d",             checked, $bits(u_default.mapped), $bits(u_legacy.mapped));    $finish;  endendmodule

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

  • Phân tích và elaborate theo LRM (pyslang 12.1.0)
  • Lint -Wall (Verilator 5.050)
  • Mô phỏng, exit 0 và output khớp kỳ vọng tính tay (Verilator 5.050, 2-state)
  • Tổng hợp, số cell khớp kỳ vọng (Yosys 0.70)

Output phải khớp: MAP RESULT inv=5a3 pass=a5c legacy=3 small=1LANE RESULT value=5PARAMS RESULT cases=4096 default_bits=12 legacy_bits=4

Kỳ vọng sau tổng hợp: lane_map: $_NOT_ = 12lane_map: $_*DFF* = 0map_passthrough: $_NOT_ = 0map_passthrough: $_*DFF* = 0

Chỉ kiểm các cấu hình W=4/LANES=3, W=1/LANES=1 và W=2/LANES=2; yêu cầu W và LANES >= 1. Không khẳng định cấu hình width bằng 0 hợp lệ.

Verilator 2-state; đầu vào chỉ 0/1, không kiểm X/Z hoặc delay vật lý.

Dùng localparam trong parameter port list (SystemVerilog) để suy ra TOTAL; đây không phải cú pháp Verilog-1995.

defparam chỉ xuất hiện trong testbench để đọc code cũ; code RTL mới dùng named parameter override. Không kiểm mọi biến thể defparam theo LRM.

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.

Lệnh lint của example kiểm lane_map.sv; wrapper và TB được elaborate/mô phỏng cùng các instance. Mở tb_params.sv để thấy điều kiện $fatal: nếu một output không khớp, test dừng trước dòng tổng kết, không chỉ in số đẹp rồi coi là đạt.

Kết quả này không khẳng định mọi giá trị parameter đều hợp lệ hay mọi tool đều hỗ trợ mọi cấu trúc giống nhau. Cần kiểm tra riêng mỗi cấu hình sản phẩm thực sự dùng.

Hai nhánh generate, hai kết quả synthesis

Tính tay kỳ vọng trước khi synthesis:

  • lane_map mặc định đảo 12 bit độc lập: cần 12 cổng NOT và không có flip-flop.
  • map_passthrough nối lane_map với INVERT=0: không cần cổng NOT và không có flip-flop.

Yosys thực sự cho kết quả khớp hai kỳ vọng này. [3]

Đừng đếm “ba lần lặp” thành “ba cổng NOT”: mỗi lane có 4 bit. Ngược lại, ba block generate ở nhánh passthrough không buộc synthesis giữ ba hộp phần cứng hoặc ba buffer riêng. Một block có tên giúp tổ chức mô hình; nó không bảo đảm số cell hay hình dạng hierarchy sau tối ưu.

Nếu muốn chọn đảo/giữ nguyên theo một tín hiệu thay đổi lúc chạy, giao diện và logic phải có tín hiệu điều khiển tương ứng; đổi parameter giữa chừng không phải cách làm của ví dụ này. Bài chưa đo timing, PPA hoặc kết quả mapping sang thư viện cell công nghệ cụ thể.

Đọc defparam và hierarchy trong code cũ

Testbench còn có một instance u_legacy rồi cấu hình bằng:

defparam u_legacy.W = 2;
defparam u_legacy.LANES = 2;

Hai dòng này đã được parser/elaborator và simulator của pipeline chấp nhận trong đúng chương trình của bài. Kết quả $bits(u_legacy.mapped)=4 cho thấy cấu hình đó đã có hiệu lực; nó không phải một thay đổi độ rộng tại mỗi vòng test.

TB đọc u_default.g_lane[2].value để quan sát lane cao. u_default là tên instance; g_lane[2] là block generate có chỉ số; value là dây trong block đó. Con số 2 là chỉ số cố định có trong cấu hình LANES=3 này; đừng mặc định đường dẫn đó tồn tại khi cấu hình chỉ có một lane.

Bài không khuyến nghị dùng đường dẫn sâu để thay thế giao diện cổng giữa các khối. Quy tắc đầy đủ về scope, defparam và generate cần tiếp tục đối chiếu [4].

Biên tham số và lỗi tư duy thường gặp

  1. Chỉ test cấu hình mặc định. Bug part-select hoặc độ rộng có thể chỉ xuất hiện khi W=1, LANES=1, hoặc khi số lane khác mặc định. Ví dụ kiểm ba cặp kích thước, không chỉ 4*3.
  2. Dùng parameter như input. data thay đổi qua 4096 giá trị; W, LANES, INVERT của từng instance giữ nguyên cấu hình trong lần mô phỏng này.
  3. Nhầm số block với số bit/cell. Ba lane 4 bit là bus 12 bit. Synthesis còn có thể tối ưu các dây nối, nên số block không phải số cell.
  4. Cấu hình zero-width mà không thiết kế quy ước. Ví dụ yêu cầu W>=1, LANES>=1. Với TOTAL=0, biểu thức range thành [-1:0], không thể coi đó là cách khai báo một bus rỗng theo ý định. Bài không chạy cấu hình bằng 0 và không khẳng định mọi tool sẽ báo lỗi giống nhau.
  5. Đếm for bằng trực giác CPU. Vòng generate mô tả cấu trúc; vòng for có #1 trong TB tạo một chuỗi stimulus theo thời gian mô phỏng. Khi gặp vòng for trong procedural RTL, phải xét riêng chức năng và khả năng synthesis.

Tự kiểm tra

Cấu hình trước, dữ liệu sau

  1. W=2 và LANES=2 trong instance legacy: TOTAL và độ rộng mapped là bao nhiêu bit?
  2. Với LANES=3, for-generate của lane_map tạo bao nhiêu block g_lane?
  3. Wrapper INVERT=0 có bao nhiêu cổng NOT trong kết quả synthesis của ví dụ?
  4. Với W=4/LANES=3/INVERT=1 và data=a 5 c (hex), u_default.g_lane[2].value bằng bao nhiêu (hex)?
  5. Mục nào trong ví dụ mang dữ liệu thay đổi qua từng vòng test?

Checklist cho RTL có cấu hình

  • Xác định knob bên ngoài và giá trị dẫn xuất bằng localparam.
  • Đọc riêng danh sách parameter #(...) và kết nối cổng (...).
  • Tính số lane, độ rộng bus và ranh giới part-select bằng tay trước khi mô phỏng.
  • Kiểm cấu hình mặc định, cấu hình ghi đè, cấu hình biên và từng nhánh generate được sử dụng.
  • Khi debug hierarchy, kiểm block/index có thật trong cấu hình đã elaborate.
  • Giữ đúng phạm vi bằng chứng: input 0/1, cấu hình đã chạy, cell count ở tool cụ thể; chưa xác minh đầy đủ LRM và chưa đo timing vật lý.

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

  1. IEEE 1800-2023 SystemVerilog LRM, LRM: parameters và elaboration. Chưa đối chiếu với văn bản nguồn (chưa mở).
  2. Thử nghiệm parameter/generate: content/code/ex.a6.params-generate/verification.json, Bằng chứng chạy thực. Trích dẫn; nguồn đã đọc toàn văn.
  3. Thử nghiệm parameter/generate: content/code/ex.a6.params-generate/verification.json, Bằng chứng synthesis. Trích dẫn; nguồn đã đọc toàn văn.
  4. IEEE 1800-2023 SystemVerilog LRM, LRM: generate và defparam. Chưa đối chiếu với văn bản nguồn (chưa mở).
  5. IEEE 1800-2023 SystemVerilog LRM, Parameters, elaboration, generate constructs and defparam. Chưa đối chiếu với văn bản nguồn (chưa mở).
  6. Thử nghiệm parameter/generate: content/code/ex.a6.params-generate/verification.json, verification.json: PARAMS RESULT. Trích dẫn; nguồn đã đọc toàn văn.
  7. IEEE 1800-2023 SystemVerilog LRM, Loop generate constructs. Chưa đối chiếu với văn bản nguồn (chưa mở).
  8. Thử nghiệm parameter/generate: content/code/ex.a6.params-generate/verification.json, meta.yaml: synth map_passthrough; verification.json: achieved. Trích dẫn; nguồn đã đọc toàn văn.
  9. Thử nghiệm parameter/generate: content/code/ex.a6.params-generate/verification.json, verification.json: LANE RESULT. Trích dẫn; nguồn đã đọc toàn văn.
  10. Thử nghiệm parameter/generate: content/code/ex.a6.params-generate/verification.json, content/code/ex.a6.params-generate/verification.json. Trích dẫn; nguồn đã đọc toàn văn.