.. currentmodule:: myhdl .. _model-rtl: ************ RTL modeling ************ Introduction ============ .. index:: single: modeling; RTL style RTL (Register Transfer Level) is a modeling abstraction level that is typically used to write synthesizable models. :dfn:`Synthesis` refers to the process by which an HDL description is automatically compiled into an implementation for an ASIC or FPGA. This chapter describes how MyHDL supports it. .. _model-comb: Combinatorial logic =================== .. index:: single: combinatorial logic .. _model-comb-templ: Template -------- .. testsetup:: * from myhdl import * Combinatorial logic is described with a code pattern as follows:: def top(): ... @always_comb def combLogic(): ... return combLogic, ... The :func:`always_comb` decorator describes combinatorial logic. [#]_. The decorated function is a local function that specifies what happens when one of the input signals of the logic changes. The :func:`always_comb` decorator infers the input signals automatically. It returns a generator that is sensitive to all inputs, and that executes the function whenever an input changes. .. _model-comb-ex: Example ------- The following is an example of a combinatorial multiplexer .. testcode:: comb1 from myhdl import Signal, Simulation, delay, always_comb 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 muxLogic(): if sel == 1: z.next = a else: z.next = b return muxLogic # Once we've created some signals... z, a, b, sel = [Signal(intbv(0)) for i in range(4)] # ...it can be instantiated as follows mux_1 = Mux(z, a, b, sel) To verify it, we will simulate the logic with some random patterns. The ``random`` module in Python's standard library comes in handy for such purposes. The function ``randrange(n)`` returns a random natural integer smaller than *n*. It is used in the test bench code to produce random input values .. testcode:: comb1 :hide: import random random.seed(0xDECAFBAD) .. testcode:: comb1 from random import randrange def test(): print "z a b sel" for i in range(8): a.next, b.next, sel.next = randrange(8), randrange(8), randrange(2) yield delay(10) print "%s %s %s %s" % (z, a, b, sel) test_1 = test() sim = Simulation(mux_1, test_1).run() Because of the randomness, the simulation output varies between runs [#]_. One particular run produced the following output .. testoutput:: comb1 z a b sel 6 6 0 1 7 7 2 1 7 6 7 0 0 3 0 0 1 1 1 1 1 5 1 0 2 3 2 0 1 1 0 1 .. _model-seq: Sequential logic ================ .. index:: single: sequential logic .. _model-seq-templ: Template -------- Sequential RTL models are sensitive to a clock edge. In addition, they may be sensitive to a reset signal. The :func:`always_seq` decorator supports this model directly:: def top(, clock, ..., reset, ...): ... @always_seq(clock.posedge, reset=reset) def seqLogic(): ... return seqLogic, ... The :func:`always_seq` decorator automatically infers the reset functionality. It detects which signals need to be reset, and uses their initial values as the reset values. The reset signal itself needs to be specified as a :class:`ResetSignal` object. For example:: reset = ResetSignal(0, active=0, async=True) The first parameter specifies the initial value. The *active* parameter specifies the value on which the reset is active, and the *async* parameter specifies whether it is an asychronous (``True``) or a synchronous (``False``) reset. If no reset is needed, you can assign ``None`` to the *reset* parameter in the :func:`always_seq` parameter. .. _model-seq-ex: Example ------- The following code is a description of an incrementer with enable, and an asynchronous reset. .. testcode:: seq1 from myhdl import * ACTIVE_LOW, INACTIVE_HIGH = 0, 1 def Inc(count, enable, clock, reset, n): """ Incrementer with enable. count -- output enable -- control input, increment when 1 clock -- clock input reset -- asynchronous reset input n -- counter max value """ @always_seq(clock.posedge, reset=reset) def incLogic(): if enable: count.next = (count + 1) % n return incLogic For the test bench, we will use an independent clock generator, stimulus generator, and monitor. After applying enough stimulus patterns, we can raise the :func:`StopSimulation()` exception to stop the simulation run. The test bench for a small incrementer and a small number of patterns is a follows .. testcode:: seq1 :hide: import random random.seed(0xDECAFBAD) .. testcode:: seq1 from random import randrange def testbench(): count, enable, clock = [Signal(intbv(0)) for i in range(3)] reset = ResetSignal(0, active=ACTIVE_LOW, async=True) inc_1 = Inc(count, enable, clock, reset, n=4) 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(12): 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" % (enable, count) return clockGen, stimulus, inc_1, monitor tb = testbench() Simulation(tb).run() The simulation produces the following output .. testoutput:: seq1 enable count 1 1 0 1 1 2 1 3 0 3 1 0 1 1 1 2 1 3 1 0 0 0 1 1 .. _mode-seq-templ-alt: Alternative template -------------------- The template with the :func:`always_seq` decorator is convenient as it infers the reset functionality automatically. Alternatively, you can use a more explicit template as follows:: def top(, clock, ..., reset, ...): ... @always(clock.posedge, reset.negedge) def seqLogic(): if not reset: else: With this template, the reset values have to be specified explicitly. .. _model-fsm: Finite State Machine modeling ============================= .. index:: single: modeling; Finite State Machine Finite State Machine (FSM) modeling is very common in RTL design and therefore deserves special attention. For code clarity, the state values are typically represented by a set of identifiers. A standard Python idiom for this purpose is to assign a range of integers to a tuple of identifiers, like so .. doctest:: >>> SEARCH, CONFIRM, SYNC = range(3) >>> CONFIRM 1 However, this technique has some drawbacks. Though it is clearly the intention that the identifiers belong together, this information is lost as soon as they are defined. Also, the identifiers evaluate to integers, whereas a string representation of the identifiers would be preferable. To solve these issues, we need an *enumeration type*. .. index:: single: enum(); example usage MyHDL supports enumeration types by providing a function :func:`enum`. The arguments to :func:`enum` are the string representations of the identifiers, and its return value is an enumeration type. The identifiers are available as attributes of the type. For example .. doctest:: >>> from myhdl import enum >>> t_State = enum('SEARCH', 'CONFIRM', 'SYNC') >>> t_State >>> t_State.CONFIRM CONFIRM We can use this type to construct a state signal as follows:: state = Signal(t_State.SEARCH) As an example, we will use a framing controller FSM. It is an imaginary example, but similar control structures are often found in telecommunication applications. Suppose that we need to find the Start Of Frame (SOF) position of an incoming frame of bytes. A sync pattern detector continuously looks for a framing pattern and indicates it to the FSM with a ``syncFlag`` signal. When found, the FSM moves from the initial ``SEARCH`` state to the ``CONFIRM`` state. When the ``syncFlag`` is confirmed on the expected position, the FSM declares ``SYNC``, otherwise it falls back to the ``SEARCH`` state. This FSM can be coded as follows .. testcode:: sm1 from myhdl import * ACTIVE_LOW = 0 FRAME_SIZE = 8 t_State = enum('SEARCH', 'CONFIRM', 'SYNC') def FramerCtrl(SOF, state, syncFlag, clk, reset): """ Framing control FSM. SOF -- start-of-frame output bit state -- FramerState output syncFlag -- sync pattern found indication input clk -- clock input reset_n -- active low reset """ index = Signal(0) # position in frame @always_seq(clk.posedge, reset=reset) def FSM(): index.next = (index + 1) % FRAME_SIZE SOF.next = 0 if state == t_State.SEARCH: index.next = 1 if syncFlag: state.next = t_State.CONFIRM elif state == t_State.CONFIRM: if index == 0: if syncFlag: state.next = t_State.SYNC else: state.next = t_State.SEARCH elif state == t_State.SYNC: if index == 0: if not syncFlag: state.next = t_State.SEARCH SOF.next = (index == FRAME_SIZE-1) else: raise ValueError("Undefined state") return FSM .. index:: single: waveform viewing At this point, we will use the example to demonstrate the MyHDL support for waveform viewing. During simulation, signal changes can be written to a VCD output file. The VCD file can then be loaded and viewed in a waveform viewer tool such as :program:`gtkwave`. .. % The user interface of this feature consists of a single function, :func:`traceSignals`. To explain how it works, recall that in MyHDL, an instance is created by assigning the result of a function call to an instance name. For example:: tb_fsm = testbench() To enable VCD tracing, the instance should be created as follows instead:: tb_fsm = traceSignals(testbench) Note that the first argument of :func:`traceSignals` consists of the uncalled function. By calling the function under its control, :func:`traceSignals` gathers information about the hierarchy and the signals to be traced. In addition to a function argument, :func:`traceSignals` accepts an arbitrary number of non-keyword and keyword arguments that will be passed to the function call. A small test bench for our framing controller example, with signal tracing enabled, is shown below: .. testcode:: sm1 def testbench(): SOF = Signal(bool(0)) syncFlag = Signal(bool(0)) clk = Signal(bool(0)) reset = ResetSignal(1, active=ACTIVE_LOW, async=True) state = Signal(t_State.SEARCH) framectrl = FramerCtrl(SOF, state, syncFlag, clk, reset) @always(delay(10)) def clkgen(): clk.next = not clk @instance def stimulus(): for i in range(3): yield clk.posedge for n in (12, 8, 8, 4): syncFlag.next = 1 yield clk.posedge syncFlag.next = 0 for i in range(n-1): yield clk.posedge raise StopSimulation @always_seq(clk.posedge, reset=reset) def output_printer(): print syncFlag, SOF, state return framectrl, clkgen, stimulus, output_printer tb_fsm = traceSignals(testbench) sim = Simulation(tb_fsm) sim.run() .. testoutput:: sm1 :hide: False False SEARCH False False SEARCH False False SEARCH 1 False SEARCH 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 0 False SEARCH 0 False SEARCH 0 False SEARCH 1 False SEARCH 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 0 False CONFIRM 1 False CONFIRM 0 False SYNC 0 False SYNC 0 False SYNC 0 False SYNC 0 False SYNC 0 False SYNC 0 False SYNC 1 True SYNC 0 False SYNC 0 False SYNC .. testcleanup:: sm1 import os os.remove('testbench.vcd') When we run the test bench, it generates a VCD file called :file:`testbench.vcd`. When we load this file into :program:`gtkwave`, we can view the waveforms: .. image:: tbfsm.png Signals are dumped in a suitable format. This format is inferred at the :class:`Signal` construction time, from the type of the initial value. In particular, :class:`bool` signals are dumped as single bits. (This only works starting with Python 2.3, when :class:`bool` has become a separate type). Likewise, :class:`intbv` signals with a defined bit width are dumped as bit vectors. To support the general case, other types of signals are dumped as a string representation, as returned by the standard :func:`str` function. .. warning:: Support for literal string representations is not part of the VCD standard. It is specific to :program:`gtkwave`. To generate a standard VCD file, you need to use signals with a defined bit width only. .. rubric:: Footnotes .. [#] The name :func:`always_comb` refers to a construct with similar semantics in SystemVerilog. .. [#] It also possible to have a reproducible random output, by explicitly providing a seed value. See the documentation of the ``random`` module.