1. 程式人生 > >MyHDL中文手冊(三)—— 第一組示例

MyHDL中文手冊(三)—— 第一組示例

MyHDL示例入門。

一個基本的MyHDL模擬。

我們將用一個經典的HelloWorld樣式示例來介紹MyHDL。所有示例程式碼都可以在github【示例/手冊/】下的分發目錄中找到。下面是名為hello1.py的MyHDL模擬指令碼的內容:

from myhdl import block, delay, always, now

@block
def HelloWorld():

    @always(delay(10))
    def say_hello():
        print("%s Hello World!" % now())

    return say_hello

inst = HelloWorld()
inst.run_sim(30)

模擬結果如下:

$ python hello1.py
10 Hello World!
20 Hello World!
30 Hello World!
<class 'myhdl._SuspendSimulation'>: Simulated 30 timesteps

指令碼的第一行從myhdl包匯入許多物件。在Python中,我們只能使用在原始檔中定義的識別符號。然後,我們定義了一個名為HelloWorld的函式。在MyHDL中,一個硬體模組由一個用block裝飾器裝飾的函式來建模。選擇block這個名字是為了避免與Python的模組概念混淆。我們後續將使用這個術語。
HelloWorld函式的引數表用於定義硬體塊的介面。在第一個示例中,介面為空。在頂層函式內部,我們聲明瞭一個名為Say_Hello

的本地函式,它定義了期望的行為。此函式使用一個always(總是)裝飾器來修飾,該裝飾器以一個delay(延遲)物件作為其引數。其含義是,當超過指定的延遲間隔時,將執行該函式;如果模擬允許,一直重複“延遲、執行”的步驟。

在幕後,always裝飾器建立一個Python生成器,並重用被修飾函式的名稱作為生成器的名字。生成器是MyHDL中的基本物件,我們將在後面對它們進行更多的討論。

最後,頂層函式返回Say_Hello生成器。
以上是定義硬體塊內容的基本MyHDL程式碼模式的最簡單情況。我們將進一步描述一般情況。

在MyHDL中,我們通過呼叫相應的函式來建立一個硬體塊的instance

(例項)。block裝飾器確保返回值實際上是塊類的一個例項,並帶有一個有用的API。在本例中,變數inst引用HelloWorld塊例項。為了模擬這個例項,我們使用它的run_sim方法。我們可以使用它來執行所需時間步長的模擬。

訊號與併發

實際的硬體設計通常是大規模併發的,這意味著大量的功能單元是並行執行的。通過允許任意數量的併發執行的生成器,MyHDL支援這種併發行為。
伴隨併發而來的是確定性通訊的問題。硬體語言使用特殊物件來支援併發程式碼之間的通訊順序的確定性(同一時刻並行的程式碼變化如何互相影響呢?)。特別是MyHDL有一個signal(訊號)物件,該物件大致按照VHDL signal(訊號)建模。
我們將通過擴充套件和修改第一個示例來演示訊號和併發。我們定義了一個硬體塊,它包含兩個生成器,一個驅動時鐘訊號,另一個對時鐘訊號的正沿敏感:

from myhdl import block, Signal, delay, always, now

@block
def HelloWorld():

    clk = Signal(0)

    @always(delay(10))
    def drive_clk():
        clk.next = not clk

    @always(clk.posedge)
    def say_hello():
        print("%s Hello World!" % now())

    return drive_clk, say_hello


inst = HelloWorld()
inst.run_sim(50)

時鐘驅動函式clk_driver驅動時鐘訊號,它定義了在一定延遲後連續切換時鐘訊號的生成器。訊號的新值是通過賦值給它的next屬性來指定的。這是與VHDL訊號assign和Verilog非阻塞分配等效的MyHDL。

Say_Hello函式是從第一個示例修改的。它對時鐘訊號的上升沿敏感,該上升沿是由訊號的邊緣屬性指定的。邊緣說明符是always裝飾器的引數。因此,裝飾功能將在每個上升的時鐘邊緣上執行。

clk訊號構造為初始值0。一個generator(生成器)驅動它,另一個對它很敏感。這種通訊的結果是generator並行執行,但它們的動作是由時鐘訊號協調的。

當我們執行模擬時,我們得到:

$ python hello2.py
10 Hello World!
30 Hello World!
50 Hello World!
<class 'myhdl._SuspendSimulation'>: Simulated 50 timesteps

引數、埠和層次結構

我們已經看到,MyHDL使用函式來建模硬體塊。到目前為止,這些函式還沒有引數。然而,要建立通用的、可重用的塊,我們將需要引數。例如,我們可以建立一個時鐘驅動程式塊,如下所示:

from myhdl import block, delay, instance
@block
def ClkDriver(clk, period=20):
    lowTime = int(period / 2)
    highTime = period - lowTime

    @instance
    def drive_clk():
        while True:
            yield delay(lowTime)
            clk.next = 1
            yield delay(highTime)
            clk.next = 0

    return drive_clk

所述塊封裝時鐘驅動生成器。它有兩個引數。
第一個引數是clk是時鐘訊號。訊號引數是MyHDL建模DFN:port:的方法。第二個引數是時鐘週期,預設值為20。

由於時鐘的低時間可能不同於高時間,在奇數週期的情況下,我們不能再使用具有單個延遲值的always裝飾器。相反,drive_clk函式現在是一個具有所需行為的顯式定義的生成器函式。它用instance(例項)裝飾器來裝飾。您可以看到drive_clk是一個生成器函式,因為它包含yield語句。

當呼叫生成器函式時,它返回生成器物件。這基本上就是instance裝飾器所做的事情。它不像always裝飾器那樣複雜,但是它可以用來從任何本地生成器函式建立生成器。

yield語句是一個通用的Python構造,但是MyHDL以一種特定的方式使用它。它的含義與VHDL中的WAIT語句類似:語句掛起生成器的執行,它的子句指定生成器在恢復之前應該等待的條件。在這種情況下,生成器等待一定的延遲。

請注意,為了確保生成器“永久”執行,我們將其行為包裝在一個while True迴圈中。

類似地,我們可以定義一個一般的Hello函式,如下所示:

from myhdl import block, always, now
@block
def Hello(clk, to="World!"):

    @always(clk.posedge)
    def say_hello():
        print("%s Hello %s" % (now(), to))

    return say_hello

通過使用適當的引數呼叫函式,我們可以建立任意數量的例項。層次結構可以通過在更高級別的函式中定義例項並返回它們來建模。對於任意數量的層次結構,可以重複此模式。因此,MyHDL例項的一般定義是遞迴的:例項或者是一個例項序列,或者是一個生成器。

例如,我們將建立一個包含四個低階函式例項的高階函式,並對其進行模擬:

from myhdl import block, Signal

from ClkDriver import ClkDriver
from Hello import Hello


@block
def Greetings():

    clk1 = Signal(0)
    clk2 = Signal(0)

    clkdriver_1 = ClkDriver(clk1)  # positional and default association
    clkdriver_2 = ClkDriver(clk=clk2, period=19)  # named association
    hello_1 = Hello(clk=clk1)  # named and default association
    hello_2 = Hello(to="MyHDL", clk=clk2)  # named association

    return clkdriver_1, clkdriver_2, hello_1, hello_2


inst = Greetings()
inst.run_sim(50)

在標準Python中,位置或命名引數關聯可以在例項化中使用,也可以在例項化時混合使用。所有這些樣式都在上面的示例中得到了演示。如果有很多引數,命名關聯可能非常有用,因為在這種情況下呼叫中的引數順序並不重要。

模擬結果如下:

$ python greetings.py
9 Hello MyHDL
10 Hello World!
28 Hello MyHDL
30 Hello World!
47 Hello MyHDL
50 Hello World!
<class 'myhdl._SuspendSimulation'>: Simulated 50 timesteps

# 術語回顧

一些常用的術語在Python和硬體設計中有不同的含義。為了更好地理解這些差異,把這些差異明確化是很必要的。

Python中的模組引用特定檔案中的所有原始碼。一個模組可以通過匯入被其他模組重用。另一方面,在硬體設計中,模組通常是指具有正確定義的介面可重用硬體單元。因為這些含義非常不同,所以在MyHDL中為硬體模組選擇的術語是block

一個硬體塊可以通過例項化在另一個塊中重用。

Python(和其他面嚮物件語言)中的例項指的是由類建構函式建立的物件。在硬體設計中,例項是通過例項化block建立的硬體塊的具體體現。在MyHDL中,例如block例項實際上是特定類的例項。因此,這兩種意思並不完全相同,但它們很好地吻合。

通常,block和instance這兩個詞的含義應從上下文中明確。有時,為了清晰起見,我們用“硬體”或“MyHDL”來限定它們。

關於MyHDL和Python的幾點注意

在結束本章介紹時,強調MyHDL本身不是一種語言是有用的。底層語言是Python,MyHDL被實現為一個名為myhdl的Python包。此外,保持myhdl包儘可能簡約是設計目標之一,因此MyHDL描述非常“純Python”。

將Python作為底層語言在以下幾個方面具有重要意義:

Python是一種非常強大的高階語言。這為轉化為高生產力和複雜問題的提供瞭解決方案。
Python得到了一些非常聰明的頭腦的不斷改進,並得到了大量使用者的支援。Python完全得益於開源開發模型。
Python附帶了廣泛的標準庫。一些功能可能與MyHDL使用者直接相關:例如字串處理、正則表示式、隨機數生成、單元測試支援、作業系統介面和GUI開發。此外,還有數學、資料庫連線、網路程式設計、網際網路資料處理等模組。

# 總結與展望

以下是我們在本章中所學到的概述:

生成器是MyHDL模型的基本構件。它們提供了大規模併發和敏感性列表的建模方法。
MyHDL提供了從本地函式建立有用生成器的裝飾器和用於建立硬體塊的裝飾器。
硬體結構和層次結構用Python函式描述,用block裝飾器裝飾。signal物件用於併發生成器之間的通訊。塊例項提供了一種方法來模擬它。

這些概念足以開始使用MyHDL進行建模和模擬。

然而,MyHDL還有更多的內容。以下概述了從後面章節中可以學到的內容:

myhdl支援面向硬體的型別,這使得編寫典型的硬體模型更加容易。這些都在第一章面向硬體的型別中進行了描述。
MyHDL支援複雜的高階建模技術。這在第一章高階建模中進行了描述。
MyHDL允許在硬體設計中使用現代軟體驗證技術,如單元測試。這是單元測試的主題。
它可以與其他HDL語言(如Verilog和VHDL)共同模擬MyHDL模型。這在與Verilog的聯合模擬章節中進行了描述。
最後但並非最不重要的是,MyHDL模型可以轉換為Verilog或VHDL,為矽實現提供一條路徑。這是本章轉換到Verilog和VHDL的主題。