mirror of
https://github.com/myhdl/myhdl.git
synced 2024-12-14 07:44:38 +08:00
added new doc based on sphinx
This commit is contained in:
parent
0464469a03
commit
8678647c72
66
doc/Makefile
Normal file
66
doc/Makefile
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build.py
|
||||||
|
PAPER =
|
||||||
|
|
||||||
|
ALLSPHINXOPTS = -d build/doctrees -D latex_paper_size=$(PAPER) \
|
||||||
|
$(SPHINXOPTS) source
|
||||||
|
|
||||||
|
.PHONY: help clean html web htmlhelp latex changes linkcheck
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " web to make files usable by Sphinx.web"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " changes to make an overview over all changed/added/deprecated items"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -rf build/*
|
||||||
|
|
||||||
|
html:
|
||||||
|
mkdir -p build/html build/doctrees
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in build/html."
|
||||||
|
|
||||||
|
web:
|
||||||
|
mkdir -p build/web build/doctrees
|
||||||
|
$(SPHINXBUILD) -b web $(ALLSPHINXOPTS) build/web
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run"
|
||||||
|
@echo " python -m sphinx.web build/web"
|
||||||
|
@echo "to start the server."
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
mkdir -p build/htmlhelp build/doctrees
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in build/htmlhelp."
|
||||||
|
|
||||||
|
latex:
|
||||||
|
mkdir -p build/latex build/doctrees
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in build/latex."
|
||||||
|
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||||
|
"run these through (pdf)latex."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
mkdir -p build/changes build/doctrees
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in build/changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
mkdir -p build/linkcheck build/doctrees
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in build/linkcheck/output.txt."
|
132
doc/source/conf.py
Normal file
132
doc/source/conf.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# MyHDL documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart.py on Thu Mar 20 11:33:23 2008.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its containing dir.
|
||||||
|
#
|
||||||
|
# The contents of this file are pickled, so don't put values in the namespace
|
||||||
|
# that aren't pickleable (module imports are okay, they're removed automatically).
|
||||||
|
#
|
||||||
|
# All configuration values have a default value; values that are commented out
|
||||||
|
# serve to show the default value.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# If your extensions are in another directory, add it here.
|
||||||
|
#sys.path.append('some/directory')
|
||||||
|
|
||||||
|
# General configuration
|
||||||
|
# ---------------------
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
|
# coming with Sphinx (named 'sphinx.addons.*') or your custom ones.
|
||||||
|
#extensions = []
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['.templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General substitutions.
|
||||||
|
project = 'MyHDL'
|
||||||
|
copyright = '2008, 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.6dev'
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = '0.6dev'
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of documents that shouldn't be included in the build.
|
||||||
|
#unused_docs = []
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
#add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
#add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
|
||||||
|
# Options for HTML output
|
||||||
|
# -----------------------
|
||||||
|
|
||||||
|
# The style sheet to use for HTML and HTML Help pages. A file of that name
|
||||||
|
# must exist either in Sphinx' static/ path, or in one of the custom paths
|
||||||
|
# given in html_static_path.
|
||||||
|
html_style = 'default.css'
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['.static']
|
||||||
|
|
||||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
|
# using the given strftime format.
|
||||||
|
html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
#html_use_smartypants = True
|
||||||
|
|
||||||
|
# Content template for the index page.
|
||||||
|
#html_index = ''
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_use_modindex = True
|
||||||
|
|
||||||
|
# If true, the reST sources are included in the HTML build as _sources/<name>.
|
||||||
|
#html_copy_source = True
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'MyHDLdoc'
|
||||||
|
|
||||||
|
|
||||||
|
# Options for LaTeX output
|
||||||
|
# ------------------------
|
||||||
|
|
||||||
|
# The paper size ('letter' or 'a4').
|
||||||
|
#latex_paper_size = 'letter'
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#latex_font_size = '10pt'
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, document class [howto/manual]).
|
||||||
|
#latex_documents = []
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#latex_preamble = ''
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_use_modindex = True
|
21
doc/source/index.rst
Normal file
21
doc/source/index.rst
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.. MyHDL documentation master file, created by sphinx-quickstart.py on Thu Mar 20 11:33:23 2008.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
Welcome to MyHDL's documentation!
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
manual/MyHDL
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
||||||
|
|
84
doc/source/manual/MyHDL.rst
Normal file
84
doc/source/manual/MyHDL.rst
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
********************
|
||||||
|
The MyHDL manual
|
||||||
|
********************
|
||||||
|
|
||||||
|
.. % \renewcommand{\ttdefault}{cmtt}
|
||||||
|
.. % \renewcommand{\sfdefault}{cmss}
|
||||||
|
.. % \newcommand{\myhdl}{\protect \mbox{MyHDL}}
|
||||||
|
|
||||||
|
XXX: input{boilerplate} :XXX
|
||||||
|
XXX: input{copyright} :XXX
|
||||||
|
|
||||||
|
.. topic:: Abstract
|
||||||
|
|
||||||
|
The goal of the MyHDL project is to empower hardware designers with the elegance
|
||||||
|
and simplicity of the Python language.
|
||||||
|
|
||||||
|
MyHDL is a free, open-source (LGPL) package for using Python as a hardware
|
||||||
|
description and verification language. Python is a very high level language, and
|
||||||
|
hardware designers can use its full power to model and simulate their designs.
|
||||||
|
Moreover, MyHDL can convert a design to Verilog. In combination with an external
|
||||||
|
synthesis tool, it provides a complete path from Python to a silicon
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
*Modeling*
|
||||||
|
|
||||||
|
Python's power and clarity make MyHDL an ideal solution for high level modeling.
|
||||||
|
Python is famous for enabling elegant solutions to complex modeling problems.
|
||||||
|
Moreover, Python is outstanding for rapid application development and
|
||||||
|
experimentation.
|
||||||
|
|
||||||
|
The key idea behind MyHDL is the use of Python generators to model hardware
|
||||||
|
concurrency. Generators are best described as resumable functions. In MyHDL,
|
||||||
|
generators are used in a specific way so that they become similar to always
|
||||||
|
blocks in Verilog or processes in VHDL.
|
||||||
|
|
||||||
|
A hardware module is modeled as a function that returns any number of
|
||||||
|
generators. This approach makes it straightforward to support features such as
|
||||||
|
arbitrary hierarchy, named port association, arrays of instances, and
|
||||||
|
conditional instantiation.
|
||||||
|
|
||||||
|
Furthermore, MyHDL provides classes that implement traditional hardware
|
||||||
|
description concepts. It provides a signal class to support communication
|
||||||
|
between generators, a class to support bit oriented operations, and a class for
|
||||||
|
enumeration types.
|
||||||
|
|
||||||
|
*Simulation and Verification*
|
||||||
|
|
||||||
|
The built-in simulator runs on top of the Python interpreter. It supports
|
||||||
|
waveform viewing by tracing signal changes in a VCD file.
|
||||||
|
|
||||||
|
With MyHDL, the Python unit test framework can be used on hardware designs.
|
||||||
|
Although unit testing is a popular modern software verification technique, it is
|
||||||
|
not yet common in the hardware design world, making it one more area in which
|
||||||
|
MyHDL innovates.
|
||||||
|
|
||||||
|
MyHDL can also be used as hardware verification language for VHDL and Verilog
|
||||||
|
designs, by co-simulation with traditional HDL simulators.
|
||||||
|
|
||||||
|
*Conversion to Verilog*
|
||||||
|
|
||||||
|
The converter to Verilog works on an instantiated design that has been fully
|
||||||
|
elaborated. Consequently, the original design structure can be arbitrarily
|
||||||
|
complex.
|
||||||
|
|
||||||
|
The converter automates certain tasks that are tedious or hard in Verilog
|
||||||
|
directly. Notable features are the possibility to choose between various FSM
|
||||||
|
state encodings based on a single attribute, the mapping of certain high-level
|
||||||
|
objects to RAM and ROM descriptions, and the automated handling of signed
|
||||||
|
arithmetic issues.
|
||||||
|
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
background
|
||||||
|
intro
|
||||||
|
modeling
|
||||||
|
unittest
|
||||||
|
cosimulation
|
||||||
|
conversion
|
||||||
|
reference
|
||||||
|
|
171
doc/source/manual/background.rst
Normal file
171
doc/source/manual/background.rst
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
|
||||||
|
.. _background:
|
||||||
|
|
||||||
|
**********************
|
||||||
|
Background information
|
||||||
|
**********************
|
||||||
|
|
||||||
|
|
||||||
|
.. _prerequisites:
|
||||||
|
|
||||||
|
Prerequisites
|
||||||
|
=============
|
||||||
|
|
||||||
|
You need a basic understanding of Python to use MyHDL. If you don't know Python,
|
||||||
|
don't worry: it it is one of the easiest programming languages to learn [#]_.
|
||||||
|
Learning Python is one of the best time investments that engineering
|
||||||
|
professionals can make [#]_.
|
||||||
|
|
||||||
|
For starters, http://www.python.org/doc/current/tut/tut.html is probably the
|
||||||
|
best choice for an on-line tutorial. For alternatives, see
|
||||||
|
http://www.python.org/doc/Newbies.html.
|
||||||
|
|
||||||
|
A working knowledge of a hardware description language such as Verilog or VHDL
|
||||||
|
is helpful.
|
||||||
|
|
||||||
|
Code examples in this manual are sometimes shortened for clarity. Complete
|
||||||
|
executable examples can be found in the distribution directory at
|
||||||
|
:file:`example/manual/`.
|
||||||
|
|
||||||
|
|
||||||
|
.. _tutorial:
|
||||||
|
|
||||||
|
A small tutorial on generators
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. index:: single: generators; tutorial on
|
||||||
|
|
||||||
|
Generators are a relatively recent Python feature. They were introduced in
|
||||||
|
Python 2.2. Because generators are the key concept in MyHDL, a small tutorial is
|
||||||
|
included a here.
|
||||||
|
|
||||||
|
Consider the following nonsensical function::
|
||||||
|
|
||||||
|
def function():
|
||||||
|
for i in range(5):
|
||||||
|
return i
|
||||||
|
|
||||||
|
You can see why it doesn't make a lot of sense. As soon as the first loop
|
||||||
|
iteration is entered, the function returns::
|
||||||
|
|
||||||
|
>>> function()
|
||||||
|
0
|
||||||
|
|
||||||
|
Returning is fatal for the function call. Further loop iterations never get a
|
||||||
|
chance, and nothing is left over from the function call when it returns.
|
||||||
|
|
||||||
|
To change the function into a generator function, we replace :keyword:`return`
|
||||||
|
with :keyword:`yield`::
|
||||||
|
|
||||||
|
def generator():
|
||||||
|
for i in range(5):
|
||||||
|
yield i
|
||||||
|
|
||||||
|
Now we get::
|
||||||
|
|
||||||
|
>>> generator()
|
||||||
|
<generator object at 0x815d5a8>
|
||||||
|
|
||||||
|
When a generator function is called, it returns a generator object. A generator
|
||||||
|
object supports the iterator protocol, which is an expensive way of saying that
|
||||||
|
you can let it generate subsequent values by calling its :func:`next` method::
|
||||||
|
|
||||||
|
>>> g = generator()
|
||||||
|
>>> g.next()
|
||||||
|
0
|
||||||
|
>>> g.next()
|
||||||
|
1
|
||||||
|
>>> g.next()
|
||||||
|
2
|
||||||
|
>>> g.next()
|
||||||
|
3
|
||||||
|
>>> g.next()
|
||||||
|
4
|
||||||
|
>>> g.next()
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<stdin>", line 1, in ?
|
||||||
|
StopIteration
|
||||||
|
|
||||||
|
Now we can generate the subsequent values from the for loop on demand, until
|
||||||
|
they are exhausted. What happens is that the :keyword:`yield` statement is like
|
||||||
|
a :keyword:`return`, except that it is non-fatal: the generator remembers its
|
||||||
|
state and the point in the code when it yielded. A higher order agent can decide
|
||||||
|
when to get the next value by calling the generator's :func:`next` method. We
|
||||||
|
say that generators are :dfn:`resumable functions`.
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: VHDL; process
|
||||||
|
single: Verilog; always block
|
||||||
|
|
||||||
|
If you are familiar with hardware description languages, this may ring a bell.
|
||||||
|
In hardware simulations, there is also a higher order agent, the Simulator, that
|
||||||
|
interacts with such resumable functions; they are called :dfn:`processes` in
|
||||||
|
VHDL and :dfn:`always blocks` in Verilog. Similarly, Python generators provide
|
||||||
|
an elegant and efficient method to model concurrency, without having to resort
|
||||||
|
to some form of threading.
|
||||||
|
|
||||||
|
.. %
|
||||||
|
.. %
|
||||||
|
|
||||||
|
.. index:: single: sensitivity list
|
||||||
|
|
||||||
|
The use of generators to model concurrency is the first key concept in MyHDL.
|
||||||
|
The second key concept is a related one: in MyHDL, the yielded values are used
|
||||||
|
to specify the conditions on which the generator should wait before resuming. In
|
||||||
|
other words, :keyword:`yield` statements work as general sensitivity lists.
|
||||||
|
|
||||||
|
.. %
|
||||||
|
|
||||||
|
For more info about generators, consult the on-line Python documentation, e.g.
|
||||||
|
at http://www.python.org/doc/2.2.2/whatsnew.
|
||||||
|
|
||||||
|
|
||||||
|
.. _deco:
|
||||||
|
|
||||||
|
About decorators
|
||||||
|
================
|
||||||
|
|
||||||
|
.. index:: single: decorators; about
|
||||||
|
|
||||||
|
Python 2.4 introduced a new feature called decorators. MyHDL 0.5 takes advantage
|
||||||
|
of this new feature by defining a number of decorators that facilitate hardware
|
||||||
|
descriptions. However, many users may not yet be familiar with decorators.
|
||||||
|
Therefore, an introduction is included here.
|
||||||
|
|
||||||
|
A decorator consists of special syntax in front of a function declaration. It
|
||||||
|
refers to a decorator function. The decorator function automatically transforms
|
||||||
|
the declared function into some other callable object.
|
||||||
|
|
||||||
|
A decorator function :func:`deco` is used in a decorator statement as follows::
|
||||||
|
|
||||||
|
@deco
|
||||||
|
def func(arg1, arg2, ...):
|
||||||
|
<body>
|
||||||
|
|
||||||
|
This code is equivalent to the following::
|
||||||
|
|
||||||
|
def func(arg1, arg2, ...):
|
||||||
|
<body>
|
||||||
|
func = deco(func)
|
||||||
|
|
||||||
|
Note that the decorator statement goes directly in front of the function
|
||||||
|
declaration, and that the function name :func:`func` is automatically reused for
|
||||||
|
the final result.
|
||||||
|
|
||||||
|
MyHDL 0.5 uses decorators to create ready-to-simulate generators from local
|
||||||
|
function definitions. Their functionality and usage will be described
|
||||||
|
extensively in this manual.
|
||||||
|
|
||||||
|
For more info about Python decorators, consult the on-line Python documentation,
|
||||||
|
e.g. at http://www.python.org/doc/2.4/whatsnew/node6.html.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Because MyHDL 0.5 uses decorators, it requires Python 2.4 or a later version.
|
||||||
|
|
||||||
|
.. rubric:: Footnotes
|
||||||
|
|
||||||
|
.. [#] You must be bored by such claims, but in Python's case it's true.
|
||||||
|
|
||||||
|
.. [#] I am not biased.
|
||||||
|
|
1087
doc/source/manual/conversion.rst
Normal file
1087
doc/source/manual/conversion.rst
Normal file
File diff suppressed because it is too large
Load Diff
415
doc/source/manual/cosimulation.rst
Normal file
415
doc/source/manual/cosimulation.rst
Normal file
@ -0,0 +1,415 @@
|
|||||||
|
|
||||||
|
.. _cosim:
|
||||||
|
|
||||||
|
***********************************
|
||||||
|
Co-simulation with Verilog and VHDL
|
||||||
|
***********************************
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-intro:
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
One of the most exciting possibilities of MyHDL\ is to use it as a hardware
|
||||||
|
verification language (HVL). A HVL is a language used to write test benches and
|
||||||
|
verification environments, and to control simulations.
|
||||||
|
|
||||||
|
Nowadays, it is generally acknowledged that HVLs should be equipped with modern
|
||||||
|
software techniques, such as object orientation. The reason is that verification
|
||||||
|
it the most complex and time-consuming task of the design process. Consequently,
|
||||||
|
every useful technique is welcome. Moreover, test benches are not required to be
|
||||||
|
implementable. Therefore, unlike with synthesizable code, there are no
|
||||||
|
constraints on creativity.
|
||||||
|
|
||||||
|
Technically, verification of a design implemented in another language requires
|
||||||
|
co-simulation. MyHDL is enabled for co-simulation with any HDL simulator that
|
||||||
|
has a procedural language interface (PLI). The MyHDL\ side is designed to be
|
||||||
|
independent of a particular simulator, On the other hand, for each HDL simulator
|
||||||
|
a specific PLI module will have to be written in C. Currently, the MyHDL release
|
||||||
|
contains a PLI module for two Verilog simulators: Icarus and Cver.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-hdl:
|
||||||
|
|
||||||
|
The HDL side
|
||||||
|
============
|
||||||
|
|
||||||
|
To introduce co-simulation, we will continue to use the Gray encoder example
|
||||||
|
from the previous chapters. Suppose that we want to synthesize it and write it
|
||||||
|
in Verilog for that purpose. Clearly we would like to reuse our unit test
|
||||||
|
verification environment.
|
||||||
|
|
||||||
|
To start, let's recall how the Gray encoder in MyHDL looks like::
|
||||||
|
|
||||||
|
def bin2gray(B, G, width):
|
||||||
|
""" Gray encoder.
|
||||||
|
|
||||||
|
B -- input intbv signal, binary encoded
|
||||||
|
G -- output intbv signal, gray encoded
|
||||||
|
width -- bit width
|
||||||
|
"""
|
||||||
|
|
||||||
|
@always_comb
|
||||||
|
def logic():
|
||||||
|
for i in range(width):
|
||||||
|
G.next[i] = B[i+1] ^ B[i]
|
||||||
|
|
||||||
|
return logic
|
||||||
|
|
||||||
|
To show the co-simulation flow, we don't need the Verilog implementation yet,
|
||||||
|
but only the interface. Our Gray encoder in Verilog would have the following
|
||||||
|
interface::
|
||||||
|
|
||||||
|
module bin2gray(B, G);
|
||||||
|
|
||||||
|
parameter width = 8;
|
||||||
|
input [width-1:0] B;
|
||||||
|
output [width-1:0] G;
|
||||||
|
....
|
||||||
|
|
||||||
|
To write a test bench, one creates a new module that instantiates the design
|
||||||
|
under test (DUT). The test bench declares nets and regs (or signals in VHDL)
|
||||||
|
that are attached to the DUT, and to stimulus generators and response checkers.
|
||||||
|
In an all-HDL flow, the generators and checkers are written in the HDL itself,
|
||||||
|
but we will want to write them in MyHDL. To make the connection, we need to
|
||||||
|
declare which regs & nets are driven and read by the MyHDL\ simulator. For our
|
||||||
|
example, this is done as follows::
|
||||||
|
|
||||||
|
module dut_bin2gray;
|
||||||
|
|
||||||
|
reg [`width-1:0] B;
|
||||||
|
wire [`width-1:0] G;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
$from_myhdl(B);
|
||||||
|
$to_myhdl(G);
|
||||||
|
end
|
||||||
|
|
||||||
|
bin2gray dut (.B(B), .G(G));
|
||||||
|
defparam dut.width = `width;
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
The ``$from_myhdl`` task call declares which regs are driven by MyHDL, and the
|
||||||
|
``$to_myhdl`` task call which regs & nets are read by it. These tasks take an
|
||||||
|
arbitrary number of arguments. They are defined in a PLI module written in C
|
||||||
|
and made available in a simulator-dependent manner. In Icarus Verilog, the
|
||||||
|
tasks are defined in a ``myhdl.vpi`` module that is compiled from C source code.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-myhdl:
|
||||||
|
|
||||||
|
The MyHDL side
|
||||||
|
==============
|
||||||
|
|
||||||
|
MyHDL supports co-simulation by a ``Cosimulation`` object. A ``Cosimulation``
|
||||||
|
object must know how to run a HDL simulation. Therefore, the first argument to
|
||||||
|
its constructor is a command string to execute a simulation.
|
||||||
|
|
||||||
|
The way to generate and run an simulation executable is simulator dependent.
|
||||||
|
For example, in Icarus Verilog, a simulation executable for our example can be
|
||||||
|
obtained obtained by running the ``iverilog`` compiler as follows::
|
||||||
|
|
||||||
|
% iverilog -o bin2gray -Dwidth=4 bin2gray.v dut_bin2gray.v
|
||||||
|
|
||||||
|
This generates a ``bin2gray`` executable for a parameter ``width`` of 4, by
|
||||||
|
compiling the contributing verilog files.
|
||||||
|
|
||||||
|
The simulation itself is run by the ``vvp`` command::
|
||||||
|
|
||||||
|
% vvp -m ./myhdl.vpi bin2gray
|
||||||
|
|
||||||
|
This runs the ``bin2gray`` simulation, and specifies to use the ``myhdl.vpi``
|
||||||
|
PLI module present in the current directory. (This is just a command line usage
|
||||||
|
example; actually simulating with the ``myhdl.vpi`` module is only meaningful
|
||||||
|
from a ``Cosimulation`` object.)
|
||||||
|
|
||||||
|
We can use a ``Cosimulation`` object to provide a HDL version of a design to the
|
||||||
|
MyHDL simulator. Instead of a generator function, we write a function that
|
||||||
|
returns a ``Cosimulation`` object. For our example and the Icarus Verilog
|
||||||
|
simulator, this is done as follows::
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from myhdl import Cosimulation
|
||||||
|
|
||||||
|
cmd = "iverilog -o bin2gray -Dwidth=%s bin2gray.v dut_bin2gray.v"
|
||||||
|
|
||||||
|
def bin2gray(B, G, width):
|
||||||
|
os.system(cmd % width)
|
||||||
|
return Cosimulation("vvp -m ./myhdl.vpi bin2gray", B=B, G=G)
|
||||||
|
|
||||||
|
After the executable command argument, the ``Cosimulation`` constructor takes an
|
||||||
|
arbitrary number of keyword arguments. Those arguments make the link between
|
||||||
|
MyHDL Signals and HDL nets, regs, or signals, by named association. The keyword
|
||||||
|
is the name of an argument in a ``$to_myhdl`` or ``$from_myhdl`` call; the
|
||||||
|
argument is a MyHDL Signal.
|
||||||
|
|
||||||
|
With all this in place, we can now use the existing unit test to verify the
|
||||||
|
Verilog implementation. Note that we kept the same name and parameters for the
|
||||||
|
the ``bin2gray`` function: all we need to do is to provide this alternative
|
||||||
|
definition to the existing unit test.
|
||||||
|
|
||||||
|
Let's try it on the Verilog design::
|
||||||
|
|
||||||
|
module bin2gray(B, G);
|
||||||
|
|
||||||
|
parameter width = 8;
|
||||||
|
input [width-1:0] B;
|
||||||
|
output [width-1:0] G;
|
||||||
|
reg [width-1:0] G;
|
||||||
|
integer i;
|
||||||
|
wire [width:0] extB;
|
||||||
|
|
||||||
|
assign extB = {1'b0, B}; // zero-extend input
|
||||||
|
|
||||||
|
always @(extB) begin
|
||||||
|
for (i=0; i < width; i=i+1)
|
||||||
|
G[i] <= extB[i+1] ^ extB[i];
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
When we run our unit test, we get::
|
||||||
|
|
||||||
|
% python test_bin2gray.py
|
||||||
|
Check that only one bit changes in successive codewords ... ok
|
||||||
|
Check that all codewords occur exactly once ... ok
|
||||||
|
Check that the code is an original Gray code ... ok
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Ran 3 tests in 2.729s
|
||||||
|
|
||||||
|
OK
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-restr:
|
||||||
|
|
||||||
|
Restrictions
|
||||||
|
============
|
||||||
|
|
||||||
|
In the ideal case, it should be possible to simulate any HDL description
|
||||||
|
seamlessly with MyHDL. Moreover the communicating signals at each side should
|
||||||
|
act transparently as a single one, enabling fully race free operation.
|
||||||
|
|
||||||
|
For various reasons, it may not be possible or desirable to achieve full
|
||||||
|
generality. As anyone that has developed applications with the Verilog PLI can
|
||||||
|
testify, the restrictions in a particular simulator, and the differences over
|
||||||
|
various simulators, can be quite frustrating. Moreover, full generality may
|
||||||
|
require a disproportionate amount of development work compared to a slightly
|
||||||
|
less general solution that may be sufficient for the target application.
|
||||||
|
|
||||||
|
Consequently, I have tried to achieve a solution which is simple enough so that
|
||||||
|
one can reasonably expect that any PLI-enabled simulator can support it, and
|
||||||
|
that is relatively easy to verify and maintain. At the same time, the solution
|
||||||
|
is sufficiently general to cover the target application space.
|
||||||
|
|
||||||
|
The result is a compromise that places certain restrictions on the HDL code. In
|
||||||
|
this section, these restrictions are presented.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-pass:
|
||||||
|
|
||||||
|
Only passive HDL can be co-simulated
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
The most important restriction of the MyHDL co-simulation solution is that only
|
||||||
|
"passive" HDL can be co-simulated. This means that the HDL code should not
|
||||||
|
contain any statements with time delays. In other words, the MyHDL simulator
|
||||||
|
should be the master of time; in particular, any clock signal should be
|
||||||
|
generated at the MyHDL side.
|
||||||
|
|
||||||
|
At first this may seem like an important restriction, but if one considers the
|
||||||
|
target application for co-simulation, it probably isn't.
|
||||||
|
|
||||||
|
MyHDL supports co-simulation so that test benches for HDL designs can be written
|
||||||
|
in Python. Let's consider the nature of the target HDL designs. For high-level,
|
||||||
|
behavioral models that are not intended for implementation, it should come as no
|
||||||
|
surprise that I would recommend to write them in MyHDL directly; that is one of
|
||||||
|
the goals of the MyHDL effort. Likewise, gate level designs with annotated
|
||||||
|
timing are not the target application: static timing analysis is a much better
|
||||||
|
verification method for such designs.
|
||||||
|
|
||||||
|
Rather, the targeted HDL designs are naturally models that are intended for
|
||||||
|
implementation, most likely through synthesis. As time delays are meaningless in
|
||||||
|
synthesizable code, the restriction is compatible with the target application.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-race:
|
||||||
|
|
||||||
|
Race sensitivity issues
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
In a typical RTL code, some events cause other events to occur in the same time
|
||||||
|
step. For example, when a clock signal triggers some signals may change in the
|
||||||
|
same time step. For race-free operation, an HDL must differentiate between such
|
||||||
|
events within a time step. This is done by the concept of "delta" cycles. In a
|
||||||
|
fully general, race free co-simulation, the co-simulators would communicate at
|
||||||
|
the level of delta cycles. However, in MyHDL co-simulation, this is not entirely
|
||||||
|
the case.
|
||||||
|
|
||||||
|
Delta cycles from the MyHDL simulator toward the HDL co-simulator are preserved.
|
||||||
|
However, in the opposite direction, they are not. The signals changes are only
|
||||||
|
returned to the MyHDL simulator after all delta cycles have been performed in
|
||||||
|
the HDL co-simulator.
|
||||||
|
|
||||||
|
What does this mean? Let's start with the good news. As explained in the
|
||||||
|
previous section, the concept behind MyHDL co-simulation implies that clocks are
|
||||||
|
generated at the MyHDL side. *When using a MyHDL clock and its corresponding
|
||||||
|
HDL signal directly as a clock, co-simulation is race free.* In other words, the
|
||||||
|
case that most closely reflects the MyHDL co-simulation approach, is race free.
|
||||||
|
|
||||||
|
The situation is different when you want to use a signal driven by the HDL (and
|
||||||
|
the corresponding MyHDL signal) as a clock. Communication triggered by such a
|
||||||
|
clock is not race free. The solution is to treat such an interface as a chip
|
||||||
|
interface instead of an RTL interface. For example, when data is triggered at
|
||||||
|
positive clock edges, it can safely be sampled at negative clock edges.
|
||||||
|
Alternatively, the MyHDL data signals can be declared with a delay value, so
|
||||||
|
that they are guaranteed to change after the clock edge.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-impl:
|
||||||
|
|
||||||
|
Implementation notes
|
||||||
|
====================
|
||||||
|
|
||||||
|
This section requires some knowledge of PLI terminology.
|
||||||
|
|
||||||
|
|
||||||
|
Enabling a simulator for co-simulation requires a PLI module written in C. In
|
||||||
|
Verilog, the PLI is part of the "standard". However, different simulators
|
||||||
|
implement different versions and portions of the standard. Worse yet, the
|
||||||
|
behavior of certain PLI callbacks is not defined on some essential points. As a
|
||||||
|
result, one should plan to write or at least customize a specific PLI module for
|
||||||
|
any simulator. The release contains a PLI module for the open source Icarus and
|
||||||
|
Cver simulators.
|
||||||
|
|
||||||
|
This section documents the current approach and status of the PLI module
|
||||||
|
implementation and some reflections on future implementations.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-icarus:
|
||||||
|
|
||||||
|
Icarus Verilog
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-icarus-delta:
|
||||||
|
|
||||||
|
Delta cycle implementation
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
To make co-simulation work, a specific type of PLI callback is needed. The
|
||||||
|
callback should be run when all pending events have been processed, while
|
||||||
|
allowing the creation of new events in the current time step (e.g. by the MyHDL
|
||||||
|
simulator). In some Verilog simulators, the ``cbReadWriteSync`` callback does
|
||||||
|
exactly that. However, in others, including Icarus, it does not. The callback's
|
||||||
|
behavior is not fully standardized; some simulators run the callback before non-
|
||||||
|
blocking assignment events have been processed.
|
||||||
|
|
||||||
|
Consequently, I had to look for a workaround. One half of the solution is to use
|
||||||
|
the ``cbReadOnlySync`` callback. This callback runs after all pending events
|
||||||
|
have been processed. However, it does not permit to create new events in the
|
||||||
|
current time step. The second half of the solution is to map MyHDL delta cycles
|
||||||
|
onto real Verilog time steps. Note that fortunately I had some freedom here
|
||||||
|
because of the restriction that only passive HDL code can be co-simulated.
|
||||||
|
|
||||||
|
I chose to make the time granularity in the Verilog simulator a 1000 times finer
|
||||||
|
than in the MyHDL simulator. For each MyHDL time step, 1000 Verilog time steps
|
||||||
|
are available for MyHDL delta cycles. In practice, only a few delta cycles per
|
||||||
|
time step should be needed. Exceeding this limit almost certainly indicates a
|
||||||
|
design error; the limit is checked at run-time. The factor 1000 also makes it
|
||||||
|
easy to distinguish "real" time from delta cycle time when printing out the
|
||||||
|
Verilog time.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-icarus-pass:
|
||||||
|
|
||||||
|
Passive Verilog check
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
As explained before, co-simulated Verilog should not contain delay statements.
|
||||||
|
Ideally, there should be a run-time check to flag non-compliant code. However,
|
||||||
|
there is currently no such check in the Icarus module.
|
||||||
|
|
||||||
|
The check can be written using the ``cbNextSimTime`` VPI callback in Verilog.
|
||||||
|
However, Icarus 0.7 doesn't support this callback. In the meantime, support for
|
||||||
|
it has been added to the Icarus development branch. When Icarus 0.8 is
|
||||||
|
released, a check will be added.
|
||||||
|
|
||||||
|
In the mean time, just don't do this. It may appear to "work" but it really
|
||||||
|
won't as events will be missed over the co-simulation interface.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-cver:
|
||||||
|
|
||||||
|
Cver
|
||||||
|
----
|
||||||
|
|
||||||
|
MyHDL co-simulation is supported with the open source Verilog simulator Cver.
|
||||||
|
The PLI module is based on the one for Icarus and basically has the same
|
||||||
|
functionality. Only some cosmetic modifications were required.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-impl-verilog:
|
||||||
|
|
||||||
|
Other Verilog simulators
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The Icarus module is written with VPI calls, which are provided by the most
|
||||||
|
recent generation of the Verilog PLI. Some simulators may only support TF/ACC
|
||||||
|
calls, requiring a complete redesign of the interface module.
|
||||||
|
|
||||||
|
If the simulator supports VPI, the Icarus module should be reusable to a large
|
||||||
|
extent. However, it may be possible to improve on it. The workaround to support
|
||||||
|
delta cycles described in Section :ref:`cosim-icarus-delta` may not be
|
||||||
|
necessary. In some simulators, the ``cbReadWriteSync`` callback occurs after all
|
||||||
|
events (including non-blocking assignments) have been processed. In that case,
|
||||||
|
the functionality can be supported without a finer time granularity in the
|
||||||
|
Verilog simulator.
|
||||||
|
|
||||||
|
There are also Verilog standardization efforts underway to resolve the ambiguity
|
||||||
|
of the ``cbReadWriteSync`` callback. The solution will be to introduce new, well
|
||||||
|
defined callbacks. From reading some proposals, I conclude that the
|
||||||
|
``cbEndOfSimTime`` callback would provide the required functionality.
|
||||||
|
|
||||||
|
The MyHDL project currently has no access to commercial Verilog simulators, so
|
||||||
|
progress in co-simulation support depends on external interest and
|
||||||
|
participation. Users have reported that they are using MyHDL co-simulation with
|
||||||
|
the simulators from Aldec and Modelsim.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-impl-syscalls:
|
||||||
|
|
||||||
|
Interrupted system calls
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The PLI module uses ``read`` and ``write`` system calls to communicate between
|
||||||
|
the co-simulators. The implementation assumes that these calls are restarted
|
||||||
|
automatically by the operating system when interrupted. This is apparently what
|
||||||
|
happens on the Linux box on which MyHDL is developed.
|
||||||
|
|
||||||
|
It is known how non-restarted interrupted system calls should be handled, but
|
||||||
|
currently such code cannot be tested on the MyHDL development platform. Also, it
|
||||||
|
is not clear whether this is still a relevant issue with modern operating
|
||||||
|
systems. Therefore, this issue has not been addressed at this moment. However,
|
||||||
|
assertions have been included that should trigger when this situation occurs.
|
||||||
|
|
||||||
|
Whenever an assertion fires in the PLI module, please report it. The same holds
|
||||||
|
for Python exceptions that cannot be easily explained.
|
||||||
|
|
||||||
|
|
||||||
|
.. _cosim-impl-vhdl:
|
||||||
|
|
||||||
|
VHDL
|
||||||
|
----
|
||||||
|
|
||||||
|
It would be nice to have an interface to VHDL simulators such as the Modelsim
|
||||||
|
VHDL simulator. This will require a PLI module using the PLI of the VHDL
|
||||||
|
simulator.
|
||||||
|
|
||||||
|
The MyHDL project currently has no access to commercial VHDL simulators, so
|
||||||
|
progress in co-simulation support will depend on external interest and
|
||||||
|
participation.
|
||||||
|
|
537
doc/source/manual/intro.rst
Normal file
537
doc/source/manual/intro.rst
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
|
||||||
|
.. _intro:
|
||||||
|
|
||||||
|
*********************
|
||||||
|
Introduction to MyHDL
|
||||||
|
*********************
|
||||||
|
|
||||||
|
|
||||||
|
.. _intro-basic:
|
||||||
|
|
||||||
|
A basic MyHDL simulation
|
||||||
|
========================
|
||||||
|
|
||||||
|
We will introduce MyHDL with a classic ``Hello World`` style example. All
|
||||||
|
example code can be found in the distribution directory under
|
||||||
|
:file:`example/manual/`. Here are the contents of a MyHDL\ simulation script
|
||||||
|
called :file:`hello1.py`::
|
||||||
|
|
||||||
|
from myhdl import Signal, delay, always, now, Simulation
|
||||||
|
|
||||||
|
def HelloWorld():
|
||||||
|
|
||||||
|
interval = delay(10)
|
||||||
|
|
||||||
|
@always(interval)
|
||||||
|
def sayHello():
|
||||||
|
print "%s Hello World!" % now()
|
||||||
|
|
||||||
|
return sayHello
|
||||||
|
|
||||||
|
|
||||||
|
inst = HelloWorld()
|
||||||
|
sim = Simulation(inst)
|
||||||
|
sim.run(30)
|
||||||
|
|
||||||
|
When we run this simulation, we get the following output::
|
||||||
|
|
||||||
|
% python hello1.py
|
||||||
|
10 Hello World!
|
||||||
|
20 Hello World!
|
||||||
|
30 Hello World!
|
||||||
|
_SuspendSimulation: Simulated 30 timesteps
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. index:: single: decorator; always
|
||||||
|
|
||||||
|
Inside the top level function we declare a local function called
|
||||||
|
:func:`sayHello` that defines the desired behavior. This function is decorated
|
||||||
|
with an :func:`always` decorator that has a delay object as its parameter. The
|
||||||
|
meaning is that the function will be executed whenever the specified delay
|
||||||
|
interval has expired.
|
||||||
|
|
||||||
|
Behind the curtains, the :func:`always` decorator creates a Python *generator*
|
||||||
|
and reuses the name of the decorated function for it. Generators are the
|
||||||
|
fundamental objects in MyHDL, and we will say much more about them further on.
|
||||||
|
|
||||||
|
Finally, the top level function returns the local generator. This is the
|
||||||
|
simplest case of the basic MyHDL code pattern to define the contents of a
|
||||||
|
hardware module. We will describe the general case further on.
|
||||||
|
|
||||||
|
In MyHDL, we create an *instance* of a hardware module by calling the
|
||||||
|
corresponding function. In the example, variable ``inst`` refers to an instance
|
||||||
|
of :func:`HelloWorld`. To simulate the instance, we pass it as an argument to a
|
||||||
|
:class:`Simulation` object constructor. We then run the simulation for the
|
||||||
|
desired amount of timesteps.
|
||||||
|
|
||||||
|
|
||||||
|
.. _intro-conc:
|
||||||
|
|
||||||
|
Signals, ports, and concurrency
|
||||||
|
===============================
|
||||||
|
|
||||||
|
In the previous section, we simulated a design with a single generator and no
|
||||||
|
concurrency. On the other hand, real hardware descriptions are typically
|
||||||
|
massively concurrent. MyHDL supports this by allowing an arbitrary number of
|
||||||
|
concurrently running generators.
|
||||||
|
|
||||||
|
With concurrency comes the problem of deterministic communication. Hardware
|
||||||
|
languages use special objects to support deterministic communication between
|
||||||
|
concurrent code. In particular, MyHDL has a :class:`Signal` object which is
|
||||||
|
roughly modeled after VHDL signals.
|
||||||
|
|
||||||
|
We will demonstrate signals and concurrency by extending and modifying our first
|
||||||
|
example. We define two hardware modules, one that drives a clock signal, and one
|
||||||
|
that is sensitive to a positive edge on a clock signal::
|
||||||
|
|
||||||
|
from myhdl import Signal, delay, always, now, Simulation
|
||||||
|
|
||||||
|
|
||||||
|
def ClkDriver(clk):
|
||||||
|
|
||||||
|
halfPeriod = delay(10)
|
||||||
|
|
||||||
|
@always(halfPeriod)
|
||||||
|
def driveClk():
|
||||||
|
clk.next = not clk
|
||||||
|
|
||||||
|
return driveClk
|
||||||
|
|
||||||
|
|
||||||
|
def HelloWorld(clk):
|
||||||
|
|
||||||
|
@always(clk.posedge)
|
||||||
|
def sayHello():
|
||||||
|
print "%s Hello World!" % now()
|
||||||
|
|
||||||
|
return sayHello
|
||||||
|
|
||||||
|
|
||||||
|
clk = Signal(0)
|
||||||
|
clkdriver_inst = ClkDriver(clk)
|
||||||
|
hello_inst = HelloWorld(clk)
|
||||||
|
sim = Simulation(clkdriver_inst, hello_inst)
|
||||||
|
sim.run(50)
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: VHDL; signal assignment
|
||||||
|
single: Verilog; non-blocking assignment
|
||||||
|
|
||||||
|
The clock driver function :func:`ClkDriver` has a clock signal as its parameter.
|
||||||
|
This is how a *port* is modeled in MyHDL. The function defines a generator that
|
||||||
|
continuously toggles a clock signal after a certain delay. A new value of a
|
||||||
|
signal is specified by assigning to its ``next`` attribute. This is the MyHDL
|
||||||
|
equivalent of the VHDL signal assignment and the Verilog non-blocking
|
||||||
|
assignment.
|
||||||
|
|
||||||
|
.. %
|
||||||
|
.. %
|
||||||
|
|
||||||
|
.. index:: single: wait; for a rising edge
|
||||||
|
|
||||||
|
The :func:`HelloWorld` function is modified from the first example. It now also
|
||||||
|
takes a clock signal as parameter. Its generator is made sensitive to a rising
|
||||||
|
edge of the clock signal. This is specified by the ``posedge`` attribute of a
|
||||||
|
signal. The edge specifier is the argument of the ``always`` decorator. As a
|
||||||
|
result, the decorated function will be executed on every rising clock edge.
|
||||||
|
|
||||||
|
.. %
|
||||||
|
|
||||||
|
The ``clk`` signal is constructed with an initial value ``0``. When creating an
|
||||||
|
instance of each hardware module, the same clock signal is passed as the
|
||||||
|
argument. The result is that the instances are now connected through the clock
|
||||||
|
signal. The :class:`Simulation` object is constructed with the two instances.
|
||||||
|
|
||||||
|
When we run the simulation, we get::
|
||||||
|
|
||||||
|
% python hello2.py
|
||||||
|
10 Hello World!
|
||||||
|
30 Hello World!
|
||||||
|
50 Hello World!
|
||||||
|
_SuspendSimulation: Simulated 50 timesteps
|
||||||
|
|
||||||
|
|
||||||
|
.. _intro-hier:
|
||||||
|
|
||||||
|
Parameters and hierarchy
|
||||||
|
========================
|
||||||
|
|
||||||
|
We have seen that MyHDL uses functions to model hardware modules. We have also
|
||||||
|
seen that ports are modeled by using signals as parameters. To make designs
|
||||||
|
reusable we will also want to use other objects as parameters. For example, we
|
||||||
|
can change the clock generator function to make it more general and reusable, by
|
||||||
|
making the clock period parameterizable, as follows::
|
||||||
|
|
||||||
|
from myhdl import Signal, delay, instance, always, now, Simulation
|
||||||
|
|
||||||
|
def ClkDriver(clk, period=20):
|
||||||
|
|
||||||
|
lowTime = int(period/2)
|
||||||
|
highTime = period - lowTime
|
||||||
|
|
||||||
|
@instance
|
||||||
|
def driveClk():
|
||||||
|
while True:
|
||||||
|
yield delay(lowTime)
|
||||||
|
clk.next = 1
|
||||||
|
yield delay(highTime)
|
||||||
|
clk.next = 0
|
||||||
|
|
||||||
|
return driveClk
|
||||||
|
|
||||||
|
In addition to the clock signal, the clock period is a parameter, with a default
|
||||||
|
value of ``20``.
|
||||||
|
|
||||||
|
.. index:: single: decorator; instance
|
||||||
|
|
||||||
|
As the low time of the clock may differ from the high time in case of an odd
|
||||||
|
period, we cannot use the :func:`always` decorator with a single delay value
|
||||||
|
anymore. Instead, the :func:`driveClk` function is now a generator function with
|
||||||
|
an explicit definition of the desired behavior. It is decorated with the
|
||||||
|
:func:`instance` decorator. You can see that :func:`driveClk` is a generator
|
||||||
|
function because it contains ``yield`` statements.
|
||||||
|
|
||||||
|
When a generator function is called, it returns a generator object. This is
|
||||||
|
basically what the :func:`instance` decorator does. It is less sophisticated
|
||||||
|
than the :func:`always` decorator, but it can be used to create a generator from
|
||||||
|
any local generator function.
|
||||||
|
|
||||||
|
The ``yield`` statement is a general Python construct, but MyHDL uses it in a
|
||||||
|
dedicated way. In MyHDL, it has a similar meaning as the wait statement in
|
||||||
|
VHDL: the statement suspends execution of a generator, and its clauses specify
|
||||||
|
the conditions on which the generator should wait before resuming. In this case,
|
||||||
|
the generator waits for a certain delay.
|
||||||
|
|
||||||
|
Note that to make sure that the generator runs "forever", we wrap its behavior
|
||||||
|
in a ``while True`` loop.
|
||||||
|
|
||||||
|
Similarly, we can define a general :func:`Hello` function as follows::
|
||||||
|
|
||||||
|
def Hello(clk, to="World!"):
|
||||||
|
|
||||||
|
@always(clk.posedge)
|
||||||
|
def sayHello():
|
||||||
|
print "%s Hello %s" % (now(), to)
|
||||||
|
|
||||||
|
return sayHello
|
||||||
|
|
||||||
|
.. index:: single: instance; defined
|
||||||
|
|
||||||
|
We can create any number of instances by calling the functions with the
|
||||||
|
appropriate parameters. Hierarchy can be modeled by defining the instances in a
|
||||||
|
higher-level function, and returning them. This pattern can be repeated for an
|
||||||
|
arbitrary number of hierarchical levels. Consequently, the general definition of
|
||||||
|
a MyHDL instance is recursive: an instance is either a sequence of instances, or
|
||||||
|
a generator.
|
||||||
|
|
||||||
|
.. %
|
||||||
|
|
||||||
|
As an example, we will create a higher-level function with four instances of the
|
||||||
|
lower-level functions, and simulate it::
|
||||||
|
|
||||||
|
def greetings():
|
||||||
|
|
||||||
|
clk1 = Signal(0)
|
||||||
|
clk2 = Signal(0)
|
||||||
|
|
||||||
|
clkdriver_1 = ClkDriver(clk1) # positional and default association
|
||||||
|
clkdriver_2 = ClkDriver(clk=clk2, period=19) # named association
|
||||||
|
hello_1 = Hello(clk=clk1) # named and default association
|
||||||
|
hello_2 = Hello(to="MyHDL", clk=clk2) # named association
|
||||||
|
|
||||||
|
return clkdriver_1, clkdriver_2, hello_1, hello_2
|
||||||
|
|
||||||
|
|
||||||
|
inst = greetings()
|
||||||
|
sim = Simulation(inst)
|
||||||
|
sim.run(50)
|
||||||
|
|
||||||
|
As in standard Python, positional or named parameter association can be used in
|
||||||
|
instantiations, or a mix of both [#]_. All these styles are demonstrated in the
|
||||||
|
example above. Named association can be very useful if there are a lot of
|
||||||
|
parameters, as the argument order in the call does not matter in that case.
|
||||||
|
|
||||||
|
The simulation produces the following output::
|
||||||
|
|
||||||
|
% python greetings.py
|
||||||
|
9 Hello MyHDL
|
||||||
|
10 Hello World!
|
||||||
|
28 Hello MyHDL
|
||||||
|
30 Hello World!
|
||||||
|
47 Hello MyHDL
|
||||||
|
50 Hello World!
|
||||||
|
_SuspendSimulation: Simulated 50 timesteps
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Some commonly used terminology has different meanings in Python versus hardware
|
||||||
|
design. Rather than artificially changing terminology, I think it's best to keep
|
||||||
|
it and explicitly describing the differences.
|
||||||
|
|
||||||
|
.. index:: single: module; in Python versus hardware design
|
||||||
|
|
||||||
|
A :dfn:`module` in Python refers to all source code in a particular file. A
|
||||||
|
module can be reused by other modules by importing. In hardware design, a module
|
||||||
|
is a reusable block of hardware with a well defined interface. It can be reused
|
||||||
|
in another module by :dfn:`instantiating` it.
|
||||||
|
|
||||||
|
.. %
|
||||||
|
|
||||||
|
.. index:: single: instance; in Python versus hardware design
|
||||||
|
|
||||||
|
An :dfn:`instance` in Python (and other object-oriented languages) refers to the
|
||||||
|
object created by a class constructor. In hardware design, an instance is a
|
||||||
|
particular incarnation of a hardware module.
|
||||||
|
|
||||||
|
.. %
|
||||||
|
|
||||||
|
Normally, the meaning should be clear from the context. Occasionally, I may
|
||||||
|
qualify terms with the words 'hardware' or 'MyHDL' to avoid ambiguity.
|
||||||
|
|
||||||
|
|
||||||
|
.. _intro-bit:
|
||||||
|
|
||||||
|
Bit oriented operations
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Hardware design involves dealing with bits and bit-oriented operations. The
|
||||||
|
standard Python type :class:`int` has most of the desired features, but lacks
|
||||||
|
support for indexing and slicing. For this reason, MyHDL provides the
|
||||||
|
:class:`intbv` class. The name was chosen to suggest an integer with bit vector
|
||||||
|
flavor.
|
||||||
|
|
||||||
|
Class :class:`intbv` works transparently with other integer-like types. Like
|
||||||
|
class :class:`int`, it provides access to the underlying two's complement
|
||||||
|
representation for bitwise operations. In addition, it is a mutable type that
|
||||||
|
provides indexing and slicing operations, and some additional bit-oriented
|
||||||
|
support such as concatenation.
|
||||||
|
|
||||||
|
|
||||||
|
.. _intro-indexing:
|
||||||
|
|
||||||
|
Bit indexing
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. index:: single: bit indexing
|
||||||
|
|
||||||
|
As an example, we will consider the design of a Gray encoder. The following code
|
||||||
|
is a Gray encoder modeled in MyHDL::
|
||||||
|
|
||||||
|
from myhdl import Signal, delay, Simulation, always_comb, instance, intbv, bin
|
||||||
|
|
||||||
|
def bin2gray(B, G, width):
|
||||||
|
""" Gray encoder.
|
||||||
|
|
||||||
|
B -- input intbv signal, binary encoded
|
||||||
|
G -- output intbv signal, gray encoded
|
||||||
|
width -- bit width
|
||||||
|
"""
|
||||||
|
|
||||||
|
@always_comb
|
||||||
|
def logic():
|
||||||
|
for i in range(width):
|
||||||
|
G.next[i] = B[i+1] ^ B[i]
|
||||||
|
|
||||||
|
return logic
|
||||||
|
|
||||||
|
This code introduces a few new concepts. The string in triple quotes at the
|
||||||
|
start of the function is a :dfn:`doc string`. This is standard Python practice
|
||||||
|
for structured documentation of code.
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: decorator; always_comb
|
||||||
|
single: wait; for a signal value change
|
||||||
|
single: combinatorial logic
|
||||||
|
|
||||||
|
Furthermore, we introduce a third decorator: :func:`always_comb`. It is used
|
||||||
|
with a classic function and specifies that the resulting generator should wait
|
||||||
|
for a value change on any input signal. This is typically used to describe
|
||||||
|
combinatorial logic. The :func:`always_comb` decorator automatically infers
|
||||||
|
which signals are used as inputs.
|
||||||
|
|
||||||
|
.. %
|
||||||
|
.. %
|
||||||
|
|
||||||
|
Finally, the code contains bit indexing operations and an exclusive-or operator
|
||||||
|
as required for a Gray encoder. By convention, the lsb of an :class:`intbv`
|
||||||
|
object has index ``0``.
|
||||||
|
|
||||||
|
To verify the Gray encoder, we write a test bench that prints input and output
|
||||||
|
for all possible input values::
|
||||||
|
|
||||||
|
def testBench(width):
|
||||||
|
|
||||||
|
B = Signal(intbv(0))
|
||||||
|
G = Signal(intbv(0))
|
||||||
|
|
||||||
|
dut = bin2gray(B, G, width)
|
||||||
|
|
||||||
|
@instance
|
||||||
|
def stimulus():
|
||||||
|
for i in range(2**width):
|
||||||
|
B.next = intbv(i)
|
||||||
|
yield delay(10)
|
||||||
|
print "B: " + bin(B, width) + "| G: " + bin(G, width)
|
||||||
|
|
||||||
|
return dut, stimulus
|
||||||
|
|
||||||
|
We use the conversion function ``bin`` to get a binary string representation of
|
||||||
|
the signal values. This function is exported by the ``myhdl`` package and
|
||||||
|
supplements the standard Python ``hex`` and ``oct`` conversion functions.
|
||||||
|
|
||||||
|
As a demonstration, we set up a simulation for a small width::
|
||||||
|
|
||||||
|
sim = Simulation(testBench(width=3))
|
||||||
|
sim.run()
|
||||||
|
|
||||||
|
The simulation produces the following output::
|
||||||
|
|
||||||
|
% python bin2gray.py
|
||||||
|
B: 000 | G: 000
|
||||||
|
B: 001 | G: 001
|
||||||
|
B: 010 | G: 011
|
||||||
|
B: 011 | G: 010
|
||||||
|
B: 100 | G: 110
|
||||||
|
B: 101 | G: 111
|
||||||
|
B: 110 | G: 101
|
||||||
|
B: 111 | G: 100
|
||||||
|
StopSimulation: No more events
|
||||||
|
|
||||||
|
|
||||||
|
.. _intro-slicing:
|
||||||
|
|
||||||
|
Bit slicing
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. index:: single: bit slicing
|
||||||
|
|
||||||
|
For a change, we will use a traditional function as an example to illustrate
|
||||||
|
slicing. The following function calculates the HEC byte of an ATM header. ::
|
||||||
|
|
||||||
|
from myhdl import intbv, concat
|
||||||
|
|
||||||
|
COSET = 0x55
|
||||||
|
|
||||||
|
def calculateHec(header):
|
||||||
|
""" Return hec for an ATM header, represented as an intbv.
|
||||||
|
|
||||||
|
The hec polynomial is 1 + x + x**2 + x**8.
|
||||||
|
"""
|
||||||
|
hec = intbv(0)
|
||||||
|
for bit in header[32:]:
|
||||||
|
hec[8:] = concat(hec[7:2],
|
||||||
|
bit ^ hec[1] ^ hec[7],
|
||||||
|
bit ^ hec[0] ^ hec[7],
|
||||||
|
bit ^ hec[7]
|
||||||
|
)
|
||||||
|
return hec ^ COSET
|
||||||
|
|
||||||
|
The code shows how slicing access and assignment is supported on the
|
||||||
|
:class:`intbv` data type. In accordance with the most common hardware
|
||||||
|
convention, and unlike standard Python, slicing ranges are downward. The code
|
||||||
|
also demonstrates concatenation of :class:`intbv` objects.
|
||||||
|
|
||||||
|
As in standard Python, the slicing range is half-open: the highest index bit is
|
||||||
|
not included. Unlike standard Python however, this index corresponds to the
|
||||||
|
*leftmost* item. Both indices can be omitted from the slice. If the leftmost
|
||||||
|
index is omitted, the meaning is to access "all" higher order bits. If the
|
||||||
|
rightmost index is omitted, it is ``0`` by default.
|
||||||
|
|
||||||
|
The half-openness of a slice may seem awkward at first, but it helps to avoid
|
||||||
|
one-off count issues in practice. For example, the slice ``hex[8:]`` has exactly
|
||||||
|
``8`` bits. Likewise, the slice ``hex[7:2]`` has ``7-2=5`` bits. You can think
|
||||||
|
about it as follows: for a slice ``[i:j]``, only bits below index ``i`` are
|
||||||
|
included, and the bit with index ``j`` is the last bit included.
|
||||||
|
|
||||||
|
When an intbv object is sliced, a new intbv object is returned. This new intbv
|
||||||
|
object is always positive, even when the original object was negative.
|
||||||
|
|
||||||
|
|
||||||
|
.. _intro-python:
|
||||||
|
|
||||||
|
Some remarks on MyHDL and Python
|
||||||
|
================================
|
||||||
|
|
||||||
|
To conclude this introductory chapter, it is useful to stress that MyHDL is not
|
||||||
|
a language in itself. The underlying language is Python, and MyHDL is
|
||||||
|
implemented as a Python package called ``myhdl``. Moreover, it is a design goal
|
||||||
|
to keep the ``myhdl`` package as minimalistic as possible, so that MyHDL
|
||||||
|
descriptions are very much "pure Python".
|
||||||
|
|
||||||
|
To have Python as the underlying language is significant in several ways:
|
||||||
|
|
||||||
|
* Python is a very powerful high level language. This translates into high
|
||||||
|
productivity and elegant solutions to complex problems.
|
||||||
|
|
||||||
|
* Python is continuously improved by some very clever minds, supported by a
|
||||||
|
large and fast growing user base. Python profits fully from the open source
|
||||||
|
development model.
|
||||||
|
|
||||||
|
* Python comes with an extensive standard library. Some functionality is likely
|
||||||
|
to be of direct interest to MyHDL users: examples include string handling,
|
||||||
|
regular expressions, random number generation, unit test support, operating
|
||||||
|
system interfacing and GUI development. In addition, there are modules for
|
||||||
|
mathematics, database connections, networking programming, internet data
|
||||||
|
handling, and so on.
|
||||||
|
|
||||||
|
* Python has a powerful C extension model. All built-in types are written with
|
||||||
|
the same C API that is available for custom extensions. To a module user, there
|
||||||
|
is no difference between a standard Python module and a C extension module ---
|
||||||
|
except performance. The typical Python development model is to prototype
|
||||||
|
everything in Python until the application is stable, and (only) then rewrite
|
||||||
|
performance critical modules in C if necessary.
|
||||||
|
|
||||||
|
|
||||||
|
.. _intro-summary:
|
||||||
|
|
||||||
|
Summary and perspective
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Here is an overview of what we have learned in this chapter:
|
||||||
|
|
||||||
|
* Generators are the basic building blocks of MyHDL models. They provide the way
|
||||||
|
to model massive concurrency and sensitiviy lists.
|
||||||
|
|
||||||
|
* MyHDL provides decorators that create useful generators from local functions.
|
||||||
|
|
||||||
|
* Hardware structure and hierarchy is described with classic Python functions.
|
||||||
|
|
||||||
|
* ``Signal`` objects are used to communicate between concurrent generators.
|
||||||
|
|
||||||
|
* ``intbv`` objects are used to describe bit-oriented operations.
|
||||||
|
|
||||||
|
* A ``Simulation`` object is used to simulate MyHDL models.
|
||||||
|
|
||||||
|
These concepts are sufficient to start describing and simulating MyHDL models.
|
||||||
|
|
||||||
|
However, there is much more to MyHDL. Here is an overview of what can be learned
|
||||||
|
from the following chapters:
|
||||||
|
|
||||||
|
* MyHDL supports sophisticated and high level modeling techniques. This is
|
||||||
|
described in Chapter :ref:`model`
|
||||||
|
|
||||||
|
* MyHDL enables the use of modern software verfication techniques, such as unit
|
||||||
|
testing, on hardware designs. This is the topic of Chapter :ref:`unittest`.
|
||||||
|
|
||||||
|
* It is possible to co-simulate MyHDL models with other HDL languages such as
|
||||||
|
Verilog and VHDL. This is described in Chapter :ref:`cosim`.
|
||||||
|
|
||||||
|
* Last but not least, MyHDL models can be converted to Verilog, providing a path
|
||||||
|
to a silicon implementation. This is the topic of Chapter :ref:`conv`.
|
||||||
|
|
||||||
|
.. rubric:: Footnotes
|
||||||
|
|
||||||
|
.. [#] The exception is the ``from module import *`` syntax, that imports all the
|
||||||
|
symbols from a module. Although this is generally considered bad practice, it
|
||||||
|
can be tolerated for large modules that export a lot of symbols. One may argue
|
||||||
|
that ``myhdl`` falls into that category.
|
||||||
|
|
||||||
|
.. [#] All positional parameters have to go before any named parameter.
|
||||||
|
|
1154
doc/source/manual/modeling.rst
Normal file
1154
doc/source/manual/modeling.rst
Normal file
File diff suppressed because it is too large
Load Diff
590
doc/source/manual/reference.rst
Normal file
590
doc/source/manual/reference.rst
Normal file
@ -0,0 +1,590 @@
|
|||||||
|
|
||||||
|
.. _ref:
|
||||||
|
|
||||||
|
*********
|
||||||
|
Reference
|
||||||
|
*********
|
||||||
|
|
||||||
|
MyHDL is implemented as a Python package called ``myhdl``. This chapter
|
||||||
|
describes the objects that are exported by this package.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-sim:
|
||||||
|
|
||||||
|
Simulation
|
||||||
|
==========
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-simclass:
|
||||||
|
|
||||||
|
The :class:`Simulation` class
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: Simulation(arg [, arg ...])
|
||||||
|
|
||||||
|
Class to construct a new simulation. Each argument should be a MyHDL instance.
|
||||||
|
In MyHDL, an instance is recursively defined as being either a sequence of
|
||||||
|
instances, or a MyHDL generator, or a Cosimulation object. See section
|
||||||
|
:ref:`ref-gen` for the definition of MyHDL generators and their interaction with
|
||||||
|
a :class:`Simulation` object. See Section :ref:`ref-cosim` for the
|
||||||
|
:class:`Cosimulation` object. At most one :class:`Cosimulation` object can be
|
||||||
|
passed to a :class:`Simulation` constructor.
|
||||||
|
|
||||||
|
A :class:`Simulation` object has the following method:
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: Simulation.run([duration])
|
||||||
|
|
||||||
|
Run the simulation forever (by default) or for a specified duration.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-simsupport:
|
||||||
|
|
||||||
|
Simulation support functions
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: now()
|
||||||
|
|
||||||
|
Returns the current simulation time.
|
||||||
|
|
||||||
|
|
||||||
|
.. exception:: StopSimulation()
|
||||||
|
|
||||||
|
Base exception that is caught by the ``Simulation.run()`` method to stop a
|
||||||
|
simulation.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-trace:
|
||||||
|
|
||||||
|
Waveform tracing
|
||||||
|
----------------
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: traceSignals(func [, *args] [, **kwargs])
|
||||||
|
|
||||||
|
Enables signal tracing to a VCD file for waveform viewing. *func* is a function
|
||||||
|
that returns an instance. :func:`traceSignals` calls *func* under its control
|
||||||
|
and passes *\*args* and *\*\*kwargs* to the call. In this way, it finds the
|
||||||
|
hierarchy and the signals to be traced.
|
||||||
|
|
||||||
|
The return value is the same as would be returned by the call ``func(*args,
|
||||||
|
**kwargs)``. The top-level instance name and the basename of the VCD output
|
||||||
|
filename is ``func.func_name`` by default. If the VCD file exists already, it
|
||||||
|
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:
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: traceSignals.name
|
||||||
|
|
||||||
|
This attribute is used to overwrite the default top-level instance name and the
|
||||||
|
basename of the VCD output filename.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-model:
|
||||||
|
|
||||||
|
Modeling
|
||||||
|
========
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-sig:
|
||||||
|
|
||||||
|
The :class:`Signal` class
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: Signal([val=None] [, delay=0])
|
||||||
|
|
||||||
|
This class is used to construct a new signal and to initialize its value to
|
||||||
|
*val*. Optionally, a delay can be specified.
|
||||||
|
|
||||||
|
A :class:`Signal` object has the following attributes:
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: Signal.posedge
|
||||||
|
|
||||||
|
Attribute that represents the positive edge of a signal, to be used in
|
||||||
|
sensitivity lists.
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: Signal.negedge
|
||||||
|
|
||||||
|
Attribute that represents the negative edge of a signal, to be used in
|
||||||
|
sensitivity lists.
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: Signal.next
|
||||||
|
|
||||||
|
Read-write attribute that represents the next value of the signal.
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: Signal.val
|
||||||
|
|
||||||
|
Read-only attribute that represents the current value of the signal.
|
||||||
|
|
||||||
|
This attribute is always available to access the current value; however in many
|
||||||
|
practical case it will not be needed. Whenever there is no ambiguity, the Signal
|
||||||
|
object's current value is used implicitly. In particular, all Python's standard
|
||||||
|
numeric, bit-wise, logical and comparison operators are implemented on a Signal
|
||||||
|
object by delegating to its current value. The exception is augmented
|
||||||
|
assignment. These operators are not implemented as they would break the rule
|
||||||
|
that the current value should be a read-only attribute. In addition, when a
|
||||||
|
Signal object is assigned to the ``next`` attribute of another Signal object,
|
||||||
|
its current value is assigned instead.
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: Signal.min
|
||||||
|
|
||||||
|
Read-only attribute that is the minimum value (inclusive) of a numeric signal,
|
||||||
|
or *None* for no minimum.
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: Signal.max
|
||||||
|
|
||||||
|
Read-only attribute that is the maximum value (exclusive) of a numeric signal,
|
||||||
|
or *None* for no maximum.
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: Signal.driven
|
||||||
|
|
||||||
|
Writable attribute that can be used to indicate that the signal is supposed to
|
||||||
|
be driven from the MyHDL code, and how it should be declared in Verilog after
|
||||||
|
conversion. The allowed values are ``'reg'`` and ``'wire'``.
|
||||||
|
|
||||||
|
This attribute is useful when the Verilog converter cannot infer automatically
|
||||||
|
whether and how a signal is driven. This occurs when the signal is driven from
|
||||||
|
user-defined Verilog code.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-gen:
|
||||||
|
|
||||||
|
MyHDL generators and trigger objects
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. index:: single: sensitivity list
|
||||||
|
|
||||||
|
MyHDL generators are standard Python generators with specialized
|
||||||
|
:keyword:`yield` statements. In hardware description languages, the equivalent
|
||||||
|
statements are called *sensitivity lists*. The general format of
|
||||||
|
:keyword:`yield` statements in in MyHDL generators is:
|
||||||
|
|
||||||
|
.. %
|
||||||
|
|
||||||
|
When a generator executes a :keyword:`yield` statement, its execution is
|
||||||
|
suspended at that point. At the same time, each *clause* is a *trigger object*
|
||||||
|
which defines the condition upon which the generator should be resumed. However,
|
||||||
|
per invocation of a :keyword:`yield` statement, the generator resumes exactly
|
||||||
|
once, regardless of the number of clauses. This happens on the first trigger
|
||||||
|
that occurs.
|
||||||
|
|
||||||
|
In this section, the trigger objects and their functionality will be described.
|
||||||
|
|
||||||
|
Some MyHDL objects that are described elsewhere can directly be used as trigger
|
||||||
|
objects. In particular, a signal can be used as a trigger object. Whenever a
|
||||||
|
signal changes value, the generator resumes. Likewise, the objects referred to
|
||||||
|
by the signal attributes ``posedge`` and ``negedge`` are trigger objects. The
|
||||||
|
generator resumes on the occurrence of a positive or a negative edge on the
|
||||||
|
signal, respectively. An edge occurs when there is a change from false to true
|
||||||
|
(positive) or vice versa (negative). For the full description of the
|
||||||
|
:class:`Signal` class and its attributes, see section :ref:`ref-sig`.
|
||||||
|
|
||||||
|
Furthermore, MyHDL generators can be used as clauses in ``yield`` statements.
|
||||||
|
Such a generator is forked, and starts operating immediately, while the original
|
||||||
|
generator waits for it to complete. The original generator resumes when the
|
||||||
|
forked generator returns.
|
||||||
|
|
||||||
|
In addition, the following functions return trigger objects:
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: delay(t)
|
||||||
|
|
||||||
|
Return a trigger object that specifies that the generator should resume after a
|
||||||
|
delay *t*.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: join(arg [, arg ...])
|
||||||
|
|
||||||
|
Join a number of trigger objects together and return a joined trigger object.
|
||||||
|
The effect is that the joined trigger object will trigger when *all* of its
|
||||||
|
arguments have triggered.
|
||||||
|
|
||||||
|
Finally, as a special case, the Python ``None`` object can be present in a
|
||||||
|
``yield`` statement. It is the do-nothing trigger object. The generator
|
||||||
|
immediately resumes, as if no ``yield`` statement were present. This can be
|
||||||
|
useful if the ``yield`` statement also has generator clauses: those generators
|
||||||
|
are forked, while the original generator resumes immediately.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-deco:
|
||||||
|
|
||||||
|
Decorator functions
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.
|
||||||
|
|
||||||
|
MyHDL defines a number of decorator functions, that make it easier to create
|
||||||
|
generators from local generator functions.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: instance()
|
||||||
|
|
||||||
|
The :func:`instance` decorator is the most general decorator. It automatically
|
||||||
|
creates a generator by calling the decorated generator function.
|
||||||
|
|
||||||
|
It is used as follows::
|
||||||
|
|
||||||
|
def top(...):
|
||||||
|
...
|
||||||
|
@instance
|
||||||
|
def inst():
|
||||||
|
<generator body>
|
||||||
|
...
|
||||||
|
return inst, ...
|
||||||
|
|
||||||
|
This is equivalent to::
|
||||||
|
|
||||||
|
def top(...):
|
||||||
|
...
|
||||||
|
def _gen_func():
|
||||||
|
<generator body>
|
||||||
|
...
|
||||||
|
inst = _gen_func()
|
||||||
|
...
|
||||||
|
return inst, ...
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: always(arg [, *args])
|
||||||
|
|
||||||
|
The :func:`always` decorator is a specialized decorator that targets a widely
|
||||||
|
used coding pattern. It is used as follows::
|
||||||
|
|
||||||
|
def top(...):
|
||||||
|
...
|
||||||
|
@always(event1, event2, ...)
|
||||||
|
def inst()
|
||||||
|
<body>
|
||||||
|
...
|
||||||
|
return inst, ...
|
||||||
|
|
||||||
|
This is equivalent to the following::
|
||||||
|
|
||||||
|
def top(...):
|
||||||
|
...
|
||||||
|
def _func():
|
||||||
|
<body>
|
||||||
|
|
||||||
|
def _gen_func()
|
||||||
|
while True:
|
||||||
|
yield event1, event2, ...
|
||||||
|
_func()
|
||||||
|
...
|
||||||
|
inst = _gen_func()
|
||||||
|
...
|
||||||
|
return inst, ...
|
||||||
|
|
||||||
|
The argument list of the decorator corresponds to the sensitivity list. Only
|
||||||
|
signals, edge specifiers, or delay objects are allowed. The decorated function
|
||||||
|
should be a classic function.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: always_comb()
|
||||||
|
|
||||||
|
The :func:`always_comb` decorator is used to describe combinatorial logic. ::
|
||||||
|
|
||||||
|
def top(...):
|
||||||
|
...
|
||||||
|
@always_comb
|
||||||
|
def comb_inst():
|
||||||
|
<combinatorial body>
|
||||||
|
...
|
||||||
|
return comb_inst, ...
|
||||||
|
|
||||||
|
The :func:`always_comb` decorator infers the inputs of the combinatorial logic
|
||||||
|
and the corresponding sensitivity list automatically. The decorated function
|
||||||
|
should be a classic function.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-intbv:
|
||||||
|
|
||||||
|
The :class:`intbv` class
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: intbv([val=None] [, min=None] [, max=None])
|
||||||
|
|
||||||
|
This class represents :class:`int`\ -like objects with some additional features
|
||||||
|
that make it suitable for hardware design. The *val* argument can be an
|
||||||
|
:class:`int`, a :class:`long`, an :class:`intbv` or a bit string (a string with
|
||||||
|
only '0's or '1's). For a bit string argument, the value is calculated as in
|
||||||
|
``int(bitstring, 2)``. The optional *min* and *max* arguments can be used to
|
||||||
|
specify the minimum and maximum value of the :class:`intbv` object. As in
|
||||||
|
standard Python practice for ranges, the minimum value is inclusive and the
|
||||||
|
maximum value is exclusive.
|
||||||
|
|
||||||
|
The minimum and maximum values of an :class:`intbv` object are available as
|
||||||
|
attributes:
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: intbv.min
|
||||||
|
|
||||||
|
Read-only attribute that is the minimum value (inclusive) of an :class:`intbv`,
|
||||||
|
or *None* for no minimum.
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: intbv.max
|
||||||
|
|
||||||
|
Read-only attribute that is the maximum value (exclusive) of an :class:`intbv`,
|
||||||
|
or *None* for no maximum.
|
||||||
|
|
||||||
|
Unlike :class:`int` objects, :class:`intbv` objects are mutable; this is also
|
||||||
|
the reason for their existence. Mutability is needed to support assignment to
|
||||||
|
indexes and slices, as is common in hardware design. For the same reason,
|
||||||
|
:class:`intbv` is not a subclass from :class:`int`, even though :class:`int`
|
||||||
|
provides most of the desired functionality. (It is not possible to derive a
|
||||||
|
mutable subtype from an immutable base type.)
|
||||||
|
|
||||||
|
An :class:`intbv` object supports the same comparison, numeric, bitwise,
|
||||||
|
logical, and conversion operations as :class:`int` objects. See
|
||||||
|
http://www.python.org/doc/current/lib/typesnumeric.html for more information on
|
||||||
|
such operations. In all binary operations, :class:`intbv` objects can work
|
||||||
|
together with :class:`int` objects. For mixed-type numeric operations, the
|
||||||
|
result type is an :class:`int` or a :class:`long`. For mixed-type bitwise
|
||||||
|
operations, the result type is an :class:`intbv`.
|
||||||
|
|
||||||
|
In addition, :class:`intbv` objects support indexing and slicing operations:
|
||||||
|
|
||||||
|
+-----------------+---------------------------------+--------+
|
||||||
|
| Operation | Result | Notes |
|
||||||
|
+=================+=================================+========+
|
||||||
|
| ``bv[i]`` | item *i* of *bv* | \(1) |
|
||||||
|
+-----------------+---------------------------------+--------+
|
||||||
|
| ``bv[i] = x`` | item *i* of *bv* is replaced by | \(1) |
|
||||||
|
| | *x* | |
|
||||||
|
+-----------------+---------------------------------+--------+
|
||||||
|
| ``bv[i:j]`` | slice of *bv* from *i* downto | (2)(3) |
|
||||||
|
| | *j* | |
|
||||||
|
+-----------------+---------------------------------+--------+
|
||||||
|
| ``bv[i:j] = t`` | slice of *bv* from *i* downto | (2)(4) |
|
||||||
|
| | *j* is replaced by *t* | |
|
||||||
|
+-----------------+---------------------------------+--------+
|
||||||
|
|
||||||
|
(1)
|
||||||
|
Indexing follows the most common hardware design conventions: the lsb bit is the
|
||||||
|
rightmost bit, and it has index 0. This has the following desirable property: if
|
||||||
|
the :class:`intbv` value is decomposed as a sum of powers of 2, the bit with
|
||||||
|
index *i* corresponds to the term ``2**i``.
|
||||||
|
|
||||||
|
(2)
|
||||||
|
In contrast to standard Python sequencing conventions, slicing range are
|
||||||
|
downward. This is a consequence of the indexing convention, combined with the
|
||||||
|
common convention that the most significant digits of a number are the leftmost
|
||||||
|
ones. The Python convention of half-open ranges is followed: the bit with the
|
||||||
|
highest index is not included. However, it is the *leftmost* bit in this case.
|
||||||
|
As in standard Python, this takes care of one-off issues in many practical
|
||||||
|
cases: in particular, ``bv[i:]`` returns *i* bits; ``bv[i:j]`` has ``i-j`` bits.
|
||||||
|
When the low index *j* is omitted, it defaults to ``0``. When the high index *i*
|
||||||
|
is omitted, it means "all" higher order bits.
|
||||||
|
|
||||||
|
(3)
|
||||||
|
The object returned from a slicing access operation is always a positive
|
||||||
|
:class:`intbv`; higher order bits are implicitly assumed to be zero. The bit
|
||||||
|
width is implicitly stored in the return object, so that it can be used in
|
||||||
|
concatenations and as an iterator. In addition, for a bit width w, the *min* and
|
||||||
|
*max* attributes are implicitly set to ``0`` and ``2**w``, respectively.
|
||||||
|
|
||||||
|
(4)
|
||||||
|
When setting a slice to a value, it is checked whether the slice is wide enough.
|
||||||
|
|
||||||
|
In addition, an :class:`intbv` object supports the iterator protocol. This makes
|
||||||
|
it possible to iterate over all its bits, from the high index to index 0. This
|
||||||
|
is only possible for :class:`intbv` objects with a defined bit width.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-model-misc:
|
||||||
|
|
||||||
|
Miscellaneous modeling support functions
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: bin(num [, width])
|
||||||
|
|
||||||
|
Returns a bit string representation. If the optional *width* is provided, and if
|
||||||
|
it is larger than the width of the default representation, the bit string is
|
||||||
|
padded with the sign bit.
|
||||||
|
|
||||||
|
This function complements the standard Python conversion functions ``hex`` and
|
||||||
|
``oct``. A binary string representation is often useful in hardware design.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: concat(base [, arg ...])
|
||||||
|
|
||||||
|
Returns an :class:`intbv` object formed by concatenating the arguments.
|
||||||
|
|
||||||
|
The following argument types are supported: :class:`intbv` objects with a
|
||||||
|
defined bit width, :class:`bool` objects, signals of the previous objects, and
|
||||||
|
bit strings. All these objects have a defined bit width. The first argument
|
||||||
|
*base* is special as it doesn't need to have a defined bit width. In addition to
|
||||||
|
the previously mentioned objects, unsized :class:`intbv`, :class:`int` and
|
||||||
|
:class:`long` objects are supported, as well as signals of such objects.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: downrange(high [, low=0])
|
||||||
|
|
||||||
|
Generates a downward range list of integers.
|
||||||
|
|
||||||
|
This function is modeled after the standard ``range`` function, but works in the
|
||||||
|
downward direction. The returned interval is half-open, with the *high* index
|
||||||
|
not included. *low* is optional and defaults to zero. This function is
|
||||||
|
especially useful in conjunction with the :class:`intbv` class, that also works
|
||||||
|
with downward indexing.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: enum(arg [, arg ...] [, encoding='binary'])
|
||||||
|
|
||||||
|
Returns an enumeration type.
|
||||||
|
|
||||||
|
The arguments should be string literals that represent the desired names of the
|
||||||
|
enumeration type attributes. The returned type should be assigned to a type
|
||||||
|
name. For example::
|
||||||
|
|
||||||
|
t_EnumType = enum('ATTR_NAME_1', 'ATTR_NAME_2', ...)
|
||||||
|
|
||||||
|
The enumeration type identifiers are available as attributes of the type name,
|
||||||
|
for example: ``t_EnumType.ATTR_NAME_1``
|
||||||
|
|
||||||
|
The optional keyword argument *encoding* specifies the encoding scheme used in
|
||||||
|
Verilog output. The available encodings are ``'binary'``, ``'one_hot'``, and
|
||||||
|
``'one_cold'``.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: instances()
|
||||||
|
|
||||||
|
Looks up all MyHDL instances in the local name space and returns them in a list.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-cosim:
|
||||||
|
|
||||||
|
Co-simulation
|
||||||
|
=============
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-cosim-myhdl:
|
||||||
|
|
||||||
|
MyHDL
|
||||||
|
-----
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: Cosimulation(exe, **kwargs)
|
||||||
|
|
||||||
|
Class to construct a new Cosimulation object.
|
||||||
|
|
||||||
|
The *exe* argument is a command string to execute an HDL simulation. The
|
||||||
|
*kwargs* keyword arguments provide a named association between signals (regs &
|
||||||
|
nets) in the HDL simulator and signals in the MyHDL simulator. Each keyword
|
||||||
|
should be a name listed in a ``$to_myhdl`` or ``$from_myhdl`` call in the HDL
|
||||||
|
code. Each argument should be a :class:`Signal` declared in the MyHDL code.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-cosim-verilog:
|
||||||
|
|
||||||
|
Verilog
|
||||||
|
-------
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: $to_myhdl(arg, [, arg ...])
|
||||||
|
|
||||||
|
Task that defines which signals (regs & nets) should be read by the MyHDL
|
||||||
|
simulator. This task should be called at the start of the simulation.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: $from_myhdl(arg, [, arg ...])
|
||||||
|
|
||||||
|
Task that defines which signals should be driven by the MyHDL simulator. In
|
||||||
|
Verilog, only regs can be specified. This task should be called at the start of
|
||||||
|
the simulation.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-cosim-vhdl:
|
||||||
|
|
||||||
|
VHDL
|
||||||
|
----
|
||||||
|
|
||||||
|
Not implemented yet.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-conv:
|
||||||
|
|
||||||
|
Conversion to Verilog
|
||||||
|
=====================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-conv-conv:
|
||||||
|
|
||||||
|
Conversion
|
||||||
|
----------
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: toVerilog(func [, *args] [, **kwargs])
|
||||||
|
|
||||||
|
Converts a MyHDL design instance to equivalent Verilog code, and also generates
|
||||||
|
a test bench to verify it. *func* is a function that returns an instance.
|
||||||
|
:func:`toVerilog` calls *func* under its control and passes *\*args* and
|
||||||
|
*\*\*kwargs* to the call.
|
||||||
|
|
||||||
|
The return value is the same as would be returned by the call ``func(*args,
|
||||||
|
**kwargs)``. It should be assigned to an instance name.
|
||||||
|
|
||||||
|
The top-level instance name and the basename of the Verilog output filename is
|
||||||
|
``func.func_name`` by default.
|
||||||
|
|
||||||
|
For more information about the restrictions on convertible MyHDL code, see
|
||||||
|
section :ref:`conv-subset` in Chapter :ref:`conv`.
|
||||||
|
|
||||||
|
The :func:`toVerilog` callable has the following attribute:
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: toVerilog.name
|
||||||
|
|
||||||
|
This attribute is used to overwrite the default top-level instance name and the
|
||||||
|
basename of the Verilog output filename.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ref-conv-user:
|
||||||
|
|
||||||
|
User-defined Verilog code
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
A user can insert user-defined code in the Verilog output by using the
|
||||||
|
``__verilog__`` hook.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: __verilog__
|
||||||
|
|
||||||
|
When defined within a function under elaboration, the ``__verilog__`` hook
|
||||||
|
variable specifies user-defined code that should be used instead of converted
|
||||||
|
code for that function. The user-defined code should be a Python format string
|
||||||
|
that uses keys to refer to the variables that should be interpolated in the
|
||||||
|
string. Any variable in the function context can be referred to.
|
||||||
|
|
||||||
|
Note that this hook cannot be used inside generator functions or decorated local
|
||||||
|
functions, as these are not elaborated.
|
||||||
|
|
357
doc/source/manual/unittest.rst
Normal file
357
doc/source/manual/unittest.rst
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
|
||||||
|
.. _unittest:
|
||||||
|
|
||||||
|
************
|
||||||
|
Unit testing
|
||||||
|
************
|
||||||
|
|
||||||
|
|
||||||
|
.. _unittest-intro:
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
Many aspects in the design flow of modern digital hardware design can be viewed
|
||||||
|
as a special kind of software development. From that viewpoint, it is a natural
|
||||||
|
question whether advances in software design techniques can not also be applied
|
||||||
|
to hardware design.
|
||||||
|
|
||||||
|
.. index:: single: extreme programming
|
||||||
|
|
||||||
|
One software design approach that gets a lot of attention recently is *Extreme
|
||||||
|
Programming* (XP). It is a fascinating set of techniques and guidelines that
|
||||||
|
often seems to go against the conventional wisdom. On other occasions, XP just
|
||||||
|
seems to emphasize the common sense, which doesn't always coincide with common
|
||||||
|
practice. For example, XP stresses the importance of normal workweeks, if we are
|
||||||
|
to have the fresh mind needed for good software development.
|
||||||
|
|
||||||
|
.. %
|
||||||
|
|
||||||
|
It is not my intention nor qualification to present a tutorial on Extreme
|
||||||
|
Programming. Instead, in this section I will highlight one XP concept which I
|
||||||
|
think is very relevant to hardware design: the importance and methodology of
|
||||||
|
unit testing.
|
||||||
|
|
||||||
|
|
||||||
|
.. _unittest-why:
|
||||||
|
|
||||||
|
The importance of unit tests
|
||||||
|
============================
|
||||||
|
|
||||||
|
Unit testing is one of the corner stones of Extreme Programming. Other XP
|
||||||
|
concepts, such as collective ownership of code and continuous refinement, are
|
||||||
|
only possible by having unit tests. Moreover, XP emphasizes that writing unit
|
||||||
|
tests should be automated, that they should test everything in every class, and
|
||||||
|
that they should run perfectly all the time.
|
||||||
|
|
||||||
|
I believe that these concepts apply directly to hardware design. In addition,
|
||||||
|
unit tests are a way to manage simulation time. For example, a state machine
|
||||||
|
that runs very slowly on infrequent events may be impossible to verify at the
|
||||||
|
system level, even on the fastest simulator. On the other hand, it may be easy
|
||||||
|
to verify it exhaustively in a unit test, even on the slowest simulator.
|
||||||
|
|
||||||
|
It is clear that unit tests have compelling advantages. On the other hand, if we
|
||||||
|
need to test everything, we have to write lots of unit tests. So it should be
|
||||||
|
easy and pleasant to create, manage and run them. Therefore, XP emphasizes the
|
||||||
|
need for a unit test framework that supports these tasks. In this chapter, we
|
||||||
|
will explore the use of the ``unittest`` module from the standard Python library
|
||||||
|
for creating unit tests for hardware designs.
|
||||||
|
|
||||||
|
|
||||||
|
.. _unittest-dev:
|
||||||
|
|
||||||
|
Unit test development
|
||||||
|
=====================
|
||||||
|
|
||||||
|
In this section, we will informally explore the application of unit test
|
||||||
|
techniques to hardware design. We will do so by a (small) example: testing a
|
||||||
|
binary to Gray encoder as introduced in section :ref:`intro-indexing`.
|
||||||
|
|
||||||
|
|
||||||
|
.. _unittest-req:
|
||||||
|
|
||||||
|
Defining the requirements
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
We start by defining the requirements. For a Gray encoder, we want to the output
|
||||||
|
to comply with Gray code characteristics. Let's define a :dfn:`code` as a list
|
||||||
|
of :dfn:`codewords`, where a codeword is a bit string. A code of order ``n`` has
|
||||||
|
``2**n`` codewords.
|
||||||
|
|
||||||
|
A well-known characteristic is the one that Gray codes are all about:
|
||||||
|
|
||||||
|
Consecutive codewords in a Gray code should differ in a single bit.
|
||||||
|
|
||||||
|
Is this sufficient? Not quite: suppose for example that an implementation
|
||||||
|
returns the lsb of each binary input. This would comply with the requirement,
|
||||||
|
but is obviously not what we want. Also, we don't want the bit width of Gray
|
||||||
|
codewords to exceed the bit width of the binary codewords.
|
||||||
|
|
||||||
|
Each codeword in a Gray code of order n must occur exactly once in the binary
|
||||||
|
code of the same order.
|
||||||
|
|
||||||
|
With the requirements written down we can proceed.
|
||||||
|
|
||||||
|
|
||||||
|
.. _unittest-first:
|
||||||
|
|
||||||
|
Writing the test first
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
A fascinating guideline in the XP world is to write the unit test first. That
|
||||||
|
is, before implementing something, first write the test that will verify it.
|
||||||
|
This seems to go against our natural inclination, and certainly against common
|
||||||
|
practices. Many engineers like to implement first and think about verification
|
||||||
|
afterwards.
|
||||||
|
|
||||||
|
But if you think about it, it makes a lot of sense to deal with verification
|
||||||
|
first. Verification is about the requirements only --- so your thoughts are not
|
||||||
|
yet cluttered with implementation details. The unit tests are an executable
|
||||||
|
description of the requirements, so they will be better understood and it will
|
||||||
|
be very clear what needs to be done. Consequently, the implementation should go
|
||||||
|
smoother. Perhaps most importantly, the test is available when you are done
|
||||||
|
implementing, and can be run anytime by anybody to verify changes.
|
||||||
|
|
||||||
|
Python has a standard ``unittest`` module that facilitates writing, managing and
|
||||||
|
running unit tests. With ``unittest``, a test case is written by creating a
|
||||||
|
class that inherits from ``unittest.TestCase``. Individual tests are created by
|
||||||
|
methods of that class: all method names that start with ``test`` are considered
|
||||||
|
to be tests of the test case.
|
||||||
|
|
||||||
|
We will define a test case for the Gray code properties, and then write a test
|
||||||
|
for each of the requirements. The outline of the test case class is as follows::
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
class TestGrayCodeProperties(TestCase):
|
||||||
|
|
||||||
|
def testSingleBitChange(self):
|
||||||
|
""" Check that only one bit changes in successive codewords """
|
||||||
|
....
|
||||||
|
|
||||||
|
|
||||||
|
def testUniqueCodeWords(self):
|
||||||
|
""" Check that all codewords occur exactly once """
|
||||||
|
....
|
||||||
|
|
||||||
|
Each method will be a small test bench that tests a single requirement. To write
|
||||||
|
the tests, we don't need an implementation of the Gray encoder, but we do need
|
||||||
|
the interface of the design. We can specify this by a dummy implementation, as
|
||||||
|
follows::
|
||||||
|
|
||||||
|
def bin2gray(B, G, width):
|
||||||
|
### NOT IMPLEMENTED YET! ###
|
||||||
|
yield None
|
||||||
|
|
||||||
|
For the first requirement, we will write a test bench that applies all
|
||||||
|
consecutive input numbers, and compares the current output with the previous one
|
||||||
|
for each input. Then we check that the difference is a single bit. We will test
|
||||||
|
all Gray codes up to a certain order ``MAX_WIDTH``. ::
|
||||||
|
|
||||||
|
def testSingleBitChange(self):
|
||||||
|
""" Check that only one bit changes in successive codewords """
|
||||||
|
|
||||||
|
def test(B, G, width):
|
||||||
|
B.next = intbv(0)
|
||||||
|
yield delay(10)
|
||||||
|
for i in range(1, 2**width):
|
||||||
|
G_Z.next = G
|
||||||
|
B.next = intbv(i)
|
||||||
|
yield delay(10)
|
||||||
|
diffcode = bin(G ^ G_Z)
|
||||||
|
self.assertEqual(diffcode.count('1'), 1)
|
||||||
|
|
||||||
|
for width in range(1, MAX_WIDTH):
|
||||||
|
B = Signal(intbv(-1))
|
||||||
|
G = Signal(intbv(0))
|
||||||
|
G_Z = Signal(intbv(0))
|
||||||
|
dut = bin2gray(B, G, width)
|
||||||
|
check = test(B, G, width)
|
||||||
|
sim = Simulation(dut, check)
|
||||||
|
sim.run(quiet=1)
|
||||||
|
|
||||||
|
Note how the actual check is performed by a ``self.assertEqual`` method, defined
|
||||||
|
by the ``unittest.TestCase`` class.
|
||||||
|
|
||||||
|
Similarly, we write a test bench for the second requirement. Again, we simulate
|
||||||
|
all numbers, and put the result in a list. The requirement implies that if we
|
||||||
|
sort the result list, we should get a range of numbers::
|
||||||
|
|
||||||
|
def testUniqueCodeWords(self):
|
||||||
|
""" Check that all codewords occur exactly once """
|
||||||
|
|
||||||
|
def test(B, G, width):
|
||||||
|
actual = []
|
||||||
|
for i in range(2**width):
|
||||||
|
B.next = intbv(i)
|
||||||
|
yield delay(10)
|
||||||
|
actual.append(int(G))
|
||||||
|
actual.sort()
|
||||||
|
expected = range(2**width)
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
for width in range(1, MAX_WIDTH):
|
||||||
|
B = Signal(intbv(-1))
|
||||||
|
G = Signal(intbv(0))
|
||||||
|
dut = bin2gray(B, G, width)
|
||||||
|
check = test(B, G, width)
|
||||||
|
sim = Simulation(dut, check)
|
||||||
|
sim.run(quiet=1)
|
||||||
|
|
||||||
|
|
||||||
|
.. _unittest-impl:
|
||||||
|
|
||||||
|
Test-driven implementation
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
With the test written, we begin with the implementation. For illustration
|
||||||
|
purposes, we will intentionally write some incorrect implementations to see how
|
||||||
|
the test behaves.
|
||||||
|
|
||||||
|
The easiest way to run tests defined with the ``unittest`` framework, is to put
|
||||||
|
a call to its ``main`` method at the end of the test module::
|
||||||
|
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
Let's run the test using the dummy Gray encoder shown earlier::
|
||||||
|
|
||||||
|
% python test_gray.py -v
|
||||||
|
Check that only one bit changes in successive codewords ... FAIL
|
||||||
|
Check that all codewords occur exactly once ... FAIL
|
||||||
|
<trace backs not shown>
|
||||||
|
|
||||||
|
As expected, this fails completely. Let us try an incorrect implementation, that
|
||||||
|
puts the lsb of in the input on the output::
|
||||||
|
|
||||||
|
def bin2gray(B, G, width):
|
||||||
|
### INCORRECT - DEMO PURPOSE ONLY! ###
|
||||||
|
|
||||||
|
@always_comb
|
||||||
|
def logic():
|
||||||
|
G.next = B[0]
|
||||||
|
|
||||||
|
return logic
|
||||||
|
|
||||||
|
Running the test produces::
|
||||||
|
|
||||||
|
% python test_gray.py -v
|
||||||
|
Check that only one bit changes in successive codewords ... ok
|
||||||
|
Check that all codewords occur exactly once ... FAIL
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
FAIL: Check that all codewords occur exactly once
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "test_gray.py", line 109, in testUniqueCodeWords
|
||||||
|
sim.run(quiet=1)
|
||||||
|
...
|
||||||
|
File "test_gray.py", line 104, in test
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
File "/usr/local/lib/python2.2/unittest.py", line 286, in failUnlessEqual
|
||||||
|
raise self.failureException, \
|
||||||
|
AssertionError: [0, 0, 1, 1] != [0, 1, 2, 3]
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Ran 2 tests in 0.785s
|
||||||
|
|
||||||
|
Now the test passes the first requirement, as expected, but fails the second
|
||||||
|
one. After the test feedback, a full traceback is shown that can help to debug
|
||||||
|
the test output.
|
||||||
|
|
||||||
|
Finally, if we use the correct implementation as in section
|
||||||
|
:ref:`intro-indexing`, the output is::
|
||||||
|
|
||||||
|
% python test_gray.py -v
|
||||||
|
Check that only one bit changes in successive codewords ... ok
|
||||||
|
Check that all codewords occur exactly once ... ok
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Ran 2 tests in 6.364s
|
||||||
|
|
||||||
|
OK
|
||||||
|
|
||||||
|
|
||||||
|
.. _unittest-change:
|
||||||
|
|
||||||
|
Changing requirements
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
In the previous section, we concentrated on the general requirements of a Gray
|
||||||
|
code. It is possible to specify these without specifying the actual code. It is
|
||||||
|
easy to see that there are several codes that satisfy these requirements. In
|
||||||
|
good XP style, we only tested the requirements and nothing more.
|
||||||
|
|
||||||
|
It may be that more control is needed. For example, the requirement may be for a
|
||||||
|
particular code, instead of compliance with general properties. As an
|
||||||
|
illustration, we will show how to test for *the* original Gray code, which is
|
||||||
|
one specific instance that satisfies the requirements of the previous section.
|
||||||
|
In this particular case, this test will actually be easier than the previous
|
||||||
|
one.
|
||||||
|
|
||||||
|
We denote the original Gray code of order ``n`` as ``Ln``. Some examples::
|
||||||
|
|
||||||
|
L1 = ['0', '1']
|
||||||
|
L2 = ['00', '01', '11', '10']
|
||||||
|
L3 = ['000', '001', '011', '010', '110', '111', '101', 100']
|
||||||
|
|
||||||
|
It is possible to specify these codes by a recursive algorithm, as follows:
|
||||||
|
|
||||||
|
#. L1 = ['0', '1']
|
||||||
|
|
||||||
|
#. Ln+1 can be obtained from Ln as follows. Create a new code Ln0 by prefixing
|
||||||
|
all codewords of Ln with '0'. Create another new code Ln1 by prefixing all
|
||||||
|
codewords of Ln with '1', and reversing their order. Ln+1 is the concatenation
|
||||||
|
of Ln0 and Ln1.
|
||||||
|
|
||||||
|
Python is well-known for its elegant algorithmic descriptions, and this is a
|
||||||
|
good example. We can write the algorithm in Python as follows::
|
||||||
|
|
||||||
|
def nextLn(Ln):
|
||||||
|
""" Return Gray code Ln+1, given Ln. """
|
||||||
|
Ln0 = ['0' + codeword for codeword in Ln]
|
||||||
|
Ln1 = ['1' + codeword for codeword in Ln]
|
||||||
|
Ln1.reverse()
|
||||||
|
return Ln0 + Ln1
|
||||||
|
|
||||||
|
The code ``['0' + codeword for ...]`` is called a :dfn:`list comprehension`. It
|
||||||
|
is a concise way to describe lists built by short computations in a for loop.
|
||||||
|
|
||||||
|
The requirement is now that the output code matches the expected code Ln. We use
|
||||||
|
the ``nextLn`` function to compute the expected result. The new test case code
|
||||||
|
is as follows::
|
||||||
|
|
||||||
|
class TestOriginalGrayCode(TestCase):
|
||||||
|
|
||||||
|
def testOriginalGrayCode(self):
|
||||||
|
""" Check that the code is an original Gray code """
|
||||||
|
|
||||||
|
Rn = []
|
||||||
|
|
||||||
|
def stimulus(B, G, n):
|
||||||
|
for i in range(2**n):
|
||||||
|
B.next = intbv(i)
|
||||||
|
yield delay(10)
|
||||||
|
Rn.append(bin(G, width=n))
|
||||||
|
|
||||||
|
Ln = ['0', '1'] # n == 1
|
||||||
|
for n in range(2, MAX_WIDTH):
|
||||||
|
Ln = nextLn(Ln)
|
||||||
|
del Rn[:]
|
||||||
|
B = Signal(intbv(-1))
|
||||||
|
G = Signal(intbv(0))
|
||||||
|
dut = bin2gray(B, G, n)
|
||||||
|
stim = stimulus(B, G, n)
|
||||||
|
sim = Simulation(dut, stim)
|
||||||
|
sim.run(quiet=1)
|
||||||
|
self.assertEqual(Ln, Rn)
|
||||||
|
|
||||||
|
As it happens, our implementation is apparently an original Gray code::
|
||||||
|
|
||||||
|
% python test_gray.py -v TestOriginalGrayCode
|
||||||
|
Check that the code is an original Gray code ... ok
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Ran 1 tests in 3.091s
|
||||||
|
|
||||||
|
OK
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user