两个数据变量进行某个操作(op),产生一个输出,这个输出存在一个匿名变量中。这个匿名变量就是以OpBinding的形式绑定到hardware graph中的。
0. 这里先简单对 "=" 和 ":="做一个讨论。
假设 a & b 的结果输出到一个匿名变量,记为anonymousVariable。
a & b的用法有两种:
1) c := a & b
":="的意思为连接。变量c作为下游,连接到anonymousVariable,接收其值。即anonymousVariable的值的变化会传播到下游c中。
2) c = a & b
"="的意思是赋予。之所以不说赋值,是因为更多情况下赋予的不是值,而是引用。
这里就是把anonymousVariable赋给c。就相当于给a & b的输出这个匿名变量取了一个名字,叫做c。
因为anonymousVariable是匿名的,所以这里使用c来简化引用也是有意义的。
总结下来:
1) 两者的区别在于,一个是匿名变量本身,一个是匿名变量的下一级连接。
2) ":="是连接到的意思(connect to);
3) "="也不是赋值,而是赋引用,取名字或者起别名的意思;
1. OpBinding
1) 两个操作数:a op b
以a & b为例,方法&定义在UInt中:
这里a是this,b是that。&的结果为UInt(this.width max that.width),这是一个匿名变量。
binop定义在Bits中:
其中a是this, b是other,dest为匿名变量。
这里要求this, other亦即a, b都是已经绑定了的(设想一下,输入输出端口已经绑定了,内部变量使用Wire/Reg绑定了)。dest是新创建的变量,还没有绑定。
DefPrim定义了一个Definition,无关绑定。其id为dest。
case class DefPrim[T <: Data](sourceInfo: SourceInfo, id: T, op: PrimOp, args: Arg*) extends Definition
pushOp定义于Builder中:
这里cmd.id即为dest,调用其bind进行绑定。绑定类型为OpBinding。
2) 一个操作数:取反
与1)类似。
3) 一个操作数:截取部分位
如ByteSelector中:
io.in(7, 0)这个截取定义于Bits类中:
其中,UInt(Width(w))为匿名变量。
pushOp与之前定义一致。
可见位截取生成的临时变量也是使用OpBinding绑定到hardware graph中的。
2. ReadOnlyBinding
OpBinding为只读绑定,即ReadOnlyBinding。
只读绑定的变量,只能被读取,而不能被赋值。换一种说法:在硬件模型(hardware graph)中,只读绑定的变量,只能作为上游输出值(传播值),而不能作为下游,接收值(被传播)。
3. 连接的左值
只读绑定的变量只能作为右值,而不能作为左值。这里的左右是针对连接符号“:=”而言的。也是在连接方法中进行限制的。
连接符号定义于Data类中,
单个连接和批量连接,这里不做进一步区分解析。
这里如果this.topBinding为ReadOnlyBinding,则跑出异常。
所以this可以是WireBinding/RegBinding/PortBinding/MemPortBinding等的变量,而不能是OpBinding的变量。亦即,可以是模块的输出端口,内部定义的Wire()/Reg()临时变量,而不能是op的结果,也不能是比特截取的结果。