├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── amaranth_examples ├── __init__.py ├── comb_test.py ├── connectors.py ├── counter.py ├── custom_board.py ├── ddr.py ├── instance.py ├── pll_ecp5.py ├── pll_ice40.py └── spi_oversampled.py ├── poetry.lock └── pyproject.toml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-22.04 11 | env: 12 | YOSYS: yowasp-yosys 13 | NEXTPNR_ICE40: yowasp-nextpnr-ice40 14 | ICEPACK: yowasp-icepack 15 | NEXTPNR_ECP5: yowasp-nextpnr-ecp5 16 | ECPPACK: yowasp-ecppack 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | - name: Install Poetry 21 | run: 22 | pip install --upgrade pip 23 | pip install poetry poethepoet 24 | - name: Create Poetry environment 25 | run: | 26 | python -V 27 | poetry --version 28 | poetry install 29 | - name: Install YoWASP 30 | run: | 31 | poetry run pip install yowasp-yosys yowasp-nextpnr-ice40 yowasp-nextpnr-ecp5 32 | - name: Run tests 33 | run: poe test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | build/ 3 | *.vcd 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amaranth examples 2 | 3 | This repository contains a variety of Amaranth examples: 4 | 5 | * [comb_test.py](amaranth_examples/comb_test.py): Testbench for a purely combinatorial Module, using Settle. 6 | * [connectors.py](amaranth_examples/connectors.py): Demonstrates using connectors defined in a Platform. 7 | * [counter.py](amaranth_examples/counter.py): Simple logic example with a testbench 8 | * [custom_board.py](amaranth_examples/custom_board.py): Demonstrates adding your own Platform for your own FPGA board and synthesising a bitstream for it. 9 | * [ddr.py](amaranth_examples/ddr.py): Demonstrates use of DDR outputs on a custom ECP5 board 10 | * [instance.py](amaranth_examples/instance.py): Using an Instance to instantiate a module (from Verilog or a platform primitive), and adding a Verilog file to the build process. 11 | * [pll_ecp5.py](amaranth_examples/pll_ecp5.py): Use a platform PLL primitive on the ECP5. 12 | * [pll_ice40.py](amaranth_examples/pll_ice40.py): Use a platform PLL primitive on the iCE40. 13 | * [spi_oversampled.py](amaranth_examples/spi_oversampled.py): A toy SPI peripheral which oversamples SCLK/MOSI from a higher-frequency internal sync domain 14 | -------------------------------------------------------------------------------- /amaranth_examples/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | -------------------------------------------------------------------------------- /amaranth_examples/comb_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example testbench for a combinatorial-only module. 3 | """ 4 | 5 | from amaranth import Module, Signal, Elaboratable 6 | from amaranth.sim import Simulator, Settle 7 | 8 | 9 | class ALU(Elaboratable): 10 | """ 11 | A simple ALU that either adds or subtracts two inputs, 12 | but implemented only using combinatorial statements, 13 | without any synchronous logic. 14 | """ 15 | def __init__(self): 16 | self.op = Signal() 17 | self.a = Signal(8) 18 | self.b = Signal(8) 19 | self.y = Signal(9) 20 | 21 | def elaborate(self, platform): 22 | m = Module() 23 | 24 | with m.If(self.op): 25 | m.d.comb += self.y.eq(self.a + self.b) 26 | with m.Else(): 27 | m.d.comb += self.y.eq(self.a - self.b) 28 | 29 | return m 30 | 31 | 32 | def test_comb_alu(): 33 | alu = ALU() 34 | 35 | def testbench(): 36 | for a in range(20): 37 | yield alu.a.eq(a) 38 | for b in range(20): 39 | yield alu.b.eq(b) 40 | 41 | # In this combinatorial testbench, we use `yield Settle()` 42 | # to request the simulator advance until all combinatorial 43 | # statements have been fully resolved. 44 | yield alu.op.eq(1) 45 | yield Settle() 46 | assert (yield alu.y) == a + b 47 | 48 | yield alu.op.eq(0) 49 | yield Settle() 50 | assert (yield alu.y) == (a - b) % 512 51 | 52 | sim = Simulator(alu) 53 | 54 | # Instead of `sim.add_sync_process`, which would try to use a clock domain 55 | # (by default, "sync") and error out saying it doesn't exist, we use 56 | # `sim.add_process`. 57 | sim.add_process(testbench) 58 | 59 | sim.run() 60 | -------------------------------------------------------------------------------- /amaranth_examples/connectors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrate use of platform connectors. 3 | 4 | Connectors require calling `platform.add_resources()` to create a new 5 | resource which you can later request. 6 | """ 7 | 8 | from amaranth import Module, Signal, Elaboratable 9 | from amaranth.build import Resource, Pins 10 | from amaranth_boards.icebreaker import ICEBreakerPlatform 11 | 12 | 13 | class Top(Elaboratable): 14 | def elaborate(self, platform): 15 | m = Module() 16 | 17 | # Say we want to add some LEDs and switches to pmod 1, which 18 | # has pins 1-4 and 7-10 (plus 5/6 and 11/12 which are power and gnd). 19 | platform.add_resources([ 20 | Resource("leds", 0, Pins("1 2 3 4", dir="o", conn=("pmod", 1))), 21 | Resource("sw", 0, Pins("7 8 9 10", dir="i", conn=("pmod", 1))), 22 | ]) 23 | 24 | # Now we can request and use them. 25 | leds = platform.request("leds", 0).o 26 | switches = platform.request("sw", 0).i 27 | m.d.sync += leds.eq(~switches) 28 | 29 | return m 30 | 31 | 32 | def test_connectors(): 33 | top = Top() 34 | plat = ICEBreakerPlatform() 35 | plat.build(top) 36 | 37 | 38 | if __name__ == "__main__": 39 | test_connectors() 40 | -------------------------------------------------------------------------------- /amaranth_examples/counter.py: -------------------------------------------------------------------------------- 1 | """ 2 | A counter that generates a pulse on rollover. 3 | 4 | Demonstrates basic modules and synchronous testbenches. 5 | """ 6 | 7 | from amaranth import Module, Signal, Elaboratable 8 | from amaranth.sim import Simulator 9 | 10 | 11 | class Counter(Elaboratable): 12 | def __init__(self, limit): 13 | self.limit = limit 14 | 15 | # Counter state. 16 | # 17 | # Using `range(limit)` means the signal will be wide enough to 18 | # represent any integer up to but excluding `limit`. 19 | # 20 | # For example, with limit=128, we'd get a 7-bit signal which can 21 | # represent the integers 0 to 127. 22 | self.counter = Signal(range(limit)) 23 | 24 | # Rollover output will be pulsed high for one clock cycle 25 | # when the counter reaches `limit-1`. 26 | self.rollover = Signal() 27 | 28 | def elaborate(self, platform): 29 | m = Module() 30 | 31 | # Make the output `rollover` always equal to this comparison, 32 | # which will only be 1 for a single cycle every counter period. 33 | m.d.comb += self.rollover.eq(self.counter == self.limit - 1) 34 | 35 | # Conditionally reset the counter to 0 on rollover, otherwise 36 | # increment it. We could write the comparison out again here 37 | # to the same effect. 38 | with m.If(self.rollover): 39 | m.d.sync += self.counter.eq(0) 40 | with m.Else(): 41 | m.d.sync += self.counter.eq(self.counter + 1) 42 | 43 | return m 44 | 45 | 46 | def test_counter(): 47 | """Simple testbench for the Counter above.""" 48 | 49 | # Create a counter with a rollover at 18. 50 | # This awkward non-power-of-two value will help test that we're not 51 | # rolling over by accident of the bit width of the counter. 52 | counter = Counter(limit=18) 53 | 54 | # Test benches are written as Python generators, which yield 55 | # commands to the simulator such as "send me the current value of this 56 | # signal" or "advance the simulation by one clock cycle". 57 | def testbench(): 58 | for step in range(20): 59 | # Check outputs are correct at each step. 60 | assert (yield counter.counter) == (step % 18) 61 | if step == 17: 62 | assert (yield counter.rollover) 63 | else: 64 | assert not (yield counter.rollover) 65 | 66 | # Advance simulation by one cycle. 67 | yield 68 | 69 | sim = Simulator(counter) 70 | 71 | # To test synchronous processes, we create a clock at some nominal 72 | # frequency (which only changes the displayed timescale in the output), 73 | # and add our testbench as a "synchronous" process. 74 | sim.add_clock(1/10e6) 75 | sim.add_sync_process(testbench) 76 | 77 | sim.run() 78 | -------------------------------------------------------------------------------- /amaranth_examples/custom_board.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrate synthesis for a custom board, using an iCE40UP5k FPGA. 3 | """ 4 | 5 | from amaranth import Module, Signal, Elaboratable 6 | from amaranth.vendor import LatticeICE40Platform 7 | from amaranth.build import Resource, Pins, Clock, Attrs 8 | 9 | 10 | class CustomPlatform(LatticeICE40Platform): 11 | """ 12 | This CustomPlatform represents our particular custom FPGA board, 13 | where we've wired a 20MHz clock to pin 35, and put LEDs (active high) 14 | on pins 46-48. 15 | 16 | We use the Clock property to let amaranth know this signal is a clock at 17 | a particular frequency, which it in turn tells the placement software 18 | about, so that nextpnr can check the circuit is able to run at the 19 | specified frequency. 20 | 21 | We use the Attrs(GLOBAL=True) property to request that this clock input 22 | is immediately put into a global buffer on the iCE40 FPGA. 23 | 24 | By specifying `default_clk`, we tell amaranth to create a default clock 25 | domain named "sync" if none exists using the specified signal as the 26 | clock input. If we didn't specify default_clk we'd have to create the 27 | clock domain ourselves. 28 | """ 29 | device = "iCE40UP5K" 30 | package = "sg48" 31 | default_clk = "clk" 32 | resources = [ 33 | Resource("clk", 0, Pins("35", dir="i"), Clock(20e6), Attrs(GLOBAL=True)), 34 | Resource("led", 0, Pins("46", dir="o")), 35 | Resource("led", 1, Pins("47", dir="o")), 36 | Resource("led", 2, Pins("48", dir="o")), 37 | ] 38 | connectors = [] 39 | 40 | 41 | class Top(Elaboratable): 42 | """ 43 | This simple Top module requests the three LED pins from the Platform. 44 | 45 | Output pins like this LEDs have an `o` attribute which we assign to 46 | to set the output. Other pins might have `i` and `oe` attributes, or 47 | for gearboxed pins (where xdr is specified), `i0`, `i1`, `i_clk`, etc. 48 | 49 | We'll make a 24-bit counter and just set the LEDs to the top bits. 50 | """ 51 | def elaborate(self, platform): 52 | m = Module() 53 | 54 | ctr = Signal(24) 55 | m.d.sync += ctr.eq(ctr + 1) 56 | 57 | leds = [platform.request("led", i) for i in range(3)] 58 | m.d.comb += [ 59 | leds[0].o.eq(ctr[-1]), 60 | leds[1].o.eq(ctr[-2]), 61 | leds[2].o.eq(ctr[-3]), 62 | ] 63 | 64 | return m 65 | 66 | 67 | def test_synthesise_custom_board(): 68 | # All we need to do is create the top-level module, then call the 69 | # platform's `build()` method with it. 70 | top = Top() 71 | plat = CustomPlatform() 72 | plat.build(top) 73 | 74 | 75 | if __name__ == "__main__": 76 | test_synthesise_custom_board() 77 | -------------------------------------------------------------------------------- /amaranth_examples/ddr.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrate use of DDR outputs on a custom ECP5 board. 3 | """ 4 | 5 | from amaranth import Module, Elaboratable, ClockSignal 6 | from amaranth.vendor import LatticeECP5Platform 7 | from amaranth.build import Resource, Pins, Clock 8 | 9 | 10 | class CustomPlatform(LatticeECP5Platform): 11 | """ 12 | This CustomPlatform represents our particular custom FPGA board, 13 | where we've wired a 20MHz clock to pin P3, and want to use U1 and V1 14 | as DDR inputs and outputs. 15 | 16 | We use the Clock property to let amaranth know this signal is a clock at 17 | a particular frequency, which it in turn tells the placement software 18 | about, so that nextpnr can check the circuit is able to run at the 19 | specified frequency. 20 | 21 | By specifying `default_clk`, we tell amaranth to create a default clock 22 | domain named "sync" if none exists using the specified signal as the 23 | clock input. If we didn't specify default_clk we'd have to create the 24 | clock domain ourselves. 25 | """ 26 | device = "LFE5U-25F" 27 | package = "BG381" 28 | speed = "6" 29 | default_clk = "clk" 30 | resources = [ 31 | Resource("clk", 0, Pins("P3", dir="i"), Clock(20e6)), 32 | Resource("gpi", 0, Pins("U1", dir="i")), 33 | Resource("gpo", 0, Pins("V1", dir="o")), 34 | ] 35 | connectors = [] 36 | 37 | 38 | class Top(Elaboratable): 39 | """ 40 | This simple Top module reads the input DDR and copies it to the output. 41 | """ 42 | def elaborate(self, platform): 43 | m = Module() 44 | 45 | # Requesting the pins with `xdr=2` requests a DDR gearing. 46 | # Using `xdr=1` would be SDR - registered but only one data 47 | # connection. Higher values of `xdr` may be allowed on particular 48 | # hardware, for example `xdr=7` for video interfaces. 49 | # When `xdr` is greater than 0, the `i_clk`/`o_clk` signals 50 | # become available. 51 | gpi = platform.request("gpi", 0, xdr=2) 52 | gpo = platform.request("gpo", 0, xdr=2) 53 | 54 | # Hook up clock signals. 55 | m.d.comb += [ 56 | gpi.i_clk.eq(ClockSignal()), 57 | gpo.o_clk.eq(ClockSignal()), 58 | ] 59 | 60 | # Connect inputs to outputs through a register. 61 | m.d.sync += [ 62 | gpo.o0.eq(gpi.i0), 63 | gpo.o1.eq(gpi.i1), 64 | ] 65 | 66 | return m 67 | 68 | 69 | def test_synthesise_custom_board(): 70 | # All we need to do is create the top-level module, then call the 71 | # platform's `build()` method with it. 72 | top = Top() 73 | plat = CustomPlatform() 74 | plat.build(top) 75 | 76 | 77 | if __name__ == "__main__": 78 | test_synthesise_custom_board() 79 | -------------------------------------------------------------------------------- /amaranth_examples/instance.py: -------------------------------------------------------------------------------- 1 | """ 2 | Uses an Instance which could be a platform primitive but in this case is 3 | an external Verilog file which we'll include in the build. 4 | """ 5 | 6 | from amaranth import Module, Signal, Elaboratable, Instance, ClockSignal 7 | from amaranth_boards.icebreaker import ICEBreakerPlatform 8 | 9 | 10 | class Top(Elaboratable): 11 | def elaborate(self, platform): 12 | m = Module() 13 | 14 | # First, we'll make up some simple Verilog. 15 | # We could have read this from a file or similar. 16 | v = """ 17 | module counter ( clk, cnt ); 18 | parameter WIDTH = 8; 19 | input clk; 20 | output reg [WIDTH-1:0] cnt; 21 | 22 | always @(posedge clk) begin 23 | cnt <= cnt + 1; 24 | end 25 | endmodule 26 | """ 27 | 28 | # We use `platform.add_file()` to add the file to the build. 29 | platform.add_file("counter.v", v) 30 | 31 | # Now, we can use Instance to instantiate this module. 32 | count = Signal(8) 33 | counter = Instance( 34 | "counter", 35 | # Parameters starting with `p_` are Verilog parameters. 36 | p_WIDTH=count.width, 37 | # Parameters starting with `i_` are inputs. 38 | # In this case we get the clock signal using `ClockSignal()`, 39 | # although we could have assigned any signal. 40 | i_clk=ClockSignal("sync"), 41 | # Parameters starting with `o_` are outputs. 42 | # We assign the output of the module to our `count` Signal. 43 | o_cnt=count 44 | # We could also use `a_` for Verilog attributes and `io_` 45 | # for Verilog inouts. 46 | ) 47 | 48 | # We have to add the instance to our submodules. 49 | # Often this is written `counter = m.submodules.counter = Instance(...` 50 | m.submodules.counter = counter 51 | 52 | # Finally, let's just bind the top count bit to an LED. 53 | led = platform.request("led_g", 0) 54 | m.d.comb += led.o.eq(count[-1]) 55 | 56 | return m 57 | 58 | 59 | def test_instance(): 60 | top = Top() 61 | plat = ICEBreakerPlatform() 62 | plat.build(top) 63 | 64 | 65 | if __name__ == "__main__": 66 | test_instance() 67 | -------------------------------------------------------------------------------- /amaranth_examples/pll_ecp5.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrates instantiating and using a PLL on an ECP5 platform. 3 | """ 4 | 5 | from amaranth import Signal, Module, Elaboratable, Instance, ClockDomain 6 | from amaranth_boards.ulx3s import ULX3S_12F_Platform 7 | 8 | 9 | class Top(Elaboratable): 10 | def elaborate(self, platform): 11 | m = Module() 12 | 13 | # We'll need to create our own "sync" clock domain using the PLL's 14 | # output, since the default sync domain would use the 25MHz input. 15 | cd_sync = ClockDomain("sync") 16 | m.domains += cd_sync 17 | 18 | # We add a clock constraint so amaranth can tell nextpnr to check that 19 | # this clock domain meets timing. 20 | platform.add_clock_constraint(cd_sync.clk, 100e6) 21 | 22 | # Create an Instance with the required parameters, inputs, and outputs. 23 | # For the ECP5, this is EHXPLLL; refer to the "FPGA Libraries Reference 24 | # Guide" from Lattice for more details. 25 | m.submodules.pll = Instance( 26 | "EHXPLLL", 27 | 28 | # Parameters are taken from the `ecppll` output: 29 | # $ ecppll -i 25 -o 100 -f /dev/stdout 30 | a_FREQUENCY_PIN_CLKI="25", 31 | a_FREQUENCY_PIN_CLKOP="100", 32 | a_ICP_CURRENT="12", 33 | a_LPF_RESISTOR="8", 34 | p_CLKI_DIV=1, 35 | p_CLKOP_DIV=6, 36 | p_CLKFB_DIV=4, 37 | p_FEEDBK_PATH="CLKOP", 38 | p_CLKOP_ENABLE="ENABLED", 39 | 40 | # Input from the clk25 pin. 41 | i_CLKI=platform.request("clk25").i, 42 | 43 | # Output to the clock domain's clk signal. 44 | # We could also have written ClockSignal("sync"). 45 | o_CLKOP=cd_sync.clk, 46 | 47 | # We also need to connect up the feedback signal, in this 48 | # case directly to the output. 49 | i_CLKFB=cd_sync.clk, 50 | ) 51 | 52 | # Now our sync logic runs at 100MHz: 53 | cnt = Signal(24) 54 | m.d.sync += cnt.eq(cnt + 1) 55 | m.d.comb += platform.request("led", 0).o.eq(cnt[-1]) 56 | 57 | return m 58 | 59 | 60 | def test_pll_ecp5(): 61 | top = Top() 62 | 63 | # The real ULX3S_12F_Platform inconsiderately requires openFPGAloader 64 | # present in the build environment, even if you're not programming. 65 | class FakeULX3SPlatform(ULX3S_12F_Platform): 66 | @property 67 | def required_tools(self): 68 | return super().required_tools[:-1] 69 | 70 | plat = FakeULX3SPlatform() 71 | plat.build(top, program=False) 72 | 73 | 74 | if __name__ == "__main__": 75 | test_pll_ecp5() 76 | -------------------------------------------------------------------------------- /amaranth_examples/pll_ice40.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrates instantiating and using a PLL on an iCE40 platform. 3 | """ 4 | 5 | from amaranth import Signal, Module, Elaboratable, Instance, ClockDomain 6 | from amaranth_boards.icebreaker import ICEBreakerPlatform 7 | 8 | 9 | class Top(Elaboratable): 10 | def elaborate(self, platform): 11 | m = Module() 12 | 13 | # We'll need to create our own "sync" clock domain using the PLL's 14 | # output, since the default sync domain would use the 12MHz input. 15 | cd_sync = ClockDomain("sync") 16 | m.domains += cd_sync 17 | 18 | # We add a clock constraint so amaranth can tell nextpnr to check that 19 | # this clock domain meets timing. 20 | platform.add_clock_constraint(cd_sync.clk, 48e6) 21 | 22 | # A mystery errata on iCE40 devices means that BRAMs will read as 23 | # all-zero for ~3µs after configuration completes. If you use a "sync" 24 | # domain without creating it, Amaranth will create one for you and 25 | # ensure it is reset for 3µs after startup to avoid this issue. 26 | # However, if you create your own clock domain, you have to deal with 27 | # this manually if it's important. For this example there are no BRAMs 28 | # so it's not important, but for the sake of example, a suitable reset 29 | # is added, delaying for approx 15µs using the 48MHz PLL output clock. 30 | # Additionally, since we're running this timer off the PLL, we can 31 | # keep this domain in reset until the PLL is locked. 32 | # For more details, see create_missing_domain() in 33 | # amaranth/vendor/_lattice_ice40.py. 34 | cd_por = ClockDomain("por", local=True) 35 | m.domains += cd_por 36 | delay = int(5 * 3e-6 * 48e6) 37 | timer = Signal(range(delay)) 38 | ready = Signal() 39 | pll_locked = Signal() 40 | with m.If(timer == delay): 41 | m.d.por += ready.eq(1) 42 | with m.Else(): 43 | m.d.por += timer.eq(timer + 1) 44 | m.d.comb += cd_por.clk.eq(cd_sync.clk), cd_por.rst.eq(~pll_locked) 45 | m.d.comb += cd_sync.rst.eq(~ready) 46 | 47 | # Create an Instance with the required parameters, inputs, and outputs. 48 | # We have a choice of either SB_PLL40_CORE or SB_PLL40_PAD (or the _2F 49 | # versions of each, to have two output frequencies). 50 | # Use _CORE when the input signal comes from logic or routing or a 51 | # non-global pin or you need to use the input signal and have it 52 | # drive a PLL too; use _PAD when the clock input signal goes directly 53 | # to the PLL and is only used for the PLL. You also have to use _PAD 54 | # if the PLL is fed from the pin that the PLL is located on, as 55 | # otherwise the PLL disables that input signal (hope you spotted this 56 | # fun fact in the documentation!). For the ICEBreaker, that means we'll 57 | # have to use _PAD. 58 | m.submodules.pll = Instance( 59 | "SB_PLL40_PAD", 60 | 61 | # Parameters are taken from the `icepll` output: 62 | # $ icepll -i 12 -o 40 -m 63 | p_FEEDBACK_PATH="SIMPLE", 64 | p_DIVR=0, 65 | p_DIVF=52, 66 | p_DIVQ=4, 67 | p_FILTER_RANGE=1, 68 | 69 | # Input from the clk12 pin. Since we want the raw pin without 70 | # an input buffer, use `dir="-"` and then don't try to access 71 | # a `.i` attribute. 72 | i_PACKAGEPIN=platform.request("clk12", dir="-"), 73 | 74 | # Force RESET off. 75 | i_RESETB=1, 76 | 77 | # Output to the clock domain's clk signal. 78 | # We could also have written ClockSignal("sync"). 79 | o_PLLOUTGLOBAL=cd_sync.clk, 80 | 81 | # We'll use the LOCK output to keep the POR domain in reset 82 | # until the PLL has locked. 83 | o_LOCK=pll_locked, 84 | ) 85 | 86 | # Now our sync logic runs at 48MHz: 87 | cnt = Signal(24) 88 | m.d.sync += cnt.eq(cnt + 1) 89 | m.d.comb += platform.request("led_g", 0).o.eq(cnt[-1]) 90 | 91 | return m 92 | 93 | 94 | def test_pll_ice40(): 95 | top = Top() 96 | plat = ICEBreakerPlatform() 97 | plat.build(top) 98 | 99 | 100 | if __name__ == "__main__": 101 | test_pll_ice40() 102 | -------------------------------------------------------------------------------- /amaranth_examples/spi_oversampled.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple SPI peripheral device which oversamples SCLK using a higher-frequency 3 | internal sync domain. 4 | 5 | This uses SPI mode 0: the clock idles low and rising edges are active; 6 | data is sampled on the rising edge and changed on the falling edge. 7 | 8 | This works when the external SPI clock is sufficiently slow (maybe 1/3 or less 9 | of the sync frequency); for higher-speed SPI clocks it would be better to run 10 | some logic directly from that external clock (which likely then needs to enter 11 | the FPGA via a clock input pin) and synchronise the data internally. 12 | """ 13 | 14 | from amaranth import Module, Signal, Elaboratable, Cat 15 | from amaranth.sim import Simulator 16 | 17 | 18 | class SPIPeriph(Elaboratable): 19 | def __init__(self): 20 | # Data received from controller 21 | self.din = Signal(8) 22 | 23 | # Data to send to controller 24 | self.dout = Signal(8) 25 | 26 | # SPI interface 27 | self.csn = Signal() 28 | self.sck = Signal() 29 | self.sdi = Signal() 30 | self.sdo = Signal() 31 | 32 | def elaborate(self, platform): 33 | m = Module() 34 | 35 | # Detect edges on SCK 36 | last_sck = Signal() 37 | m.d.sync += last_sck.eq(self.sck) 38 | sck_rose = Signal() 39 | sck_fell = Signal() 40 | m.d.comb += sck_rose.eq(self.sck & ~last_sck) 41 | m.d.comb += sck_fell.eq(~self.sck & last_sck) 42 | 43 | # Always output the current most significant bit of `dout`. 44 | m.d.comb += self.sdo.eq(self.dout[-1]) 45 | 46 | with m.If(~self.csn): 47 | # Capture SDI into `din` on rising edge. 48 | with m.If(sck_rose): 49 | m.d.sync += self.din.eq(Cat(self.sdi, self.din)) 50 | # Shift `dout` into SDO on falling edge. 51 | with m.If(sck_fell): 52 | m.d.sync += self.dout.eq(self.dout.rotate_left(1)) 53 | 54 | return m 55 | 56 | 57 | def test_spi_periph(): 58 | spi = SPIPeriph() 59 | 60 | # Test benches are written as Python generators, which yield commands 61 | # to the simulator such as "set this signal to this value" or 62 | # "read the value of this signal". 63 | def testbench(): 64 | # Set up the starting conditions. 65 | # Start with CS not asserted and dout (data to send) 0xAB. 66 | yield spi.csn.eq(1) 67 | yield spi.dout.eq(0xAB) 68 | yield 69 | yield 70 | 71 | # Assert CS. 72 | yield spi.csn.eq(0) 73 | yield 74 | 75 | bits = [] 76 | 77 | # Run 8 clock cycles. 78 | for clk in range(8): 79 | # On the rising edge, capture the output and set new input. 80 | bits.append((yield spi.sdo)) 81 | yield spi.sdi.eq(clk & 1) 82 | yield spi.sck.eq(1) 83 | yield 84 | yield 85 | 86 | yield spi.sck.eq(0) 87 | yield 88 | yield 89 | 90 | # De-assert CS. 91 | yield spi.csn.eq(1) 92 | yield 93 | 94 | # Check the device received 0x55 from us, and we received 0xAB from it. 95 | assert (yield spi.din) == 0x55 96 | assert bits == [1, 0, 1, 0, 1, 0, 1, 1] 97 | 98 | # Run the simulator at a nominal 10MHz. 99 | sim = Simulator(spi) 100 | sim.add_clock(1/10e6) 101 | sim.add_sync_process(testbench) 102 | 103 | # Output a VCD file for visualisation. 104 | with sim.write_vcd("spi.vcd"): 105 | sim.run() 106 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "amaranth" 5 | version = "0.4.dev230+ge55dec9" 6 | description = "Amaranth hardware definition language" 7 | optional = false 8 | python-versions = "~=3.8" 9 | files = [] 10 | develop = false 11 | 12 | [package.dependencies] 13 | Jinja2 = ">=3.0,<4.0" 14 | pyvcd = ">=0.2.2,<0.5" 15 | 16 | [package.extras] 17 | builtin-yosys = ["amaranth-yosys (>=0.10)"] 18 | remote-build = ["paramiko (>=2.7,<3.0)"] 19 | 20 | [package.source] 21 | type = "git" 22 | url = "https://github.com/amaranth-lang/amaranth" 23 | reference = "main" 24 | resolved_reference = "e55dec9615adfaf6558738930889c74713a3f109" 25 | 26 | [[package]] 27 | name = "amaranth-boards" 28 | version = "0.1.dev239+g54000b0" 29 | description = "Board and connector definitions for Amaranth HDL" 30 | optional = false 31 | python-versions = "~=3.8" 32 | files = [] 33 | develop = false 34 | 35 | [package.dependencies] 36 | amaranth = ">=0.3,<0.5" 37 | 38 | [package.source] 39 | type = "git" 40 | url = "https://github.com/amaranth-lang/amaranth-boards" 41 | reference = "main" 42 | resolved_reference = "54000b09498080706152bbb8782f68b8efa0ad33" 43 | 44 | [[package]] 45 | name = "atomicwrites" 46 | version = "1.4.1" 47 | description = "Atomic file writes." 48 | optional = false 49 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 50 | files = [ 51 | {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, 52 | ] 53 | 54 | [[package]] 55 | name = "attrs" 56 | version = "23.1.0" 57 | description = "Classes Without Boilerplate" 58 | optional = false 59 | python-versions = ">=3.7" 60 | files = [ 61 | {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, 62 | {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, 63 | ] 64 | 65 | [package.extras] 66 | cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] 67 | dev = ["attrs[docs,tests]", "pre-commit"] 68 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] 69 | tests = ["attrs[tests-no-zope]", "zope-interface"] 70 | tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] 71 | 72 | [[package]] 73 | name = "colorama" 74 | version = "0.4.6" 75 | description = "Cross-platform colored terminal text." 76 | optional = false 77 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 78 | files = [ 79 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 80 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 81 | ] 82 | 83 | [[package]] 84 | name = "flake8" 85 | version = "3.9.2" 86 | description = "the modular source code checker: pep8 pyflakes and co" 87 | optional = false 88 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 89 | files = [ 90 | {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, 91 | {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, 92 | ] 93 | 94 | [package.dependencies] 95 | mccabe = ">=0.6.0,<0.7.0" 96 | pycodestyle = ">=2.7.0,<2.8.0" 97 | pyflakes = ">=2.3.0,<2.4.0" 98 | 99 | [[package]] 100 | name = "iniconfig" 101 | version = "2.0.0" 102 | description = "brain-dead simple config-ini parsing" 103 | optional = false 104 | python-versions = ">=3.7" 105 | files = [ 106 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 107 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 108 | ] 109 | 110 | [[package]] 111 | name = "jinja2" 112 | version = "3.1.2" 113 | description = "A very fast and expressive template engine." 114 | optional = false 115 | python-versions = ">=3.7" 116 | files = [ 117 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, 118 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, 119 | ] 120 | 121 | [package.dependencies] 122 | MarkupSafe = ">=2.0" 123 | 124 | [package.extras] 125 | i18n = ["Babel (>=2.7)"] 126 | 127 | [[package]] 128 | name = "markupsafe" 129 | version = "2.1.3" 130 | description = "Safely add untrusted strings to HTML/XML markup." 131 | optional = false 132 | python-versions = ">=3.7" 133 | files = [ 134 | {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, 135 | {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, 136 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, 137 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, 138 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, 139 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, 140 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, 141 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, 142 | {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, 143 | {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, 144 | {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, 145 | {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, 146 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, 147 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, 148 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, 149 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, 150 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, 151 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, 152 | {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, 153 | {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, 154 | {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, 155 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, 156 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, 157 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, 158 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, 159 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, 160 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, 161 | {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, 162 | {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, 163 | {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, 164 | {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, 165 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, 166 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, 167 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, 168 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, 169 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, 170 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, 171 | {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, 172 | {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, 173 | {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, 174 | {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, 175 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, 176 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, 177 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, 178 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, 179 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, 180 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, 181 | {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, 182 | {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, 183 | {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, 184 | ] 185 | 186 | [[package]] 187 | name = "mccabe" 188 | version = "0.6.1" 189 | description = "McCabe checker, plugin for flake8" 190 | optional = false 191 | python-versions = "*" 192 | files = [ 193 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 194 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 195 | ] 196 | 197 | [[package]] 198 | name = "packaging" 199 | version = "23.2" 200 | description = "Core utilities for Python packages" 201 | optional = false 202 | python-versions = ">=3.7" 203 | files = [ 204 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 205 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 206 | ] 207 | 208 | [[package]] 209 | name = "pluggy" 210 | version = "1.3.0" 211 | description = "plugin and hook calling mechanisms for python" 212 | optional = false 213 | python-versions = ">=3.8" 214 | files = [ 215 | {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, 216 | {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, 217 | ] 218 | 219 | [package.extras] 220 | dev = ["pre-commit", "tox"] 221 | testing = ["pytest", "pytest-benchmark"] 222 | 223 | [[package]] 224 | name = "py" 225 | version = "1.11.0" 226 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 227 | optional = false 228 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 229 | files = [ 230 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 231 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 232 | ] 233 | 234 | [[package]] 235 | name = "pycodestyle" 236 | version = "2.7.0" 237 | description = "Python style guide checker" 238 | optional = false 239 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 240 | files = [ 241 | {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, 242 | {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, 243 | ] 244 | 245 | [[package]] 246 | name = "pyflakes" 247 | version = "2.3.1" 248 | description = "passive checker of Python programs" 249 | optional = false 250 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 251 | files = [ 252 | {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, 253 | {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, 254 | ] 255 | 256 | [[package]] 257 | name = "pytest" 258 | version = "6.2.5" 259 | description = "pytest: simple powerful testing with Python" 260 | optional = false 261 | python-versions = ">=3.6" 262 | files = [ 263 | {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, 264 | {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, 265 | ] 266 | 267 | [package.dependencies] 268 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 269 | attrs = ">=19.2.0" 270 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 271 | iniconfig = "*" 272 | packaging = "*" 273 | pluggy = ">=0.12,<2.0" 274 | py = ">=1.8.2" 275 | toml = "*" 276 | 277 | [package.extras] 278 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 279 | 280 | [[package]] 281 | name = "pyvcd" 282 | version = "0.4.0" 283 | description = "Python VCD file support" 284 | optional = false 285 | python-versions = ">=3.7" 286 | files = [ 287 | {file = "pyvcd-0.4.0-py2.py3-none-any.whl", hash = "sha256:a21b10e5018b7940c8f2c20ef83d97496e86f15e215afed134ee115166035e17"}, 288 | {file = "pyvcd-0.4.0.tar.gz", hash = "sha256:31be3f501441a9b8c5dc72660ff7b9cfef9b43b2121a23d96f586d2863270290"}, 289 | ] 290 | 291 | [[package]] 292 | name = "toml" 293 | version = "0.10.2" 294 | description = "Python Library for Tom's Obvious, Minimal Language" 295 | optional = false 296 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 297 | files = [ 298 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 299 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 300 | ] 301 | 302 | [metadata] 303 | lock-version = "2.0" 304 | python-versions = "^3.10" 305 | content-hash = "aea475b84cba6b8bf37640de15b85ef27b225797b4e68cbb2dea1b3c8e9a4248" 306 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "amaranth-examples" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Adam Greig "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.10" 9 | amaranth = {git="https://github.com/amaranth-lang/amaranth", branch="main"} 10 | amaranth-boards = {git="https://github.com/amaranth-lang/amaranth-boards", branch="main"} 11 | 12 | [tool.poetry.dev-dependencies] 13 | pytest = "^6.2" 14 | flake8 = "^3.8" 15 | 16 | [build-system] 17 | requires = ["poetry-core>=1.0.0"] 18 | build-backend = "poetry.core.masonry.api" 19 | 20 | [tool.pytest.ini_options] 21 | python_files = "*.py" 22 | 23 | [tool.poe.tasks] 24 | test = "pytest" 25 | check = "flake8" 26 | --------------------------------------------------------------------------------