PAYATHON 2026

SystemVerilog 的 $cast 为何在 VCS 中失败,而 Aldec 未报错?

数据小陈

结论:示例中的 $cast(c, p) 应当失败

p_class p = new(); 创建了一个实际类型为 p_class 的对象。$cast(c, p) 试图让 c_class 句柄引用该对象,但这个对象没有 c_class 扩展的成员,因此转换应当失败。

VCS 报出的 Dynamic cast failed 与代码语义一致。Aldec 没有报错,不能据此判断转换成功,还需要检查返回值、完整日志和运行配置。

$cast 检查的是句柄所引用对象的实际类型

句柄的声明类型和所引用对象的实际类型可能不同。例如:

p_class p = new();
c_class c = new();

这里创建了两个独立对象:

  • p 声明为 p_class,引用一个 p_class 对象。
  • c 声明为 c_class,引用另一个 c_class 对象。

c_class extends p_class 表示每个 c_class 对象都包含从 p_class 继承的部分,所以子类句柄可以直接赋给父类句柄:

p = c;

赋值后,p 的声明类型仍是 p_class,但它引用的实际对象是 c_class。此时执行 $cast(c, p),转换就可以成功。

对于本例中的非空对象句柄,向下转换要求源句柄引用的对象属于目标类,或属于目标类的进一步派生类。运行时检查通过后,目标句柄引用同一个对象。

$cast 不会创建对象、复制成员,也不会给 p_class 对象补上 c_Var。类型信息的具体存储方式属于仿真器的实现细节,不能仅凭这段代码推断。

正确演示成功的向下转换

保留原来的两个类定义,将 top 替换为下面的代码:

module top;
  initial begin
    p_class p;
    c_class original;
    c_class recovered;

    original = new();
    original.p_Var = 10;
    original.c_Var = 20;

    // Upcast: both handles refer to the same c_class object.
    p = original;

    // Downcast: check the actual object type at runtime.
    if ($cast(recovered, p)) begin
      recovered.display();
    end else begin
      $fatal(1, "Expected a c_class-compatible object");
    end
  end
endmodule

按照语言语义,这里的转换应成功,display() 应输出:

p_Var = 10
c_Var = 20

这是根据代码推导的预期结果,并非在两款仿真器中实际运行的测试记录。转换成功后,original、p 和 recovered 引用同一个对象,通过任一句柄修改可访问的成员,修改都会作用于该对象。

实际使用中,接口可能返回父类句柄,而调用方需要访问特定子类的成员。此时可以用 $cast 检查实际对象是否满足要求。

失败处理取决于调用方式

$cast 的参数顺序是目标在前、源在后:

$cast(destination, source);

两种调用方式的区别如下:

调用方式成功时失败时
作为 task:$cast(c, p);更新目标句柄产生运行时错误,目标句柄保持原值
作为 function:ok = $cast(c, p);返回 1 并更新目标句柄返回 0,目标句柄保持原值,不因转换失败自动报运行时错误

任务式和函数式调用的失败处理规则不同。如果需要自行处理类型不匹配的情况,应检查返回值。可参考 Siemens 的 $cast 使用说明和 Accellera 关于两种调用形式的说明。

在原示例中,可以将 $cast(c, p); 替换为:

if (!$cast(c, p)) begin
  $display("Cast failed; c still refers to its original object");
end
c.display();

这次转换应返回 0。失败不会改写 c,因此后面的 c.display() 仍可访问先前创建的子类对象,并输出 10 和 20。看到这两个值并不表示转换成功。

怎样排查 VCS 与 Aldec 的差异

先用函数式调用记录转换结果。保留原类定义,使用以下最小测试:

module top;
  initial begin
    p_class p;
    c_class c;
    int ok;

    p = new();
    c = new();

    ok = $cast(c, p);
    $display("cast status = %0d", ok);
  end
endmodule

这里预期的 cast status 是 0。函数式调用失败本身不会自动报错,运行时只看到状态输出是正常的。

随后对照两款工具的结果:

  1. 确认使用相同源码、相同顶层模块,并启用 SystemVerilog 编译模式。
  2. 记录工具版本、编译和运行选项,查看完整日志中的错误、警告及诊断抑制信息。
  3. 再单独测试任务式调用,检查失败诊断是否被隐藏、降级,或受其他配置影响。

如果 Aldec 的函数式调用也返回 0,说明转换结果一致,应继续调查原先为何没有看到任务式调用的错误诊断。如果返回 1,则需要结合具体版本和配置进一步复现,不能直接认定这是合法转换。

原例的 display() 没有声明为 virtual,这不影响 $cast 的成败。只有希望通过父类句柄调用方法,并根据实际对象类型选择子类实现时,才需要讨论虚方法派发。

备注:内容仅供参考。