Scope và hierarchy: cùng tên value, khác instance

Khoảng 30 phútMức 3/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 tên module, instance, generate block và tín hiệu cục bộ
  • Đọc đường dẫn tương đối và đường dẫn từ root của ví dụ
  • Phân biệt index generate ở elaboration với chọn dữ liệu lúc runtime
  • Dùng hierarchy để quan sát debug mà vẫn giữ hợp đồng RTL qua port

Hai ngăn có cùng nhãn không phải cùng một ngăn

Một module có tín hiệu nội bộ tên value. Khi module được instantiate hai lần, không có một value dùng chung cho mọi bản sao. Mỗi instance có scope và kết nối riêng. Để debug đúng nơi, cần đọc đường dẫn chứ không chỉ tìm một tên bằng văn bản.

Ví dụ tạo hai lane bằng generate, mỗi lane chứa một scoped_cell. Cùng input byte a5, lane thấp nhìn nibble 5, lane cao nhìn nibble a. TB còn có biến value riêng bằng 99 để cho thấy một tên ngắn không đủ xác định đối tượng.

Các đường dẫn đã viết được parser/simulator kiểm thật; toàn bộ name lookup, package scope, upward reference và những trường hợp khác còn [1].

Module type khác tên instance

scoped_cell là tên module type. u_cell là tên instance bên trong mỗi generate block. hierarchy_pair là module bao hai lane và dut là instance của nó ở testbench.

Viết một đường dẫn bắt đầu từ dut là chọn instance có thật trong TB. Không dùng tên module type thay cho instance rồi mong nó trỏ tới mọi bản sao cùng lúc. Tên module trong source và tên instance trong cây elaboration trả lời hai câu hỏi khác nhau.

Generate block được đặt tên g_lane. Hai nhánh có tên g_lane[0] và g_lane[1]; tên u_cell lặp lại hợp lệ vì nằm ở hai scope khác nhau. Named block giúp đường dẫn dễ đọc hơn tên tự sinh vốn có thể đổi khi chỉnh source.

Ba biến value độc lập

TB khai báo int value=99. Mỗi scoped_cell khai báo một wire 4 bit tên value, nối với input data của instance đó. Trong scope TB, tên ngắn value ở phép kiểm là biến 99; trong leaf, tên ngắn value ở biểu thức cộng là wire của leaf.

Tại data a5, hai wire leaf lần lượt bằng 5 và 10, còn value của TB vẫn 99. Không có việc “lần gán sau thắng” giữa ba tên này: đó là ba đối tượng thuộc ba scope khác nhau.

Parameter STEP cũng thuộc mỗi instance. Lane 0 override STEP=1, lane 1 override STEP=2 tại elaboration. Hai instance cùng type nhưng không bắt buộc có cùng parameter.

Đọc từng đoạn của đường dẫn

dut.g_lane[1].u_cell.value đi từ TB tới instance dut, chọn generate block lane 1, vào instance u_cell rồi đọc wire value. Mỗi đoạn chọn một đối tượng trong scope trước đó.

Đường dẫn $root.tb_hierarchy.dut.g_lane[0].u_cell.result trong TB bắt đầu từ root và chỉ rõ top tb_hierarchy trước khi đi xuống leaf. TB đã kiểm đường dẫn này bằng kết quả qua port. Đây là đường dẫn của đúng ví dụ; không suy ra mọi simulator đều hỗ trợ mọi dạng hierarchical name.

Một tên phân cấp phụ thuộc cấu trúc elaborated design. Đổi tên u_cell hoặc chèn một wrapper có thể làm đường dẫn TB cũ không còn đúng dù hành vi port giữ nguyên. Parser/elaborator phải bắt lỗi tên; không thay giá trị expected để che lỗi kết nối hoặc lookup.

Kiểm 256 input và hai STEP khác nhau

Tính trước: byte a5 cho nibble thấp 5 cộng STEP 1 thành 6; nibble cao 10 cộng STEP 2 thành 12. Byte ff cho kết quả 16 và 17, nên output mỗi lane cần 5 bit.

Hai scope cùng tên value, hai parameter STEP khác nhau

Kiểm đường dẫn instance/generate/root và tách quan sát hierarchy khỏi đường dữ liệu qua port trên 256 input.

hierarchy_pair.sv RTL
`timescale 1ns/1psmodule scoped_cell #(parameter integer STEP = 1)  (input wire [3:0] data, output wire [4:0] result);  wire [3:0] value;  assign value = data;  assign result = {1'b0, value} + 5'(STEP);endmodulemodule hierarchy_pair(input wire [7:0] data, output wire [9:0] results);  genvar lane;  generate    for (lane = 0; lane < 2; lane = lane + 1) begin : g_lane      scoped_cell #(.STEP(lane + 1)) u_cell        (.data(data[lane*4 +: 4]), .result(results[lane*5 +: 5]));    end  endgenerateendmodule
tb_hierarchy.sv testbench
module tb_hierarchy;  timeunit 1ns;  timeprecision 1ps;  logic [7:0] data;  wire [9:0] results;  int value = 99;  int cases_checked = 0;  hierarchy_pair dut (.data(data), .results(results));  initial begin    for (int i = 0; i < 256; i++) begin      data = 8'(i);      #1;      if (int'(results[4:0]) != i % 16 + 1 || int'(results[9:5]) != i / 16 + 2 ||          int'(dut.g_lane[0].u_cell.value) != i % 16 ||          int'(dut.g_lane[1].u_cell.value) != i / 16 ||          dut.g_lane[0].u_cell.STEP != 1 || dut.g_lane[1].u_cell.STEP != 2 ||          $root.tb_hierarchy.dut.g_lane[0].u_cell.result !== results[4:0] || value != 99)        $fatal(1, "hierarchy mismatch at %0d", i);      cases_checked++;      if (i == 165) begin        $display("HIER RESULT tb_value=%0d low_value=%0d high_value=%0d low_step=%0d high_step=%0d",                 value, dut.g_lane[0].u_cell.value, dut.g_lane[1].u_cell.value,                 dut.g_lane[0].u_cell.STEP, dut.g_lane[1].u_cell.STEP);        $display("PORT RESULT low=%0d high=%0d root_low=%0d", results[4:0], results[9:5],                 $root.tb_hierarchy.dut.g_lane[0].u_cell.result);      end    end    $display("HIER CASES checked=%0d low=%0d high=%0d", cases_checked, results[4:0], results[9:5]);    $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: HIER RESULT tb_value=99 low_value=5 high_value=10 low_step=1 high_step=2PORT RESULT low=6 high=12 root_low=6HIER CASES checked=256 low=16 high=17

Kỳ vọng sau tổng hợp: hierarchy_pair: $_*DFF* = 0

Hierarchical reference chỉ đọc trong TB; RTL nối bằng port, không dùng force hoặc cross-module assignment.

Verilator 2-state kiểm dữ liệu 0/1. Tên instance/generate trong mô phỏng không bảo đảm còn nguyên sau synthesis tối ưu.

Chỉ kiểm các đường dẫn constant index và root đã viết, không claim mọi name lookup/package scope hoặc chỉ số generate runtime.

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 chạy đủ 256 byte. Oracle dùng chia/lấy dư số nguyên cho high/low nibble, kiểm cả port và wire nội bộ, parameter của hai instance và đường dẫn root. Sau vòng lặp mới in HIER CASES checked=256 low=16 high=17.

Parse/lint/sim/synth đều đạt. Simulation ở ví dụ này dùng Verilator với input 0/1; synthesis generic kiểm 0 FF. Không lấy tên mô phỏng làm lời hứa hierarchy sẽ còn nguyên sau flatten/optimization. [2]

Chọn phần tử dữ liệu khác chọn instance

g_lane[0] trong đường dẫn là chọn một scope generate bằng index hằng. Generate tạo cấu trúc tại elaboration; nó không phải một mảng các object có thể truy cập tùy ý bằng mọi biến runtime.

Nếu yêu cầu là chọn output lane trong lúc chạy, hãy đưa kết quả qua port/bus rồi chọn dữ liệu trên bus theo hợp đồng thiết kế. Thay số 0 trong đường dẫn bằng một biến không tự tạo phần cứng mux giữa các instance.

Bài không đưa một đoạn đường dẫn runtime không hợp lệ vào file rồi gọi nó đã mô phỏng. Phần đã kiểm chỉ dùng hai constant index. Quy tắc đầy đủ về index/hierarchical names phải tiếp tục đọc LRM và diagnostic của tool đang dùng.

Quan sát nội bộ khác nối thiết kế

RTL của ví dụ nối toàn bộ dữ liệu qua named ports. Hierarchical references chỉ được đọc trong TB để đối chiếu và debug, không dùng để ghi đè logic bên trong hoặc force output.

Với interface ổn định, test kiểm ở port ít phụ thuộc tên nội bộ hơn. Probe hierarchy hữu ích để định vị lỗi, nhưng cần biết chi phí bảo trì khi refactor. Trong flow lớn, bind/interface/probe được thiết kế riêng có thể phù hợp hơn; bài này chưa kiểm các cơ chế đó.

Cũng không suy ra mọi cross-module reference đều synthesizable vì TB đọc được tín hiệu. Yosys ở đây chỉ tổng hợp RTL nối bằng port, không tổng hợp TB có $root.

Tên đúng nhưng scope sai

  • Nhầm scoped_cell là instance name thay vì module type.
  • Đọc lane 0 trong khi lỗi đang ở lane 1; cả hai có wire tên value nên nhìn lướt khó thấy.
  • Quên parameter override riêng và đoán cả hai cộng 1.
  • Dùng variable runtime ở nơi cần constant generate index.
  • Dựa vào tên nội bộ sau synthesis mà không kiểm báo cáo/netlist tương ứng.
  • Biến probe debug thành đường nối chức năng, làm kết nối ẩn khó review.

Tự kiểm tra

Tự kiểm tra: hier-ref

  1. Tại data=a5, dut.g_lane[1].u_cell.value bằng bao nhiêu?
  2. Tại data=a5, hai output low/high của ví dụ bằng gì?
  3. scoped_cell và u_cell lần lượt là gì?
  4. Nếu cần chọn lane theo input runtime, hướng nào đúng với hợp đồng bài?
  5. TB đọc được $root...result có bảo đảm đường dẫn còn nguyên sau synthesis không?

Debug bằng cây hierarchy

Bắt đầu từ top/instance đúng, đi qua từng named scope, xác nhận parameter của instance và so tín hiệu nội bộ với hợp đồng port. Sau khi tìm được nguyên nhân, sửa kết nối hoặc RTL đúng chỗ thay vì sửa expected để hợp thức hóa kết quả sai.

Bằng chứng phủ 256 byte và những đường dẫn đã nêu; chưa phủ toàn bộ namespace/package rules, force, bind, visibility sau tối ưu hoặc dữ liệu X/Z. Bài vẫn draft với LRM needs-spec-check.

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 A6.hier-ref: source và verification.json, Bằng chứng hierarchy. Trích dẫn; nguồn đã đọc toàn văn.
  3. IEEE 1800-2023 SystemVerilog LRM, Scopes, hierarchical names, generate blocks, root scope. Chưa đối chiếu với văn bản nguồn (chưa mở).
  4. Thử nghiệm A6.hier-ref: source và verification.json, Ví dụ đã chạy: source và verification.json. Trích dẫn; nguồn đã đọc toàn văn.
  5. Thử nghiệm A6.hier-ref: source và verification.json, hierarchy_pair.sv, tb_hierarchy.sv và verification.json. Trích dẫn; nguồn đã đọc toàn văn.