介绍
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中时,我们可以查看波形:
信号以适当的格式转储。这种格式是在信号构造时,从初始值的类型推断出来的。特别是,bool信号被转储为单个比特。(这只适用于Python2.3,当bool成为一个单独的类型时)。同样,具有定义位宽的intbv信号被转储为位向量。为了支持一般情况,其他类型的信号被转储为字符串表示形式,由标准str函数返回。
注意:支持文字字符串表示不是VCD标准的一部分。GTKWave扩展了对于字符串原文ascii码的支持。要生成标准的VCD文件,只需要使用具有定义位宽的信号。