EDA开源仿真工具verilator入门4:访问函数介绍

访问函数(Visitor Function)是verilator算法实现的主要工作方式之一,本节将介绍一下访问函数。

访问者模式

Verilator采用“访问者”的设计模式(visitor pattern)来实现提炼和优化传递(passes),此方法允许传递算法与其操作的抽象语法树(AST)分离。

所有的访问者都是类VNVisitor的派生类,而AstNode的所有派生类都实现了accept方法,accept方法将VNVisitor的派生类实例作为参数,进而可以应用VNVisitor的visit方法到活跃的AstNode实例(AstNode派生类的accept方法以VNVisitor的派生类实例作为参数并调用对应实例的visit函数并通过this指针将VNVisitor派生实例的指针传入)。

一种可能的问题是,调用accept方法可能会执行一种编辑,从而有可能毁坏作为参数传入的node,AstNode提供了acceptSubtreeReturnEdits方法来执行accept并返回编辑后的node(即使原始node被销毁,若未销毁则返回原始node)。

访问者类通过重载visit函数来访问不同的AstNode派生类,如果没有发现具体的visit函数实现,根据C++的规则会按顺序逐层网上找,例如如果要执行AstIf类的函数accept,查找顺序如下:

void visit(AstIf* nodep)
void visit(AstNodeIf* nodep)
void visit(AstNodeStmt* nodep)
void visit(AstNode* nodep)

参数传递

有三种方式在visitor函数之间传递参数:

1. visitor类的成员变量,通常是信息从父类传递到子类;

2.用户特征(user attributes),每一个AstNode(注意是AST node,不是visitor)有5个用户特征,可以通过函数user1-user5()来获取,得到的是一个整数,或者获取一个指针(类型为AstNUser)通过函数user1p - user5p(图遍历中经常用到的一种技术)。

一个visitor首先通过调用AstNode::user#ClearTree()来清理掉其想用的用户特征,然后就可以用任何其想要的数据标记任何node的user#()。Readers仅仅会调用 nodep->user(),当然需要通过cast转换成合适的类型,所以经常会看到VN_CAST(nodep->userp(), SOMETYPE)这种类型转换操作。在每一个visitor的顶层都有注释来说明user()特征如何应用到visitor类的。例如:

// NODE STATE
// Cleared entire netlist
//   AstModule::user1p()     // bool. True to inline this module

这说明,在AstNetlist中user1ClearTree()被调用,每一个AstModule的user1()用来标识我们是否要内嵌它。

这些注释非常重要,必须要确定user#()在一个给定的AstNode类型中绝不允许被用做两种不同的目的。

需要注意,调用user#ClearTree的速度很快,不会遍历整棵树,所以它经常在每个模块中调用。

3.参数可以在visitor之间传递,这种方式类似于“正常”函数调用者(caller)到被调用者(callee)的方式。这是类型AstNUser的第二个参数vup,在大多数visitor函数中都会被忽略。V3Width实现了这种方式,但被证明比上面说的还要糟糕。(V3Width几乎是第一个写入的模块,由于其要在每个地方都传递vup进而拖慢了速度,将来很有可能被删除)。

迭代器

VNVisitor提供了一套迭代器来方便游走整个树结构,每一个操作都在VNVisitor类,并且取参数类型:AstNode*的参数。

iterate: 应用AstNode的accept方法到visitor函数;

iterateAndNextIgnoreEdit:应用一个List中每一个AstNode的accept方法(通过指针nextp和backp连接);

iterateAndNextNull:只有给出的node是非空的时候,应用一个List中每一个AstNode的accept方,法,如果一个node被一个accept调用编辑了,将再一次调用accept;

iterateListBackwards:从最后一个开始应用一个List中每一个AstNode的accept方法;

iterateChildren:在每一个子类执行操作op1p通过op4p按顺序应用iterateAndNextNull;

iterateChildrenBackwards:在每一个子类执行操作op1p通过op4p按顺序应用iterateListBackwards。

注意子类改变时迭代器的使用

Visitors经常用一个顶点去替代另一个顶点,V3Width和V3Const是两个主要的例子。对于这种替换的父类visitor,应该意识到调用这种迭代可能导致子类改变,例如:

// nodep->lhsp() is 0x1234000
iterateAndNextNull(nodep->lhsp());  // and under covers nodep->lhsp() changes
// nodep->lhsp() is 0x5678400
iterateAndNextNull(nodep->lhsp());

此段代码是可以正常运行的,因为即使第一次迭代会导致新的顶点替代lhsp(),编辑完之后会更新nodep->lhsp(),第二次调用将能正确地看到改变。而另一种情况:

lp = nodep->lhsp();
// nodep->lhsp() is 0x1234000, lp is 0x1234000
iterateAndNextNull(lp); **lhsp=NULL;**  // and under covers nodep->lhsp() changes
// nodep->lhsp() is 0x5678400, lp is 0x1234000
iterateAndNextNull(lp);

会引起bug或者core,因为lp是一个悬空指针(dangling pointer),因此建议设置lhsp=NULL来保证避免这些悬空指针。另一种是,V3Width在特殊情况下使用acceptSubtreeReturnEdits,该方法在单个顶点上操作并且如果存在新的指针会返回。需要注意acceptSubtreeReturnEdits并不会执行nextp()转到对应的链接。

lp = acceptSubtreeReturnEdits(lp)

鉴别派生类

一个基本的要求是鉴别AstNode的派生类具体是哪一个,例如,一个visitor对于AstIf和AstGenIf可能不区分visit方法,都对应函数:

void visit(AstNodeIf* nodep)

然而有些方法如果AstGenIf的话,会添加一些额外的代码。Verilator为此提供了VN_IS的方法,对于任意可能的node类型,如果是此类型则返回true否则返回false。所以visit方法可以使用:

if (VN_IS(nodep, AstGenIf) {
  <code specific to AstGenIf>
}

并且,VN_CAST类似于C++中的dynamic_cast,如果类型相符则返回指针,否则返回NULL空指针(ture 或者 false的情况用VN_IS,速度会更快)。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值