From 0539f16aa2fe60cf6b8df804042fd6d41ba04480 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Apr 2020 07:21:31 +0000 Subject: [PATCH] Add (heavily work in progress) documentation. To render correctly, the docs require: * pygments/pygments#1441 --- .github/workflows/main.yaml | 28 + .gitignore | 4 +- docs/.gitignore | 1 + docs/_code/led_blinker.py | 24 + docs/_code/up_counter.py | 79 ++ docs/_code/up_counter.v | 49 + {doc => docs/_historical}/COMPAT_SUMMARY.md | 0 {doc => docs/_historical}/PROPOSAL.md | 0 docs/_images/up_counter_gtkwave.png | Bin 0 -> 33649 bytes docs/_static/custom.css | 5 + docs/conf.py | 27 + docs/index.rst | 24 + docs/install.rst | 121 +++ docs/intro.rst | 93 ++ docs/lang.rst | 969 ++++++++++++++++++++ docs/requirements.txt | 3 + docs/start.rst | 120 +++ nmigen/hdl/ast.py | 4 +- nmigen/hdl/dsl.py | 7 +- nmigen/test/test_hdl_ast.py | 18 +- nmigen/test/test_hdl_dsl.py | 12 +- 21 files changed, 1565 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/main.yaml create mode 100644 docs/.gitignore create mode 100644 docs/_code/led_blinker.py create mode 100644 docs/_code/up_counter.py create mode 100644 docs/_code/up_counter.v rename {doc => docs/_historical}/COMPAT_SUMMARY.md (100%) rename {doc => docs/_historical}/PROPOSAL.md (100%) create mode 100644 docs/_images/up_counter_gtkwave.png create mode 100644 docs/_static/custom.css create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/install.rst create mode 100644 docs/intro.rst create mode 100644 docs/lang.rst create mode 100644 docs/requirements.txt create mode 100644 docs/start.rst diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..69dfbeb --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,28 @@ +on: push +name: CI +jobs: + build: + if: github.event_name == 'push' && github.event.ref == 'refs/heads/doc' + runs-on: ubuntu-latest + steps: + - name: Check out source code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install dependencies + run: | + python -m pip install --upgrade setuptools setuptools_scm wheel + pip install -r docs/requirements.txt + pip install -e . + - name: Build documentation + run: | + sphinx-build docs docs/_build + - name: Publish documentation + uses: JamesIves/github-pages-deploy-action@releases/v3 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: gh-pages + FOLDER: docs/_build + TARGET_FOLDER: latest/ diff --git a/.gitignore b/.gitignore index 6c5c94a..7c2e9bc 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,6 @@ __pycache__/ *.gtkw # misc user-created -*.il -*.v +/*.il +/*.v /build diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..69fa449 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +_build/ diff --git a/docs/_code/led_blinker.py b/docs/_code/led_blinker.py new file mode 100644 index 0000000..8fef849 --- /dev/null +++ b/docs/_code/led_blinker.py @@ -0,0 +1,24 @@ +from nmigen import * + + +class LEDBlinker(Elaboratable): + def elaborate(self, platform): + m = Module() + + led = platform.request("led") + + half_freq = int(platform.default_clk_frequency // 2) + timer = Signal(range(half_freq + 1)) + + with m.If(timer == half_freq): + m.d.sync += led.eq(~led) + m.d.sync += timer.eq(0) + with m.Else(): + m.d.sync += timer.eq(timer + 1) + + return m +# --- BUILD --- +from nmigen_boards.icestick import * + + +ICEStickPlatform().build(LEDBlinker(), do_program=True) diff --git a/docs/_code/up_counter.py b/docs/_code/up_counter.py new file mode 100644 index 0000000..227b3c5 --- /dev/null +++ b/docs/_code/up_counter.py @@ -0,0 +1,79 @@ +from nmigen import * + + +class UpCounter(Elaboratable): + """ + A 16-bit up counter with a fixed limit. + + Parameters + ---------- + limit : int + The value at which the counter overflows. + + Attributes + ---------- + en : Signal, in + The counter is incremented if ``en`` is asserted, and retains + its value otherwise. + ovf : Signal, out + ``ovf`` is asserted when the counter reaches its limit. + """ + def __init__(self, limit): + self.limit = limit + + # Ports + self.en = Signal() + self.ovf = Signal() + + # State + self.count = Signal(16) + + def elaborate(self, platform): + m = Module() + + m.d.comb += self.ovf.eq(self.count == self.limit) + + with m.If(self.en): + with m.If(self.ovf): + m.d.sync += self.count.eq(0) + with m.Else(): + m.d.sync += self.count.eq(self.count + 1) + + return m +# --- TEST --- +from nmigen.back.pysim import Simulator + + +dut = UpCounter(25) +def bench(): + # Disabled counter should not overflow. + yield dut.en.eq(0) + for _ in range(30): + yield + assert not (yield dut.ovf) + + # Once enabled, the counter should overflow in 25 cycles. + yield dut.en.eq(1) + for _ in range(25): + yield + assert not (yield dut.ovf) + yield + assert (yield dut.ovf) + + # The overflow should clear in one cycle. + yield + assert not (yield dut.ovf) + + +sim = Simulator(dut) +sim.add_clock(1e-6) # 1 MHz +sim.add_sync_process(bench) +with sim.write_vcd("up_counter.vcd"): + sim.run() +# --- CONVERT --- +from nmigen.back import verilog + + +top = UpCounter(25) +with open("up_counter.v", "w") as f: + f.write(verilog.convert(top, ports=[top.en, top.ovf])) diff --git a/docs/_code/up_counter.v b/docs/_code/up_counter.v new file mode 100644 index 0000000..c8cde8e --- /dev/null +++ b/docs/_code/up_counter.v @@ -0,0 +1,49 @@ +(* generator = "nMigen" *) +module top(clk, rst, en, ovf); + (* src = "/nmigen/hdl/ir.py:526" *) + input clk; + (* src = "/nmigen/hdl/ir.py:526" *) + input rst; + (* src = "up_counter.py:26" *) + input en; + (* src = "up_counter.py:27" *) + output ovf; + (* src = "up_counter.py:30" *) + reg [15:0] count = 16'h0000; + (* src = "up_counter.py:30" *) + reg [15:0] \count$next ; + (* src = "up_counter.py:35" *) + wire \$1 ; + (* src = "up_counter.py:41" *) + wire [16:0] \$3 ; + (* src = "up_counter.py:41" *) + wire [16:0] \$4 ; + assign \$1 = count == (* src = "up_counter.py:35" *) 5'h19; + assign \$4 = count + (* src = "up_counter.py:41" *) 1'h1; + always @(posedge clk) + count <= \count$next ; + always @* begin + \count$next = count; + (* src = "up_counter.py:37" *) + casez (en) + /* src = "up_counter.py:37" */ + 1'h1: + (* src = "up_counter.py:38" *) + casez (ovf) + /* src = "up_counter.py:38" */ + 1'h1: + \count$next = 16'h0000; + /* src = "up_counter.py:40" */ + default: + \count$next = \$3 [15:0]; + endcase + endcase + (* src = "/nmigen/hdl/xfrm.py:518" *) + casez (rst) + 1'h1: + \count$next = 16'h0000; + endcase + end + assign \$3 = \$4 ; + assign ovf = \$1 ; +endmodule diff --git a/doc/COMPAT_SUMMARY.md b/docs/_historical/COMPAT_SUMMARY.md similarity index 100% rename from doc/COMPAT_SUMMARY.md rename to docs/_historical/COMPAT_SUMMARY.md diff --git a/doc/PROPOSAL.md b/docs/_historical/PROPOSAL.md similarity index 100% rename from doc/PROPOSAL.md rename to docs/_historical/PROPOSAL.md diff --git a/docs/_images/up_counter_gtkwave.png b/docs/_images/up_counter_gtkwave.png new file mode 100644 index 0000000000000000000000000000000000000000..3f166dadcf6e7f382671c0b27f9105153a590750 GIT binary patch literal 33649 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 UhJM?fTb@ng*QKtdiE2OpAL;7)J^%m! literal 0 HcmV?d00001 diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..43d1abf --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,5 @@ +/* Some of our section titles are looong */ +.wy-menu-vertical { width: 340px; } + +/* Many of our diagnostics are even longer */ +.rst-content pre.literal-block, .rst-content div[class^="highlight"] pre, .rst-content .linenodiv pre { white-space: pre-wrap; } diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..be03e7b --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,27 @@ +import os, sys +sys.path.insert(0, os.path.abspath(".")) + +import nmigen + +project = "nMigen core" +version = nmigen.__version__ +release = version.split("+")[0] +copyright = "2020, nMigen developers" + +extensions = [ + "sphinx.ext.intersphinx", + "sphinx.ext.doctest", + "sphinx.ext.todo", + "sphinx_rtd_theme", +] + +with open(".gitignore") as f: + exclude_patterns = [line.strip() for line in f.readlines()] + +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} + +todo_include_todos = True + +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] +html_css_files = ["custom.css"] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..badd1f0 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,24 @@ +The nMigen core manual +###################### + +.. warning:: + + This manual is a work in progress and is seriously incomplete! + +.. toctree:: + :maxdepth: 2 + + intro + install + start + lang + +Index +===== + +* :ref:`genindex` + +Search +====== + +* :ref:`search` diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..59895fb --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,121 @@ +Installation +############ + +System requirements +=================== + +nMigen requires Python 3.6; it works on CPython_ 3.6 (or newer), and works faster on PyPy3.6_ 7.2 (or newer). + +Simulating nMigen code requires no additional software. However, a waveform viewer like GTKWave_ is invaluable for debugging. + +Converting nMigen code to Verilog requires Yosys_ 0.9 (or newer). + +Synthesizing, placing and routing an nMigen design for an FPGA requires Yosys_ 0.9 (or newer), as well as the FPGA family specific toolchain. + +.. TODO: Link to FPGA family docs here + +.. _CPython: https://www.python.org/ +.. _PyPy3.6: https://www.pypy.org/ +.. _Yosys: http://www.clifford.at/yosys/ +.. _GTKWave: http://gtkwave.sourceforge.net/ + + +Installing prerequisites +======================== + +... on Windows +-------------- + +.. todo:: + + Determine what's appropriate here (do we put Python in PATH? what about Yosys? is there something better than GTKWave? do we just give up and suggest WSL?) + + +... on Debian Linux +------------------- + +nMigen works on Debian 10 or newer. The required version of Yosys is available in the main repository since Debian 11, but requires the Backports_ repository on Debian 10. Run: + +.. note: debian 10 provides: python3 3.7.3, yosys 0.8 (yosys 0.9 in backports) +.. note: debian 11 provides: python3 3.8.2, yosys 0.9 + +.. code-block:: shell + + $ sudo apt-get install python3-pip yosys gtkwave + +.. _Backports: https://wiki.debian.org/Backports + + +... on Ubuntu Linux +------------------- + +nMigen works on Ubuntu 20.04 LTS or newer. + +.. note: ubuntu 20.04 provides: python3 3.8.2, yosys 0.9 + +.. code-block:: shell + + $ sudo apt-get install python3-pip yosys gtkwave + + +... on macOS +------------ + +nMigen works best with Homebrew_. Run: + +.. code-block:: shell + + $ brew install python yosys gtkwave + +.. _Homebrew: https://brew.sh + + +... on other platforms +---------------------- + +Refer to the `Yosys README`_ for detailed build instructions. + +.. _Yosys README: https://github.com/YosysHQ/yosys/#setup + + +Installing nMigen +================= + +The latest release of nMigen should work well for most applications. A development snapshot---any commit from the ``master`` branch of nMigen---should be similarly reliable, but is likely to include experimental API changes that will be in flux until the next release. With that in mind, development snapshots can be used to try out new functionality or to avoid bugs fixed since the last release. + + +Latest release +-------------- + +To install the latest release of nMigen, run: + +.. code-block:: shell + + $ pip3 install --upgrade nmigen + + +Development snapshot +-------------------- + +To install a development snapshot of nMigen for the first time, run: + +.. code-block:: shell + + $ git clone https://gitlab.com/nmigen/nmigen + $ cd nmigen + $ pip3 install --editable . + +Any changes made to the ``nmigen`` directory will immediately affect any code that uses nMigen. To update the snapshot, run: + +.. code-block:: shell + + $ cd nmigen + $ git pull --ff-only origin master + + +Installing board definitions +============================= + +.. todo:: + + Explain how to install ``_. diff --git a/docs/intro.rst b/docs/intro.rst new file mode 100644 index 0000000..32414c7 --- /dev/null +++ b/docs/intro.rst @@ -0,0 +1,93 @@ +.. TODO: this introduction is written for people well familiar with HDLs; we likely need + another one for people who will use nMigen as their first HDL + +Introduction +############ + +The core nMigen project provides an open-source toolchain for developing hardware based on synchronous digital logic using the Python programming language. It aims to be easy to learn and use, reduce or eliminate common coding mistakes, and simplify the design of complex hardware with reusable components. + +The nMigen toolchain consists of the :ref:`nMigen language `, the :ref:`standard library `, the :ref:`simulator `, and the :ref:`build system `, covering all steps of a typical FPGA development workflow. At the same time, it does not restrict the designer's choice of tools: existing industry-standard (System)Verilog or VHDL code can be integrated into an nMigen design flow, or, conversely, nMigen code can be integrated into an existing Verilog-based design flow. + +.. TODO: add links to connect_rpc docs once they exist + + +.. _intro-lang: + +The nMigen language +=================== + +The :doc:`nMigen hardware description language ` is a Python library for register transfer level modeling of synchronous logic. Ordinary Python code is used to construct a netlist of a digital circuit, which can be simulated, directly synthesized via Yosys_, or converted to human-readable Verilog code for use with industry-standard toolchains. + +By relying on the flexibility, rich functionality and widespread adoption of the Python language, the nMigen language is focused on a single task: modeling digital logic well. It has first-class support for building blocks like clock domains and finite state machines, and uses simple rules for arithmetic operations that closely match the Python semantics. Python classes, functions, loops and conditionals can be used to build organized and flexible designs; Python libraries can be seamlessly used with nMigen during design or verification; and Python development tools can process nMigen code. + +A core design principle of the nMigen language is to be not only easy to use, but also hard to accidentally misuse. Some HDLs provide functionality that has unexpected and undesirable behavior in synthesis, often with expensive consequences, and require a significant effort in learning a "safe" coding style and adopting third-party linting tools. nMigen lacks non-synthesizable constructs and avoids error-prone inference in favor of explicit instantiation. It has many diagnostics (and regularly adds new ones) highlighting potential design issues. Most importantly, all usability issues are considered `reportable bugs`_. + +.. _Yosys: http://www.clifford.at/yosys/ +.. _reportable bugs: https://gitlab.com/nmigen/nmigen/issues + + +.. _intro-stdlib: + +The nMigen standard library +=========================== + +The nMigen language comes with a standard library---a collection of essential digital design components and interfaces. It includes clock domain crossing primitives, synchronous and asynchronous FIFOs, a flexible I/O buffer interface, and more. By providing reliable building blocks out of the box, nMigen allows the designer to focus on their application and avoids subtle differences in behavior between different designs. + +.. TODO: link to stdlib here + +Clock domain crossing often requires special treatment, such as using vendor-defined attributes or instantiating device-specific primitives. The CDC primitives in the nMigen standard library can be overridden by the platform integration, and every platform integration included with nMigen follows the vendor recommendations for CDC. + +High-speed designs usually require the use of registered (and sometimes, geared) I/O buffers. The nMigen standard library provides a common interface to be used between I/O buffers and peripheral implementations. The nMigen build system, if used, can instantiate I/O buffers for every platform integration included with nMigen. + +While many designs will use at least some vendor-specific functionality, the components provided by the nMigen standard library reduce the amount of code that needs to be changed when migrating between FPGA families, and the common interfaces simplify peripherals, test benches and simulations. + +The nMigen standard library is optional: the nMigen language can be used without it. Conversely, it is possible to use the nMigen standard library components in Verilog or VHDL code, with some limitations. + +.. TODO: link to connect_rpc docs here *again* + + +.. _intro-sim: + +The nMigen simulator +==================== + +The nMigen project includes an advanced simulator for nMigen code implemented in Python with no system dependencies; in this simulator, test benches are written as Python generator functions. Of course, it is always possible to convert an nMigen design to Verilog for use with well-known tool like `Icarus Verilog`_ or Verilator_. + +The nMigen simulator is event-driven and can simulate designs with multiple clocks or asynchronous resets. Although it is slower than `Icarus Verilog`_, it compiles the netlist to Python code ahead of time, achieving remarkably high performance for a pure Python implementation---especially when running on PyPy_. + +Although nMigen does not support native code simulation or co-simulation at the moment, such support will be added in near future. + +.. _Icarus Verilog: http://iverilog.icarus.com/ +.. _Verilator: https://www.veripool.org/wiki/verilator +.. _GTKWave: http://gtkwave.sourceforge.net/ +.. _PyPy: https://www.pypy.org/ + + +.. _intro-build: + +The nMigen build system +======================= + +To achieve an end-to-end FPGA development workflow, the nMigen project integrates with all major FPGA toolchains and provides definitions for many common development boards. + +.. TODO: link to vendor docs and board docs here + + +FPGA toolchain integration +-------------------------- + +Each FPGA family requires the use of synthesis and place & route tools specific for that device family. The nMigen build system directly integrates with every major open-source and commercial FPGA toolchain, and can be easily extended to cover others. + +Through this integration, nMigen can specialize the CDC primitives and I/O buffers for a particular device and toolchain; generate I/O and clock constraints from board definition files; synchronize the power-on reset in single-clock designs; include (System)Verilog and VHDL files in the design (if supported by the toolchain); and finally, generate a script running synthesis, placement, routing, and timing analysis. The generated code can be customized to insert additional options, commands, constraints, and so on. + +The nMigen build system produces self-contained, portable build trees that require only the toolchain to be present in the environment. This makes builds easier to reproduce, or to run on a remote machine. The generated build scripts are always provided for both \*nix and Windows. + + +Development board definitions +----------------------------- + +Getting started with a new FPGA development board often requires going through a laborous and error-prone process of deriving toolchain configuration and constraint files from the supplied documentation. The nMigen project includes a community-maintained repository of definitions for many open-source and commercial FPGA development boards. + +These board definitions contain everything that is necessary to start using the board: FPGA family and model, clocks and resets, descriptions of on-board peripherals (including pin direction and attributes such as I/O standard), connector pinouts, and for boards with a built-in debug probe, the steps required to program the board. It takes a single Python invocation to generate, build, and download a test design that shows whether the board, toolchain, and programmer are working correctly. + +nMigen establishes a pin naming convention for many common peripherals (such as 7-segment displays, SPI flashes and SDRAM memories), enabling the reuse of unmodified interface code with many different boards. Further, the polarity of all control signals is unified to be active high, eliminating accidental polarity inversions and making simulation traces easier to follow; active low signals are inverted during I/O buffer instantiation. diff --git a/docs/lang.rst b/docs/lang.rst new file mode 100644 index 0000000..e730c2a --- /dev/null +++ b/docs/lang.rst @@ -0,0 +1,969 @@ +Language guide +############## + +.. warning:: + + This guide is a work in progress and is seriously incomplete! + +This guide introduces the nMigen language in depth. It assumes familiarity with synchronous digital logic and the Python programming language, but does not require experience with any hardware description language. + +.. TODO: link to a good synchronous logic tutorial and a Python tutorial? + + +.. _lang-prelude: + +The prelude +=========== + +Because nMigen is a regular Python library, it needs to be imported before use. The root ``nmigen`` module, called *the prelude*, is carefully curated to export a small amount of the most essential names, useful in nearly every design. In source files dedicated to nMigen code, it is a good practice to use a :ref:`glob import ` for readability: + +.. code-block:: + + from nmigen import * + +However, if a source file uses nMigen together with other libraries, or if glob imports are frowned upon, it is conventional to use a short alias instead: + +.. code-block:: + + import nmigen as nm + +All of the examples below assume that a glob import is used. + +.. testsetup:: + + from nmigen import * + + +.. _lang-values: + +Values +====== + +The basic building block of the nMigen language is a *value*, which is a term for a binary number that is computed or stored anywhere in the design. Each value has a *width*---the amount of bits used to represent the value---and a *signedness*---the interpretation of the value by arithmetic operations---collectively called its *shape*. Signed values always use `two's complement`_ representation. + +.. _two's complement: https://en.wikipedia.org/wiki/Two's_complement + + +.. _lang-constants: + +Constants +========= + +The simplest nMigen value is a *constant*, representing a fixed number, and introduced using ``Const(...)`` or its short alias ``C(...)``: + +.. doctest:: + + >>> ten = Const(10) + >>> minus_two = C(-2) + +The code above does not specify any shape for the constants. If the shape is omitted, nMigen uses unsigned shape for positive numbers and signed shape for negative numbers, with the width inferred from the smallest amount of bits necessary to represent the number. As a special case, in order to get the same inferred shape for ``True`` and ``False``, ``0`` is considered to be 1-bit unsigned. + +.. doctest:: + + >>> ten.shape() + unsigned(4) + >>> minus_two.shape() + signed(2) + >>> C(0).shape() + unsigned(1) + +The shape of the constant can be specified explicitly, in which case the number's binary representation will be truncated or extended to fit the shape. Although rarely useful, 0-bit constants are permitted. + +.. doctest:: + + >>> Const(360, unsigned(8)).value + 104 + >>> Const(129, signed(8)).value + -127 + >>> Const(1, unsigned(0)).value + 0 + + +.. _lang-shapes: + +Shapes +====== + +A ``Shape`` is an object with two attributes, ``.width`` and ``.signed``. It can be constructed directly: + +.. doctest:: + + >>> Shape(width=5, signed=False) + unsigned(5) + >>> Shape(width=12, signed=True) + signed(12) + +However, in most cases, the shape is always constructed with the same signedness, and the aliases ``signed`` and ``unsigned`` are more convenient: + +.. doctest:: + + >>> unsigned(5) == Shape(width=5, signed=False) + True + >>> signed(12) == Shape(width=12, signed=True) + True + + +Shapes of values +---------------- + +All values have a ``.shape()`` method that computes their shape. The width of a value ``v``, ``v.shape().width``, can also be retrieved with ``len(v)``. + +.. doctest:: + + >>> Const(5).shape() + unsigned(3) + >>> len(Const(5)) + 3 + + +.. _lang-shapecasting: + +Shape casting +============= + +Shapes can be *cast* from other objects, which are called *shape-castable*. Casting is a convenient way to specify a shape indirectly, for example, by a range of numbers representable by values with that shape. + +Casting to a shape can be done explicitly with ``Shape.cast``, but is usually implicit, since shape-castable objects are accepted anywhere shapes are. + + +Shapes from integers +-------------------- + +Casting a shape from an integer ``i`` is a shorthand for constructing a shape with ``unsigned(i)``: + +.. doctest:: + + >>> Shape.cast(5) + unsigned(5) + >>> C(0, 3).shape() + unsigned(3) + + +Shapes from ranges +------------------ + +Casting a shape from a :py:class:`range` ``r`` produces a shape that: + + * has a width large enough to represent both ``min(r)`` and ``max(r)``, and + * is signed if either ``min(r)`` or ``max(r)`` are negative, unsigned otherwise. + +Specifying a shape with a range is convenient for counters, indexes, and all other values whose width is derived from a set of numbers they must be able to fit: + +.. doctest:: + + >>> Const(0, range(100)).shape() + unsigned(7) + >>> items = [1, 2, 3] + >>> C(1, range(len(items))).shape() + unsigned(2) + +.. _lang-exclrange: + +.. warning:: + + Python ranges are *exclusive* or *half-open*, meaning they do not contain their ``.stop`` element. Because of this, values with shapes cast from a ``range(stop)`` where ``stop`` is a power of 2 are not wide enough to represent ``stop`` itself: + + .. doctest:: + + >>> fencepost = C(256, range(256)) + >>> fencepost.shape() + unsigned(8) + >>> fencepost.value + 0 + + Be mindful of this edge case! + + +Shapes from enumerations +------------------------ + +Casting a shape from an :py:class:`enum.Enum` subclass ``E``: + + * fails if any of the enumeration members have non-integer values, + * has a width large enough to represent both ``min(m.value for m in E)`` and ``max(m.value for m in E)``, and + * is signed if either ``min(m.value for m in E)`` or ``max(m.value for m in E)`` are negative, unsigned otherwise. + +Specifying a shape with an enumeration is convenient for finite state machines, multiplexers, complex control signals, and all other values whose width is derived from a few distinct choices they must be able to fit: + +.. testsetup:: + + import enum + +.. testcode:: + + class Direction(enum.Enum): + TOP = 0 + LEFT = 1 + BOTTOM = 2 + RIGHT = 3 + +.. doctest:: + + >>> Shape.cast(Direction) + unsigned(2) + +.. note:: + + The enumeration does not have to subclass :py:class:`enum.IntEnum`; it only needs to have integers as values of every member. Using enumerations based on :py:class:`enum.Enum` rather than :py:class:`enum.IntEnum` prevents unwanted implicit conversion of enum members to integers. + + +.. _lang-valuecasting: + +Value casting +============= + +Like shapes, values may be *cast* from other objects, which are called *value-castable*. Casting allows objects that are not provided by nMigen, such as integers or enumeration members, to be used in nMigen expressions directly. + +.. TODO: link to UserValue + +Casting to a value can be done explicitly with ``Value.cast``, but is usually implicit, since value-castable objects are accepted anywhere values are. + + +Values from integers +-------------------- + +Casting a value from an integer ``i`` is a shorthand for ``Const(i)``: + +.. doctest:: + + >>> Value.cast(5) + (const 3'd5) + + +Values from enumeration members +------------------------------- + +Casting a value from an enumeration member ``m`` is a shorthand for ``Const(m.value, type(m))``: + +.. doctest:: + + >>> Value.cast(Direction.LEFT) + (const 2'd1) + + +.. _lang-signals: + +Signals +======= + +.. |emph:assigned| replace:: *assigned* +.. _emph:assigned: #lang-assigns + +A *signal* is a value representing a (potentially) varying number. Signals can be |emph:assigned|_ in a :ref:`combinatorial ` or :ref:`synchronous ` domain, in which case they are generated as wires or registers, respectively. Signals always have a well-defined value; they cannot be uninitialized or undefined. + + +Signal shapes +------------- + +A signal can be created with an explicitly specified shape (any :ref:`shape-castable ` object); if omitted, the shape defaults to ``unsigned(1)``. Although rarely useful, 0-bit signals are permitted. + +.. doctest:: + + >>> Signal().shape() + unsigned(1) + >>> Signal(4).shape() + unsigned(4) + >>> Signal(range(-8, 7)).shape() + signed(4) + >>> Signal(Direction).shape() + unsigned(2) + >>> Signal(0).shape() + unsigned(0) + + +.. _lang-signalname: + +Signal names +------------ + +Each signal has a *name*, which is used in the waveform viewer, diagnostic messages, Verilog output, and so on. In most cases, the name is omitted and inferred from the name of the variable or attribute the signal is placed into: + +.. testsetup:: + + class dummy(object): pass + self = dummy() + +.. doctest:: + + >>> foo = Signal() + >>> foo.name + 'foo' + >>> self.bar = Signal() + >>> self.bar.name + 'bar' + +However, the name can also be specified explicitly with the ``name=`` parameter: + +.. doctest:: + + >>> foo2 = Signal(name="second_foo") + >>> foo2.name + 'second_foo' + +The names do not need to be unique; if two signals with the same name end up in the same namespace while preparing for simulation or synthesis, one of them will be renamed to remove the ambiguity. + + +.. _lang-initial: + +Initial signal values +--------------------- + +Each signal has an *initial value*, specified with the ``reset=`` parameter. If the initial value is not specified explicitly, zero is used by default. An initial value can be specified with an integer or an enumeration member. + +Signals :ref:`assigned ` in a :ref:`combinatorial ` domain assume their initial value when none of the assignments are :ref:`active `. Signals assigned in a :ref:`synchronous ` domain assume their initial value after *power-on reset* and, unless the signal is :ref:`reset-less `, *explicit reset*. Signals that are used but never assigned are equivalent to constants of their initial value. + +.. TODO: using "reset" for "initial value" is awful, let's rename it to "init" + +.. doctest:: + + >>> Signal(4).reset + 0 + >>> Signal(4, reset=5).reset + 5 + >>> Signal(Direction, reset=Direction.LEFT).reset + 1 + + +.. _lang-resetless: + +Reset-less signals +------------------ + +Signals assigned in a :ref:`synchronous ` domain can be *resettable* or *reset-less*, specified with the ``reset_less=`` parameter. If the parameter is not specified, signals are resettable by default. Resettable signals assume their :ref:`initial value ` on explicit reset, which can be asserted via the clock domain or by using ``ResetInserter``. Reset-less signals are not affected by explicit reset. + +.. TODO: link to clock domain and ResetInserter docs + +Signals assigned in a :ref:`combinatorial ` domain are not affected by the ``reset_less`` parameter. + +.. doctest:: + + >>> Signal().reset_less + False + >>> Signal(reset_less=True).reset_less + True + + +.. _lang-operators: + +Operators +========= + +To describe computations, nMigen values can be combined with each other or with :ref:`value-castable ` objects using a rich array of arithmetic, bitwise, logical, bit sequence, and other *operators* to form *expressions*, which are themselves values. + + +.. _lang-abstractexpr: + +Performing or describing computations? +-------------------------------------- + +Code written in the Python language *performs* computations on concrete objects, like integers, with the goal of calculating a concrete result: + +.. doctest:: + + >>> a = 5 + >>> a + 1 + 6 + +In contrast, code written in the nMigen language *describes* computations on abstract objects, like :ref:`signals `, with the goal of generating a hardware *circuit* that can be simulated, synthesized, and so on. nMigen expressions are ordinary Python objects that represent parts of this circuit: + +.. doctest:: + + >>> a = Signal(8, reset=5) + >>> a + 1 + (+ (sig a) (const 1'd1)) + +Although the syntax is similar, it is important to remember that nMigen values exist on a higher level of abstraction than Python values. For example, expressions that include nMigen values cannot be used in Python control flow structures: + +.. doctest:: + + >>> if a == 0: + ... print("Zero!") + Traceback (most recent call last): + ... + TypeError: Attempted to convert nMigen value to Python boolean + +Because the value of ``a``, and therefore ``a == 0``, is not known at the time when the ``if`` statement is executed, there is no way to decide whether the body of the statement should be executed---in fact, if the design is synthesized, by the time ``a`` has any concrete value, the Python program has long finished! To solve this problem, nMigen provides its own :ref:`control structures ` that, also, manipulate circuits. + + +.. _lang-widthext: + +Width extension +--------------- + +Many of the operations described below (for example, addition, equality, bitwise OR, and part select) extend the width of one or both operands to match the width of the expression. When this happens, unsigned values are always zero-extended and signed values are always sign-extended regardless of the operation or signedness of the result. + + +.. _lang-arithops: + +Arithmetic operators +-------------------- + +Most arithmetic operations on integers provided by Python can be used on nMigen values, too. + +Although Python integers have unlimited precision and nMigen values are represented with a :ref:`finite amount of bits `, arithmetics on nMigen values never overflows because the width of the arithmetic expression is always sufficient to represent all possible results. + +.. doctest:: + + >>> a = Signal(8) + >>> (a + 1).shape() # needs to represent 1 to 256 + unsigned(9) + +Similarly, although Python integers are always signed and nMigen values can be either :ref:`signed or unsigned `, if any of the operands of an nMigen arithmetic expression is signed, the expression itself is also signed, matching the behavior of Python. + +.. doctest:: + + >>> a = Signal(unsigned(8)) + >>> b = Signal(signed(8)) + >>> (a + b).shape() # needs to represent -128 to 382 + signed(10) + +While arithmetic computations never result in an overflow, :ref:`assigning ` their results to signals may truncate the most significant bits. + +The following table lists the arithmetic operations provided by nMigen: + +============ ========================== ====== +Operation Description Notes +============ ========================== ====== +``a + b`` addition +``-a`` negation +``a - b`` subtraction +``a * b`` multiplication +``a // b`` floor division [#opA1]_ +``a % b`` modulo [#opA1]_ +``abs(a)`` absolute value +============ ========================== ====== + +.. [#opA1] Divisor must be unsigned; this is an nMigen limitation that may be lifted in the future. + + +.. _lang-cmpops: + +Comparison operators +-------------------- + +All comparison operations on integers provided by Python can be used on nMigen values. However, due to a limitation of Python, chained comparisons (e.g. ``a < b < c``) cannot be used. + +Similar to arithmetic operations, if any operand of a comparison expression is signed, a signed comparison is performed. The result of a comparison is a 1-bit unsigned value. + +The following table lists the comparison operations provided by nMigen: + +============ ========================== +Operation Description +============ ========================== +``a == b`` equality +``a != b`` inequality +``a < b`` less than +``a <= b`` less than or equal +``a > b`` greater than +``a >= b`` greater than or equal +============ ========================== + + +.. _lang-bitops: + +Bitwise, shift, and rotate operators +------------------------------------ + +All bitwise and shift operations on integers provided by Python can be used on nMigen values as well. + +Similar to arithmetic operations, if any operand of a bitwise expression is signed, the expression itself is signed as well. A shift expression is signed if the shifted value is signed. A rotate expression is always unsigned. + +Rotate operations with variable rotate amounts cannot be efficiently synthesized for non-power-of-2 widths of the rotated value. Because of that, the rotate operations are only provided for constant rotate amounts, specified as Python :py:class:`int`\ s. + +The following table lists the bitwise and shift operations provided by nMigen: + +===================== ========================================== ====== +Operation Description Notes +===================== ========================================== ====== +``~a`` bitwise NOT; complement +``a & b`` bitwise AND +``a | b`` bitwise OR +``a ^ b`` bitwise XOR +``a.implies(b)`` bitwise IMPLY_ +``a >> b`` arithmetic right shift by variable amount [#opB1]_, [#opB2]_ +``a << b`` left shift by variable amount [#opB2]_ +``a.rotate_left(i)`` left rotate by constant amount [#opB3]_ +``a.rotate_right(i)`` right rotate by constant amount [#opB3]_ +===================== ========================================== ====== + +.. _IMPLY: https://en.wikipedia.org/wiki/IMPLY_gate +.. [#opB1] Logical and arithmetic right shift of an unsigned value are equivalent. Logical right shift of a signed value can be expressed by :ref:`converting it to unsigned ` first. +.. [#opB2] Shift amount must be unsigned; integer shifts in Python require the amount to be positive. +.. [#opB3] Rotate amount can be negative, in which case the direction is reversed. +.. TODO: add constshifts https://gitlab.com/nmigen/nmigen/issues/378 + +.. _lang-hugeshift: + +.. note:: + + Because nMigen ensures that the width of a left shift expression is wide enough to represent any possible result, left shift by a wide amount produces exponentially wider intermediate values, stressing the synthesis tools: + + .. doctest:: + + >>> (1 << C(0, 32)).shape() + unsigned(4294967296) + + Although nMigen will detect and reject expressions wide enough to break other tools, it is a good practice to explicitly limit the width of a non-constant shift amount in a left shift. + + +.. _lang-reduceops: + +Reduction operators +------------------- + +Bitwise reduction operations on integers are not provided by Python, but are very useful for hardware. They are similar to bitwise operations applied "sideways"; for example, if bitwise AND is a binary operator that applies AND to each pair of bits between its two operands, then reduction AND is an unary operator that applies AND to all of the bits in its sole operand. + +The result of a reduction is a 1-bit unsigned value. + +The following table lists the reduction operations provided by nMigen: + +============ ============================================= ====== +Operation Description Notes +============ ============================================= ====== +``a.all()`` reduction AND; are all bits set? [#opR1]_ +``a.any()`` reduction OR; is any bit set? [#opR1]_ +``a.xor()`` reduction XOR; is an odd number of bits set? +``a.bool()`` conversion to boolean; is non-zero? [#opR2]_ +============ ============================================= ====== + +.. [#opR1] Conceptually the same as applying the Python :py:func:`all` or :py:func:`any` function to the value viewed as a collection of bits. +.. [#opR2] Conceptually the same as applying the Python :py:func:`bool` function to the value viewed as an integer. + + +.. _lang-logicops: + +Logical operators +----------------- + +Unlike the arithmetic or bitwise operators, it is not possible to change the behavior of the Python logical operators ``not``, ``and``, and ``or``. Due to that, logical expressions in nMigen are written using bitwise operations on boolean (1-bit unsigned) values, with explicit boolean conversions added where necessary. + +The following table lists the Python logical expressions and their nMigen equivalents: + +================= ==================================== +Python expression nMigen expression (any operands) +================= ==================================== +``not a`` ``~(a).bool()`` +``a and b`` ``(a).bool() & (b).bool()`` +``a or b`` ``(a).bool() | (b).bool()`` +================= ==================================== + +When the operands are known to be boolean values, such as comparisons, reductions, or boolean signals, the ``.bool()`` conversion may be omitted for clarity: + +================= ==================================== +Python expression nMigen expression (boolean operands) +================= ==================================== +``not p`` ``~(p)`` +``p and q`` ``(p) & (q)`` +``p or q`` ``(p) | (q)`` +================= ==================================== + +.. _lang-logicprecedence: + +.. warning:: + + Because of Python :ref:`operator precedence `, logical operators bind less tightly than comparison operators whereas bitwise operators bind more tightly than comparison operators. As a result, all logical expressions in nMigen **must** have parenthesized operands. + + Omitting parentheses around operands in an nMigen a logical expression is likely to introduce a subtle bug: + + .. doctest:: + + >>> en = Signal() + >>> addr = Signal(8) + >>> en & (addr == 0) # correct + (& (sig en) (== (sig addr) (const 1'd0))) + >>> en & addr == 0 # WRONG! addr is truncated to 1 bit + (== (& (sig en) (sig addr)) (const 1'd0)) + + .. TODO: can we detect this footgun automatically? #380 + +.. _lang-negatebool: + +.. warning:: + + When applied to nMigen boolean values, the ``~`` operator computes negation, and when applied to Python boolean values, the ``not`` operator also computes negation. However, the ``~`` operator applied to Python boolean values produces an unexpected result: + + .. doctest:: + + >>> ~False + -1 + >>> ~True + -2 + + Because of this, Python booleans used in nMigen logical expressions **must** be negated with the ``not`` operator, not the ``~`` operator. Negating a Python boolean with the ``~`` operator in an nMigen logical expression is likely to introduce a subtle bug: + + .. doctest:: + + >>> stb = Signal() + >>> use_stb = True + >>> (not use_stb) | stb # correct + (| (const 1'd0) (sig stb)) + >>> ~use_stb | stb # WRONG! MSB of 2-bit wide OR expression is always 1 + (| (const 2'sd-2) (sig stb)) + + nMigen automatically detects some cases of misuse of ``~`` and emits a detailed diagnostic message. + + .. TODO: this isn't quite reliable, #380 + + +.. _lang-seqops: + +Bit sequence operators +---------------------- + +Apart from acting as numbers, nMigen values can also be treated as bit :ref:`sequences `, supporting slicing, concatenation, replication, and other sequence operations. Since some of the operators Python defines for sequences clash with the operators it defines for numbers, nMigen gives these operators a different name. Except for the names, nMigen values follow Python sequence semantics, with the least significant bit at index 0. + +Because every nMigen value has a single fixed width, bit slicing and replication operations require the subscripts and count to be constant, specified as Python :py:class:`int`\ s. It is often useful to slice a value with a constant width and variable offset, but this cannot be expressed with the Python slice notation. To solve this problem, nMigen provides additional *part select* operations with the necessary semantics. + +The result of any bit sequence operation is an unsigned value. + +The following table lists the bit sequence operations provided by nMigen: + +======================= ================================================ ====== +Operation Description Notes +======================= ================================================ ====== +``len(a)`` bit length; value width [#opS1]_ +``a[i:j:k]`` bit slicing by constant subscripts [#opS2]_ +``iter(a)`` bit iteration +``a.bit_select(b, i)`` overlapping part select with variable offset +``a.word_select(b, i)`` non-overlapping part select with variable offset +``Cat(a, b)`` concatenation [#opS3]_ +``Repl(a, i)`` replication +======================= ================================================ ====== + +.. [#opS1] Words "length" and "width" have the same meaning when talking about nMigen values. Conventionally, "width" is used. +.. [#opS2] All variations of the Python slice notation are supported, including "extended slicing". E.g. all of ``a[0]``, ``a[1:9]``, ``a[2:]``, ``a[:-2]``, ``a[::-1]``, ``a[0:8:2]`` select bits in the same way as other Python sequence types select their elements. +.. [#opS3] In the concatenated value, ``a`` occupies the least significant bits, and ``b`` the most significant bits. + +For the operators introduced by nMigen, the following table explains them in terms of Python code operating on tuples of bits rather than nMigen values: + +======================= ====================== +nMigen operation Equivalent Python code +======================= ====================== +``Cat(a, b)`` ``a + b`` +``Repl(a, i)`` ``a * i`` +``a.bit_select(b, i)`` ``a[b:b+i]`` +``a.word_select(b, i)`` ``a[b*i:b*i+i]`` +======================= ====================== + +.. warning:: + + In Python, the digits of a number are written right-to-left (0th exponent at the right), and the elements of a sequence are written left-to-right (0th element at the left). This mismatch can cause confusion when numeric operations (like shifts) are mixed with bit sequence operations (like concatenations). For example, ``Cat(C(0b1001), C(0b1010))`` has the same value as ``C(0b1010_1001)``, ``val[4:]`` is equivalent to ``val >> 4``, and ``val[-1]`` refers to the most significant bit. + + Such confusion can often be avoided by not using numeric and bit sequence operations in the same expression. For example, although it may seem natural to describe a shift register with a numeric shift and a sequence slice operations, using sequence operations alone would make it easier to understand. + +.. note:: + + Could nMigen have used a different indexing or iteration order for values? Yes, but it would be necessary to either place the most significant bit at index 0, or deliberately break the Python sequence type interface. Both of these options would cause more issues than using different iteration orders for numeric and sequence operations. + + +.. _lang-convops: + +Conversion operators +-------------------- + +The ``.as_signed()`` and ``.as_unsigned()`` conversion operators reinterpret the bits of a value with the requested signedness. This is useful when the same value is sometimes treated as signed and sometimes as unsigned, or when a signed value is constructed using slices or concatenations. For example, ``(pc + imm[:7].as_signed()).as_unsigned()`` sign-extends the 7 least significant bits of ``imm`` to the width of ``pc``, performs the addition, and produces an unsigned result. + +.. TODO: more general shape conversion? https://gitlab.com/nmigen/nmigen/issues/381 + + +.. _lang-muxop: + +Choice operator +--------------- + +The ``Mux(sel, val1, val0)`` choice expression (similar to the :ref:`conditional expression ` in Python) is equal to the operand ``val1`` if ``sel`` is non-zero, and to the other operand ``val0`` otherwise. If any of ``val1`` or ``val0`` are signed, the expression itself is signed as well. + + +.. _lang-modules: + +Modules +======= + +A *module* is a unit of the nMigen design hierarchy: the smallest collection of logic that can be independently simulated, synthesized, or otherwise processed. Modules associate signals with :ref:`control domains `, provide :ref:`control structures `, manage clock domains, and aggregate submodules. + +.. TODO: link to clock domains +.. TODO: link to submodules + +Every nMigen design starts with a fresh module: + +.. doctest:: + + >>> m = Module() + + +.. _lang-domains: + +Control domains +--------------- + +A *control domain* is a named group of :ref:`signals ` that change their value in identical conditions. + +All designs have a single predefined *combinatorial domain*, containing all signals that change immediately when any value used to compute them changes. The name ``comb`` is reserved for the combinatorial domain. + +A design can also have any amount of user-defined *synchronous domains*, also called *clock domains*, containing signals that change when a specific edge occurs on the domain's clock signal or, for domains with asynchronous reset, on the domain's reset signal. Most modules only use a single synchronous domain, conventionally called ``sync``, but the name ``sync`` does not have to be used, and lacks any special meaning beyond being the default. + +The behavior of assignments differs for signals in :ref:`combinatorial ` and :ref:`synchronous ` domains. Collectively, signals in synchronous domains contain the state of a design, whereas signals in the combinatorial domain cannot form feedback loops or hold state. + +.. TODO: link to clock domains + + +.. _lang-assigns: + +Assigning to signals +-------------------- + +*Assignments* are used to change the values of signals. An assignment statement can be introduced with the ``.eq(...)`` syntax: + +.. doctest:: + + >>> s = Signal() + >>> s.eq(1) + (eq (sig s) (const 1'd1)) + +Similar to :ref:`how nMigen operators work `, an nMigen assignment is an ordinary Python object used to describe a part of a circuit. An assignment does not have any effect on the signal it changes until it is added to a control domain in a module. Once added, it introduces logic into the circuit generated from that module. + + +.. _lang-assignlhs: + +Assignment targets +------------------ + +The target of an assignment can be more complex than a single signal. It is possible to assign to any combination of signals, :ref:`bit slices `, :ref:`concatenations `, and :ref:`part selects ` as long as it includes no other values: + +.. TODO: mention arrays, records, user values + +.. doctest:: + + >>> a = Signal(8) + >>> b = Signal(4) + >>> Cat(a, b).eq(0) + (eq (cat (sig a) (sig b)) (const 1'd0)) + >>> a[:4].eq(b) + (eq (slice (sig a) 0:4) (sig b)) + >>> Cat(a, a).bit_select(b, 2).eq(0b11) + (eq (part (cat (sig a) (sig a)) (sig b) 2 1) (const 2'd3)) + + +.. _lang-assigndomains: + +Assignment domains +------------------ + +The ``m.d. += ...`` syntax is used to add assignments to a specific control domain in a module. It can add just a single assignment, or an entire sequence of them: + +.. testcode:: + + a = Signal() + b = Signal() + c = Signal() + m.d.comb += a.eq(1) + m.d.sync += [ + b.eq(c), + c.eq(b), + ] + +If the name of a domain is not known upfront, the ``m.d[""] += ...`` syntax can be used instead: + +.. testcode:: + + def add_toggle(num): + t = Signal() + m.d[f"sync_{num}"] += t.eq(~t) + add_toggle(2) + +.. _lang-signalgranularity: + +Every signal included in the target of an assignment becomes a part of the domain, or equivalently, *driven* by that domain. A signal can be either undriven or driven by exactly one domain; it is an error to add two assignments to the same signal to two different domains: + +.. doctest:: + + >>> d = Signal() + >>> m.d.comb += d.eq(1) + >>> m.d.sync += d.eq(0) + Traceback (most recent call last): + ... + nmigen.hdl.dsl.SyntaxError: Driver-driver conflict: trying to drive (sig d) from d.sync, but it is already driven from d.comb + +.. note:: + + Clearly, nMigen code that drives a single bit of a signal from two different domains does not describe a meaningful circuit. However, driving two different bits of a signal from two different domains does not inherently cause such a conflict. Would nMigen accept the following code? + + .. testcode:: + + e = Signal(2) + m.d.comb += e[0].eq(0) + m.d.sync += e[1].eq(1) + + The answer is no. While this kind of code is occasionally useful, rejecting it greatly simplifies backends, simulators, and analyzers. + + +.. _lang-assignorder: + +Assignment order +---------------- + +Unlike with two different domains, adding multiple assignments to the same signal to the same domain is well-defined. + +Assignments to different signal bits apply independently. For example, the following two snippets are equivalent: + +.. testcode:: + + a = Signal(8) + m.d.comb += [ + a[0:4].eq(C(1, 4)), + a[4:8].eq(C(2, 4)), + ] + +.. testcode:: + + a = Signal(8) + m.d.comb += a.eq(Cat(C(1, 4), C(2, 4))) + +If multiple assignments change the value of the same signal bits, the assignment that is added last determines the final value. For example, the following two snippets are equivalent: + +.. testcode:: + + b = Signal(9) + m.d.comb += [ + b[0:9].eq(Cat(C(1, 3), C(2, 3), C(3, 3))), + b[0:6].eq(Cat(C(4, 3), C(5, 3))), + b[3:6].eq(C(6, 3)), + ] + +.. testcode:: + + b = Signal(9) + m.d.comb += b.eq(Cat(C(4, 3), C(6, 3), C(3, 3))) + +Multiple assignments to the same signal bits are more useful when combined with control structures, which can make some of the assignments :ref:`active or inactive `. If all assignments to some signal bits are :ref:`inactive `, their final values are determined by the signal's domain, :ref:`combinatorial ` or :ref:`synchronous `. + + +.. _lang-control: + +Control structures +------------------ + +Although it is possible to write any decision tree as a combination of :ref:`assignments ` and :ref:`choice expressions `, nMigen provides *control structures* tailored for this task: If, Switch, and FSM. The syntax of all control structures is based on :ref:`context managers ` and uses ``with`` blocks, for example: + +.. TODO: link to relevant subsections + +.. testcode:: + + timer = Signal(8) + with m.If(timer == 0): + m.d.sync += timer.eq(10) + with m.Else(): + m.d.sync += timer.eq(timer - 1) + +While some nMigen control structures are superficially similar to imperative control flow statements (such as Python's ``if``), their function---together with :ref:`expressions ` and :ref:`assignments `---is to describe circuits. The code above is equivalent to: + +.. testcode:: + + timer = Signal(8) + m.d.sync += timer.eq(Mux(timer == 0, 10, timer - 1)) + +Because all branches of a decision tree affect the generated circuit, all of the Python code inside nMigen control structures is always evaluated in the order in which it appears in the program. This can be observed through Python code with side effects, such as ``print()``: + +.. testcode:: + + timer = Signal(8) + with m.If(timer == 0): + print("inside `If`") + m.d.sync += timer.eq(10) + with m.Else(): + print("inside `Else`") + m.d.sync += timer.eq(timer - 1) + +.. testoutput:: + + inside `If` + inside `Else` + + +.. _lang-active: + +Active and inactive assignments +------------------------------- + +An assignment added inside an nMigen control structure, i.e. ``with m.<...>:`` block, is *active* if the condition of the control structure is satisfied, and *inactive* otherwise. For any given set of conditions, the final value of every signal assigned in a module is the same as if the inactive assignments were removed and the active assignments were performed unconditionally, taking into account the :ref:`assignment order `. + +For example, there are two possible cases in the circuit generated from the following code: + +.. testcode:: + + timer = Signal(8) + m.d.sync += timer.eq(timer - 1) + with m.If(timer == 0): + m.d.sync += timer.eq(10) + +When ``timer == 0`` is true, the code reduces to: + +.. code-block:: + + m.d.sync += timer.eq(timer - 1) + m.d.sync += timer.eq(10) + +Due to the :ref:`assignment order `, it further reduces to: + +.. code-block:: + + m.d.sync += timer.eq(10) + +When ``timer == 0`` is false, the code reduces to: + +.. code-block:: + + m.d.sync += timer.eq(timer - 1) + +Combining these cases together, the code above is equivalent to: + +.. testcode:: + + timer = Signal(8) + m.d.sync += timer.eq(Mux(timer == 0, 10, timer - 1)) + + +.. _lang-comb: + +Combinatorial evaluation +------------------------ + +Signals in the combinatorial :ref:`control domain ` change whenever any value used to compute them changes. The final value of a combinatorial signal is equal to its :ref:`initial value ` updated by the :ref:`active assignments ` in the :ref:`assignment order `. Combinatorial signals cannot hold any state. + +Consider the following code: + +.. testsetup:: + en = Signal() + b = Signal(8) + +.. testcode:: + + a = Signal(8, reset=1) + with m.If(en): + m.d.comb += a.eq(b + 1) + +Whenever the signals ``en`` or ``b`` change, the signal ``a`` changes as well. If ``en`` is false, the final value of ``a`` is its initial value, ``1``. If ``en`` is true, the final value of ``a`` is equal to ``b + 1``. + +A combinatorial signal that is computed directly or indirectly based on its own value is a part of a *combinatorial feedback loop*, sometimes shortened to just *feedback loop*. Combinatorial feedback loops can be stable (i.e. implement a constant driver or a transparent latch), or unstable (i.e. implement a ring oscillator). nMigen prohibits using assignments to describe any kind of a combinatorial feedback loop, including transparent latches. + +.. warning:: + + The current version of nMigen does not detect combinatorial feedback loops, but processes the design under the assumption that there aren't any. If the design does in fact contain a combinatorial feedback loop, it will likely be **silently miscompiled**, though some cases will be detected during synthesis or place & route. + + This hazard will be eliminated in the future. + +.. TODO: fix this, either as a part of https://gitlab.com/nmigen/nmigen/issues/6 or on its own + +.. note:: + + In the exceedingly rare case when a combinatorial feedback loop is desirable, it is possible to implement it by directly instantiating technology primitives (e.g. device-specific LUTs or latches). This is also the only way to introduce a combinatorial feedback loop with well-defined behavior in simulation and synthesis, regardless of the HDL being used. + + +.. _lang-sync: + +Synchronous evaluation +---------------------- + +Signals in synchronous :ref:`control domains ` change whenever a specific transition (positive or negative edge) occurs on the clock of the synchronous domain. In addition, the signals in clock domains with an asynchronous reset change when such a reset is asserted. The final value of a synchronous signal is equal to its :ref:`initial value ` if the reset (of any type) is asserted, or to its current value updated by the :ref:`active assignments ` in the :ref:`assignment order ` otherwise. Synchronous signals always hold state. + +.. TODO: link to clock domains diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..49e26f1 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +sphinx~=3.0 +sphinx-rtd-theme~=0.5.0 +-e git+http://github.com/pygments/pygments.git#egg=pygments diff --git a/docs/start.rst b/docs/start.rst new file mode 100644 index 0000000..8ebd898 --- /dev/null +++ b/docs/start.rst @@ -0,0 +1,120 @@ +Getting started +############### + +This section demonstrates the basic nMigen workflow to provide a cursory overview of the language and the toolchain. See the :doc:`language guide ` for a detailed introduction to the language. + +.. TODO: add link to build system doc +.. TODO: add link to more complex examples? + + +A counter +========= + +As a first example, consider a counter with a fixed limit, enable, and overflow. The code for this example is shown below. `Download <_code/up_counter.py>`_ and run it: + +.. code-block:: shell + + $ python3 up_counter.py + + +Implementing a counter +---------------------- + +A 16-bit up counter with enable input, overflow output, and a limit fixed at design time can be implemented in nMigen as follows: + +.. literalinclude:: _code/up_counter.py + :linenos: + :lineno-match: + :end-before: # --- TEST --- + +The reusable building block of nMigen designs is an ``Elaboratable``: a Python class that includes HDL signals (``en`` and ``ovf``, in this case) as a part of its interface, and provides the ``elaborate`` method that defines its behavior. + +.. TODO: link to Elaboratable reference + +Most ``elaborate`` implementations use a ``Module`` helper to describe combinatorial (``m.d.comb``) and synchronous (``m.d.sync``) logic controlled with conditional syntax (``m.If``, ``m.Elif``, ``m.Else``) similar to Python's. They can also instantiate vendor-defined black boxes or modules written in other HDLs. + +.. TODO: link to DSL reference + + +Testing a counter +----------------- + +To verify its functionality, the counter can be simulated for a small amount of time, with a test bench driving it and checking a few simple conditions: + +.. literalinclude:: _code/up_counter.py + :linenos: + :lineno-match: + :start-after: # --- TEST --- + :end-before: # --- CONVERT --- + +The test bench is implemented as a Python generator function that is co-simulated with the counter itself. The test bench can inspect the simulated signals with ``yield sig``, update them with ``yield sig.eq(val)``, and advance the simulation by one clock cycle with ``yield``. + +.. TODO: link to simulator reference + +When run, the test bench finishes successfully, since all of the assertions hold, and produces a VCD file with waveforms recorded for every ``Signal`` as well as the clock of the ``sync`` domain: + +.. image:: _images/up_counter_gtkwave.png + :alt: A screenshot of GTKWave displaying waveforms near the clock cycle where the counter overflows. + + +Converting a counter +-------------------- + +Although some nMigen workflows do not include Verilog at all, it is still the de facto standard for HDL interoperability. Any nMigen design can be converted to synthesizable Verilog using the corresponding backend: + +.. literalinclude:: _code/up_counter.py + :linenos: + :lineno-match: + :start-after: # --- CONVERT --- + +The signals that will be connected to the ports of the top-level Verilog module should be specified explicitly. The rising edge clock and synchronous reset signals of the ``sync`` domain are added automatically; if necessary, the control signals can be configured explicitly. The result is the following Verilog code (lightly edited for clarity): + +.. TODO: link to clock domain section of language reference + +.. literalinclude:: _code/up_counter.v + :language: verilog + :linenos: + +To aid debugging, the generated Verilog code has the same general structure as the nMigen source code (although more verbose), and contains extensive source location information. + +.. note:: + + Unfortunately, at the moment none of the supported toolchains will use the source location information in diagnostic messages. + + +A blinking LED +============== + +Although nMigen works well as a standalone HDL, it also includes a build system that integrates with FPGA toolchains, and many board definition files for common developer boards that include pinouts and programming adapter invocations. The following code will blink a LED with a frequency of 1 Hz on any board that has a LED and an oscillator: + +.. literalinclude:: _code/led_blinker.py + :linenos: + :lineno-match: + :end-before: # --- BUILD --- + +The ``LEDBlinker`` module will use the first LED available on the board, and derive the clock divisor from the oscillator frequency specified in the clock constraint. It can be used, for example, with the `Lattice iCEStick evaluation board `_, one of the many boards already supported by nMigen: + +.. TODO: link to list of supported boards + +.. todo:: + + Link to the installation instructions for the FOSS iCE40 toolchain, probably as a part of board documentation. + +.. literalinclude:: _code/led_blinker.py + :linenos: + :lineno-match: + :start-after: # --- BUILD --- + +With only a single line of code, the design is synthesized, placed, routed, and programmed to the on-board Flash memory. Although not all applications will use the nMigen build system, the designs that choose it can benefit from the "turnkey" built-in workflows; if necessary, the built-in workflows can be customized to include user-specified options, commands, and files. + +.. TODO: link to build system reference + +.. note:: + + The ability to check with minimal effort whether the entire toolchain functions correctly is so important that it is built into every board definition file. To use it with the iCEStick board, run: + + .. code-block:: shell + + $ python3 -m nmigen_boards.icestick + + This command will build and program a test bitstream similar to the example above. diff --git a/nmigen/hdl/ast.py b/nmigen/hdl/ast.py index 558e018..37a3b46 100644 --- a/nmigen/hdl/ast.py +++ b/nmigen/hdl/ast.py @@ -135,7 +135,7 @@ class Value(metaclass=ABCMeta): self.src_loc = tracer.get_src_loc(1 + src_loc_at) def __bool__(self): - raise TypeError("Attempted to convert nMigen value to boolean") + raise TypeError("Attempted to convert nMigen value to Python boolean") def __invert__(self): return Operator("~", [self]) @@ -184,7 +184,7 @@ class Value(metaclass=ABCMeta): # Neither Python nor HDLs implement shifts by negative values; prohibit any shifts # by a signed value to make sure the shift amount can always be interpreted as # an unsigned value. - raise NotImplementedError("Shift by a signed value is not supported") + raise TypeError("Shift amount must be unsigned") def __lshift__(self, other): other = Value.cast(other) other.__check_shamt() diff --git a/nmigen/hdl/dsl.py b/nmigen/hdl/dsl.py index 6a7f5f9..12081ac 100644 --- a/nmigen/hdl/dsl.py +++ b/nmigen/hdl/dsl.py @@ -214,10 +214,9 @@ class Module(_ModuleBuilderRoot, Elaboratable): width, signed = cond.shape() if signed: warnings.warn("Signed values in If/Elif conditions usually result from inverting " - "Python booleans with ~, which leads to unexpected results: ~True is " - "-2, which is truthful. Replace `~flag` with `not flag`. (If this is " - "a false positive, silence this warning with " - "`m.If(x)` → `m.If(x.bool())`.)", + "Python booleans with ~, which leads to unexpected results. " + "Replace `~flag` with `not flag`. (If this is a false positive, " + "silence this warning with `m.If(x)` → `m.If(x.bool())`.)", SyntaxWarning, stacklevel=4) return cond diff --git a/nmigen/test/test_hdl_ast.py b/nmigen/test/test_hdl_ast.py index 88ad264..4946c3e 100644 --- a/nmigen/test/test_hdl_ast.py +++ b/nmigen/test/test_hdl_ast.py @@ -159,7 +159,7 @@ class ValueTestCase(FHDLTestCase): def test_bool(self): with self.assertRaises(TypeError, - msg="Attempted to convert nMigen value to boolean"): + msg="Attempted to convert nMigen value to Python boolean"): if Const(0): pass @@ -466,11 +466,11 @@ class OperatorTestCase(FHDLTestCase): self.assertEqual(v1.shape(), unsigned(11)) def test_shl_wrong(self): - with self.assertRaises(NotImplementedError, - msg="Shift by a signed value is not supported"): + with self.assertRaises(TypeError, + msg="Shift amount must be unsigned"): 1 << Const(0, signed(6)) - with self.assertRaises(NotImplementedError, - msg="Shift by a signed value is not supported"): + with self.assertRaises(TypeError, + msg="Shift amount must be unsigned"): Const(1, unsigned(4)) << -1 def test_shr(self): @@ -479,11 +479,11 @@ class OperatorTestCase(FHDLTestCase): self.assertEqual(v1.shape(), unsigned(4)) def test_shr_wrong(self): - with self.assertRaises(NotImplementedError, - msg="Shift by a signed value is not supported"): + with self.assertRaises(TypeError, + msg="Shift amount must be unsigned"): 1 << Const(0, signed(6)) - with self.assertRaises(NotImplementedError, - msg="Shift by a signed value is not supported"): + with self.assertRaises(TypeError, + msg="Shift amount must be unsigned"): Const(1, unsigned(4)) << -1 def test_lt(self): diff --git a/nmigen/test/test_hdl_dsl.py b/nmigen/test/test_hdl_dsl.py index 72cf3df..c3a5b27 100644 --- a/nmigen/test/test_hdl_dsl.py +++ b/nmigen/test/test_hdl_dsl.py @@ -289,9 +289,9 @@ class DSLTestCase(FHDLTestCase): m = Module() with self.assertWarns(SyntaxWarning, msg="Signed values in If/Elif conditions usually result from inverting Python " - "booleans with ~, which leads to unexpected results: ~True is -2, which is " - "truthful. Replace `~flag` with `not flag`. (If this is a false positive, " - "silence this warning with `m.If(x)` → `m.If(x.bool())`.)"): + "booleans with ~, which leads to unexpected results. Replace `~flag` with " + "`not flag`. (If this is a false positive, silence this warning with " + "`m.If(x)` → `m.If(x.bool())`.)"): with m.If(~True): pass @@ -301,9 +301,9 @@ class DSLTestCase(FHDLTestCase): pass with self.assertWarns(SyntaxWarning, msg="Signed values in If/Elif conditions usually result from inverting Python " - "booleans with ~, which leads to unexpected results: ~True is -2, which is " - "truthful. Replace `~flag` with `not flag`. (If this is a false positive, " - "silence this warning with `m.If(x)` → `m.If(x.bool())`.)"): + "booleans with ~, which leads to unexpected results. Replace `~flag` with " + "`not flag`. (If this is a false positive, silence this warning with " + "`m.If(x)` → `m.If(x.bool())`.)"): with m.Elif(~True): pass -- 2.30.2