天天看點

MyHDL中文手冊(四)——面向硬體的類型intbv類按位索引位的切片modvb類有符号和無符号的表示法

intbv和modbv

  • intbv類
  • 按位索引
  • 位的切片
  • modvb類
  • 有符号和無符号的表示法

(本系列基于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()