Skip to content

Commit 5cd7372

Browse files
committed
Updated to use Amaranth HDL
1 parent 144b858 commit 5cd7372

26 files changed

+128
-159
lines changed

00_intro.md

Lines changed: 13 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# What's this?
22

3-
This is a series of graded exercises in using nMigen, the Python-based Hardware Design Language, to design and verify digital circuits!
3+
This is a series of graded exercises in using [Amaranth HDL](https://amaranth-lang.org/docs/amaranth/latest/intro.html), the Python-based Hardware Design Language, to design and verify digital circuits!
44

55
It's also a work in progress.
66

7-
Please note, the goal is not to be pedantically correct in every explanation. As you progress through the exercises, you'll learn for yourself the breadth of nMigen and what you can do with it!
7+
Please note, the goal is not to be pedantically correct in every explanation. As you progress through the exercises, you'll learn for yourself the breadth of Amaranth and what you can do with it!
88

99
## Prerequisite knowledge
1010

@@ -16,66 +16,26 @@ You will need knowledge of:
1616

1717
## Software prerequisites
1818

19-
If you're using Windows, I hope you're using Windows Subsystem for Linux. If not, you're on your own.
19+
YosysHQ has put together a [comprehensive installer](https://github.com/YosysHQ/oss-cad-suite-build#installation) containing everything you need, and then some. Simply follow the instructions.
2020

21-
### Option 1: Docker (recommended)
21+
### Tip for Windows users
2222

23-
Victor Muñoz has put together a [docker container](https://github.com/vmunoz82/eda_tools) that has everything you need: Python 3, nMigen, yosys, SymbiYosys, and the Z3, boolector, and yices solvers.
23+
If you just downloaded the exe file, it will be in your Downloads folder. Run the executable, and it extracts everything to Downloads/oss-cad-suite. You can simply move the oss-cad-suite directory to somewhere more convient.
2424

25-
You'll need to get [Docker Desktop](https://www.docker.com/get-started). If you're on Windows, follow the instructions for [Docker/WSL](https://docs.docker.com/docker-for-windows/wsl/) instead.
25+
*Do not use PowerShell*. Use Command Prompt instead. Always be sure to run `<your-directory>\oss-cad-suite\environment.bat` in Command Prompt before doing any work. You'll know if the environment was set properly if you see `[OSS CAD Suite]` before your prompt:
2626

27-
Also, if you're on Windows, you will want to download and run an [X server](https://medium.com/@japheth.yates/the-complete-wsl2-gui-setup-2582828f4577) so that you can use gtkwave from the command line on WSL.
27+
```txt
28+
F:\amaranth-exercises>..\oss-cad-suite\environment.bat
2829
29-
Once you've done that, you can open up WSL (if you're on Windows) and then run [`eda_tools.sh`](https://raw.githubusercontent.com/vmunoz82/eda_tools/main/eda_tools.sh). This will conveniently start up the docker container with all the right options.
30-
31-
You may want to replace `-v $HOME:/$HOME` in `eda_tools.sh` with `-v /your/host/working/directory:/some/nice/directory/on/docker`, and then you can access your files in `/some/nice/directory/on/docker` in the docker container. For example, I do my work in WSL in `/mnt/f` so I simply use `-v /mnt/f:/mnt/f`.
32-
33-
Remember that the docker container will not save local files (e.g. files in `/workspace`), so make sure you have your working directory mapped!
34-
35-
### Option 2: Build it all yourself
36-
37-
You will need:
38-
39-
* Python 3.6 or above: See below for Python 3.6 on WSL.
40-
41-
* Install yosys, Symbiyosys, yices2, and z3.
42-
* `sudo apt install curl`
43-
* Now follow the [instructions to install these](https://symbiyosys.readthedocs.io/en/latest/install.html). It is highly recommended to follow those instructions, especially for yosys since the git repo has many more fixes than the official release.
44-
* Note that when you see `-j$(nproc)`, it means to specify the number of processors your CPU has. You can't really go wrong by using `-j4`. You can go higher if you know you have more.
45-
* z3 takes a long time to compile. Go watch a YouTube video in the meantime.
46-
47-
* Install nMigen
48-
* `pip3 install wheel`
49-
* `pip3 install git+https://github.com/nmigen/nmigen`
50-
51-
* Signal viewer for simulation and formal verification
52-
* [gtkwave](https://sourceforge.net/projects/gtkwave/)
53-
* For WSL, get the Windows version unless you want to run an [X server](https://medium.com/@japheth.yates/the-complete-wsl2-gui-setup-2582828f4577).
54-
* The package for gtkwave for Windows gives you no clue how to install for Windows. Unzip the zip file into, say, C:, and then add C:\gtkwave\bin to your Windows path.
55-
56-
## Python and WSL
57-
58-
Python 2 and Python 3 are not compatible. This is why this happens:
59-
60-
* `python`, `pip` -> Python 2
61-
* `python3`, `pip3` -> Python 3
62-
63-
On my freshly-installed WSL, the only version present is Python 3.8. If it is not on your version, you may want to upgrade to 3.8. Upgrading is beyond the scope of this document.
64-
65-
Now install some bare minimum tools:
66-
67-
```sh
68-
sudo apt install python3-pip build-essential libssl-dev libffi-dev
30+
[OSS CAD Suite] F:\amaranth-exercises>
6931
```
7032

71-
In installing the yosys prerequisites, Python 2 will be installed. So be aware that when you want to run anything under Python 3, you must use `python3` (or `pip3` for installing), not `python` (or `pip`).
72-
73-
# Tip for vscode users:
33+
## Tip for vscode users
7434

7535
Open File > Preferences > Settings, look for pylint args, and add:
7636

77-
```
78-
--contextmanager-decorators=contextlib.contextmanager,nmigen.hdl.dsl._guardedcontextmanager
37+
```txt
38+
--contextmanager-decorators=contextlib.contextmanager,amaranth.hdl.dsl._guardedcontextmanager
7939
```
8040

81-
This is because pylint doesn't recognize that `nmigen.hdl.dsl._guardedcontextmanager` is a valid context manager. Otherwise pylint will complain for every `with m.If` statement.
41+
This is because pylint doesn't recognize that `amaranth.hdl.dsl._guardedcontextmanager` is a valid context manager. Otherwise pylint will complain for every `with m.If` statement.

01_input.md

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# Exercise 1: Counting coin
22

33
<p align="right">
4-
Khajiit has knowledge... if you have coin.
4+
<i>Khajiit has knowledge... if you have coin.</i>
55
</p>
66

77
---
8-
## What you'll do:
8+
9+
## What you'll do
910

1011
Create a module that takes as inputs:
1112

@@ -29,25 +30,25 @@ Use formal verification to:
2930

3031
## Step 1: Create a module
3132

32-
An nMigen *module* is a Python class that has inputs and outputs, and code that generates the logic for the desired functionality. Note that I didn't say code that *implements* the function. A key concept is that nMigen is a Python library for writing logic. When the module is *elaborated*, your code runs and nMigen outputs the corresponding logic.
33+
An Amaranth *module* is a Python class that has inputs and outputs, and code that generates the logic for the desired functionality. Note that I didn't say code that *implements* the function. A key concept is that Amaranth is a Python library for writing logic. When the module is *elaborated*, your code runs and Amaranth outputs the corresponding logic.
3334

34-
You can think of the logic that nMigen writes as an integrated circuit, and the code that you write as the instructions for how to create a copy of that integrated circuit.
35+
You can think of the logic that Amaranth writes as an integrated circuit, and the code that you write as the instructions for how to create a copy of that integrated circuit.
3536

36-
Modules can use other modules (*submodules*), and you can have as many copies of the module as you want. Your design has one top-level module that contains all of your other modules. When you elaborate the top-level module, nMigen outputs its logic into a file. That file can then be given to other software for synthesis on an FPGA, or for formal verification (i.e. bug hunting).
37+
Modules can use other modules (*submodules*), and you can have as many copies of the module as you want. Your design has one top-level module that contains all of your other modules. When you elaborate the top-level module, Amaranth outputs its logic into a file. That file can then be given to other software for synthesis on an FPGA, or for formal verification (i.e. bug hunting).
3738

38-
![Toolchain flow](diagrams/nmigen_blocks.png)
39+
![Toolchain flow](diagrams/amaranth_blocks.png)
3940

4041
You can use the [`skeleton.py`](skeleton.py) file to start. Some key features here are:
4142

4243
* Your class is derived from `Elaboratable`
4344
* Your public (or visible) inputs and outputs are attributes of the class.
44-
* The class has an `elaborate` function that gets called by nMigen to generate the logic.
45+
* The class has an `elaborate` function that gets called by Amaranth to generate the logic.
4546
* The class has a class-level `formal` method for verifying your logic.
4647
* You can run your class to generate the output. This requires the `main` function in [`util.py`](util.py).
4748

4849
## Step 2: Create input and output signals
4950

50-
A `Signal` in nMigen is a wire or register with some number of bits. For example:
51+
A `Signal` in Amaranth is a wire or register with some number of bits. For example:
5152

5253
```python
5354
x = Signal(5) # creates a 5-bit signal named "x".
@@ -64,7 +65,7 @@ For this exercise, the logic is completely *combinatorial*: the outputs depend d
6465
m.d.comb += y.eq(x)
6566
```
6667

67-
This is just like writing `y = x`, except using the nMigen library, which requires using the `eq` function. That function returns a statement which can then be added to one of the domains in the module. Here, `m.d.comb` is the module's combinatorial domain.
68+
This is just like writing `y = x`, except using the Amaranth library, which requires using the `eq` function. That function returns a statement which can then be added to one of the domains in the module. Here, `m.d.comb` is the module's combinatorial domain.
6869

6970
You can use constants, and arithmetic functions, too:
7071

@@ -95,7 +96,7 @@ if x == 7:
9596
y = z
9697
```
9798

98-
The equivalent in nMigen is:
99+
The equivalent in Amaranth is:
99100

100101
```python
101102
m.d.comb += y.eq(0)
@@ -116,12 +117,12 @@ This does the same thing.
116117

117118
![An if-else-statement](diagrams/if.png)
118119

119-
## Step 4: Make sure it compiles!
120+
## Step 4: Make sure it compiles
120121

121122
You can quickly check that there aren't any syntax errors by just generating the code:
122123

123-
```
124-
python3 your_file.py gen
124+
```sh
125+
python your_file.py gen
125126
```
126127

127128
## Step 5: Write some formal verification code
@@ -180,22 +181,22 @@ If the engine cannot satisfy a cover, it will complain. This usually means that
180181

181182
Write the asserts and covers according to the requirements of the exercise. Compile again to make sure you haven't introduced any syntax errors.
182183

183-
```
184-
python3 your_file.py gen
184+
```sh
185+
python your_file.py gen
185186
```
186187

187188
## Step 6: Run the formal verification engine for covers
188189

189190
Running your code with `gen` will have it output a file `toplevel.il`. This can be run through the SymbiYosys formal verification tool. It requires a `.sby` configuration file, which I've included in `answers/e01_to_pennies.sby`.
190191

191-
```
192-
python3 your_class.py gen
192+
```sh
193+
python your_class.py gen
193194
sby -f answers/e01_to_pennies.sby cover
194195
```
195196

196197
First, look for the cover conditions to be satisfied. These are the `Reached cover statement at...` lines below. And, if all your cover statements were satisfied, you'll get a `DONE (PASS, rc=0)` line.
197198

198-
```
199+
```txt
199200
SBY 15:17:27 [answers/e01_to_pennies_cover] Removing directory 'answers/e01_to_pennies_cover'.
200201
SBY 15:17:27 [answers/e01_to_pennies_cover] Copy 'toplevel.il' to 'answers/e01_to_pennies_cover/src/toplevel.il'.
201202
SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: smtbmc z3
@@ -236,17 +237,22 @@ The cover statements aren't found in the order you put them in your code!
236237

237238
Each cover statement found generates a trace where you can see the inputs and outputs, as well as some intermediate signals if they are there, using `gtkwave`:
238239

239-
```
240+
```sh
240241
gtkwave -f answers/e01_to_pennies_cover/engine_0/trace0.vcd
241242
```
242243

243-
In this case, the first trace came from the first cover statement in the exercise. The output shows `1DD`, or 477.
244+
In this case, the first trace came from the first cover statement in the exercise. The output shows `1DD`, or 477. You can tell which cover statement the trace corresponds to by looking at the output from `sby`:
245+
246+
```txt
247+
SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Reached cover statement at answers/to_pennies.py:45 in step 0.
248+
SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Writing trace to VCD file: engine_0/trace0.vcd
249+
```
244250

245251
![gtkwave output](diagrams/ex1_tr0.JPG)
246252

247253
## Step 7: Run the formal verification engine for bounded model checking
248254

249-
```
255+
```sh
250256
sby -f answers/e01_to_pennies.sby bmc
251257
```
252258

02_switch.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# Exercise 2: The day after
22

33
<p align="right">
4-
The 14th of January is World Logic Day.
4+
<i>The 14th of January is World Logic Day.</i>
55
</p>
66

77
----
8-
## What you'll do:
8+
9+
## What you'll do
910

1011
Create a module that takes as inputs a date, consisting of:
1112

@@ -108,7 +109,7 @@ As before, you will need to:
108109
* Define your inputs and outputs.
109110
* Write the logic for the module in `elaborate`.
110111
* Write your asserts in `formal`.
111-
* Make sure it compiles with `python3 your_file.py gen`.
112+
* Make sure it compiles with `python your_file.py gen`.
112113
* Run formal verification in cover mode using `sby -f answers/e02_next_day.sby cover`.
113114
* Run formal verification in BMC mode using `sby -f answers/e02_next_day.sby bmc`.
114115

03_parts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Exercise 3: Life finds a way
22

3-
## What you'll do, part 1:
3+
## What you'll do, part 1
44

55
Create a module that determines the state of a cell in the Game of Life. It takes as input:
66

04_signs.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
Write a module that takes a 64-bit signed number, and negates it in three ways:
66

7-
* Invert and add 1.
8-
* Directly negate it.
7+
* Bitwise complement (invert) it and add 1.
8+
* Arithmetically negate it.
99
* Starting from the least significant bit, copy up to and including the first 1, then invert the remaining bits.
1010

1111
That last one seems a bit strange, but it works like this (using 8-bit numbers):
@@ -29,8 +29,8 @@ However, in 2's complement, `1000` is -8 while `0111` is 7, which means that if
2929
x = Signal(signed(4))
3030
y = Signal(signed(4))
3131
m.d.comb += [
32-
x.eq(0b1000),
33-
y.eq(0b0111),
32+
x.eq(0b1000), # -8
33+
y.eq(0b0111), # +7
3434
Assert(x < y),
3535
]
3636
```
@@ -41,8 +41,8 @@ To treat an unsigned (or signed) signal as signed:
4141
x = Signal(4)
4242
y = Signal(4)
4343
m.d.comb += [
44-
x.eq(0b1000),
45-
y.eq(0b0111),
44+
x.eq(0b1000), # 8 (or -8 if signed)
45+
y.eq(0b0111), # 7
4646
Assert(x.as_signed() < y.as_signed()),
4747
]
4848
```
@@ -55,10 +55,10 @@ Negating a signal always results in a signed signal, regardless of whether the i
5555
x = Signal(4)
5656
y = Signal(4)
5757
m.d.comb += [
58-
x.eq(0b0011),
59-
y.eq(0b1101),
58+
x.eq(0b0011), # 3
59+
y.eq(0b1101), # 13
6060
Assert(y > x),
61-
Assert(-x < x),
61+
Assert(-x < x), # -x is signed, so this is a signed comparison
6262
]
6363
```
6464

@@ -68,8 +68,8 @@ Equality, on the other hand, does not take into account signedness. It is bit-fo
6868
x = Signal(4)
6969
y = Signal(4)
7070
m.d.comb += [
71-
x.eq(0b0011),
72-
y.eq(0b1101),
71+
x.eq(0b0011), # 3
72+
y.eq(0b1101), # 13 (or -3 if signed)
7373
Assert(y > x),
7474
Assert(-x == y),
7575
]
@@ -90,10 +90,12 @@ m.d.comb += [
9090

9191
### Library: priority encoder
9292

93-
A *priority encoder* is a circuit that takes an N-bit input and outputs the position of the first set bit, where "first" could mean most significant or least significant. nMigen has a coding library with a PriorityEncoder module built in, which outputs the position of the first least significant bit set.
93+
A *priority encoder* is a circuit that takes an N-bit input and outputs the position of the first set bit, where "first" could mean first *most* significant or first *least* significant. Amaranth has a coding library with a `PriorityEncoder` module built in, which outputs the position of the first *least* significant bit set.
94+
95+
The encoder has three ports (attributes): `i`, the input, `o`, the output, which is the lowest bit number that is set in the input, and `n`, set if and only if the input is zero. The encoder also has a parameter, `width`, which tells it how many bits are in the input.
9496

9597
```python
96-
from nmigen.lib.coding import PriorityEncoder
98+
from amaranth.lib.coding import PriorityEncoder
9799

98100
enc = PriorityEncoder(width=8)
99101
input = Signal(8)
@@ -107,7 +109,7 @@ m.d.comb += [
107109
]
108110
```
109111

110-
For example, the input `0101000` will result in the output being 3 and bit_set being high.
112+
For example, setting `input` to `0101000` will result in `output` being 3 and `bit_set` being high.
111113

112114
### Limitations on slices
113115

@@ -123,13 +125,11 @@ You can achieve the same effect using bit tricks. For example, `1 << y` gives yo
123125

124126
What does `(-1 << y) & x` give you?
125127

126-
-----
127-
128-
Interested in more bit-manipulation tricks? Check out Knuth's The Art of Computer Programming, Volume 4A, chapter 7.1.3 (Bitwise Tricks and Techniques). Hacker's Delight also contains copious bit manipulation fun.
128+
> Interested in more bit-manipulation tricks? Check out Knuth's [The Art of Computer Programming](https://en.wikipedia.org/wiki/The_Art_of_Computer_Programming), Volume 4A, chapter 7.1.3 (Bitwise Tricks and Techniques). [Hacker's Delight](https://en.wikipedia.org/wiki/Hacker%27s_Delight) also contains copious bit manipulation fun.
129129
130130
-----
131131

132-
## What you'll do, part 2:
132+
## What you'll do, part 2
133133

134134
Suppose you have a chip that can compare two 16-bit numbers, but only as unsigned numbers. It outputs whether the first is less than the second. Write such a module.
135135

0 commit comments

Comments
 (0)