├── .gitignore ├── diagrams ├── if.png ├── past1.png ├── past2.png ├── bad_and.png ├── cell3x3.png ├── cell4x4.png ├── ex1_tr0.JPG ├── next_day.png ├── past_t0.png ├── some_logic.png ├── sync_flip.png ├── sync_load.png ├── sync_reset.png ├── life_examples.png ├── nmigen_blocks.png ├── fixing_induction.png ├── induction_math.png ├── past_and_friends.png ├── pennies_module.png ├── simple_asserts.png ├── sync_clk_assume.png ├── sync_resetless.png ├── sync_clk_no_assume.png ├── sync_flip_diagram.png ├── sync_load_diagram.png └── sync_reset_diagram.png ├── README.md ├── answers ├── e01_to_pennies.sby ├── e02_next_day.sby ├── e03_cell3x3.sby ├── e03_cell4x4.sby ├── e04_negate.sby ├── e05_counter.sby ├── e06_counter.sby ├── e04_signed_compare.sby ├── e07_counter.sby ├── e05_counter.py ├── util.py ├── e06_counter.py ├── e03_cell3x3.py ├── e07_counter.py ├── e04_negate.py ├── e04_signed_compare.py ├── e01_to_pennies.py ├── e03_cell4x4.py └── e02_next_day.py ├── challenge01.md ├── skeleton.py ├── util.py ├── skeleton_sync.py ├── 07_proof.md ├── 05_sync.md ├── 02_switch.md ├── 00_intro.md ├── 03_parts.md ├── 04_signs.md ├── 06_past.md └── 01_input.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | __pycache__ 3 | *.il 4 | *_bmc 5 | *_cover 6 | *_prove 7 | -------------------------------------------------------------------------------- /diagrams/if.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/if.png -------------------------------------------------------------------------------- /diagrams/past1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/past1.png -------------------------------------------------------------------------------- /diagrams/past2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/past2.png -------------------------------------------------------------------------------- /diagrams/bad_and.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/bad_and.png -------------------------------------------------------------------------------- /diagrams/cell3x3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/cell3x3.png -------------------------------------------------------------------------------- /diagrams/cell4x4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/cell4x4.png -------------------------------------------------------------------------------- /diagrams/ex1_tr0.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/ex1_tr0.JPG -------------------------------------------------------------------------------- /diagrams/next_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/next_day.png -------------------------------------------------------------------------------- /diagrams/past_t0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/past_t0.png -------------------------------------------------------------------------------- /diagrams/some_logic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/some_logic.png -------------------------------------------------------------------------------- /diagrams/sync_flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/sync_flip.png -------------------------------------------------------------------------------- /diagrams/sync_load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/sync_load.png -------------------------------------------------------------------------------- /diagrams/sync_reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/sync_reset.png -------------------------------------------------------------------------------- /diagrams/life_examples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/life_examples.png -------------------------------------------------------------------------------- /diagrams/nmigen_blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/nmigen_blocks.png -------------------------------------------------------------------------------- /diagrams/fixing_induction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/fixing_induction.png -------------------------------------------------------------------------------- /diagrams/induction_math.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/induction_math.png -------------------------------------------------------------------------------- /diagrams/past_and_friends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/past_and_friends.png -------------------------------------------------------------------------------- /diagrams/pennies_module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/pennies_module.png -------------------------------------------------------------------------------- /diagrams/simple_asserts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/simple_asserts.png -------------------------------------------------------------------------------- /diagrams/sync_clk_assume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/sync_clk_assume.png -------------------------------------------------------------------------------- /diagrams/sync_resetless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/sync_resetless.png -------------------------------------------------------------------------------- /diagrams/sync_clk_no_assume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/sync_clk_no_assume.png -------------------------------------------------------------------------------- /diagrams/sync_flip_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/sync_flip_diagram.png -------------------------------------------------------------------------------- /diagrams/sync_load_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/sync_load_diagram.png -------------------------------------------------------------------------------- /diagrams/sync_reset_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertBaruch/nmigen-exercises/HEAD/diagrams/sync_reset_diagram.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graded exercises for nMigen 2 | 3 | Work in progress! 4 | 5 | * [00 - Introduction](00_intro.md) 6 | * [01 - Counting coin: basic concepts](01_input.md) 7 | * [02 - The day after: more if, and switch-case](02_switch.md) 8 | * [03 - Life finds a way: parts of signals](03_parts.md) 9 | * [04 - Signs: signed signals](04_signs.md) 10 | * [05 - Synchronicity: synchronous signals](05_sync.md) 11 | * [06 - Living in the past: multi-step asserts](06_past.md) 12 | * [07 - Prove it! Formal verification by induction](07_proof.md) 13 | -------------------------------------------------------------------------------- /answers/e01_to_pennies.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | cover 3 | bmc 4 | 5 | [options] 6 | bmc: mode bmc 7 | cover: mode cover 8 | depth 1 9 | multiclock on 10 | 11 | [engines] 12 | cover: smtbmc z3 13 | bmc: smtbmc z3 14 | 15 | [script] 16 | read_verilog < Module: 19 | """Implements the logic for the Counter module.""" 20 | m = Module() 21 | 22 | with m.If(self.count == 9): 23 | m.d.sync += self.count.eq(1) 24 | with m.Else(): 25 | m.d.sync += self.count.eq(self.count+1) 26 | 27 | return m 28 | 29 | @classmethod 30 | def formal(cls) -> Tuple[Module, List[Signal]]: 31 | """Formal verification for the Counter module.""" 32 | m = Module() 33 | m.submodules.c = c = cls() 34 | 35 | m.d.comb += Assert((c.count >= 1) & (c.count <= 9)) 36 | m.d.comb += Cover(c.count == 3) 37 | 38 | return m, [] 39 | 40 | 41 | if __name__ == "__main__": 42 | main(Counter) 43 | -------------------------------------------------------------------------------- /skeleton.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assume, Assert, Cover 8 | 9 | from util import main 10 | 11 | 12 | class MyClass(Elaboratable): 13 | """Logic for my module. 14 | 15 | This is a skeleton for writing your own modules. 16 | """ 17 | 18 | def __init__(self): 19 | # Inputs 20 | self.my_input = Signal() 21 | 22 | # Outputs 23 | self.my_output = Signal() 24 | 25 | def elaborate(self, _: Platform) -> Module: 26 | """Implements the logic for my module.""" 27 | m = Module() 28 | 29 | m.d.comb += self.my_output.eq(self.my_input) 30 | 31 | return m 32 | 33 | @classmethod 34 | def formal(cls) -> Tuple[Module, List[Signal]]: 35 | """Formal verification for my module.""" 36 | m = Module() 37 | m.submodules.my_class = my_class = cls() 38 | 39 | # Make sure that the output is always the same as the input 40 | m.d.comb += Assert(my_class.my_input == my_class.my_output) 41 | 42 | # Cover the case where the output is 1. 43 | m.d.comb += Cover(my_class.my_output == 1) 44 | 45 | return m, [my_class.my_input] 46 | 47 | 48 | if __name__ == "__main__": 49 | main(MyClass) 50 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | """ 4 | This module provides various global utilities. 5 | """ 6 | import sys 7 | 8 | from nmigen.back import rtlil 9 | from nmigen.hdl import Fragment 10 | 11 | if sys.version_info < (3, 8): 12 | print("Python 3.8 or above is required") 13 | sys.exit(1) 14 | 15 | 16 | def main(cls, filename="toplevel.il"): 17 | """Runs a file in simulate or generate mode. 18 | 19 | Add this to your file: 20 | 21 | from util import main 22 | 23 | if __name__ == "__main__": 24 | main(YourClass) 25 | 26 | Then, you can run the file in simulate or generate mode: 27 | 28 | python sim will run YourClass.sim and output to whatever vcd 29 | file you wrote to. 30 | python gen will run YourClass.formal and output in RTLIL format 31 | to toplevel.il. You can then formally verify using 32 | sby -f . 33 | """ 34 | 35 | if len(sys.argv) < 2 or (sys.argv[1] != "sim" and sys.argv[1] != "gen"): 36 | print(f"Usage: python3 {sys.argv[0]} sim|gen") 37 | sys.exit(1) 38 | 39 | if sys.argv[1] == "sim": 40 | cls.sim() 41 | else: 42 | design, ports = cls.formal() 43 | fragment = Fragment.get(design, None) 44 | output = rtlil.convert(fragment, ports=ports) 45 | with open(filename, "w") as f: 46 | f.write(output) 47 | -------------------------------------------------------------------------------- /answers/util.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | """ 4 | This module provides various global utilities. 5 | """ 6 | import sys 7 | 8 | from nmigen.back import rtlil 9 | from nmigen.hdl import Fragment 10 | 11 | if sys.version_info < (3, 8): 12 | print("Python 3.8 or above is required") 13 | sys.exit(1) 14 | 15 | 16 | def main(cls, filename="toplevel.il"): 17 | """Runs a file in simulate or generate mode. 18 | 19 | Add this to your file: 20 | 21 | from util import main 22 | 23 | if __name__ == "__main__": 24 | main(YourClass) 25 | 26 | Then, you can run the file in simulate or generate mode: 27 | 28 | python sim will run YourClass.sim and output to whatever vcd 29 | file you wrote to. 30 | python gen will run YourClass.formal and output in RTLIL format 31 | to toplevel.il. You can then formally verify using 32 | sby -f . 33 | """ 34 | 35 | if len(sys.argv) < 2 or (sys.argv[1] != "sim" and sys.argv[1] != "gen"): 36 | print(f"Usage: python3 {sys.argv[0]} sim|gen") 37 | sys.exit(1) 38 | 39 | if sys.argv[1] == "sim": 40 | cls.sim() 41 | else: 42 | design, ports = cls.formal() 43 | fragment = Fragment.get(design, None) 44 | output = rtlil.convert(fragment, ports=ports) 45 | with open(filename, "w") as f: 46 | f.write(output) 47 | -------------------------------------------------------------------------------- /answers/e06_counter.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable, ClockSignal, ResetSignal, ClockDomain 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assume, Assert, Cover, Past, Initial, Rose 8 | 9 | from util import main 10 | 11 | 12 | class Counter(Elaboratable): 13 | """Logic for the Counter module.""" 14 | 15 | def __init__(self): 16 | self.count = Signal(4, reset=1) 17 | 18 | def elaborate(self, _: Platform) -> Module: 19 | """Implements the logic for the Counter module.""" 20 | m = Module() 21 | 22 | with m.If(self.count == 9): 23 | m.d.sync += self.count.eq(1) 24 | with m.Else(): 25 | m.d.sync += self.count.eq(self.count+1) 26 | 27 | return m 28 | 29 | @classmethod 30 | def formal(cls) -> Tuple[Module, List[Signal]]: 31 | """Formal verification for the Counter module.""" 32 | m = Module() 33 | m.submodules.c = c = cls() 34 | 35 | m.d.comb += Assert((c.count >= 1) & (c.count <= 9)) 36 | 37 | sync_clk = ClockSignal("sync") 38 | sync_rst = ResetSignal("sync") 39 | 40 | with m.If(Rose(sync_clk) & ~Initial()): 41 | with m.If(c.count == 1): 42 | m.d.comb += Assert(Past(c.count) == 9) 43 | with m.Else(): 44 | m.d.comb += Assert(c.count == (Past(c.count) + 1)) 45 | 46 | # Make sure the clock is clocking 47 | m.d.comb += Assume(sync_clk == ~Past(sync_clk)) 48 | 49 | # Don't want to test what happens when we reset. 50 | m.d.comb += Assume(~sync_rst) 51 | 52 | return m, [sync_clk, sync_rst] 53 | 54 | 55 | if __name__ == "__main__": 56 | main(Counter) 57 | -------------------------------------------------------------------------------- /answers/e03_cell3x3.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assume, Assert, Cover 8 | 9 | from util import main 10 | 11 | 12 | class Cell3x3(Elaboratable): 13 | """Logic for the Cell3x3 module.""" 14 | 15 | def __init__(self): 16 | self.input = Signal(9) 17 | self.output = Signal() 18 | 19 | def elaborate(self, _: Platform) -> Module: 20 | """Implements the logic for the Cell3x3 module.""" 21 | m = Module() 22 | 23 | neighbors = Signal(range(9)) 24 | c = self.input 25 | m.d.comb += neighbors.eq(c[0] + c[1] + c[2] + 26 | c[3] + c[5] + 27 | c[6] + c[7] + c[8]) 28 | 29 | middle = self.input[4] 30 | m.d.comb += self.output.eq(0) 31 | with m.If(middle): 32 | with m.If((neighbors == 2) | (neighbors == 3)): 33 | m.d.comb += self.output.eq(1) 34 | with m.Else(): 35 | with m.If(neighbors == 3): 36 | m.d.comb += self.output.eq(1) 37 | 38 | return m 39 | 40 | @classmethod 41 | def formal(cls) -> Tuple[Module, List[Signal]]: 42 | """Formal verification for the Cell3x3 module.""" 43 | m = Module() 44 | m.submodules.c = c = cls() 45 | 46 | s = 0 47 | for n in range(9): 48 | if n != 4: 49 | s += c.input[n] 50 | 51 | with m.If(s == 3): 52 | m.d.comb += Assert(c.output) 53 | with m.If(s == 2): 54 | m.d.comb += Assert(c.output == c.input[4]) 55 | 56 | return m, [c.input] 57 | 58 | 59 | if __name__ == "__main__": 60 | main(Cell3x3) 61 | -------------------------------------------------------------------------------- /answers/e07_counter.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable, ClockSignal, ResetSignal, ClockDomain 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assume, Assert, Cover, Past, Initial, Rose 8 | 9 | from util import main 10 | 11 | 12 | class Counter(Elaboratable): 13 | """Logic for the Counter module.""" 14 | 15 | def __init__(self): 16 | self.count = Signal(40, reset=1) 17 | 18 | def elaborate(self, _: Platform) -> Module: 19 | """Implements the logic for the Counter module.""" 20 | m = Module() 21 | 22 | with m.If(self.count == 999_999_999_999): 23 | m.d.sync += self.count.eq(1) 24 | with m.Else(): 25 | m.d.sync += self.count.eq(self.count+1) 26 | 27 | return m 28 | 29 | @classmethod 30 | def formal(cls) -> Tuple[Module, List[Signal]]: 31 | """Formal verification for the Counter module.""" 32 | m = Module() 33 | m.submodules.c = c = cls() 34 | 35 | m.d.comb += Assert((c.count >= 1) & (c.count <= 999_999_999_999)) 36 | 37 | sync_clk = ClockSignal("sync") 38 | sync_rst = ResetSignal("sync") 39 | 40 | with m.If(Rose(sync_clk) & ~Initial()): 41 | with m.If(c.count == 1): 42 | m.d.comb += Assert(Past(c.count) == 999_999_999_999) 43 | with m.Else(): 44 | m.d.comb += Assert(c.count == (Past(c.count) + 1)) 45 | 46 | # Make sure the clock is clocking 47 | m.d.comb += Assume(sync_clk == ~Past(sync_clk)) 48 | 49 | # Don't want to test what happens when we reset. 50 | m.d.comb += Assume(~sync_rst) 51 | 52 | return m, [sync_clk, sync_rst] 53 | 54 | 55 | if __name__ == "__main__": 56 | main(Counter) 57 | -------------------------------------------------------------------------------- /skeleton_sync.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen import ClockSignal, ResetSignal 7 | from nmigen.build import Platform 8 | from nmigen.asserts import Assume, Assert, Cover 9 | from nmigen.asserts import Past, Initial 10 | 11 | from util import main 12 | 13 | 14 | class MyClass(Elaboratable): 15 | """Logic for my module. 16 | 17 | This is a skeleton for writing your own modules. 18 | """ 19 | 20 | def __init__(self): 21 | # Inputs 22 | self.my_input = Signal() 23 | 24 | # Outputs 25 | self.my_output = Signal() 26 | 27 | def elaborate(self, _: Platform) -> Module: 28 | """Implements the logic for my module.""" 29 | m = Module() 30 | 31 | m.d.comb += self.my_output.eq(self.my_input) 32 | 33 | return m 34 | 35 | @classmethod 36 | def formal(cls) -> Tuple[Module, List[Signal]]: 37 | """Formal verification for my module.""" 38 | m = Module() 39 | m.submodules.my_class = my_class = cls() 40 | 41 | sync_clk = ClockSignal("sync") 42 | sync_rst = ResetSignal("sync") 43 | 44 | # Make sure that the output is always the same as the input 45 | m.d.comb += Assert(my_class.my_input == my_class.my_output) 46 | 47 | # Cover the case where the output is 1. 48 | m.d.comb += Cover(my_class.my_output == 1) 49 | 50 | # Make sure the clock is clocking 51 | m.d.comb += Assume(sync_clk == ~Past(sync_clk)) 52 | 53 | # Include this only if you don't want to test resets 54 | m.d.comb += Assume(~sync_rst) 55 | 56 | # Ensure sync's clock and reset signals are manipulable. 57 | return m, [sync_clk, sync_rst, my_class.my_input] 58 | 59 | 60 | if __name__ == "__main__": 61 | main(MyClass) 62 | -------------------------------------------------------------------------------- /answers/e04_negate.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assume, Assert, Cover 8 | from nmigen.lib.coding import PriorityEncoder 9 | 10 | from util import main 11 | 12 | 13 | class Negate(Elaboratable): 14 | """Logic for the Negate module.""" 15 | 16 | def __init__(self): 17 | self.input = Signal(64) 18 | self.output1 = Signal(64) # invert and add 1 19 | self.output2 = Signal(64) # direct negation 20 | self.output3 = Signal(64) # using priority encoder 21 | 22 | def elaborate(self, _: Platform) -> Module: 23 | """Implements the logic for the Negate module.""" 24 | m = Module() 25 | m.submodules.enc = enc = PriorityEncoder(64) 26 | 27 | m.d.comb += self.output1.eq(~self.input + 1) 28 | m.d.comb += self.output2.eq(-self.input) 29 | m.d.comb += enc.i.eq(self.input) 30 | 31 | with m.If(enc.n): 32 | m.d.comb += self.output3.eq(0) 33 | with m.Else(): 34 | neg1 = Signal(64) 35 | m.d.comb += neg1.eq(-1) 36 | # n 37 | # 1111111111000000 # mask = -1 << n 38 | # 0000000000111111 # lower_mask = ~(-1 << n) 39 | # 0000000001000000 # 1 << n 40 | mask = Signal(64) 41 | m.d.comb += mask.eq(neg1 << enc.o) 42 | lower_mask = ~mask 43 | m.d.comb += self.output3.eq((~self.input & mask) 44 | | (self.input & lower_mask) 45 | | (1 << enc.o)) 46 | 47 | return m 48 | 49 | @classmethod 50 | def formal(cls) -> Tuple[Module, List[Signal]]: 51 | """Formal verification for the Negate module.""" 52 | m = Module() 53 | m.submodules.c = c = cls() 54 | 55 | m.d.comb += Assert(c.output1 == c.output2) 56 | m.d.comb += Assert(c.output2 == c.output3) 57 | m.d.comb += Assert(c.output1[63] == (c.output1.as_signed() < 0)) 58 | 59 | return m, [c.input] 60 | 61 | 62 | if __name__ == "__main__": 63 | main(Negate) 64 | -------------------------------------------------------------------------------- /answers/e04_signed_compare.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assume, Assert, Cover 8 | from nmigen.lib.coding import PriorityEncoder 9 | 10 | from util import main 11 | 12 | 13 | class UnsignedComparator(Elaboratable): 14 | """Logic for the UnsignedComparator module.""" 15 | 16 | def __init__(self): 17 | self.a = Signal(16) 18 | self.b = Signal(16) 19 | self.lt = Signal() 20 | 21 | def elaborate(self, _: Platform) -> Module: 22 | """Implements the logic for the UnsignedComparator module.""" 23 | m = Module() 24 | 25 | m.d.comb += self.lt.eq(self.a < self.b) 26 | 27 | return m 28 | 29 | 30 | class SignedComparator(Elaboratable): 31 | """Logic for the SignedComparator module.""" 32 | 33 | def __init__(self): 34 | self.a = Signal(16) 35 | self.b = Signal(16) 36 | self.lt = Signal() 37 | 38 | def elaborate(self, _: Platform) -> Module: 39 | """Implements the logic for the SignedComparator module.""" 40 | m = Module() 41 | m.submodules.ucmp = ucmp = UnsignedComparator() 42 | 43 | ult = Signal() # Unsigned less than 44 | 45 | # Hook up the submodule 46 | m.d.comb += [ 47 | ucmp.a.eq(self.a), 48 | ucmp.b.eq(self.b), 49 | ult.eq(ucmp.lt), 50 | ] 51 | 52 | is_a_neg = self.a[15] 53 | is_b_neg = self.b[15] 54 | 55 | with m.If(~is_a_neg & ~is_b_neg): 56 | m.d.comb += self.lt.eq(ult) 57 | with m.Elif(is_a_neg & ~is_b_neg): 58 | m.d.comb += self.lt.eq(1) 59 | with m.Elif(~is_a_neg & is_b_neg): 60 | m.d.comb += self.lt.eq(0) 61 | with m.Else(): 62 | m.d.comb += self.lt.eq(ult) 63 | 64 | return m 65 | 66 | @classmethod 67 | def formal(cls) -> Tuple[Module, List[Signal]]: 68 | """Formal verification for the SignedComparator module.""" 69 | m = Module() 70 | m.submodules.c = c = cls() 71 | 72 | m.d.comb += Assert(c.lt == (c.a.as_signed() < c.b.as_signed())) 73 | 74 | return m, [c.a, c.b] 75 | 76 | 77 | if __name__ == "__main__": 78 | main(SignedComparator) 79 | -------------------------------------------------------------------------------- /answers/e01_to_pennies.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assume, Assert, Cover 8 | 9 | from util import main 10 | 11 | 12 | class ToPennies(Elaboratable): 13 | """Logic for the ToPennies module.""" 14 | 15 | def __init__(self): 16 | # Inputs 17 | self.pennies = Signal(8) 18 | self.nickels = Signal(4) 19 | self.dimes = Signal(4) 20 | self.quarters = Signal(4) 21 | self.dollars = Signal(4) 22 | 23 | # Outputs 24 | # The maximum is 2355 pennies, so we'll need 12 bits 25 | self.pennies_out = Signal(12) 26 | 27 | def elaborate(self, _: Platform) -> Module: 28 | """Implements the logic for the ToPennies module.""" 29 | m = Module() 30 | 31 | m.d.comb += self.pennies_out.eq(self.pennies + 32 | 5 * self.nickels + 33 | 10 * self.dimes + 34 | 25 * self.quarters + 35 | 100 * self.dollars) 36 | 37 | return m 38 | 39 | @classmethod 40 | def formal(cls) -> Tuple[Module, List[Signal]]: 41 | """Formal verification for the ToPennies module.""" 42 | m = Module() 43 | m.submodules.to_pennies = to_pennies = cls() 44 | 45 | m.d.comb += Cover((to_pennies.pennies == 37) & 46 | (to_pennies.nickels == 3) & 47 | (to_pennies.dimes == 10) & 48 | (to_pennies.quarters == 5) & 49 | (to_pennies.dollars == 2)) 50 | 51 | m.d.comb += Cover(to_pennies.pennies_out == 548) 52 | 53 | m.d.comb += Cover((to_pennies.pennies_out == 64) & 54 | (to_pennies.nickels == 2 * to_pennies.dimes) & 55 | (to_pennies.dimes > 0)) 56 | 57 | m.d.comb += Assert((to_pennies.pennies_out % 5) 58 | == (to_pennies.pennies % 5)) 59 | 60 | with m.If(to_pennies.pennies == 0): 61 | m.d.comb += Assert((to_pennies.pennies % 5) == 0) 62 | 63 | return m, [to_pennies.pennies, to_pennies.nickels, to_pennies.dimes, 64 | to_pennies.quarters, to_pennies.dollars] 65 | 66 | 67 | if __name__ == "__main__": 68 | main(ToPennies) 69 | -------------------------------------------------------------------------------- /answers/e03_cell4x4.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assume, Assert, Cover 8 | 9 | from e03_cell3x3 import Cell3x3 10 | from util import main 11 | 12 | 13 | class Cell4x4(Elaboratable): 14 | """Logic for the Cell4x4 module.""" 15 | 16 | def __init__(self): 17 | self.input = Signal(16) 18 | self.output = Signal(4) 19 | 20 | def elaborate(self, _: Platform) -> Module: 21 | """Implements the logic for the Cell4x4 module.""" 22 | m = Module() 23 | 24 | c = [Cell3x3(), Cell3x3(), Cell3x3(), Cell3x3()] 25 | m.submodules += c 26 | 27 | # Mapping: inputs: 28 | # 29 | # 0 1 2 3 30 | # 4 5 6 7 31 | # 8 9 10 11 32 | # 12 13 14 15 33 | # 34 | # outputs: 35 | # 36 | # 0 1 37 | # 2 3 38 | 39 | # Hook up the submodules 40 | m.d.comb += [ 41 | c[0].input[0].eq(self.input[0]), 42 | c[0].input[1].eq(self.input[1]), 43 | c[0].input[2].eq(self.input[2]), 44 | c[0].input[3].eq(self.input[4]), 45 | c[0].input[4].eq(self.input[5]), 46 | c[0].input[5].eq(self.input[6]), 47 | c[0].input[6].eq(self.input[8]), 48 | c[0].input[7].eq(self.input[9]), 49 | c[0].input[8].eq(self.input[10]), 50 | c[1].input[0:].eq(self.input[1:4]), 51 | c[1].input[3:].eq(self.input[5:8]), 52 | c[1].input[6:].eq(self.input[9:12]), 53 | c[2].input[0:].eq(self.input[4:7]), 54 | c[2].input[3:].eq(self.input[8:11]), 55 | c[2].input[6:].eq(self.input[12:15]), 56 | c[3].input[0:].eq(self.input[5:8]), 57 | c[3].input[3:].eq(self.input[9:12]), 58 | c[3].input[6:].eq(self.input[13:]), 59 | ] 60 | 61 | m.d.comb += [ 62 | self.output[0].eq(c[0].output), 63 | self.output[1].eq(c[1].output), 64 | self.output[2].eq(c[2].output), 65 | self.output[3].eq(c[3].output), 66 | ] 67 | 68 | return m 69 | 70 | @classmethod 71 | def formal(cls) -> Tuple[Module, List[Signal]]: 72 | """Formal verification for the Cell4x4 module.""" 73 | m = Module() 74 | m.submodules.c = c = cls() 75 | 76 | m.d.comb += Cover( 77 | (c.output[0] == c.input[5]) & 78 | (c.output[1] == c.input[6]) & 79 | (c.output[2] == c.input[9]) & 80 | (c.output[3] == c.input[10]) & 81 | (c.output != 0) 82 | ) 83 | 84 | with m.If((c.output == 0b1111) & 85 | (c.input[5:7] == 0b11) & 86 | (c.input[9:11] == 0b11)): 87 | m.d.comb += Assert(c.input == 0b0000011001100000) 88 | return m, [c.input] 89 | 90 | 91 | if __name__ == "__main__": 92 | main(Cell4x4) 93 | -------------------------------------------------------------------------------- /07_proof.md: -------------------------------------------------------------------------------- 1 | # Exercise 7: Prove it! 2 | 3 | ## What you'll do: 4 | 5 | Change your counter so that instead of going from 1 to 9, it goes from 1 to 999,999,999,999 and then to 1 again. 6 | 7 | Use prove mode instead of bmc mode to show that your counter still does what it is supposed to. 8 | 9 | ## Proof by induction 10 | 11 | Bounded model checking is nice, but requires you to run the circuit for as many time steps as necessary to hit all states. Clearly for a 40-bit counter like the above, it just doesn't make sense to run bounded model checking for a trillion steps. 12 | 13 | Instead, we rely on proof by induction, which is a mathematical technique used to prove that something works for *any* number of steps. 14 | 15 | Suppose we had some function, call it `f`, that takes an integer parameter `k`. Let's suppose the function outputs true or false, and we want to show that `f(k)` is true for every value of `k` starting with, say, 0. To do this, we first show that `f(0)` is true. This is called the *base case*. Then we show that *if we assume* that `f(n)` is true for some `n`, *then* `f(n+1)` is also true. This is called the *induction step*. 16 | 17 | Notice that we don't have to prove that `f(n)` is true, just assume it is, and based on that see if `f(n+1)` is true. 18 | 19 | So if we can show that the base case is true, and that the inductive step is true, we can conclude that logically, every `f(k)`, no matter what `k` is, is true (for `k >= 0`). 20 | 21 | ![Inductive reasoning](diagrams/induction_math.png) 22 | 23 | ## Formal verification by induction 24 | 25 | In formal verification, we can use induction, but only if we carefully craft the base case and induction step: 26 | 27 | The base case: Bounded model checking for N steps passes. 28 | 29 | The inductive step: Starting from any valid state, proceeding for N+1 steps passes. 30 | 31 | The `N` mentioned above is the `depth` parameter in the `[options]` section of sby file. And, instead of specifying `bmc` as the `mode` in `[options]` and in `[engines]`, we specify `prove` to perform verification by induction. 32 | 33 | Formal verification by induction is a powerful technique. However, in complex circuits the state space might be too large to fully assert on, so you may need to do many rounds of debugging to find out why the inductive step found a series of N valid states followed by an invalid (i.e. assertion-breaking) state. Most likely you just need to figure out whether the first state it chose (or any of the states along the path) is valid, and if not, eliminate it with another assert. 34 | 35 | ![A bad induction trace](diagrams/fixing_induction.png) 36 | 37 | But once you've done that, you'll know that from its initial state, your circuit will not break any assert for *any* number of time steps. 38 | 39 | ## Your turn 40 | 41 | Use the supplied [`answers/e07_counter.sby`](answers/e07_counter.sby) file and run it in prove mode: 42 | 43 | ``` 44 | sby -f answers/e07_counter.sby prove 45 | ``` 46 | 47 | The depth has been kept at 22 like before. But, you can reduce that to as little as 2 steps, which covers one positive edge of the clock, and therefore any transition of the counter. Of course, if you have cover statements, you may need to increase the depth. 48 | 49 | # Stumped? 50 | 51 | Well, there shouldn't be any change except to your counter logic, and using prove mode. But if you need it, the answer is in [`answers/e07_counter.py`](answers/e07_counter.py) 52 | -------------------------------------------------------------------------------- /05_sync.md: -------------------------------------------------------------------------------- 1 | # Exercise 5: Synchronicity 2 | 3 |

4 | A connecting principle, linked to the invisible... 5 |

6 | 7 | ------ 8 | ## What you'll do: 9 | 10 | Create a module that counts from 1 to 9 and back to 1 again. Formally verify that the output can never be greater than 9 and can never be 0. Cover the case where the output is 3. 11 | 12 | ## Synchronous domains 13 | 14 | Unlike combinatorial domains, synchronous domains are associated with a clock signal and a reset signal. Assignments that are added to a synchronous domain take place on one of its clock's edge, the positive edge by default. 15 | 16 | The `sync` domain is the default domain that is always present: 17 | 18 | ```python 19 | x = Signal() 20 | m.d.sync += x.eq(~x) 21 | ``` 22 | 23 | The above will flip `x` on every positive edge of the sync clock. 24 | 25 | ![Flipping the x signal, diagram](diagrams/sync_flip_diagram.png) 26 | ![Flipping the x signal, waveforms](diagrams/sync_flip.png) 27 | 28 | You can only drive a signal from one domain. This would result in a driver-driver conflict: 29 | 30 | ```python 31 | x = Signal() 32 | m.d.sync += x.eq(~x) 33 | m.d.comb += x.eq(0) 34 | ``` 35 | 36 | ## Synchronous resets 37 | 38 | A signal driven from a synchronous domain gets reset when that domain's reset signal goes high and the clock's edge happens, or on circuit initialization. The reset value of the signal is set by the `reset` key when creating the signal, and defaults to 0: 39 | 40 | ```python 41 | x = Signal(reset=1) 42 | m.d.sync += x.eq(~x) 43 | ``` 44 | 45 | ![The x signal resets to 1, diagram](diagrams/sync_reset_diagram.png) 46 | ![The x signal resets to 1, waveforms](diagrams/sync_reset.png) 47 | 48 | You can prevent the signal from getting reset, except on circuit initialization, by using the key `reset_less`: 49 | 50 | ```python 51 | x = Signal(reset=1, reset_less=True) 52 | m.d.sync += x.eq(~x) 53 | ``` 54 | 55 | ![Resetless, diagram](diagrams/sync_resetless.png) 56 | 57 | ## Synchronous signals are registers 58 | 59 | If a synchronous signal isn't assigned on an edge, it retains its value. 60 | 61 | ```python 62 | x = Signal(8) 63 | load = Signal() 64 | loadval = Signal(8) 65 | 66 | with m.If(load): 67 | m.d.sync += x.eq(loadval) 68 | ``` 69 | 70 | For the above, only if `load` is high will `x` be loaded with `loadval` on the sync clock edge. 71 | 72 | ![Loading, diagram](diagrams/sync_load_diagram.png) 73 | ![Loading, waveforms](diagrams/sync_load.png) 74 | 75 | You can think of the domain's reset signal as loading all registers in that domain with their reset values (unless they're `reset_less`). 76 | 77 | ## Bounded model checking 78 | 79 | Up until now, we've been using bounded model checking to assert on combinatorial logic. However, with sequential logic, signals can change with time. So, bounded model checking specifies the number of time steps to take after the initial reset. This is specified in the `[options]` section of the sby file, as the value of `depth`. That's why it's called "bounded model checking", because it checks your model only up to the depth you specify. 80 | 81 | This means that you will have to know how long to run your verification before you can be sure all your asserts are checked. Later we will see a more powerful technique (induction) which doesn't have such a painful requirement. 82 | 83 | What exactly is "a time step"? It's a change in a clock signal. So, a full cycle of the sync clock would be two time steps. 84 | 85 | ----- 86 | 87 | This is only true if the `multiclock` parameter in `[options]` is set to `on`. Unless you really really know what you're doing, leave it set to `on`. 88 | 89 | ----- 90 | 91 | ## The domain for Asserts and Covers 92 | 93 | Unless you really really know what you're doing, keep asserts and covers in the combinatorial domain. The asserts will be checked on every time step. 94 | 95 | ## Your turn 96 | 97 | You can use the [answers/e05_counter.sby](answers/e05_counter.sby) file to run your formal verification. It is set up for 22 time steps, or 11 cycles of the clock. This should be enough to have the counter cycle around once. 98 | -------------------------------------------------------------------------------- /answers/e02_next_day.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assume, Assert, Cover 8 | 9 | from util import main 10 | 11 | 12 | class NextDay(Elaboratable): 13 | """Logic for the NextDay module.""" 14 | 15 | def __init__(self): 16 | # Inputs 17 | self.year = Signal(range(1, 10000)) 18 | self.month = Signal(range(1, 13)) 19 | self.day = Signal(range(1, 32)) 20 | 21 | # Outputs 22 | self.next_year = Signal(range(1, 10001)) 23 | self.next_month = Signal.like(self.month) 24 | self.next_day = Signal.like(self.day) 25 | self.invalid = Signal() 26 | 27 | def elaborate(self, _: Platform) -> Module: 28 | """Implements the logic for the NextDay module.""" 29 | m = Module() 30 | 31 | is_leap_year = Signal() 32 | max_day = Signal.like(self.day) 33 | 34 | m.d.comb += is_leap_year.eq(0) # We can override this below! 35 | with m.If((self.year % 4) == 0): 36 | m.d.comb += is_leap_year.eq(1) 37 | with m.If((self.year % 100) == 0): 38 | m.d.comb += is_leap_year.eq(0) 39 | with m.If((self.year % 400) == 0): 40 | m.d.comb += is_leap_year.eq(1) 41 | 42 | with m.Switch(self.month): 43 | with m.Case(1, 3, 5, 7, 8, 10, 12): 44 | m.d.comb += max_day.eq(31) 45 | with m.Case(2): 46 | m.d.comb += max_day.eq(28 + is_leap_year) 47 | with m.Default(): 48 | m.d.comb += max_day.eq(30) 49 | 50 | m.d.comb += self.next_year.eq(self.year) 51 | m.d.comb += self.next_month.eq(self.month) 52 | m.d.comb += self.next_day.eq(self.day + 1) 53 | 54 | with m.If(self.day == max_day): 55 | m.d.comb += self.next_day.eq(1) 56 | m.d.comb += self.next_month.eq(self.month + 1) 57 | with m.If(self.month == 12): 58 | m.d.comb += self.next_month.eq(1) 59 | m.d.comb += self.next_year.eq(self.year + 1) 60 | 61 | m.d.comb += self.invalid.eq(0) 62 | with m.If((self.day < 1) | (self.day > max_day) | 63 | (self.month < 1) | (self.month > 12) | 64 | (self.year < 1) | (self.year > 9999)): 65 | m.d.comb += self.invalid.eq(1) 66 | 67 | with m.If(self.invalid): 68 | m.d.comb += self.next_year.eq(0) 69 | m.d.comb += self.next_month.eq(0) 70 | m.d.comb += self.next_day.eq(0) 71 | 72 | return m 73 | 74 | @classmethod 75 | def formal(cls) -> Tuple[Module, List[Signal]]: 76 | """Formal verification for the NextDay module.""" 77 | m = Module() 78 | m.submodules.nd = nd = cls() 79 | 80 | # We don't have to create a signal here. Using is_zero is like just copying the logic. 81 | is_zero = ((nd.next_year == 0) & 82 | (nd.next_month == 0) & 83 | (nd.next_day == 0)) 84 | m.d.comb += Assert(nd.invalid == is_zero) 85 | 86 | all_nonzero = ((nd.next_year != 0) & 87 | (nd.next_month != 0) & 88 | (nd.next_day != 0)) 89 | m.d.comb += Assert(all_nonzero | is_zero) 90 | 91 | with m.If(~nd.invalid): 92 | with m.If(nd.day == 31): 93 | m.d.comb += Assert(nd.next_day == 1) 94 | with m.If((nd.month == 12) & (nd.day == 31)): 95 | m.d.comb += Assert(nd.next_month == 1) 96 | 97 | with m.If(((nd.year % 2) == 1) & (nd.month == 2) & (nd.day == 29)): 98 | m.d.comb += Assert(nd.invalid) 99 | 100 | m.d.comb += Cover((nd.next_month == 2) & (nd.next_day == 29)) 101 | 102 | return m, [nd.year, nd.month, nd.day] 103 | 104 | 105 | if __name__ == "__main__": 106 | main(NextDay) 107 | -------------------------------------------------------------------------------- /02_switch.md: -------------------------------------------------------------------------------- 1 | # Exercise 2: The day after 2 | 3 |

4 | The 14th of January is World Logic Day. 5 |

6 | 7 | ---- 8 | ## What you'll do: 9 | 10 | Create a module that takes as inputs a date, consisting of: 11 | 12 | * The Anno Domini year (1-9999) 13 | * The month number (1-12) 14 | * The day number (1-31) 15 | 16 | And outputs the day number, month number, and year for the following day. If the inputs aren't a valid date in this range, output all zeros and raise an "invalid" signal. Don't bother with the Julian/Gregorian changeover, just assume that all days are in the Gregorian calendar. The technical term for Gregorian dates during the Julian calendar is "proleptic Gregorian". 17 | 18 | ![Inputs and outputs to the module](diagrams/next_day.png) 19 | 20 | A leap year occurs if the year is divisible by four, unless the year is divisible by 100. But if the year is divisible by 400, it's a leap year. For example, 1900 was not a leap year, but 2000 was. 21 | 22 | The number of days in the month goes according to this table: 23 | 24 | | Month number | Days in month | 25 | |--------------|---------------| 26 | | 1 | 31 | 27 | | 2 | 28, or 29 in a leap year | 28 | | 3 | 31 | 29 | | 4 | 30 | 30 | | 5 | 31 | 31 | | 6 | 30 | 32 | | 7 | 31 | 33 | | 8 | 31 | 34 | | 9 | 30 | 35 | | 10 | 31 | 36 | | 11 | 30 | 37 | | 12 | 31 | 38 | 39 | You will also formally verify that: 40 | 41 | * An all zero numeric output always happens with invalid. 42 | * Either all numeric outputs are zero, or no numeric outputs are. 43 | * The day after 31 in a valid date is always 1. 44 | * The month after 31 Dec in a valid date is always 1. 45 | * 29 Feb in an odd year is always invalid. 46 | * What inputs can lead to an output date of 29 Feb? 47 | 48 | ### More about if-else 49 | 50 | In the previous exercise we discussed `with m.If` and `with m.Else`. You can also add `with m.Elif` to the mix: 51 | 52 | ```python 53 | with m.If(x == 10): 54 | m.d.comb += z.eq(50) 55 | with m.Elif((x == 11) | (x == 13)): 56 | m.d.comb += z.eq(40) 57 | with m.Elif(x == 12): 58 | m.d.comb += z.eq(30) 59 | with m.Else(): 60 | m.d.comb += z.eq(0) 61 | ``` 62 | 63 | ### The switch-case 64 | 65 | The switch-case compares a given signal to any number of cases. It's equivalent to a whole if-else chain, but is sometimes more convenient. This is the equivalent of the example above: 66 | 67 | ```python 68 | with m.Switch(x): 69 | with m.Case(10): 70 | m.d.comb += z.eq(50) 71 | with m.Case(11, 13): 72 | m.d.comb += z.eq(40) 73 | with m.Case(12): 74 | m.d.comb += z.eq(30) 75 | with m.Default(): 76 | m.d.comb += z.eq(0) # If no case matches 77 | ``` 78 | 79 | ### Specifying the range of a signal 80 | 81 | You don't always have to figure out the number of bits a signal needs. You can specify its range using Python's `range` like so: 82 | 83 | ```python 84 | x = Signal(range(stop)) 85 | y = Signal(range(start, stop)) 86 | ``` 87 | 88 | This means that the signal will have as many bits as it takes to represent any integer between start (inclusive) and stop (exclusive). Excluding the stop value is consistent with the way `range` works in Python. So, a signal defined by `Signal(range(1, 8))` would be 3 bits wide, to represent all the integers between 1 and 7 (inclusive). 89 | 90 | Note that this doesn't perform any kind of check on the values you assign to the signal. A signal "starting" from 1 can still be assigned 0. 91 | 92 | ### This signal is like that signal 93 | 94 | If you want a signal to have the same shape as an existing signal, then you can use `like`: 95 | 96 | ```python 97 | x = Signal(range(1, 10)) 98 | y = Signal.like(x) 99 | ``` 100 | 101 | With this, `y` will have the same shape as `x`. 102 | 103 | ## Your turn 104 | 105 | As before, you will need to: 106 | 107 | * Start from `skeleton.py`. 108 | * Define your inputs and outputs. 109 | * Write the logic for the module in `elaborate`. 110 | * Write your asserts in `formal`. 111 | * Make sure it compiles with `python3 your_file.py gen`. 112 | * Run formal verification in cover mode using `sby -f answers/e02_next_day.sby cover`. 113 | * Run formal verification in BMC mode using `sby -f answers/e02_next_day.sby bmc`. 114 | 115 | If you want, you can run all formal verification modes by leaving off the mode: `sby -f answers/e02_next_day.sby`. This actually runs all the things specified in the `[tasks]` section in the sby file. 116 | 117 | ## Stumped? 118 | 119 | The answer to this exercise is in [`answers/e02_next_day.py`](answers/e02_next_day.py). 120 | -------------------------------------------------------------------------------- /00_intro.md: -------------------------------------------------------------------------------- 1 | # What's this? 2 | 3 | This is a series of graded exercises in using nMigen, the Python-based Hardware Design Language, to design and verify digital circuits! 4 | 5 | It's also a work in progress. 6 | 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! 8 | 9 | ## Prerequisite knowledge 10 | 11 | You will need knowledge of: 12 | 13 | * Basic digital electronics (do you know what an AND gate is? A flip-flop?) 14 | * Basic Python 3 (do you know how to write a class?) 15 | * How to run things on the command-line (do you know how to use command-line options? What a PATH is?) 16 | 17 | ## Software prerequisites 18 | 19 | If you're using Windows, I hope you're using Windows Subsystem for Linux. If not, you're on your own. 20 | 21 | ### Option 1: Docker (recommended) 22 | 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. 24 | 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. 26 | 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. 28 | 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 69 | ``` 70 | 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: 74 | 75 | Open File > Preferences > Settings, look for pylint args, and add: 76 | 77 | ``` 78 | --contextmanager-decorators=contextlib.contextmanager,nmigen.hdl.dsl._guardedcontextmanager 79 | ``` 80 | 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. 82 | -------------------------------------------------------------------------------- /03_parts.md: -------------------------------------------------------------------------------- 1 | # Exercise 3: Life finds a way 2 | 3 | ## What you'll do, part 1: 4 | 5 | Create a module that determines the state of a cell in the Game of Life. It takes as input: 6 | 7 | * A 9-bit signal encoding the a 3x3 area, where a 0 is a dead cell and a 1 is a live cell. 8 | 9 | The positions of the cells are encoded with bit positions as follows: 10 | 11 | | | -1 | 0 | +1 | 12 | |--:|----|---|----| 13 | | -1: | 0 | 1 | 2 | 14 | | 0: | 3 | 4 | 5 | 15 | | +1: | 6 | 7 | 8 | 16 | 17 | Bit position 4 is the "middle cell", while the other bit positions are its neighbors. 18 | 19 | The output is a 1 if the middle cell becomes live, or 0 if the middle cell becomes dead. The rules are: 20 | 21 | * If the middle cell is alive, and has exactly 2 or 3 live neighbors, it stays alive. 22 | * If the middle cell is dead, and has exactly 3 live neighbors, it becomes alive. 23 | * Otherwise, the middle cell becomes dead. 24 | 25 | ![Life examples](diagrams/life_examples.png) 26 | 27 | Formally verify that: 28 | 29 | * If the middle cell, dead or alive, has exactly 3 live neighbors, it becomes alive. 30 | * If the middle cell has exactly 2 live neighbors, it retains its state. 31 | 32 | ![Diagram for 3x3 module](diagrams/cell3x3.png) 33 | 34 | ### Bit positions 35 | 36 | You can extract sequential bits from a signal using Python's slice notation: 37 | 38 | ```python 39 | x = Signal(16) 40 | y = x[0] # Extracts the least significant bit 41 | z = x[-1] # Extracts the most significant bit 42 | a = x[1:4] # a extracts bits 1, 2, and 3 of x. 43 | ``` 44 | 45 | Some tricks you can play: 46 | 47 | ```python 48 | x = Signal(16) 49 | b = x[:8] # b extracts the least significant eight bits of x (i.e. bits 0-7). 50 | c = x[8:] # c extracts the most significant eight bits of x (i.e. bits 8-15). 51 | y = x[::-1] # y extracts the bits of x in reverse. 52 | z = x[::2] # z extracts every other bit of x. 53 | ``` 54 | 55 | All of the above are expressions, and you can use them in other expressions: 56 | 57 | ```python 58 | x = Signal(16) 59 | with m.If(x[0]): 60 | # x is odd 61 | with m.Else(): 62 | # x is even 63 | ``` 64 | 65 | ```python 66 | x = Signal(16) 67 | with m.Switch(x[:2]): 68 | with m.Case(0): 69 | # x % 4 == 0 70 | with m.Case(1): 71 | # x % 4 == 1 72 | with m.Case(2): 73 | # x % 4 == 2 74 | with m.Case(3): 75 | # x % 4 == 3 76 | ``` 77 | 78 | ```python 79 | x = Signal(16) 80 | y = x[:3] + 3 81 | ``` 82 | 83 | You can also assign bit positions in a signal. For example, this could implement a left rotate: 84 | 85 | ```python 86 | x = Signal(16) 87 | y = Signal(16) 88 | 89 | m.d.comb += y[0].eq(x[-1]) 90 | m.d.comb += y[1:].eq(x) 91 | ``` 92 | 93 | Here's a left shift into a carry bit: 94 | 95 | ```python 96 | x = Signal(16) 97 | y = Signal(16) 98 | c = Signal() 99 | 100 | m.d.comb += c.eq(x[-1]) 101 | m.d.comb += y[0].eq(0) # Or equivalently: 102 | m.d.comb += y[1:].eq(x) # y.eq(x << 1) 103 | ``` 104 | 105 | ## What you'll do, part 2: 106 | 107 | Create a 4x4 module (so it has a 16-bit input) that outputs the middle 4 cells for the next time step. Use copies of your 3x3 module to achieve this. 108 | 109 | Cover the case where the middle four cells don't change state, and at least one of the cells ends up alive. 110 | 111 | Also, prove that if the outputs are all ones, and the four middle inputs are all ones, then all the other inputs must be zero. 112 | 113 | ![Diagram for 4x4 module](diagrams/cell4x4.png) 114 | 115 | ### Submodules 116 | 117 | As in the `formal` function, you can add submodules to a module: 118 | 119 | ```python 120 | m = Module() 121 | m.submodules.my_submodule = my_submodule = MySubmodule() 122 | m.submodules.another_submodule = another_submodule = AnotherSubmodule() 123 | ``` 124 | 125 | Then you can hook up their inputs and outputs: 126 | 127 | ```python 128 | m.d.comb += another_submodule.input0.eq(my_submodule.output1) 129 | ``` 130 | 131 | ### A whole bunch of submodules! 132 | 133 | You don't have to add submodules to `m.submodules` one at a time. You can add an array of submodules: 134 | 135 | ```python 136 | m = Module() 137 | a = MySubmodule1() 138 | b = MySubmodule2() 139 | c = MySubmodule3() 140 | m.submodules += [a, b, c] 141 | ``` 142 | 143 | ### A whole bunch of statments! 144 | 145 | You don't have to add statements to `m.d.comb` one at a time. You can add an array of statements: 146 | 147 | ```python 148 | m.d.comb += [ 149 | x.eq(2), 150 | another_submodule.input3.eq(y << 4), 151 | z.eq(my_submodule.output2), 152 | ] 153 | ``` 154 | 155 | ## Stumped? 156 | 157 | The answers are in [`answers/e03_cell3x3.py`](answers/e03_cell3x3.py) and [`answers/e03_cell4x4.py`](answers/e03_cell4x4.py) -------------------------------------------------------------------------------- /04_signs.md: -------------------------------------------------------------------------------- 1 | # Exercise 4: Signs 2 | 3 | ## What you'll do, part 1: 4 | 5 | Write a module that takes a 64-bit signed number, and negates it in three ways: 6 | 7 | * Invert and add 1. 8 | * Directly negate it. 9 | * Starting from the least significant bit, copy up to and including the first 1, then invert the remaining bits. 10 | 11 | That last one seems a bit strange, but it works like this (using 8-bit numbers): 12 | 13 | Given as input `11101000`, copy up to and including the first 1 (`1000`), then invert the remaining bits: `00011000`. Compare this to inverting first (`00010111`) and adding one: `00011000`. Hey, at least the last technique doesn't require an adder. 14 | 15 | Formally verify that: 16 | 17 | * These three techniques always result in the same number. 18 | * The most significant bit of the number being set always means it is less than 0. 19 | 20 | When you complete this exercise, think about how long the formal verification engine took to run. Formal verification is not a brute-force process. If it were, then even at one check per nanosecond, it would take over 500 years to verify every 64-bit number. Rather, the formal verification engine is a solver, technically an [SMT solver](https://en.wikipedia.org/wiki/Satisfiability_modulo_theories), and every built-in theory such a solver has enables the solver to shortcut some brute-force approaches. The solver we have configured to use in the `[engines]` section of the sby file is the [Z3 solver](https://en.wikipedia.org/wiki/Z3_Theorem_Prover), which has overall good performance. 21 | 22 | ### Signedness 23 | 24 | Up until now, when you've created a Signal, you've created unsigned signals. This means that arithmetic and comparisons on them are unsigned. So, the 4-bit unsigned number `1000` is always *greater* than the 4-bit unsigned number `0111`. 25 | 26 | However, in 2's complement, `1000` is -8 while `0111` is 7, which means that if these 4-bit signals were treated as *signed*, `1000` should be *less* than `0111`. 27 | 28 | ```python 29 | x = Signal(signed(4)) 30 | y = Signal(signed(4)) 31 | m.d.comb += [ 32 | x.eq(0b1000), 33 | y.eq(0b0111), 34 | Assert(x < y), 35 | ] 36 | ``` 37 | 38 | To treat an unsigned (or signed) signal as signed: 39 | 40 | ```python 41 | x = Signal(4) 42 | y = Signal(4) 43 | m.d.comb += [ 44 | x.eq(0b1000), 45 | y.eq(0b0111), 46 | Assert(x.as_signed() < y.as_signed()), 47 | ] 48 | ``` 49 | 50 | There is also a corresponding `as_unsigned()` function to convert a signed (or unsigned) signal to an unsigned signal. 51 | 52 | Negating a signal always results in a signed signal, regardless of whether the input signal is signed or unsigned: 53 | 54 | ```python 55 | x = Signal(4) 56 | y = Signal(4) 57 | m.d.comb += [ 58 | x.eq(0b0011), 59 | y.eq(0b1101), 60 | Assert(y > x), 61 | Assert(-x < x), 62 | ] 63 | ``` 64 | 65 | Equality, on the other hand, does not take into account signedness. It is bit-for-bit equality: 66 | 67 | ```python 68 | x = Signal(4) 69 | y = Signal(4) 70 | m.d.comb += [ 71 | x.eq(0b0011), 72 | y.eq(0b1101), 73 | Assert(y > x), 74 | Assert(-x == y), 75 | ] 76 | ``` 77 | 78 | All arithmetic operations that apply to signed signals are signed. For example, right shifts do take into account signedness, and the result is the same signedness as the input: 79 | 80 | ```python 81 | x = Signal(4) 82 | y = Signal(signed(4)) 83 | m.d.comb += [ 84 | x.eq(0b1000), 85 | y.eq(0b1000), 86 | Assert((x >> 1) == 0b0100), 87 | Assert((y >> 1) == 0b1100), 88 | ] 89 | ``` 90 | 91 | ### Library: priority encoder 92 | 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. 94 | 95 | ```python 96 | from nmigen.lib.coding import PriorityEncoder 97 | 98 | enc = PriorityEncoder(width=8) 99 | input = Signal(8) 100 | output = Signal(3) 101 | bit_set = Signal() 102 | 103 | m.d.comb += [ 104 | enc.i.eq(input), 105 | output.eq(enc.o), 106 | bit_set.eq(~enc.n), # n really means that the input is zero 107 | ] 108 | ``` 109 | 110 | For example, the input `0101000` will result in the output being 3 and bit_set being high. 111 | 112 | ### Limitations on slices 113 | 114 | Slices such as `x[1:4]` require integers as indices, not signals. So you can't have: 115 | 116 | ```python 117 | x = Signal(8) 118 | y = Signal(3) 119 | z = x[y] # Results in an error 120 | ``` 121 | 122 | You can achieve the same effect using bit tricks. For example, `1 << y` gives you a 1 in the yth bit position. `(x >> y) & 1` gives you the yth bit of x. 123 | 124 | What does `(-1 << y) & x` give you? 125 | 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. 129 | 130 | ----- 131 | 132 | ## What you'll do, part 2: 133 | 134 | 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. 135 | 136 | Now write another module that uses the above module, and based only the most significant bits (i.e. the sign bits) of the original inputs, and the output of the unsigned comparator, outputs whether the first is less than the second, treated as *signed* numbers. The idea here is that you're limited to an unsigned comparator, and need a signed comparator. 137 | 138 | Formally verify that the output of your module is correct, by using `as_signed()`. 139 | 140 | ## Stumped? 141 | 142 | The answers are in [`answers/e04_negate.py`](answers/e04_negate.py) and [`answers/e04_signed_compare.py`](answers/e04_signed_compare.py) 143 | -------------------------------------------------------------------------------- /06_past.md: -------------------------------------------------------------------------------- 1 | # Exercise 6: Living in the past 2 | 3 | ## What you'll do: 4 | 5 | In the previous exercise, you showed that the output of a counter that counted from 1 to 9, going back to 1, was never 0 and was never greater than 9. You also covered the case where the output was 3. Hopefully you saw that the output started at 1, went to 2, and then to 3. 6 | 7 | Show that each positive edge of the clock increments the counter by one, and that the counter goes from 9 back to 1. 8 | 9 | ## The Past 10 | 11 | In formal verification, you can refer to the value of a signal as it was one time step ago by using `Past`: 12 | 13 | ```python 14 | from nmigen.asserts import Past 15 | 16 | m.d.comb += Assert((x == 2) & (Past(x) == 1)) 17 | ``` 18 | 19 | The above asserts that `x` is now 2, but one time step ago it was 1. 20 | 21 | ![Past, waveforms](diagrams/past1.png) 22 | 23 | We can also look at the value of a signal N steps ago by giving `clocks=N` to `Past`: 24 | 25 | ```python 26 | m.d.comb += Assert((x == 2) & (Past(x, clocks=2) == 1)) 27 | ``` 28 | 29 | The above asserts that `x` is now 2, but two time steps ago it was 1. The default for `clocks` is 1. 30 | 31 | ![Past, waveforms](diagrams/past2.png) 32 | 33 | What is the value of `Past` before the very first time step? It is the reset value of the signal. 34 | 35 | ![Past before time begins](diagrams/past_t0.png) 36 | 37 | ## The clock and reset signals 38 | 39 | So far you haven't used the clock or reset signals in anything. But you can access them using `ClockSignal("domain-name")` and `ResetSignal("domain-name")`. 40 | 41 | ```python 42 | from nmigen import ClockSignal, ResetSignal 43 | 44 | sync = ClockSignal("sync") 45 | with m.If(sync): 46 | m.d.comb += Assert(x == 2) 47 | ``` 48 | 49 | The above checks that `x` is always 2 when the `sync` clock is high, but makes no assertion about what `x` is when the `sync` clock is low. 50 | 51 | ## Other past-looking functions 52 | 53 | ```python 54 | from nmigen.asserts import Stable, Rose, Fell 55 | ``` 56 | 57 | `Stable(x)` is equivalent to `x == Past(x)`. 58 | 59 | `Rose(x)` is equivalent to `(x == 1) & (Past(x) == 0)`. 60 | 61 | `Fell(x)` is equivalent to `(x == 0) & (Past(x) == 1)`. 62 | 63 | `Stable(x, clocks=N)`, `Rose(x, clocks=N)`, and `Fell(x, clocks=N)` are equivalent to using `clocks=N+1` in the `Past` expression, so the default `clocks` value is 0. 64 | 65 | ![Waveforms showing Past and friends](diagrams/past_and_friends.png) 66 | 67 | ## Initial 68 | 69 | The `Initial()` signal is 1 when we are on the very first time step, and 0 otherwise. This is useful for determining whether `Past` is going to give you the reset value. 70 | 71 | ```python 72 | from nmigen.asserts import Initial 73 | ``` 74 | 75 | ## Assumptions 76 | 77 | There's a hidden assumption that you're making when you ask the formal verification engine to verify a circuit with clocks in it: that the clocks actually clock. 78 | 79 | Earlier we said that there is a built-in clock domain, `sync`. However, you still need to explain to the engine how that input signal (for it is an input signal to your circuit) is supposed to operate. We do that with assumptions. 80 | 81 | Assumptions force the verification engine to ensure that the assumptions are true. So for example, suppose `x` were an input to some module that you want to verify: 82 | 83 | ```python 84 | from nmigen.asserts import Assume 85 | 86 | x = Signal(16) # An input signal 87 | m.d.comb += Assume(x < 0xD000) 88 | ``` 89 | 90 | The above would force the engine not to consider cases where `x >= 0xD000`. As a less direct assumption: 91 | 92 | ```python 93 | x = Signal(4) # An input signal 94 | y = Signal(4) # Another input signal 95 | z = Signal(4) 96 | m.d.comb += z.eq(x + y) 97 | m.d.comb += Assume(z < 10) 98 | ``` 99 | 100 | Now the engine can only consider `x` and `y` such that `x + y < 10`. By the way, this is where the prover's built-in theories can really help. With a theory of linear arithmetic built in, the Z3 solver is able to short-cut a brute-force solution to the equation. 101 | 102 | The fun continues when you have complex circuits with signals that indicate complex conditions. You can simply assume that such a signal goes high, and the solver will be forced to make that complex condition happen. 103 | 104 | This may sound similar to the Cover statement, but covering only tries to find one path to making the condition happen. Assumption works with bounded model checking (and later, induction) to find all such paths. 105 | 106 | ## Assuming a clock 107 | 108 | So, because we need to assume that the `sync` clock actually clocks, we add this to the `formal` function in our skeleton code (see [`skeleton_sync.py`](skeleton_sync.py)): 109 | 110 | ```python 111 | ... 112 | 113 | sync_clk = ClockSignal("sync") 114 | sync_rst = ResetSignal("sync") 115 | 116 | # Make sure the clock is clocking 117 | m.d.comb += Assume(sync_clk == ~Past(sync_clk)) 118 | 119 | # Include this only if you don't want to test resets 120 | m.d.comb += Assume(~sync_rst) 121 | 122 | # Ensure sync's clock and reset signals are manipulable. 123 | return m, [sync_clk, sync_rst, my_class.my_input] 124 | ``` 125 | 126 | Without this assumption, formal verification can feel free to simply not clock `sync`, and then your counter will be stuck at its initial value. 127 | 128 | ![Waveforms for clock with assumptions](diagrams/sync_clk_assume.png) 129 | ![Waveforms for clock without assumptions](diagrams/sync_clk_no_assume.png) 130 | 131 | ## Bad assumptions 132 | 133 | Sometimes you might get too clever and write some assumptions that can't all simultaneously work. For example, consider the assumption that `sync_clk == ~Past(sync_clk)`. Remember we said that the `Past` of any signal before the initial time step is its reset value? The reset value for clocks is `0`. Therefore, at the first time step, `Past(sync_clk)` is `0` and so `~Past(sync_clk)` is `1`. And because of our assumption, `sync_clk` on the first time step will be `1`. 134 | 135 | However, if you wanted to force the clock to be `0` on the first time step using an assumption: 136 | 137 | ```python 138 | with m.If(Initial()): 139 | m.d.comb += Assume(~sync_clk) 140 | ``` 141 | 142 | Now you have two contradictory assumptions. One says that the clock must be the opposite of its past value, the past value on the first step is 0, so the clock must be 1 on the first step. 143 | 144 | The other assumption says that the clock must be 0 on the first step. 145 | 146 | If you run into contradictory assumptions, formal verification will give you a message like this: 147 | 148 | ``` 149 | SBY 10:42:59 [e06_counter_bmc] engine_0: ## 0:00:00 Assumptions are unsatisfiable! 150 | SBY 10:42:59 [e06_counter_bmc] engine_0: ## 0:00:00 Status: PREUNSAT 151 | ``` 152 | 153 | ## Your turn 154 | 155 | Armed with all the above, you should now be able to construct your assumptions and assertions to meet the problem posed at the beginning of this exercise. Remember that sequential positive edges on the `sync` clock are two time steps apart! 156 | 157 | ## Stumped? 158 | 159 | The answer to this exercise is in [`answers/e06_counter.py`](answers/e06_counter.py). 160 | -------------------------------------------------------------------------------- /01_input.md: -------------------------------------------------------------------------------- 1 | # Exercise 1: Counting coin 2 | 3 |

4 | Khajiit has knowledge... if you have coin. 5 |

6 | 7 | --- 8 | ## What you'll do: 9 | 10 | Create a module that takes as inputs: 11 | 12 | * A number of pennies, from 0 to 255 13 | * A number of nickels (5-penny pieces), from 0 to 15 14 | * A number of dimes (10-penny pieces), from 0 to 15 15 | * A number of quarters (25-penny pieces), from 0 to 15 16 | * A number of dollars (100-penny pieces), from 0 to 15 17 | 18 | And outputs the equivalent number of pennies you have. 19 | 20 | ![Module block diagram](diagrams/pennies_module.png) 21 | 22 | Use formal verification to: 23 | 24 | * Cover the case where the inputs are: 37 pennies, 3 nickels, 10 dimes, 5 quarters, and 2 dollars. 25 | * Cover the case where the output is 548 pennies. 26 | * Cover the case where the output is 64 pennies, and there are twice as many nickels as there are dimes, and there is at least one dime. 27 | * Prove that if there are no input pennies, then the number of output pennies is always a multiple of 5. 28 | * Prove that the number of output pennies, modulo 5, will always equal the number of input pennies, modulo 5, regardless of the other inputs. 29 | 30 | ## Step 1: Create a module 31 | 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 | 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 | 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 | 38 | ![Toolchain flow](diagrams/nmigen_blocks.png) 39 | 40 | You can use the [`skeleton.py`](skeleton.py) file to start. Some key features here are: 41 | 42 | * Your class is derived from `Elaboratable` 43 | * 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 a class-level `formal` method for verifying your logic. 46 | * You can run your class to generate the output. This requires the `main` function in [`util.py`](util.py). 47 | 48 | ## Step 2: Create input and output signals 49 | 50 | A `Signal` in nMigen is a wire or register with some number of bits. For example: 51 | 52 | ```python 53 | x = Signal(5) # creates a 5-bit signal named "x". 54 | self.y = Signal() # creates a 1-bit signal named "y" which is an attribute of the class. 55 | ``` 56 | 57 | Think about the input and output signals you'll need, and how many bits each will have to be to represent the range of values they need. Then add them in the `__init__` function of your class. 58 | 59 | ## Step 3: Write the logic 60 | 61 | For this exercise, the logic is completely *combinatorial*: the outputs depend directly on the inputs, with no stored state. In later exercises we will look at *synchronous* logic. The logic-creating statements that you write are added to a *domain*, which can be the combinatorial domain, or a synchronous domain. Here's an example of how to assign signal `x` to signal `y` combinatorically: 62 | 63 | ```python 64 | m.d.comb += y.eq(x) 65 | ``` 66 | 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 | 69 | You can use constants, and arithmetic functions, too: 70 | 71 | ```python 72 | m.d.comb += y.eq(x + 1) 73 | m.d.comb += z.eq(7) 74 | m.d.comb += a.eq(x * 3) 75 | ``` 76 | 77 | ![The logic diagram for the above code](diagrams/some_logic.png) 78 | 79 | What happens if you assign the same signal twice? 80 | 81 | ```python 82 | m.d.comb += y.eq(0) 83 | m.d.comb += y.eq(x + 1) 84 | ``` 85 | 86 | It's always the last statement that takes precedence, so `y` will be set to `x+1`, not `0`. 87 | 88 | ### The if-statement 89 | 90 | In Python, you can write something like this: 91 | 92 | ```python 93 | y = 0 94 | if x == 7: 95 | y = z 96 | ``` 97 | 98 | The equivalent in nMigen is: 99 | 100 | ```python 101 | m.d.comb += y.eq(0) 102 | with m.If(x == 7): 103 | m.d.comb += y.eq(z) 104 | ``` 105 | 106 | In this way, you can conditionally assign `y`. You can even use `else`: 107 | 108 | ```python 109 | with m.If(x == 7): 110 | m.d.comb += y.eq(z) 111 | with m.Else(): 112 | m.d.comb += y.eq(0) 113 | ``` 114 | 115 | This does the same thing. 116 | 117 | ![An if-else-statement](diagrams/if.png) 118 | 119 | ## Step 4: Make sure it compiles! 120 | 121 | You can quickly check that there aren't any syntax errors by just generating the code: 122 | 123 | ``` 124 | python3 your_file.py gen 125 | ``` 126 | 127 | ## Step 5: Write some formal verification code 128 | 129 | Formal verification is a way of showing that your code does what you claim it does. It's more powerful than unit tests because while a unit test says, "given this input, I expect this output", formal verification can say, "this is always true". In a sense, formal verification is a unit test for all inputs and outputs. 130 | 131 | Formal verification can also help you see how your logic can get into some specified condition. This is called "covering". 132 | 133 | Write your formal verification code in the `formal` function of the class. This function returns two things: the module with formal verification logic (just like `elaborate` does), and the signals that the formal verification engine is allowed to manipulate (effectively your inputs). 134 | 135 | ### Assertions 136 | 137 | You can assert that a condition is always true by using an `Assert` statement: 138 | 139 | ```python 140 | m.d.comb += Assert(self.x == self.y) # Assert that x is always the same as y. 141 | m.d.comb += Assert((self.x == 1) & (self.y == 2)) # Assert that x is always 1 and y is always 2. 142 | m.d.comb += Assert(self.x == 1 and self.y == 2) # This is a syntax error. 143 | ``` 144 | 145 | ![The logic diagram for the above code](diagrams/simple_asserts.png) 146 | 147 | Note that we can't use Python's `and` like in that last assertion. This is because we are writing logic, and what we really need is a logic bitwise and. Also note the careful placement of parentheses. We have to add these parentheses because in Python, bitwise operators have lower precedence than comparisons. So these two statements are very different: 148 | 149 | ```python 150 | m.d.comb += Assert((x == 7) & y) # Assert that x is always 7 and y is always 1. 151 | m.d.comb += Assert(x == 7 & y) # Assert that x is always (7 bitwise anded with y). 152 | m.d.comb += Assert(x == 7 and y) # This is a syntax error. 153 | ``` 154 | 155 | ![The logic diagram for the above code](diagrams/bad_and.png) 156 | 157 | When the formal verification engine runs, it tries to falsify any of your assertions. If it can't, you win! But if it can, it will show you what inputs it used to make your assertion fail. Then you can debug. 158 | 159 | By the way, if-statements also work for asserts: 160 | 161 | ```python 162 | with m.If(x == 7): 163 | m.d.comb += Assert(y) 164 | ``` 165 | 166 | This means, if `x` is `7`, then `y` must be `1`. Otherwise we make no assertion about `y`. 167 | 168 | ### Covers 169 | 170 | You can ask the formal verification engine to manipulate the inputs to make some condition true. For example: 171 | 172 | ```python 173 | m.d.comb += Cover(self.output == 3) # What inputs lead to the output being 3? 174 | m.d.comb += Cover(self.output == 2 * self.input) # What inputs lead to the output being twice the input? 175 | ``` 176 | 177 | If the engine cannot satisfy a cover, it will complain. This usually means that your cover condition is impossible to achieve, and you'll have to debug to find out why. If a cover can be achieved, then the engine will show you what inputs it used to make it happen. 178 | 179 | ### Your turn 180 | 181 | Write the asserts and covers according to the requirements of the exercise. Compile again to make sure you haven't introduced any syntax errors. 182 | 183 | ``` 184 | python3 your_file.py gen 185 | ``` 186 | 187 | ## Step 6: Run the formal verification engine for covers 188 | 189 | 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`. 190 | 191 | ``` 192 | python3 your_class.py gen 193 | sby -f answers/e01_to_pennies.sby cover 194 | ``` 195 | 196 | 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. 197 | 198 | ``` 199 | SBY 15:17:27 [answers/e01_to_pennies_cover] Removing directory 'answers/e01_to_pennies_cover'. 200 | SBY 15:17:27 [answers/e01_to_pennies_cover] Copy 'toplevel.il' to 'answers/e01_to_pennies_cover/src/toplevel.il'. 201 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: smtbmc z3 202 | SBY 15:17:27 [answers/e01_to_pennies_cover] base: starting process "cd answers/e01_to_pennies_cover/src; yosys -ql ../model/design.log ../model/design.ys" 203 | SBY 15:17:27 [answers/e01_to_pennies_cover] base: finished (returncode=0) 204 | SBY 15:17:27 [answers/e01_to_pennies_cover] smt2: starting process "cd answers/e01_to_pennies_cover/model; yosys -ql design_smt2.log design_smt2.ys" 205 | SBY 15:17:27 [answers/e01_to_pennies_cover] smt2: finished (returncode=0) 206 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: starting process "cd answers/e01_to_pennies_cover; yosys-smtbmc -s z3 --presat -c --noprogress -t 1 --append 0 --dump-vcd engine_0/trace%.vcd --dump-vlogtb engine_0/trace%_tb.v --dump-smtc engine_0/trace%.smtc model/design_smt2.smt2" 207 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Solver: z3 208 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Checking cover reachability in step 0.. 209 | 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. 210 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Writing trace to VCD file: engine_0/trace0.vcd 211 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Writing trace to Verilog testbench: engine_0/trace0_tb.v 212 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Writing trace to constraints file: engine_0/trace0.smtc 213 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Checking cover reachability in step 0.. 214 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Reached cover statement at answers/to_pennies.py:53 in step 0. 215 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Writing trace to VCD file: engine_0/trace1.vcd 216 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Writing trace to Verilog testbench: engine_0/trace1_tb.v 217 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Writing trace to constraints file: engine_0/trace1.smtc 218 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Checking cover reachability in step 0.. 219 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Reached cover statement at answers/to_pennies.py:51 in step 0. 220 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Writing trace to VCD file: engine_0/trace2.vcd 221 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Writing trace to Verilog testbench: engine_0/trace2_tb.v 222 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Writing trace to constraints file: engine_0/trace2.smtc 223 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: ## 0:00:00 Status: passed 224 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: finished (returncode=0) 225 | SBY 15:17:27 [answers/e01_to_pennies_cover] engine_0: Status returned by engine: pass 226 | SBY 15:17:27 [answers/e01_to_pennies_cover] summary: Elapsed clock time [H:MM:SS (secs)]: 0:00:00 (0) 227 | SBY 15:17:27 [answers/e01_to_pennies_cover] summary: Elapsed process time [H:MM:SS (secs)]: 0:00:00 (0) 228 | SBY 15:17:27 [answers/e01_to_pennies_cover] summary: engine_0 (smtbmc z3) returned pass 229 | SBY 15:17:27 [answers/e01_to_pennies_cover] summary: trace: answers/e01_to_pennies_cover/engine_0/trace0.vcd 230 | SBY 15:17:27 [answers/e01_to_pennies_cover] summary: trace: answers/e01_to_pennies_cover/engine_0/trace1.vcd 231 | SBY 15:17:27 [answers/e01_to_pennies_cover] summary: trace: answers/e01_to_pennies_cover/engine_0/trace2.vcd 232 | SBY 15:17:27 [answers/e01_to_pennies_cover] DONE (PASS, rc=0) 233 | ``` 234 | 235 | The cover statements aren't found in the order you put them in your code! 236 | 237 | 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`: 238 | 239 | ``` 240 | gtkwave -f answers/e01_to_pennies_cover/engine_0/trace0.vcd 241 | ``` 242 | 243 | In this case, the first trace came from the first cover statement in the exercise. The output shows `1DD`, or 477. 244 | 245 | ![gtkwave output](diagrams/ex1_tr0.JPG) 246 | 247 | ## Step 7: Run the formal verification engine for bounded model checking 248 | 249 | ``` 250 | sby -f answers/e01_to_pennies.sby bmc 251 | ``` 252 | 253 | "BMC" stands for Bounded Model Checking. More on this later exercises. If all assertions succeed, then you'll get a `DONE (PASS, rc=0)` line at the end. If not, you'll get a trace that you can look at. 254 | 255 | ``` 256 | SBY 15:17:28 [answers/e01_to_pennies_bmc] Removing directory 'answers/e01_to_pennies_bmc'. 257 | SBY 15:17:28 [answers/e01_to_pennies_bmc] Copy 'toplevel.il' to 'answers/e01_to_pennies_bmc/src/toplevel.il'. 258 | SBY 15:17:28 [answers/e01_to_pennies_bmc] engine_0: smtbmc z3 259 | SBY 15:17:28 [answers/e01_to_pennies_bmc] base: starting process "cd answers/e01_to_pennies_bmc/src; yosys -ql ../model/design.log ../model/design.ys" 260 | SBY 15:17:28 [answers/e01_to_pennies_bmc] base: finished (returncode=0) 261 | SBY 15:17:28 [answers/e01_to_pennies_bmc] smt2: starting process "cd answers/e01_to_pennies_bmc/model; yosys -ql design_smt2.log design_smt2.ys" 262 | SBY 15:17:28 [answers/e01_to_pennies_bmc] smt2: finished (returncode=0) 263 | SBY 15:17:28 [answers/e01_to_pennies_bmc] engine_0: starting process "cd answers/e01_to_pennies_bmc; yosys-smtbmc -s z3 --presat --noprogress -t 1 --append 0 --dump-vcd engine_0/trace.vcd --dump-vlogtb engine_0/trace_tb.v --dump-smtc engine_0/trace.smtc model/design_smt2.smt2" 264 | SBY 15:17:28 [answers/e01_to_pennies_bmc] engine_0: ## 0:00:00 Solver: z3 265 | SBY 15:17:28 [answers/e01_to_pennies_bmc] engine_0: ## 0:00:00 Checking assumptions in step 0.. 266 | SBY 15:17:28 [answers/e01_to_pennies_bmc] engine_0: ## 0:00:00 Checking assertions in step 0.. 267 | SBY 15:17:41 [answers/e01_to_pennies_bmc] engine_0: ## 0:00:13 Status: passed 268 | SBY 15:17:41 [answers/e01_to_pennies_bmc] engine_0: finished (returncode=0) 269 | SBY 15:17:41 [answers/e01_to_pennies_bmc] engine_0: Status returned by engine: pass 270 | SBY 15:17:41 [answers/e01_to_pennies_bmc] summary: Elapsed clock time [H:MM:SS (secs)]: 0:00:13 (13) 271 | SBY 15:17:41 [answers/e01_to_pennies_bmc] summary: Elapsed process time [H:MM:SS (secs)]: 0:00:13 (13) 272 | SBY 15:17:41 [answers/e01_to_pennies_bmc] summary: engine_0 (smtbmc z3) returned pass 273 | SBY 15:17:41 [answers/e01_to_pennies_bmc] DONE (PASS, rc=0) 274 | ``` 275 | 276 | ## Stumped? 277 | 278 | The answer to this exercise is in [`answers/e01_to_pennies.py`](answers/e01_to_pennies.py). 279 | --------------------------------------------------------------------------------