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。函数式调用失败本身不会自动报错,运行时只看到状态输出是正常的。
随后对照两款工具的结果:
- 确认使用相同源码、相同顶层模块,并启用 SystemVerilog 编译模式。
- 记录工具版本、编译和运行选项,查看完整日志中的错误、警告及诊断抑制信息。
- 再单独测试任务式调用,检查失败诊断是否被隐藏、降级,或受其他配置影响。
如果 Aldec 的函数式调用也返回 0,说明转换结果一致,应继续调查原先为何没有看到任务式调用的错误诊断。如果返回 1,则需要结合具体版本和配置进一步复现,不能直接认定这是合法转换。
原例的 display() 没有声明为 virtual,这不影响 $cast 的成败。只有希望通过父类句柄调用方法,并根据实际对象类型选择子类实现时,才需要讨论虚方法派发。
备注:内容仅供参考。
