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装饰器的参数。因此,装饰功能将在每个上升的时钟边缘上执行。

扫描二维码关注公众号,回复: 3946243 查看本文章

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的主题。

猜你喜欢

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