Virtual dispatch và cast: kiểu handle khác với kiểu object

Khoảng 45 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ẽ

  • So sánh virtual và nonvirtual dispatch qua base handle
  • Dùng super trong override mà vẫn giữ contract
  • Kiểm $cast thành công, thất bại và destination không đổi

API chung cần gọi đúng hành vi của object thực

Một array Operation giữ ba loại xử lý. Caller chỉ biết API score, còn từng object quyết định công thức. Nếu quên virtual ở extension point base, môi trường có thể luôn gọi hành vi mặc định dù đã tạo derived object đúng.

Virtual chọn override theo object thực

Operation.score trả x+1; DoubleOp trả 2 x; OffsetOp gọi super.score(x)+3 nên trả x+4. TB lưu cả ba qua Operation handles rồi kiểm từng input 0..7. Việc gán handle không clone hoặc đổi runtime type của object.

[1]

Method thường vẫn đi theo kiểu handle ở case này

Tag là nonvirtual ở cả Operation và DoubleOp. Qua ops[1] kiểu Operation, tag trả 1; qua twice kiểu DoubleOp, tag trả 2. Đừng dùng kết quả nonvirtual tag để kết luận score cũng dispatch như thế; qualifier được quyết định theo từng method.

Override có thể dựa trên phần base rõ ràng

OffsetOp giữ phép cộng 1 của base rồi thêm 3. Super call trong implementation không ngăn caller qua base handle tới override của OffsetOp. Thay toàn bộ công thức hoặc dùng lại phần base là lựa chọn thiết kế cần được kiểm bằng oracle độc lập.

Downcast có kiểm tra thay vì tin mọi base handle

Function-form $cast(view, ops[1]) thành công vì object là DoubleOp. Cast ops[0] là Operation object vào cùng destination phải trả 0 và giữ view cũ. Main kiểm cả return value lẫn identity sau thất bại; không chỉ nhìn field tình cờ có giá trị đúng.

Đếm đúng số call được cộng vào tổng

Tám input cho ba operation tạo 24 kết quả được cộng: base 36, double 56, offset 60, tổng 152. TB gọi mỗi operation một lần mỗi input rồi dùng observed cho assertions và cộng tổng; không gọi lặp chỉ để kiểm rồi làm lệch accounting.

Derived method virtual không sửa được mọi base API đã nonvirtual

Mutation bỏ virtual ở score của Operation bị assertion virtual dispatch bắt. Không sửa caller sang kiểu DoubleOp để lấy xanh, vì caller qua base handle là mục tiêu của ví dụ. Ghi qualifier ở base cùng contract mà môi trường thực sự dùng.

Dispatch, nonvirtual tag và hai cast

Expected trước chạy: 24 kết quả cộng sum 152, base_tag1/derived_tag2, cast accept 1/reject 0 và destination giữ identity sau reject. Chỉ kiểm function-form $cast/class hierarchy này; không claim mọi cast, null input hoặc X/Z behavior.

Virtual dispatch và cast: kiểu handle khác với kiểu object

Expected tính trước chạy: VIRTUAL CHECK calls=24 sum=152 base_tag=1 derived_tag=2 cast_accept=1 cast_reject=0 retained=1

tb.sv testbench
`timescale 1ns/1psclass Operation;  virtual function int score(int value);return value+1;endfunction  function int tag();return 1;endfunctionendclassclass DoubleOp extends Operation;  virtual function int score(int value);return 2*value;endfunction  function int tag();return 2;endfunctionendclassclass OffsetOp extends Operation;  virtual function int score(int value);return super.score(value)+3;endfunctionendclassmodule tb_virtual;  Operation ops[3];  DoubleOp twice,view,kept;  OffsetOp offset_op;  int total=0,calls=0,accepted,rejected;  int observed[3];  initial begin    ops[0]=new();twice=new();offset_op=new();ops[1]=twice;ops[2]=offset_op;    for(int n=0;n<8;n++)begin      foreach(ops[i])observed[i]=ops[i].score(n);      if(observed[0]!=n+1||observed[1]!=2*n||observed[2]!=n+4)$fatal(1,"virtual dispatch n=%0d",n);      foreach(ops[i])begin total+=observed[i];calls++;end    end    accepted=$cast(view,ops[1]);kept=view;rejected=$cast(view,ops[0]);    if(total!=152||calls!=24||ops[1].tag()!=1||twice.tag()!=2||accepted!=1||rejected!=0||view!=kept||view!=twice)      $fatal(1,"virtual/nonvirtual or checked downcast");    $display("VIRTUAL CHECK calls=24 sum=152 base_tag=1 derived_tag=2 cast_accept=1 cast_reject=0 retained=1");    $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 (Verilator 5.050, 2-state)

Output phải khớp: VIRTUAL CHECK calls=24 sum=152 base_tag=1 derived_tag=2 cast_accept=1 cast_reject=0 retained=1

Verilator 2-state, không claim X/Z hoặc synthesis.

24 score invocation trên ba object/input 0..7, nonvirtual tag và function-form cast accept/reject/retained; không claim mọi cast hoặc null/XZ input.

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.

[2]

Tự kiểm tra

Tự kiểm tra: virtual-poly

  1. Tổng score của ba operation trên input 0..7 là bao nhiêu?
  2. Function-form cast DoubleOp từ ops[1] rồi ops[0] trả gì?
  3. Nonvirtual tag qua ops[1] và twice lần lượt trả gì?
  4. Sau cast thất bại vào view, main đòi điều gì?
  5. TB dùng observed để gọi score mấy lần trong vòng input?

Kiểm cả call site và dynamic object

Với mỗi call, ghi kiểu khai báo handle, kiểu object và qualifier của method trong base API. Với cast, kiểm return trước khi dùng destination và giữ assertion cho failure path. Đừng lấy class name trong biến làm bằng chứng runtime type.

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

  1. SystemVerilog Virtual Methods — bản chụp resources.zip, Nguồn đào tạo đã đọc. Trích dẫn; nguồn đã đọc các mục liên quan.
  2. Thí nghiệm nguyên bản ex.b5.virtual-poly, Bằng chứng thực nghiệm. Trích dẫn; nguồn đã đọc toàn văn.
  3. IEEE 1800-2023 SystemVerilog LRM, Class handles, inheritance, virtual methods, access and parameterization. Chưa đối chiếu với văn bản nguồn (chưa mở).
  4. Thí nghiệm nguyên bản ex.b5.virtual-poly, Source, self-checks, verification.json và mutation/capability probes. Trích dẫn; nguồn đã đọc toàn văn.
  5. SystemVerilog Virtual Methods — bản chụp resources.zip, Without virtual; With virtual. Trích dẫn; nguồn đã đọc các mục liên quan.