From 626fa15d62ab5d1677bbf03409d25d55abe20e74 Mon Sep 17 00:00:00 2001 From: Jan Decaluwe Date: Wed, 18 Apr 2012 20:44:07 +0200 Subject: [PATCH 1/4] corrected Easics link --- doc/source/whatsnew/0.7.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/0.7.rst b/doc/source/whatsnew/0.7.rst index 31a4044d..9460e9b7 100644 --- a/doc/source/whatsnew/0.7.rst +++ b/doc/source/whatsnew/0.7.rst @@ -400,7 +400,7 @@ Thanks to Francesco Balau for packaging MyHDL for Ubuntu. I would also like to thank `Easics`_ for the opportunity to use MyHDL in industrial projects. -.. _`Easics`: http:www.easics.com +.. _`Easics`: http://www.easics.com From 5ad772c4b107d18f63d01e5729d348ae30f8a62d Mon Sep 17 00:00:00 2001 From: Jan Decaluwe Date: Wed, 25 Apr 2012 21:25:04 +0200 Subject: [PATCH 2/4] Implemented and documented timescale attribute for VCD output --HG-- branch : 0.8-dev --- doc/source/conf.py | 6 +++--- doc/source/manual/reference.rst | 21 +++++++++++++++------ example/cookbook/dff/dff.py | 1 + myhdl/_traceSignals.py | 11 +++++++---- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 7896d021..a0d01052 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -34,15 +34,15 @@ master_doc = 'index' # General substitutions. project = 'MyHDL' -copyright = '2008-2010, Jan Decaluwe' +copyright = '2008-2012, Jan Decaluwe' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. -version = '0.7' +version = '0.8' # The full version, including alpha/beta/rc tags. -release = '0.7' +release = '0.8-dev' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/doc/source/manual/reference.rst b/doc/source/manual/reference.rst index a9ecde98..a9673bed 100644 --- a/doc/source/manual/reference.rst +++ b/doc/source/manual/reference.rst @@ -80,13 +80,18 @@ Waveform tracing will be moved to a backup file by attaching a timestamp to it, before creating the new file. -The ``traceSignals`` callable has the following attribute: + The ``traceSignals`` callable has the following attribute: -.. attribute:: traceSignals.name + .. attribute:: name - This attribute is used to overwrite the default top-level instance name and the - basename of the VCD output filename. + This attribute is used to overwrite the default top-level instance name and the + basename of the VCD output filename. + + .. attribute:: timescale + + This attribute is used to set the timescale corresponding to unit steps, + according to the VCD format. The default is "1ns". .. _ref-model: @@ -641,9 +646,13 @@ Conversion .. attribute:: name - This attribute is used to overwrite the default top-level instance name and the - basename of the Verilog output filename. + This attribute is used to overwrite the default top-level instance name and the + basename of the Verilog output filename. + .. attribute:: timescale + + This attribute is used to set the timescale in Verilog format. Default is + "1ns/10ps". .. function:: toVHDL(func[, *args][, **kwargs]) diff --git a/example/cookbook/dff/dff.py b/example/cookbook/dff/dff.py index ff3c3253..ecd18d71 100644 --- a/example/cookbook/dff/dff.py +++ b/example/cookbook/dff/dff.py @@ -29,6 +29,7 @@ def test_dff(): return dff_inst, clkgen, stimulus def simulate(timesteps): + traceSignals.timescale = "1ps" tb = traceSignals(test_dff) sim = Simulation(tb) sim.run(timesteps) diff --git a/myhdl/_traceSignals.py b/myhdl/_traceSignals.py index bb46018e..8dbe12c5 100644 --- a/myhdl/_traceSignals.py +++ b/myhdl/_traceSignals.py @@ -45,10 +45,13 @@ _error.MultipleTraces = "Cannot trace multiple instances simultaneously" class _TraceSignalsClass(object): - __slot__ = ("name", ) + __slot__ = ("name", + "timescale", + ) def __init__(self): self.name = None + self.timescale = "1ns" def __call__(self, dut, *args, **kwargs): global _tracing @@ -82,7 +85,7 @@ class _TraceSignalsClass(object): vcdfile = open(vcdpath, 'w') _simulator._tracing = 1 _simulator._tf = vcdfile - _writeVcdHeader(vcdfile) + _writeVcdHeader(vcdfile, self.timescale) _writeVcdSigs(vcdfile, h.hierarchy) finally: _tracing = 0 @@ -111,7 +114,7 @@ def _namecode(n): code = _codechars[r] + code return code -def _writeVcdHeader(f): +def _writeVcdHeader(f, timescale): print >> f, "$date" print >> f, " %s" % time.asctime() print >> f, "$end" @@ -119,7 +122,7 @@ def _writeVcdHeader(f): print >> f, " MyHDL %s" % __version__ print >> f, "$end" print >> f, "$timescale" - print >> f, " 1ns" + print >> f, " %s" % timescale print >> f, "$end" print >> f From 20a9394123690ae9faec975b1bf683fee5938d78 Mon Sep 17 00:00:00 2001 From: Jan Decaluwe Date: Sun, 6 May 2012 09:59:31 +0200 Subject: [PATCH 3/4] break up the modeling chapter in three Mark high level clearly as separate from synthesis --HG-- branch : 0.8-dev --- .hgignore | 2 + .../manual/{modeling.rst => highlevel.rst} | 653 +----------------- doc/source/manual/index.rst | 4 +- doc/source/manual/intro.rst | 6 +- doc/source/manual/rtl.rst | 442 ++++++++++++ doc/source/manual/structure.rst | 196 ++++++ 6 files changed, 673 insertions(+), 630 deletions(-) rename doc/source/manual/{modeling.rst => highlevel.rst} (50%) create mode 100644 doc/source/manual/rtl.rst create mode 100644 doc/source/manual/structure.rst diff --git a/.hgignore b/.hgignore index bf23d96d..c3842147 100644 --- a/.hgignore +++ b/.hgignore @@ -21,6 +21,8 @@ transcript *.0 *.bak *.wlf +*.rej +*.out doc/build build/ dist/ diff --git a/doc/source/manual/modeling.rst b/doc/source/manual/highlevel.rst similarity index 50% rename from doc/source/manual/modeling.rst rename to doc/source/manual/highlevel.rst index 0558e6b7..a34c64e4 100644 --- a/doc/source/manual/modeling.rst +++ b/doc/source/manual/highlevel.rst @@ -1,639 +1,40 @@ .. currentmodule:: myhdl -.. _model: - -******************* -Modeling techniques -******************* - - -.. _model-structure: - -Structural modeling -=================== - -.. index:: single: modeling; structural - -Hardware descriptions need to support the concepts of module instantiation and -hierarchy. In MyHDL, an instance is recursively defined as being either a -sequence of instances, or a generator. Hierarchy is modeled by defining -instances in a higher-level function, and returning them. The following is a -schematic example of the basic case. :: - - def top(...): - ... - instance_1 = module_1(...) - instance_2 = module_2(...) - ... - instance_n = module_n(...) - ... - return instance_1, instance_2, ... , instance_n - -Note that MyHDL uses conventional procedural techniques for modeling structure. -This makes it straightforward to model more complex cases. - - -.. _model-conf: - -Conditional instantiation -------------------------- - -.. index:: single: conditional instantiation - -To model conditional instantiation, we can select the returned instance under -parameter control. For example:: - - SLOW, MEDIUM, FAST = range(3) - - def top(..., speed=SLOW): - ... - def slowAndSmall(): - ... - ... - def fastAndLarge(): - ... - if speed == SLOW: - return slowAndSmall() - elif speed == FAST: - return fastAndLarge() - else: - raise NotImplementedError - - -.. _model-instarray: - -Lists of instances and signals ------------------------------- - -.. index:: single: lists of instances and signals - -Python lists are easy to create. We can use them to model lists of instances. - -Suppose we have a top module that instantiates a single ``channel`` submodule, -as follows:: - - def top(...): - - din = Signal(0) - dout = Signal(0) - clk = Signal(bool(0)) - reset = Signal(bool(0)) - - channel_inst = channel(dout, din, clk, reset) - - return channel_inst - -If we wanted to support an arbitrary number of channels, we can use lists of -signals and a list of instances, as follows:: - - def top(..., n=8): - - din = [Signal(0) for i in range(n)] - dout = [Signal(0) for in range(n)] - clk = Signal(bool(0)) - reset = Signal(bool(0)) - channel_inst = [None for i in range(n)] - - for i in range(n): - channel_inst[i] = channel(dout[i], din[i], clk, reset) - - return channel_inst - -.. _model-shadow-signals: - -Converting between lists of signals and bit vectors ---------------------------------------------------- - -Compared to HDLs such as VHDL and Verilog, MyHDL signals are less -flexible for structural modeling. For example, slicing a signal -returns a slice of the current value. For behavioral code, this is -just fine. However, it implies that you cannot use such as slice in -structural descriptions. In other words, a signal slice cannot be used -as a signal. - -In MyHDL, you can address such cases by a concept called -shadow signals. A shadow signal is constructed out of -other signals and follows their value changes automatically. -For example, a :class:`_SliceSignal` follows the value of -an index or a slice from another signal. Likewise, -A :class:`ConcatSignal` follows the -values of a number of signals as a concatenation. - -As an example, suppose we have a system with N requesters that -need arbitration. Each requester has a ``request`` output -and a ``grant`` input. To connect them in the system, we can -use list of signals. For example, a list of request signals -can be constructed as follows:: - - request_list = [Signal(bool()) for i in range(M)] - -Suppose that an arbiter module is available that is -instantiated as follows:: - - arb = arbiter(grant_vector, request_vector, clock, reset) - -The ``request_vector`` input is a bit vector that can have -any of its bits asserted. The ``grant_vector`` is an output -bit vector with just a single bit asserted, or none. -Such a module is typically based on bit vectors because -they are easy to process in RTL code. In MyHDL, a bit vector -is modeled using the :class:`intbv` type. - -We need a way to "connect" the list of signals to the -bit vector and vice versa. Of course, we can do this with explicit -code, but shadow signals can do this automatically. For -example, we can construct a ``request_vector`` as a -:class:`ConcatSignal` object:: - - request_vector = ConcatSignal(*reversed(request_list) - -Note that we reverse the list first. This is done because the index range -of lists is the inverse of the range of :class:`intbv` bit vectors. -By reversing, the indices correspond to the same bit. - -The inverse problem exist for the ``grant_vector``. It would be defined as follows:: - - grant_vector = Signal(intbv(0)[M:]) - -To construct a list of signals that are connected automatically to the -bit vector, we can use the :class:`Signal` call interface to construct -:class:`_SliceSignal` objects:: - - grant_list = [grant_vector(i) for i in range(M)] - -Note the round brackets used for this type of slicing. Also, it may not be -necessary to construct this list explicitly. You can simply use -``grant_vector(i)`` in an instantiation. - -To decide when to use normal or shadow signals, consider the data -flow. Use normal signals to connect to *outputs*. Use shadow signals to -transform these signals so that they can be used as *inputs*. - - -.. _model-infer-instlist: - -Inferring the list of instances -------------------------------- - -In MyHDL, instances have to be returned explicitly by a top level function. It -may be convenient to assemble the list of instances automatically. For this -purpose, MyHDL provides the function :func:`instances`. Using the first example -in this section, it is used as follows:: - - from myhdl import instances - - def top(...): - ... - instance_1 = module_1(...) - instance_2 = module_2(...) - ... - instance_n = module_n(...) - ... - return instances() - -Function :func:`instances` uses introspection to inspect the type of the local -variables defined by the calling function. All variables that comply with the -definition of an instance are assembled in a list, and that list is returned. - - -.. _model-rtl: - -RTL modeling -============ - -.. index:: single: modeling; RTL style - -The present section describes how MyHDL supports RTL style modeling as is -typically used for synthesizable models. - - -.. _model-comb: - -Combinatorial logic -------------------- - -.. index:: single: combinatorial logic - - -.. _model-comb-templ: - -Template -^^^^^^^^ - -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:: - - 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 - -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:: - - from random import randrange - - z, a, b, sel = [Signal(0) for i in range(4)] - - mux_1 = Mux(z, a, b, sel) - - 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) - sim.run() - -Because of the randomness, the simulation output varies between runs [#]_. One -particular run produced the following output:: - - % python mux.py - z a b sel - 6 6 1 1 - 7 7 1 1 - 7 3 7 0 - 1 2 1 0 - 7 7 5 1 - 4 7 4 0 - 4 0 4 0 - 3 3 5 1 - StopSimulation: No more events - - -.. _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. We will describe one of the most common patterns: a -template with a rising clock edge and an asynchronous reset signal. Other -templates are similar. :: - - def top(, clock, ..., reset, ...): - ... - @always(clock.posedge, reset.negedge) - def seqLogic(): - if reset == : - - else: - - ... - return seqLogic, ... - - -.. _model-seq-ex: - -Example -^^^^^^^ - -The following code is a description of an incrementer with enable, and an -asynchronous reset. :: - - from random import randrange - 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(clock.posedge, reset.negedge) - def incLogic(): - if reset == ACTIVE_LOW: - count.next = 0 - else: - 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 ``StopSimulation`` exception to stop the simulation run. The test bench for -a small incrementer and a small number of patterns is a follows:: - - def testbench(): - count, enable, clock, reset = [Signal(intbv(0)) for i in range(4)] - - 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() - - def main(): - Simulation(tb).run() - -The simulation produces the following output:: - - % python inc.py - enable count - 0 0 - 1 1 - 0 1 - 1 2 - 1 3 - 1 0 - 0 0 - 1 1 - 0 1 - 0 1 - 0 1 - 1 2 - StopSimulation - - -.. _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:: - - >>> 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:: - - >>> 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:: - - from myhdl import * - - ACTIVE_LOW = 0 - FRAME_SIZE = 8 - t_State = enum('SEARCH', 'CONFIRM', 'SYNC') - - def FramerCtrl(SOF, state, syncFlag, clk, reset_n): - - """ 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(clk.posedge, reset_n.negedge) - 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 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:: - - def testbench(): - - SOF = Signal(bool(0)) - syncFlag = Signal(bool(0)) - clk = Signal(bool(0)) - reset_n = Signal(bool(1)) - state = Signal(t_State.SEARCH) - - framectrl = FramerCtrl(SOF, state, syncFlag, clk, reset_n) - - @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 - - return framectrl, clkgen, stimulus - - - tb_fsm = traceSignals(testbench) - sim = Simulation(tb_fsm) - sim.run() - -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. - - .. _model-hl: +******************* High level modeling -=================== +******************* + +Introduction +============ .. index:: single: modeling; high level +To write synthesizable models in MyHDL, you should stick to +the RTL templates shown in :ref:`model-rtl`. However, +modeling in MyHDL is much more powerful than that. +Conceptually, MyHDL is a library for general event-driven +modeling and simulation of hardware systems. + +There are many reasons why it can be useful to model at a +higher abstraction level than RTL. For example, you can +use MyHDL to verify architectural features, such as system +throughput, latency and buffer sizes. You can also write +high level models for specialized technology-dependent cores +that are not going through synthesis. Last but not least, +you can use MyHDL to write test benches that verify a system +model or a synthesizable description. + +This chapter explores some of the options for high level +modeling with MyHDL. + .. _model-bfm: Modeling with bus-functional procedures ---------------------------------------- +======================================= .. index:: single: bus-functional procedure @@ -916,7 +317,7 @@ Now, transmission of a new byte only starts when the previous one is received:: .. _model-mem: Modeling memories with built-in types -------------------------------------- +===================================== .. index:: single: modeling; memories @@ -1014,7 +415,7 @@ to get the size of a Python object. .. _model-err: Modeling errors using exceptions --------------------------------- +================================ In the previous section, we used Python data types for modeling. If such a type is used inappropriately, Python's run time error system will come into play. For @@ -1113,7 +514,7 @@ detected by a regular check on the length of the list. .. _model-obj: Object oriented modeling ------------------------- +======================== .. index:: single: modeling; object oriented diff --git a/doc/source/manual/index.rst b/doc/source/manual/index.rst index ac50dea3..9b2cced8 100644 --- a/doc/source/manual/index.rst +++ b/doc/source/manual/index.rst @@ -12,7 +12,9 @@ Contents: preface background intro - modeling + structure + rtl + highlevel unittest cosimulation conversion diff --git a/doc/source/manual/intro.rst b/doc/source/manual/intro.rst index 1eea03cf..6ff99533 100644 --- a/doc/source/manual/intro.rst +++ b/doc/source/manual/intro.rst @@ -47,9 +47,9 @@ The first line of the script imports a number of objects from the ``myhdl`` package. In Python we can only use identifiers that are literally defined in the source file [#]_. -Then, we define a function called :func:`HelloWorld`. In MyHDL, classic -functions are used to model hardware modules. In particular, the parameter list -is used to define the interface. In this first example, the interface is empty. +Then, we define a function called :func:`HelloWorld`. In MyHDL, hardware +modules can be modeled using classic functions. In particular, the parameter list +is then used to define the interface. In this first example, the interface is empty. .. index:: single: decorator; always diff --git a/doc/source/manual/rtl.rst b/doc/source/manual/rtl.rst new file mode 100644 index 00000000..c7442035 --- /dev/null +++ b/doc/source/manual/rtl.rst @@ -0,0 +1,442 @@ +.. 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. +This chapter describes how MyHDL supports it. + + +.. _model-comb: + +Combinatorial logic +=================== + +.. index:: single: combinatorial logic + + +.. _model-comb-templ: + +Template +-------- + +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:: + + 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 + +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:: + + from random import randrange + + z, a, b, sel = [Signal(0) for i in range(4)] + + mux_1 = Mux(z, a, b, sel) + + 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) + sim.run() + +Because of the randomness, the simulation output varies between runs [#]_. One +particular run produced the following output:: + + % python mux.py + z a b sel + 6 6 1 1 + 7 7 1 1 + 7 3 7 0 + 1 2 1 0 + 7 7 5 1 + 4 7 4 0 + 4 0 4 0 + 3 3 5 1 + StopSimulation: No more events + + +.. _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. We will describe one of the most common patterns: a +template with a rising clock edge and an asynchronous reset signal. Other +templates are similar. :: + + def top(, clock, ..., reset, ...): + ... + @always(clock.posedge, reset.negedge) + def seqLogic(): + if reset == : + + else: + + ... + return seqLogic, ... + + +.. _model-seq-ex: + +Example +------- + +The following code is a description of an incrementer with enable, and an +asynchronous reset. :: + + from random import randrange + 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(clock.posedge, reset.negedge) + def incLogic(): + if reset == ACTIVE_LOW: + count.next = 0 + else: + 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 ``StopSimulation`` exception to stop the simulation run. The test bench for +a small incrementer and a small number of patterns is a follows:: + + def testbench(): + count, enable, clock, reset = [Signal(intbv(0)) for i in range(4)] + + 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() + + def main(): + Simulation(tb).run() + +The simulation produces the following output:: + + % python inc.py + enable count + 0 0 + 1 1 + 0 1 + 1 2 + 1 3 + 1 0 + 0 0 + 1 1 + 0 1 + 0 1 + 0 1 + 1 2 + StopSimulation + + +.. _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:: + + >>> 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:: + + >>> 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:: + + from myhdl import * + + ACTIVE_LOW = 0 + FRAME_SIZE = 8 + t_State = enum('SEARCH', 'CONFIRM', 'SYNC') + + def FramerCtrl(SOF, state, syncFlag, clk, reset_n): + + """ 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(clk.posedge, reset_n.negedge) + 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 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:: + + def testbench(): + + SOF = Signal(bool(0)) + syncFlag = Signal(bool(0)) + clk = Signal(bool(0)) + reset_n = Signal(bool(1)) + state = Signal(t_State.SEARCH) + + framectrl = FramerCtrl(SOF, state, syncFlag, clk, reset_n) + + @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 + + return framectrl, clkgen, stimulus + + + tb_fsm = traceSignals(testbench) + sim = Simulation(tb_fsm) + sim.run() + +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. + + diff --git a/doc/source/manual/structure.rst b/doc/source/manual/structure.rst new file mode 100644 index 00000000..f9379c72 --- /dev/null +++ b/doc/source/manual/structure.rst @@ -0,0 +1,196 @@ +.. currentmodule:: myhdl + + +.. _model-structure: + +******************* +Structural modeling +******************* + +.. index:: single: modeling; structural + +Introduction +============ + +Hardware descriptions need to support the concepts of module instantiation and +hierarchy. In MyHDL, an instance is recursively defined as being either a +sequence of instances, or a generator. Hierarchy is modeled by defining +instances in a higher-level function, and returning them. The following is a +schematic example of the basic case. :: + + def top(...): + ... + instance_1 = module_1(...) + instance_2 = module_2(...) + ... + instance_n = module_n(...) + ... + return instance_1, instance_2, ... , instance_n + +Note that MyHDL uses conventional procedural techniques for modeling structure. +This makes it straightforward to model more complex cases. + + +.. _model-conf: + +Conditional instantiation +========================= + +.. index:: single: conditional instantiation + +To model conditional instantiation, we can select the returned instance under +parameter control. For example:: + + SLOW, MEDIUM, FAST = range(3) + + def top(..., speed=SLOW): + ... + def slowAndSmall(): + ... + ... + def fastAndLarge(): + ... + if speed == SLOW: + return slowAndSmall() + elif speed == FAST: + return fastAndLarge() + else: + raise NotImplementedError + + +.. _model-instarray: + +Lists of instances and signals +------------------------------ + +.. index:: single: lists of instances and signals + +Python lists are easy to create. We can use them to model lists of instances. + +Suppose we have a top module that instantiates a single ``channel`` submodule, +as follows:: + + def top(...): + + din = Signal(0) + dout = Signal(0) + clk = Signal(bool(0)) + reset = Signal(bool(0)) + + channel_inst = channel(dout, din, clk, reset) + + return channel_inst + +If we wanted to support an arbitrary number of channels, we can use lists of +signals and a list of instances, as follows:: + + def top(..., n=8): + + din = [Signal(0) for i in range(n)] + dout = [Signal(0) for in range(n)] + clk = Signal(bool(0)) + reset = Signal(bool(0)) + channel_inst = [None for i in range(n)] + + for i in range(n): + channel_inst[i] = channel(dout[i], din[i], clk, reset) + + return channel_inst + +.. _model-shadow-signals: + +Converting between lists of signals and bit vectors +=================================================== + +Compared to HDLs such as VHDL and Verilog, MyHDL signals are less +flexible for structural modeling. For example, slicing a signal +returns a slice of the current value. For behavioral code, this is +just fine. However, it implies that you cannot use such as slice in +structural descriptions. In other words, a signal slice cannot be used +as a signal. + +In MyHDL, you can address such cases by a concept called +shadow signals. A shadow signal is constructed out of +other signals and follows their value changes automatically. +For example, a :class:`_SliceSignal` follows the value of +an index or a slice from another signal. Likewise, +A :class:`ConcatSignal` follows the +values of a number of signals as a concatenation. + +As an example, suppose we have a system with N requesters that +need arbitration. Each requester has a ``request`` output +and a ``grant`` input. To connect them in the system, we can +use list of signals. For example, a list of request signals +can be constructed as follows:: + + request_list = [Signal(bool()) for i in range(M)] + +Suppose that an arbiter module is available that is +instantiated as follows:: + + arb = arbiter(grant_vector, request_vector, clock, reset) + +The ``request_vector`` input is a bit vector that can have +any of its bits asserted. The ``grant_vector`` is an output +bit vector with just a single bit asserted, or none. +Such a module is typically based on bit vectors because +they are easy to process in RTL code. In MyHDL, a bit vector +is modeled using the :class:`intbv` type. + +We need a way to "connect" the list of signals to the +bit vector and vice versa. Of course, we can do this with explicit +code, but shadow signals can do this automatically. For +example, we can construct a ``request_vector`` as a +:class:`ConcatSignal` object:: + + request_vector = ConcatSignal(*reversed(request_list) + +Note that we reverse the list first. This is done because the index range +of lists is the inverse of the range of :class:`intbv` bit vectors. +By reversing, the indices correspond to the same bit. + +The inverse problem exist for the ``grant_vector``. It would be defined as follows:: + + grant_vector = Signal(intbv(0)[M:]) + +To construct a list of signals that are connected automatically to the +bit vector, we can use the :class:`Signal` call interface to construct +:class:`_SliceSignal` objects:: + + grant_list = [grant_vector(i) for i in range(M)] + +Note the round brackets used for this type of slicing. Also, it may not be +necessary to construct this list explicitly. You can simply use +``grant_vector(i)`` in an instantiation. + +To decide when to use normal or shadow signals, consider the data +flow. Use normal signals to connect to *outputs*. Use shadow signals to +transform these signals so that they can be used as *inputs*. + + +.. _model-infer-instlist: + +Inferring the list of instances +=============================== + +In MyHDL, instances have to be returned explicitly by a top level function. It +may be convenient to assemble the list of instances automatically. For this +purpose, MyHDL provides the function :func:`instances`. Using the first example +in this section, it is used as follows:: + + from myhdl import instances + + def top(...): + ... + instance_1 = module_1(...) + instance_2 = module_2(...) + ... + instance_n = module_n(...) + ... + return instances() + +Function :func:`instances` uses introspection to inspect the type of the local +variables defined by the calling function. All variables that comply with the +definition of an instance are assembled in a list, and that list is returned. + + From 9182cadc92591a7504993b0054363ca2ba281a75 Mon Sep 17 00:00:00 2001 From: Jan Decaluwe Date: Sun, 6 May 2012 14:59:24 +0200 Subject: [PATCH 4/4] clarified synthesis --HG-- branch : 0.8-dev --- doc/source/manual/conversion.rst | 6 +++--- doc/source/manual/rtl.rst | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/source/manual/conversion.rst b/doc/source/manual/conversion.rst index 1a7fa575..8661c2b2 100644 --- a/doc/source/manual/conversion.rst +++ b/doc/source/manual/conversion.rst @@ -35,9 +35,9 @@ or VHDL, using the function :func:`toVerilog` or :func:`toVHDL` from the MyHDL library. When the design is intended for implementation -a third-party :dfn:`synthesis tool` is used to convert the Verilog or VHDL -design to a gate implementation for an ASIC or FPGA. With this step, there is -a path from a hardware description in Python to an FPGA or ASIC implementation. +a third-party :dfn:`synthesis tool` is used to compile the Verilog or VHDL +model into an implementation for an ASIC or FPGA. With this step, there is +an automated path from a hardware description in Python to an FPGA or ASIC implementation. The conversion does not start from source files, but from an instantiated design that has been *elaborated* by the Python interpreter. The converter uses the diff --git a/doc/source/manual/rtl.rst b/doc/source/manual/rtl.rst index c7442035..860ed442 100644 --- a/doc/source/manual/rtl.rst +++ b/doc/source/manual/rtl.rst @@ -14,6 +14,8 @@ Introduction 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.