MyHDL中文手册(四)——面向硬件的类型


(本系列基于MyHDL 0.10.0 版 on Python3)
译自 http://docs.myhdl.org/en/stable/manual/intro.html

intbv类

硬件设计涉及到处理位和面向位的操作。标准Python类型int具有所需的大部分特性,但缺乏对索引(比如data[3])和切片(比如head[7:4])的支持。因此,MyHDL提供了intbv类。选择该名称是为了表示具有位向量风格的整数(Integer Bit Vector)。

intbv可以透明地与其他类似整数的类型一起工作。与类int一样,它为位操作提供了对底层2的补码表示值的访问。但是,与int不同的是,它是一个可变类型(注意python整数类型值不可以改变)。这意味着它的值可以在创建对象之后通过方法和操作符(如切片赋值)进行更改。

intbv支持与int相同的算术运算符。此外,它提供了一些功能,使其适合于硬件设计。首先,可以限制允许值的范围。这使得在仿真过程中可以在运行时检查值。后端工具可以确定表示对象的最小位宽。其次,它通过提供索引和切片接口来支持位级操作。

intbv对象一般构造如下:

intbv([val=None] [, min=None]  [, max=None])

Val是初始值。min和max可用于约束值。遵循Python约定,min是包含在内的,max是不包含在内的。因此,允许的值范围是[min,max-1]。
让我们看一些例子。创建一个无约束的intbv对象如下所示:

>>> a = intbv(24)

创建对象后,min和max用作可检查的属性。此外,标准的Python函数len可用于确定位宽。如果我们检查以前创建的对象,我们得到:

>>> a
intbv(24)
>>> print(a.min)
None
>>> print(a.max)
None
>>> len(a)
0

因为实例化是不受约束的,所以min和max属性是未定义的。同样,位宽也是未定义的,它由返回值0表示。
创建受约束的intbv对象如下所示:

>>> a = intbv(24, min=0, max=25)

创建受约束的intbv对象如下所示:

>>> a = intbv(24, min=0, max=25)

现在检查该对象可以得到:

>>> a
intbv(24)
>>> a.min
0
>>> a.max
25
>>> len(a)
5

我们看到允许的值范围是0->24,需要5位来表示对象。
最小和最大绑定属性允许对表示范围进行细粒度控制和错误检查。边界值不必是对称的或2的幂。在所有情况下,位宽都被适当地设置为表示该范围内的值。例如:

>>> a = intbv(6, min=0, max=7)
>>> len(a)
3
>>> a = intbv(6, min=-3, max=7)
>>> len(a)
4
>>> a = intbv(6, min=-13, max=7)
>>> len(a)
5

按位索引

硬件设计中通常要求能够访问各个位。intbv类实现了一个索引接口,该接口提供对基于2的补码表示值的访问。下面说明位索引读取访问:

>>> from myhdl import bin
>>> a = intbv(24)
>>> bin(a)
'11000'
>>> int(a[0])
0
>>> int(a[3])
1
>>> b = intbv(-23)
>>> bin(b)
'101001'
>>> int(b[0])
1
>>> int(b[3])
1
>>> int(b[4])
0

我们使用MyHDL提供的bin函数,因为它显示负值的两个补码表示,而不像Python的内建函数具有相同的名称。请注意,较低的位索引对应于数据的低比特LSB。下面的代码演示了位索引赋值:

>>> bin(a)
'11000'
>>> a[3] = 0
>>> bin(a)
'10000'
>>> a
intbv(16)
>>> b
intbv(-23)
>>> bin(b)
'101001'
>>> b[3] = 0
>>> bin(b)
'100001'
>>> b
intbv(-31)

位的切片

intbv类型还支持位切片,用于两种读取访问分配。例如:

>>> a = intbv(24)
>>> bin(a)
'11000'
>>> a[4:1]
intbv(4)
>>> bin(a[4:1])
'100'
>>> a[4:1] = 0b001
>>> bin(a)
'10010'
>>> a
intbv(18)

根据最常见的硬件约定,与标准Python不同,切片范围是向下的(41)。在标准Python中,切片范围是半开放的:不包括最高索引位。所以intbv的切片也不包括MSB。但是,与标准Python不同的是,该索引对应于切片索引最左边的项。这里,就出现了MyHDL与生成后的verilog最大的区别。
两个索引都可以从切片中省略。如果省略最右边的索引,则默认为0。如果省略最左边的索引,其含义是访问“所有”高阶位。例如:

>>> bin(a)
'11000'
>>> bin(a[4:])
'1000'
>>> a[4:] = '0001'
>>> bin(a)
'10001'
>>> a[:] = 0b10101
>>> bin(a)
'10101'

切片半开放方式一开始可能看起来很尴尬,但它在实践中有助于位宽计数。例如,片a[8:]正好有8位。同样,片a[7:2]也有7-2=5位。您可以这样考虑:对于一个片[i:j],只包含索引i以下的位,而索引j的位是包含的最后一个位。

当对intbv对象进行切片时,将返回一个新的intbv对象。这个新的intbv对象始终是正的,值边界是根据片指定的位宽设置的。例如:

>>> a = intbv(6, min=-3, max=7)
>>> len(a)
4
>>> b = a[4:]
>>> b
intbv(6L)
>>> len(b)
4
>>> b.min
0
>>> b.max
16

在本例中,原始对象是用一个等于其位宽的切片来分割的。返回的对象具有相同的值和位宽,但其值范围由所有可以由位宽表示的正值组成。

切片返回的对象是正的,即使原始对象是负的:

>>> a = intbv(-3)
>>> bin(a, width=5)
'11101'
>>> b = a[5:]
>>> b
intbv(29L)
>>> bin(b)
'11101'

在本例中,两个对象的位模式在位宽范围内是相同的,但是它们的值有相反的符号。

有时,硬件工程师喜欢通过直接定义对象的位宽来约束对象,而不是定义允许的值范围。使用intbv类的切片属性,可以这样做:

>>> a = intbv(24)[5:]

这里实际发生的是首先创建一个无约束的intbv,然后对其进行切片。切片intbv返回一个新的intbv,其中包含设置适当的约束。现在检查该对象显示:

>>> a.min
0
>>> a.max
32
>>> len(a)
5

请注意,max属性是32,与5位一样,可以表示范围0->31。以这种方式创建intbv很方便,但缺点是只能指定0到2次方之间的正值范围。

modvb类

在硬件建模中,经常需要对环回行为进行优雅的建模。intbv实例不自动支持这一点,因为它们断言任何赋值都在绑定约束内。然而,环回建模可以简单明了。例如,出于其它目的,计数器的环回条件通常可以显而易见地表示出来。在许多场景中,求余操作符提供了一个优雅的单行代码:

count.next = (count + 1) % 2**8

但是,intbv类型不支持某些有趣的情况。例如,我们将使用变量和增强型赋值来描述一个自由运行的计数器,如下所示:

count_var += 1

对于intbv类型,这是不可能的,因为我们不能将取模(或者求余)行为添加到这个描述中。增加的左移存在类似的问题,如下所示:

shifter <<= 4

为了直接支持这些操作,MyHDL提供了modbv类型。modbv被实现为intbv的一个子类。这两个类具有相同的接口,并以一种简单明了的方式进行算术操作。唯一的区别是边界是如何处理的:超限值会导致intbv出现错误,而modbv则会被环回。例如,上面的模计数器可以建模如下:

count = Signal(modbv(0, min=0, max=2**8))
...
count.next = count + 1

环绕行为一般定义如下:

val = (val - min) % (max - min) + min

在典型情况下,当min=0时,这会减少到:

val = val % max

有符号和无符号的表示法

intbv被设计成尽可能高的级别。intbv对象的底层值是Python int,它表示为具有“不确定”位宽的2的补码。范围界限仅用于错误检查,并用于计算表示所需的最小位宽。因此,算术可以像普通整数一样执行。

相反,诸如Verilog和VHDL之类的HDL通常要求设计人员处理表示问题,特别是可综合代码。它们提供低级别的类型,如有符号和无符号的算术。这种类型的算术规则要比普通整数复杂得多。

在某些情况下,将intbv对象解释为“有符号”和“无符号”可能是有用的。基本上,它取决于属性min。如果min<0,则对象是“有符号的”,否则它是“无符号的”。“有符号”对象的位宽将占一个符号位,而“无符号”对象的位宽则不会,因为这是多余的。从前面的章节中,我们了解到切片操作的返回值始终是“unsigned”。

在某些应用中,希望将“unsigned”的intbv转换为“signed”,换句话说,将MSB位解释为符号位。MSB位是对象位宽内的最高顺序位。为此,intbv提供了intbv.signed方法。例如:

>>> a = intbv(12, min=0, max=16)
>>> bin(a)
'1100'
>>> b = a.signed()
>>> b
-4
>>> bin(b, width=4)
'1100'

Signed把原始对象值的MSB位扩展为高阶位,并以整数的形式返回结果。自然,对于“有符号”的返回值将始终与原始值相同,因为它已经有了符号位。
作为一个例子,让我们以一个8位宽的数据总线为例,它将被建模如下:

data_bus = intbv(0)[8:]

现在考虑在这个数据总线上传输一个复数。数据总线的上4位用于实值,下4位用于虚值。由于实值和假想值都有正负值范围,我们可以将它们从数据总线中分割出来,并按如下方式进行转换:

real.next = data_bus[8:4].signed()
imag.next = data_bus[4:].signed()

猜你喜欢

转载自blog.csdn.net/zt5169/article/details/83865842