天天看點

MyHDL中文手冊(三)—— 第一組示例一個基本的MyHDL仿真。信号與并發參數、端口和層次結構關于MyHDL和Python的幾點注意

MyHDL示例入門。

  • 一個基本的MyHDL仿真。
  • 信号與并發
  • 參數、端口和層次結構
  • 關于MyHDL和Python的幾點注意

(本系列基于MyHDL 0.10.0 版 on Python3)

譯自 http://docs.myhdl.org/en/stable/manual/intro.html

一個基本的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的主題。