├── .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 |
--------------------------------------------------------------------------------