doc/fhdl: document Module API
This commit is contained in:
parent
aef78b2395
commit
794c4e6041
167
doc/fhdl.rst
167
doc/fhdl.rst
|
@ -1,16 +1,14 @@
|
||||||
The FHDL layer
|
The FHDL layer
|
||||||
##############
|
##############
|
||||||
|
|
||||||
The Fragmented Hardware Description Language (FHDL) is the lowest layer of Migen. It consists of a formal system to describe signals, and combinatorial and synchronous statements operating on them. The formal system itself is low level and close to the synthesizable subset of Verilog, and we then rely on Python algorithms to build complex structures by combining FHDL elements and encapsulating them in "fragments".
|
The Fragmented Hardware Description Language (FHDL) is the lowest layer of Migen. It consists of a formal system to describe signals, and combinatorial and synchronous statements operating on them. The formal system itself is low level and close to the synthesizable subset of Verilog, and we then rely on Python algorithms to build complex structures by combining FHDL elements.
|
||||||
The FHDL module also contains a back-end to produce synthesizable Verilog, and some structure analysis and manipulation functionality. A VHDL back-end [vhdlbe]_ is in development.
|
The FHDL module also contains a back-end to produce synthesizable Verilog, and some structure analysis and manipulation functionality.
|
||||||
|
|
||||||
.. [vhdlbe] https://github.com/peteut/migen
|
|
||||||
|
|
||||||
FHDL differs from MyHDL [myhdl]_ in fundamental ways. MyHDL follows the event-driven paradigm of traditional HDLs (see :ref:`background`) while FHDL separates the code into combinatorial statements, synchronous statements, and reset values. In MyHDL, the logic is described directly in the Python AST. The converter to Verilog or VHDL then examines the Python AST and recognizes a subset of Python that it translates into V*HDL statements. This seriously impedes the capability of MyHDL to generate logic procedurally. With FHDL, you manipulate a custom AST from Python, and you can more easily design algorithms that operate on it.
|
FHDL differs from MyHDL [myhdl]_ in fundamental ways. MyHDL follows the event-driven paradigm of traditional HDLs (see :ref:`background`) while FHDL separates the code into combinatorial statements, synchronous statements, and reset values. In MyHDL, the logic is described directly in the Python AST. The converter to Verilog or VHDL then examines the Python AST and recognizes a subset of Python that it translates into V*HDL statements. This seriously impedes the capability of MyHDL to generate logic procedurally. With FHDL, you manipulate a custom AST from Python, and you can more easily design algorithms that operate on it.
|
||||||
|
|
||||||
.. [myhdl] http://www.myhdl.org
|
.. [myhdl] http://www.myhdl.org
|
||||||
|
|
||||||
FHDL is made of several elements, which are briefly explained below.
|
FHDL is made of several elements, which are briefly explained below. They all can be imported from the ``migen.fhdl.std`` module.
|
||||||
|
|
||||||
Expressions
|
Expressions
|
||||||
***********
|
***********
|
||||||
|
@ -26,7 +24,7 @@ Negative integers are explicitly supported. As with MyHDL [countin]_, arithmetic
|
||||||
|
|
||||||
Signal
|
Signal
|
||||||
======
|
======
|
||||||
The signal object represents a value that is expected to change in the circuit. It does exactly what Verilog's "wire" and "reg" and VHDL's "signal" and "variable" do.
|
The signal object represents a value that is expected to change in the circuit. It does exactly what Verilog's "wire" and "reg" and VHDL's "signal" do.
|
||||||
|
|
||||||
The main point of the signal object is that it is identified by its Python ID (as returned by the :py:func:`id` function), and nothing else. It is the responsibility of the V*HDL back-end to establish an injective mapping between Python IDs and the V*HDL namespace. It should perform name mangling to ensure this. The consequence of this is that signal objects can safely become members of arbitrary Python classes, or be passed as parameters to functions or methods that generate logic involving them.
|
The main point of the signal object is that it is identified by its Python ID (as returned by the :py:func:`id` function), and nothing else. It is the responsibility of the V*HDL back-end to establish an injective mapping between Python IDs and the V*HDL namespace. It should perform name mangling to ensure this. The consequence of this is that signal objects can safely become members of arbitrary Python classes, or be passed as parameters to functions or methods that generate logic involving them.
|
||||||
|
|
||||||
|
@ -34,7 +32,6 @@ The properties of a signal object are:
|
||||||
|
|
||||||
* An integer or a (integer, boolean) pair that defines the number of bits and whether the bit of higher index of the signal is a sign bit (i.e. the signal is signed). The defaults are one bit and unsigned. Alternatively, the ``min`` and ``max`` parameters can be specified to define the range of the signal and determine its bit width and signedness. As with Python ranges, ``min`` is inclusive and defaults to 0, ``max`` is exclusive and defaults to 2.
|
* An integer or a (integer, boolean) pair that defines the number of bits and whether the bit of higher index of the signal is a sign bit (i.e. the signal is signed). The defaults are one bit and unsigned. Alternatively, the ``min`` and ``max`` parameters can be specified to define the range of the signal and determine its bit width and signedness. As with Python ranges, ``min`` is inclusive and defaults to 0, ``max`` is exclusive and defaults to 2.
|
||||||
* A name, used as a hint for the V*HDL back-end name mangler.
|
* A name, used as a hint for the V*HDL back-end name mangler.
|
||||||
* A boolean "variable". If true, the signal will behave like a VHDL variable, or a Verilog reg that uses blocking assignment. This parameter only has an effect when the signal's value is modified in a synchronous statement.
|
|
||||||
* The signal's reset value. It must be an integer, and defaults to 0. When the signal's value is modified with a synchronous statement, the reset value is the initialization value of the associated register. When the signal is assigned to in a conditional combinatorial statement (``If`` or ``Case``), the reset value is the value that the signal has when no condition that causes the signal to be driven is verified. This enforces the absence of latches in designs. If the signal is permanently driven using a combinatorial statement, the reset value has no effect.
|
* The signal's reset value. It must be an integer, and defaults to 0. When the signal's value is modified with a synchronous statement, the reset value is the initialization value of the associated register. When the signal is assigned to in a conditional combinatorial statement (``If`` or ``Case``), the reset value is the value that the signal has when no condition that causes the signal to be driven is verified. This enforces the absence of latches in designs. If the signal is permanently driven using a combinatorial statement, the reset value has no effect.
|
||||||
|
|
||||||
The sole purpose of the name property is to make the generated V*HDL code easier to understand and debug. From a purely functional point of view, it is perfectly OK to have several signals with the same name property. The back-end will generate a unique name for each object. If no name property is specified, Migen will analyze the code that created the signal object, and try to extract the variable or member name from there. For example, the following statements will create one or several signals named "bar": ::
|
The sole purpose of the name property is to make the generated V*HDL code easier to understand and debug. From a purely functional point of view, it is perfectly OK to have several signals with the same name property. The back-end will generate a unique name for each object. If no name property is specified, Migen will analyze the code that created the signal object, and try to extract the variable or member name from there. For example, the following statements will create one or several signals named "bar": ::
|
||||||
|
@ -206,35 +203,153 @@ Inline synthesis directives (pseudo-comments such as ``// synthesis attribute ke
|
||||||
|
|
||||||
SynthesisDirective("attribute keep of {clksig} is true", clksig=clock_domain.clk)
|
SynthesisDirective("attribute keep of {clksig} is true", clksig=clock_domain.clk)
|
||||||
|
|
||||||
Fragments
|
Modules
|
||||||
*********
|
*******
|
||||||
A "fragment" is a unit of logic, which is composed of:
|
|
||||||
|
|
||||||
* A list of combinatorial statements.
|
Modules play the same role as Verilog modules and VHDL entities. Similarly, they are organized in a tree structure. A FHDL module is a Python object that derives from the ``Module`` class. This class defines special attributes to be used by derived classes to describe their logic. They are explained below.
|
||||||
* A list of synchronous statements, or a clock domain name -> synchronous statements dictionary.
|
|
||||||
* A set of specials (memories, instances, etc.)
|
|
||||||
* A list of simulation functions (see :ref:`simulating`).
|
|
||||||
|
|
||||||
Fragments can reference arbitrary signals, including signals that are referenced in other fragments. Fragments can be combined using the "+" operator, which returns a new fragment containing the concatenation of each matched pair of lists.
|
Combinatorial statements
|
||||||
|
========================
|
||||||
|
|
||||||
Fragments can be passed to the back-end for conversion to Verilog.
|
A combinatorial statement is a statement that is executed whenever one of its inputs changes.
|
||||||
|
|
||||||
Specials are using a set, not a list, to facilitate sharing of certain elements by different components of a design. For example, one component may use a static memory buffer that another component would map onto a bus, using another port to that memory. Both components should reference the memory object in their fragments, but that memory should appear only once in the final design. The Python ``set`` provides this functionality.
|
Combinatorial statements are added to a module by using the ``comb`` special attribute. Like most module special attributes, it must be accessed using the ``+=`` incrementation operator, and either a single statement, a tuple of statements or a list of statements can appear on the right hand side.
|
||||||
|
|
||||||
By convention, classes that generate logic implement a method called ``get_fragment``. When called, this method builds a new fragment implementing the desired functionality of the class, and returns it. This convention allows fragments to be built automatically by combining the fragments from all relevant objects in the local scope, by using the autofragment module.
|
For example, the module below implements a OR gate: ::
|
||||||
|
|
||||||
|
class ORGate(Module):
|
||||||
|
def __init__(self):
|
||||||
|
self.a = Signal()
|
||||||
|
self.b = Signal()
|
||||||
|
self.x = Signal()
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
self.comb += x.eq(a | b)
|
||||||
|
|
||||||
|
To improve code readability, it is recommended to place the interface of the module at the beginning of the ``__init__`` function, and separate it from the implementation using three hash signs.
|
||||||
|
|
||||||
|
Synchronous statements
|
||||||
|
======================
|
||||||
|
|
||||||
|
A synchronous statements is a statement that is executed at each edge of some clock signal.
|
||||||
|
|
||||||
|
They are added to a module by using the ``sync`` special attribute, which has the same properties as the ``comb`` attribute.
|
||||||
|
|
||||||
|
The ``sync`` special attribute also has sub-attributes that correspond to abstract clock domains. For example, to add a statement to the clock domain named ``foo``, one would write ``self.sync.foo += statement``. The default clock domain is ``sys`` and writing ``self.sync += statement`` is equivalent to writing ``self.sync.sys += statement``.
|
||||||
|
|
||||||
|
Submodules and specials
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Submodules and specials can be added by using the ``submodules`` and ``specials`` attributes respectively. This can be done in two ways:
|
||||||
|
|
||||||
|
#. anonymously, by using the ``+=`` operator on the special attribute directly, e.g. ``self.submodules += some_other_module``. Like with the ``comb`` and ``sync`` attributes, a single module/special or a tuple or list can be specified.
|
||||||
|
#. by naming the submodule/special using a subattribute of the ``submodules`` or ``specials`` attribute, e.g. ``self.submodules.foo = module_foo``. The submodule/special is then accessible as an attribute of the object, e.g. ``self.foo`` (and not ``self.submodules.foo``). Only one submodule/special can be added at a time using this form.
|
||||||
|
|
||||||
|
Clock domains
|
||||||
|
=============
|
||||||
|
|
||||||
|
Specifying the implementation of a clock domain is done using the ``ClockDomain`` object. It contains the name of the clock domain, a clock signal that can be driven like any other signal in the design (for example, using a PLL instance), and optionally a reset signal. Clock domains without a reset signal are reset using e.g. ``initial`` statements in Verilog, which in many FPGA families initalize the registers during configuration.
|
||||||
|
|
||||||
|
The name can be omitted if it can be extracted from the variable name. When using this automatic naming feature, prefixes ``_``, ``cd_`` and ``_cd_`` are removed.
|
||||||
|
|
||||||
|
Clock domains are then added to a module using the ``clock_domains`` special attribute, which behaves exactly like ``submodules`` and ``specials``.
|
||||||
|
|
||||||
|
Summary of special attributes
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. table:: Summary of special attributes
|
||||||
|
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| Syntax | Action |
|
||||||
|
+============================================+==============================================================+
|
||||||
|
| self.comb += stmt | Add combinatorial statement to current module. |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.comb += stmtA, stmtB | Add combinatorial statements A and B to current module. |
|
||||||
|
| | |
|
||||||
|
| self.comb += [stmtA, stmtB] | |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.sync += stmt | Add synchronous statement to current module, in default |
|
||||||
|
| | clock domain sys. |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.sync.foo += stmt | Add synchronous statement to current module, in clock domain |
|
||||||
|
| | foo. |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.sync.foo += stmtA, stmtB | Add synchronous statements A and B to current module, in |
|
||||||
|
| | clock domain foo. |
|
||||||
|
| self.sync.foo += [stmtA, stmtB] | |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.submodules += mod | Add anonymous submodule to current module. |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.submodules += modA, modB | Add anonymous submodules A and B to current module. |
|
||||||
|
| | |
|
||||||
|
| self.submodules += [modA, modB] | |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.submodules.bar = mod | Add submodule named bar to current module. The submodule can |
|
||||||
|
| | then be accessed using self.bar. |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.specials += spe | Add anonymous special to current module. |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.specials += speA, speB | Add anonymous specials A and B to current module. |
|
||||||
|
| | |
|
||||||
|
| self.specials += [speA, speB] | |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.specials.bar = spe | Add special named bar to current module. The special can |
|
||||||
|
| | then be accessed using self.bar. |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.clock_domains += cd | Add clock domain to current module. |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.clock_domains += cdA, cdB | Add clock domains A and B to current module. |
|
||||||
|
| | |
|
||||||
|
| self.clock_domains += [cdA, cdB] | |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
| self.clock_domains.pix = ClockDomain() | Create and add clock domain pix to current module. The clock |
|
||||||
|
| | domain name is pix in all cases. It can be accessed using |
|
||||||
|
| self.clock_domains._pix = ClockDomain() | self.pix, self._pix, self.cd_pix and self._cd_pix, |
|
||||||
|
| | respectively. |
|
||||||
|
| self.clock_domains.cd_pix = ClockDomain() | |
|
||||||
|
| | |
|
||||||
|
| self.clock_domains._cd_pix = ClockDomain() | |
|
||||||
|
+--------------------------------------------+--------------------------------------------------------------+
|
||||||
|
|
||||||
|
Clock domain management
|
||||||
|
=======================
|
||||||
|
|
||||||
|
When a module has named submodules that define one or several clock domains with the same name, those clock domain names are prefixed with the name of each submodule plus an underscore.
|
||||||
|
|
||||||
|
An example use case of this feature is a system with two independent video outputs. Each video output module is made of a clock generator module that defines a clock domain ``pix`` and drives the clock signal, plus a driver module that has synchronous statements and other elements in clock domain ``pix``. The designer of the video output module can simply use the clock domain name ``pix`` in that module. In the top-level system module, the video output submodules are named ``video0`` and ``video1``. Migen then automatically renames the ``pix`` clock domain of each module to ``video0_pix`` and ``video1_pix``. Note that happens only because the clock domain is defined (using ClockDomain objects), not simply referenced (using e.g. synchronous statements) in the video output modules.
|
||||||
|
|
||||||
|
Clock domain name overlap is an error condition when any of the submodules that defines the clock domains is anonymous.
|
||||||
|
|
||||||
|
Finalization mechanism
|
||||||
|
======================
|
||||||
|
|
||||||
|
Sometimes, it is desirable that some of a module logic be created only after the user has finished manipulating that module. For example, the FSM module supports that states be defined dynamically, and the width of the state signal can be known only after all states have been added. One solution is to declare the final number of states in the FSM constructor, but this is not user-friendly. A better solution is to automatically create the state signal just before the FSM module is converted to V*HDL. Migen supports this using the so-called finalization mechanism.
|
||||||
|
|
||||||
|
Modules can overload a ``do_finalize`` method that can create logic and is called using the algorithm below:
|
||||||
|
|
||||||
|
#. Finalization of the current module begins.
|
||||||
|
#. If the module has already been finalized (e.g. manually), the procedure stops here.
|
||||||
|
#. Submodules of the current module are recursively finalized.
|
||||||
|
#. ``do_finalize`` is called for the current module.
|
||||||
|
#. Any new submodules created by the current module's ``do_finalize`` are recursively finalized.
|
||||||
|
|
||||||
|
Finalization is automatically invoked at V*HDL conversion and at simulation. It can be manually invoked for any module by calling its ``finalize`` method.
|
||||||
|
|
||||||
|
The clock domain management mechanism explained above happens during finalization.
|
||||||
|
|
||||||
|
Simulation
|
||||||
|
==========
|
||||||
|
|
||||||
|
The ``do_simulation`` method of the ``Module`` class can be overloaded and will be executed at each clock cycle. See :ref:`simulating` for more information on using the simulator.
|
||||||
|
|
||||||
|
Simulation of designs with several clock domains is not supported yet.
|
||||||
|
|
||||||
Conversion for synthesis
|
Conversion for synthesis
|
||||||
************************
|
************************
|
||||||
|
|
||||||
Any FHDL fragment (except, of course, its simulation functions) can be converted into synthesizable Verilog HDL. This is accomplished by using the ``convert`` function in the ``verilog`` module.
|
Any FHDL module (except, of course, its simulation functions) can be converted into synthesizable Verilog HDL. This is accomplished by using the ``convert`` function in the ``verilog`` module.
|
||||||
|
|
||||||
Migen does not provide support for any specific synthesis tools or ASIC/FPGA technologies. Users must run themselves the generated code through the appropriate tool flow for hardware implementation.
|
Migen does not provide support for any specific synthesis tools or ASIC/FPGA technologies. Users must run themselves the generated code through the appropriate tool flow for hardware implementation.
|
||||||
|
|
||||||
The Mibuild package, available separately from the Migen website, provides scripts to interface third-party FPGA tools to Migen and a database of boards for the easy deployment of designs.
|
The Mibuild package, available separately from the Migen website, provides scripts to interface third-party FPGA tools to Migen and a database of boards for the easy deployment of designs.
|
||||||
|
|
||||||
Multi-clock-domain designs
|
|
||||||
**************************
|
|
||||||
|
|
||||||
A clock domain is identified by its name (a string). A design with multiple clock domains passes a dictionary instead of a list of synchronous statements in the ``Fragment`` constructor. Keys of that dictionary are the names of the clock domains, and the associated values are the statements that should be executed at each cycle of the clock in that domain.
|
|
||||||
|
|
||||||
Mapping clock domain names to clock signals is done during conversion. The ``clock_domain`` parameter of the conversion function accepts a dictionary keyed by clock domain names that contains ``ClockDomain`` objects. ``ClockDomain`` objects are containers for a clock signal and a optional reset signal. Those signals can be driven like other FHDL signals.
|
|
||||||
|
|
Loading…
Reference in New Issue