MyHDL中文手册(六)—— RTL建模

介绍

RTL(寄存器传输层)是一种抽象建模层,通常用于编写可综合的模型。综合是指将HDL描述自动编译到ASIC或FPGA实现的过程。本章描述MyHDL如何支持它。

组合逻辑

模板

组合逻辑用代码模式描述如下:

from myhdl import block, always_comb

@block
def top(<parameters>):
    ...
    @always_comb
    def comb_logic():
        <functional code>
    ...
    return comb_logic, ...

always_comb装饰器描述组合逻辑。这个名称在SystemVerilog中引用了类似的结构。修饰函数是一个本地函数,它指定当逻辑的一个输入信号发生变化时会发生什么。always_comb装饰器自动推断输入信号。它返回一个对所有输入敏感的生成器,它在输入发生变化时执行该函数。

示例

下面是组合复用器的一个示例

from myhdl import block, always_comb, Signal
@block
def mux(z, a, b, sel):

    """ Multiplexer.
    z -- mux output
    a, b -- data inputs
    sel -- control input: select a if asserted, otherwise b
    """
    @always_comb
    def comb():
        if sel == 1:
            z.next = a
        else:
            z.next = b
    return comb

为了验证它,我们将用一些随机模式仿真逻辑。Python标准库中的随机模块可用于此目的。函数Randrange(N)返回一个小于n的随机自然整数。在测试平台代码中使用它来产生随机输入值。

import random
from myhdl import block, instance, Signal, intbv, delay
from mux import mux

random.seed(5)
randrange = random.randrange

@block
def test_mux():

    z, a, b, sel = [Signal(intbv(0)) for i in range(4)]

    mux_1 = mux(z, a, b, sel)

    @instance
    def stimulus():
        print("z a b sel")
        for i in range(12):
            a.next, b.next, sel.next = randrange(8), randrange(8), randrange(2)
            yield delay(10)
            print("%s %s %s %s" % (z, a, b, sel))

    return mux_1, stimulus

tb = test_mux()
tb.run_sim()

保持随机值的可重现性通常是有用的。这可以通过在代码中提供种子值来实现。运行产生以下输出:

$ python test_mux.py
z a b sel
5 4 5 0
3 7 3 0
2 2 1 1
7 7 3 1
3 1 3 0
3 3 6 1
6 2 6 0
1 1 2 1
2 2 2 0
3 0 3 0
2 2 2 1
3 5 3 0
<class 'myhdl.StopSimulation'>: No more events

时序逻辑

模板

RTL时序模型对时钟边缘很敏感。此外,它们可能对复位信号敏感。always_seq装饰器直接支持此模型:

示例

from myhdl import block, always_seq

@instance
def top(<parameters>, clock, ..., reset, ...):
    ...
    @always_seq(clock.posedge, reset=reset)
    def seq_logic():
        <functional code>
    ...
    return seq_logic, ...

always_seq装饰器自动推断重置功能。它检测需要重置的信号,并使用它们的初始值作为重置值(或者称为复位值)。重置信号本身需要指定为ResetSignal对象。例如:

reset = ResetSignal(0, active=0, async=True)

第一个参数指定被重置对象的初始值。Active激活参数指定重置处于有效状态的值,异步参数指定它是同步重置(True)还是同步重置(False)。如果不需要重置,则可以将None分配给always_seq参数中的重置参数(即reset=None)。
下面的代码描述了启用和异步重置的增量器。

from myhdl import block, always_seq

@block
def inc(count, enable, clock, reset):
    """ Incrementer with enable.

    count -- output
    enable -- control input, increment when 1
    clock -- clock input
    reset -- asynchronous reset input
    """
    
    @always_seq(clock.posedge, reset=reset)
    def seq():
        if enable:
            count.next = count + 1

    return seq

对于测试平台,我们将使用独立的时钟生成器、激励生成器和监视器。在应用足够的激励模式后,我们可以提出StopSimulation异常来停止仿真运行。小型增量器和少量模式的测试平台如下

import random
from myhdl import block, always, instance, Signal, \
    ResetSignal, modbv, delay, StopSimulation
from inc import inc

random.seed(1)
randrange = random.randrange

ACTIVE_LOW, INACTIVE_HIGH = 0, 1

@block
def testbench():
    m = 3
    count = Signal(modbv(0)[m:])
    enable = Signal(bool(0))
    clock  = Signal(bool(0))
    reset = ResetSignal(0, active=0, async=True)

    inc_1 = inc(count, enable, clock, reset)

    HALF_PERIOD = delay(10)

    @always(HALF_PERIOD)
    def clockGen():
        clock.next = not clock

    @instance
    def stimulus():
        reset.next = ACTIVE_LOW
        yield clock.negedge
        reset.next = INACTIVE_HIGH
        for i in range(16):
            enable.next = min(1, randrange(3))
            yield clock.negedge
        raise StopSimulation()

    @instance
    def monitor():
        print("enable  count")
        yield reset.posedge
        while 1:
            yield clock.posedge
            yield delay(1)
            print("   %s      %s" % (int(enable), count))

    return clockGen, stimulus, inc_1, monitor

tb = testbench()
tb.run_sim()

仿真结果如下

$ python test_inc.py
enable  count
   0      0
   1      1
   0      1
   1      2
   0      2
   1      3
   1      4
   1      5
   1      6
   1      7
   0      7
   0      7
   1      0
   0      0
   1      1
   1      2

可替换的模板

带有always_seq装饰器的模板非常方便,因为它自动推断重置功能。但是在同一个块中信号的重置值可能不统一,您可以使用更明确的模板,如下所示:

from myhdl import block, always

@block
def top(<parameters>, clock, ..., reset, ...):
    ...
    @always(clock.posedge, reset.negedge)
    def seq_logic():
       if not reset:
           <reset code>
       else:
           <functional code>

    return seq_logic,...

使用此模板,必须显式指定重置值。

有限状态机建模

有限状态机(FSM)建模在RTL设计中非常常见,因此值得特别关注。

为了代码清晰,状态值通常由一组标识符表示。用于此目的一个标准Python习惯用法是将一个整数范围分配给一个标识符元组,如下所示

>>> SEARCH, CONFIRM, SYNC = range(3)
>>> CONFIRM
1

然而,这种技术也有一些缺点。虽然明确的意图是标识符共同表示一组含义,但一旦定义了这些标识符,单独使用时就会失去组信息的指向(从单个标识符看不出其归属)。此外,标识符求值为整数,而标识符的字符串表示形式更可取。要解决这些问题,我们需要枚举类型。

MyHDL通过提供函数枚举来支持枚举类型。enum的参数是标识符的字符串表示形式,其返回值是枚举类型。标识符可作为该类型的属性使用。例如

>>> from myhdl import enum
>>> t_State = enum('SEARCH', 'CONFIRM', 'SYNC')
>>> t_State
<Enum: SEARCH, CONFIRM, SYNC>
>>> t_State.CONFIRM
CONFIRM

我们可以使用这种类型来构造一个状态信号,如下所示:

state = Signal(t_State.SEARCH)

作为示例,我们将使用帧控制器FSM。这是一个假想的例子,但类似的控制结构经常发现在电信应用。假设我们需要找到传入的字节帧的开始帧(SOF)位置。同步模式检测器持续查找成帧模式,并使用syncFlag信号将其指示给FSM。找到后,FSM将从初始搜索状态移动到确认状态。当在预期位置上确认syncFlag时,FSM声明同步,否则将退回到搜索状态。这个FSM可以按如下方式编码

from myhdl import block, always_seq, Signal, intbv, enum

ACTIVE_LOW = 0
FRAME_SIZE = 8
t_state = enum('SEARCH', 'CONFIRM', 'SYNC')

@block
def framer_ctrl(sof, state, sync_flag, clk, reset_n):

    """ Framing control FSM.

    sof -- start-of-frame output bit
    state -- FramerState output
    sync_flag -- sync pattern found indication input
    clk -- clock input
    reset_n -- active low reset

    """

    index = Signal(intbv(0, min=0, max=FRAME_SIZE)) # position in frame

    @always_seq(clk.posedge, reset=reset_n)
    def FSM():
        if reset_n == ACTIVE_LOW:
            sof.next = 0
            index.next = 0
            state.next = t_state.SEARCH

        else:
            index.next = (index + 1) % FRAME_SIZE
            sof.next = 0

            if state == t_state.SEARCH:
                index.next = 1
                if sync_flag:
                    state.next = t_state.CONFIRM

            elif state == t_state.CONFIRM:
                if index == 0:
                    if sync_flag:
                        state.next = t_state.SYNC
                    else:
                        state.next = t_state.SEARCH

            elif state == t_state.SYNC:
                if index == 0:
                    if not sync_flag:
                        state.next = t_state.SEARCH
                sof.next = (index == FRAME_SIZE-1)

            else:
                raise ValueError("Undefined state")

    return FSM

此时,我们将使用这个示例来演示MyHDL如何支持波形查看。在仿真过程中,可以将信号变化写入VCD输出文件。然后可以在波形查看器工具(如GTKWave)中加载和查看VCD文件。

该特性的用户界面由一个单独的函数traceSignals组成。为了解释它是如何工作的,回想一下在MyHDL中,实例是通过将函数调用的结果赋值给实例名来创建的。例如:

tb_fsm = testbench()

要启用VCD跟踪,应该按如下方式创建实例:

tb_fsm = traceSignals(testbench)

请注意,traceSignals的第一个参数由未调用的函数组成。traceSignals通过调用其控制下的函数,收集有关层次结构和要跟踪的信号的信息。除了函数参数外,traceSignals还接受任意数量的非关键字和关键字参数,这些参数将传递给函数调用。

帧控制器示例的一个小测试平台(启用了信号跟踪)如下所示:

import myhdl
from myhdl import block, always, instance, Signal, ResetSignal, delay, StopSimulation
from fsm import framer_ctrl, t_state

ACTIVE_LOW = 0

@block
def testbench():

    sof = Signal(bool(0))
    sync_flag = Signal(bool(0))
    clk = Signal(bool(0))
    reset_n = ResetSignal(1, active=ACTIVE_LOW, async=True)
    state = Signal(t_state.SEARCH)

    frame_ctrl_0 = framer_ctrl(sof, state, sync_flag, clk, reset_n)

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

    @instance
    def stimulus():
        for i in range(3):
            yield clk.negedge
        for n in (12, 8, 8, 4):
            sync_flag.next = 1
            yield clk.negedge
            sync_flag.next = 0
            for i in range(n-1):
                yield clk.negedge
        raise StopSimulation()

    return frame_ctrl_0, clkgen, stimulus
tb = testbench()
tb.config_sim(trace=True)
tb.run_sim()

当我们运行测试平台时,它会生成一个名为testben.vcd的VCD文件。当我们将此文件加载到GTKWave中时,我们可以查看波形:
state enum gtkwave
信号以适当的格式转储。这种格式是在信号构造时,从初始值的类型推断出来的。特别是,bool信号被转储为单个比特。(这只适用于Python2.3,当bool成为一个单独的类型时)。同样,具有定义位宽的intbv信号被转储为位向量。为了支持一般情况,其他类型的信号被转储为字符串表示形式,由标准str函数返回。
注意:支持文字字符串表示不是VCD标准的一部分。GTKWave扩展了对于字符串原文ascii码的支持。要生成标准的VCD文件,只需要使用具有定义位宽的信号。

猜你喜欢

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