├── .coveragerc ├── .gitignore ├── LICENSE.txt ├── README.md ├── doc ├── COMPAT_SUMMARY.md └── PROPOSAL.md ├── examples ├── basic │ ├── alu.py │ ├── alu_hier.py │ ├── arst.py │ ├── cdc.py │ ├── ctr.py │ ├── ctr_en.py │ ├── fsm.py │ ├── gpio.py │ ├── inst.py │ ├── mem.py │ ├── pmux.py │ ├── por.py │ ├── sel.py │ └── uart.py └── board │ ├── 01_blinky.py │ └── 02_domain.py ├── nmigen ├── __init__.py ├── _toolchain.py ├── _unused.py ├── _utils.py ├── asserts.py ├── back │ ├── __init__.py │ ├── pysim.py │ ├── rtlil.py │ └── verilog.py ├── build │ ├── __init__.py │ ├── dsl.py │ ├── plat.py │ ├── res.py │ └── run.py ├── cli.py ├── compat │ ├── __init__.py │ ├── fhdl │ │ ├── __init__.py │ │ ├── bitcontainer.py │ │ ├── conv_output.py │ │ ├── decorators.py │ │ ├── module.py │ │ ├── specials.py │ │ ├── structure.py │ │ └── verilog.py │ ├── genlib │ │ ├── __init__.py │ │ ├── cdc.py │ │ ├── coding.py │ │ ├── fifo.py │ │ ├── fsm.py │ │ ├── record.py │ │ └── resetsync.py │ └── sim │ │ └── __init__.py ├── hdl │ ├── __init__.py │ ├── ast.py │ ├── cd.py │ ├── dsl.py │ ├── ir.py │ ├── mem.py │ ├── rec.py │ └── xfrm.py ├── lib │ ├── __init__.py │ ├── cdc.py │ ├── coding.py │ ├── fifo.py │ └── io.py ├── rpc.py ├── test │ ├── __init__.py │ ├── compat │ │ ├── __init__.py │ │ ├── support.py │ │ ├── test_coding.py │ │ ├── test_constant.py │ │ ├── test_fifo.py │ │ ├── test_fsm.py │ │ ├── test_passive.py │ │ ├── test_signed.py │ │ └── test_size.py │ ├── test_build_dsl.py │ ├── test_build_plat.py │ ├── test_build_res.py │ ├── test_compat.py │ ├── test_examples.py │ ├── test_hdl_ast.py │ ├── test_hdl_cd.py │ ├── test_hdl_dsl.py │ ├── test_hdl_ir.py │ ├── test_hdl_mem.py │ ├── test_hdl_rec.py │ ├── test_hdl_xfrm.py │ ├── test_lib_cdc.py │ ├── test_lib_coding.py │ ├── test_lib_fifo.py │ ├── test_lib_io.py │ ├── test_sim.py │ └── utils.py ├── tracer.py ├── utils.py └── vendor │ ├── __init__.py │ ├── intel.py │ ├── lattice_ecp5.py │ ├── lattice_ice40.py │ ├── lattice_machxo2.py │ ├── xilinx_7series.py │ ├── xilinx_spartan_3_6.py │ └── xilinx_ultrascale.py └── setup.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | include = 4 | nmigen/* 5 | omit = 6 | nmigen/test/* 7 | */__init__.py 8 | 9 | [report] 10 | exclude_lines = 11 | :nocov: 12 | partial_branches = 13 | :nobr: 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | /*.egg-info 4 | /.eggs 5 | 6 | # coverage 7 | /.coverage 8 | /htmlcov 9 | 10 | # tests 11 | **/test/spec_*/ 12 | *.vcd 13 | *.gtkw 14 | 15 | # misc user-created 16 | *.il 17 | *.v 18 | /build 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-2020 M-Labs Limited 2 | Copyright (C) 2020 whitequark 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | 25 | Other authors retain ownership of their contributions. If a submission can 26 | reasonably be considered independently copyrightable, it's yours and we 27 | encourage you to claim it with appropriate copyright notices. This submission 28 | then falls under the "otherwise noted" category. All submissions are strongly 29 | encouraged to use the two-clause BSD license reproduced above. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This repos has moved to https://gitlab.com/nmigen/nmigen** 2 | 3 | # nMigen 4 | 5 | ## A refreshed Python toolbox for building complex digital hardware 6 | 7 | **Although nMigen is incomplete and in active development, it can already be used for real-world designs. The nMigen language (`nmigen.hdl.ast`, `nmigen.hdl.dsl`) will not undergo incompatible changes. The nMigen standard library (`nmigen.lib`) and build system (`nmigen.build`) will undergo minimal changes before their design is finalized.** 8 | 9 | Despite being faster than schematics entry, hardware design with Verilog and VHDL remains tedious and inefficient for several reasons. The event-driven model introduces issues and manual coding that are unnecessary for synchronous circuits, which represent the lion's share of today's logic designs. Counterintuitive arithmetic rules result in steeper learning curves and provide a fertile ground for subtle bugs in designs. Finally, support for procedural generation of logic (metaprogramming) through "generate" statements is very limited and restricts the ways code can be made generic, reused and organized. 10 | 11 | To address those issues, we have developed the *nMigen FHDL*, a library that replaces the event-driven paradigm with the notions of combinatorial and synchronous statements, has arithmetic rules that make integers always behave like mathematical integers, and most importantly allows the design's logic to be constructed by a Python program. This last point enables hardware designers to take advantage of the richness of the Python language—object oriented programming, function parameters, generators, operator overloading, libraries, etc.—to build well organized, reusable and elegant designs. 12 | 13 | Other nMigen libraries are built on FHDL and provide various tools and logic cores. nMigen also contains a simulator that allows test benches to be written in Python. 14 | 15 | See the [doc/](doc/) folder for more technical information. 16 | 17 | nMigen is based on [Migen][], a hardware description language developed by [M-Labs][]. Although Migen works very well in production, its design could be improved in many fundamental ways, and nMigen reimplements Migen concepts from scratch to do so. nMigen also provides an extensive [compatibility layer](#migration-from-migen) that makes it possible to build and simulate most Migen designs unmodified, as well as integrate modules written for Migen and nMigen. 18 | 19 | The development of nMigen has been supported by [M-Labs][] and [LambdaConcept][]. 20 | 21 | [migen]: https://m-labs.hk/migen 22 | [yosys]: http://www.clifford.at/yosys/ 23 | [m-labs]: https://m-labs.hk 24 | [lambdaconcept]: http://lambdaconcept.com/ 25 | 26 | ### HLS? 27 | 28 | nMigen is *not* a "Python-to-FPGA" conventional high level synthesis (HLS) tool. It will *not* take a Python program as input and generate a hardware implementation of it. In nMigen, the Python program is executed by a regular Python interpreter, and it emits explicit statements in the FHDL domain-specific language. Writing a conventional HLS tool that uses nMigen as an internal component might be a good idea, on the other hand :) 29 | 30 | ### Installation 31 | 32 | nMigen requires Python 3.6 (or newer), [Yosys][] 0.9 (or newer), as well as a device-specific toolchain. 33 | 34 | pip install git+https://github.com/m-labs/nmigen.git 35 | pip install git+https://github.com/m-labs/nmigen-boards.git 36 | 37 | ### Introduction 38 | 39 | TBD 40 | 41 | ### Supported devices 42 | 43 | nMigen can be used to target any FPGA or ASIC process that accepts behavioral Verilog-2001 as input. It also offers extended support for many FPGA families, providing toolchain integration, abstractions for device-specific primitives, and more. Specifically: 44 | 45 | * Lattice iCE40 (toolchains: **Yosys+nextpnr**, LSE-iCECube2, Synplify-iCECube2); 46 | * Lattice MachXO2 (toolchains: Diamond); 47 | * Lattice ECP5 (toolchains: **Yosys+nextpnr**, Diamond); 48 | * Xilinx Spartan 3A (toolchains: ISE); 49 | * Xilinx Spartan 6 (toolchains: ISE); 50 | * Xilinx 7-series (toolchains: Vivado); 51 | * Xilinx UltraScale (toolchains: Vivado); 52 | * Intel (toolchains: Quartus). 53 | 54 | FOSS toolchains are listed in **bold**. 55 | 56 | ### Migration from [Migen][] 57 | 58 | If you are already familiar with [Migen][], the good news is that nMigen provides a comprehensive Migen compatibility layer! An existing Migen design can be synthesized and simulated with nMigen in three steps: 59 | 60 | 1. Replace all `from migen import <...>` statements with `from nmigen.compat import <...>`. 61 | 2. Replace every explicit mention of the default `sys` clock domain with the new default `sync` clock domain. E.g. `ClockSignal("sys")` is changed to `ClockSignal("sync")`. 62 | 3. Migrate from Migen build/platform system to nMigen build/platform system. nMigen does not provide a build/platform compatibility layer because both the board definition files and the platform abstraction differ too much. 63 | 64 | Note that nMigen will **not** produce the exact same RTL as Migen did. nMigen has been built to allow you to take advantage of the new and improved functionality it has (such as producing hierarchical RTL) while making migration as painless as possible. 65 | 66 | Once your design passes verification with nMigen, you can migrate it to the nMigen syntax one module at a time. Migen modules can be added to nMigen modules and vice versa, so there is no restriction on the order of migration, either. 67 | 68 | ### Community 69 | 70 | nMigen discussions take place on the M-Labs IRC channel, [#m-labs at freenode.net](https://webchat.freenode.net/?channels=m-labs). Feel free to join to ask questions about using nMigen or discuss ongoing development of nMigen and its related projects. 71 | 72 | ### License 73 | 74 | nMigen is released under the very permissive two-clause BSD license. Under the terms of this license, you are authorized to use nMigen for closed-source proprietary designs. 75 | 76 | Even though we do not require you to do so, these things are awesome, so please do them if possible: 77 | * tell us that you are using nMigen 78 | * put the [nMigen logo](doc/nmigen_logo.svg) on the page of a product using it, with a link to https://nmigen.org 79 | * cite nMigen in publications related to research it has helped 80 | * send us feedback and suggestions for improvements 81 | * send us bug reports when something goes wrong 82 | * send us the modifications and improvements you have done to nMigen as pull requests on GitHub 83 | 84 | See LICENSE file for full copyright and license info. 85 | 86 | "Electricity! It's like magic!" 87 | -------------------------------------------------------------------------------- /doc/COMPAT_SUMMARY.md: -------------------------------------------------------------------------------- 1 | Migen and nMigen compatibility summary 2 | ====================================== 3 | 4 | nMigen intends to provide as close to 100% compatibility to Migen as possible without compromising its other goals. However, Migen widely uses `*` imports, tends to expose implementation details, and in general does not have a well-defined interface. This document attempts to elucidate a well-defined Migen API surface (including, when necessary, private items that have been used downstream), and describes the intended nMigen replacements and their implementation status. 5 | 6 | API change legend: 7 | - *id*: identical 8 | - *obs*: removed or incompatibly changed with compatibility stub provided 9 | - *obs →n*: removed or incompatibly changed with compatibility stub provided, use *n* instead 10 | - *brk*: removed or incompatibly changed with no replacement provided 11 | - *brk →n*: removed or incompatibly changed with no replacement provided, use *n* instead 12 | - *→n*: renamed to *n* 13 | - *⇒m*: merged into *m* 14 | - *a=→b=*: parameter *a* renamed to *b* 15 | - *a=∼*: parameter *a* removed 16 | - *.a=→.b*: attribute *a* renamed to *b* 17 | - *.a=∼*: attribute *a* removed 18 | - *?*: no decision made yet 19 | 20 | When describing renames or replacements, `mod` refers to a 3rd-party package `mod` (no nMigen implementation provided), `.mod.item` refers to `nmigen.mod.item`, and "(import `.item`)" means that, while `item` is provided under `nmigen.mod.item`, it is aliased to, and should be imported from a shorter path for readability. 21 | 22 | Status legend: 23 | - (−) No decision yet, or no replacement implemented 24 | - (+) Implemented replacement (the API and/or compatibility shim are provided) 25 | - (⊕) Verified replacement and/or compatibility shim (the compatibility shim is manually reviewed and/or has 100% test coverage) 26 | - (⊙) No direct replacement or compatibility shim is provided 27 | 28 | Compatibility summary 29 | --------------------- 30 | 31 | - (−) `fhdl` → `.hdl` 32 | - (⊕) `bitcontainer` ⇒ `.tools` 33 | - (⊕) `log2_int` id 34 | - (⊕) `bits_for` id 35 | - (⊕) `value_bits_sign` → `Value.shape` 36 | - (⊕) `conv_output` **obs** 37 | - (⊕) `decorators` ⇒ `.hdl.xfrm` 38 |
Note: `transform_*` methods not considered part of public API. 39 | - (⊙) `ModuleTransformer` **brk** 40 | - (⊙) `ControlInserter` **brk** 41 | - (⊕) `CEInserter` → `EnableInserter` 42 | - (⊕) `ResetInserter` id 43 | - (⊕) `ClockDomainsRenamer` → `DomainRenamer`, `cd_remapping=`→`domain_map=` 44 | - (⊙) `edif` **brk** 45 | - (⊕) `module` **obs** → `.hdl.dsl` 46 |
Note: any class inheriting from `Module` in oMigen should inherit from `Elaboratable` in nMigen and use an nMigen `Module` in its `.elaborate()` method. 47 | - (⊕) `FinalizeError` **obs** 48 | - (⊕) `Module` **obs** → `.hdl.dsl.Module` 49 | - (⊙) `namer` **brk** 50 | - (⊙) `simplify` **brk** 51 | - (⊕) `specials` **obs** 52 | - (⊙) `Special` **brk** 53 | - (⊕) `Tristate` **obs** 54 | - (⊕) `TSTriple` **obs** → `.lib.io.Pin` 55 | - (⊕) `Instance` → `.hdl.ir.Instance` 56 | - (⊕) `Memory` id 57 |
Note: nMigen memories should not be added as submodules. 58 | - (⊕) `.get_port` **obs** → `.read_port()` + `.write_port()` 59 | - (⊕) `_MemoryPort` **obs** → `.hdl.mem.ReadPort` + `.hdl.mem.WritePort` 60 | - (⊕) `READ_FIRST`/`WRITE_FIRST` **obs** 61 |
Note: `READ_FIRST` corresponds to `mem.read_port(transparent=False)`, and `WRITE_FIRST` to `mem.read_port(transparent=True)`. 62 | - (⊙) `NO_CHANGE` **brk** 63 |
Note: in designs using `NO_CHANGE`, replace it with logic implementing required semantics explicitly, or with a different mode. 64 | - (⊕) `structure` → `.hdl.ast` 65 | - (⊕) `DUID` id 66 | - (⊕) `_Value` → `Value` 67 |
Note: values no longer valid as keys in `dict` and `set`; use `ValueDict` and `ValueSet` instead. 68 | - (⊕) `wrap` → `Value.cast` 69 | - (⊕) `_Operator` → `Operator`, `op=`→`operator=`, `.op`→`.operator` 70 | - (⊕) `Mux` id 71 | - (⊕) `_Slice` → `Slice` id 72 | - (⊕) `_Part` → `Part` id 73 | - (⊕) `Cat` id, `.l`→`.parts` 74 | - (⊕) `Replicate` → `Repl`, `v=`→`value=`, `n=`→`count=`, `.v`→`.value`, `.n`→`.count` 75 | - (⊕) `Constant` → `Const`, `bits_sign=`→`shape=`, `.nbits`→`.width` 76 | - (⊕) `Signal` id, `bits_sign=`→`shape=`, `attr=`→`attrs=`, `name_override=`∼, `related=`, `variable=`∼, `.nbits`→`.width` 77 | - (⊕) `ClockSignal` id, `cd=`→`domain=`, `.cd`→`.domain` 78 | - (⊕) `ResetSignal` id, `cd=`→`domain=`, `.cd`→`.domain` 79 | - (⊕) `_Statement` → `Statement` 80 | - (⊕) `_Assign` → `Assign`, `l=`→`lhs=`, `r=`→`rhs=` 81 | - (⊕) `_check_statement` **obs** → `Statement.cast` 82 | - (⊕) `If` **obs** → `.hdl.dsl.Module.If` 83 | - (⊕) `Case` **obs** → `.hdl.dsl.Module.Switch` 84 | - (⊕) `_ArrayProxy` → `.hdl.ast.ArrayProxy`, `choices=`→`elems=`, `key=`→`index=` 85 | - (⊕) `Array` id 86 | - (⊕) `ClockDomain` → `.hdl.cd.ClockDomain` 87 | - (⊙) `_ClockDomainList` **brk** 88 | - (⊙) `SPECIAL_INPUT`/`SPECIAL_OUTPUT`/`SPECIAL_INOUT` **brk** 89 | - (⊙) `_Fragment` **brk** → `.hdl.ir.Fragment` 90 | - (⊙) `tools` **brk** 91 | - (⊙) `insert_resets` **brk** → `.hdl.xfrm.ResetInserter` 92 | - (⊙) `rename_clock_domain` **brk** → `.hdl.xfrm.DomainRenamer` 93 | - (⊙) `tracer` **brk** 94 | - (⊕) `get_var_name` → `.tracer.get_var_name` 95 | - (⊙) `remove_underscore` **brk** 96 | - (⊙) `get_obj_var_name` **brk** 97 | - (⊙) `index_id` **brk** 98 | - (⊙) `trace_back` **brk** 99 | - (⊙) `verilog` 100 | - (⊙) `DummyAttrTranslate` ? 101 | - (⊕) `convert` **obs** → `.back.verilog.convert` 102 | - (⊙) `visit` **brk** → `.hdl.xfrm` 103 | - (⊙) `NodeVisitor` **brk** 104 | - (⊙) `NodeTransformer` **brk** → `.hdl.xfrm.ValueTransformer`/`.hdl.xfrm.StatementTransformer` 105 | - (−) `genlib` → `.lib` 106 | - (−) `cdc` ? 107 | - (⊙) `MultiRegImpl` **brk** 108 | - (⊕) `MultiReg` → `.lib.cdc.FFSynchronizer` 109 | - (−) `PulseSynchronizer` ? 110 | - (−) `BusSynchronizer` ? 111 | - (⊕) `GrayCounter` **obs** → `.lib.coding.GrayEncoder` 112 | - (⊕) `GrayDecoder` **obs** → `.lib.coding.GrayDecoder` 113 |
Note: `.lib.coding.GrayEncoder` and `.lib.coding.GrayDecoder` are purely combinatorial. 114 | - (−) `ElasticBuffer` ? 115 | - (−) `lcm` ? 116 | - (−) `Gearbox` ? 117 | - (⊕) `coding` id 118 | - (⊕) `Encoder` id 119 | - (⊕) `PriorityEncoder` id 120 | - (⊕) `Decoder` id 121 | - (⊕) `PriorityDecoder` id 122 | - (−) `divider` ? 123 | - (−) `Divider` ? 124 | - (⊕) `fifo` → `.lib.fifo` 125 | - (⊕) `_FIFOInterface` → `FIFOInterface` 126 | - (⊕) `SyncFIFO` id, `.replace=`∼ 127 | - (⊕) `SyncFIFOBuffered` id, `.fifo=`∼ 128 | - (⊕) `AsyncFIFO` ? 129 | - (⊕) `AsyncFIFOBuffered`, `.fifo=`∼ 130 | - (⊕) `fsm` **obs** 131 |
Note: FSMs are a part of core nMigen DSL; however, not all functionality is provided. The compatibility shim is a complete port of Migen FSM module. 132 | - (⊙) `io` **brk** 133 |
Note: all functionality in this module is a part of nMigen platform system. 134 | - (−) `misc` ? 135 | - (−) `split` ? 136 | - (−) `displacer` ? 137 | - (−) `chooser` ? 138 | - (−) `timeline` ? 139 | - (−) `WaitTimer` ? 140 | - (−) `BitSlip` ? 141 | - (⊕) `record` **obs** → `.hdl.rec.Record` 142 |
Note: nMigen uses a `Layout` object to represent record layouts. 143 | - (⊕) `DIR_NONE` id 144 | - (⊕) `DIR_M_TO_S` → `DIR_FANOUT` 145 | - (⊕) `DIR_S_TO_M` → `DIR_FANIN` 146 | - (⊕) `Record` id 147 | - (⊙) `set_layout_parameters` **brk** 148 | - (⊙) `layout_len` **brk** 149 | - (⊙) `layout_get` **brk** 150 | - (⊙) `layout_partial` **brk** 151 | - (⊕) `resetsync` **obs** 152 | - (⊕) `AsyncResetSynchronizer` **obs** → `.lib.cdc.ResetSynchronizer` 153 | - (−) `roundrobin` ? 154 | - (−) `SP_WITHDRAW`/`SP_CE` ? 155 | - (−) `RoundRobin` ? 156 | - (−) `sort` ? 157 | - (−) `BitonicSort` ? 158 | - (⊕) `sim` **obs** → `.back.pysim` 159 |
Note: only items directly under `nmigen.compat.sim`, not submodules, are provided. 160 | - (⊙) `core` **brk** 161 | - (⊙) `vcd` **brk** → `vcd` 162 | - (⊙) `Simulator` **brk** 163 | - (⊕) `run_simulation` **obs** → `.back.pysim.Simulator` 164 | - (⊕) `passive` **obs** → `.hdl.ast.Passive` 165 | - (⊙) `build` **brk** 166 |
Note: the build system has been completely redesigned in nMigen. 167 | - (⊙) `util` **brk** 168 | -------------------------------------------------------------------------------- /doc/PROPOSAL.md: -------------------------------------------------------------------------------- 1 | *The text below is the original nMigen implementation proposal. It is provided for illustrative and historical purposes only.* 2 | 3 | This repository contains a proposal for the design of nMigen in form of an implementation. This implementation deviates from the existing design of Migen by making several observations of its drawbacks: 4 | 5 | * Migen is strongly tailored towards Verilog, yet translation of Migen to Verilog is not straightforward, leaves much semantics implicit (e.g. signedness, width extension, combinatorial assignments, sub-signal assignments...); 6 | * Hierarchical designs are useful for floorplanning and optimization, yet Migen does not support them at all; 7 | * Migen's syntax is not easily composable, and something like an FSM requires extending Migen's syntax in non-orthogonal ways; 8 | * Migen reimplements a lot of mature open-source tooling, such as conversion of RTL to Verilog (Yosys' Verilog backend), or simulation (Icarus Verilog, Verilator, etc.), and often lacks in features, speed, or corner case handling. 9 | * Migen requires awkward specials for some FPGA features such as asynchronous resets. 10 | 11 | It also observes that Yosys' intermediate language, RTLIL, is an ideal target for Migen-style logic, as conversion of FHDL to RTLIL is essentially a 1:1 translation, with the exception of the related issues of naming and hierarchy. 12 | 13 | This proposal makes several major changes to Migen that hopefully solve all of these drawbacks: 14 | 15 | * nMigen changes FHDL's internal representation to closely match that of RTLIL; 16 | * nMigen outputs RTLIL and relies on Yosys for conversion to Verilog, EDIF, etc; 17 | * nMigen uses an exact mapping between FHDL signals and RTLIL names to off-load logic simulation to Icarus Verilog, Verilator, etc; 18 | * nMigen uses an uniform, composable Python eHDL; 19 | * nMigen outputs hierarchical RTLIL, automatically threading signals through the hierarchy; 20 | * nMigen supports asynchronous reset directly; 21 | * nMigen makes driving a signal from multiple clock domains a precise, hard error. 22 | 23 | This proposal keeps in mind but does not make the following major changes: 24 | 25 | * nMigen could be easily modified to flatten the hierarchy if a signal is driven simultaneously from multiple modules; 26 | * nMigen could be easily modified to support `x` values (invalid / don't care) by relying on RTLIL's ability to directly represent them; 27 | * nMigen could be easily modified to support negative edge triggered flip-flops by relying on RTLIL's ability to directly represent them; 28 | * nMigen could be easily modified to track Python source locations of primitives and export them to RTLIL/Verilog through the `src` attribute, displaying the Python source locations in timing reports directly. 29 | 30 | This proposal also makes the following simplifications: 31 | * Specials are eliminated. Primitives such as memory ports are represented directly, and primitives such as tristate buffers are lowered to a selectable implementation via ordinary dependency injection (`f.submodules += platform.get_tristate(triple, io)`). 32 | 33 | The internals of nMigen in this proposal are cleaned up, yet they are kept sufficiently close to Migen that \~all Migen code should be possible to run directly on nMigen using a syntactic compatibility layer. 34 | 35 | One might reasonably expect that a roundtrip through RTLIL would result in unreadable Verilog. 36 | However, this is not the case, e.g. consider the examples: 37 | 38 |
39 | alu.v 40 | 41 | ```verilog 42 | module \$1 (co, sel, a, b, o); 43 | wire [17:0] _04_; 44 | input [15:0] a; 45 | input [15:0] b; 46 | output co; 47 | reg \co$next ; 48 | output [15:0] o; 49 | reg [15:0] \o$next ; 50 | input [1:0] sel; 51 | assign _04_ = $signed(+ a) + $signed(- b); 52 | always @* begin 53 | \o$next = 16'h0000; 54 | \co$next = 1'h0; 55 | casez ({ 1'h1, sel == 2'h2, sel == 1'h1, sel == 0'b0 }) 56 | 4'bzzz1: 57 | \o$next = a | b; 58 | 4'bzz1z: 59 | \o$next = a & b; 60 | 4'bz1zz: 61 | \o$next = a ^ b; 62 | 4'b1zzz: 63 | { \co$next , \o$next } = _04_[16:0]; 64 | endcase 65 | end 66 | assign o = \o$next ; 67 | assign co = \co$next ; 68 | endmodule 69 | ``` 70 |
71 | 72 |
73 | alu_hier.v 74 | 75 | ```verilog 76 | module add(b, o, a); 77 | wire [16:0] _0_; 78 | input [15:0] a; 79 | input [15:0] b; 80 | output [15:0] o; 81 | reg [15:0] \o$next ; 82 | assign _0_ = a + b; 83 | always @* begin 84 | \o$next = 16'h0000; 85 | \o$next = _0_[15:0]; 86 | end 87 | assign o = \o$next ; 88 | endmodule 89 | 90 | module sub(b, o, a); 91 | wire [16:0] _0_; 92 | input [15:0] a; 93 | input [15:0] b; 94 | output [15:0] o; 95 | reg [15:0] \o$next ; 96 | assign _0_ = a - b; 97 | always @* begin 98 | \o$next = 16'h0000; 99 | \o$next = _0_[15:0]; 100 | end 101 | assign o = \o$next ; 102 | endmodule 103 | 104 | module top(a, b, o, add_o, sub_o, op); 105 | input [15:0] a; 106 | wire [15:0] add_a; 107 | reg [15:0] \add_a$next ; 108 | wire [15:0] add_b; 109 | reg [15:0] \add_b$next ; 110 | input [15:0] add_o; 111 | input [15:0] b; 112 | output [15:0] o; 113 | reg [15:0] \o$next ; 114 | input op; 115 | wire [15:0] sub_a; 116 | reg [15:0] \sub_a$next ; 117 | wire [15:0] sub_b; 118 | reg [15:0] \sub_b$next ; 119 | input [15:0] sub_o; 120 | add add ( 121 | .a(add_a), 122 | .b(add_b), 123 | .o(add_o) 124 | ); 125 | sub sub ( 126 | .a(sub_a), 127 | .b(sub_b), 128 | .o(sub_o) 129 | ); 130 | always @* begin 131 | \o$next = 16'h0000; 132 | \add_a$next = 16'h0000; 133 | \add_b$next = 16'h0000; 134 | \sub_a$next = 16'h0000; 135 | \sub_b$next = 16'h0000; 136 | \add_a$next = a; 137 | \sub_a$next = a; 138 | \add_b$next = b; 139 | \sub_b$next = b; 140 | casez ({ 1'h1, op }) 141 | 2'bz1: 142 | \o$next = sub_o; 143 | 2'b1z: 144 | \o$next = add_o; 145 | endcase 146 | end 147 | assign o = \o$next ; 148 | assign add_a = \add_a$next ; 149 | assign add_b = \add_b$next ; 150 | assign sub_a = \sub_a$next ; 151 | assign sub_b = \sub_b$next ; 152 | endmodule 153 | ``` 154 |
155 |
156 | clkdiv.v 157 | 158 | ```verilog 159 | module \$1 (sys_clk, o); 160 | wire [16:0] _0_; 161 | output o; 162 | reg \o$next ; 163 | input sys_clk; 164 | wire sys_rst; 165 | (* init = 16'hffff *) 166 | reg [15:0] v = 16'hffff; 167 | reg [15:0] \v$next ; 168 | assign _0_ = v + 1'h1; 169 | always @(posedge sys_clk) 170 | v <= \v$next ; 171 | always @* begin 172 | \o$next = 1'h0; 173 | \v$next = _0_[15:0]; 174 | \o$next = v[15]; 175 | casez (sys_rst) 176 | 1'h1: 177 | \v$next = 16'hffff; 178 | endcase 179 | end 180 | assign o = \o$next ; 181 | endmodule 182 | ``` 183 |
184 | 185 |
186 | arst.v 187 | 188 | ```verilog 189 | module \$1 (o, sys_clk, sys_rst); 190 | wire [16:0] _0_; 191 | output o; 192 | reg \o$next ; 193 | input sys_clk; 194 | input sys_rst; 195 | (* init = 16'h0000 *) 196 | reg [15:0] v = 16'h0000; 197 | reg [15:0] \v$next ; 198 | assign _0_ = v + 1'h1; 199 | always @(posedge sys_clk or posedge sys_rst) 200 | if (sys_rst) 201 | v <= 16'h0000; 202 | else 203 | v <= \v$next ; 204 | always @* begin 205 | \o$next = 1'h0; 206 | \v$next = _0_[15:0]; 207 | \o$next = v[15]; 208 | end 209 | assign o = \o$next ; 210 | endmodule 211 | ``` 212 |
213 | 214 |
215 | pmux.v 216 | 217 | ```verilog 218 | module \$1 (c, o, s, a, b); 219 | input [15:0] a; 220 | input [15:0] b; 221 | input [15:0] c; 222 | output [15:0] o; 223 | reg [15:0] \o$next ; 224 | input [2:0] s; 225 | always @* begin 226 | \o$next = 16'h0000; 227 | casez (s) 228 | 3'bzz1: 229 | \o$next = a; 230 | 3'bz1z: 231 | \o$next = b; 232 | 3'b1zz: 233 | \o$next = c; 234 | 3'hz: 235 | \o$next = 16'h0000; 236 | endcase 237 | end 238 | assign o = \o$next ; 239 | endmodule 240 | ``` 241 |
242 | -------------------------------------------------------------------------------- /examples/basic/alu.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.cli import main 3 | 4 | 5 | class ALU(Elaboratable): 6 | def __init__(self, width): 7 | self.sel = Signal(2) 8 | self.a = Signal(width) 9 | self.b = Signal(width) 10 | self.o = Signal(width) 11 | self.co = Signal() 12 | 13 | def elaborate(self, platform): 14 | m = Module() 15 | with m.If(self.sel == 0b00): 16 | m.d.comb += self.o.eq(self.a | self.b) 17 | with m.Elif(self.sel == 0b01): 18 | m.d.comb += self.o.eq(self.a & self.b) 19 | with m.Elif(self.sel == 0b10): 20 | m.d.comb += self.o.eq(self.a ^ self.b) 21 | with m.Else(): 22 | m.d.comb += Cat(self.o, self.co).eq(self.a - self.b) 23 | return m 24 | 25 | 26 | if __name__ == "__main__": 27 | alu = ALU(width=16) 28 | main(alu, ports=[alu.sel, alu.a, alu.b, alu.o, alu.co]) 29 | -------------------------------------------------------------------------------- /examples/basic/alu_hier.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.cli import main 3 | 4 | 5 | class Adder(Elaboratable): 6 | def __init__(self, width): 7 | self.a = Signal(width) 8 | self.b = Signal(width) 9 | self.o = Signal(width) 10 | 11 | def elaborate(self, platform): 12 | m = Module() 13 | m.d.comb += self.o.eq(self.a + self.b) 14 | return m 15 | 16 | 17 | class Subtractor(Elaboratable): 18 | def __init__(self, width): 19 | self.a = Signal(width) 20 | self.b = Signal(width) 21 | self.o = Signal(width) 22 | 23 | def elaborate(self, platform): 24 | m = Module() 25 | m.d.comb += self.o.eq(self.a - self.b) 26 | return m 27 | 28 | 29 | class ALU(Elaboratable): 30 | def __init__(self, width): 31 | self.op = Signal() 32 | self.a = Signal(width) 33 | self.b = Signal(width) 34 | self.o = Signal(width) 35 | 36 | self.add = Adder(width) 37 | self.sub = Subtractor(width) 38 | 39 | def elaborate(self, platform): 40 | m = Module() 41 | m.submodules.add = self.add 42 | m.submodules.sub = self.sub 43 | m.d.comb += [ 44 | self.add.a.eq(self.a), 45 | self.sub.a.eq(self.a), 46 | self.add.b.eq(self.b), 47 | self.sub.b.eq(self.b), 48 | ] 49 | with m.If(self.op): 50 | m.d.comb += self.o.eq(self.sub.o) 51 | with m.Else(): 52 | m.d.comb += self.o.eq(self.add.o) 53 | return m 54 | 55 | 56 | if __name__ == "__main__": 57 | alu = ALU(width=16) 58 | main(alu, ports=[alu.op, alu.a, alu.b, alu.o]) 59 | -------------------------------------------------------------------------------- /examples/basic/arst.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.cli import main 3 | 4 | 5 | class ClockDivisor(Elaboratable): 6 | def __init__(self, factor): 7 | self.v = Signal(factor) 8 | self.o = Signal() 9 | 10 | def elaborate(self, platform): 11 | m = Module() 12 | m.d.sync += self.v.eq(self.v + 1) 13 | m.d.comb += self.o.eq(self.v[-1]) 14 | return m 15 | 16 | 17 | if __name__ == "__main__": 18 | m = Module() 19 | m.domains.sync = sync = ClockDomain("sync", async_reset=True) 20 | m.submodules.ctr = ctr = ClockDivisor(factor=16) 21 | main(m, ports=[ctr.o, sync.clk]) 22 | -------------------------------------------------------------------------------- /examples/basic/cdc.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.lib.cdc import FFSynchronizer 3 | from nmigen.cli import main 4 | 5 | 6 | i, o = Signal(name="i"), Signal(name="o") 7 | m = Module() 8 | m.submodules += FFSynchronizer(i, o) 9 | 10 | if __name__ == "__main__": 11 | main(m, ports=[i, o]) 12 | -------------------------------------------------------------------------------- /examples/basic/ctr.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.cli import main, pysim 3 | 4 | 5 | class Counter(Elaboratable): 6 | def __init__(self, width): 7 | self.v = Signal(width, reset=2**width-1) 8 | self.o = Signal() 9 | 10 | def elaborate(self, platform): 11 | m = Module() 12 | m.d.sync += self.v.eq(self.v + 1) 13 | m.d.comb += self.o.eq(self.v[-1]) 14 | return m 15 | 16 | 17 | ctr = Counter(width=16) 18 | if __name__ == "__main__": 19 | main(ctr, ports=[ctr.o]) 20 | -------------------------------------------------------------------------------- /examples/basic/ctr_en.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.back import rtlil, verilog, pysim 3 | 4 | 5 | class Counter(Elaboratable): 6 | def __init__(self, width): 7 | self.v = Signal(width, reset=2**width-1) 8 | self.o = Signal() 9 | self.en = Signal() 10 | 11 | def elaborate(self, platform): 12 | m = Module() 13 | m.d.sync += self.v.eq(self.v + 1) 14 | m.d.comb += self.o.eq(self.v[-1]) 15 | return EnableInserter(self.en)(m) 16 | 17 | 18 | ctr = Counter(width=16) 19 | 20 | print(verilog.convert(ctr, ports=[ctr.o, ctr.en])) 21 | 22 | sim = pysim.Simulator(ctr) 23 | sim.add_clock(1e-6) 24 | def ce_proc(): 25 | yield; yield; yield 26 | yield ctr.en.eq(1) 27 | yield; yield; yield 28 | yield ctr.en.eq(0) 29 | yield; yield; yield 30 | yield ctr.en.eq(1) 31 | sim.add_sync_process(ce_proc) 32 | with sim.write_vcd("ctrl.vcd", "ctrl.gtkw", traces=[ctr.en, ctr.v, ctr.o]): 33 | sim.run_until(100e-6, run_passive=True) 34 | -------------------------------------------------------------------------------- /examples/basic/fsm.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.cli import main 3 | 4 | 5 | class UARTReceiver(Elaboratable): 6 | def __init__(self, divisor): 7 | self.divisor = divisor 8 | 9 | self.i = Signal() 10 | self.data = Signal(8) 11 | self.rdy = Signal() 12 | self.ack = Signal() 13 | self.err = Signal() 14 | 15 | def elaborate(self, platform): 16 | m = Module() 17 | 18 | ctr = Signal(range(self.divisor)) 19 | stb = Signal() 20 | with m.If(ctr == 0): 21 | m.d.sync += ctr.eq(self.divisor - 1) 22 | m.d.comb += stb.eq(1) 23 | with m.Else(): 24 | m.d.sync += ctr.eq(ctr - 1) 25 | 26 | bit = Signal(3) 27 | with m.FSM() as fsm: 28 | with m.State("START"): 29 | with m.If(~self.i): 30 | m.next = "DATA" 31 | m.d.sync += [ 32 | ctr.eq(self.divisor // 2), 33 | bit.eq(7), 34 | ] 35 | with m.State("DATA"): 36 | with m.If(stb): 37 | m.d.sync += [ 38 | bit.eq(bit - 1), 39 | self.data.eq(Cat(self.i, self.data)) 40 | ] 41 | with m.If(bit == 0): 42 | m.next = "STOP" 43 | with m.State("STOP"): 44 | with m.If(stb): 45 | with m.If(self.i): 46 | m.next = "DONE" 47 | with m.Else(): 48 | m.next = "ERROR" 49 | 50 | with m.State("DONE"): 51 | m.d.comb += self.rdy.eq(1) 52 | with m.If(self.ack): 53 | m.next = "START" 54 | 55 | m.d.comb += self.err.eq(fsm.ongoing("ERROR")) 56 | with m.State("ERROR"): 57 | pass 58 | 59 | return m 60 | 61 | 62 | if __name__ == "__main__": 63 | rx = UARTReceiver(20) 64 | main(rx, ports=[rx.i, rx.data, rx.rdy, rx.ack, rx.err]) 65 | -------------------------------------------------------------------------------- /examples/basic/gpio.py: -------------------------------------------------------------------------------- 1 | from types import SimpleNamespace 2 | from nmigen import * 3 | from nmigen.cli import main 4 | 5 | 6 | class GPIO(Elaboratable): 7 | def __init__(self, pins, bus): 8 | self.pins = pins 9 | self.bus = bus 10 | 11 | def elaborate(self, platform): 12 | m = Module() 13 | m.d.comb += self.bus.r_data.eq(self.pins[self.bus.addr]) 14 | with m.If(self.bus.we): 15 | m.d.sync += self.pins[self.bus.addr].eq(self.bus.w_data) 16 | return m 17 | 18 | 19 | if __name__ == "__main__": 20 | bus = Record([ 21 | ("addr", 3), 22 | ("r_data", 1), 23 | ("w_data", 1), 24 | ("we", 1), 25 | ]) 26 | pins = Signal(8) 27 | gpio = GPIO(Array(pins), bus) 28 | main(gpio, ports=[pins, bus.addr, bus.r_data, bus.w_data, bus.we]) 29 | -------------------------------------------------------------------------------- /examples/basic/inst.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.cli import main 3 | 4 | 5 | class System(Elaboratable): 6 | def __init__(self): 7 | self.adr = Signal(16) 8 | self.dat_r = Signal(8) 9 | self.dat_w = Signal(8) 10 | self.we = Signal() 11 | 12 | def elaborate(self, platform): 13 | m = Module() 14 | m.submodules.cpu = Instance("CPU", 15 | p_RESET_ADDR=0xfff0, 16 | i_d_adr =self.adr, 17 | i_d_dat_r=self.dat_r, 18 | o_d_dat_w=self.dat_w, 19 | i_d_we =self.we, 20 | ) 21 | return m 22 | 23 | 24 | if __name__ == "__main__": 25 | sys = System() 26 | main(sys, ports=[sys.adr, sys.dat_r, sys.dat_w, sys.we]) 27 | -------------------------------------------------------------------------------- /examples/basic/mem.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.cli import main 3 | 4 | 5 | class RegisterFile(Elaboratable): 6 | def __init__(self): 7 | self.adr = Signal(4) 8 | self.dat_r = Signal(8) 9 | self.dat_w = Signal(8) 10 | self.we = Signal() 11 | self.mem = Memory(width=8, depth=16, init=[0xaa, 0x55]) 12 | 13 | def elaborate(self, platform): 14 | m = Module() 15 | m.submodules.rdport = rdport = self.mem.read_port() 16 | m.submodules.wrport = wrport = self.mem.write_port() 17 | m.d.comb += [ 18 | rdport.addr.eq(self.adr), 19 | self.dat_r.eq(rdport.data), 20 | wrport.addr.eq(self.adr), 21 | wrport.data.eq(self.dat_w), 22 | wrport.en.eq(self.we), 23 | ] 24 | return m 25 | 26 | 27 | if __name__ == "__main__": 28 | rf = RegisterFile() 29 | main(rf, ports=[rf.adr, rf.dat_r, rf.dat_w, rf.we]) 30 | -------------------------------------------------------------------------------- /examples/basic/pmux.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.cli import main 3 | 4 | 5 | class ParMux(Elaboratable): 6 | def __init__(self, width): 7 | self.s = Signal(3) 8 | self.a = Signal(width) 9 | self.b = Signal(width) 10 | self.c = Signal(width) 11 | self.o = Signal(width) 12 | 13 | def elaborate(self, platform): 14 | m = Module() 15 | with m.Switch(self.s): 16 | with m.Case("--1"): 17 | m.d.comb += self.o.eq(self.a) 18 | with m.Case("-1-"): 19 | m.d.comb += self.o.eq(self.b) 20 | with m.Case("1--"): 21 | m.d.comb += self.o.eq(self.c) 22 | with m.Case(): 23 | m.d.comb += self.o.eq(0) 24 | return m 25 | 26 | 27 | if __name__ == "__main__": 28 | pmux = ParMux(width=16) 29 | main(pmux, ports=[pmux.s, pmux.a, pmux.b, pmux.c, pmux.o]) 30 | -------------------------------------------------------------------------------- /examples/basic/por.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | from nmigen.cli import main 3 | 4 | 5 | m = Module() 6 | cd_por = ClockDomain(reset_less=True) 7 | cd_sync = ClockDomain() 8 | m.domains += cd_por, cd_sync 9 | 10 | delay = Signal(range(256), reset=255) 11 | with m.If(delay != 0): 12 | m.d.por += delay.eq(delay - 1) 13 | m.d.comb += [ 14 | ClockSignal().eq(cd_por.clk), 15 | ResetSignal().eq(delay != 0), 16 | ] 17 | 18 | if __name__ == "__main__": 19 | main(m, ports=[cd_por.clk]) 20 | -------------------------------------------------------------------------------- /examples/basic/sel.py: -------------------------------------------------------------------------------- 1 | from types import SimpleNamespace 2 | from nmigen import * 3 | from nmigen.cli import main 4 | 5 | 6 | class FlatGPIO(Elaboratable): 7 | def __init__(self, pins, bus): 8 | self.pins = pins 9 | self.bus = bus 10 | 11 | def elaborate(self, platform): 12 | bus = self.bus 13 | 14 | m = Module() 15 | m.d.comb += bus.r_data.eq(self.pins.word_select(bus.addr, len(bus.r_data))) 16 | with m.If(bus.we): 17 | m.d.sync += self.pins.word_select(bus.addr, len(bus.w_data)).eq(bus.w_data) 18 | return m 19 | 20 | 21 | if __name__ == "__main__": 22 | bus = Record([ 23 | ("addr", 3), 24 | ("r_data", 2), 25 | ("w_data", 2), 26 | ("we", 1), 27 | ]) 28 | pins = Signal(8) 29 | gpio = FlatGPIO(pins, bus) 30 | main(gpio, ports=[pins, bus.addr, bus.r_data, bus.w_data, bus.we]) 31 | -------------------------------------------------------------------------------- /examples/basic/uart.py: -------------------------------------------------------------------------------- 1 | from nmigen import * 2 | 3 | 4 | class UART(Elaboratable): 5 | """ 6 | Parameters 7 | ---------- 8 | divisor : int 9 | Set to ``round(clk-rate / baud-rate)``. 10 | E.g. ``12e6 / 115200`` = ``104``. 11 | """ 12 | def __init__(self, divisor, data_bits=8): 13 | assert divisor >= 4 14 | 15 | self.data_bits = data_bits 16 | self.divisor = divisor 17 | 18 | self.tx_o = Signal() 19 | self.rx_i = Signal() 20 | 21 | self.tx_data = Signal(data_bits) 22 | self.tx_rdy = Signal() 23 | self.tx_ack = Signal() 24 | 25 | self.rx_data = Signal(data_bits) 26 | self.rx_err = Signal() 27 | self.rx_ovf = Signal() 28 | self.rx_rdy = Signal() 29 | self.rx_ack = Signal() 30 | 31 | def elaborate(self, platform): 32 | m = Module() 33 | 34 | tx_phase = Signal(range(self.divisor)) 35 | tx_shreg = Signal(1 + self.data_bits + 1, reset=-1) 36 | tx_count = Signal(range(len(tx_shreg) + 1)) 37 | 38 | m.d.comb += self.tx_o.eq(tx_shreg[0]) 39 | with m.If(tx_count == 0): 40 | m.d.comb += self.tx_ack.eq(1) 41 | with m.If(self.tx_rdy): 42 | m.d.sync += [ 43 | tx_shreg.eq(Cat(C(0, 1), self.tx_data, C(1, 1))), 44 | tx_count.eq(len(tx_shreg)), 45 | tx_phase.eq(self.divisor - 1), 46 | ] 47 | with m.Else(): 48 | with m.If(tx_phase != 0): 49 | m.d.sync += tx_phase.eq(tx_phase - 1) 50 | with m.Else(): 51 | m.d.sync += [ 52 | tx_shreg.eq(Cat(tx_shreg[1:], C(1, 1))), 53 | tx_count.eq(tx_count - 1), 54 | tx_phase.eq(self.divisor - 1), 55 | ] 56 | 57 | rx_phase = Signal(range(self.divisor)) 58 | rx_shreg = Signal(1 + self.data_bits + 1, reset=-1) 59 | rx_count = Signal(range(len(rx_shreg) + 1)) 60 | 61 | m.d.comb += self.rx_data.eq(rx_shreg[1:-1]) 62 | with m.If(rx_count == 0): 63 | m.d.comb += self.rx_err.eq(~(~rx_shreg[0] & rx_shreg[-1])) 64 | with m.If(~self.rx_i): 65 | with m.If(self.rx_ack | ~self.rx_rdy): 66 | m.d.sync += [ 67 | self.rx_rdy.eq(0), 68 | self.rx_ovf.eq(0), 69 | rx_count.eq(len(rx_shreg)), 70 | rx_phase.eq(self.divisor // 2), 71 | ] 72 | with m.Else(): 73 | m.d.sync += self.rx_ovf.eq(1) 74 | with m.Else(): 75 | with m.If(rx_phase != 0): 76 | m.d.sync += rx_phase.eq(rx_phase - 1) 77 | with m.Else(): 78 | m.d.sync += [ 79 | rx_shreg.eq(Cat(rx_shreg[1:], self.rx_i)), 80 | rx_count.eq(rx_count - 1), 81 | rx_phase.eq(self.divisor - 1), 82 | ] 83 | with m.If(rx_count == 1): 84 | m.d.sync += self.rx_rdy.eq(1) 85 | 86 | return m 87 | 88 | 89 | if __name__ == "__main__": 90 | uart = UART(divisor=5) 91 | ports = [ 92 | uart.tx_o, uart.rx_i, 93 | uart.tx_data, uart.tx_rdy, uart.tx_ack, 94 | uart.rx_data, uart.rx_rdy, uart.rx_err, uart.rx_ovf, uart.rx_ack 95 | ] 96 | 97 | import argparse 98 | 99 | parser = argparse.ArgumentParser() 100 | p_action = parser.add_subparsers(dest="action") 101 | p_action.add_parser("simulate") 102 | p_action.add_parser("generate") 103 | 104 | args = parser.parse_args() 105 | if args.action == "simulate": 106 | from nmigen.back.pysim import Simulator, Passive 107 | 108 | sim = Simulator(uart) 109 | sim.add_clock(1e-6) 110 | 111 | def loopback_proc(): 112 | yield Passive() 113 | while True: 114 | yield uart.rx_i.eq((yield uart.tx_o)) 115 | yield 116 | sim.add_sync_process(loopback_proc) 117 | 118 | def transmit_proc(): 119 | assert (yield uart.tx_ack) 120 | assert not (yield uart.rx_rdy) 121 | 122 | yield uart.tx_data.eq(0x5A) 123 | yield uart.tx_rdy.eq(1) 124 | yield 125 | yield uart.tx_rdy.eq(0) 126 | yield 127 | assert not (yield uart.tx_ack) 128 | 129 | for _ in range(uart.divisor * 12): yield 130 | 131 | assert (yield uart.tx_ack) 132 | assert (yield uart.rx_rdy) 133 | assert not (yield uart.rx_err) 134 | assert (yield uart.rx_data) == 0x5A 135 | 136 | yield uart.rx_ack.eq(1) 137 | yield 138 | sim.add_sync_process(transmit_proc) 139 | 140 | with sim.write_vcd("uart.vcd", "uart.gtkw"): 141 | sim.run() 142 | 143 | if args.action == "generate": 144 | from nmigen.back import verilog 145 | 146 | print(verilog.convert(uart, ports=ports)) 147 | -------------------------------------------------------------------------------- /examples/board/01_blinky.py: -------------------------------------------------------------------------------- 1 | # If the design does not create a "sync" clock domain, it is created by the nMigen build system 2 | # using the platform default clock (and default reset, if any). 3 | 4 | from nmigen import * 5 | from nmigen_boards.ice40_hx1k_blink_evn import * 6 | 7 | 8 | class Blinky(Elaboratable): 9 | def elaborate(self, platform): 10 | led = platform.request("led", 0) 11 | timer = Signal(20) 12 | 13 | m = Module() 14 | m.d.sync += timer.eq(timer + 1) 15 | m.d.comb += led.o.eq(timer[-1]) 16 | return m 17 | 18 | 19 | if __name__ == "__main__": 20 | platform = ICE40HX1KBlinkEVNPlatform() 21 | platform.build(Blinky(), do_program=True) 22 | -------------------------------------------------------------------------------- /examples/board/02_domain.py: -------------------------------------------------------------------------------- 1 | # If more control over clocking and resets is required, a "sync" clock domain could be created 2 | # explicitly, which overrides the default behavior. Any other clock domains could also be 3 | # independently created in addition to the main "sync" domain. 4 | 5 | from nmigen import * 6 | from nmigen_boards.ice40_hx1k_blink_evn import * 7 | 8 | 9 | class BlinkyWithDomain(Elaboratable): 10 | def elaborate(self, platform): 11 | clk3p3 = platform.request("clk3p3") 12 | led = platform.request("led", 0) 13 | timer = Signal(20) 14 | 15 | m = Module() 16 | m.domains.sync = ClockDomain() 17 | m.d.comb += ClockSignal().eq(clk3p3.i) 18 | m.d.sync += timer.eq(timer + 1) 19 | m.d.comb += led.o.eq(timer[-1]) 20 | return m 21 | 22 | 23 | if __name__ == "__main__": 24 | platform = ICE40HX1KBlinkEVNPlatform() 25 | platform.build(BlinkyWithDomain(), do_program=True) 26 | -------------------------------------------------------------------------------- /nmigen/__init__.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | try: 3 | __version__ = pkg_resources.get_distribution(__name__).version 4 | except pkg_resources.DistributionNotFound: 5 | pass 6 | 7 | 8 | from .hdl import * 9 | 10 | 11 | __all__ = [ 12 | "Shape", "unsigned", "signed", 13 | "Value", "Const", "C", "Mux", "Cat", "Repl", "Array", "Signal", "ClockSignal", "ResetSignal", 14 | "Module", 15 | "ClockDomain", 16 | "Elaboratable", "Fragment", "Instance", 17 | "Memory", 18 | "Record", 19 | "DomainRenamer", "ResetInserter", "EnableInserter", 20 | ] 21 | -------------------------------------------------------------------------------- /nmigen/_toolchain.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | 5 | __all__ = ["ToolNotFound", "tool_env_var", "has_tool", "require_tool"] 6 | 7 | 8 | class ToolNotFound(Exception): 9 | pass 10 | 11 | 12 | def tool_env_var(name): 13 | return name.upper().replace("-", "_") 14 | 15 | 16 | def _get_tool(name): 17 | return os.environ.get(tool_env_var(name), name) 18 | 19 | 20 | def has_tool(name): 21 | return shutil.which(_get_tool(name)) is not None 22 | 23 | 24 | def require_tool(name): 25 | env_var = tool_env_var(name) 26 | path = _get_tool(name) 27 | if shutil.which(path) is None: 28 | if env_var in os.environ: 29 | raise ToolNotFound("Could not find required tool {} in {} as " 30 | "specified via the {} environment variable". 31 | format(name, path, env_var)) 32 | else: 33 | raise ToolNotFound("Could not find required tool {} in PATH. Place " 34 | "it directly in PATH or specify path explicitly " 35 | "via the {} environment variable". 36 | format(name, env_var)) 37 | return path 38 | -------------------------------------------------------------------------------- /nmigen/_unused.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import warnings 3 | 4 | from ._utils import get_linter_option 5 | 6 | 7 | __all__ = ["UnusedMustUse", "MustUse"] 8 | 9 | 10 | class UnusedMustUse(Warning): 11 | pass 12 | 13 | 14 | class MustUse: 15 | _MustUse__silence = False 16 | _MustUse__warning = UnusedMustUse 17 | 18 | def __new__(cls, *args, src_loc_at=0, **kwargs): 19 | frame = sys._getframe(1 + src_loc_at) 20 | self = super().__new__(cls) 21 | self._MustUse__used = False 22 | self._MustUse__context = dict( 23 | filename=frame.f_code.co_filename, 24 | lineno=frame.f_lineno, 25 | source=self) 26 | return self 27 | 28 | def __del__(self): 29 | if self._MustUse__silence: 30 | return 31 | if hasattr(self, "_MustUse__used") and not self._MustUse__used: 32 | if get_linter_option(self._MustUse__context["filename"], 33 | self._MustUse__warning.__name__, bool, True): 34 | warnings.warn_explicit( 35 | "{!r} created but never used".format(self), self._MustUse__warning, 36 | **self._MustUse__context) 37 | 38 | 39 | _old_excepthook = sys.excepthook 40 | def _silence_elaboratable(type, value, traceback): 41 | # Don't show anything if the interpreter crashed; that'd just obscure the exception 42 | # traceback instead of helping. 43 | MustUse._MustUse__silence = True 44 | _old_excepthook(type, value, traceback) 45 | sys.excepthook = _silence_elaboratable 46 | -------------------------------------------------------------------------------- /nmigen/_utils.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import functools 3 | import warnings 4 | import linecache 5 | import re 6 | from collections import OrderedDict 7 | from collections.abc import Iterable 8 | from contextlib import contextmanager 9 | 10 | from .utils import * 11 | 12 | 13 | __all__ = ["flatten", "union" , "log2_int", "bits_for", "memoize", "final", "deprecated", 14 | "get_linter_options", "get_linter_option"] 15 | 16 | 17 | def flatten(i): 18 | for e in i: 19 | if isinstance(e, Iterable): 20 | yield from flatten(e) 21 | else: 22 | yield e 23 | 24 | 25 | def union(i, start=None): 26 | r = start 27 | for e in i: 28 | if r is None: 29 | r = e 30 | else: 31 | r |= e 32 | return r 33 | 34 | 35 | def memoize(f): 36 | memo = OrderedDict() 37 | @functools.wraps(f) 38 | def g(*args): 39 | if args not in memo: 40 | memo[args] = f(*args) 41 | return memo[args] 42 | return g 43 | 44 | 45 | def final(cls): 46 | def init_subclass(): 47 | raise TypeError("Subclassing {}.{} is not supported" 48 | .format(cls.__module__, cls.__name__)) 49 | cls.__init_subclass__ = init_subclass 50 | return cls 51 | 52 | 53 | def deprecated(message, stacklevel=2): 54 | def decorator(f): 55 | @functools.wraps(f) 56 | def wrapper(*args, **kwargs): 57 | warnings.warn(message, DeprecationWarning, stacklevel=stacklevel) 58 | return f(*args, **kwargs) 59 | return wrapper 60 | return decorator 61 | 62 | 63 | def _ignore_deprecated(f=None): 64 | if f is None: 65 | @contextlib.contextmanager 66 | def context_like(): 67 | with warnings.catch_warnings(): 68 | warnings.filterwarnings(action="ignore", category=DeprecationWarning) 69 | yield 70 | return context_like() 71 | else: 72 | @functools.wraps(f) 73 | def decorator_like(*args, **kwargs): 74 | with warnings.catch_warnings(): 75 | warnings.filterwarnings(action="ignore", category=DeprecationWarning) 76 | f(*args, **kwargs) 77 | return decorator_like 78 | 79 | 80 | def extend(cls): 81 | def decorator(f): 82 | if isinstance(f, property): 83 | name = f.fget.__name__ 84 | else: 85 | name = f.__name__ 86 | setattr(cls, name, f) 87 | return decorator 88 | 89 | 90 | def get_linter_options(filename): 91 | first_line = linecache.getline(filename, 1) 92 | if first_line: 93 | match = re.match(r"^#\s*nmigen:\s*((?:\w+=\w+\s*)(?:,\s*\w+=\w+\s*)*)\n$", first_line) 94 | if match: 95 | return dict(map(lambda s: s.strip().split("=", 2), match.group(1).split(","))) 96 | return dict() 97 | 98 | 99 | def get_linter_option(filename, name, type, default): 100 | options = get_linter_options(filename) 101 | if name not in options: 102 | return default 103 | 104 | option = options[name] 105 | if type is bool: 106 | if option in ("1", "yes", "enable"): 107 | return True 108 | if option in ("0", "no", "disable"): 109 | return False 110 | return default 111 | if type is int: 112 | try: 113 | return int(option, 0) 114 | except ValueError: 115 | return default 116 | assert False 117 | -------------------------------------------------------------------------------- /nmigen/asserts.py: -------------------------------------------------------------------------------- 1 | from .hdl.ast import AnyConst, AnySeq, Assert, Assume, Cover 2 | from .hdl.ast import Past, Stable, Rose, Fell, Initial 3 | -------------------------------------------------------------------------------- /nmigen/back/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-labs/nmigen/6c5afdc3c37e82854b08861e8b6d95f644b45f6e/nmigen/back/__init__.py -------------------------------------------------------------------------------- /nmigen/back/verilog.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import subprocess 4 | 5 | from .._toolchain import * 6 | from . import rtlil 7 | 8 | 9 | __all__ = ["YosysError", "convert", "convert_fragment"] 10 | 11 | 12 | class YosysError(Exception): 13 | pass 14 | 15 | 16 | def _yosys_version(): 17 | yosys_path = require_tool("yosys") 18 | version = subprocess.check_output([yosys_path, "-V"], encoding="utf-8") 19 | m = re.match(r"^Yosys ([\d.]+)(?:\+(\d+))?", version) 20 | tag, offset = m[1], m[2] or 0 21 | return tuple(map(int, tag.split("."))), offset 22 | 23 | 24 | def _convert_rtlil_text(rtlil_text, *, strip_internal_attrs=False, write_verilog_opts=()): 25 | version, offset = _yosys_version() 26 | if version < (0, 9): 27 | raise YosysError("Yosys {}.{} is not supported".format(*version)) 28 | 29 | attr_map = [] 30 | if strip_internal_attrs: 31 | attr_map.append("-remove generator") 32 | attr_map.append("-remove top") 33 | attr_map.append("-remove src") 34 | attr_map.append("-remove nmigen.hierarchy") 35 | attr_map.append("-remove nmigen.decoding") 36 | 37 | script = """ 38 | # Convert nMigen's RTLIL to readable Verilog. 39 | read_ilang <{}".format(conn, plat) 253 | for conn, plat in self.mapping.items())) 254 | 255 | def __len__(self): 256 | return len(self.mapping) 257 | 258 | def __iter__(self): 259 | for conn_pin, plat_pin in self.mapping.items(): 260 | yield "{}_{}:{}".format(self.name, self.number, conn_pin), plat_pin 261 | -------------------------------------------------------------------------------- /nmigen/build/run.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from contextlib import contextmanager 3 | from abc import ABCMeta, abstractmethod 4 | import os 5 | import sys 6 | import subprocess 7 | import tempfile 8 | import zipfile 9 | import hashlib 10 | 11 | 12 | __all__ = ["BuildPlan", "BuildProducts", "LocalBuildProducts"] 13 | 14 | 15 | class BuildPlan: 16 | def __init__(self, script): 17 | """A build plan. 18 | 19 | Parameters 20 | ---------- 21 | script : str 22 | The base name (without extension) of the script that will be executed. 23 | """ 24 | self.script = script 25 | self.files = OrderedDict() 26 | 27 | def add_file(self, filename, content): 28 | """ 29 | Add ``content``, which can be a :class:`str`` or :class:`bytes`, to the build plan 30 | as ``filename``. The file name can be a relative path with directories separated by 31 | forward slashes (``/``). 32 | """ 33 | assert isinstance(filename, str) and filename not in self.files 34 | self.files[filename] = content 35 | 36 | def digest(self, size=64): 37 | """ 38 | Compute a `digest`, a short byte sequence deterministically and uniquely identifying 39 | this build plan. 40 | """ 41 | hasher = hashlib.blake2b(digest_size=size) 42 | for filename in sorted(self.files): 43 | hasher.update(filename.encode("utf-8")) 44 | content = self.files[filename] 45 | if isinstance(content, str): 46 | content = content.encode("utf-8") 47 | hasher.update(content) 48 | hasher.update(self.script.encode("utf-8")) 49 | return hasher.digest() 50 | 51 | def archive(self, file): 52 | """ 53 | Archive files from the build plan into ``file``, which can be either a filename, or 54 | a file-like object. The produced archive is deterministic: exact same files will 55 | always produce exact same archive. 56 | """ 57 | with zipfile.ZipFile(file, "w") as archive: 58 | # Write archive members in deterministic order and with deterministic timestamp. 59 | for filename in sorted(self.files): 60 | archive.writestr(zipfile.ZipInfo(filename), self.files[filename]) 61 | 62 | def execute_local(self, root="build", *, run_script=True): 63 | """ 64 | Execute build plan using the local strategy. Files from the build plan are placed in 65 | the build root directory ``root``, and, if ``run_script`` is ``True``, the script 66 | appropriate for the platform (``{script}.bat`` on Windows, ``{script}.sh`` elsewhere) is 67 | executed in the build root. 68 | 69 | Returns :class:`LocalBuildProducts`. 70 | """ 71 | os.makedirs(root, exist_ok=True) 72 | cwd = os.getcwd() 73 | try: 74 | os.chdir(root) 75 | 76 | for filename, content in self.files.items(): 77 | filename = os.path.normpath(filename) 78 | # Just to make sure we don't accidentally overwrite anything outside of build root. 79 | assert not filename.startswith("..") 80 | dirname = os.path.dirname(filename) 81 | if dirname: 82 | os.makedirs(dirname, exist_ok=True) 83 | 84 | mode = "wt" if isinstance(content, str) else "wb" 85 | with open(filename, mode) as f: 86 | f.write(content) 87 | 88 | if run_script: 89 | if sys.platform.startswith("win32"): 90 | # Without "call", "cmd /c {}.bat" will return 0. 91 | # See https://stackoverflow.com/a/30736987 for a detailed 92 | # explanation of why, including disassembly/decompilation 93 | # of relevant code in cmd.exe. 94 | # Running the script manually from a command prompt is 95 | # unaffected- i.e. "call" is not required. 96 | subprocess.check_call(["cmd", "/c", "call {}.bat".format(self.script)]) 97 | else: 98 | subprocess.check_call(["sh", "{}.sh".format(self.script)]) 99 | 100 | return LocalBuildProducts(os.getcwd()) 101 | 102 | finally: 103 | os.chdir(cwd) 104 | 105 | def execute(self): 106 | """ 107 | Execute build plan using the default strategy. Use one of the ``execute_*`` methods 108 | explicitly to have more control over the strategy. 109 | """ 110 | return self.execute_local() 111 | 112 | 113 | class BuildProducts(metaclass=ABCMeta): 114 | @abstractmethod 115 | def get(self, filename, mode="b"): 116 | """ 117 | Extract ``filename`` from build products, and return it as a :class:`bytes` (if ``mode`` 118 | is ``"b"``) or a :class:`str` (if ``mode`` is ``"t"``). 119 | """ 120 | assert mode in ("b", "t") 121 | 122 | @contextmanager 123 | def extract(self, *filenames): 124 | """ 125 | Extract ``filenames`` from build products, place them in an OS-specific temporary file 126 | location, with the extension preserved, and delete them afterwards. This method is used 127 | as a context manager, e.g.: :: 128 | 129 | with products.extract("bitstream.bin", "programmer.cfg") \ 130 | as bitstream_filename, config_filename: 131 | subprocess.check_call(["program", "-c", config_filename, bitstream_filename]) 132 | """ 133 | files = [] 134 | try: 135 | for filename in filenames: 136 | # On Windows, a named temporary file (as created by Python) is not accessible to 137 | # others if it's still open within the Python process, so we close it and delete 138 | # it manually. 139 | file = tempfile.NamedTemporaryFile(prefix="nmigen_", suffix="_" + filename, 140 | delete=False) 141 | files.append(file) 142 | file.write(self.get(filename)) 143 | file.close() 144 | 145 | if len(files) == 0: 146 | return (yield) 147 | elif len(files) == 1: 148 | return (yield files[0].name) 149 | else: 150 | return (yield [file.name for file in files]) 151 | finally: 152 | for file in files: 153 | os.unlink(file.name) 154 | 155 | 156 | class LocalBuildProducts(BuildProducts): 157 | def __init__(self, root): 158 | # We provide no guarantees that files will be available on the local filesystem (i.e. in 159 | # any way other than through `products.get()`) in general, so downstream code must never 160 | # rely on this, even when we happen to use a local build most of the time. 161 | self.__root = root 162 | 163 | def get(self, filename, mode="b"): 164 | super().get(filename, mode) 165 | with open(os.path.join(self.__root, filename), "r" + mode) as f: 166 | return f.read() 167 | -------------------------------------------------------------------------------- /nmigen/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from .hdl.ir import Fragment 4 | from .back import rtlil, verilog, pysim 5 | 6 | 7 | __all__ = ["main"] 8 | 9 | 10 | def main_parser(parser=None): 11 | if parser is None: 12 | parser = argparse.ArgumentParser() 13 | 14 | p_action = parser.add_subparsers(dest="action") 15 | 16 | p_generate = p_action.add_parser("generate", 17 | help="generate RTLIL or Verilog from the design") 18 | p_generate.add_argument("-t", "--type", dest="generate_type", 19 | metavar="LANGUAGE", choices=["il", "v"], 20 | default="v", 21 | help="generate LANGUAGE (il for RTLIL, v for Verilog; default: %(default)s)") 22 | p_generate.add_argument("generate_file", 23 | metavar="FILE", type=argparse.FileType("w"), nargs="?", 24 | help="write generated code to FILE") 25 | 26 | p_simulate = p_action.add_parser( 27 | "simulate", help="simulate the design") 28 | p_simulate.add_argument("-v", "--vcd-file", 29 | metavar="VCD-FILE", type=argparse.FileType("w"), 30 | help="write execution trace to VCD-FILE") 31 | p_simulate.add_argument("-w", "--gtkw-file", 32 | metavar="GTKW-FILE", type=argparse.FileType("w"), 33 | help="write GTKWave configuration to GTKW-FILE") 34 | p_simulate.add_argument("-p", "--period", dest="sync_period", 35 | metavar="TIME", type=float, default=1e-6, 36 | help="set 'sync' clock domain period to TIME (default: %(default)s)") 37 | p_simulate.add_argument("-c", "--clocks", dest="sync_clocks", 38 | metavar="COUNT", type=int, required=True, 39 | help="simulate for COUNT 'sync' clock periods") 40 | 41 | return parser 42 | 43 | 44 | def main_runner(parser, args, design, platform=None, name="top", ports=()): 45 | if args.action == "generate": 46 | fragment = Fragment.get(design, platform) 47 | generate_type = args.generate_type 48 | if generate_type is None and args.generate_file: 49 | if args.generate_file.name.endswith(".v"): 50 | generate_type = "v" 51 | if args.generate_file.name.endswith(".il"): 52 | generate_type = "il" 53 | if generate_type is None: 54 | parser.error("specify file type explicitly with -t") 55 | if generate_type == "il": 56 | output = rtlil.convert(fragment, name=name, ports=ports) 57 | if generate_type == "v": 58 | output = verilog.convert(fragment, name=name, ports=ports) 59 | if args.generate_file: 60 | args.generate_file.write(output) 61 | else: 62 | print(output) 63 | 64 | if args.action == "simulate": 65 | fragment = Fragment.get(design, platform) 66 | sim = pysim.Simulator(fragment) 67 | sim.add_clock(args.sync_period) 68 | with sim.write_vcd(vcd_file=args.vcd_file, gtkw_file=args.gtkw_file, traces=ports): 69 | sim.run_until(args.sync_period * args.sync_clocks, run_passive=True) 70 | 71 | 72 | def main(*args, **kwargs): 73 | parser = main_parser() 74 | main_runner(parser, parser.parse_args(), *args, **kwargs) 75 | -------------------------------------------------------------------------------- /nmigen/compat/__init__.py: -------------------------------------------------------------------------------- 1 | from .fhdl.structure import * 2 | from .fhdl.module import * 3 | from .fhdl.specials import * 4 | from .fhdl.bitcontainer import * 5 | from .fhdl.decorators import * 6 | # from .fhdl.simplify import * 7 | 8 | from .sim import * 9 | 10 | from .genlib.record import * 11 | from .genlib.fsm import * 12 | -------------------------------------------------------------------------------- /nmigen/compat/fhdl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-labs/nmigen/6c5afdc3c37e82854b08861e8b6d95f644b45f6e/nmigen/compat/fhdl/__init__.py -------------------------------------------------------------------------------- /nmigen/compat/fhdl/bitcontainer.py: -------------------------------------------------------------------------------- 1 | from ... import utils 2 | from ...hdl import ast 3 | from ..._utils import deprecated 4 | 5 | 6 | __all__ = ["log2_int", "bits_for", "value_bits_sign"] 7 | 8 | 9 | @deprecated("instead of `log2_int`, use `nmigen.utils.log2_int`") 10 | def log2_int(n, need_pow2=True): 11 | return utils.log2_int(n, need_pow2) 12 | 13 | 14 | @deprecated("instead of `bits_for`, use `nmigen.utils.bits_for`") 15 | def bits_for(n, require_sign_bit=False): 16 | return utils.bits_for(n, require_sign_bit) 17 | 18 | 19 | @deprecated("instead of `value_bits_sign(v)`, use `v.shape()`") 20 | def value_bits_sign(v): 21 | return ast.Value.cast(v).shape() 22 | -------------------------------------------------------------------------------- /nmigen/compat/fhdl/conv_output.py: -------------------------------------------------------------------------------- 1 | from operator import itemgetter 2 | 3 | 4 | class ConvOutput: 5 | def __init__(self): 6 | self.main_source = "" 7 | self.data_files = dict() 8 | 9 | def set_main_source(self, src): 10 | self.main_source = src 11 | 12 | def add_data_file(self, filename_base, content): 13 | filename = filename_base 14 | i = 1 15 | while filename in self.data_files: 16 | parts = filename_base.split(".", maxsplit=1) 17 | parts[0] += "_" + str(i) 18 | filename = ".".join(parts) 19 | i += 1 20 | self.data_files[filename] = content 21 | return filename 22 | 23 | def __str__(self): 24 | r = self.main_source + "\n" 25 | for filename, content in sorted(self.data_files.items(), 26 | key=itemgetter(0)): 27 | r += filename + ":\n" + content 28 | return r 29 | 30 | def write(self, main_filename): 31 | with open(main_filename, "w") as f: 32 | f.write(self.main_source) 33 | for filename, content in self.data_files.items(): 34 | with open(filename, "w") as f: 35 | f.write(content) 36 | -------------------------------------------------------------------------------- /nmigen/compat/fhdl/decorators.py: -------------------------------------------------------------------------------- 1 | from ...hdl.ast import * 2 | from ...hdl.xfrm import ResetInserter as NativeResetInserter 3 | from ...hdl.xfrm import EnableInserter as NativeEnableInserter 4 | from ...hdl.xfrm import DomainRenamer as NativeDomainRenamer 5 | from ..._utils import deprecated 6 | 7 | 8 | __all__ = ["ResetInserter", "CEInserter", "ClockDomainsRenamer"] 9 | 10 | 11 | class _CompatControlInserter: 12 | _control_name = None 13 | _native_inserter = None 14 | 15 | def __init__(self, clock_domains=None): 16 | self.clock_domains = clock_domains 17 | 18 | def __call__(self, module): 19 | if self.clock_domains is None: 20 | signals = {self._control_name: ("sync", Signal(name=self._control_name))} 21 | else: 22 | def name(cd): 23 | return self._control_name + "_" + cd 24 | signals = {name(cd): (cd, Signal(name=name(cd))) for cd in self.clock_domains} 25 | for name, (cd, signal) in signals.items(): 26 | setattr(module, name, signal) 27 | return self._native_inserter(dict(signals.values()))(module) 28 | 29 | 30 | @deprecated("instead of `migen.fhdl.decorators.ResetInserter`, " 31 | "use `nmigen.hdl.xfrm.ResetInserter`; note that nMigen ResetInserter accepts " 32 | "a dict of reset signals (or a single reset signal) as an argument, not " 33 | "a set of clock domain names (or a single clock domain name)") 34 | class CompatResetInserter(_CompatControlInserter): 35 | _control_name = "reset" 36 | _native_inserter = NativeResetInserter 37 | 38 | 39 | @deprecated("instead of `migen.fhdl.decorators.CEInserter`, " 40 | "use `nmigen.hdl.xfrm.EnableInserter`; note that nMigen EnableInserter accepts " 41 | "a dict of enable signals (or a single enable signal) as an argument, not " 42 | "a set of clock domain names (or a single clock domain name)") 43 | class CompatCEInserter(_CompatControlInserter): 44 | _control_name = "ce" 45 | _native_inserter = NativeEnableInserter 46 | 47 | 48 | class CompatClockDomainsRenamer(NativeDomainRenamer): 49 | def __init__(self, cd_remapping): 50 | super().__init__(cd_remapping) 51 | 52 | 53 | ResetInserter = CompatResetInserter 54 | CEInserter = CompatCEInserter 55 | ClockDomainsRenamer = CompatClockDomainsRenamer 56 | -------------------------------------------------------------------------------- /nmigen/compat/fhdl/module.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterable 2 | 3 | from ..._utils import flatten, deprecated 4 | from ...hdl import dsl, ir 5 | 6 | 7 | __all__ = ["Module", "FinalizeError"] 8 | 9 | 10 | def _flat_list(e): 11 | if isinstance(e, Iterable): 12 | return list(flatten(e)) 13 | else: 14 | return [e] 15 | 16 | 17 | class CompatFinalizeError(Exception): 18 | pass 19 | 20 | 21 | FinalizeError = CompatFinalizeError 22 | 23 | 24 | class _CompatModuleProxy: 25 | def __init__(self, cm): 26 | object.__setattr__(self, "_cm", cm) 27 | 28 | 29 | class _CompatModuleComb(_CompatModuleProxy): 30 | @deprecated("instead of `self.comb +=`, use `m.d.comb +=`") 31 | def __iadd__(self, assigns): 32 | self._cm._module._add_statement(assigns, domain=None, depth=0, compat_mode=True) 33 | return self 34 | 35 | 36 | class _CompatModuleSyncCD: 37 | def __init__(self, cm, cd): 38 | self._cm = cm 39 | self._cd = cd 40 | 41 | @deprecated("instead of `self.sync. +=`, use `m.d. +=`") 42 | def __iadd__(self, assigns): 43 | self._cm._module._add_statement(assigns, domain=self._cd, depth=0, compat_mode=True) 44 | return self 45 | 46 | 47 | class _CompatModuleSync(_CompatModuleProxy): 48 | @deprecated("instead of `self.sync +=`, use `m.d.sync +=`") 49 | def __iadd__(self, assigns): 50 | self._cm._module._add_statement(assigns, domain="sync", depth=0, compat_mode=True) 51 | return self 52 | 53 | def __getattr__(self, name): 54 | return _CompatModuleSyncCD(self._cm, name) 55 | 56 | def __setattr__(self, name, value): 57 | if not isinstance(value, _CompatModuleSyncCD): 58 | raise AttributeError("Attempted to assign sync property - use += instead") 59 | 60 | 61 | class _CompatModuleSpecials(_CompatModuleProxy): 62 | @deprecated("instead of `self.specials. =`, use `m.submodules. =`") 63 | def __setattr__(self, name, value): 64 | self._cm._submodules.append((name, value)) 65 | setattr(self._cm, name, value) 66 | 67 | @deprecated("instead of `self.specials +=`, use `m.submodules +=`") 68 | def __iadd__(self, other): 69 | self._cm._submodules += [(None, e) for e in _flat_list(other)] 70 | return self 71 | 72 | 73 | class _CompatModuleSubmodules(_CompatModuleProxy): 74 | @deprecated("instead of `self.submodules. =`, use `m.submodules. =`") 75 | def __setattr__(self, name, value): 76 | self._cm._submodules.append((name, value)) 77 | setattr(self._cm, name, value) 78 | 79 | @deprecated("instead of `self.submodules +=`, use `m.submodules +=`") 80 | def __iadd__(self, other): 81 | self._cm._submodules += [(None, e) for e in _flat_list(other)] 82 | return self 83 | 84 | 85 | class _CompatModuleClockDomains(_CompatModuleProxy): 86 | @deprecated("instead of `self.clock_domains. =`, use `m.domains. =`") 87 | def __setattr__(self, name, value): 88 | self.__iadd__(value) 89 | setattr(self._cm, name, value) 90 | 91 | @deprecated("instead of `self.clock_domains +=`, use `m.domains +=`") 92 | def __iadd__(self, other): 93 | self._cm._module.domains += _flat_list(other) 94 | return self 95 | 96 | 97 | class CompatModule(ir.Elaboratable): 98 | _MustUse__silence = True 99 | 100 | # Actually returns another nMigen Elaboratable (nmigen.dsl.Module), not a Fragment. 101 | def get_fragment(self): 102 | assert not self.get_fragment_called 103 | self.get_fragment_called = True 104 | self.finalize() 105 | return self._module 106 | 107 | def elaborate(self, platform): 108 | if not self.get_fragment_called: 109 | self.get_fragment() 110 | return self._module 111 | 112 | def __getattr__(self, name): 113 | if name == "comb": 114 | return _CompatModuleComb(self) 115 | elif name == "sync": 116 | return _CompatModuleSync(self) 117 | elif name == "specials": 118 | return _CompatModuleSpecials(self) 119 | elif name == "submodules": 120 | return _CompatModuleSubmodules(self) 121 | elif name == "clock_domains": 122 | return _CompatModuleClockDomains(self) 123 | elif name == "finalized": 124 | self.finalized = False 125 | return self.finalized 126 | elif name == "_module": 127 | self._module = dsl.Module() 128 | return self._module 129 | elif name == "_submodules": 130 | self._submodules = [] 131 | return self._submodules 132 | elif name == "_clock_domains": 133 | self._clock_domains = [] 134 | return self._clock_domains 135 | elif name == "get_fragment_called": 136 | self.get_fragment_called = False 137 | return self.get_fragment_called 138 | else: 139 | raise AttributeError("'{}' object has no attribute '{}'" 140 | .format(type(self).__name__, name)) 141 | 142 | def finalize(self, *args, **kwargs): 143 | def finalize_submodules(): 144 | for name, submodule in self._submodules: 145 | if not hasattr(submodule, "finalize"): 146 | continue 147 | if submodule.finalized: 148 | continue 149 | submodule.finalize(*args, **kwargs) 150 | 151 | if not self.finalized: 152 | self.finalized = True 153 | finalize_submodules() 154 | self.do_finalize(*args, **kwargs) 155 | finalize_submodules() 156 | for name, submodule in self._submodules: 157 | self._module._add_submodule(submodule, name) 158 | 159 | def do_finalize(self): 160 | pass 161 | 162 | 163 | Module = CompatModule 164 | -------------------------------------------------------------------------------- /nmigen/compat/fhdl/specials.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from ..._utils import deprecated, extend 4 | from ...hdl.ast import * 5 | from ...hdl.ir import Elaboratable 6 | from ...hdl.mem import Memory as NativeMemory 7 | from ...hdl.ir import Fragment, Instance 8 | from ...hdl.dsl import Module 9 | from .module import Module as CompatModule 10 | from .structure import Signal 11 | from ...lib.io import Pin 12 | 13 | 14 | __all__ = ["TSTriple", "Instance", "Memory", "READ_FIRST", "WRITE_FIRST", "NO_CHANGE"] 15 | 16 | 17 | class TSTriple: 18 | def __init__(self, bits_sign=None, min=None, max=None, reset_o=0, reset_oe=0, reset_i=0, 19 | name=None): 20 | self.o = Signal(bits_sign, min=min, max=max, reset=reset_o, 21 | name=None if name is None else name + "_o") 22 | self.oe = Signal(reset=reset_oe, 23 | name=None if name is None else name + "_oe") 24 | self.i = Signal(bits_sign, min=min, max=max, reset=reset_i, 25 | name=None if name is None else name + "_i") 26 | 27 | def __len__(self): 28 | return len(self.o) 29 | 30 | def get_tristate(self, io): 31 | return Tristate(io, self.o, self.oe, self.i) 32 | 33 | 34 | class Tristate(Elaboratable): 35 | def __init__(self, target, o, oe, i=None): 36 | self.target = target 37 | self.o = o 38 | self.oe = oe 39 | self.i = i if i is not None else None 40 | 41 | def elaborate(self, platform): 42 | if hasattr(platform, "get_input_output"): 43 | pin = Pin(len(self.target), dir="oe" if self.i is None else "io") 44 | pin.o = self.o 45 | pin.oe = self.oe 46 | if self.i is not None: 47 | pin.i = self.i 48 | return platform.get_input_output(pin, self.target, attrs={}, invert=None) 49 | 50 | m = Module() 51 | m.d.comb += self.i.eq(self.target) 52 | m.submodules += Instance("$tribuf", 53 | p_WIDTH=len(self.target), 54 | i_EN=self.oe, 55 | i_A=self.o, 56 | o_Y=self.target, 57 | ) 58 | 59 | f = m.elaborate(platform) 60 | f.flatten = True 61 | return f 62 | 63 | 64 | (READ_FIRST, WRITE_FIRST, NO_CHANGE) = range(3) 65 | 66 | 67 | class _MemoryPort(CompatModule): 68 | def __init__(self, adr, dat_r, we=None, dat_w=None, async_read=False, re=None, 69 | we_granularity=0, mode=WRITE_FIRST, clock_domain="sync"): 70 | self.adr = adr 71 | self.dat_r = dat_r 72 | self.we = we 73 | self.dat_w = dat_w 74 | self.async_read = async_read 75 | self.re = re 76 | self.we_granularity = we_granularity 77 | self.mode = mode 78 | self.clock = ClockSignal(clock_domain) 79 | 80 | 81 | @extend(NativeMemory) 82 | @deprecated("it is not necessary or permitted to add Memory as a special or submodule") 83 | def elaborate(self, platform): 84 | return Fragment() 85 | 86 | 87 | class CompatMemory(NativeMemory, Elaboratable): 88 | def __init__(self, width, depth, init=None, name=None): 89 | super().__init__(width=width, depth=depth, init=init, name=name) 90 | 91 | @deprecated("instead of `get_port()`, use `read_port()` and `write_port()`") 92 | def get_port(self, write_capable=False, async_read=False, has_re=False, we_granularity=0, 93 | mode=WRITE_FIRST, clock_domain="sync"): 94 | if we_granularity >= self.width: 95 | warnings.warn("do not specify `we_granularity` greater than memory width, as it " 96 | "is a hard error in non-compatibility mode", 97 | DeprecationWarning, stacklevel=1) 98 | we_granularity = 0 99 | if we_granularity == 0: 100 | warnings.warn("instead of `we_granularity=0`, use `we_granularity=None` or avoid " 101 | "specifying it at all, as it is a hard error in non-compatibility mode", 102 | DeprecationWarning, stacklevel=1) 103 | we_granularity = None 104 | assert mode != NO_CHANGE 105 | rdport = self.read_port(domain="comb" if async_read else clock_domain, 106 | transparent=mode == WRITE_FIRST) 107 | rdport.addr.name = "{}_addr".format(self.name) 108 | adr = rdport.addr 109 | dat_r = rdport.data 110 | if write_capable: 111 | wrport = self.write_port(domain=clock_domain, granularity=we_granularity) 112 | wrport.addr = rdport.addr 113 | we = wrport.en 114 | dat_w = wrport.data 115 | else: 116 | we = None 117 | dat_w = None 118 | if has_re: 119 | if mode == READ_FIRST: 120 | re = rdport.en 121 | else: 122 | warnings.warn("the combination of `has_re=True` and `mode=WRITE_FIRST` has " 123 | "surprising behavior: keeping `re` low would merely latch " 124 | "the address, while the data will change with changing memory " 125 | "contents; avoid using `re` with transparent ports as it is a hard " 126 | "error in non-compatibility mode", 127 | DeprecationWarning, stacklevel=1) 128 | re = Signal() 129 | else: 130 | re = None 131 | mp = _MemoryPort(adr, dat_r, we, dat_w, 132 | async_read, re, we_granularity, mode, 133 | clock_domain) 134 | mp.submodules.rdport = rdport 135 | if write_capable: 136 | mp.submodules.wrport = wrport 137 | return mp 138 | 139 | 140 | Memory = CompatMemory 141 | -------------------------------------------------------------------------------- /nmigen/compat/fhdl/structure.py: -------------------------------------------------------------------------------- 1 | import builtins 2 | import warnings 3 | from collections import OrderedDict 4 | 5 | from ...utils import bits_for 6 | from ..._utils import deprecated, extend 7 | from ...hdl import ast 8 | from ...hdl.ast import (DUID, 9 | Shape, signed, unsigned, 10 | Value, Const, C, Mux, Slice as _Slice, Part, Cat, Repl, 11 | Signal as NativeSignal, 12 | ClockSignal, ResetSignal, 13 | Array, ArrayProxy as _ArrayProxy) 14 | from ...hdl.cd import ClockDomain 15 | 16 | 17 | __all__ = ["DUID", "wrap", "Mux", "Cat", "Replicate", "Constant", "C", "Signal", "ClockSignal", 18 | "ResetSignal", "If", "Case", "Array", "ClockDomain"] 19 | 20 | 21 | @deprecated("instead of `wrap`, use `Value.cast`") 22 | def wrap(v): 23 | return Value.cast(v) 24 | 25 | 26 | class CompatSignal(NativeSignal): 27 | def __init__(self, bits_sign=None, name=None, variable=False, reset=0, 28 | reset_less=False, name_override=None, min=None, max=None, 29 | related=None, attr=None, src_loc_at=0, **kwargs): 30 | if min is not None or max is not None: 31 | warnings.warn("instead of `Signal(min={min}, max={max})`, " 32 | "use `Signal(range({min}, {max}))`" 33 | .format(min=min or 0, max=max or 2), 34 | DeprecationWarning, stacklevel=2 + src_loc_at) 35 | 36 | if bits_sign is None: 37 | if min is None: 38 | min = 0 39 | if max is None: 40 | max = 2 41 | max -= 1 # make both bounds inclusive 42 | if min > max: 43 | raise ValueError("Lower bound {} should be less or equal to higher bound {}" 44 | .format(min, max + 1)) 45 | sign = min < 0 or max < 0 46 | if min == max: 47 | bits = 0 48 | else: 49 | bits = builtins.max(bits_for(min, sign), bits_for(max, sign)) 50 | shape = signed(bits) if sign else unsigned(bits) 51 | else: 52 | if not (min is None and max is None): 53 | raise ValueError("Only one of bits/signedness or bounds may be specified") 54 | shape = bits_sign 55 | 56 | super().__init__(shape=shape, name=name_override or name, 57 | reset=reset, reset_less=reset_less, 58 | attrs=attr, src_loc_at=1 + src_loc_at, **kwargs) 59 | 60 | 61 | Signal = CompatSignal 62 | 63 | 64 | @deprecated("instead of `Constant`, use `Const`") 65 | def Constant(value, bits_sign=None): 66 | return Const(value, bits_sign) 67 | 68 | 69 | @deprecated("instead of `Replicate`, use `Repl`") 70 | def Replicate(v, n): 71 | return Repl(v, n) 72 | 73 | 74 | @extend(Const) 75 | @property 76 | @deprecated("instead of `.nbits`, use `.width`") 77 | def nbits(self): 78 | return self.width 79 | 80 | 81 | @extend(NativeSignal) 82 | @property 83 | @deprecated("instead of `.nbits`, use `.width`") 84 | def nbits(self): 85 | return self.width 86 | 87 | 88 | @extend(NativeSignal) 89 | @NativeSignal.nbits.setter 90 | @deprecated("instead of `.nbits = x`, use `.width = x`") 91 | def nbits(self, value): 92 | self.width = value 93 | 94 | 95 | @extend(NativeSignal) 96 | @deprecated("instead of `.part`, use `.bit_select`") 97 | def part(self, offset, width): 98 | return Part(self, offset, width, src_loc_at=2) 99 | 100 | 101 | @extend(Cat) 102 | @property 103 | @deprecated("instead of `.l`, use `.parts`") 104 | def l(self): 105 | return self.parts 106 | 107 | 108 | @extend(ast.Operator) 109 | @property 110 | @deprecated("instead of `.op`, use `.operator`") 111 | def op(self): 112 | return self.operator 113 | 114 | 115 | @extend(_ArrayProxy) 116 | @property 117 | @deprecated("instead `_ArrayProxy.choices`, use `ArrayProxy.elems`") 118 | def choices(self): 119 | return self.elems 120 | 121 | 122 | class If(ast.Switch): 123 | @deprecated("instead of `If(cond, ...)`, use `with m.If(cond): ...`") 124 | def __init__(self, cond, *stmts): 125 | cond = Value.cast(cond) 126 | if len(cond) != 1: 127 | cond = cond.bool() 128 | super().__init__(cond, {("1",): ast.Statement.cast(stmts)}) 129 | 130 | @deprecated("instead of `.Elif(cond, ...)`, use `with m.Elif(cond): ...`") 131 | def Elif(self, cond, *stmts): 132 | cond = Value.cast(cond) 133 | if len(cond) != 1: 134 | cond = cond.bool() 135 | self.cases = OrderedDict((("-" + k,), v) for (k,), v in self.cases.items()) 136 | self.cases[("1" + "-" * len(self.test),)] = ast.Statement.cast(stmts) 137 | self.test = Cat(self.test, cond) 138 | return self 139 | 140 | @deprecated("instead of `.Else(...)`, use `with m.Else(): ...`") 141 | def Else(self, *stmts): 142 | self.cases[()] = ast.Statement.cast(stmts) 143 | return self 144 | 145 | 146 | class Case(ast.Switch): 147 | @deprecated("instead of `Case(test, { value: stmts })`, use `with m.Switch(test):` and " 148 | "`with m.Case(value): stmts`; instead of `\"default\": stmts`, use " 149 | "`with m.Case(): stmts`") 150 | def __init__(self, test, cases): 151 | new_cases = [] 152 | default = None 153 | for k, v in cases.items(): 154 | if isinstance(k, (bool, int)): 155 | k = Const(k) 156 | if (not isinstance(k, Const) 157 | and not (isinstance(k, str) and k == "default")): 158 | raise TypeError("Case object is not a Migen constant") 159 | if isinstance(k, str) and k == "default": 160 | default = v 161 | continue 162 | else: 163 | k = k.value 164 | new_cases.append((k, v)) 165 | if default is not None: 166 | new_cases.append((None, default)) 167 | super().__init__(test, OrderedDict(new_cases)) 168 | 169 | @deprecated("instead of `Case(...).makedefault()`, use an explicit default case: " 170 | "`with m.Case(): ...`") 171 | def makedefault(self, key=None): 172 | if key is None: 173 | for choice in self.cases.keys(): 174 | if (key is None 175 | or (isinstance(choice, str) and choice == "default") 176 | or choice > key): 177 | key = choice 178 | elif isinstance(key, str) and key == "default": 179 | key = () 180 | else: 181 | key = ("{:0{}b}".format(ast.Value.cast(key).value, len(self.test)),) 182 | stmts = self.cases[key] 183 | del self.cases[key] 184 | self.cases[()] = stmts 185 | return self 186 | -------------------------------------------------------------------------------- /nmigen/compat/fhdl/verilog.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from ...hdl.ir import Fragment 4 | from ...hdl.cd import ClockDomain 5 | from ...back import verilog 6 | from .conv_output import ConvOutput 7 | 8 | 9 | def convert(fi, ios=None, name="top", special_overrides=dict(), 10 | attr_translate=None, create_clock_domains=True, 11 | display_run=False): 12 | if display_run: 13 | warnings.warn("`display_run=True` support has been removed", 14 | DeprecationWarning, stacklevel=1) 15 | if special_overrides: 16 | warnings.warn("`special_overrides` support as well as `Special` has been removed", 17 | DeprecationWarning, stacklevel=1) 18 | # TODO: attr_translate 19 | 20 | def missing_domain(name): 21 | if create_clock_domains: 22 | return ClockDomain(name) 23 | v_output = verilog.convert( 24 | elaboratable=fi.get_fragment(), 25 | name=name, 26 | ports=ios or (), 27 | missing_domain=missing_domain 28 | ) 29 | output = ConvOutput() 30 | output.set_main_source(v_output) 31 | return output 32 | -------------------------------------------------------------------------------- /nmigen/compat/genlib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-labs/nmigen/6c5afdc3c37e82854b08861e8b6d95f644b45f6e/nmigen/compat/genlib/__init__.py -------------------------------------------------------------------------------- /nmigen/compat/genlib/cdc.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from ..._utils import deprecated 4 | from ...lib.cdc import FFSynchronizer as NativeFFSynchronizer 5 | from ...lib.cdc import PulseSynchronizer as NativePulseSynchronizer 6 | from ...hdl.ast import * 7 | from ..fhdl.module import CompatModule 8 | from ..fhdl.structure import If 9 | 10 | 11 | __all__ = ["MultiReg", "PulseSynchronizer", "GrayCounter", "GrayDecoder"] 12 | 13 | 14 | class MultiReg(NativeFFSynchronizer): 15 | def __init__(self, i, o, odomain="sync", n=2, reset=0): 16 | old_opts = [] 17 | new_opts = [] 18 | if odomain != "sync": 19 | old_opts.append(", odomain={!r}".format(odomain)) 20 | new_opts.append(", o_domain={!r}".format(odomain)) 21 | if n != 2: 22 | old_opts.append(", n={!r}".format(n)) 23 | new_opts.append(", stages={!r}".format(n)) 24 | warnings.warn("instead of `MultiReg(...{})`, use `FFSynchronizer(...{})`" 25 | .format("".join(old_opts), "".join(new_opts)), 26 | DeprecationWarning, stacklevel=2) 27 | super().__init__(i, o, o_domain=odomain, stages=n, reset=reset) 28 | self.odomain = odomain 29 | 30 | 31 | @deprecated("instead of `migen.genlib.cdc.PulseSynchronizer`, use `nmigen.lib.cdc.PulseSynchronizer`") 32 | class PulseSynchronizer(NativePulseSynchronizer): 33 | def __init__(self, idomain, odomain): 34 | super().__init__(i_domain=idomain, o_domain=odomain) 35 | 36 | 37 | @deprecated("instead of `migen.genlib.cdc.GrayCounter`, use `nmigen.lib.coding.GrayEncoder`") 38 | class GrayCounter(CompatModule): 39 | def __init__(self, width): 40 | self.ce = Signal() 41 | self.q = Signal(width) 42 | self.q_next = Signal(width) 43 | self.q_binary = Signal(width) 44 | self.q_next_binary = Signal(width) 45 | 46 | ### 47 | 48 | self.comb += [ 49 | If(self.ce, 50 | self.q_next_binary.eq(self.q_binary + 1) 51 | ).Else( 52 | self.q_next_binary.eq(self.q_binary) 53 | ), 54 | self.q_next.eq(self.q_next_binary ^ self.q_next_binary[1:]) 55 | ] 56 | self.sync += [ 57 | self.q_binary.eq(self.q_next_binary), 58 | self.q.eq(self.q_next) 59 | ] 60 | 61 | 62 | @deprecated("instead of `migen.genlib.cdc.GrayDecoder`, use `nmigen.lib.coding.GrayDecoder`") 63 | class GrayDecoder(CompatModule): 64 | def __init__(self, width): 65 | self.i = Signal(width) 66 | self.o = Signal(width, reset_less=True) 67 | 68 | # # # 69 | 70 | o_comb = Signal(width) 71 | self.comb += o_comb[-1].eq(self.i[-1]) 72 | for i in reversed(range(width-1)): 73 | self.comb += o_comb[i].eq(o_comb[i+1] ^ self.i[i]) 74 | self.sync += self.o.eq(o_comb) 75 | -------------------------------------------------------------------------------- /nmigen/compat/genlib/coding.py: -------------------------------------------------------------------------------- 1 | from ...lib.coding import * 2 | 3 | 4 | __all__ = ["Encoder", "PriorityEncoder", "Decoder", "PriorityDecoder"] 5 | -------------------------------------------------------------------------------- /nmigen/compat/genlib/fifo.py: -------------------------------------------------------------------------------- 1 | from ..._utils import deprecated, extend 2 | from ...lib.fifo import (FIFOInterface as NativeFIFOInterface, 3 | SyncFIFO as NativeSyncFIFO, SyncFIFOBuffered as NativeSyncFIFOBuffered, 4 | AsyncFIFO as NativeAsyncFIFO, AsyncFIFOBuffered as NativeAsyncFIFOBuffered) 5 | 6 | 7 | __all__ = ["_FIFOInterface", "SyncFIFO", "SyncFIFOBuffered", "AsyncFIFO", "AsyncFIFOBuffered"] 8 | 9 | 10 | class CompatFIFOInterface(NativeFIFOInterface): 11 | @deprecated("attribute `fwft` must be provided to FIFOInterface constructor") 12 | def __init__(self, width, depth): 13 | super().__init__(width=width, depth=depth, fwft=False) 14 | del self.fwft 15 | 16 | 17 | @extend(NativeFIFOInterface) 18 | @property 19 | @deprecated("instead of `fifo.din`, use `fifo.w_data`") 20 | def din(self): 21 | return self.w_data 22 | 23 | 24 | @extend(NativeFIFOInterface) 25 | @NativeFIFOInterface.din.setter 26 | @deprecated("instead of `fifo.din = x`, use `fifo.w_data = x`") 27 | def din(self, w_data): 28 | self.w_data = w_data 29 | 30 | 31 | @extend(NativeFIFOInterface) 32 | @property 33 | @deprecated("instead of `fifo.writable`, use `fifo.w_rdy`") 34 | def writable(self): 35 | return self.w_rdy 36 | 37 | 38 | @extend(NativeFIFOInterface) 39 | @NativeFIFOInterface.writable.setter 40 | @deprecated("instead of `fifo.writable = x`, use `fifo.w_rdy = x`") 41 | def writable(self, w_rdy): 42 | self.w_rdy = w_rdy 43 | 44 | 45 | @extend(NativeFIFOInterface) 46 | @property 47 | @deprecated("instead of `fifo.we`, use `fifo.w_en`") 48 | def we(self): 49 | return self.w_en 50 | 51 | 52 | @extend(NativeFIFOInterface) 53 | @NativeFIFOInterface.we.setter 54 | @deprecated("instead of `fifo.we = x`, use `fifo.w_en = x`") 55 | def we(self, w_en): 56 | self.w_en = w_en 57 | 58 | 59 | @extend(NativeFIFOInterface) 60 | @property 61 | @deprecated("instead of `fifo.dout`, use `fifo.r_data`") 62 | def dout(self): 63 | return self.r_data 64 | 65 | 66 | @extend(NativeFIFOInterface) 67 | @NativeFIFOInterface.dout.setter 68 | @deprecated("instead of `fifo.dout = x`, use `fifo.r_data = x`") 69 | def dout(self, r_data): 70 | self.r_data = r_data 71 | 72 | 73 | @extend(NativeFIFOInterface) 74 | @property 75 | @deprecated("instead of `fifo.readable`, use `fifo.r_rdy`") 76 | def readable(self): 77 | return self.r_rdy 78 | 79 | 80 | @extend(NativeFIFOInterface) 81 | @NativeFIFOInterface.readable.setter 82 | @deprecated("instead of `fifo.readable = x`, use `fifo.r_rdy = x`") 83 | def readable(self, r_rdy): 84 | self.r_rdy = r_rdy 85 | 86 | 87 | @extend(NativeFIFOInterface) 88 | @property 89 | @deprecated("instead of `fifo.re`, use `fifo.r_en`") 90 | def re(self): 91 | return self.r_en 92 | 93 | 94 | @extend(NativeFIFOInterface) 95 | @NativeFIFOInterface.re.setter 96 | @deprecated("instead of `fifo.re = x`, use `fifo.r_en = x`") 97 | def re(self, r_en): 98 | self.r_en = r_en 99 | 100 | 101 | @extend(NativeFIFOInterface) 102 | def read(self): 103 | """Read method for simulation.""" 104 | assert (yield self.r_rdy) 105 | value = (yield self.r_data) 106 | yield self.r_en.eq(1) 107 | yield 108 | yield self.r_en.eq(0) 109 | yield 110 | return value 111 | 112 | @extend(NativeFIFOInterface) 113 | def write(self, data): 114 | """Write method for simulation.""" 115 | assert (yield self.w_rdy) 116 | yield self.w_data.eq(data) 117 | yield self.w_en.eq(1) 118 | yield 119 | yield self.w_en.eq(0) 120 | yield 121 | 122 | 123 | class CompatSyncFIFO(NativeSyncFIFO): 124 | def __init__(self, width, depth, fwft=True): 125 | super().__init__(width=width, depth=depth, fwft=fwft) 126 | 127 | 128 | class CompatSyncFIFOBuffered(NativeSyncFIFOBuffered): 129 | def __init__(self, width, depth): 130 | super().__init__(width=width, depth=depth) 131 | 132 | 133 | class CompatAsyncFIFO(NativeAsyncFIFO): 134 | def __init__(self, width, depth): 135 | super().__init__(width=width, depth=depth) 136 | 137 | 138 | class CompatAsyncFIFOBuffered(NativeAsyncFIFOBuffered): 139 | def __init__(self, width, depth): 140 | super().__init__(width=width, depth=depth) 141 | 142 | 143 | _FIFOInterface = CompatFIFOInterface 144 | SyncFIFO = CompatSyncFIFO 145 | SyncFIFOBuffered = CompatSyncFIFOBuffered 146 | AsyncFIFO = CompatAsyncFIFO 147 | AsyncFIFOBuffered = CompatAsyncFIFOBuffered 148 | -------------------------------------------------------------------------------- /nmigen/compat/genlib/fsm.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from ..._utils import deprecated, _ignore_deprecated 4 | from ...hdl.xfrm import ValueTransformer, StatementTransformer 5 | from ...hdl.ast import * 6 | from ...hdl.ast import Signal as NativeSignal 7 | from ..fhdl.module import CompatModule, CompatFinalizeError 8 | from ..fhdl.structure import Signal, If, Case 9 | 10 | 11 | __all__ = ["AnonymousState", "NextState", "NextValue", "FSM"] 12 | 13 | 14 | class AnonymousState: 15 | pass 16 | 17 | 18 | class NextState(Statement): 19 | def __init__(self, state): 20 | super().__init__() 21 | self.state = state 22 | 23 | 24 | class NextValue(Statement): 25 | def __init__(self, target, value): 26 | super().__init__() 27 | self.target = target 28 | self.value = value 29 | 30 | 31 | def _target_eq(a, b): 32 | if type(a) != type(b): 33 | return False 34 | ty = type(a) 35 | if ty == Const: 36 | return a.value == b.value 37 | elif ty == NativeSignal or ty == Signal: 38 | return a is b 39 | elif ty == Cat: 40 | return all(_target_eq(x, y) for x, y in zip(a.l, b.l)) 41 | elif ty == Slice: 42 | return (_target_eq(a.value, b.value) 43 | and a.start == b.start 44 | and a.stop == b.stop) 45 | elif ty == Part: 46 | return (_target_eq(a.value, b.value) 47 | and _target_eq(a.offset == b.offset) 48 | and a.width == b.width) 49 | elif ty == ArrayProxy: 50 | return (all(_target_eq(x, y) for x, y in zip(a.choices, b.choices)) 51 | and _target_eq(a.key, b.key)) 52 | else: 53 | raise ValueError("NextValue cannot be used with target type '{}'" 54 | .format(ty)) 55 | 56 | 57 | class _LowerNext(ValueTransformer, StatementTransformer): 58 | def __init__(self, next_state_signal, encoding, aliases): 59 | self.next_state_signal = next_state_signal 60 | self.encoding = encoding 61 | self.aliases = aliases 62 | # (target, next_value_ce, next_value) 63 | self.registers = [] 64 | 65 | def _get_register_control(self, target): 66 | for x in self.registers: 67 | if _target_eq(target, x[0]): 68 | return x[1], x[2] 69 | raise KeyError 70 | 71 | def on_unknown_statement(self, node): 72 | if isinstance(node, NextState): 73 | try: 74 | actual_state = self.aliases[node.state] 75 | except KeyError: 76 | actual_state = node.state 77 | return self.next_state_signal.eq(self.encoding[actual_state]) 78 | elif isinstance(node, NextValue): 79 | try: 80 | next_value_ce, next_value = self._get_register_control(node.target) 81 | except KeyError: 82 | related = node.target if isinstance(node.target, Signal) else None 83 | next_value = Signal(node.target.shape(), 84 | name=None if related is None else "{}_fsm_next".format(related.name)) 85 | next_value_ce = Signal( 86 | name=None if related is None else "{}_fsm_next_ce".format(related.name)) 87 | self.registers.append((node.target, next_value_ce, next_value)) 88 | return next_value.eq(node.value), next_value_ce.eq(1) 89 | else: 90 | return node 91 | 92 | 93 | @deprecated("instead of `migen.genlib.fsm.FSM()`, use `with m.FSM():`; note that there is no " 94 | "replacement for `{before,after}_{entering,leaving}` and `delayed_enter` methods") 95 | class FSM(CompatModule): 96 | def __init__(self, reset_state=None): 97 | self.actions = OrderedDict() 98 | self.state_aliases = dict() 99 | self.reset_state = reset_state 100 | 101 | self.before_entering_signals = OrderedDict() 102 | self.before_leaving_signals = OrderedDict() 103 | self.after_entering_signals = OrderedDict() 104 | self.after_leaving_signals = OrderedDict() 105 | 106 | def act(self, state, *statements): 107 | if self.finalized: 108 | raise CompatFinalizeError 109 | if self.reset_state is None: 110 | self.reset_state = state 111 | if state not in self.actions: 112 | self.actions[state] = [] 113 | self.actions[state] += statements 114 | 115 | def delayed_enter(self, name, target, delay): 116 | if self.finalized: 117 | raise CompatFinalizeError 118 | if delay > 0: 119 | state = name 120 | for i in range(delay): 121 | if i == delay - 1: 122 | next_state = target 123 | else: 124 | next_state = AnonymousState() 125 | self.act(state, NextState(next_state)) 126 | state = next_state 127 | else: 128 | self.state_aliases[name] = target 129 | 130 | def ongoing(self, state): 131 | is_ongoing = Signal() 132 | self.act(state, is_ongoing.eq(1)) 133 | return is_ongoing 134 | 135 | def _get_signal(self, d, state): 136 | if state not in self.actions: 137 | self.actions[state] = [] 138 | try: 139 | return d[state] 140 | except KeyError: 141 | is_el = Signal() 142 | d[state] = is_el 143 | return is_el 144 | 145 | def before_entering(self, state): 146 | return self._get_signal(self.before_entering_signals, state) 147 | 148 | def before_leaving(self, state): 149 | return self._get_signal(self.before_leaving_signals, state) 150 | 151 | def after_entering(self, state): 152 | signal = self._get_signal(self.after_entering_signals, state) 153 | self.sync += signal.eq(self.before_entering(state)) 154 | return signal 155 | 156 | def after_leaving(self, state): 157 | signal = self._get_signal(self.after_leaving_signals, state) 158 | self.sync += signal.eq(self.before_leaving(state)) 159 | return signal 160 | 161 | @_ignore_deprecated 162 | def do_finalize(self): 163 | nstates = len(self.actions) 164 | self.encoding = dict((s, n) for n, s in enumerate(self.actions.keys())) 165 | self.decoding = {n: s for s, n in self.encoding.items()} 166 | 167 | decoder = lambda n: "{}/{}".format(self.decoding[n], n) 168 | self.state = Signal(range(nstates), reset=self.encoding[self.reset_state], decoder=decoder) 169 | self.next_state = Signal.like(self.state) 170 | 171 | for state, signal in self.before_leaving_signals.items(): 172 | encoded = self.encoding[state] 173 | self.comb += signal.eq((self.state == encoded) & ~(self.next_state == encoded)) 174 | if self.reset_state in self.after_entering_signals: 175 | self.after_entering_signals[self.reset_state].reset = 1 176 | for state, signal in self.before_entering_signals.items(): 177 | encoded = self.encoding[state] 178 | self.comb += signal.eq(~(self.state == encoded) & (self.next_state == encoded)) 179 | 180 | self._finalize_sync(self._lower_controls()) 181 | 182 | def _lower_controls(self): 183 | return _LowerNext(self.next_state, self.encoding, self.state_aliases) 184 | 185 | def _finalize_sync(self, ls): 186 | cases = dict((self.encoding[k], ls.on_statement(v)) for k, v in self.actions.items() if v) 187 | self.comb += [ 188 | self.next_state.eq(self.state), 189 | Case(self.state, cases).makedefault(self.encoding[self.reset_state]) 190 | ] 191 | self.sync += self.state.eq(self.next_state) 192 | for register, next_value_ce, next_value in ls.registers: 193 | self.sync += If(next_value_ce, register.eq(next_value)) 194 | -------------------------------------------------------------------------------- /nmigen/compat/genlib/record.py: -------------------------------------------------------------------------------- 1 | from ...tracer import * 2 | from ..fhdl.structure import * 3 | 4 | from functools import reduce 5 | from operator import or_ 6 | 7 | 8 | (DIR_NONE, DIR_S_TO_M, DIR_M_TO_S) = range(3) 9 | 10 | # Possible layout elements: 11 | # 1. (name, size) 12 | # 2. (name, size, direction) 13 | # 3. (name, sublayout) 14 | # size can be an int, or a (int, bool) tuple for signed numbers 15 | # sublayout must be a list 16 | 17 | 18 | def set_layout_parameters(layout, **layout_dict): 19 | def resolve(p): 20 | if isinstance(p, str): 21 | try: 22 | return layout_dict[p] 23 | except KeyError: 24 | return p 25 | else: 26 | return p 27 | 28 | r = [] 29 | for f in layout: 30 | if isinstance(f[1], (int, tuple, str)): # cases 1/2 31 | if len(f) == 3: 32 | r.append((f[0], resolve(f[1]), f[2])) 33 | else: 34 | r.append((f[0], resolve(f[1]))) 35 | elif isinstance(f[1], list): # case 3 36 | r.append((f[0], set_layout_parameters(f[1], **layout_dict))) 37 | else: 38 | raise TypeError 39 | return r 40 | 41 | 42 | def layout_len(layout): 43 | r = 0 44 | for f in layout: 45 | if isinstance(f[1], (int, tuple)): # cases 1/2 46 | if len(f) == 3: 47 | fname, fsize, fdirection = f 48 | else: 49 | fname, fsize = f 50 | elif isinstance(f[1], list): # case 3 51 | fname, fsublayout = f 52 | fsize = layout_len(fsublayout) 53 | else: 54 | raise TypeError 55 | if isinstance(fsize, tuple): 56 | r += fsize[0] 57 | else: 58 | r += fsize 59 | return r 60 | 61 | 62 | def layout_get(layout, name): 63 | for f in layout: 64 | if f[0] == name: 65 | return f 66 | raise KeyError(name) 67 | 68 | 69 | def layout_partial(layout, *elements): 70 | r = [] 71 | for path in elements: 72 | path_s = path.split("/") 73 | last = path_s.pop() 74 | copy_ref = layout 75 | insert_ref = r 76 | for hop in path_s: 77 | name, copy_ref = layout_get(copy_ref, hop) 78 | try: 79 | name, insert_ref = layout_get(insert_ref, hop) 80 | except KeyError: 81 | new_insert_ref = [] 82 | insert_ref.append((hop, new_insert_ref)) 83 | insert_ref = new_insert_ref 84 | insert_ref.append(layout_get(copy_ref, last)) 85 | return r 86 | 87 | 88 | class Record: 89 | def __init__(self, layout, name=None, **kwargs): 90 | try: 91 | self.name = get_var_name() 92 | except NameNotFound: 93 | self.name = "" 94 | self.layout = layout 95 | 96 | if self.name: 97 | prefix = self.name + "_" 98 | else: 99 | prefix = "" 100 | for f in self.layout: 101 | if isinstance(f[1], (int, tuple)): # cases 1/2 102 | if(len(f) == 3): 103 | fname, fsize, fdirection = f 104 | else: 105 | fname, fsize = f 106 | finst = Signal(fsize, name=prefix + fname, **kwargs) 107 | elif isinstance(f[1], list): # case 3 108 | fname, fsublayout = f 109 | finst = Record(fsublayout, prefix + fname, **kwargs) 110 | else: 111 | raise TypeError 112 | setattr(self, fname, finst) 113 | 114 | def eq(self, other): 115 | return [getattr(self, f[0]).eq(getattr(other, f[0])) 116 | for f in self.layout if hasattr(other, f[0])] 117 | 118 | def iter_flat(self): 119 | for f in self.layout: 120 | e = getattr(self, f[0]) 121 | if isinstance(e, Signal): 122 | if len(f) == 3: 123 | yield e, f[2] 124 | else: 125 | yield e, DIR_NONE 126 | elif isinstance(e, Record): 127 | yield from e.iter_flat() 128 | else: 129 | raise TypeError 130 | 131 | def flatten(self): 132 | return [signal for signal, direction in self.iter_flat()] 133 | 134 | def raw_bits(self): 135 | return Cat(*self.flatten()) 136 | 137 | def connect(self, *slaves, keep=None, omit=None): 138 | if keep is None: 139 | _keep = set([f[0] for f in self.layout]) 140 | elif isinstance(keep, list): 141 | _keep = set(keep) 142 | else: 143 | _keep = keep 144 | if omit is None: 145 | _omit = set() 146 | elif isinstance(omit, list): 147 | _omit = set(omit) 148 | else: 149 | _omit = omit 150 | 151 | _keep = _keep - _omit 152 | 153 | r = [] 154 | for f in self.layout: 155 | field = f[0] 156 | self_e = getattr(self, field) 157 | if isinstance(self_e, Signal): 158 | if field in _keep: 159 | direction = f[2] 160 | if direction == DIR_M_TO_S: 161 | r += [getattr(slave, field).eq(self_e) for slave in slaves] 162 | elif direction == DIR_S_TO_M: 163 | r.append(self_e.eq(reduce(or_, [getattr(slave, field) for slave in slaves]))) 164 | else: 165 | raise TypeError 166 | else: 167 | for slave in slaves: 168 | r += self_e.connect(getattr(slave, field), keep=keep, omit=omit) 169 | return r 170 | 171 | def connect_flat(self, *slaves): 172 | r = [] 173 | iter_slaves = [slave.iter_flat() for slave in slaves] 174 | for m_signal, m_direction in self.iter_flat(): 175 | if m_direction == DIR_M_TO_S: 176 | for iter_slave in iter_slaves: 177 | s_signal, s_direction = next(iter_slave) 178 | assert(s_direction == DIR_M_TO_S) 179 | r.append(s_signal.eq(m_signal)) 180 | elif m_direction == DIR_S_TO_M: 181 | s_signals = [] 182 | for iter_slave in iter_slaves: 183 | s_signal, s_direction = next(iter_slave) 184 | assert(s_direction == DIR_S_TO_M) 185 | s_signals.append(s_signal) 186 | r.append(m_signal.eq(reduce(or_, s_signals))) 187 | else: 188 | raise TypeError 189 | return r 190 | 191 | def __len__(self): 192 | return layout_len(self.layout) 193 | 194 | def __repr__(self): 195 | return "" 196 | -------------------------------------------------------------------------------- /nmigen/compat/genlib/resetsync.py: -------------------------------------------------------------------------------- 1 | from ..._utils import deprecated 2 | from ...lib.cdc import ResetSynchronizer as NativeResetSynchronizer 3 | 4 | 5 | __all__ = ["AsyncResetSynchronizer"] 6 | 7 | 8 | @deprecated("instead of `migen.genlib.resetsync.AsyncResetSynchronizer`, " 9 | "use `nmigen.lib.cdc.ResetSynchronizer`; note that ResetSynchronizer accepts " 10 | "a clock domain name as an argument, not a clock domain object") 11 | class CompatResetSynchronizer(NativeResetSynchronizer): 12 | def __init__(self, cd, async_reset): 13 | super().__init__(async_reset, domain=cd.name) 14 | 15 | 16 | AsyncResetSynchronizer = CompatResetSynchronizer 17 | -------------------------------------------------------------------------------- /nmigen/compat/sim/__init__.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import inspect 3 | from collections.abc import Iterable 4 | from ...hdl.cd import ClockDomain 5 | from ...back.pysim import * 6 | 7 | 8 | __all__ = ["run_simulation", "passive"] 9 | 10 | 11 | def run_simulation(fragment_or_module, generators, clocks={"sync": 10}, vcd_name=None, 12 | special_overrides={}): 13 | assert not special_overrides 14 | 15 | if hasattr(fragment_or_module, "get_fragment"): 16 | fragment = fragment_or_module.get_fragment() 17 | else: 18 | fragment = fragment_or_module 19 | 20 | if not isinstance(generators, dict): 21 | generators = {"sync": generators} 22 | fragment.domains += ClockDomain("sync") 23 | 24 | sim = Simulator(fragment) 25 | for domain, period in clocks.items(): 26 | sim.add_clock(period / 1e9, domain=domain) 27 | for domain, processes in generators.items(): 28 | def wrap(process): 29 | def wrapper(): 30 | yield from process 31 | return wrapper 32 | if isinstance(processes, Iterable) and not inspect.isgenerator(processes): 33 | for process in processes: 34 | sim.add_sync_process(wrap(process), domain=domain) 35 | else: 36 | sim.add_sync_process(wrap(processes), domain=domain) 37 | 38 | if vcd_name is not None: 39 | with sim.write_vcd(vcd_name): 40 | sim.run() 41 | else: 42 | sim.run() 43 | 44 | 45 | def passive(generator): 46 | @functools.wraps(generator) 47 | def wrapper(*args, **kwargs): 48 | yield Passive() 49 | yield from generator(*args, **kwargs) 50 | return wrapper 51 | -------------------------------------------------------------------------------- /nmigen/hdl/__init__.py: -------------------------------------------------------------------------------- 1 | from .ast import Shape, unsigned, signed 2 | from .ast import Value, Const, C, Mux, Cat, Repl, Array, Signal, ClockSignal, ResetSignal 3 | from .dsl import Module 4 | from .cd import ClockDomain 5 | from .ir import Elaboratable, Fragment, Instance 6 | from .mem import Memory 7 | from .rec import Record 8 | from .xfrm import DomainRenamer, ResetInserter, EnableInserter 9 | 10 | 11 | __all__ = [ 12 | "Shape", "unsigned", "signed", 13 | "Value", "Const", "C", "Mux", "Cat", "Repl", "Array", "Signal", "ClockSignal", "ResetSignal", 14 | "Module", 15 | "ClockDomain", 16 | "Elaboratable", "Fragment", "Instance", 17 | "Memory", 18 | "Record", 19 | "DomainRenamer", "ResetInserter", "EnableInserter", 20 | ] 21 | -------------------------------------------------------------------------------- /nmigen/hdl/cd.py: -------------------------------------------------------------------------------- 1 | from .. import tracer 2 | from .ast import Signal 3 | 4 | 5 | __all__ = ["ClockDomain", "DomainError"] 6 | 7 | 8 | class DomainError(Exception): 9 | pass 10 | 11 | 12 | class ClockDomain: 13 | """Synchronous domain. 14 | 15 | Parameters 16 | ---------- 17 | name : str or None 18 | Domain name. If ``None`` (the default) the name is inferred from the variable name this 19 | ``ClockDomain`` is assigned to (stripping any `"cd_"` prefix). 20 | reset_less : bool 21 | If ``True``, the domain does not use a reset signal. Registers within this domain are 22 | still all initialized to their reset state once, e.g. through Verilog `"initial"` 23 | statements. 24 | clock_edge : str 25 | The edge of the clock signal on which signals are sampled. Must be one of "pos" or "neg". 26 | async_reset : bool 27 | If ``True``, the domain uses an asynchronous reset, and registers within this domain 28 | are initialized to their reset state when reset level changes. Otherwise, registers 29 | are initialized to reset state at the next clock cycle when reset is asserted. 30 | local : bool 31 | If ``True``, the domain will propagate only downwards in the design hierarchy. Otherwise, 32 | the domain will propagate everywhere. 33 | 34 | Attributes 35 | ---------- 36 | clk : Signal, inout 37 | The clock for this domain. Can be driven or used to drive other signals (preferably 38 | in combinatorial context). 39 | rst : Signal or None, inout 40 | Reset signal for this domain. Can be driven or used to drive. 41 | """ 42 | 43 | @staticmethod 44 | def _name_for(domain_name, signal_name): 45 | if domain_name == "sync": 46 | return signal_name 47 | else: 48 | return "{}_{}".format(domain_name, signal_name) 49 | 50 | def __init__(self, name=None, *, clk_edge="pos", reset_less=False, async_reset=False, 51 | local=False): 52 | if name is None: 53 | try: 54 | name = tracer.get_var_name() 55 | except tracer.NameNotFound: 56 | raise ValueError("Clock domain name must be specified explicitly") 57 | if name.startswith("cd_"): 58 | name = name[3:] 59 | if name == "comb": 60 | raise ValueError("Domain '{}' may not be clocked".format(name)) 61 | 62 | if clk_edge not in ("pos", "neg"): 63 | raise ValueError("Domain clock edge must be one of 'pos' or 'neg', not {!r}" 64 | .format(clk_edge)) 65 | 66 | self.name = name 67 | 68 | self.clk = Signal(name=self._name_for(name, "clk"), src_loc_at=1) 69 | self.clk_edge = clk_edge 70 | 71 | if reset_less: 72 | self.rst = None 73 | else: 74 | self.rst = Signal(name=self._name_for(name, "rst"), src_loc_at=1) 75 | 76 | self.async_reset = async_reset 77 | 78 | self.local = local 79 | 80 | def rename(self, new_name): 81 | self.name = new_name 82 | self.clk.name = self._name_for(new_name, "clk") 83 | if self.rst is not None: 84 | self.rst.name = self._name_for(new_name, "rst") 85 | -------------------------------------------------------------------------------- /nmigen/hdl/mem.py: -------------------------------------------------------------------------------- 1 | import operator 2 | from collections import OrderedDict 3 | 4 | from .. import tracer 5 | from .ast import * 6 | from .ir import Elaboratable, Instance 7 | 8 | 9 | __all__ = ["Memory", "ReadPort", "WritePort", "DummyPort"] 10 | 11 | 12 | class Memory: 13 | """A word addressable storage. 14 | 15 | Parameters 16 | ---------- 17 | width : int 18 | Access granularity. Each storage element of this memory is ``width`` bits in size. 19 | depth : int 20 | Word count. This memory contains ``depth`` storage elements. 21 | init : list of int 22 | Initial values. At power on, each storage element in this memory is initialized to 23 | the corresponding element of ``init``, if any, or to zero otherwise. 24 | Uninitialized memories are not currently supported. 25 | name : str 26 | Name hint for this memory. If ``None`` (default) the name is inferred from the variable 27 | name this ``Signal`` is assigned to. 28 | attrs : dict 29 | Dictionary of synthesis attributes. 30 | 31 | Attributes 32 | ---------- 33 | width : int 34 | depth : int 35 | init : list of int 36 | attrs : dict 37 | """ 38 | def __init__(self, *, width, depth, init=None, name=None, attrs=None, simulate=True): 39 | if not isinstance(width, int) or width < 0: 40 | raise TypeError("Memory width must be a non-negative integer, not {!r}" 41 | .format(width)) 42 | if not isinstance(depth, int) or depth < 0: 43 | raise TypeError("Memory depth must be a non-negative integer, not {!r}" 44 | .format(depth)) 45 | 46 | self.name = name or tracer.get_var_name(depth=2, default="$memory") 47 | self.src_loc = tracer.get_src_loc() 48 | 49 | self.width = width 50 | self.depth = depth 51 | self.attrs = OrderedDict(() if attrs is None else attrs) 52 | 53 | # Array of signals for simulation. 54 | self._array = Array() 55 | if simulate: 56 | for addr in range(self.depth): 57 | self._array.append(Signal(self.width, name="{}({})" 58 | .format(name or "memory", addr))) 59 | 60 | self.init = init 61 | 62 | @property 63 | def init(self): 64 | return self._init 65 | 66 | @init.setter 67 | def init(self, new_init): 68 | self._init = [] if new_init is None else list(new_init) 69 | if len(self.init) > self.depth: 70 | raise ValueError("Memory initialization value count exceed memory depth ({} > {})" 71 | .format(len(self.init), self.depth)) 72 | 73 | try: 74 | for addr in range(len(self._array)): 75 | if addr < len(self._init): 76 | self._array[addr].reset = operator.index(self._init[addr]) 77 | else: 78 | self._array[addr].reset = 0 79 | except TypeError as e: 80 | raise TypeError("Memory initialization value at address {:x}: {}" 81 | .format(addr, e)) from None 82 | 83 | def read_port(self, *, src_loc_at=0, **kwargs): 84 | return ReadPort(self, src_loc_at=1 + src_loc_at, **kwargs) 85 | 86 | def write_port(self, *, src_loc_at=0, **kwargs): 87 | return WritePort(self, src_loc_at=1 + src_loc_at, **kwargs) 88 | 89 | def __getitem__(self, index): 90 | """Simulation only.""" 91 | return self._array[index] 92 | 93 | 94 | class ReadPort(Elaboratable): 95 | def __init__(self, memory, *, domain="sync", transparent=True, src_loc_at=0): 96 | if domain == "comb" and not transparent: 97 | raise ValueError("Read port cannot be simultaneously asynchronous and non-transparent") 98 | 99 | self.memory = memory 100 | self.domain = domain 101 | self.transparent = transparent 102 | 103 | self.addr = Signal(range(memory.depth), 104 | name="{}_r_addr".format(memory.name), src_loc_at=1 + src_loc_at) 105 | self.data = Signal(memory.width, 106 | name="{}_r_data".format(memory.name), src_loc_at=1 + src_loc_at) 107 | if self.domain != "comb" and not transparent: 108 | self.en = Signal(name="{}_r_en".format(memory.name), reset=1, 109 | src_loc_at=2 + src_loc_at) 110 | else: 111 | self.en = Const(1) 112 | 113 | def elaborate(self, platform): 114 | f = Instance("$memrd", 115 | p_MEMID=self.memory, 116 | p_ABITS=self.addr.width, 117 | p_WIDTH=self.data.width, 118 | p_CLK_ENABLE=self.domain != "comb", 119 | p_CLK_POLARITY=1, 120 | p_TRANSPARENT=self.transparent, 121 | i_CLK=ClockSignal(self.domain) if self.domain != "comb" else Const(0), 122 | i_EN=self.en, 123 | i_ADDR=self.addr, 124 | o_DATA=self.data, 125 | ) 126 | if self.domain == "comb": 127 | # Asynchronous port 128 | f.add_statements(self.data.eq(self.memory._array[self.addr])) 129 | f.add_driver(self.data) 130 | elif not self.transparent: 131 | # Synchronous, read-before-write port 132 | f.add_statements( 133 | Switch(self.en, { 134 | 1: self.data.eq(self.memory._array[self.addr]) 135 | }) 136 | ) 137 | f.add_driver(self.data, self.domain) 138 | else: 139 | # Synchronous, write-through port 140 | # This model is a bit unconventional. We model transparent ports as asynchronous ports 141 | # that are latched when the clock is high. This isn't exactly correct, but it is very 142 | # close to the correct behavior of a transparent port, and the difference should only 143 | # be observable in pathological cases of clock gating. A register is injected to 144 | # the address input to achieve the correct address-to-data latency. Also, the reset 145 | # value of the data output is forcibly set to the 0th initial value, if any--note that 146 | # many FPGAs do not guarantee this behavior! 147 | if len(self.memory.init) > 0: 148 | self.data.reset = self.memory.init[0] 149 | latch_addr = Signal.like(self.addr) 150 | f.add_statements( 151 | latch_addr.eq(self.addr), 152 | Switch(ClockSignal(self.domain), { 153 | 0: self.data.eq(self.data), 154 | 1: self.data.eq(self.memory._array[latch_addr]), 155 | }), 156 | ) 157 | f.add_driver(latch_addr, self.domain) 158 | f.add_driver(self.data) 159 | return f 160 | 161 | 162 | class WritePort(Elaboratable): 163 | def __init__(self, memory, *, domain="sync", granularity=None, src_loc_at=0): 164 | if granularity is None: 165 | granularity = memory.width 166 | if not isinstance(granularity, int) or granularity < 0: 167 | raise TypeError("Write port granularity must be a non-negative integer, not {!r}" 168 | .format(granularity)) 169 | if granularity > memory.width: 170 | raise ValueError("Write port granularity must not be greater than memory width " 171 | "({} > {})" 172 | .format(granularity, memory.width)) 173 | if memory.width // granularity * granularity != memory.width: 174 | raise ValueError("Write port granularity must divide memory width evenly") 175 | 176 | self.memory = memory 177 | self.domain = domain 178 | self.granularity = granularity 179 | 180 | self.addr = Signal(range(memory.depth), 181 | name="{}_w_addr".format(memory.name), src_loc_at=1 + src_loc_at) 182 | self.data = Signal(memory.width, 183 | name="{}_w_data".format(memory.name), src_loc_at=1 + src_loc_at) 184 | self.en = Signal(memory.width // granularity, 185 | name="{}_w_en".format(memory.name), src_loc_at=1 + src_loc_at) 186 | 187 | def elaborate(self, platform): 188 | f = Instance("$memwr", 189 | p_MEMID=self.memory, 190 | p_ABITS=self.addr.width, 191 | p_WIDTH=self.data.width, 192 | p_CLK_ENABLE=1, 193 | p_CLK_POLARITY=1, 194 | p_PRIORITY=0, 195 | i_CLK=ClockSignal(self.domain), 196 | i_EN=Cat(Repl(en_bit, self.granularity) for en_bit in self.en), 197 | i_ADDR=self.addr, 198 | i_DATA=self.data, 199 | ) 200 | if len(self.en) > 1: 201 | for index, en_bit in enumerate(self.en): 202 | offset = index * self.granularity 203 | bits = slice(offset, offset + self.granularity) 204 | write_data = self.memory._array[self.addr][bits].eq(self.data[bits]) 205 | f.add_statements(Switch(en_bit, { 1: write_data })) 206 | else: 207 | write_data = self.memory._array[self.addr].eq(self.data) 208 | f.add_statements(Switch(self.en, { 1: write_data })) 209 | for signal in self.memory._array: 210 | f.add_driver(signal, self.domain) 211 | return f 212 | 213 | 214 | class DummyPort: 215 | """Dummy memory port. 216 | 217 | This port can be used in place of either a read or a write port for testing and verification. 218 | It does not include any read/write port specific attributes, i.e. none besides ``"domain"``; 219 | any such attributes may be set manually. 220 | """ 221 | def __init__(self, *, data_width, addr_width, domain="sync", name=None, granularity=None): 222 | self.domain = domain 223 | 224 | if granularity is None: 225 | granularity = data_width 226 | if name is None: 227 | name = tracer.get_var_name(depth=2, default="dummy") 228 | 229 | self.addr = Signal(addr_width, 230 | name="{}_addr".format(name), src_loc_at=1) 231 | self.data = Signal(data_width, 232 | name="{}_data".format(name), src_loc_at=1) 233 | self.en = Signal(data_width // granularity, 234 | name="{}_en".format(name), src_loc_at=1) 235 | -------------------------------------------------------------------------------- /nmigen/hdl/rec.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from collections import OrderedDict 3 | from functools import reduce 4 | 5 | from .. import tracer 6 | from .._utils import union, deprecated 7 | from .ast import * 8 | 9 | 10 | __all__ = ["Direction", "DIR_NONE", "DIR_FANOUT", "DIR_FANIN", "Layout", "Record"] 11 | 12 | 13 | Direction = Enum('Direction', ('NONE', 'FANOUT', 'FANIN')) 14 | 15 | DIR_NONE = Direction.NONE 16 | DIR_FANOUT = Direction.FANOUT 17 | DIR_FANIN = Direction.FANIN 18 | 19 | 20 | class Layout: 21 | @staticmethod 22 | def cast(obj, *, src_loc_at=0): 23 | if isinstance(obj, Layout): 24 | return obj 25 | return Layout(obj, src_loc_at=1 + src_loc_at) 26 | 27 | def __init__(self, fields, *, src_loc_at=0): 28 | self.fields = OrderedDict() 29 | for field in fields: 30 | if not isinstance(field, tuple) or len(field) not in (2, 3): 31 | raise TypeError("Field {!r} has invalid layout: should be either " 32 | "(name, shape) or (name, shape, direction)" 33 | .format(field)) 34 | if len(field) == 2: 35 | name, shape = field 36 | direction = DIR_NONE 37 | if isinstance(shape, list): 38 | shape = Layout.cast(shape) 39 | else: 40 | name, shape, direction = field 41 | if not isinstance(direction, Direction): 42 | raise TypeError("Field {!r} has invalid direction: should be a Direction " 43 | "instance like DIR_FANIN" 44 | .format(field)) 45 | if not isinstance(name, str): 46 | raise TypeError("Field {!r} has invalid name: should be a string" 47 | .format(field)) 48 | if not isinstance(shape, Layout): 49 | try: 50 | shape = Shape.cast(shape, src_loc_at=1 + src_loc_at) 51 | except Exception as error: 52 | raise TypeError("Field {!r} has invalid shape: should be castable to Shape " 53 | "or a list of fields of a nested record" 54 | .format(field)) 55 | if name in self.fields: 56 | raise NameError("Field {!r} has a name that is already present in the layout" 57 | .format(field)) 58 | self.fields[name] = (shape, direction) 59 | 60 | def __getitem__(self, item): 61 | if isinstance(item, tuple): 62 | return Layout([ 63 | (name, shape, dir) 64 | for (name, (shape, dir)) in self.fields.items() 65 | if name in item 66 | ]) 67 | 68 | return self.fields[item] 69 | 70 | def __iter__(self): 71 | for name, (shape, dir) in self.fields.items(): 72 | yield (name, shape, dir) 73 | 74 | def __eq__(self, other): 75 | return self.fields == other.fields 76 | 77 | 78 | # Unlike most Values, Record *can* be subclassed. 79 | class Record(Value): 80 | @staticmethod 81 | def like(other, *, name=None, name_suffix=None, src_loc_at=0): 82 | if name is not None: 83 | new_name = str(name) 84 | elif name_suffix is not None: 85 | new_name = other.name + str(name_suffix) 86 | else: 87 | new_name = tracer.get_var_name(depth=2 + src_loc_at, default=None) 88 | 89 | def concat(a, b): 90 | if a is None: 91 | return b 92 | return "{}__{}".format(a, b) 93 | 94 | fields = {} 95 | for field_name in other.fields: 96 | field = other[field_name] 97 | if isinstance(field, Record): 98 | fields[field_name] = Record.like(field, name=concat(new_name, field_name), 99 | src_loc_at=1 + src_loc_at) 100 | else: 101 | fields[field_name] = Signal.like(field, name=concat(new_name, field_name), 102 | src_loc_at=1 + src_loc_at) 103 | 104 | return Record(other.layout, name=new_name, fields=fields, src_loc_at=1) 105 | 106 | def __init__(self, layout, *, name=None, fields=None, src_loc_at=0): 107 | if name is None: 108 | name = tracer.get_var_name(depth=2 + src_loc_at, default=None) 109 | 110 | self.name = name 111 | self.src_loc = tracer.get_src_loc(src_loc_at) 112 | 113 | def concat(a, b): 114 | if a is None: 115 | return b 116 | return "{}__{}".format(a, b) 117 | 118 | self.layout = Layout.cast(layout, src_loc_at=1 + src_loc_at) 119 | self.fields = OrderedDict() 120 | for field_name, field_shape, field_dir in self.layout: 121 | if fields is not None and field_name in fields: 122 | field = fields[field_name] 123 | if isinstance(field_shape, Layout): 124 | assert isinstance(field, Record) and field_shape == field.layout 125 | else: 126 | assert isinstance(field, Signal) and field_shape == field.shape() 127 | self.fields[field_name] = field 128 | else: 129 | if isinstance(field_shape, Layout): 130 | self.fields[field_name] = Record(field_shape, name=concat(name, field_name), 131 | src_loc_at=1 + src_loc_at) 132 | else: 133 | self.fields[field_name] = Signal(field_shape, name=concat(name, field_name), 134 | src_loc_at=1 + src_loc_at) 135 | 136 | def __getattr__(self, name): 137 | return self[name] 138 | 139 | def __getitem__(self, item): 140 | if isinstance(item, str): 141 | try: 142 | return self.fields[item] 143 | except KeyError: 144 | if self.name is None: 145 | reference = "Unnamed record" 146 | else: 147 | reference = "Record '{}'".format(self.name) 148 | raise AttributeError("{} does not have a field '{}'. Did you mean one of: {}?" 149 | .format(reference, item, ", ".join(self.fields))) from None 150 | elif isinstance(item, tuple): 151 | return Record(self.layout[item], fields={ 152 | field_name: field_value 153 | for field_name, field_value in self.fields.items() 154 | if field_name in item 155 | }) 156 | else: 157 | return super().__getitem__(item) 158 | 159 | def shape(self): 160 | return Shape(sum(len(f) for f in self.fields.values())) 161 | 162 | def _lhs_signals(self): 163 | return union((f._lhs_signals() for f in self.fields.values()), start=SignalSet()) 164 | 165 | def _rhs_signals(self): 166 | return union((f._rhs_signals() for f in self.fields.values()), start=SignalSet()) 167 | 168 | def __repr__(self): 169 | fields = [] 170 | for field_name, field in self.fields.items(): 171 | if isinstance(field, Signal): 172 | fields.append(field_name) 173 | else: 174 | fields.append(repr(field)) 175 | name = self.name 176 | if name is None: 177 | name = "" 178 | return "(rec {} {})".format(name, " ".join(fields)) 179 | 180 | def connect(self, *subordinates, include=None, exclude=None): 181 | def rec_name(record): 182 | if record.name is None: 183 | return "unnamed record" 184 | else: 185 | return "record '{}'".format(record.name) 186 | 187 | for field in include or {}: 188 | if field not in self.fields: 189 | raise AttributeError("Cannot include field '{}' because it is not present in {}" 190 | .format(field, rec_name(self))) 191 | for field in exclude or {}: 192 | if field not in self.fields: 193 | raise AttributeError("Cannot exclude field '{}' because it is not present in {}" 194 | .format(field, rec_name(self))) 195 | 196 | stmts = [] 197 | for field in self.fields: 198 | if include is not None and field not in include: 199 | continue 200 | if exclude is not None and field in exclude: 201 | continue 202 | 203 | shape, direction = self.layout[field] 204 | if not isinstance(shape, Layout) and direction == DIR_NONE: 205 | raise TypeError("Cannot connect field '{}' of {} because it does not have " 206 | "a direction" 207 | .format(field, rec_name(self))) 208 | 209 | item = self.fields[field] 210 | subord_items = [] 211 | for subord in subordinates: 212 | if field not in subord.fields: 213 | raise AttributeError("Cannot connect field '{}' of {} to subordinate {} " 214 | "because the subordinate record does not have this field" 215 | .format(field, rec_name(self), rec_name(subord))) 216 | subord_items.append(subord.fields[field]) 217 | 218 | if isinstance(shape, Layout): 219 | sub_include = include[field] if include and field in include else None 220 | sub_exclude = exclude[field] if exclude and field in exclude else None 221 | stmts += item.connect(*subord_items, include=sub_include, exclude=sub_exclude) 222 | else: 223 | if direction == DIR_FANOUT: 224 | stmts += [sub_item.eq(item) for sub_item in subord_items] 225 | if direction == DIR_FANIN: 226 | stmts += [item.eq(reduce(lambda a, b: a | b, subord_items))] 227 | 228 | return stmts 229 | -------------------------------------------------------------------------------- /nmigen/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-labs/nmigen/6c5afdc3c37e82854b08861e8b6d95f644b45f6e/nmigen/lib/__init__.py -------------------------------------------------------------------------------- /nmigen/lib/coding.py: -------------------------------------------------------------------------------- 1 | """Encoders and decoders between binary and one-hot representation.""" 2 | 3 | from .. import * 4 | 5 | 6 | __all__ = [ 7 | "Encoder", "Decoder", 8 | "PriorityEncoder", "PriorityDecoder", 9 | "GrayEncoder", "GrayDecoder", 10 | ] 11 | 12 | 13 | class Encoder(Elaboratable): 14 | """Encode one-hot to binary. 15 | 16 | If one bit in ``i`` is asserted, ``n`` is low and ``o`` indicates the asserted bit. 17 | Otherwise, ``n`` is high and ``o`` is ``0``. 18 | 19 | Parameters 20 | ---------- 21 | width : int 22 | Bit width of the input 23 | 24 | Attributes 25 | ---------- 26 | i : Signal(width), in 27 | One-hot input. 28 | o : Signal(range(width)), out 29 | Encoded binary. 30 | n : Signal, out 31 | Invalid: either none or multiple input bits are asserted. 32 | """ 33 | def __init__(self, width): 34 | self.width = width 35 | 36 | self.i = Signal(width) 37 | self.o = Signal(range(width)) 38 | self.n = Signal() 39 | 40 | def elaborate(self, platform): 41 | m = Module() 42 | with m.Switch(self.i): 43 | for j in range(self.width): 44 | with m.Case(1 << j): 45 | m.d.comb += self.o.eq(j) 46 | with m.Case(): 47 | m.d.comb += self.n.eq(1) 48 | return m 49 | 50 | 51 | class PriorityEncoder(Elaboratable): 52 | """Priority encode requests to binary. 53 | 54 | If any bit in ``i`` is asserted, ``n`` is low and ``o`` indicates the least significant 55 | asserted bit. 56 | Otherwise, ``n`` is high and ``o`` is ``0``. 57 | 58 | Parameters 59 | ---------- 60 | width : int 61 | Bit width of the input. 62 | 63 | Attributes 64 | ---------- 65 | i : Signal(width), in 66 | Input requests. 67 | o : Signal(range(width)), out 68 | Encoded binary. 69 | n : Signal, out 70 | Invalid: no input bits are asserted. 71 | """ 72 | def __init__(self, width): 73 | self.width = width 74 | 75 | self.i = Signal(width) 76 | self.o = Signal(range(width)) 77 | self.n = Signal() 78 | 79 | def elaborate(self, platform): 80 | m = Module() 81 | for j in reversed(range(self.width)): 82 | with m.If(self.i[j]): 83 | m.d.comb += self.o.eq(j) 84 | m.d.comb += self.n.eq(self.i == 0) 85 | return m 86 | 87 | 88 | class Decoder(Elaboratable): 89 | """Decode binary to one-hot. 90 | 91 | If ``n`` is low, only the ``i``th bit in ``o`` is asserted. 92 | If ``n`` is high, ``o`` is ``0``. 93 | 94 | Parameters 95 | ---------- 96 | width : int 97 | Bit width of the output. 98 | 99 | Attributes 100 | ---------- 101 | i : Signal(range(width)), in 102 | Input binary. 103 | o : Signal(width), out 104 | Decoded one-hot. 105 | n : Signal, in 106 | Invalid, no output bits are to be asserted. 107 | """ 108 | def __init__(self, width): 109 | self.width = width 110 | 111 | self.i = Signal(range(width)) 112 | self.n = Signal() 113 | self.o = Signal(width) 114 | 115 | def elaborate(self, platform): 116 | m = Module() 117 | with m.Switch(self.i): 118 | for j in range(len(self.o)): 119 | with m.Case(j): 120 | m.d.comb += self.o.eq(1 << j) 121 | with m.If(self.n): 122 | m.d.comb += self.o.eq(0) 123 | return m 124 | 125 | 126 | class PriorityDecoder(Decoder): 127 | """Decode binary to priority request. 128 | 129 | Identical to :class:`Decoder`. 130 | """ 131 | 132 | 133 | class GrayEncoder(Elaboratable): 134 | """Encode binary to Gray code. 135 | 136 | Parameters 137 | ---------- 138 | width : int 139 | Bit width. 140 | 141 | Attributes 142 | ---------- 143 | i : Signal(width), in 144 | Input natural binary. 145 | o : Signal(width), out 146 | Encoded Gray code. 147 | """ 148 | def __init__(self, width): 149 | self.width = width 150 | 151 | self.i = Signal(width) 152 | self.o = Signal(width) 153 | 154 | def elaborate(self, platform): 155 | m = Module() 156 | m.d.comb += self.o.eq(self.i ^ self.i[1:]) 157 | return m 158 | 159 | 160 | class GrayDecoder(Elaboratable): 161 | """Decode Gray code to binary. 162 | 163 | Parameters 164 | ---------- 165 | width : int 166 | Bit width. 167 | 168 | Attributes 169 | ---------- 170 | i : Signal(width), in 171 | Input Gray code. 172 | o : Signal(width), out 173 | Decoded natural binary. 174 | """ 175 | def __init__(self, width): 176 | self.width = width 177 | 178 | self.i = Signal(width) 179 | self.o = Signal(width) 180 | 181 | def elaborate(self, platform): 182 | m = Module() 183 | m.d.comb += self.o[-1].eq(self.i[-1]) 184 | for i in reversed(range(self.width - 1)): 185 | m.d.comb += self.o[i].eq(self.o[i + 1] ^ self.i[i]) 186 | return m 187 | -------------------------------------------------------------------------------- /nmigen/lib/io.py: -------------------------------------------------------------------------------- 1 | from .. import * 2 | from ..hdl.rec import * 3 | 4 | 5 | __all__ = ["pin_layout", "Pin"] 6 | 7 | 8 | def pin_layout(width, dir, xdr=0): 9 | """ 10 | Layout of the platform interface of a pin or several pins, which may be used inside 11 | user-defined records. 12 | 13 | See :class:`Pin` for details. 14 | """ 15 | if not isinstance(width, int) or width < 1: 16 | raise TypeError("Width must be a positive integer, not {!r}" 17 | .format(width)) 18 | if dir not in ("i", "o", "oe", "io"): 19 | raise TypeError("Direction must be one of \"i\", \"o\", \"io\", or \"oe\", not {!r}""" 20 | .format(dir)) 21 | if not isinstance(xdr, int) or xdr < 0: 22 | raise TypeError("Gearing ratio must be a non-negative integer, not {!r}" 23 | .format(xdr)) 24 | 25 | fields = [] 26 | if dir in ("i", "io"): 27 | if xdr > 0: 28 | fields.append(("i_clk", 1)) 29 | if xdr in (0, 1): 30 | fields.append(("i", width)) 31 | else: 32 | for n in range(xdr): 33 | fields.append(("i{}".format(n), width)) 34 | if dir in ("o", "oe", "io"): 35 | if xdr > 0: 36 | fields.append(("o_clk", 1)) 37 | if xdr in (0, 1): 38 | fields.append(("o", width)) 39 | else: 40 | for n in range(xdr): 41 | fields.append(("o{}".format(n), width)) 42 | if dir in ("oe", "io"): 43 | fields.append(("oe", 1)) 44 | return Layout(fields) 45 | 46 | 47 | class Pin(Record): 48 | """ 49 | An interface to an I/O buffer or a group of them that provides uniform access to input, output, 50 | or tristate buffers that may include a 1:n gearbox. (A 1:2 gearbox is typically called "DDR".) 51 | 52 | A :class:`Pin` is identical to a :class:`Record` that uses the corresponding :meth:`pin_layout` 53 | except that it allos accessing the parameters like ``width`` as attributes. It is legal to use 54 | a plain :class:`Record` anywhere a :class:`Pin` is used, provided that these attributes are 55 | not necessary. 56 | 57 | Parameters 58 | ---------- 59 | width : int 60 | Width of the ``i``/``iN`` and ``o``/``oN`` signals. 61 | dir : ``"i"``, ``"o"``, ``"io"``, ``"oe"`` 62 | Direction of the buffers. If ``"i"`` is specified, only the ``i``/``iN`` signals are 63 | present. If ``"o"`` is specified, only the ``o``/``oN`` signals are present. If ``"oe"`` is 64 | specified, the ``o``/``oN`` signals are present, and an ``oe`` signal is present. 65 | If ``"io"`` is specified, both the ``i``/``iN`` and ``o``/``oN`` signals are present, and 66 | an ``oe`` signal is present. 67 | xdr : int 68 | Gearbox ratio. If equal to 0, the I/O buffer is combinatorial, and only ``i``/``o`` 69 | signals are present. If equal to 1, the I/O buffer is SDR, and only ``i``/``o`` signals are 70 | present. If greater than 1, the I/O buffer includes a gearbox, and ``iN``/``oN`` signals 71 | are present instead, where ``N in range(0, N)``. For example, if ``xdr=2``, the I/O buffer 72 | is DDR; the signal ``i0`` reflects the value at the rising edge, and the signal ``i1`` 73 | reflects the value at the falling edge. 74 | name : str 75 | Name of the underlying record. 76 | 77 | Attributes 78 | ---------- 79 | i_clk: 80 | I/O buffer input clock. Synchronizes `i*`. Present if ``xdr`` is nonzero. 81 | i : Signal, out 82 | I/O buffer input, without gearing. Present if ``dir="i"`` or ``dir="io"``, and ``xdr`` is 83 | equal to 0 or 1. 84 | i0, i1, ... : Signal, out 85 | I/O buffer inputs, with gearing. Present if ``dir="i"`` or ``dir="io"``, and ``xdr`` is 86 | greater than 1. 87 | o_clk: 88 | I/O buffer output clock. Synchronizes `o*`, including `oe`. Present if ``xdr`` is nonzero. 89 | o : Signal, in 90 | I/O buffer output, without gearing. Present if ``dir="o"`` or ``dir="io"``, and ``xdr`` is 91 | equal to 0 or 1. 92 | o0, o1, ... : Signal, in 93 | I/O buffer outputs, with gearing. Present if ``dir="o"`` or ``dir="io"``, and ``xdr`` is 94 | greater than 1. 95 | oe : Signal, in 96 | I/O buffer output enable. Present if ``dir="io"`` or ``dir="oe"``. Buffers generally 97 | cannot change direction more than once per cycle, so at most one output enable signal 98 | is present. 99 | """ 100 | def __init__(self, width, dir, *, xdr=0, name=None, src_loc_at=0): 101 | self.width = width 102 | self.dir = dir 103 | self.xdr = xdr 104 | 105 | super().__init__(pin_layout(self.width, self.dir, self.xdr), 106 | name=name, src_loc_at=src_loc_at + 1) 107 | -------------------------------------------------------------------------------- /nmigen/rpc.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | import importlib 5 | 6 | from .hdl import Signal, Record, Elaboratable 7 | from .back import rtlil 8 | 9 | 10 | __all__ = ["main"] 11 | 12 | 13 | def _collect_modules(names): 14 | modules = {} 15 | for name in names: 16 | py_module_name, py_class_name = name.rsplit(".", 1) 17 | py_module = importlib.import_module(py_module_name) 18 | if py_class_name == "*": 19 | for py_class_name in py_module.__all__: 20 | py_class = py_module.__dict__[py_class_name] 21 | if not issubclass(py_class, Elaboratable): 22 | continue 23 | modules["{}.{}".format(py_module_name, py_class_name)] = py_class 24 | else: 25 | py_class = py_module.__dict__[py_class_name] 26 | if not isinstance(py_class, type) or not issubclass(py_class, Elaboratable): 27 | raise TypeError("{}.{} is not a class inheriting from Elaboratable" 28 | .format(py_module_name, py_class_name)) 29 | modules[name] = py_class 30 | return modules 31 | 32 | 33 | def _serve_yosys(modules): 34 | while True: 35 | request_json = sys.stdin.readline() 36 | if not request_json: break 37 | request = json.loads(request_json) 38 | 39 | if request["method"] == "modules": 40 | response = {"modules": list(modules.keys())} 41 | 42 | elif request["method"] == "derive": 43 | module_name = request["module"] 44 | 45 | args, kwargs = [], {} 46 | for parameter_name, parameter in request["parameters"].items(): 47 | if parameter["type"] == "unsigned": 48 | parameter_value = int(parameter["value"], 2) 49 | elif parameter["type"] == "signed": 50 | width = len(parameter["value"]) 51 | parameter_value = int(parameter["value"], 2) 52 | if parameter_value & (1 << (width - 1)): 53 | parameter_value = -((1 << width) - value) 54 | elif parameter["type"] == "string": 55 | parameter_value = parameter["value"] 56 | elif parameter["type"] == "real": 57 | parameter_value = float(parameter["value"]) 58 | else: 59 | raise NotImplementedError("Unrecognized parameter type {}" 60 | .format(parameter_name)) 61 | if parameter_name.startswith("$"): 62 | index = int(parameter_name[1:]) 63 | while len(args) < index: 64 | args.append(None) 65 | args[index] = parameter_value 66 | if parameter_name.startswith("\\"): 67 | kwargs[parameter_name[1:]] = parameter_value 68 | 69 | try: 70 | elaboratable = modules[module_name](*args, **kwargs) 71 | ports = [] 72 | # By convention, any public attribute that is a Signal or a Record is 73 | # considered a port. 74 | for port_name, port in vars(elaboratable).items(): 75 | if not port_name.startswith("_") and isinstance(port, (Signal, Record)): 76 | ports += port._lhs_signals() 77 | rtlil_text = rtlil.convert(elaboratable, name=module_name, ports=ports) 78 | response = {"frontend": "ilang", "source": rtlil_text} 79 | except Exception as error: 80 | response = {"error": "{}: {}".format(type(error).__name__, str(error))} 81 | 82 | else: 83 | return {"error": "Unrecognized method {!r}".format(request["method"])} 84 | 85 | sys.stdout.write(json.dumps(response)) 86 | sys.stdout.write("\n") 87 | sys.stdout.flush() 88 | 89 | 90 | def main(): 91 | parser = argparse.ArgumentParser(description=r""" 92 | The nMigen RPC server allows a HDL synthesis program to request an nMigen module to 93 | be elaborated on demand using the parameters it provides. For example, using Yosys together 94 | with the nMigen RPC server allows instantiating parametric nMigen modules directly 95 | from Verilog. 96 | """) 97 | def add_modules_arg(parser): 98 | parser.add_argument("modules", metavar="MODULE", type=str, nargs="+", 99 | help="import and provide MODULES") 100 | protocols = parser.add_subparsers(metavar="PROTOCOL", dest="protocol", required=True) 101 | protocol_yosys = protocols.add_parser("yosys", help="use Yosys JSON-based RPC protocol") 102 | add_modules_arg(protocol_yosys) 103 | 104 | args = parser.parse_args() 105 | modules = _collect_modules(args.modules) 106 | if args.protocol == "yosys": 107 | _serve_yosys(modules) 108 | 109 | 110 | if __name__ == "__main__": 111 | main() 112 | -------------------------------------------------------------------------------- /nmigen/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-labs/nmigen/6c5afdc3c37e82854b08861e8b6d95f644b45f6e/nmigen/test/__init__.py -------------------------------------------------------------------------------- /nmigen/test/compat/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-labs/nmigen/6c5afdc3c37e82854b08861e8b6d95f644b45f6e/nmigen/test/compat/__init__.py -------------------------------------------------------------------------------- /nmigen/test/compat/support.py: -------------------------------------------------------------------------------- 1 | from ..._utils import _ignore_deprecated 2 | from ...compat import * 3 | from ...compat.fhdl import verilog 4 | 5 | 6 | class SimCase: 7 | def setUp(self, *args, **kwargs): 8 | with _ignore_deprecated(): 9 | self.tb = self.TestBench(*args, **kwargs) 10 | 11 | def test_to_verilog(self): 12 | verilog.convert(self.tb) 13 | 14 | def run_with(self, generator): 15 | with _ignore_deprecated(): 16 | run_simulation(self.tb, generator) 17 | -------------------------------------------------------------------------------- /nmigen/test/compat/test_coding.py: -------------------------------------------------------------------------------- 1 | # nmigen: UnusedElaboratable=no 2 | 3 | import unittest 4 | 5 | from ...compat import * 6 | from ...compat.genlib.coding import * 7 | 8 | from .support import SimCase 9 | 10 | 11 | class EncCase(SimCase, unittest.TestCase): 12 | class TestBench(Module): 13 | def __init__(self): 14 | self.submodules.dut = Encoder(8) 15 | 16 | def test_sizes(self): 17 | self.assertEqual(len(self.tb.dut.i), 8) 18 | self.assertEqual(len(self.tb.dut.o), 3) 19 | self.assertEqual(len(self.tb.dut.n), 1) 20 | 21 | def test_run_sequence(self): 22 | seq = list(range(1<<8)) 23 | def gen(): 24 | for _ in range(256): 25 | if seq: 26 | yield self.tb.dut.i.eq(seq.pop(0)) 27 | yield 28 | if (yield self.tb.dut.n): 29 | self.assertNotIn((yield self.tb.dut.i), [1< 0: 58 | self.assertEqual(i & 1<<(o - 1), 0) 59 | self.assertGreaterEqual(i, 1< 0: 114 | self.assertEqual(i & 1<<(o - 1), 0) 115 | self.assertGreaterEqual(i, 1< q, 14 | lambda p, q: p >= q, 15 | lambda p, q: p < q, 16 | lambda p, q: p <= q, 17 | lambda p, q: p == q, 18 | lambda p, q: p != q, 19 | ] 20 | self.vals = [] 21 | for asign in 1, -1: 22 | for bsign in 1, -1: 23 | for f in comps: 24 | r = Signal() 25 | r0 = f(asign*self.a, bsign*self.b) 26 | self.comb += r.eq(r0) 27 | self.vals.append((asign, bsign, f, r, r0.op)) 28 | 29 | def test_comparisons(self): 30 | def gen(): 31 | for i in range(-4, 4): 32 | yield self.tb.a.eq(i) 33 | yield self.tb.b.eq(i) 34 | yield 35 | a = yield self.tb.a 36 | b = yield self.tb.b 37 | for asign, bsign, f, r, op in self.tb.vals: 38 | r, r0 = (yield r), f(asign*a, bsign*b) 39 | self.assertEqual(r, int(r0), 40 | "got {}, want {}*{} {} {}*{} = {}".format( 41 | r, asign, a, op, bsign, b, r0)) 42 | self.run_with(gen()) 43 | -------------------------------------------------------------------------------- /nmigen/test/compat/test_size.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from ..._utils import _ignore_deprecated 4 | from ...compat import * 5 | 6 | 7 | def _same_slices(a, b): 8 | return a.value is b.value and a.start == b.start and a.stop == b.stop 9 | 10 | 11 | class SignalSizeCase(unittest.TestCase): 12 | def setUp(self): 13 | self.i = C(0xaa) 14 | self.j = C(-127) 15 | with _ignore_deprecated(): 16 | self.s = Signal((13, True)) 17 | 18 | def test_len(self): 19 | self.assertEqual(len(self.s), 13) 20 | self.assertEqual(len(self.i), 8) 21 | self.assertEqual(len(self.j), 8) 22 | -------------------------------------------------------------------------------- /nmigen/test/test_build_plat.py: -------------------------------------------------------------------------------- 1 | from .. import * 2 | from ..build.plat import * 3 | from .utils import * 4 | 5 | 6 | class MockPlatform(Platform): 7 | resources = [] 8 | connectors = [] 9 | 10 | required_tools = [] 11 | 12 | def toolchain_prepare(self, fragment, name, **kwargs): 13 | raise NotImplementedError 14 | 15 | 16 | class PlatformTestCase(FHDLTestCase): 17 | def setUp(self): 18 | self.platform = MockPlatform() 19 | 20 | def test_add_file_str(self): 21 | self.platform.add_file("x.txt", "foo") 22 | self.assertEqual(self.platform.extra_files["x.txt"], "foo") 23 | 24 | def test_add_file_bytes(self): 25 | self.platform.add_file("x.txt", b"foo") 26 | self.assertEqual(self.platform.extra_files["x.txt"], b"foo") 27 | 28 | def test_add_file_exact_duplicate(self): 29 | self.platform.add_file("x.txt", b"foo") 30 | self.platform.add_file("x.txt", b"foo") 31 | 32 | def test_add_file_io(self): 33 | with open(__file__) as f: 34 | self.platform.add_file("x.txt", f) 35 | with open(__file__) as f: 36 | self.assertEqual(self.platform.extra_files["x.txt"], f.read()) 37 | 38 | def test_add_file_wrong_filename(self): 39 | with self.assertRaises(TypeError, 40 | msg="File name must be a string, not 1"): 41 | self.platform.add_file(1, "") 42 | 43 | def test_add_file_wrong_contents(self): 44 | with self.assertRaises(TypeError, 45 | msg="File contents must be str, bytes, or a file-like object, not 1"): 46 | self.platform.add_file("foo", 1) 47 | 48 | def test_add_file_wrong_duplicate(self): 49 | self.platform.add_file("foo", "") 50 | with self.assertRaises(ValueError, 51 | msg="File 'foo' already exists"): 52 | self.platform.add_file("foo", "bar") 53 | -------------------------------------------------------------------------------- /nmigen/test/test_compat.py: -------------------------------------------------------------------------------- 1 | from ..hdl.ir import Fragment 2 | from ..compat import * 3 | from .utils import * 4 | 5 | 6 | class CompatTestCase(FHDLTestCase): 7 | def test_fragment_get(self): 8 | m = Module() 9 | f = Fragment.get(m, platform=None) 10 | -------------------------------------------------------------------------------- /nmigen/test/test_examples.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | from pathlib import Path 4 | 5 | from .utils import * 6 | 7 | 8 | def example_test(name): 9 | path = (Path(__file__).parent / ".." / ".." / "examples" / name).resolve() 10 | def test_function(self): 11 | subprocess.check_call([sys.executable, str(path), "generate"], stdout=subprocess.DEVNULL) 12 | return test_function 13 | 14 | 15 | class ExamplesTestCase(FHDLTestCase): 16 | test_alu = example_test("basic/alu.py") 17 | test_alu_hier = example_test("basic/alu_hier.py") 18 | test_arst = example_test("basic/arst.py") 19 | test_cdc = example_test("basic/cdc.py") 20 | test_ctr = example_test("basic/ctr.py") 21 | test_ctr_en = example_test("basic/ctr_en.py") 22 | test_fsm = example_test("basic/fsm.py") 23 | test_gpio = example_test("basic/gpio.py") 24 | test_inst = example_test("basic/inst.py") 25 | test_mem = example_test("basic/mem.py") 26 | test_pmux = example_test("basic/pmux.py") 27 | test_por = example_test("basic/por.py") 28 | test_uart = example_test("basic/uart.py") 29 | -------------------------------------------------------------------------------- /nmigen/test/test_hdl_cd.py: -------------------------------------------------------------------------------- 1 | from ..hdl.cd import * 2 | from .utils import * 3 | 4 | 5 | class ClockDomainTestCase(FHDLTestCase): 6 | def test_name(self): 7 | sync = ClockDomain() 8 | self.assertEqual(sync.name, "sync") 9 | self.assertEqual(sync.clk.name, "clk") 10 | self.assertEqual(sync.rst.name, "rst") 11 | self.assertEqual(sync.local, False) 12 | pix = ClockDomain() 13 | self.assertEqual(pix.name, "pix") 14 | self.assertEqual(pix.clk.name, "pix_clk") 15 | self.assertEqual(pix.rst.name, "pix_rst") 16 | cd_pix = ClockDomain() 17 | self.assertEqual(pix.name, "pix") 18 | dom = [ClockDomain("foo")][0] 19 | self.assertEqual(dom.name, "foo") 20 | with self.assertRaises(ValueError, 21 | msg="Clock domain name must be specified explicitly"): 22 | ClockDomain() 23 | cd_reset = ClockDomain(local=True) 24 | self.assertEqual(cd_reset.local, True) 25 | 26 | def test_edge(self): 27 | sync = ClockDomain() 28 | self.assertEqual(sync.clk_edge, "pos") 29 | sync = ClockDomain(clk_edge="pos") 30 | self.assertEqual(sync.clk_edge, "pos") 31 | sync = ClockDomain(clk_edge="neg") 32 | self.assertEqual(sync.clk_edge, "neg") 33 | 34 | def test_edge_wrong(self): 35 | with self.assertRaises(ValueError, 36 | msg="Domain clock edge must be one of 'pos' or 'neg', not 'xxx'"): 37 | ClockDomain("sync", clk_edge="xxx") 38 | 39 | def test_with_reset(self): 40 | pix = ClockDomain() 41 | self.assertIsNotNone(pix.clk) 42 | self.assertIsNotNone(pix.rst) 43 | self.assertFalse(pix.async_reset) 44 | 45 | def test_without_reset(self): 46 | pix = ClockDomain(reset_less=True) 47 | self.assertIsNotNone(pix.clk) 48 | self.assertIsNone(pix.rst) 49 | self.assertFalse(pix.async_reset) 50 | 51 | def test_async_reset(self): 52 | pix = ClockDomain(async_reset=True) 53 | self.assertIsNotNone(pix.clk) 54 | self.assertIsNotNone(pix.rst) 55 | self.assertTrue(pix.async_reset) 56 | 57 | def test_rename(self): 58 | sync = ClockDomain() 59 | self.assertEqual(sync.name, "sync") 60 | self.assertEqual(sync.clk.name, "clk") 61 | self.assertEqual(sync.rst.name, "rst") 62 | sync.rename("pix") 63 | self.assertEqual(sync.name, "pix") 64 | self.assertEqual(sync.clk.name, "pix_clk") 65 | self.assertEqual(sync.rst.name, "pix_rst") 66 | 67 | def test_rename_reset_less(self): 68 | sync = ClockDomain(reset_less=True) 69 | self.assertEqual(sync.name, "sync") 70 | self.assertEqual(sync.clk.name, "clk") 71 | sync.rename("pix") 72 | self.assertEqual(sync.name, "pix") 73 | self.assertEqual(sync.clk.name, "pix_clk") 74 | 75 | def test_wrong_name_comb(self): 76 | with self.assertRaises(ValueError, 77 | msg="Domain 'comb' may not be clocked"): 78 | comb = ClockDomain() 79 | -------------------------------------------------------------------------------- /nmigen/test/test_hdl_mem.py: -------------------------------------------------------------------------------- 1 | # nmigen: UnusedElaboratable=no 2 | 3 | from ..hdl.ast import * 4 | from ..hdl.mem import * 5 | from .utils import * 6 | 7 | 8 | class MemoryTestCase(FHDLTestCase): 9 | def test_name(self): 10 | m1 = Memory(width=8, depth=4) 11 | self.assertEqual(m1.name, "m1") 12 | m2 = [Memory(width=8, depth=4)][0] 13 | self.assertEqual(m2.name, "$memory") 14 | m3 = Memory(width=8, depth=4, name="foo") 15 | self.assertEqual(m3.name, "foo") 16 | 17 | def test_geometry(self): 18 | m = Memory(width=8, depth=4) 19 | self.assertEqual(m.width, 8) 20 | self.assertEqual(m.depth, 4) 21 | 22 | def test_geometry_wrong(self): 23 | with self.assertRaises(TypeError, 24 | msg="Memory width must be a non-negative integer, not -1"): 25 | m = Memory(width=-1, depth=4) 26 | with self.assertRaises(TypeError, 27 | msg="Memory depth must be a non-negative integer, not -1"): 28 | m = Memory(width=8, depth=-1) 29 | 30 | def test_init(self): 31 | m = Memory(width=8, depth=4, init=range(4)) 32 | self.assertEqual(m.init, [0, 1, 2, 3]) 33 | 34 | def test_init_wrong_count(self): 35 | with self.assertRaises(ValueError, 36 | msg="Memory initialization value count exceed memory depth (8 > 4)"): 37 | m = Memory(width=8, depth=4, init=range(8)) 38 | 39 | def test_init_wrong_type(self): 40 | with self.assertRaises(TypeError, 41 | msg="Memory initialization value at address 1: " 42 | "'str' object cannot be interpreted as an integer"): 43 | m = Memory(width=8, depth=4, init=[1, "0"]) 44 | 45 | def test_attrs(self): 46 | m1 = Memory(width=8, depth=4) 47 | self.assertEqual(m1.attrs, {}) 48 | m2 = Memory(width=8, depth=4, attrs={"ram_block": True}) 49 | self.assertEqual(m2.attrs, {"ram_block": True}) 50 | 51 | def test_read_port_transparent(self): 52 | mem = Memory(width=8, depth=4) 53 | rdport = mem.read_port() 54 | self.assertEqual(rdport.memory, mem) 55 | self.assertEqual(rdport.domain, "sync") 56 | self.assertEqual(rdport.transparent, True) 57 | self.assertEqual(len(rdport.addr), 2) 58 | self.assertEqual(len(rdport.data), 8) 59 | self.assertEqual(len(rdport.en), 1) 60 | self.assertIsInstance(rdport.en, Const) 61 | self.assertEqual(rdport.en.value, 1) 62 | 63 | def test_read_port_non_transparent(self): 64 | mem = Memory(width=8, depth=4) 65 | rdport = mem.read_port(transparent=False) 66 | self.assertEqual(rdport.memory, mem) 67 | self.assertEqual(rdport.domain, "sync") 68 | self.assertEqual(rdport.transparent, False) 69 | self.assertEqual(len(rdport.en), 1) 70 | self.assertIsInstance(rdport.en, Signal) 71 | self.assertEqual(rdport.en.reset, 1) 72 | 73 | def test_read_port_asynchronous(self): 74 | mem = Memory(width=8, depth=4) 75 | rdport = mem.read_port(domain="comb") 76 | self.assertEqual(rdport.memory, mem) 77 | self.assertEqual(rdport.domain, "comb") 78 | self.assertEqual(rdport.transparent, True) 79 | self.assertEqual(len(rdport.en), 1) 80 | self.assertIsInstance(rdport.en, Const) 81 | self.assertEqual(rdport.en.value, 1) 82 | 83 | def test_read_port_wrong(self): 84 | mem = Memory(width=8, depth=4) 85 | with self.assertRaises(ValueError, 86 | msg="Read port cannot be simultaneously asynchronous and non-transparent"): 87 | mem.read_port(domain="comb", transparent=False) 88 | 89 | def test_write_port(self): 90 | mem = Memory(width=8, depth=4) 91 | wrport = mem.write_port() 92 | self.assertEqual(wrport.memory, mem) 93 | self.assertEqual(wrport.domain, "sync") 94 | self.assertEqual(wrport.granularity, 8) 95 | self.assertEqual(len(wrport.addr), 2) 96 | self.assertEqual(len(wrport.data), 8) 97 | self.assertEqual(len(wrport.en), 1) 98 | 99 | def test_write_port_granularity(self): 100 | mem = Memory(width=8, depth=4) 101 | wrport = mem.write_port(granularity=2) 102 | self.assertEqual(wrport.memory, mem) 103 | self.assertEqual(wrport.domain, "sync") 104 | self.assertEqual(wrport.granularity, 2) 105 | self.assertEqual(len(wrport.addr), 2) 106 | self.assertEqual(len(wrport.data), 8) 107 | self.assertEqual(len(wrport.en), 4) 108 | 109 | def test_write_port_granularity_wrong(self): 110 | mem = Memory(width=8, depth=4) 111 | with self.assertRaises(TypeError, 112 | msg="Write port granularity must be a non-negative integer, not -1"): 113 | mem.write_port(granularity=-1) 114 | with self.assertRaises(ValueError, 115 | msg="Write port granularity must not be greater than memory width (10 > 8)"): 116 | mem.write_port(granularity=10) 117 | with self.assertRaises(ValueError, 118 | msg="Write port granularity must divide memory width evenly"): 119 | mem.write_port(granularity=3) 120 | 121 | 122 | class DummyPortTestCase(FHDLTestCase): 123 | def test_name(self): 124 | p1 = DummyPort(data_width=8, addr_width=2) 125 | self.assertEqual(p1.addr.name, "p1_addr") 126 | p2 = [DummyPort(data_width=8, addr_width=2)][0] 127 | self.assertEqual(p2.addr.name, "dummy_addr") 128 | p3 = DummyPort(data_width=8, addr_width=2, name="foo") 129 | self.assertEqual(p3.addr.name, "foo_addr") 130 | 131 | def test_sizes(self): 132 | p1 = DummyPort(data_width=8, addr_width=2) 133 | self.assertEqual(p1.addr.width, 2) 134 | self.assertEqual(p1.data.width, 8) 135 | self.assertEqual(p1.en.width, 1) 136 | p2 = DummyPort(data_width=8, addr_width=2, granularity=2) 137 | self.assertEqual(p2.en.width, 4) 138 | -------------------------------------------------------------------------------- /nmigen/test/test_lib_cdc.py: -------------------------------------------------------------------------------- 1 | # nmigen: UnusedElaboratable=no 2 | 3 | from .utils import * 4 | from ..hdl import * 5 | from ..back.pysim import * 6 | from ..lib.cdc import * 7 | 8 | 9 | class FFSynchronizerTestCase(FHDLTestCase): 10 | def test_stages_wrong(self): 11 | with self.assertRaises(TypeError, 12 | msg="Synchronization stage count must be a positive integer, not 0"): 13 | FFSynchronizer(Signal(), Signal(), stages=0) 14 | with self.assertRaises(ValueError, 15 | msg="Synchronization stage count may not safely be less than 2"): 16 | FFSynchronizer(Signal(), Signal(), stages=1) 17 | 18 | def test_basic(self): 19 | i = Signal() 20 | o = Signal() 21 | frag = FFSynchronizer(i, o) 22 | 23 | sim = Simulator(frag) 24 | sim.add_clock(1e-6) 25 | def process(): 26 | self.assertEqual((yield o), 0) 27 | yield i.eq(1) 28 | yield Tick() 29 | self.assertEqual((yield o), 0) 30 | yield Tick() 31 | self.assertEqual((yield o), 0) 32 | yield Tick() 33 | self.assertEqual((yield o), 1) 34 | sim.add_process(process) 35 | sim.run() 36 | 37 | def test_reset_value(self): 38 | i = Signal(reset=1) 39 | o = Signal() 40 | frag = FFSynchronizer(i, o, reset=1) 41 | 42 | sim = Simulator(frag) 43 | sim.add_clock(1e-6) 44 | def process(): 45 | self.assertEqual((yield o), 1) 46 | yield i.eq(0) 47 | yield Tick() 48 | self.assertEqual((yield o), 1) 49 | yield Tick() 50 | self.assertEqual((yield o), 1) 51 | yield Tick() 52 | self.assertEqual((yield o), 0) 53 | sim.add_process(process) 54 | sim.run() 55 | 56 | 57 | class AsyncFFSynchronizerTestCase(FHDLTestCase): 58 | def test_stages_wrong(self): 59 | with self.assertRaises(TypeError, 60 | msg="Synchronization stage count must be a positive integer, not 0"): 61 | ResetSynchronizer(Signal(), stages=0) 62 | with self.assertRaises(ValueError, 63 | msg="Synchronization stage count may not safely be less than 2"): 64 | ResetSynchronizer(Signal(), stages=1) 65 | 66 | def test_edge_wrong(self): 67 | with self.assertRaises(ValueError, 68 | msg="AsyncFFSynchronizer async edge must be one of 'pos' or 'neg', not 'xxx'"): 69 | AsyncFFSynchronizer(Signal(), Signal(), domain="sync", async_edge="xxx") 70 | 71 | def test_pos_edge(self): 72 | i = Signal() 73 | o = Signal() 74 | m = Module() 75 | m.domains += ClockDomain("sync") 76 | m.submodules += AsyncFFSynchronizer(i, o) 77 | 78 | sim = Simulator(m) 79 | sim.add_clock(1e-6) 80 | def process(): 81 | # initial reset 82 | self.assertEqual((yield i), 0) 83 | self.assertEqual((yield o), 1) 84 | yield Tick(); yield Delay(1e-8) 85 | self.assertEqual((yield o), 1) 86 | yield Tick(); yield Delay(1e-8) 87 | self.assertEqual((yield o), 0) 88 | yield Tick(); yield Delay(1e-8) 89 | self.assertEqual((yield o), 0) 90 | yield Tick(); yield Delay(1e-8) 91 | 92 | yield i.eq(1) 93 | yield Delay(1e-8) 94 | self.assertEqual((yield o), 1) 95 | yield Tick(); yield Delay(1e-8) 96 | self.assertEqual((yield o), 1) 97 | yield i.eq(0) 98 | yield Tick(); yield Delay(1e-8) 99 | self.assertEqual((yield o), 1) 100 | yield Tick(); yield Delay(1e-8) 101 | self.assertEqual((yield o), 0) 102 | yield Tick(); yield Delay(1e-8) 103 | self.assertEqual((yield o), 0) 104 | yield Tick(); yield Delay(1e-8) 105 | sim.add_process(process) 106 | with sim.write_vcd("test.vcd"): 107 | sim.run() 108 | 109 | def test_neg_edge(self): 110 | i = Signal(reset=1) 111 | o = Signal() 112 | m = Module() 113 | m.domains += ClockDomain("sync") 114 | m.submodules += AsyncFFSynchronizer(i, o, async_edge="neg") 115 | 116 | sim = Simulator(m) 117 | sim.add_clock(1e-6) 118 | def process(): 119 | # initial reset 120 | self.assertEqual((yield i), 1) 121 | self.assertEqual((yield o), 1) 122 | yield Tick(); yield Delay(1e-8) 123 | self.assertEqual((yield o), 1) 124 | yield Tick(); yield Delay(1e-8) 125 | self.assertEqual((yield o), 0) 126 | yield Tick(); yield Delay(1e-8) 127 | self.assertEqual((yield o), 0) 128 | yield Tick(); yield Delay(1e-8) 129 | 130 | yield i.eq(0) 131 | yield Delay(1e-8) 132 | self.assertEqual((yield o), 1) 133 | yield Tick(); yield Delay(1e-8) 134 | self.assertEqual((yield o), 1) 135 | yield i.eq(1) 136 | yield Tick(); yield Delay(1e-8) 137 | self.assertEqual((yield o), 1) 138 | yield Tick(); yield Delay(1e-8) 139 | self.assertEqual((yield o), 0) 140 | yield Tick(); yield Delay(1e-8) 141 | self.assertEqual((yield o), 0) 142 | yield Tick(); yield Delay(1e-8) 143 | sim.add_process(process) 144 | with sim.write_vcd("test.vcd"): 145 | sim.run() 146 | 147 | 148 | class ResetSynchronizerTestCase(FHDLTestCase): 149 | def test_stages_wrong(self): 150 | with self.assertRaises(TypeError, 151 | msg="Synchronization stage count must be a positive integer, not 0"): 152 | ResetSynchronizer(Signal(), stages=0) 153 | with self.assertRaises(ValueError, 154 | msg="Synchronization stage count may not safely be less than 2"): 155 | ResetSynchronizer(Signal(), stages=1) 156 | 157 | def test_basic(self): 158 | arst = Signal() 159 | m = Module() 160 | m.domains += ClockDomain("sync") 161 | m.submodules += ResetSynchronizer(arst) 162 | s = Signal(reset=1) 163 | m.d.sync += s.eq(0) 164 | 165 | sim = Simulator(m) 166 | sim.add_clock(1e-6) 167 | def process(): 168 | # initial reset 169 | self.assertEqual((yield s), 1) 170 | yield Tick(); yield Delay(1e-8) 171 | self.assertEqual((yield s), 1) 172 | yield Tick(); yield Delay(1e-8) 173 | self.assertEqual((yield s), 1) 174 | yield Tick(); yield Delay(1e-8) 175 | self.assertEqual((yield s), 0) 176 | yield Tick(); yield Delay(1e-8) 177 | 178 | yield arst.eq(1) 179 | yield Delay(1e-8) 180 | self.assertEqual((yield s), 0) 181 | yield Tick(); yield Delay(1e-8) 182 | self.assertEqual((yield s), 1) 183 | yield arst.eq(0) 184 | yield Tick(); yield Delay(1e-8) 185 | self.assertEqual((yield s), 1) 186 | yield Tick(); yield Delay(1e-8) 187 | self.assertEqual((yield s), 1) 188 | yield Tick(); yield Delay(1e-8) 189 | self.assertEqual((yield s), 0) 190 | yield Tick(); yield Delay(1e-8) 191 | sim.add_process(process) 192 | with sim.write_vcd("test.vcd"): 193 | sim.run() 194 | 195 | 196 | # TODO: test with distinct clocks 197 | class PulseSynchronizerTestCase(FHDLTestCase): 198 | def test_paramcheck(self): 199 | with self.assertRaises(TypeError): 200 | ps = PulseSynchronizer("w", "r", sync_stages=0) 201 | with self.assertRaises(TypeError): 202 | ps = PulseSynchronizer("w", "r", sync_stages="abc") 203 | ps = PulseSynchronizer("w", "r", sync_stages = 1) 204 | 205 | def test_smoke(self): 206 | m = Module() 207 | m.domains += ClockDomain("sync") 208 | ps = m.submodules.dut = PulseSynchronizer("sync", "sync") 209 | 210 | sim = Simulator(m) 211 | sim.add_clock(1e-6) 212 | def process(): 213 | yield ps.i.eq(0) 214 | # TODO: think about reset 215 | for n in range(5): 216 | yield Tick() 217 | # Make sure no pulses are generated in quiescent state 218 | for n in range(3): 219 | yield Tick() 220 | self.assertEqual((yield ps.o), 0) 221 | # Check conservation of pulses 222 | accum = 0 223 | for n in range(10): 224 | yield ps.i.eq(1 if n < 4 else 0) 225 | yield Tick() 226 | accum += yield ps.o 227 | self.assertEqual(accum, 4) 228 | sim.add_process(process) 229 | sim.run() 230 | -------------------------------------------------------------------------------- /nmigen/test/test_lib_coding.py: -------------------------------------------------------------------------------- 1 | from .utils import * 2 | from ..hdl import * 3 | from ..asserts import * 4 | from ..back.pysim import * 5 | from ..lib.coding import * 6 | 7 | 8 | class EncoderTestCase(FHDLTestCase): 9 | def test_basic(self): 10 | enc = Encoder(4) 11 | def process(): 12 | self.assertEqual((yield enc.n), 1) 13 | self.assertEqual((yield enc.o), 0) 14 | 15 | yield enc.i.eq(0b0001) 16 | yield Settle() 17 | self.assertEqual((yield enc.n), 0) 18 | self.assertEqual((yield enc.o), 0) 19 | 20 | yield enc.i.eq(0b0100) 21 | yield Settle() 22 | self.assertEqual((yield enc.n), 0) 23 | self.assertEqual((yield enc.o), 2) 24 | 25 | yield enc.i.eq(0b0110) 26 | yield Settle() 27 | self.assertEqual((yield enc.n), 1) 28 | self.assertEqual((yield enc.o), 0) 29 | 30 | sim = Simulator(enc) 31 | sim.add_process(process) 32 | sim.run() 33 | 34 | 35 | class PriorityEncoderTestCase(FHDLTestCase): 36 | def test_basic(self): 37 | enc = PriorityEncoder(4) 38 | def process(): 39 | self.assertEqual((yield enc.n), 1) 40 | self.assertEqual((yield enc.o), 0) 41 | 42 | yield enc.i.eq(0b0001) 43 | yield Settle() 44 | self.assertEqual((yield enc.n), 0) 45 | self.assertEqual((yield enc.o), 0) 46 | 47 | yield enc.i.eq(0b0100) 48 | yield Settle() 49 | self.assertEqual((yield enc.n), 0) 50 | self.assertEqual((yield enc.o), 2) 51 | 52 | yield enc.i.eq(0b0110) 53 | yield Settle() 54 | self.assertEqual((yield enc.n), 0) 55 | self.assertEqual((yield enc.o), 1) 56 | 57 | sim = Simulator(enc) 58 | sim.add_process(process) 59 | sim.run() 60 | 61 | 62 | class DecoderTestCase(FHDLTestCase): 63 | def test_basic(self): 64 | dec = Decoder(4) 65 | def process(): 66 | self.assertEqual((yield dec.o), 0b0001) 67 | 68 | yield dec.i.eq(1) 69 | yield Settle() 70 | self.assertEqual((yield dec.o), 0b0010) 71 | 72 | yield dec.i.eq(3) 73 | yield Settle() 74 | self.assertEqual((yield dec.o), 0b1000) 75 | 76 | yield dec.n.eq(1) 77 | yield Settle() 78 | self.assertEqual((yield dec.o), 0b0000) 79 | 80 | sim = Simulator(dec) 81 | sim.add_process(process) 82 | sim.run() 83 | 84 | 85 | class ReversibleSpec(Elaboratable): 86 | def __init__(self, encoder_cls, decoder_cls, args): 87 | self.encoder_cls = encoder_cls 88 | self.decoder_cls = decoder_cls 89 | self.coder_args = args 90 | 91 | def elaborate(self, platform): 92 | m = Module() 93 | enc, dec = self.encoder_cls(*self.coder_args), self.decoder_cls(*self.coder_args) 94 | m.submodules += enc, dec 95 | m.d.comb += [ 96 | dec.i.eq(enc.o), 97 | Assert(enc.i == dec.o) 98 | ] 99 | return m 100 | 101 | 102 | class HammingDistanceSpec(Elaboratable): 103 | def __init__(self, distance, encoder_cls, args): 104 | self.distance = distance 105 | self.encoder_cls = encoder_cls 106 | self.coder_args = args 107 | 108 | def elaborate(self, platform): 109 | m = Module() 110 | enc1, enc2 = self.encoder_cls(*self.coder_args), self.encoder_cls(*self.coder_args) 111 | m.submodules += enc1, enc2 112 | m.d.comb += [ 113 | Assume(enc1.i + 1 == enc2.i), 114 | Assert(sum(enc1.o ^ enc2.o) == self.distance) 115 | ] 116 | return m 117 | 118 | 119 | class GrayCoderTestCase(FHDLTestCase): 120 | def test_reversible(self): 121 | spec = ReversibleSpec(encoder_cls=GrayEncoder, decoder_cls=GrayDecoder, args=(16,)) 122 | self.assertFormal(spec, mode="prove") 123 | 124 | def test_distance(self): 125 | spec = HammingDistanceSpec(distance=1, encoder_cls=GrayEncoder, args=(16,)) 126 | self.assertFormal(spec, mode="prove") 127 | -------------------------------------------------------------------------------- /nmigen/test/test_lib_io.py: -------------------------------------------------------------------------------- 1 | from .utils import * 2 | from ..hdl import * 3 | from ..hdl.rec import * 4 | from ..back.pysim import * 5 | from ..lib.io import * 6 | 7 | 8 | class PinLayoutCombTestCase(FHDLTestCase): 9 | def test_pin_layout_i(self): 10 | layout_1 = pin_layout(1, dir="i") 11 | self.assertEqual(layout_1.fields, { 12 | "i": ((1, False), DIR_NONE), 13 | }) 14 | 15 | layout_2 = pin_layout(2, dir="i") 16 | self.assertEqual(layout_2.fields, { 17 | "i": ((2, False), DIR_NONE), 18 | }) 19 | 20 | def test_pin_layout_o(self): 21 | layout_1 = pin_layout(1, dir="o") 22 | self.assertEqual(layout_1.fields, { 23 | "o": ((1, False), DIR_NONE), 24 | }) 25 | 26 | layout_2 = pin_layout(2, dir="o") 27 | self.assertEqual(layout_2.fields, { 28 | "o": ((2, False), DIR_NONE), 29 | }) 30 | 31 | def test_pin_layout_oe(self): 32 | layout_1 = pin_layout(1, dir="oe") 33 | self.assertEqual(layout_1.fields, { 34 | "o": ((1, False), DIR_NONE), 35 | "oe": ((1, False), DIR_NONE), 36 | }) 37 | 38 | layout_2 = pin_layout(2, dir="oe") 39 | self.assertEqual(layout_2.fields, { 40 | "o": ((2, False), DIR_NONE), 41 | "oe": ((1, False), DIR_NONE), 42 | }) 43 | 44 | def test_pin_layout_io(self): 45 | layout_1 = pin_layout(1, dir="io") 46 | self.assertEqual(layout_1.fields, { 47 | "i": ((1, False), DIR_NONE), 48 | "o": ((1, False), DIR_NONE), 49 | "oe": ((1, False), DIR_NONE), 50 | }) 51 | 52 | layout_2 = pin_layout(2, dir="io") 53 | self.assertEqual(layout_2.fields, { 54 | "i": ((2, False), DIR_NONE), 55 | "o": ((2, False), DIR_NONE), 56 | "oe": ((1, False), DIR_NONE), 57 | }) 58 | 59 | 60 | class PinLayoutSDRTestCase(FHDLTestCase): 61 | def test_pin_layout_i(self): 62 | layout_1 = pin_layout(1, dir="i", xdr=1) 63 | self.assertEqual(layout_1.fields, { 64 | "i_clk": ((1, False), DIR_NONE), 65 | "i": ((1, False), DIR_NONE), 66 | }) 67 | 68 | layout_2 = pin_layout(2, dir="i", xdr=1) 69 | self.assertEqual(layout_2.fields, { 70 | "i_clk": ((1, False), DIR_NONE), 71 | "i": ((2, False), DIR_NONE), 72 | }) 73 | 74 | def test_pin_layout_o(self): 75 | layout_1 = pin_layout(1, dir="o", xdr=1) 76 | self.assertEqual(layout_1.fields, { 77 | "o_clk": ((1, False), DIR_NONE), 78 | "o": ((1, False), DIR_NONE), 79 | }) 80 | 81 | layout_2 = pin_layout(2, dir="o", xdr=1) 82 | self.assertEqual(layout_2.fields, { 83 | "o_clk": ((1, False), DIR_NONE), 84 | "o": ((2, False), DIR_NONE), 85 | }) 86 | 87 | def test_pin_layout_oe(self): 88 | layout_1 = pin_layout(1, dir="oe", xdr=1) 89 | self.assertEqual(layout_1.fields, { 90 | "o_clk": ((1, False), DIR_NONE), 91 | "o": ((1, False), DIR_NONE), 92 | "oe": ((1, False), DIR_NONE), 93 | }) 94 | 95 | layout_2 = pin_layout(2, dir="oe", xdr=1) 96 | self.assertEqual(layout_2.fields, { 97 | "o_clk": ((1, False), DIR_NONE), 98 | "o": ((2, False), DIR_NONE), 99 | "oe": ((1, False), DIR_NONE), 100 | }) 101 | 102 | def test_pin_layout_io(self): 103 | layout_1 = pin_layout(1, dir="io", xdr=1) 104 | self.assertEqual(layout_1.fields, { 105 | "i_clk": ((1, False), DIR_NONE), 106 | "i": ((1, False), DIR_NONE), 107 | "o_clk": ((1, False), DIR_NONE), 108 | "o": ((1, False), DIR_NONE), 109 | "oe": ((1, False), DIR_NONE), 110 | }) 111 | 112 | layout_2 = pin_layout(2, dir="io", xdr=1) 113 | self.assertEqual(layout_2.fields, { 114 | "i_clk": ((1, False), DIR_NONE), 115 | "i": ((2, False), DIR_NONE), 116 | "o_clk": ((1, False), DIR_NONE), 117 | "o": ((2, False), DIR_NONE), 118 | "oe": ((1, False), DIR_NONE), 119 | }) 120 | 121 | 122 | class PinLayoutDDRTestCase(FHDLTestCase): 123 | def test_pin_layout_i(self): 124 | layout_1 = pin_layout(1, dir="i", xdr=2) 125 | self.assertEqual(layout_1.fields, { 126 | "i_clk": ((1, False), DIR_NONE), 127 | "i0": ((1, False), DIR_NONE), 128 | "i1": ((1, False), DIR_NONE), 129 | }) 130 | 131 | layout_2 = pin_layout(2, dir="i", xdr=2) 132 | self.assertEqual(layout_2.fields, { 133 | "i_clk": ((1, False), DIR_NONE), 134 | "i0": ((2, False), DIR_NONE), 135 | "i1": ((2, False), DIR_NONE), 136 | }) 137 | 138 | def test_pin_layout_o(self): 139 | layout_1 = pin_layout(1, dir="o", xdr=2) 140 | self.assertEqual(layout_1.fields, { 141 | "o_clk": ((1, False), DIR_NONE), 142 | "o0": ((1, False), DIR_NONE), 143 | "o1": ((1, False), DIR_NONE), 144 | }) 145 | 146 | layout_2 = pin_layout(2, dir="o", xdr=2) 147 | self.assertEqual(layout_2.fields, { 148 | "o_clk": ((1, False), DIR_NONE), 149 | "o0": ((2, False), DIR_NONE), 150 | "o1": ((2, False), DIR_NONE), 151 | }) 152 | 153 | def test_pin_layout_oe(self): 154 | layout_1 = pin_layout(1, dir="oe", xdr=2) 155 | self.assertEqual(layout_1.fields, { 156 | "o_clk": ((1, False), DIR_NONE), 157 | "o0": ((1, False), DIR_NONE), 158 | "o1": ((1, False), DIR_NONE), 159 | "oe": ((1, False), DIR_NONE), 160 | }) 161 | 162 | layout_2 = pin_layout(2, dir="oe", xdr=2) 163 | self.assertEqual(layout_2.fields, { 164 | "o_clk": ((1, False), DIR_NONE), 165 | "o0": ((2, False), DIR_NONE), 166 | "o1": ((2, False), DIR_NONE), 167 | "oe": ((1, False), DIR_NONE), 168 | }) 169 | 170 | def test_pin_layout_io(self): 171 | layout_1 = pin_layout(1, dir="io", xdr=2) 172 | self.assertEqual(layout_1.fields, { 173 | "i_clk": ((1, False), DIR_NONE), 174 | "i0": ((1, False), DIR_NONE), 175 | "i1": ((1, False), DIR_NONE), 176 | "o_clk": ((1, False), DIR_NONE), 177 | "o0": ((1, False), DIR_NONE), 178 | "o1": ((1, False), DIR_NONE), 179 | "oe": ((1, False), DIR_NONE), 180 | }) 181 | 182 | layout_2 = pin_layout(2, dir="io", xdr=2) 183 | self.assertEqual(layout_2.fields, { 184 | "i_clk": ((1, False), DIR_NONE), 185 | "i0": ((2, False), DIR_NONE), 186 | "i1": ((2, False), DIR_NONE), 187 | "o_clk": ((1, False), DIR_NONE), 188 | "o0": ((2, False), DIR_NONE), 189 | "o1": ((2, False), DIR_NONE), 190 | "oe": ((1, False), DIR_NONE), 191 | }) 192 | 193 | 194 | class PinTestCase(FHDLTestCase): 195 | def test_attributes(self): 196 | pin = Pin(2, dir="io", xdr=2) 197 | self.assertEqual(pin.width, 2) 198 | self.assertEqual(pin.dir, "io") 199 | self.assertEqual(pin.xdr, 2) 200 | -------------------------------------------------------------------------------- /nmigen/test/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import shutil 4 | import subprocess 5 | import textwrap 6 | import traceback 7 | import unittest 8 | import warnings 9 | from contextlib import contextmanager 10 | 11 | from ..hdl.ast import * 12 | from ..hdl.ir import * 13 | from ..back import rtlil 14 | from .._toolchain import require_tool 15 | 16 | 17 | __all__ = ["FHDLTestCase"] 18 | 19 | 20 | class FHDLTestCase(unittest.TestCase): 21 | def assertRepr(self, obj, repr_str): 22 | if isinstance(obj, list): 23 | obj = Statement.cast(obj) 24 | def prepare_repr(repr_str): 25 | repr_str = re.sub(r"\s+", " ", repr_str) 26 | repr_str = re.sub(r"\( (?=\()", "(", repr_str) 27 | repr_str = re.sub(r"\) (?=\))", ")", repr_str) 28 | return repr_str.strip() 29 | self.assertEqual(prepare_repr(repr(obj)), prepare_repr(repr_str)) 30 | 31 | @contextmanager 32 | def assertRaises(self, exception, msg=None): 33 | with super().assertRaises(exception) as cm: 34 | yield 35 | if msg is not None: 36 | # WTF? unittest.assertRaises is completely broken. 37 | self.assertEqual(str(cm.exception), msg) 38 | 39 | @contextmanager 40 | def assertRaisesRegex(self, exception, regex=None): 41 | with super().assertRaises(exception) as cm: 42 | yield 43 | if regex is not None: 44 | # unittest.assertRaisesRegex also seems broken... 45 | self.assertRegex(str(cm.exception), regex) 46 | 47 | @contextmanager 48 | def assertWarns(self, category, msg=None): 49 | with warnings.catch_warnings(record=True) as warns: 50 | yield 51 | self.assertEqual(len(warns), 1) 52 | self.assertEqual(warns[0].category, category) 53 | if msg is not None: 54 | self.assertEqual(str(warns[0].message), msg) 55 | 56 | def assertFormal(self, spec, mode="bmc", depth=1, engine="smtbmc", spec_name=None): 57 | caller, *_ = traceback.extract_stack(limit=2) 58 | spec_root, _ = os.path.splitext(caller.filename) 59 | spec_dir = os.path.dirname(spec_root) 60 | if spec_name is None: 61 | spec_name = "{}_{}".format( 62 | os.path.basename(spec_root).replace("test_", "spec_"), 63 | caller.name.replace("test_", "") 64 | ) 65 | 66 | # The sby -f switch seems not fully functional when sby is reading from stdin. 67 | if os.path.exists(os.path.join(spec_dir, spec_name)): 68 | shutil.rmtree(os.path.join(spec_dir, spec_name)) 69 | 70 | if mode == "hybrid": 71 | # A mix of BMC and k-induction, as per personal communication with Claire Wolf. 72 | script = "setattr -unset init w:* a:nmigen.sample_reg %d" 73 | mode = "bmc" 74 | else: 75 | script = "" 76 | 77 | config = textwrap.dedent("""\ 78 | [options] 79 | mode {mode} 80 | depth {depth} 81 | wait on 82 | 83 | [engines] 84 | {engine} 85 | 86 | [script] 87 | read_ilang top.il 88 | prep 89 | {script} 90 | 91 | [file top.il] 92 | {rtlil} 93 | """).format( 94 | mode=mode, 95 | depth=depth, 96 | engine=engine, 97 | script=script, 98 | rtlil=rtlil.convert(Fragment.get(spec, platform="formal")) 99 | ) 100 | with subprocess.Popen([require_tool("sby"), "-f", "-d", spec_name], cwd=spec_dir, 101 | universal_newlines=True, 102 | stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc: 103 | stdout, stderr = proc.communicate(config) 104 | if proc.returncode != 0: 105 | self.fail("Formal verification failed:\n" + stdout) 106 | -------------------------------------------------------------------------------- /nmigen/tracer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from opcode import opname 3 | 4 | 5 | __all__ = ["NameNotFound", "get_var_name", "get_src_loc"] 6 | 7 | 8 | class NameNotFound(Exception): 9 | pass 10 | 11 | 12 | _raise_exception = object() 13 | 14 | 15 | def get_var_name(depth=2, default=_raise_exception): 16 | frame = sys._getframe(depth) 17 | code = frame.f_code 18 | call_index = frame.f_lasti 19 | while True: 20 | call_opc = opname[code.co_code[call_index]] 21 | if call_opc in ("EXTENDED_ARG",): 22 | call_index += 2 23 | else: 24 | break 25 | if call_opc not in ("CALL_FUNCTION", "CALL_FUNCTION_KW", "CALL_FUNCTION_EX", "CALL_METHOD"): 26 | return None 27 | 28 | index = call_index + 2 29 | while True: 30 | opc = opname[code.co_code[index]] 31 | if opc in ("STORE_NAME", "STORE_ATTR"): 32 | name_index = int(code.co_code[index + 1]) 33 | return code.co_names[name_index] 34 | elif opc == "STORE_FAST": 35 | name_index = int(code.co_code[index + 1]) 36 | return code.co_varnames[name_index] 37 | elif opc == "STORE_DEREF": 38 | name_index = int(code.co_code[index + 1]) 39 | return code.co_cellvars[name_index] 40 | elif opc in ("LOAD_GLOBAL", "LOAD_ATTR", "LOAD_FAST", "LOAD_DEREF", 41 | "DUP_TOP", "BUILD_LIST"): 42 | index += 2 43 | else: 44 | if default is _raise_exception: 45 | raise NameNotFound 46 | else: 47 | return default 48 | 49 | 50 | def get_src_loc(src_loc_at=0): 51 | # n-th frame: get_src_loc() 52 | # n-1th frame: caller of get_src_loc() (usually constructor) 53 | # n-2th frame: caller of caller (usually user code) 54 | frame = sys._getframe(2 + src_loc_at) 55 | return (frame.f_code.co_filename, frame.f_lineno) 56 | -------------------------------------------------------------------------------- /nmigen/utils.py: -------------------------------------------------------------------------------- 1 | __all__ = ["log2_int", "bits_for"] 2 | 3 | 4 | def log2_int(n, need_pow2=True): 5 | if n == 0: 6 | return 0 7 | r = (n - 1).bit_length() 8 | if need_pow2 and (1 << r) != n: 9 | raise ValueError("{} is not a power of 2".format(n)) 10 | return r 11 | 12 | 13 | def bits_for(n, require_sign_bit=False): 14 | if n > 0: 15 | r = log2_int(n + 1, False) 16 | else: 17 | require_sign_bit = True 18 | r = log2_int(-n, False) 19 | if require_sign_bit: 20 | r += 1 21 | return r 22 | -------------------------------------------------------------------------------- /nmigen/vendor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-labs/nmigen/6c5afdc3c37e82854b08861e8b6d95f644b45f6e/nmigen/vendor/__init__.py -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | def scm_version(): 5 | def local_scheme(version): 6 | if version.tag and not version.distance: 7 | return version.format_with("") 8 | else: 9 | return version.format_choice("+{node}", "+{node}.dirty") 10 | return { 11 | "relative_to": __file__, 12 | "version_scheme": "guess-next-dev", 13 | "local_scheme": local_scheme 14 | } 15 | 16 | 17 | setup( 18 | name="nmigen", 19 | use_scm_version=scm_version(), 20 | author="whitequark", 21 | author_email="whitequark@whitequark.org", 22 | description="Python toolbox for building complex digital hardware", 23 | #long_description="""TODO""", 24 | license="BSD", 25 | python_requires="~=3.6", 26 | setup_requires=["setuptools_scm"], 27 | install_requires=[ 28 | "setuptools", 29 | "pyvcd~=0.1.4", # for nmigen.pysim 30 | "Jinja2", # for nmigen.build 31 | ], 32 | packages=find_packages(), 33 | entry_points={ 34 | "console_scripts": [ 35 | "nmigen-rpc = nmigen.rpc:main", 36 | ] 37 | }, 38 | project_urls={ 39 | #"Documentation": "https://nmigen.readthedocs.io/", 40 | "Source Code": "https://github.com/m-labs/nmigen", 41 | "Bug Tracker": "https://github.com/m-labs/nmigen/issues", 42 | }, 43 | ) 44 | --------------------------------------------------------------------------------