├── .gitignore ├── LICENSE ├── README.md ├── amlib ├── __init__.py ├── debug │ ├── __init__.py │ └── ila.py ├── dsp │ ├── __init__.py │ ├── convolution │ │ ├── __init__.py │ │ ├── convolutionmode.py │ │ └── mac.py │ ├── filterbank.py │ ├── fixedpointcicfilter.py │ ├── fixedpointfft.py │ ├── fixedpointfirfilter.py │ ├── fixedpointhbfilter.py │ ├── fixedpointiirfilter.py │ └── resampler.py ├── io │ ├── __init__.py │ ├── debouncer.py │ ├── i2c.py │ ├── i2s.py │ ├── led.py │ ├── max7219.py │ ├── serial.py │ ├── spi.py │ └── ws2812.py ├── soc │ ├── __init__.py │ ├── cpu.py │ ├── event.py │ ├── memory.py │ ├── peripheral.py │ ├── simplesoc.py │ └── uart.py ├── stream │ ├── __init__.py │ ├── arbiter.py │ ├── generator.py │ ├── i2c.py │ └── uart.py ├── test │ ├── __init__.py │ ├── crc.py │ └── utils.py └── utils │ ├── __init__.py │ ├── bits.py │ ├── bus.py │ ├── cdc.py │ ├── clockdivider.py │ ├── dividingcounter.py │ ├── edgetopulse.py │ ├── fifo.py │ ├── lfsr.py │ ├── nrziencoder.py │ ├── shiftregister.py │ └── timer.py ├── doc ├── conf.py ├── generate_doc.py └── unofficial-amaranth-library.rst ├── run-tests.sh ├── setup.py └── test ├── README.txt ├── dividing-counter.gtkw ├── edgetopulse.gtkw ├── fixedpointfirfilter.gtkw ├── fixedpointiirfilter.gtkw ├── i2c_stream_transmitter-bench.gtkw ├── i2s-loopback.gtkw ├── i2s-transmitter.gtkw ├── ila-basic.gtkw ├── ila-pretrigger.gtkw ├── number-to-bitbar.gtkw ├── resampler-bench-fir.gtkw ├── resampler-bench-iir.gtkw ├── serial-led-array.gtkw ├── shift-register-in.gtkw ├── shift-register-out.gtkw ├── spi-controller.gtkw ├── timer-oneshot.gtkw ├── timer-periodic.gtkw └── ws2812.gtkw /.gitignore: -------------------------------------------------------------------------------- 1 | .eggs/ 2 | *.vcd 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Each core/file in this library has its own license 2 | and copyright. Please refer to the file header of the 3 | file you use for license and copyright information. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amlib 2 | Assorted library of utility cores for amaranth HDL. 3 | 4 | This library is in active development, therefore beware that things 5 | may break! 6 | 7 | This library contains: 8 | 9 | **amlib.io** 10 | : Basic communication cores: 11 | * UART 12 | * I2C 13 | * I2S (currently transmit only) 14 | * SPI 15 | * MAX7219 SPI LED array driver core 16 | * seven segment driver, value to bitbar driver 17 | * neopixel (WS2812) RGB led strip driver core 18 | * Debouncer for debouncing button inputs 19 | 20 | **amlib.dsp** 21 | : Building blocks for digital signal processing: 22 | * fixed point FIR filter 23 | * fixed point IIR filter 24 | * fixed point CIC filter 25 | * fixed point halfband filter 26 | * fixed point FFT 27 | * filterbank 28 | * fractional resampler 29 | 30 | **amlib.dsp.convolution** 31 | : Convolution cores: 32 | * mac: A convolution core which uses parallel multiply-accumulate (MAC) calculation. 33 | This introduces a latency of only one sample. For longer impulse responses it will consume several hardware 34 | multipliers for calculations. 35 | 36 | **amlib.soc** 37 | : Building blocks for SOC creation: 38 | * CPU 39 | * interrupts 40 | * memory 41 | * wishbone 42 | * CSRs 43 | * SimpleSOC 44 | * peripherals 45 | 46 | **amlib.stream** 47 | * LiteX like streams 48 | * stream generators from ROM 49 | * stream to I2C 50 | * stream to/from FIFO 51 | * stream arbiter 52 | * stream to UART 53 | 54 | **amlib.debug** 55 | : Internal logic analyzer (ILA) 56 | 57 | **amlib.test** 58 | : Convenience tools for automated testing of simulations, CRC 59 | 60 | **amlib.utils** 61 | : basic utility modules: 62 | * bit manipulation functions 63 | * one-hot-multiplexer 64 | * synchronizer 65 | * strobe stretcher 66 | * dividing counter 67 | * edge to pulse 68 | * edge detectors 69 | * linear feedback shift register (LFSR) 70 | * NRZI encoder 71 | * shift register 72 | -------------------------------------------------------------------------------- /amlib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amaranth-farm/amlib/64e182122d2df39d0ce9e0e2dd6848640747b397/amlib/__init__.py -------------------------------------------------------------------------------- /amlib/debug/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amaranth-farm/amlib/64e182122d2df39d0ce9e0e2dd6848640747b397/amlib/debug/__init__.py -------------------------------------------------------------------------------- /amlib/dsp/__init__.py: -------------------------------------------------------------------------------- 1 | from .resampler import FractionalResampler 2 | from .fixedpointiirfilter import FixedPointIIRFilter 3 | from .fixedpointfirfilter import FixedPointFIRFilter 4 | from .filterbank import Filterbank 5 | from .fixedpointcicfilter import FixedPointCICFilter 6 | from .fixedpointhbfilter import FixedPointHBFilter 7 | from .fixedpointfft import FixedPointFFT 8 | 9 | __all__ = [FractionalResampler, FixedPointIIRFilter, FixedPointFIRFilter, Filterbank, FixedPointCICFilter, FixedPointHBFilter, FixedPointFFT] 10 | -------------------------------------------------------------------------------- /amlib/dsp/convolution/__init__.py: -------------------------------------------------------------------------------- 1 | from .convolutionmode import ConvolutionMode 2 | from .mac import StereoConvolutionMAC 3 | 4 | __all__ = [StereoConvolutionMAC, ConvolutionMode] 5 | -------------------------------------------------------------------------------- /amlib/dsp/convolution/convolutionmode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2022 Rouven Broszeit 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | 6 | from enum import Enum 7 | 8 | class ConvolutionMode(Enum): 9 | CROSSFEED = 1 10 | STEREO = 2 11 | MONO = 3 12 | 13 | -------------------------------------------------------------------------------- /amlib/dsp/filterbank.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2021 Hans Baier 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | # 6 | from .fixedpointiirfilter import FixedPointIIRFilter 7 | from .fixedpointfirfilter import FixedPointFIRFilter 8 | from amaranth import * 9 | 10 | class Filterbank(Elaboratable): 11 | def __init__(self, num_instances, 12 | samplerate: int, 13 | bitwidth: int=18, 14 | fraction_width: int=18, 15 | cutoff_freq: int=20000, 16 | filter_order: int=2, 17 | filter_structure: str='fir', # or 'iir' 18 | filter_type: str='lowpass', 19 | verbose: bool=True) -> None: 20 | 21 | self.enable_in = Signal() 22 | self.signal_in = Signal(signed(bitwidth)) 23 | self.signal_out = Signal(signed(bitwidth)) 24 | 25 | if filter_structure == 'iir': 26 | self.filters = [FixedPointIIRFilter(samplerate=samplerate, 27 | bitwidth=bitwidth, fraction_width=fraction_width, 28 | cutoff_freq=cutoff_freq, filter_order=filter_order, 29 | filter_type=filter_type, verbose=verbose) 30 | for _ in range(num_instances)] 31 | elif filter_structure == 'fir': 32 | self.filters = [FixedPointFIRFilter(samplerate=samplerate, 33 | bitwidth=bitwidth, fraction_width=fraction_width, 34 | cutoff_freq=cutoff_freq, filter_order=filter_order, 35 | filter_type=filter_type, verbose=verbose) 36 | for _ in range(num_instances)] 37 | else: 38 | assert False, f"Unsupported filter structure '{filter_structure}', supported are: 'fir' and 'iir'" 39 | 40 | def elaborate(self, platform) -> Module: 41 | m = Module() 42 | 43 | last_filter = None 44 | 45 | for f in self.filters: 46 | m.submodules += f 47 | m.d.comb += f.enable_in.eq(self.enable_in) 48 | 49 | if last_filter is not None: 50 | m.d.comb += f.signal_in.eq(last_filter.signal_out) 51 | 52 | last_filter = f 53 | 54 | m.d.comb += [ 55 | self.filters[0].signal_in.eq(self.signal_in), 56 | self.signal_out.eq(self.filters[-1].signal_out) 57 | ] 58 | 59 | return m -------------------------------------------------------------------------------- /amlib/dsp/fixedpointcicfilter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2021 Kaz Kojima 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | 6 | from amaranth import * 7 | from amaranth import Signal, Module, Elaboratable 8 | from math import log2, ceil 9 | 10 | from ..test import GatewareTestCase, sync_test_case 11 | 12 | class FixedPointCICFilter(Elaboratable): 13 | """ CIC (cascaded integrator comb) filter 14 | 15 | Attributes 16 | ---------- 17 | signal_in: Signal(width), input 18 | Filter input signal, must be +-1 only 19 | strobe_in: Signal(), input 20 | Input strobe, must be 1 sys clock period 21 | strobe_out: Signal(), out 22 | Output strobe 23 | signal_out: Signal(width), out 24 | Filter output signal 25 | 26 | Parameters 27 | ---------- 28 | bitwidth: int 29 | width 30 | filter_stage: int 31 | number of filter stages 32 | decimation: int 33 | decimation factor 34 | verbose: bool 35 | verbose flag 36 | """ 37 | def __init__(self, 38 | bitwidth: int=18, 39 | filter_stage: int=4, 40 | decimation: int=12, 41 | verbose: bool=True) -> None: 42 | 43 | self.strobe_in = Signal() 44 | self.strobe_out = Signal() 45 | self.signal_in = Signal(signed(bitwidth)) 46 | self.signal_out = Signal(signed(bitwidth)) 47 | 48 | self.stage = filter_stage 49 | self.decimation = decimation 50 | self.bitwidth = bitwidth 51 | self.delay_width = max(1 + ceil(filter_stage*log2(decimation)), bitwidth) 52 | 53 | if verbose: 54 | print(f"{filter_stage}-stage CIC with decimation: {decimation}") 55 | 56 | def elaborate(self, platform) -> Module: 57 | m = Module() 58 | 59 | n = self.stage 60 | width = self.delay_width 61 | integrator_edge = self.strobe_in 62 | comb_edge = Signal() 63 | decimate_counter = Signal(range(0, self.decimation)) 64 | # we use the array indices flipped, ascending from zero 65 | # so x[0] is x_n, x[1] is x_n- 66 | # 1, x[2] is x_n-2 ... 67 | # in other words: higher indices are past values, 0 is most recent 68 | # Integrators 69 | x = Array(Signal(signed(width), name=f"x{i}") for i in range(n)) 70 | # Combs 71 | y = Array(Signal(signed(width), name=f"y{i}") for i in range(n)) 72 | dy = Array(Signal(signed(width), name=f"dy{i}") for i in range(n)) 73 | 74 | m.d.sync += self.strobe_out.eq(comb_edge) 75 | 76 | with m.If(integrator_edge): 77 | m.d.sync += x[0].eq(self.signal_in + x[0]) 78 | m.d.sync += [x[i + 1].eq(x[i] + x[i + 1]) for i in range(n - 1)] 79 | with m.If(decimate_counter < self.decimation - 1): 80 | m.d.sync += decimate_counter.eq(decimate_counter + 1) 81 | 82 | with m.Else(): 83 | m.d.sync += decimate_counter.eq(0) 84 | m.d.sync += comb_edge.eq(1) 85 | 86 | with m.If(comb_edge): 87 | m.d.sync += y[0].eq(x[n - 1] - dy[0]) 88 | m.d.sync += [y[i + 1].eq(y[i] - dy[i + 1]) for i in range(n - 1)] 89 | m.d.sync += self.signal_out.eq(y[n - 1] >> (width - self.bitwidth)) 90 | m.d.sync += dy[0].eq(x[n - 1]) 91 | m.d.sync += [dy[i + 1].eq(y[i]) for i in range(n - 1)] 92 | m.d.sync += comb_edge.eq(0) 93 | 94 | return m 95 | 96 | class FixedPointCICFilterTest(GatewareTestCase): 97 | FRAGMENT_UNDER_TEST = FixedPointCICFilter 98 | FRAGMENT_ARGUMENTS = dict() 99 | 100 | @sync_test_case 101 | def test_cic(self): 102 | dut = self.dut 103 | N = 1024 104 | for i in range(N): 105 | yield dut.signal_in.eq(1 if ((i//256) & 1) == 0 else -1) 106 | yield 107 | yield dut.strobe_in.eq(1) 108 | yield 109 | yield dut.strobe_in.eq(0) 110 | yield 111 | -------------------------------------------------------------------------------- /amlib/dsp/fixedpointfirfilter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2021 Hans Baier 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | 6 | from scipy import signal 7 | from amaranth import * 8 | from pprint import pformat 9 | import numpy as np 10 | 11 | from ..test import GatewareTestCase, sync_test_case 12 | 13 | class FixedPointFIRFilter(Elaboratable): 14 | def __init__(self, 15 | samplerate: int, 16 | bitwidth: int=18, 17 | fraction_width: int=18, 18 | cutoff_freq: int=20000, 19 | filter_order: int=24, 20 | filter_type: str='lowpass', 21 | weight: list=None, 22 | mac_loop: bool=False, 23 | verbose: bool=True) -> None: 24 | 25 | self.enable_in = Signal() 26 | self.signal_in = Signal(signed(bitwidth)) 27 | self.signal_out = Signal(signed(bitwidth)) 28 | 29 | if type(cutoff_freq) == int: 30 | cutoff = cutoff_freq / samplerate 31 | taps = signal.firwin(filter_order, cutoff, fs=samplerate, pass_zero=filter_type, window='hamming') 32 | elif type(cutoff_freq) == list and len(cutoff_freq) == 2: 33 | Fs = samplerate 34 | Fpb = cutoff_freq[0] 35 | Fsb = cutoff_freq[1] 36 | bands = np.array([0., Fpb/Fs, Fsb/Fs, .5]) 37 | pass_zero = filter_type == True or filter_type == 'lowpass' 38 | desired = [1, 0] if pass_zero else [0, 1] 39 | taps = signal.remez(filter_order, bands, desired, weight) 40 | else: 41 | raise TypeError('cutoff_freq parameter must be int or list of start/stop band frequencies') 42 | # convert to fixed point representation 43 | self.bitwidth = bitwidth 44 | self.fraction_width = fraction_width 45 | assert bitwidth <= fraction_width, f"Bitwidth {bitwidth} must not exceed {fraction_width}" 46 | self.taps = taps_fp = [int(x * 2**fraction_width) for x in taps] 47 | 48 | self.mac_loop = mac_loop 49 | 50 | if verbose: 51 | if type(cutoff_freq) == int: 52 | print(f"{filter_order}-order windowed FIR with cutoff: {cutoff * samplerate}") 53 | else: 54 | print(f"{filter_order}-order FIR with start/stop band: {cutoff_freq} weight: {weight}") 55 | print(f"taps: {pformat(taps)}") 56 | print(f"taps ({bitwidth}.{fraction_width} fixed point): {taps_fp}\n") 57 | 58 | def conversion_error(coeff, fp_coeff): 59 | val = 2**(bitwidth - 1) 60 | fp_product = fp_coeff * val 61 | fp_result = fp_product >> fraction_width 62 | fp_error = fp_result - (coeff * val) 63 | return fp_error 64 | 65 | num_coefficients = len(taps_fp) 66 | conversion_errors = [abs(conversion_error(taps[i], taps_fp[i])) for i in range(num_coefficients)] 67 | if verbose: 68 | print("a, fixed point conversion errors: {}".format(conversion_errors)) 69 | for i in range(num_coefficients): 70 | assert (conversion_errors[i] < 1.0) 71 | 72 | def elaborate(self, platform) -> Module: 73 | m = Module() 74 | 75 | n = len(self.taps) 76 | width = self.bitwidth + self.fraction_width 77 | taps = Array(Const(n, signed(width)) for n in self.taps) 78 | 79 | # we use the array indices flipped, ascending from zero 80 | # so x[0] is x_n, x[1] is x_n- 81 | # 1, x[2] is x_n-2 ... 82 | # in other words: higher indices are past values, 0 is most recent 83 | x = Array(Signal(signed(width), name=f"x{i}") for i in range(n)) 84 | 85 | if self.mac_loop: 86 | ix = Signal(range(n + 1)) 87 | madd = Signal(signed(self.bitwidth)) 88 | a = Signal(signed(self.bitwidth)) 89 | b = Signal(signed(self.bitwidth)) 90 | 91 | with m.FSM(reset="IDLE"): 92 | with m.State("IDLE"): 93 | with m.If(self.enable_in): 94 | m.d.sync += [ 95 | ix.eq(1), 96 | a.eq(x[0]), 97 | b.eq(taps[0]), 98 | madd.eq(0) 99 | ] 100 | m.next = "MAC" 101 | 102 | with m.State("MAC"): 103 | m.d.sync += madd.eq(madd + ((a * b) >> self.fraction_width)) 104 | with m.If(ix == n): 105 | m.next = "OUTPUT" 106 | with m.Else(): 107 | m.d.sync += [ 108 | a.eq(x[ix]), 109 | b.eq(taps[ix]), 110 | ix.eq(ix + 1) 111 | ] 112 | 113 | with m.State("OUTPUT"): 114 | m.d.sync += self.signal_out.eq(madd) 115 | m.next = "IDLE" 116 | 117 | else: 118 | m.d.comb += self.signal_out.eq( 119 | sum([((x[i] * taps[i]) >> self.fraction_width) for i in range(n)])) 120 | 121 | with m.If(self.enable_in): 122 | m.d.sync += [x[i + 1].eq(x[i]) for i in range(n - 1)] 123 | 124 | m.d.sync += x[0].eq(self.signal_in) 125 | 126 | return m 127 | 128 | 129 | class FixedPointFIRFilterTest(GatewareTestCase): 130 | FRAGMENT_UNDER_TEST = FixedPointFIRFilter 131 | FRAGMENT_ARGUMENTS = dict(samplerate=336000) 132 | 133 | @sync_test_case 134 | def test_fir(self): 135 | dut = self.dut 136 | max = int(2**15 - 1) 137 | min = -max 138 | yield dut.enable_in.eq(1) 139 | for _ in range(20): yield 140 | yield dut.signal_in.eq(max) 141 | for _ in range(100): yield 142 | yield dut.signal_in.eq(min) 143 | for _ in range(5): yield 144 | yield dut.enable_in.eq(0) 145 | for _ in range(20): yield 146 | yield dut.enable_in.eq(1) 147 | for _ in range(60): yield 148 | yield dut.signal_in.eq(0) 149 | for _ in range(100): yield 150 | for i in range(10): 151 | yield dut.signal_in.eq(max) 152 | yield 153 | yield dut.signal_in.eq(0) 154 | yield 155 | for _ in range(6): yield 156 | yield dut.signal_in.eq(min) 157 | yield 158 | yield dut.signal_in.eq(0) 159 | yield 160 | for _ in range(6): yield 161 | 162 | -------------------------------------------------------------------------------- /amlib/dsp/fixedpointhbfilter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2021 Kaz Kojima 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | 6 | from scipy import signal 7 | from amaranth import * 8 | from pprint import pformat 9 | import numpy as np 10 | 11 | from ..test import GatewareTestCase, sync_test_case 12 | 13 | class FixedPointHBFilter(Elaboratable): 14 | def __init__(self, 15 | bitwidth: int=18, 16 | fraction_width: int=18, 17 | filter_order: int=19, 18 | mac_loop: bool = False, 19 | verbose: bool=True) -> None: 20 | 21 | self.strobe_in = Signal() 22 | self.strobe_out = Signal() 23 | self.signal_in = Signal(signed(bitwidth)) 24 | self.signal_out = Signal(signed(bitwidth)) 25 | 26 | assert (filter_order & 1) and (filter_order//2 & 1), f"Supported only 4m+3 filter_order {filter_order}" 27 | # firwin might give lower attenuation/ripple but slower transition band 28 | # use remez now for the transition band 29 | # taps = signal.firwin(filter_order, 0.5) 30 | bands = np.array([0.0, 0.22, 0.28, 0.5]) 31 | taps = signal.remez(filter_order, bands, [1, 0], [1, 1]) 32 | 33 | # convert to fixed point representation 34 | self.bitwidth = bitwidth 35 | self.fraction_width = fraction_width 36 | assert bitwidth <= fraction_width, f"Bitwidth {bitwidth} must not exceed {fraction_width}" 37 | self.taps = taps_fp = [int(x * 2**fraction_width) for x in taps] 38 | 39 | self.mac_loop = mac_loop 40 | 41 | if verbose: 42 | print(f"{filter_order}-order windowed Half Band") 43 | print(f"taps: {pformat(taps)}") 44 | print(f"taps ({bitwidth}.{fraction_width} fixed point): {taps_fp}\n") 45 | 46 | def conversion_error(coeff, fp_coeff): 47 | val = 2**(bitwidth - 1) 48 | fp_product = fp_coeff * val 49 | fp_result = fp_product >> fraction_width 50 | fp_error = fp_result - (coeff * val) 51 | return fp_error 52 | 53 | num_coefficients = len(taps_fp) 54 | conversion_errors = [abs(conversion_error(taps[i], taps_fp[i])) for i in range(num_coefficients)] 55 | if verbose: 56 | print("a, fixed point conversion errors: {}".format(conversion_errors)) 57 | for i in range(num_coefficients): 58 | assert (conversion_errors[i] < 1.0) 59 | 60 | def elaborate(self, platform) -> Module: 61 | m = Module() 62 | 63 | n = len(self.taps) 64 | width = self.bitwidth + self.fraction_width 65 | taps = Array(Const(n, signed(width)) for n in self.taps) 66 | decimate_counter = Signal(range(0, 2)) 67 | 68 | # we use the array indices flipped, ascending from zero 69 | # so x[0] is x_n, x[1] is x_n- 70 | # 1, x[2] is x_n-2 ... 71 | # in other words: higher indices are past values, 0 is most recent 72 | x = Array(Signal(signed(width), name=f"x{i}") for i in range(n)) 73 | 74 | if self.mac_loop: 75 | ix = Signal(range(n + 2)) 76 | madd = Signal(signed(self.bitwidth + 1)) 77 | a = Signal(signed(self.bitwidth + 1)) 78 | b = Signal(signed(self.bitwidth)) 79 | 80 | with m.FSM(reset="IDLE"): 81 | with m.State("IDLE"): 82 | with m.If(self.strobe_in): 83 | m.d.sync += [ 84 | ix.eq(2), 85 | a.eq(x[0] + x[n - 1]), 86 | b.eq(taps[0]), 87 | madd.eq(0) 88 | ] 89 | m.next = "MAC" 90 | 91 | with m.State("MAC"): 92 | m.d.sync += madd.eq(madd + ((a * b) >> self.fraction_width)) 93 | with m.If(ix > n//2): 94 | m.next = "OUTPUT" 95 | with m.Else(): 96 | m.d.sync += [ 97 | a.eq(x[ix] + x[n - 1 - ix]), 98 | b.eq(taps[ix]), 99 | ix.eq(ix + 2) 100 | ] 101 | 102 | with m.State("OUTPUT"): 103 | m.d.sync += self.signal_out.eq(madd + (x[n//2] >> 1)) 104 | m.next = "IDLE" 105 | 106 | else: 107 | m.d.comb += self.signal_out.eq( 108 | sum([(((x[2*i] + x[n - 1 - 2*i]) * taps[2*i]) >> self.fraction_width) for i in range(n//4 + 1)], (x[n//2] >> 1))) 109 | 110 | with m.If(self.strobe_in): 111 | m.d.sync += x[0].eq(self.signal_in) 112 | m.d.sync += [x[i + 1].eq(x[i]) for i in range(n - 1)] 113 | with m.If(decimate_counter < 1): 114 | m.d.sync += decimate_counter.eq(decimate_counter + 1) 115 | with m.Else(): 116 | m.d.sync += decimate_counter.eq(0) 117 | m.d.sync += self.strobe_out.eq(1) 118 | 119 | with m.If(self.strobe_out): 120 | m.d.sync += self.strobe_out.eq(0) 121 | 122 | return m 123 | 124 | 125 | class FixedPointHBFilterTest(GatewareTestCase): 126 | FRAGMENT_UNDER_TEST = FixedPointHBFilter 127 | FRAGMENT_ARGUMENTS = dict() 128 | 129 | @sync_test_case 130 | def test_hb(self): 131 | dut = self.dut 132 | max = int(2**15 - 1) 133 | min = -max 134 | for i in range(8192): 135 | yield dut.signal_in.eq(min if ((i//64) & 1) else max) 136 | yield 137 | yield dut.strobe_in.eq(1) 138 | yield 139 | yield dut.strobe_in.eq(0) 140 | yield 141 | for _ in range(58): yield 142 | -------------------------------------------------------------------------------- /amlib/dsp/fixedpointiirfilter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2021 Hans Baier 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | 6 | from math import ceil 7 | from scipy import signal 8 | from amaranth import * 9 | from pprint import pformat 10 | 11 | from ..test import GatewareTestCase, sync_test_case 12 | 13 | class FixedPointIIRFilter(Elaboratable): 14 | def __init__(self, 15 | samplerate: int, 16 | bitwidth: int=18, 17 | fraction_width: int=18, 18 | cutoff_freq: int=20000, 19 | filter_order: int=2, 20 | filter_type: str='lowpass', 21 | verbose: bool=False) -> None: 22 | 23 | self.enable_in = Signal() 24 | self.signal_in = Signal(signed(bitwidth)) 25 | self.signal_out = Signal(signed(bitwidth)) 26 | 27 | nyquist_frequency = samplerate * 0.5 28 | cutoff = cutoff_freq / nyquist_frequency 29 | allowed_ripple = 1.0 # dB 30 | b, a = signal.cheby1(filter_order, allowed_ripple, cutoff, btype=filter_type, output='ba') 31 | 32 | # convert to fixed point representation 33 | self.bitwidth = bitwidth 34 | self.fraction_width = fraction_width 35 | assert bitwidth <= fraction_width, f"Bitwidth {bitwidth} must not exceed {fraction_width}" 36 | self.b = b_fp = [int(x * 2**fraction_width) for x in b] 37 | self.a = a_fp = [int(x * 2**fraction_width) for x in a] 38 | 39 | if verbose: 40 | print(f"{filter_order}-order Chebyshev-Filter cutoff: {cutoff * nyquist_frequency}" + \ 41 | f" max ripple: {allowed_ripple}dB\n") 42 | print(f"b: {pformat(b)}") 43 | print(f"a: {pformat(a)}") 44 | print(f"b ({bitwidth}.{fraction_width} fixed point): {b_fp}") 45 | print(f"a ({bitwidth}.{fraction_width} fixed point): {a_fp}\n") 46 | assert len(b_fp) == len(a_fp) 47 | 48 | def conversion_error(coeff, fp_coeff): 49 | val = 2**(bitwidth - 1) 50 | fp_product = fp_coeff * val 51 | fp_result = fp_product >> fraction_width 52 | fp_error = fp_result - (coeff * val) 53 | return fp_error 54 | 55 | num_coefficients = len(b) 56 | conversion_errors_b = [abs(conversion_error(b[i], b_fp[i])) for i in range(num_coefficients)] 57 | conversion_errors_a = [abs(conversion_error(a[i], a_fp[i])) for i in range(num_coefficients)] 58 | if verbose: 59 | print("b, fixed point conversion errors: {}".format(conversion_errors_a)) 60 | print("a, fixed point conversion errors: {}".format(conversion_errors_b)) 61 | for i in range(num_coefficients): 62 | assert (conversion_errors_b[i] < 1.0) 63 | assert (conversion_errors_a[i] < 1.0) 64 | 65 | def elaborate(self, platform) -> Module: 66 | m = Module() 67 | 68 | # see https://en.wikipedia.org/wiki/Infinite_impulse_response 69 | # and https://en.wikipedia.org/wiki/Digital_filter 70 | # b are the input coefficients 71 | # a are the recursive (output) coefficients 72 | n = len(self.a) 73 | width = self.bitwidth + self.fraction_width 74 | b = [Const(n, signed(width)) for n in self.b] 75 | # the filter design tool generates a '1.0' coefficient for a_n, which we don't need 76 | a = [Const(n, signed(width)) for n in self.a[1:]] 77 | 78 | # we use the array indices flipped, ascending from zero 79 | # so x[0] is x_n, x[1] is x_n-1, x[2] is x_n-2 ... 80 | # in other words: higher indices are past values, 0 is most recent 81 | x = Array(Signal(signed(width), name=f"x{i}") for i in range(n)) 82 | # because y[0] would be the output value, the y array is shifted by one: 83 | # y[0] is y_n-1, y[1] is y_n-2, y[2] is y_n-3 84 | # but the signals are still named 'right' to be easy to understnad 85 | # in the waveform viewer 86 | y = Array(Signal(signed(width), name=f"y{i+1}") for i in range(n - 1)) 87 | 88 | m.d.comb += self.signal_out.eq( 89 | sum([((x[i] * b[i]) >> self.fraction_width) for i in range(n)]) 90 | - sum([((y[i] * a[i]) >> self.fraction_width) for i in range(n - 1)])) 91 | 92 | with m.If(self.enable_in): 93 | m.d.sync += [x[i + 1].eq(x[i]) for i in range(n - 1)] 94 | m.d.sync += [y[i + 1].eq(y[i]) for i in range(n - 2)] 95 | 96 | m.d.sync += x[0].eq(self.signal_in) 97 | m.d.sync += y[0].eq(self.signal_out) 98 | 99 | return m 100 | 101 | class FixedPointIIRFilterTest(GatewareTestCase): 102 | FRAGMENT_UNDER_TEST = FixedPointIIRFilter 103 | FRAGMENT_ARGUMENTS = dict(samplerate=336000) 104 | 105 | @sync_test_case 106 | def test_iir(self): 107 | dut = self.dut 108 | max = int(2**15 - 1) 109 | min = -max 110 | yield dut.enable_in.eq(1) 111 | for _ in range(20): yield 112 | yield dut.signal_in.eq(max) 113 | for _ in range(100): yield 114 | yield dut.signal_in.eq(min) 115 | for _ in range(5): yield 116 | yield dut.enable_in.eq(0) 117 | for _ in range(20): yield 118 | yield dut.enable_in.eq(1) 119 | for _ in range(60): yield 120 | yield dut.signal_in.eq(0) 121 | for _ in range(100): yield 122 | for i in range(10): 123 | yield dut.signal_in.eq(max) 124 | yield 125 | yield dut.signal_in.eq(0) 126 | yield 127 | for _ in range(6): yield 128 | yield dut.signal_in.eq(min) 129 | yield 130 | yield dut.signal_in.eq(0) 131 | yield 132 | for _ in range(6): yield 133 | -------------------------------------------------------------------------------- /amlib/dsp/resampler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2021 Hans Baier 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | 6 | import math 7 | from amaranth import * 8 | from amaranth.lib.fifo import SyncFIFO 9 | from amlib.stream import StreamInterface 10 | from .filterbank import Filterbank 11 | 12 | from ..test import GatewareTestCase, sync_test_case 13 | 14 | class FractionalResampler(Elaboratable): 15 | """Fractional Resampler 16 | converts a signal to a different samplerate by a 17 | fractional factor M/N by means of: 18 | 1. upsampling the signal by factor M 19 | 2. low pass filtering the upsampled signal 20 | 3. decimating the filtered signal by factor N 21 | 22 | Currently only fir filtering is supported and recommended. 23 | IIR filtering has not been tested working yet. 24 | """ 25 | def __init__(self, *, 26 | input_samplerate: int, 27 | upsample_factor: int, 28 | downsample_factor: int, 29 | filter_structure: str ='fir', # or 'iir' 30 | filter_instances: int = 1, 31 | filter_order: int = 24, 32 | filter_cutoff: int = 20000, 33 | bitwidth: int = 16, 34 | prescale: int = None, 35 | verbose: bool = True) -> None: 36 | self.signal_in = StreamInterface(payload_width=bitwidth) 37 | self.signal_out = StreamInterface(payload_width=bitwidth) 38 | 39 | self.input_samplerate = input_samplerate 40 | self.upsample_factor = upsample_factor 41 | self.downsample_factor = downsample_factor 42 | self.filter_structure = filter_structure 43 | self.filter_order = filter_order 44 | self.filter_instances = filter_instances 45 | self.filter_cutoff = filter_cutoff 46 | self.bitwidth = bitwidth 47 | self.verbose = verbose 48 | self.prescale = prescale 49 | 50 | def elaborate(self, platform) -> Module: 51 | m = Module() 52 | 53 | # FPGA multipliers are multiples of 9 bit wide 54 | # so add 1 bit of headroom for every 8 bits 55 | headroom_bitwidth = int(math.ceil(self.bitwidth/8) * 9) 56 | 57 | if self.filter_structure == 'iir': 58 | prescale = (self.upsample_factor - 1) if self.prescale is None else self.prescale 59 | else: 60 | prescale = 4 if self.prescale is None else self.prescale 61 | 62 | m.submodules.antialiasingfilter = antialiasingfilter = \ 63 | Filterbank(self.filter_instances, 64 | self.input_samplerate * self.upsample_factor, 65 | bitwidth=headroom_bitwidth, 66 | filter_structure=self.filter_structure, 67 | cutoff_freq=self.filter_cutoff, 68 | filter_order=self.filter_order, 69 | verbose=self.verbose) 70 | 71 | m.submodules.downsamplefifo = downsamplefifo = \ 72 | SyncFIFO(width=self.bitwidth, depth=self.upsample_factor) 73 | 74 | # upsampling 75 | upsampled_signal = Signal(signed(headroom_bitwidth)) 76 | upsample_counter = Signal(range(self.upsample_factor)) 77 | input_data = Signal(signed(self.bitwidth)) 78 | input_ready = Signal() 79 | input_valid = Signal() 80 | 81 | m.d.comb += [ 82 | self.signal_in.ready.eq(input_ready), 83 | input_valid.eq(self.signal_in.valid), 84 | input_ready.eq((upsample_counter == 0) & (downsamplefifo.w_rdy)), 85 | input_data.eq(self.signal_in.payload.as_signed()), 86 | antialiasingfilter.signal_in.eq(upsampled_signal), 87 | antialiasingfilter.enable_in.eq(upsample_counter > 0), 88 | downsamplefifo.w_en.eq(downsamplefifo.w_rdy & antialiasingfilter.enable_in), 89 | ] 90 | 91 | with m.If(input_valid & input_ready): 92 | m.d.comb += [ 93 | upsampled_signal.eq(input_data * Const(prescale)), 94 | antialiasingfilter.enable_in.eq(1), 95 | ] 96 | m.d.sync += upsample_counter.eq(self.upsample_factor - 1) 97 | with m.Elif(upsample_counter > 0): 98 | m.d.comb += upsampled_signal.eq(0) 99 | m.d.sync += upsample_counter.eq(upsample_counter - 1) 100 | 101 | # downsampling and output 102 | downsample_counter = Signal(range(self.downsample_factor)) 103 | 104 | m.d.comb += [ 105 | downsamplefifo.w_data.eq(antialiasingfilter.signal_out), 106 | self.signal_out.valid.eq(downsamplefifo.r_rdy), 107 | ] 108 | 109 | with m.If(downsamplefifo.r_rdy & self.signal_out.ready): 110 | m.d.comb += downsamplefifo.r_en.eq(1) 111 | 112 | with m.If(downsample_counter == 0): 113 | m.d.sync += downsample_counter.eq(self.downsample_factor - 1) 114 | m.d.comb += [ 115 | self.signal_out.payload.eq(downsamplefifo.r_data), 116 | self.signal_out.valid.eq(1), 117 | ] 118 | with m.Else(): 119 | m.d.sync += downsample_counter.eq(downsample_counter - 1) 120 | m.d.comb += self.signal_out.valid.eq(0) 121 | 122 | with m.Else(): 123 | m.d.comb += [ 124 | downsamplefifo.r_en.eq(0), 125 | self.signal_out.valid.eq(0), 126 | ] 127 | 128 | return m 129 | 130 | class ResamplerTestFIR(GatewareTestCase): 131 | FRAGMENT_UNDER_TEST = FractionalResampler 132 | FRAGMENT_ARGUMENTS = dict( 133 | filter_structure='fir', filter_instances=1, filter_order=24, \ 134 | input_samplerate=56000, upsample_factor=6, downsample_factor=7, \ 135 | filter_cutoff=20000, prescale=4) 136 | 137 | @sync_test_case 138 | def test_fir(self): 139 | dut = self.dut 140 | max = int(2**15 - 1) 141 | min = -max 142 | for _ in range(10): yield 143 | yield dut.signal_out.ready.eq(1) 144 | for i in range(600): 145 | yield 146 | if i < 250: 147 | if i % 6 == 0: 148 | yield dut.signal_in.valid.eq(1) 149 | yield dut.signal_in.payload.eq(max) 150 | else: 151 | yield dut.signal_in.valid.eq(0) 152 | elif i == 500: 153 | yield dut.signal_out.ready.eq(0) 154 | else: 155 | if i % 6 == 0: 156 | yield dut.signal_in.valid.eq(1) 157 | yield dut.signal_in.payload.eq(min) 158 | else: 159 | yield dut.signal_in.valid.eq(0) 160 | 161 | class ResamplerTestIIR(GatewareTestCase): 162 | FRAGMENT_UNDER_TEST = FractionalResampler 163 | FRAGMENT_ARGUMENTS = dict( 164 | filter_structure='iir', input_samplerate=56000, \ 165 | upsample_factor=6, downsample_factor=7, filter_cutoff=20000, prescale=4) 166 | 167 | @sync_test_case 168 | def test_iir(self): 169 | dut = self.dut 170 | max = int(2**15 - 1) 171 | min = -max 172 | for _ in range(10): yield 173 | yield dut.signal_out.ready.eq(1) 174 | for i in range(600): 175 | yield 176 | if i < 250: 177 | if i % 6 == 0: 178 | yield dut.signal_in.valid.eq(1) 179 | yield dut.signal_in.payload.eq(max) 180 | else: 181 | yield dut.signal_in.valid.eq(0) 182 | elif i == 500: 183 | yield dut.signal_out.ready.eq(0) 184 | else: 185 | if i % 6 == 0: 186 | yield dut.signal_in.valid.eq(1) 187 | yield dut.signal_in.payload.eq(min) 188 | else: 189 | yield dut.signal_in.valid.eq(0) -------------------------------------------------------------------------------- /amlib/io/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Hans Baier 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | from .debouncer import Debouncer 5 | from .serial import AsyncSerial, AsyncSerialRX, AsyncSerialTX 6 | from .i2c import I2CBus, I2CInitiator, I2CTarget 7 | from .spi import SPIControllerBus, SPIDeviceBus, SPIControllerInterface, SPIDeviceInterface, SPIRegisterInterface, SPIMultiplexer 8 | 9 | __all__ = [ 10 | "AsyncSerial", "AsyncSerialRX", "AsyncSerialTX", 11 | "Debouncer", 12 | "I2CBus", "I2CInitiator", "I2CTarget", 13 | "SPIControllerBus", "SPIDeviceBus", "SPIControllerInterface", "SPIDeviceInterface", "SPIRegisterInterface", "SPIMultiplexer" 14 | ] 15 | -------------------------------------------------------------------------------- /amlib/io/debouncer.py: -------------------------------------------------------------------------------- 1 | from amaranth import Signal, Elaboratable, Module 2 | 3 | class Debouncer(Elaboratable): 4 | """A debouncer for buttons or other unstable signals. 5 | Obtained from: 6 | https://github.com/lawrie/blackicemx_nmigen_examples 7 | 8 | Attributes 9 | ---------- 10 | btn_in: Signal() 11 | The signal to be debounced. 12 | 13 | btn_state_out: ignal() 14 | The current state of the button. 15 | 16 | btn_down_out: Signal() 17 | Strobed when the button is pressed. 18 | 19 | btn_up_out = Signal() 20 | Strobed when the button is released. 21 | 22 | 23 | Usage example 24 | ------------- 25 | led = platform.request("led", 0) 26 | btn = platform.request("button", 0) 27 | m = Module() 28 | m.submodules.debouncer = debouncer = Debouncer() 29 | m.d.comb += debouncer.btn_in.eq(btn) 30 | 31 | with m.If(debouncer.btn_up_out): 32 | m.d.sync += led.eq(~led) # toggle led 33 | """ 34 | def __init__(self): 35 | self.btn_in = Signal() 36 | self.btn_state_out = Signal(reset=0) 37 | self.btn_down_out = Signal() 38 | self.btn_up_out = Signal() 39 | 40 | def elaborate(self, platform): 41 | cnt = Signal(15, reset=0) 42 | btn_sync = Signal(2, reset=0) 43 | idle = Signal() 44 | cnt_max = Signal() 45 | 46 | m = Module() 47 | 48 | m.d.comb += [ 49 | idle.eq(self.btn_state_out == btn_sync[1]), 50 | cnt_max.eq(cnt.all()), 51 | self.btn_down_out.eq(~idle & cnt_max & ~self.btn_state_out), 52 | self.btn_up_out.eq(~idle & cnt_max & self.btn_state_out) 53 | ] 54 | 55 | m.d.sync += [ 56 | btn_sync[0].eq(~self.btn_in), 57 | btn_sync[1].eq(btn_sync[0]) 58 | ] 59 | 60 | with m.If(idle): 61 | m.d.sync += cnt.eq(0) 62 | with m.Else(): 63 | m.d.sync += cnt.eq(cnt + 1); 64 | with m.If(cnt_max): 65 | m.d.sync += self.btn_state_out.eq(~self.btn_state_out) 66 | 67 | return m 68 | -------------------------------------------------------------------------------- /amlib/io/led.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Hans Baier hansfbaier@gmail.com 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | from enum import IntEnum 6 | 7 | from amaranth import * 8 | from amaranth.build import Platform 9 | 10 | from ..test import GatewareTestCase, sync_test_case 11 | 12 | seven_segment_font = [ 13 | ['A', 0b1110111], ['B', 0b1111111], ['C', 0b1001110], ['D', 0b1111110], ['E', 0b1001111], ['F', 0b1000111], 14 | ['G', 0b1011110], ['H', 0b0110111], ['I', 0b0110000], ['J', 0b0111100], ['L', 0b0001110], ['N', 0b1110110], 15 | ['O', 0b1111110], ['P', 0b1100111], ['R', 0b0000101], ['S', 0b1011011], ['T', 0b0001111], ['U', 0b0111110], 16 | ['Y', 0b0100111], ['[', 0b1001110], [']', 0b1111000], ['_', 0b0001000], ['a', 0b1110111], ['b', 0b0011111], 17 | ['c', 0b0001101], ['d', 0b0111101], ['e', 0b1001111], ['f', 0b1000111], ['g', 0b1011110], ['h', 0b0010111], 18 | ['i', 0b0010000], ['j', 0b0111100], ['l', 0b0001110], ['n', 0b0010101], ['o', 0b1111110], ['p', 0b1100111], 19 | ['r', 0b0000101], ['s', 0b1011011], ['t', 0b0001111], ['u', 0b0011100], ['y', 0b0100111], ['-', 0b0000001], 20 | [' ', 0b0000000], ['0', 0b1111110], ['1', 0b0110000], ['2', 0b1101101], ['3', 0b1111001], ['4', 0b0110011], 21 | ['5', 0b1011011], ['6', 0b1011111], ['7', 0b1110000], ['8', 0b1111111], ['9', 0b1111011], ['/0', 0b0000000], 22 | ] 23 | 24 | seven_segment_hex_numbers = [ 25 | ['0', 0b1111110], ['1', 0b0110000], ['2', 0b1101101], ['3', 0b1111001], ['4', 0b0110011], 26 | ['5', 0b1011011], ['6', 0b1011111], ['7', 0b1110000], ['8', 0b1111111], ['9', 0b1111011], 27 | ['A', 0b1110111], ['b', 0b0011111], ['C', 0b1001110], ['D', 0b1111110], ['E', 0b1001111], ['F', 0b1000111], 28 | ] 29 | 30 | class NibbleToSevenSegmentHex(Elaboratable): 31 | def __init__(self): 32 | self.nibble_in = Signal(4) 33 | self.seven_segment_out = Signal(8) 34 | 35 | def elaborate(self, platform: Platform) -> Module: 36 | m = Module() 37 | 38 | with m.Switch(self.nibble_in): 39 | for digit in seven_segment_hex_numbers: 40 | with m.Case(int(f"0x{digit[0]}", 0)): 41 | m.d.comb += self.seven_segment_out.eq(digit[1]) 42 | 43 | return m 44 | 45 | class NumberToSevenSegmentHex(Elaboratable): 46 | def __init__(self, width=32, register = False): 47 | # parameters 48 | assert width % 4 == 0, "width must be a multiple of four" 49 | self.width = width 50 | self.register = register 51 | 52 | # I/O 53 | self.number_in = Signal(width) 54 | self.dots_in = Signal(width // 4) 55 | self.seven_segment_out = Signal(width * 2) 56 | 57 | def elaborate(self, platform: Platform) -> Module: 58 | m = Module() 59 | 60 | no_nibbles = self.width // 4 61 | 62 | for i in range(no_nibbles): 63 | digit_to_hex = NibbleToSevenSegmentHex() 64 | m.submodules += digit_to_hex 65 | domain = m.d.sync if self.register else m.d.comb 66 | domain += [ 67 | digit_to_hex.nibble_in.eq(self.number_in[(i * 4):(i * 4 + 4)]), 68 | self.seven_segment_out[(i * 8):(i * 8 + 7)].eq(digit_to_hex.seven_segment_out), 69 | self.seven_segment_out[(i * 8) + 7].eq(self.dots_in[i]) 70 | ] 71 | 72 | return m 73 | 74 | class NumberToBitBar(Elaboratable): 75 | """ 76 | This converts the range of an unsigned integer into bits representing a bar of 77 | a bar chart: 78 | * we map the minimal value and everything below to an empty bar 79 | * and only the maxvalue to a full bar 80 | * all values in between should be divided up linearly between one bit and all 81 | least significant bits until (all output bits - 1) set 82 | """ 83 | def __init__(self, minvalue_in, maxvalue_in, bitwidth_out, debug=False) -> None: 84 | # parameters 85 | self._debug = debug 86 | self.minvalue = minvalue_in 87 | self.maxvalue = maxvalue_in 88 | self.bitbar = [Const(int(2**n - 1), bitwidth_out) for n in range(bitwidth_out + 1)] 89 | 90 | # I/O 91 | self.value_in = Signal(range(maxvalue_in)) 92 | self.bitbar_out = Signal(bitwidth_out) 93 | 94 | def elaborate(self, platform: Platform) -> Module: 95 | m = Module() 96 | 97 | debug = self._debug 98 | 99 | bar_value = Signal(range(self.maxvalue - self.minvalue)) 100 | 101 | with m.If(self.value_in >= self.maxvalue): 102 | m.d.sync += self.bitbar_out.eq(self.bitbar[-1]) 103 | 104 | with m.Elif(self.value_in <= self.minvalue): 105 | m.d.sync += self.bitbar_out.eq(self.bitbar[0]) 106 | 107 | with m.Else(): 108 | m.d.comb += bar_value.eq(self.value_in - self.minvalue) 109 | 110 | bar_bits = self.bitbar[1:-1] 111 | if debug: print("barbits: " + str([bin(b.value) for b in bar_bits])) 112 | 113 | range_max_values = [(self.maxvalue - self.minvalue) // (len(bar_bits) - 1) * i for i in range(1, len(bar_bits))] 114 | rng_vals = [int(val) for val in range_max_values] 115 | if debug: print("ranges: " + str(rng_vals)) 116 | 117 | rng_vals_deltas = list(map(lambda x: x[0] - x[1], zip(rng_vals + [self.maxvalue], [0] + rng_vals))) 118 | if debug: print("deltas: " + str(rng_vals_deltas)) 119 | 120 | with m.If(bar_value < range_max_values[0]): 121 | m.d.sync += self.bitbar_out.eq(bar_bits[0]) 122 | 123 | range_to_value = zip(range_max_values[1:], bar_bits[1:-1]) 124 | 125 | for (range_max, bitbar) in list(range_to_value): 126 | if debug: print(f"range: {str(range_max)}, value: {bitbar}") 127 | with m.Elif(bar_value < range_max): 128 | m.d.sync += self.bitbar_out.eq(bitbar) 129 | 130 | with m.Else(): 131 | m.d.sync += self.bitbar_out.eq(bar_bits[-1]) 132 | 133 | return m 134 | 135 | class NumberToBitBarTest(GatewareTestCase): 136 | MIN = 0x10 137 | MAX = 0x82 138 | FRAGMENT_UNDER_TEST = NumberToBitBar 139 | FRAGMENT_ARGUMENTS = dict(minvalue_in=MIN, maxvalue_in=MAX, bitwidth_out=8, debug=False) 140 | 141 | @sync_test_case 142 | def test_byte_range(self): 143 | dut = self.dut 144 | yield 145 | 146 | step = (self.MAX - self.MIN) // (dut.bitbar_out.width - 2) 147 | if dut._debug: print("step: " + str(step)) 148 | 149 | for i in range(self.MAX + 10): 150 | yield dut.value_in.eq(i) 151 | yield 152 | yield 153 | if True: 154 | if i <= self.MIN: 155 | self.assertEqual((yield dut.bitbar_out), 0) 156 | elif i < (self.MIN + step): 157 | self.assertEqual((yield dut.bitbar_out), 0b1) 158 | elif i < (self.MIN + 2*step): 159 | self.assertEqual((yield dut.bitbar_out), 0b11) 160 | elif i < (self.MIN + 3*step): 161 | self.assertEqual((yield dut.bitbar_out), 0b111) 162 | elif i < (self.MIN + 4*step): 163 | self.assertEqual((yield dut.bitbar_out), 0b1111) 164 | elif i < (self.MIN + 5*step): 165 | self.assertEqual((yield dut.bitbar_out), 0b11111) 166 | elif i < (self.MIN + 6*step): 167 | self.assertEqual((yield dut.bitbar_out), 0b111111) 168 | elif i < self.MAX: 169 | self.assertEqual((yield dut.bitbar_out), 0b1111111) 170 | elif i >= self.MAX: 171 | self.assertEqual((yield dut.bitbar_out), 0b11111111) 172 | 173 | yield 174 | yield -------------------------------------------------------------------------------- /amlib/io/max7219.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Hans Baier hansfbaier@gmail.com 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | from enum import IntEnum 6 | 7 | from amaranth import * 8 | from amaranth.build import Platform 9 | from amaranth.compat.fhdl.structure import Repl 10 | 11 | from .spi import SPIControllerBus, SPIControllerInterface 12 | from ..test import GatewareTestCase, sync_test_case 13 | from ..utils import Timer 14 | 15 | 16 | class MAX7219Register(IntEnum): 17 | DECODE = 0x09 18 | INTENSITY = 0x0a 19 | SCAN_LIMIT = 0x0b 20 | SHUTDOWN = 0x0c 21 | DISPLAY_TEST = 0x0f 22 | 23 | default_init_sequence = [ 24 | [MAX7219Register.DISPLAY_TEST, 0], 25 | [MAX7219Register.DECODE, 0], 26 | [MAX7219Register.INTENSITY, 0xf], 27 | [MAX7219Register.SCAN_LIMIT, 7], 28 | [MAX7219Register.SHUTDOWN, 1], 29 | ] 30 | 31 | class SerialLEDArray(Elaboratable): 32 | def __init__(self, *, divisor, init_delay=16e6, init_sequence=default_init_sequence, no_modules=1): 33 | # parameters 34 | assert divisor % 2 == 0, "divisor must be even" 35 | self.divisor = divisor 36 | self.no_modules = no_modules 37 | self.init_delay = init_delay 38 | self.init_sequence = init_sequence 39 | 40 | # I/O 41 | self.spi_bus_out = SPIControllerBus() 42 | self.digits_in = Array(Signal(8, name=f"digit{n}") for n in range(8 * no_modules)) 43 | self.valid_in = Signal() 44 | 45 | def send_command_to_all_modules(self, spi_controller, command_byte, data_byte): 46 | return spi_controller.word_out.eq(Repl(Cat(Const(data_byte, 8), Const(command_byte, 8)), self.no_modules)) 47 | 48 | def connect_to_resource(self, spi_resource): 49 | return [ 50 | spi_resource.copi .eq(self.spi_bus_out.sdo), 51 | spi_resource.clk .eq(self.spi_bus_out.sck), 52 | spi_resource.cs .eq(~self.spi_bus_out.cs), 53 | ] 54 | 55 | def elaborate(self, platform: Platform) -> Module: 56 | m = Module() 57 | 58 | current_digits = Array(Signal(8, name=f"current_digit{n}") for n in range(8 * self.no_modules)) 59 | 60 | m.submodules.init_delay = init_delay = \ 61 | Timer(width=24, load=int(self.init_delay), reload=0, allow_restart=False) 62 | 63 | m.submodules.spi_controller = spi_controller = SPIControllerInterface(word_size=16 * self.no_modules, divisor=self.divisor, cs_idles_high=True) 64 | 65 | with m.If(self.valid_in): 66 | m.d.sync += Cat(current_digits).eq(Cat(self.digits_in)) 67 | 68 | m.d.comb += [ 69 | spi_controller.spi.connect(self.spi_bus_out), 70 | init_delay.start.eq(1), 71 | ] 72 | 73 | digit_counter = Signal(8) 74 | next_digit = Signal() 75 | 76 | with m.If(next_digit): 77 | m.d.sync += digit_counter.eq(digit_counter + 1) 78 | 79 | step_counter = Signal(range(10)) 80 | next_step = Signal() 81 | 82 | with m.If(next_step): 83 | m.d.sync += step_counter.eq(step_counter + 1) 84 | 85 | with m.FSM(name="max7219"): 86 | with m.State("WAIT"): 87 | with m.If(init_delay.done): 88 | m.next = "INIT" 89 | 90 | with m.State("INIT"): 91 | with m.Switch(step_counter): 92 | with m.Case(0): 93 | item = self.init_sequence[0] 94 | m.d.sync += self.send_command_to_all_modules(spi_controller, item[0], item[1]), 95 | m.d.comb += next_step.eq(1) 96 | with m.Case(1): 97 | m.d.comb += [ 98 | spi_controller.start_transfer.eq(1), 99 | next_step.eq(1) 100 | ] 101 | 102 | i = 1 103 | for item in self.init_sequence[1:]: 104 | with m.Case(2 * i): 105 | with m.If(spi_controller.word_complete): 106 | m.d.sync += self.send_command_to_all_modules(spi_controller, item[0], item[1]), 107 | m.d.comb += next_step.eq(1) 108 | with m.Case(2 * i + 1): 109 | m.d.comb += [ 110 | spi_controller.start_transfer.eq(1), 111 | next_step.eq(1) 112 | ] 113 | i += 1 114 | 115 | with m.Case(2 * i): 116 | with m.If(spi_controller.word_complete): 117 | m.d.comb += next_step.eq(1) 118 | 119 | with m.Default(): 120 | m.d.sync += step_counter.eq(0) 121 | m.next = "SHOWTIME" 122 | 123 | with m.State('SHOWTIME'): 124 | with m.Switch(step_counter): 125 | with m.Case(0): 126 | for module in range(self.no_modules): 127 | m.d.sync += [ 128 | spi_controller.word_out[(0 + module * 16):(8 + module * 16)].eq(current_digits[Const(module * 8, 8) + digit_counter]), 129 | spi_controller.word_out[(8 + module * 16):(16 + module * 16)].eq((digit_counter + 1)[0:8]), 130 | ] 131 | m.d.comb += next_step.eq(1) 132 | 133 | with m.Case(1): 134 | m.d.comb += [ 135 | spi_controller.start_transfer.eq(1), 136 | next_step.eq(1) 137 | ] 138 | 139 | with m.Default(): 140 | with m.If(spi_controller.word_complete): 141 | m.d.sync += step_counter.eq(0) 142 | with m.If(digit_counter >= 7): 143 | m.d.sync += digit_counter.eq(0) 144 | with m.Else(): 145 | m.d.comb += next_digit.eq(1) 146 | 147 | return m 148 | 149 | 150 | class SerialLEDArrayTest(GatewareTestCase): 151 | FRAGMENT_UNDER_TEST = SerialLEDArray 152 | FRAGMENT_ARGUMENTS = dict(divisor=10, init_delay=20, no_modules=2) 153 | 154 | def loopback(self, no_cycles): 155 | for _ in range(no_cycles): 156 | yield self.dut.spi_bus_out.sdi.eq((yield self.dut.spi_bus_out.sdo)) 157 | yield 158 | 159 | @sync_test_case 160 | def test_spi_interface(self): 161 | dut = self.dut 162 | yield 163 | yield 164 | yield from self.loopback(900) 165 | yield 166 | yield 167 | for d in range(16): 168 | yield dut.digits_in[d].eq(d) 169 | yield 170 | yield 171 | yield from self.pulse(dut.valid_in) 172 | yield from self.loopback(7200) 173 | -------------------------------------------------------------------------------- /amlib/io/serial.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adopted from the amaranth-stdio project 3 | # 4 | # Copyright (C) 2019-2020 whitequark whitequark@whitequark.org 5 | # Copyright (C) 2019 M-Labs Limited 6 | # 7 | # SPDX-License-Identifier: BSD-2-Clause 8 | 9 | from amaranth import * 10 | from amaranth.lib.cdc import FFSynchronizer 11 | from amaranth.utils import bits_for 12 | 13 | __all__ = ["AsyncSerialRX", "AsyncSerialTX", "AsyncSerial"] 14 | 15 | 16 | def _check_divisor(divisor, bound): 17 | if divisor < bound: 18 | raise ValueError("Invalid divisor {!r}; must be greater than or equal to {}" 19 | .format(divisor, bound)) 20 | 21 | 22 | def _check_parity(parity): 23 | choices = ("none", "mark", "space", "even", "odd") 24 | if parity not in choices: 25 | raise ValueError("Invalid parity {!r}; must be one of {}" 26 | .format(parity, ", ".join(choices))) 27 | 28 | 29 | def _compute_parity_bit(data, parity): 30 | if parity == "none": 31 | return C(0, 0) 32 | if parity == "mark": 33 | return C(1, 1) 34 | if parity == "space": 35 | return C(0, 1) 36 | if parity == "even": 37 | return data.xor() 38 | if parity == "odd": 39 | return ~data.xor() 40 | assert False 41 | 42 | 43 | def _wire_layout(data_bits, parity="none"): 44 | return [ 45 | ("start", 1), 46 | ("data", data_bits), 47 | ("parity", 0 if parity == "none" else 1), 48 | ("stop", 1), 49 | ] 50 | 51 | 52 | class AsyncSerialRX(Elaboratable): 53 | """Asynchronous serial receiver. 54 | 55 | Parameters 56 | ---------- 57 | divisor : int 58 | Clock divisor reset value. Should be set to ``int(clk_frequency // baudrate)``. 59 | divisor_bits : int 60 | Optional. Clock divisor width. If omitted, ``bits_for(divisor)`` is used instead. 61 | data_bits : int 62 | Data width. 63 | parity : ``"none"``, ``"mark"``, ``"space"``, ``"even"``, ``"odd"`` 64 | Parity mode. 65 | pins : :class:`amaranth.lib.io.Pin` 66 | Optional. UART pins. See :class:`amaranth_boards.resources.UARTResource` for layout. 67 | 68 | Attributes 69 | ---------- 70 | divisor : Signal, in 71 | Clock divisor. 72 | data : Signal, out 73 | Read data. Valid only when ``rdy`` is asserted. 74 | err.overflow : Signal, out 75 | Error flag. A new frame has been received, but the previous one was not acknowledged. 76 | err.frame : Signal, out 77 | Error flag. The received bits do not fit in a frame. 78 | err.parity : Signal, out 79 | Error flag. The parity check has failed. 80 | rdy : Signal, out 81 | Read strobe. 82 | ack : Signal, in 83 | Read acknowledge. Must be held asserted while data can be read out of the receiver. 84 | i : Signal, in 85 | Serial input. If ``pins`` has been specified, ``pins.rx.i`` drives it. 86 | """ 87 | def __init__(self, *, divisor, divisor_bits=None, data_bits=8, parity="none", pins=None): 88 | _check_parity(parity) 89 | self._parity = parity 90 | 91 | # The clock divisor must be at least 5 to keep the FSM synchronized with the serial input 92 | # during a DONE->IDLE->BUSY transition. 93 | _check_divisor(divisor, 5) 94 | self.divisor = Signal(divisor_bits or bits_for(divisor), reset=divisor) 95 | 96 | self.data = Signal(data_bits) 97 | self.err = Record([ 98 | ("overflow", 1), 99 | ("frame", 1), 100 | ("parity", 1), 101 | ]) 102 | self.rdy = Signal() 103 | self.ack = Signal() 104 | 105 | self.i = Signal(reset=1) 106 | 107 | self._pins = pins 108 | 109 | def elaborate(self, platform): 110 | m = Module() 111 | 112 | timer = Signal.like(self.divisor) 113 | shreg = Record(_wire_layout(len(self.data), self._parity)) 114 | bitno = Signal(range(len(shreg))) 115 | 116 | if self._pins is not None: 117 | m.submodules += FFSynchronizer(self._pins.rx.i, self.i, reset=1) 118 | 119 | with m.FSM() as fsm: 120 | with m.State("IDLE"): 121 | with m.If(~self.i): 122 | m.d.sync += [ 123 | bitno.eq(len(shreg) - 1), 124 | timer.eq(self.divisor >> 1), 125 | ] 126 | m.next = "BUSY" 127 | 128 | with m.State("BUSY"): 129 | with m.If(timer != 0): 130 | m.d.sync += timer.eq(timer - 1) 131 | with m.Else(): 132 | m.d.sync += [ 133 | shreg.eq(Cat(shreg[1:], self.i)), 134 | bitno.eq(bitno - 1), 135 | timer.eq(self.divisor - 1), 136 | ] 137 | with m.If(bitno == 0): 138 | m.next = "DONE" 139 | 140 | with m.State("DONE"): 141 | with m.If(self.ack): 142 | m.d.sync += [ 143 | self.data.eq(shreg.data), 144 | self.err.frame .eq(~((shreg.start == 0) & (shreg.stop == 1))), 145 | self.err.parity.eq(~(shreg.parity == 146 | _compute_parity_bit(shreg.data, self._parity))), 147 | ] 148 | m.d.sync += self.err.overflow.eq(~self.ack) 149 | m.next = "IDLE" 150 | 151 | with m.If(self.ack): 152 | m.d.sync += self.rdy.eq(fsm.ongoing("DONE")) 153 | 154 | return m 155 | 156 | 157 | class AsyncSerialTX(Elaboratable): 158 | """Asynchronous serial transmitter. 159 | 160 | Parameters 161 | ---------- 162 | divisor : int 163 | Clock divisor reset value. Should be set to ``int(clk_frequency // baudrate)``. 164 | divisor_bits : int 165 | Optional. Clock divisor width. If omitted, ``bits_for(divisor)`` is used instead. 166 | data_bits : int 167 | Data width. 168 | parity : ``"none"``, ``"mark"``, ``"space"``, ``"even"``, ``"odd"`` 169 | Parity mode. 170 | pins : :class:`amaranth.lib.io.Pin` 171 | Optional. UART pins. See :class:`amaranth_boards.resources.UARTResource` for layout. 172 | 173 | Attributes 174 | ---------- 175 | divisor : Signal, in 176 | Clock divisor. 177 | data : Signal, in 178 | Write data. Valid only when ``ack`` is asserted. 179 | rdy : Signal, out 180 | Write ready. Asserted when the transmitter is ready to transmit data. 181 | ack : Signal, in 182 | Write strobe. Data gets transmitted when both ``rdy`` and ``ack`` are asserted. 183 | o : Signal, out 184 | Serial output. If ``pins`` has been specified, it drives ``pins.tx.o``. 185 | """ 186 | def __init__(self, *, divisor, divisor_bits=None, data_bits=8, parity="none", pins=None): 187 | _check_parity(parity) 188 | self._parity = parity 189 | 190 | _check_divisor(divisor, 1) 191 | self.divisor = Signal(divisor_bits or bits_for(divisor), reset=divisor) 192 | 193 | self.data = Signal(data_bits) 194 | self.rdy = Signal() 195 | self.ack = Signal() 196 | 197 | self.o = Signal(reset=1) 198 | 199 | self._pins = pins 200 | 201 | def elaborate(self, platform): 202 | m = Module() 203 | 204 | timer = Signal.like(self.divisor) 205 | shreg = Record(_wire_layout(len(self.data), self._parity)) 206 | bitno = Signal(range(len(shreg))) 207 | 208 | if self._pins is not None: 209 | m.d.comb += self._pins.tx.o.eq(self.o) 210 | 211 | with m.FSM(): 212 | with m.State("IDLE"): 213 | m.d.comb += self.rdy.eq(1) 214 | with m.If(self.ack): 215 | m.d.sync += [ 216 | shreg.start .eq(0), 217 | shreg.data .eq(self.data), 218 | shreg.parity.eq(_compute_parity_bit(self.data, self._parity)), 219 | shreg.stop .eq(1), 220 | bitno.eq(len(shreg) - 1), 221 | timer.eq(self.divisor - 1), 222 | ] 223 | m.next = "BUSY" 224 | 225 | with m.State("BUSY"): 226 | with m.If(timer != 0): 227 | m.d.sync += timer.eq(timer - 1) 228 | with m.Else(): 229 | m.d.sync += [ 230 | Cat(self.o, shreg).eq(shreg), 231 | bitno.eq(bitno - 1), 232 | timer.eq(self.divisor - 1), 233 | ] 234 | with m.If(bitno == 0): 235 | m.next = "IDLE" 236 | 237 | return m 238 | 239 | 240 | class AsyncSerial(Elaboratable): 241 | """Asynchronous serial transceiver. 242 | 243 | Parameters 244 | ---------- 245 | divisor : int 246 | Clock divisor reset value. Should be set to ``int(clk_frequency // baudrate)``. 247 | divisor_bits : int 248 | Optional. Clock divisor width. If omitted, ``bits_for(divisor)`` is used instead. 249 | data_bits : int 250 | Data width. 251 | parity : ``"none"``, ``"mark"``, ``"space"``, ``"even"``, ``"odd"`` 252 | Parity mode. 253 | pins : :class:`amaranth.lib.io.Pin` 254 | Optional. UART pins. See :class:`amaranth_boards.resources.UARTResource` for layout. 255 | 256 | Attributes 257 | ---------- 258 | divisor : Signal, in 259 | Clock divisor. 260 | rx : :class:`AsyncSerialRX` 261 | See :class:`AsyncSerialRX`. 262 | tx : :class:`AsyncSerialTX` 263 | See :class:`AsyncSerialTX`. 264 | """ 265 | def __init__(self, *, divisor, divisor_bits=None, **kwargs): 266 | self.divisor = Signal(divisor_bits or bits_for(divisor), reset=divisor) 267 | 268 | self.rx = AsyncSerialRX(divisor=divisor, divisor_bits=divisor_bits, **kwargs) 269 | self.tx = AsyncSerialTX(divisor=divisor, divisor_bits=divisor_bits, **kwargs) 270 | 271 | def elaborate(self, platform): 272 | m = Module() 273 | m.submodules.rx = self.rx 274 | m.submodules.tx = self.tx 275 | m.d.comb += [ 276 | self.rx.divisor.eq(self.divisor), 277 | self.tx.divisor.eq(self.divisor), 278 | ] 279 | return m 280 | -------------------------------------------------------------------------------- /amlib/io/ws2812.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Hans Baier hansfbaier@gmail.com 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | from math import log2 6 | 7 | from amaranth import * 8 | from amaranth.utils import bits_for 9 | from amaranth.build import Platform 10 | 11 | from ..test import GatewareTestCase, sync_test_case 12 | 13 | """ 14 | Timing parameters for the WS2811 15 | The LEDs are reset by driving D0 low for at least 50us. 16 | Data is transmitted using a 800kHz signal. 17 | A '1' is 50% duty cycle, a '0' is 20% duty cycle. 18 | """ 19 | class WS2812(Elaboratable): 20 | def __init__(self, *, sys_clock_freq, no_leds): 21 | # parameters 22 | self.no_leds = no_leds 23 | self.full_cycle_length = sys_clock_freq // 800e3 24 | self.low_cycle_length = int(0.32 * self.full_cycle_length) 25 | self.high_cycle_length = int(0.64 * self.full_cycle_length) 26 | print(f"full cycle: {self.full_cycle_length}") 27 | 28 | self.mem = Memory(width=24, depth=no_leds, name="led_memory") 29 | 30 | # I / O 31 | self.red_in = Signal(8) 32 | self.green_in = Signal(8) 33 | self.blue_in = Signal(8) 34 | self.led_address_in = Signal(range(no_leds)) 35 | self.write_enable_in = Signal() 36 | 37 | self.data_out = Signal() 38 | 39 | self.start_in = Signal() 40 | self.done_out = Signal() 41 | 42 | def elaborate(self, platform: Platform) -> Module: 43 | m = Module() 44 | 45 | m.submodules.read_port = mem_read_port = self.mem.read_port() 46 | m.submodules.write_port = mem_write_port = self.mem.write_port() 47 | 48 | grb = Signal(3 * 8) 49 | 50 | led_counter = Signal(bits_for(self.no_leds) + 1) 51 | bit_counter = Signal(5) 52 | current_bit = Signal() 53 | 54 | cycle_counter_width = bits_for(int(self.full_cycle_length)) + 1 55 | cycle_counter = Signal(cycle_counter_width) 56 | current_cycle_length = Signal.like(cycle_counter) 57 | 58 | print(f"cycle counter: {cycle_counter_width}") 59 | 60 | m.d.comb += [ 61 | self.data_out.eq(1), 62 | current_bit.eq(grb[23]), 63 | current_cycle_length.eq(Mux(current_bit, self.high_cycle_length, self.low_cycle_length)), 64 | mem_write_port.addr.eq(self.led_address_in), 65 | mem_write_port.data.eq(Cat(self.blue_in, self.red_in, self.green_in)), 66 | mem_write_port.en.eq(self.write_enable_in), 67 | mem_read_port.addr.eq(led_counter), 68 | ] 69 | 70 | with m.FSM(): 71 | with m.State("IDLE"): 72 | with m.If(self.start_in): 73 | m.d.sync += led_counter.eq(0) 74 | m.next = "RESET" 75 | 76 | with m.State("RESET"): 77 | m.d.comb += self.data_out.eq(0) 78 | m.d.sync += cycle_counter.eq(cycle_counter + 1) 79 | 80 | with m.If(cycle_counter >= Const(self.full_cycle_length)): 81 | m.d.sync += cycle_counter.eq(0) 82 | 83 | with m.If(led_counter == 0): 84 | m.d.sync += [ 85 | grb.eq(mem_read_port.data), 86 | led_counter.eq(led_counter + 1), 87 | ] 88 | m.next = "TRANSMIT" 89 | 90 | with m.Else(): 91 | m.d.comb += self.done_out.eq(1) 92 | m.d.sync += led_counter.eq(0) 93 | m.next = "IDLE" 94 | 95 | with m.State("TRANSMIT"): 96 | m.d.sync += cycle_counter.eq(cycle_counter + 1) 97 | 98 | with m.If(cycle_counter < current_cycle_length): 99 | m.d.comb += self.data_out.eq(1) 100 | with m.Else(): 101 | m.d.comb += self.data_out.eq(0) 102 | 103 | with m.If(cycle_counter >= Const(self.full_cycle_length)): 104 | m.d.sync += cycle_counter.eq(0) 105 | 106 | last_bit = 23 107 | with m.If(bit_counter < last_bit): 108 | m.d.sync += [ 109 | grb.eq(grb << 1), 110 | bit_counter.eq(bit_counter + 1), 111 | ] 112 | with m.Else(): 113 | m.d.sync += [ 114 | bit_counter.eq(0), 115 | led_counter.eq(led_counter + 1), 116 | ] 117 | 118 | # transmit each LED's data 119 | with m.If(led_counter < self.no_leds): 120 | m.d.sync += grb.eq(mem_read_port.data), 121 | 122 | # if all LEDS' data has been transmitted, send another reset 123 | with m.Else(): 124 | m.next = "RESET" 125 | 126 | return m 127 | 128 | class WS2812Test(GatewareTestCase): 129 | FRAGMENT_UNDER_TEST = WS2812 130 | FRAGMENT_ARGUMENTS = dict(sys_clock_freq=8e6, no_leds=3) 131 | 132 | def write_led_color(self, dut, led_no, red, green, blue): 133 | yield dut.red_in .eq(red) 134 | yield dut.green_in .eq(green) 135 | yield dut.blue_in .eq(blue) 136 | yield dut.led_address_in.eq(led_no) 137 | yield from self.pulse(dut.write_enable_in) 138 | yield 139 | 140 | @sync_test_case 141 | def test_spi_interface(self): 142 | dut = self.dut 143 | yield 144 | yield 145 | yield 146 | yield from self.write_led_color(dut, 0, 0xff, 0, 0) 147 | yield from self.write_led_color(dut, 1, 0, 0xff, 0) 148 | yield from self.write_led_color(dut, 2, 0, 0, 0xff) 149 | 150 | yield 151 | yield from self.pulse(dut.start_in) 152 | yield 153 | yield from self.advance_cycles(1000) -------------------------------------------------------------------------------- /amlib/soc/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adapted from the LUNA project 3 | # 4 | # Copyright (c) 2020 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | from .simplesoc import SimpleSoC 8 | from .uart import UARTPeripheral 9 | from .cpu import Processor 10 | from .memory import WishboneRAM, WishboneROM 11 | 12 | __all__ = [ "SimpleSoC", "UARTPeripheral", "Processor", "WishboneRAM", "WishboneROM"] -------------------------------------------------------------------------------- /amlib/soc/cpu.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adopted from the LUNA project 3 | # 4 | # Copyright (c) 2020 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | from minerva.core import Minerva 8 | from amaranth_soc import wishbone 9 | 10 | class Processor(Minerva): 11 | """ Compatibility subclass around the Minerva RISC-V (riscv32i) processor. """ 12 | 13 | # List of features supported by the Minerva processor's wishbone busses. 14 | MINERVA_BUS_FEATURES = {'cti', 'bte', 'err'} 15 | 16 | def __init__(self, *args, **kwargs): 17 | 18 | # Create the basic Minerva processor... 19 | super().__init__(*args, **kwargs) 20 | 21 | # ... and replace its Record-based busses with amaranth-soc ones. 22 | self.ibus = wishbone.Interface(addr_width=30, data_width=32, features=self.MINERVA_BUS_FEATURES) 23 | self.dbus = wishbone.Interface(addr_width=30, data_width=32, features=self.MINERVA_BUS_FEATURES) 24 | -------------------------------------------------------------------------------- /amlib/soc/event.py: -------------------------------------------------------------------------------- 1 | 2 | # This file has been adopted from the LUNA project 3 | # 4 | # This file includes content Copyright (C) 2020 LambdaConcept. 5 | # Per our BSD license, derivative files must include this license disclaimer. 6 | # 7 | # Copyright (c) 2020 Great Scott Gadgets 8 | # SPDX-License-Identifier: BSD-3-Clause 9 | 10 | """ Peripheral interrupt helpers for LUNA devices. """ 11 | 12 | 13 | from amaranth import Signal, Elaboratable, Module 14 | from amaranth import tracer 15 | 16 | from amaranth_soc import csr 17 | 18 | from lambdasoc.periph.event import IRQLine 19 | 20 | 21 | __all__ = ["EventSource", "IRQLine", "InterruptSource"] 22 | 23 | 24 | class EventSource: 25 | """Event source. 26 | 27 | Parameters 28 | ---------- 29 | mode : ``"level"``, ``"rise"``, ``"fall"`` 30 | Trigger mode. If ``"level"``, a notification is raised when the ``stb`` signal is high. 31 | If ``"rise"`` (or ``"fall"``) a notification is raised on a rising (or falling) edge 32 | of ``stb``. 33 | name : str 34 | Name of the event. If ``None`` (default) the name is inferred from the variable 35 | name this event source is assigned to. 36 | 37 | Attributes 38 | ---------- 39 | name : str 40 | Name of the event 41 | mode : ``"level"``, ``"rise"``, ``"fall"`` 42 | Trigger mode. 43 | stb : Signal, in 44 | Event strobe. 45 | """ 46 | def __init__(self, *, mode="level", name=None, src_loc_at=0): 47 | if name is not None and not isinstance(name, str): 48 | raise TypeError("Name must be a string, not {!r}".format(name)) 49 | 50 | choices = ("level", "rise", "fall") 51 | if mode not in choices: 52 | raise ValueError("Invalid trigger mode {!r}; must be one of {}" 53 | .format(mode, ", ".join(choices))) 54 | 55 | self.name = name or tracer.get_var_name(depth=2 + src_loc_at) 56 | self.mode = mode 57 | self.stb = Signal(name="{}_stb".format(self.name)) 58 | 59 | 60 | class InterruptSource(Elaboratable): 61 | """Interrupt source. 62 | 63 | A mean of gathering multiple event sources into a single interrupt request line. 64 | 65 | Parameters 66 | ---------- 67 | events : iter(:class:`EventSource`) 68 | Event sources. 69 | name : str 70 | Name of the interrupt source. If ``None`` (default) the name is inferred from the 71 | variable name this interrupt source is assigned to. 72 | 73 | Attributes 74 | ---------- 75 | name : str 76 | Name of the interrupt source. 77 | status : :class:`csr.Element`, read-only 78 | Event status register. Each bit displays the level of the strobe of an event source. 79 | Events are ordered by position in the `events` parameter. 80 | pending : :class:`csr.Element`, read/write 81 | Event pending register. If a bit is 1, the associated event source has a pending 82 | notification. Writing 1 to a bit clears it. 83 | Events are ordered by position in the `events` parameter. 84 | enable : :class:`csr.Element`, read/write 85 | Event enable register. Writing 1 to a bit enables its associated event source. 86 | Writing 0 disables it. 87 | Events are ordered by position in the `events` parameter. 88 | irq : :class:`IRQLine`, out 89 | Interrupt request. It is raised if any event source is enabled and has a pending 90 | notification. 91 | """ 92 | def __init__(self, events, *, name=None, src_loc_at=0): 93 | if name is not None and not isinstance(name, str): 94 | raise TypeError("Name must be a string, not {!r}".format(name)) 95 | self.name = name or tracer.get_var_name(depth=2 + src_loc_at) 96 | 97 | for event in events: 98 | if not isinstance(event, EventSource): 99 | raise TypeError("Event source must be an instance of EventSource, not {!r}" 100 | .format(event)) 101 | self._events = list(events) 102 | 103 | width = len(events) 104 | self.status = csr.Element(width, "r", name="{}_status".format(self.name)) 105 | self.pending = csr.Element(width, "rw", name="{}_pending".format(self.name)) 106 | self.enable = csr.Element(width, "rw", name="{}_enable".format(self.name)) 107 | 108 | self.irq = IRQLine(name="{}_irq".format(self.name)) 109 | 110 | def elaborate(self, platform): 111 | m = Module() 112 | 113 | with m.If(self.pending.w_stb): 114 | m.d.sync += self.pending.r_data.eq(self.pending.r_data & ~self.pending.w_data) 115 | 116 | with m.If(self.enable.w_stb): 117 | m.d.sync += self.enable.r_data.eq(self.enable.w_data) 118 | 119 | for i, event in enumerate(self._events): 120 | m.d.sync += self.status.r_data[i].eq(event.stb) 121 | 122 | if event.mode in ("rise", "fall"): 123 | event_stb_r = Signal.like(event.stb, name_suffix="_r") 124 | m.d.sync += event_stb_r.eq(event.stb) 125 | 126 | event_trigger = Signal(name="{}_trigger".format(event.name)) 127 | if event.mode == "level": 128 | m.d.comb += event_trigger.eq(event.stb) 129 | elif event.mode == "rise": 130 | m.d.comb += event_trigger.eq(~event_stb_r & event.stb) 131 | elif event.mode == "fall": 132 | m.d.comb += event_trigger.eq(event_stb_r & ~event.stb) 133 | else: 134 | assert False # :nocov: 135 | 136 | with m.If(event_trigger): 137 | m.d.sync += self.pending.r_data[i].eq(1) 138 | 139 | m.d.comb += self.irq.eq((self.pending.r_data & self.enable.r_data).any()) 140 | 141 | return m 142 | -------------------------------------------------------------------------------- /amlib/soc/memory.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adopted from the LUNA project 3 | # 4 | # Copyright (c) 2020 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import math 8 | 9 | from enum import Enum 10 | from functools import reduce 11 | from operator import or_ 12 | 13 | from amaranth import Elaboratable, Record, Module, Cat, Array, Repl, Signal, Memory 14 | from amaranth_soc import wishbone, memory 15 | 16 | 17 | class WishboneRAM(Elaboratable): 18 | """ Simple Wishbone-connected RAM. """ 19 | 20 | @staticmethod 21 | def _initialization_value(value, data_width, granularity, byteorder): 22 | """ Converts a provided value into a valid Memory-initializer array. 23 | 24 | Parameters should match those provied to __init__ 25 | """ 26 | 27 | # If this is a filename, read the file's contents before processing. 28 | if isinstance(value, str): 29 | with open(value, "rb") as f: 30 | value = f.read() 31 | 32 | # If we don't have bytes, read this direction. 33 | if not isinstance(value, bytes): 34 | return value 35 | 36 | bytes_per_chunk = data_width // granularity 37 | 38 | words = (value[pos:pos + bytes_per_chunk] for pos in range(0, len(value), bytes_per_chunk)) 39 | return [int.from_bytes(word, byteorder=byteorder) for word in words] 40 | 41 | def __init__(self, *, addr_width, data_width=32, granularity=8, init=None, 42 | read_only=False, byteorder="little", name="ram"): 43 | """ 44 | Parameters: 45 | addr_width -- The -bus- address width for the relevant memory. Determines the size 46 | of the memory. 47 | data_width -- The width of each memory word. 48 | granularity -- The number of bits of data per each address. 49 | init -- Optional. The initial value of the relevant memory. Should be an array of integers, a 50 | filename, or a bytes-like object. If bytes are provided, the byteorder parametera allows 51 | control over their interpretation. If a filename is provided, this filename will not be read 52 | until elaboration; this allows reading the file to be deferred until the very last minute in 53 | e.g. systems that generate the relevant file during build. 54 | read_only -- If true, this will ignore writes to this memory, so it effectively 55 | acts as a ROM fixed to its initialization value. 56 | byteorder -- Sets the byte order of the initializer value. Ignored unless a bytes-type initializer is provided. 57 | name -- A descriptive name for the given memory. 58 | """ 59 | 60 | self.name = name 61 | self.read_only = read_only 62 | self.data_width = data_width 63 | self.initial_value = init 64 | self.byteorder = byteorder 65 | 66 | # Our granularity determines how many bits of data exist per single address. 67 | # Often, this isn't the same as our data width; which means we'll wind up with 68 | # two different address widths: a 'local' one where each address corresponds to a 69 | # data value in memory; and a 'bus' one where each address corresponds to a granularity- 70 | # sized chunk of memory. 71 | self.granularity = granularity 72 | self.bus_addr_width = addr_width 73 | 74 | # Our bus addresses are more granular than our local addresses. 75 | # Figure out how many more bits exist in our bus addresses, and use 76 | # that to figure out our local bus size. 77 | self.bytes_per_word = data_width // granularity 78 | self.bits_in_bus_only = int(math.log2(self.bytes_per_word)) 79 | self.local_addr_width = self.bus_addr_width - self.bits_in_bus_only 80 | 81 | # Create our wishbone interface. 82 | # Note that we provide the -local- address to the Interface object; as it automatically factors 83 | # in our extra bits as it computes our granularity. 84 | self.bus = wishbone.Interface(addr_width=self.local_addr_width, data_width=data_width, granularity=granularity) 85 | self.bus.memory_map = memory.MemoryMap(addr_width=self.bus_addr_width, data_width=granularity) 86 | self.bus.memory_map._frozen = False 87 | self.bus.memory_map.add_resource(self, name=name, size=2 ** addr_width) 88 | 89 | def elaborate(self, platform): 90 | m = Module() 91 | 92 | # Create our memory initializer from our initial value. 93 | initial_value = self._initialization_value(self.initial_value, self.data_width, self.granularity, self.byteorder) 94 | 95 | # Create the the memory used to store our data. 96 | memory_depth = 2 ** self.local_addr_width 97 | memory = Memory(width=self.data_width, depth=memory_depth, init=initial_value, name=self.name) 98 | 99 | # Grab a reference to the bits of our Wishbone bus that are relevant to us. 100 | local_address_bits = self.bus.adr[:self.local_addr_width] 101 | 102 | # Create a read port, and connect it to our Wishbone bus. 103 | m.submodules.rdport = read_port = memory.read_port() 104 | m.d.comb += [ 105 | read_port.addr.eq(local_address_bits), 106 | self.bus.dat_r.eq(read_port.data) 107 | ] 108 | 109 | # If this is a read/write memory, create a write port, as well. 110 | if not self.read_only: 111 | m.submodules.wrport = write_port = memory.write_port(granularity=self.granularity) 112 | m.d.comb += [ 113 | write_port.addr.eq(local_address_bits), 114 | write_port.data.eq(self.bus.dat_w) 115 | ] 116 | 117 | # Generate the write enables for each of our words. 118 | for i in range(self.bytes_per_word): 119 | m.d.comb += write_port.en[i].eq( 120 | self.bus.cyc & # Transaction is active. 121 | self.bus.stb & # Valid data is being provided. 122 | self.bus.we & # This is a write. 123 | self.bus.sel[i] # The relevant setion of the datum is being targeted. 124 | ) 125 | 126 | # We can handle any transaction request in a single cycle, when our RAM handles 127 | # the read or write. Accordingly, we'll ACK the cycle after any request. 128 | m.d.sync += self.bus.ack.eq( 129 | self.bus.cyc & 130 | self.bus.stb & 131 | ~self.bus.ack 132 | ) 133 | 134 | return m 135 | 136 | 137 | class WishboneROM(WishboneRAM): 138 | """ Wishbone-attached ROM. """ 139 | 140 | def __init__(self, data, *, addr_width, data_width=32, granularity=8, name="rom"): 141 | """ 142 | Parameters: 143 | data -- The data to fill the ROM with. 144 | 145 | addr_width -- The -bus- address width for the relevant memory. Determines the address size of the memory. 146 | Physical size is based on the data provided, as unused elements will be optimized away. 147 | data_width -- The width of each memory word. 148 | granularity -- The number of bits of data per each address. 149 | name -- A descriptive name for the ROM. 150 | """ 151 | 152 | super().__init__( 153 | addr_width=addr_width, 154 | data_width=data_width, 155 | granularity=8, 156 | init=data, 157 | read_only=True, 158 | name=name 159 | ) 160 | -------------------------------------------------------------------------------- /amlib/soc/peripheral.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adopted from the LUNA project 3 | # 4 | # Adapted from lambdasoc. 5 | # This file includes content Copyright (C) 2020 LambdaConcept. 6 | # 7 | # Per our BSD license, derivative files must include this license disclaimer. 8 | # 9 | # Copyright (c) 2020 Great Scott Gadgets 10 | # SPDX-License-Identifier: BSD-3-Clause 11 | 12 | """ Peripheral helpers for LUNA devices. """ 13 | 14 | from contextlib import contextmanager 15 | 16 | from amaranth import Module, Elaboratable 17 | from amaranth import tracer 18 | from amaranth.utils import log2_int 19 | 20 | from amaranth_soc import csr, wishbone 21 | from amaranth_soc.memory import MemoryMap 22 | from amaranth_soc.csr.wishbone import WishboneCSRBridge 23 | 24 | from .event import EventSource, IRQLine, InterruptSource 25 | 26 | 27 | __all__ = ["Peripheral", "CSRBank", "PeripheralBridge"] 28 | 29 | 30 | class Peripheral: 31 | """Wishbone peripheral. 32 | 33 | A helper class to reduce the boilerplate needed to control a peripheral with a Wishbone interface. 34 | It provides facilities for instantiating CSR registers, requesting windows to subordinate busses 35 | and sending interrupt requests to the CPU. 36 | 37 | The ``Peripheral`` class is not meant to be instantiated as-is, but rather as a base class for 38 | actual peripherals. 39 | 40 | Usage example 41 | ------------- 42 | 43 | ``` 44 | class ExamplePeripheral(Peripheral, Elaboratable): 45 | def __init__(self): 46 | super().__init__() 47 | bank = self.csr_bank() 48 | self._foo = bank.csr(8, "r") 49 | self._bar = bank.csr(8, "w") 50 | 51 | self._rdy = self.event(mode="rise") 52 | 53 | self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) 54 | self.bus = self._bridge.bus 55 | self.irq = self._bridge.irq 56 | 57 | def elaborate(self, platform): 58 | m = Module() 59 | m.submodules.bridge = self._bridge 60 | # ... 61 | return m 62 | ``` 63 | 64 | Arguments 65 | --------- 66 | name : str 67 | Name of this peripheral. If ``None`` (default) the name is inferred from the variable 68 | name this peripheral is assigned to. 69 | 70 | Properties 71 | ---------- 72 | name : str 73 | Name of the peripheral. 74 | """ 75 | def __init__(self, name=None, src_loc_at=1): 76 | if name is not None and not isinstance(name, str): 77 | raise TypeError("Name must be a string, not {!r}".format(name)) 78 | self.name = name or tracer.get_var_name(depth=2 + src_loc_at).lstrip("_") 79 | 80 | self._csr_banks = [] 81 | self._windows = [] 82 | self._events = [] 83 | 84 | self._bus = None 85 | self._irq = None 86 | 87 | @property 88 | def bus(self): 89 | """Wishbone bus interface. 90 | 91 | Return value 92 | ------------ 93 | An instance of :class:`Interface`. 94 | 95 | Exceptions 96 | ---------- 97 | Raises :exn:`NotImplementedError` if the peripheral does not have a Wishbone bus. 98 | """ 99 | if self._bus is None: 100 | raise NotImplementedError("Peripheral {!r} does not have a bus interface" 101 | .format(self)) 102 | return self._bus 103 | 104 | @bus.setter 105 | def bus(self, bus): 106 | if not isinstance(bus, wishbone.Interface): 107 | raise TypeError("Bus interface must be an instance of wishbone.Interface, not {!r}" 108 | .format(bus)) 109 | self._bus = bus 110 | 111 | @property 112 | def irq(self): 113 | """Interrupt request line. 114 | 115 | Return value 116 | ------------ 117 | An instance of :class:`IRQLine`. 118 | 119 | Exceptions 120 | ---------- 121 | Raises :exn:`NotImplementedError` if the peripheral does not have an IRQ line. 122 | """ 123 | if self._irq is None: 124 | raise NotImplementedError("Peripheral {!r} does not have an IRQ line" 125 | .format(self)) 126 | return self._irq 127 | 128 | @irq.setter 129 | def irq(self, irq): 130 | if not isinstance(irq, IRQLine): 131 | raise TypeError("IRQ line must be an instance of IRQLine, not {!r}" 132 | .format(irq)) 133 | self._irq = irq 134 | 135 | def csr_bank(self, *, addr=None, alignment=None, desc=None): 136 | """Request a CSR bank. 137 | 138 | Arguments 139 | --------- 140 | addr : int or None 141 | Address of the bank. If ``None``, the implicit next address will be used. 142 | Otherwise, the exact specified address (which must be a multiple of 143 | ``2 ** max(alignment, bridge_alignment)``) will be used. 144 | alignment : int or None 145 | Alignment of the bank. If not specified, the bridge alignment is used. 146 | See :class:`amaranth_soc.csr.Multiplexer` for details. 147 | desc: (str, optional): 148 | Documentation of the given CSR bank. 149 | 150 | Return value 151 | ------------ 152 | An instance of :class:`CSRBank`. 153 | """ 154 | bank = CSRBank(name_prefix=self.name) 155 | self._csr_banks.append((bank, addr, alignment)) 156 | return bank 157 | 158 | def window(self, *, addr_width, data_width, granularity=None, features=frozenset(), 159 | alignment=0, addr=None, sparse=None): 160 | """Request a window to a subordinate bus. 161 | 162 | See :meth:`amaranth_soc.wishbone.Decoder.add` for details. 163 | 164 | Return value 165 | ------------ 166 | An instance of :class:`amaranth_soc.wishbone.Interface`. 167 | """ 168 | window = wishbone.Interface(addr_width=addr_width, data_width=data_width, 169 | granularity=granularity, features=features) 170 | granularity_bits = log2_int(data_width // window.granularity) 171 | window.memory_map = MemoryMap(addr_width=addr_width + granularity_bits, 172 | data_width=window.granularity, alignment=alignment) 173 | self._windows.append((window, addr, sparse)) 174 | return window 175 | 176 | def event(self, *, mode="level", name=None, src_loc_at=0, desc=None): 177 | """Request an event source. 178 | 179 | See :class:`EventSource` for details. 180 | 181 | Return value 182 | ------------ 183 | An instance of :class:`EventSource`. 184 | """ 185 | event = EventSource(mode=mode, name=name, src_loc_at=1 + src_loc_at) 186 | self._events.append(event) 187 | return event 188 | 189 | def bridge(self, *, data_width=8, granularity=None, features=frozenset(), alignment=0): 190 | """Request a bridge to the resources of the peripheral. 191 | 192 | See :class:`PeripheralBridge` for details. 193 | 194 | Return value 195 | ------------ 196 | A :class:`PeripheralBridge` providing access to local resources. 197 | """ 198 | return PeripheralBridge(self, data_width=data_width, granularity=granularity, 199 | features=features, alignment=alignment) 200 | 201 | def iter_csr_banks(self): 202 | """Iterate requested CSR banks and their parameters. 203 | 204 | Yield values 205 | ------------ 206 | A tuple ``bank, addr, alignment`` describing the bank and its parameters. 207 | """ 208 | for bank, addr, alignment in self._csr_banks: 209 | yield bank, addr, alignment 210 | 211 | def iter_windows(self): 212 | """Iterate requested windows and their parameters. 213 | 214 | Yield values 215 | ------------ 216 | A tuple ``window, addr, sparse`` descr 217 | given to :meth:`Peripheral.window`. 218 | """ 219 | for window, addr, sparse in self._windows: 220 | yield window, addr, sparse 221 | 222 | def iter_events(self): 223 | """Iterate requested event sources. 224 | 225 | Yield values 226 | ------------ 227 | An instance of :class:`EventSource`. 228 | """ 229 | for event in self._events: 230 | yield event 231 | 232 | 233 | class CSRBank: 234 | """CSR register bank. 235 | 236 | Parameters 237 | ---------- 238 | name_prefix : str 239 | Name prefix of the bank registers. 240 | """ 241 | def __init__(self, *, name_prefix=""): 242 | self._name_prefix = name_prefix 243 | self._csr_regs = [] 244 | 245 | def csr(self, width, access, *, addr=None, alignment=None, name=None, desc=None, 246 | src_loc_at=0): 247 | """Request a CSR register. 248 | 249 | Parameters 250 | ---------- 251 | width : int 252 | Width of the register. See :class:`amaranth_soc.csr.Element`. 253 | access : :class:`Access` 254 | Register access mode. See :class:`amaranth_soc.csr.Element`. 255 | addr : int 256 | Address of the register. See :meth:`amaranth_soc.csr.Multiplexer.add`. 257 | alignment : int 258 | Register alignment. See :class:`amaranth_soc.csr.Multiplexer`. 259 | name : str 260 | Name of the register. If ``None`` (default) the name is inferred from the variable 261 | name this register is assigned to. 262 | desc: str 263 | Documentation for the provided register, if available. 264 | Used to capture register documentation automatically. 265 | 266 | 267 | Return value 268 | ------------ 269 | An instance of :class:`amaranth_soc.csr.Element`. 270 | """ 271 | if name is not None and not isinstance(name, str): 272 | raise TypeError("Name must be a string, not {!r}".format(name)) 273 | name = name or tracer.get_var_name(depth=2 + src_loc_at).lstrip("_") 274 | 275 | elem_name = "{}_{}".format(self._name_prefix, name) 276 | elem = csr.Element(width, access, name=elem_name) 277 | self._csr_regs.append((elem, addr, alignment)) 278 | return elem 279 | 280 | def iter_csr_regs(self): 281 | """Iterate requested CSR registers and their parameters. 282 | 283 | Yield values 284 | ------------ 285 | A tuple ``elem, addr, alignment`` describing the register and its parameters. 286 | """ 287 | for elem, addr, alignment in self._csr_regs: 288 | yield elem, addr, alignment 289 | 290 | 291 | class PeripheralBridge(Elaboratable): 292 | """Peripheral bridge. 293 | 294 | A bridge providing access to the registers and windows of a peripheral, and support for 295 | interrupt requests from its event sources. 296 | 297 | Event managment is performed by an :class:`InterruptSource` submodule. 298 | 299 | Parameters 300 | --------- 301 | periph : :class:`Peripheral` 302 | The peripheral whose resources are exposed by this bridge. 303 | data_width : int 304 | Data width. See :class:`amaranth_soc.wishbone.Interface`. 305 | granularity : int or None 306 | Granularity. See :class:`amaranth_soc.wishbone.Interface`. 307 | features : iter(str) 308 | Optional signal set. See :class:`amaranth_soc.wishbone.Interface`. 309 | alignment : int 310 | Resource alignment. See :class:`amaranth_soc.memory.MemoryMap`. 311 | 312 | Attributes 313 | ---------- 314 | bus : :class:`amaranth_soc.wishbone.Interface` 315 | Wishbone bus providing access to the resources of the peripheral. 316 | irq : :class:`IRQLine`, out 317 | Interrupt request. It is raised if any event source is enabled and has a pending 318 | notification. 319 | """ 320 | def __init__(self, periph, *, data_width, granularity, features, alignment): 321 | if not isinstance(periph, Peripheral): 322 | raise TypeError("Peripheral must be an instance of Peripheral, not {!r}" 323 | .format(periph)) 324 | 325 | self._wb_decoder = wishbone.Decoder(addr_width=1, data_width=data_width, 326 | granularity=granularity, 327 | features=features, alignment=alignment) 328 | 329 | self._csr_subs = [] 330 | 331 | for bank, bank_addr, bank_alignment in periph.iter_csr_banks(): 332 | if bank_alignment is None: 333 | bank_alignment = alignment 334 | csr_mux = csr.Multiplexer(addr_width=1, data_width=8, alignment=bank_alignment) 335 | for elem, elem_addr, elem_alignment in bank.iter_csr_regs(): 336 | if elem_alignment is None: 337 | elem_alignment = alignment 338 | csr_mux.add(elem, addr=elem_addr, alignment=elem_alignment, extend=True) 339 | 340 | csr_bridge = WishboneCSRBridge(csr_mux.bus, data_width=data_width) 341 | self._wb_decoder.add(csr_bridge.wb_bus, addr=bank_addr, extend=True) 342 | self._csr_subs.append((csr_mux, csr_bridge)) 343 | 344 | for window, window_addr, window_sparse in periph.iter_windows(): 345 | self._wb_decoder.add(window, addr=window_addr, sparse=window_sparse, extend=True) 346 | 347 | events = list(periph.iter_events()) 348 | if len(events) > 0: 349 | self._int_src = InterruptSource(events, name="{}_ev".format(periph.name)) 350 | self.irq = self._int_src.irq 351 | 352 | csr_mux = csr.Multiplexer(addr_width=1, data_width=8, alignment=alignment) 353 | csr_mux.add(self._int_src.status, extend=True) 354 | csr_mux.add(self._int_src.pending, extend=True) 355 | csr_mux.add(self._int_src.enable, extend=True) 356 | 357 | csr_bridge = WishboneCSRBridge(csr_mux.bus, data_width=data_width) 358 | self._wb_decoder.add(csr_bridge.wb_bus, extend=True) 359 | self._csr_subs.append((csr_mux, csr_bridge)) 360 | else: 361 | self._int_src = None 362 | self.irq = None 363 | 364 | self.bus = self._wb_decoder.bus 365 | 366 | def elaborate(self, platform): 367 | m = Module() 368 | 369 | for i, (csr_mux, csr_bridge) in enumerate(self._csr_subs): 370 | m.submodules[ "csr_mux_{}".format(i)] = csr_mux 371 | m.submodules["csr_bridge_{}".format(i)] = csr_bridge 372 | 373 | if self._int_src is not None: 374 | m.submodules._int_src = self._int_src 375 | 376 | m.submodules.wb_decoder = self._wb_decoder 377 | 378 | return m 379 | -------------------------------------------------------------------------------- /amlib/soc/uart.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib.fifo import SyncFIFO 3 | 4 | from ..io.serial import AsyncSerial 5 | 6 | from .peripheral import Peripheral 7 | 8 | __all__ = ["UARTPeripheral"] 9 | 10 | class UARTPeripheral(Peripheral, Elaboratable): 11 | """Asynchronous serial transceiver peripheral. 12 | 13 | See :class:`amaranth_stdio.serial.AsyncSerial` for details. 14 | 15 | CSR registers 16 | ------------- 17 | divisor : read/write 18 | Clock divisor. 19 | rx_data : read-only 20 | Receiver data. 21 | rx_rdy : read-only 22 | Receiver ready. The receiver FIFO is non-empty. 23 | rx_err : read-only 24 | Receiver error flags. See :class:`amaranth_stdio.serial.AsyncSerialRX` for layout. 25 | tx_data : write-only 26 | Transmitter data. 27 | tx_rdy : read-only 28 | Transmitter ready. The transmitter FIFO is non-full. 29 | 30 | Events 31 | ------ 32 | rx_rdy : level-triggered 33 | Receiver ready. The receiver FIFO is non-empty. 34 | rx_err : edge-triggered (rising) 35 | Receiver error. Error cause is available in the ``rx_err`` register. 36 | tx_mty : edge-triggered (rising) 37 | Transmitter empty. The transmitter FIFO is empty. 38 | 39 | Parameters 40 | ---------- 41 | rx_depth : int 42 | Depth of the receiver FIFO. 43 | tx_depth : int 44 | Depth of the transmitter FIFO. 45 | divisor : int 46 | Clock divisor reset value. Should be set to ``int(clk_frequency // baudrate)``. 47 | divisor_bits : int 48 | Optional. Clock divisor width. If omitted, ``bits_for(divisor)`` is used instead. 49 | data_bits : int 50 | Data width. 51 | parity : ``"none"``, ``"mark"``, ``"space"``, ``"even"``, ``"odd"`` 52 | Parity mode. 53 | pins : :class:`Record` 54 | Optional. UART pins. See :class:`amaranth_boards.resources.UARTResource`. 55 | 56 | Attributes 57 | ---------- 58 | bus : :class:`amaranth_soc.wishbone.Interface` 59 | Wishbone bus interface. 60 | irq : :class:`IRQLine` 61 | Interrupt request line. 62 | """ 63 | def __init__(self, *, rx_depth=16, tx_depth=16, **kwargs): 64 | super().__init__() 65 | 66 | self._phy = AsyncSerial(**kwargs) 67 | self._rx_fifo = SyncFIFO(width=self._phy.rx.data.width, depth=rx_depth) 68 | self._tx_fifo = SyncFIFO(width=self._phy.tx.data.width, depth=tx_depth) 69 | 70 | bank = self.csr_bank() 71 | self._enabled = bank.csr(1, "w") 72 | self._divisor = bank.csr(self._phy.divisor.width, "rw") 73 | self._rx_data = bank.csr(self._phy.rx.data.width, "r") 74 | self._rx_rdy = bank.csr(1, "r") 75 | self._rx_err = bank.csr(len(self._phy.rx.err), "r") 76 | self._tx_data = bank.csr(self._phy.tx.data.width, "w") 77 | self._tx_rdy = bank.csr(1, "r") 78 | 79 | self._rx_rdy_ev = self.event(mode="level") 80 | self._rx_err_ev = self.event(mode="rise") 81 | self._tx_mty_ev = self.event(mode="rise") 82 | 83 | self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) 84 | self.bus = self._bridge.bus 85 | self.irq = self._bridge.irq 86 | 87 | self.tx = Signal() 88 | self.rx = Signal() 89 | self.enabled = Signal() 90 | self.driving = Signal() 91 | 92 | def elaborate(self, platform): 93 | m = Module() 94 | m.submodules.bridge = self._bridge 95 | 96 | m.submodules.phy = self._phy 97 | m.submodules.rx_fifo = self._rx_fifo 98 | m.submodules.tx_fifo = self._tx_fifo 99 | 100 | m.d.comb += self._divisor.r_data.eq(self._phy.divisor) 101 | with m.If(self._divisor.w_stb): 102 | m.d.sync += self._phy.divisor.eq(self._divisor.w_data) 103 | 104 | # Control our UART's enable state. 105 | with m.If(self._enabled.w_stb): 106 | m.d.sync += self.enabled.eq(self._enabled.w_data) 107 | 108 | m.d.comb += [ 109 | self._rx_data.r_data .eq(self._rx_fifo.r_data), 110 | self._rx_fifo.r_en .eq(self._rx_data.r_stb), 111 | self._rx_rdy.r_data .eq(self._rx_fifo.r_rdy), 112 | 113 | self._rx_fifo.w_data .eq(self._phy.rx.data), 114 | self._rx_fifo.w_en .eq(self._phy.rx.rdy), 115 | self._phy.rx.ack .eq(self._rx_fifo.w_rdy), 116 | self._rx_err.r_data .eq(self._phy.rx.err), 117 | 118 | self._tx_fifo.w_en .eq(self._tx_data.w_stb & self.enabled), 119 | self._tx_fifo.w_data .eq(self._tx_data.w_data), 120 | self._tx_rdy.r_data .eq(self._tx_fifo.w_rdy), 121 | 122 | self._phy.tx.data .eq(self._tx_fifo.r_data), 123 | self._phy.tx.ack .eq(self._tx_fifo.r_rdy), 124 | self._tx_fifo.r_en .eq(self._phy.tx.rdy), 125 | 126 | self._rx_rdy_ev.stb .eq(self._rx_fifo.r_rdy), 127 | self._rx_err_ev.stb .eq(self._phy.rx.err.any()), 128 | self._tx_mty_ev.stb .eq(~self._tx_fifo.r_rdy), 129 | 130 | self.tx .eq(self._phy.tx.o), 131 | self._phy.rx.i .eq(self.rx), 132 | self.driving .eq(~self._phy.tx.rdy) 133 | ] 134 | 135 | return m 136 | -------------------------------------------------------------------------------- /amlib/stream/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adapted from the LUNA project 3 | # 4 | # Copyright (c) 2020 Great Scott Gadgets 5 | # Copyright (c) 2021 Hans Baier 6 | # 7 | # SPDX-License-Identifier: BSD-3-Clause 8 | 9 | """ Core stream definitions. """ 10 | 11 | from amaranth import * 12 | from amaranth.lib.fifo import FIFOInterface 13 | 14 | class StreamInterface(Record): 15 | """ Simple record implementing a unidirectional data stream. 16 | 17 | This class is similar to LiteX's streams; but instances may be optimized for 18 | interaction with USB PHYs. Accordingly, some uses may add restrictions; this 19 | is typically indicated by subclassing this interface. 20 | 21 | Attributes 22 | ----------- 23 | valid: Signal(), from originator 24 | Indicates that the current payload bytes are valid pieces of the current transaction. 25 | first: Signal(), from originator 26 | Indicates that the payload byte is the first byte of a new packet. 27 | last: Signal(), from originator 28 | Indicates that the payload byte is the last byte of the current packet. 29 | payload: Signal(payload_width), from originator 30 | The data payload to be transmitted. 31 | 32 | ready: Signal(), from receiver 33 | Indicates that the receiver will accept the payload byte at the next active 34 | clock edge. Can be de-asserted to put backpressure on the transmitter. 35 | 36 | Parameters 37 | ---------- 38 | payload_width: int 39 | The width of the stream's payload, in bits. 40 | extra_fields: list of tuples, optional 41 | A flat (non-nested) list of tuples indicating any extra fields present. 42 | Similar to a record's layout field; but cannot be nested. 43 | """ 44 | 45 | def __init__(self, *, name=None, payload_width=8, valid_width=1, extra_fields=None): 46 | """ 47 | Parameter: 48 | payload_width -- The width of the payload packets. 49 | """ 50 | 51 | # If we don't have any extra fields, use an empty list in its place. 52 | if extra_fields is None: 53 | extra_fields = [] 54 | 55 | # ... store our extra fields... 56 | self._extra_fields = extra_fields 57 | 58 | # ... and create our basic stream. 59 | super().__init__([ 60 | ('valid', valid_width), 61 | ('ready', 1), 62 | 63 | ('first', 1), 64 | ('last', 1), 65 | 66 | ('payload', payload_width), 67 | *extra_fields 68 | ], name=name) 69 | 70 | def attach(self, interface, omit=None): 71 | # Create lists of fields to be copied -to- the interface (RHS fields), 72 | # and lists of fields to be copied -from- the interface (LHS fields). 73 | rhs_fields = ['valid', 'first', 'last', 'payload', *self._extra_fields] 74 | lhs_fields = ['ready'] 75 | assignments = [] 76 | 77 | if omit: 78 | rhs_fields = [field for field in rhs_fields if field not in omit] 79 | lhs_fields = [field for field in lhs_fields if field not in omit] 80 | 81 | 82 | # Create each of our assignments. 83 | for field in rhs_fields: 84 | assignment = interface[field].eq(self[field]) 85 | assignments.append(assignment) 86 | for field in lhs_fields: 87 | assignment = self[field].eq(interface[field]) 88 | assignments.append(assignment) 89 | 90 | return assignments 91 | 92 | def connect(self, interface, omit=None): 93 | return self.attach(interface, omit=omit) 94 | 95 | def stream_eq(self, interface, *, omit=None): 96 | """ A hopefully more clear version of .connect() that more clearly indicates data_flow direction. 97 | 98 | This will either solve a common footgun or introduce a new one. We'll see and adapt accordingly. 99 | """ 100 | return interface.attach(self, omit=omit) 101 | 102 | def tap(self, interface, *, tap_ready=False, **kwargs): 103 | """ Simple extension to stream_eq() that captures a read-only view of the stream. 104 | 105 | This connects all signals from ``interface`` to their equivalents in this stream. 106 | """ 107 | core = self.stream_eq(interface, omit={"ready"}, **kwargs) 108 | 109 | if tap_ready: 110 | core.append(self.ready.eq(interface.ready)) 111 | 112 | return core 113 | 114 | def __getattr__(self, name): 115 | 116 | # Allow "data" to be a semantic alias for payload. 117 | # In some cases, this makes more sense to write; so we'll allow either. 118 | # Individual sections of the code base should stick to one or the other (please). 119 | if name == 'data': 120 | name = "payload" 121 | 122 | return super().__getattr__(name) 123 | 124 | 125 | def connect_fifo_to_stream(fifo: FIFOInterface, stream: StreamInterface, firstBit: int=None, lastBit: int=None) -> None: 126 | """Connects the output of the FIFO to the of the stream. Data flows from the fifo the stream. 127 | It is assumed the payload occupies the lowest significant bits 128 | This function connects first/last signals if their bit numbers are given 129 | """ 130 | 131 | result = [ 132 | stream.valid.eq(fifo.r_rdy), 133 | fifo.r_en.eq(stream.ready), 134 | stream.payload.eq(fifo.r_data), 135 | ] 136 | 137 | if firstBit: 138 | result.append(stream.first.eq(fifo.r_data[firstBit])) 139 | if lastBit: 140 | result.append(stream.last.eq(fifo.r_data[lastBit])) 141 | 142 | return result 143 | 144 | 145 | 146 | def connect_stream_to_fifo(stream: StreamInterface, fifo: FIFOInterface, firstBit: int=None, lastBit: int=None) -> None: 147 | """Connects the stream to the input of the FIFO. Data flows from the stream to the FIFO. 148 | It is assumed the payload occupies the lowest significant bits 149 | This function connects first/last signals if their bit numbers are given 150 | """ 151 | 152 | result = [ 153 | fifo.w_en.eq(stream.valid), 154 | stream.ready.eq(fifo.w_rdy), 155 | fifo.w_data.eq(stream.payload), 156 | ] 157 | 158 | if firstBit: 159 | result.append(fifo.w_data[firstBit].eq(stream.first)) 160 | if lastBit: 161 | result.append(fifo.w_data[lastBit].eq(stream.last)) 162 | 163 | return result 164 | -------------------------------------------------------------------------------- /amlib/stream/arbiter.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adopted from the LUNA project 3 | # 4 | # Copyright (c) 2020 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | """ Stream multiplexers/arbiters. """ 8 | 9 | from amaranth import * 10 | from . import StreamInterface 11 | 12 | 13 | class StreamMultiplexer(Elaboratable): 14 | """ Gateware that merges a collection of StreamInterfaces into a single interface. 15 | 16 | This variant performs no scheduling. Assumes that only one stream will be communicating at once. 17 | 18 | Attributes 19 | ---------- 20 | output: StreamInterface(), output stream 21 | Our output interface; has all of the active busses merged together. 22 | """ 23 | 24 | def __init__(self, stream_type=StreamInterface): 25 | """ 26 | Parameters: 27 | stream_type -- The type of stream we'll be multiplexing. Must be a subclass of StreamInterface. 28 | """ 29 | 30 | # Collection that stores each of the interfaces added to this bus. 31 | self._inputs = [] 32 | 33 | # 34 | # I/O port 35 | # 36 | self.output = stream_type() 37 | 38 | def add_input(self, input_interface): 39 | 40 | """ Adds a transmit interface to the multiplexer. """ 41 | self._inputs.append(input_interface) 42 | 43 | def elaborate(self, platform): 44 | m = Module() 45 | 46 | # Our basic functionality is simple: we'll build a priority encoder that 47 | # connects whichever interface has its .valid signal high. 48 | 49 | conditional = m.If 50 | 51 | for interface in self._inputs: 52 | 53 | # If the given interface is asserted, drive our output with its signals. 54 | with conditional(interface.valid): 55 | m.d.comb += interface.attach(self.output) 56 | 57 | # After our first iteration, use Elif instead of If. 58 | conditional = m.Elif 59 | 60 | 61 | return m 62 | 63 | 64 | class StreamArbiter(Elaboratable): 65 | """ Gateware that merges a collection of StreamInterfaces into a single interface. 66 | 67 | This variant uses a simple priority scheduler; and will use a standard valid/ready handshake 68 | to schedule a single stream to communicate at a time. Bursts of ``valid`` will never be interrupted, 69 | so streams will only be switched once the current transmitter drops ``valid`` low. 70 | 71 | 72 | Attributes 73 | ---------- 74 | source: StreamInterface(), output stream 75 | Our output interface; has all of the active busses merged together. 76 | 77 | idle: Signal(), output 78 | Asserted when none of our streams is currently active. 79 | 80 | Parameters 81 | ---------- 82 | stream_type: subclass of StreamInterface 83 | If provided, sets the type of stream we'll be multiplexing (and thus our output type). 84 | domain: str 85 | The name of the domain in which this arbiter should operate. Defaults to "sync". 86 | """ 87 | 88 | def __init__(self, *, stream_type=StreamInterface, domain="sync"): 89 | self._domain = domain 90 | 91 | # Collection that stores each of the interfaces added to this bus. 92 | self._sinks = [] 93 | 94 | # 95 | # I/O port 96 | # 97 | self.source = stream_type() 98 | self.idle = Signal() 99 | 100 | def add_stream(self, stream): 101 | """ Adds a stream to our arbiter. 102 | 103 | Parameters 104 | ---------- 105 | stream: StreamInterface subclass 106 | The stream to be added. Streams added first will have higher priority. 107 | """ 108 | self._sinks.append(stream) 109 | 110 | def elaborate(self, platform): 111 | m = Module() 112 | active_stream = self.source 113 | 114 | # Keep track of which stream is currently active. 115 | stream_count = len(self._sinks) 116 | active_stream_index = Signal(range(stream_count)) 117 | 118 | # 119 | # Stream output multiplexer. 120 | # 121 | with m.Switch(active_stream_index): 122 | 123 | # Generate a switch case for each of our possible stream indexes.. 124 | for index, stream in enumerate(self._sinks): 125 | with m.Case(index): 126 | 127 | # ... and connect up the stream while in that case. 128 | m.d.comb += active_stream.stream_eq(stream) 129 | 130 | # 131 | # Active stream selection. 132 | # 133 | 134 | # Only change which stream we're working with when the active stream stops transmitting. 135 | with m.If(~active_stream.valid): 136 | 137 | # Assume we're idle until proven otherwise. 138 | m.d.comb += self.idle.eq(1) 139 | 140 | # Check other streams to see if any are valid. We'll use a reversed list in order to maintain 141 | # our priority order; as the last assignment here "wins". 142 | for stream_index in reversed(range(stream_count)): 143 | 144 | # If another stream -is- valid, set it to be the active stream. 145 | with m.If(self._sinks[stream_index].valid): 146 | m.d.comb += self.idle.eq(0) 147 | m.d.sync += active_stream_index.eq(stream_index) 148 | 149 | # If we're operating in a domain other than sync, replace 'sync' with it. 150 | if self._domain != "sync": 151 | m = DomainRenamer(self._domain)(m) 152 | 153 | return m 154 | -------------------------------------------------------------------------------- /amlib/stream/i2c.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2021 Hans Baier hansfbaier@gmail.com 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | from amaranth import * 7 | from amaranth.build import Platform 8 | from amaranth.lib.fifo import SyncFIFO 9 | 10 | from . import StreamInterface 11 | from . import connect_stream_to_fifo 12 | from ..io.i2c import I2CInitiator, I2CTestbench 13 | from ..test import GatewareTestCase, sync_test_case 14 | 15 | class I2CStreamTransmitter(Elaboratable): 16 | def __init__(self, pads, period_cyc, clk_stretch=True, fifo_depth=16): 17 | self.pads = pads 18 | self.stream_in = StreamInterface() 19 | 20 | self._period_cyc = period_cyc 21 | self._clk_stretch = clk_stretch 22 | self._fifo_depth = fifo_depth 23 | 24 | self.i2c = I2CInitiator(self.pads, self._period_cyc, self._clk_stretch) 25 | 26 | def elaborate(self, platform: Platform) -> Module: 27 | m = Module() 28 | m.submodules.i2c = i2c = self.i2c 29 | m.submodules.input_fifo = in_fifo = SyncFIFO(width=8 + 2, depth=self._fifo_depth) 30 | m.d.comb += [ 31 | connect_stream_to_fifo(self.stream_in, in_fifo), 32 | in_fifo.w_data[8].eq(self.stream_in.first), 33 | in_fifo.w_data[9].eq(self.stream_in.last), 34 | ] 35 | 36 | payload = in_fifo.r_data[:8] 37 | first = in_fifo.r_data[8] 38 | last = in_fifo.r_data[9] 39 | 40 | # strobes are low by default 41 | m.d.comb += [ 42 | i2c.start.eq(0), 43 | i2c.stop.eq(0), 44 | i2c.read.eq(0), 45 | i2c.write.eq(0), 46 | in_fifo.r_en.eq(0), 47 | ] 48 | 49 | with m.FSM(): 50 | with m.State("IDLE"): 51 | with m.If(~i2c.busy & in_fifo.r_rdy & first): 52 | m.d.comb += i2c.start.eq(1) 53 | m.next = "STREAMING" 54 | 55 | with m.State("STREAMING"): 56 | with m.If(~i2c.busy): 57 | m.d.comb += [ 58 | i2c.data_i.eq(payload), 59 | i2c.write.eq(1), 60 | ] 61 | 62 | with m.If(in_fifo.r_rdy): 63 | m.d.comb += in_fifo.r_en.eq(1) 64 | 65 | with m.If(last): 66 | m.next = "STOP" 67 | 68 | with m.State("STOP"): 69 | with m.If(~i2c.busy): 70 | m.d.comb += i2c.stop.eq(1) 71 | m.next = "IDLE" 72 | 73 | return m 74 | 75 | 76 | class I2CStreamTransmitterTest(GatewareTestCase): 77 | FRAGMENT_UNDER_TEST = I2CStreamTransmitter 78 | FRAGMENT_ARGUMENTS = {'pads': I2CTestbench(), 'period_cyc': 4, 'clk_stretch': False, } 79 | 80 | @sync_test_case 81 | def test_basic(self): 82 | dut = self.dut 83 | yield 84 | yield 85 | yield dut.stream_in.valid.eq(1) 86 | yield dut.stream_in.payload.eq(0x55) 87 | yield dut.stream_in.first.eq(1) 88 | yield 89 | yield dut.stream_in.valid.eq(1) 90 | yield dut.stream_in.payload.eq(0xaa) 91 | yield dut.stream_in.first.eq(1) 92 | yield 93 | yield dut.stream_in.first.eq(0) 94 | yield dut.stream_in.payload.eq(0xbb) 95 | yield 96 | yield dut.stream_in.last.eq(1) 97 | yield dut.stream_in.payload.eq(0xcc) 98 | yield 99 | yield dut.stream_in.valid.eq(0) 100 | yield dut.stream_in.last.eq(0) 101 | yield 102 | yield 103 | yield 104 | for _ in range(330): yield -------------------------------------------------------------------------------- /amlib/stream/uart.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adopted from the LUNA project 3 | # 4 | # Copyright (c) 2020 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | """ UART interface gateware.""" 8 | 9 | from amaranth import Elaboratable, Module, Signal, Cat 10 | from amaranth_soc import wishbone, memory 11 | 12 | from ..stream import StreamInterface 13 | from ..test import GatewareTestCase, sync_test_case 14 | 15 | 16 | class UARTTransmitter(Elaboratable): 17 | """ Simple UART transitter. 18 | 19 | Intended for communicating with the debug controller; currently assumes 8n1. 20 | 21 | Attributes 22 | ---------- 23 | 24 | tx: Signal(), output 25 | The UART output. 26 | driving: Signal(), output 27 | True iff the UART is in the middle of driving data. In some cases, it's desireable 28 | to have the UART drive the line only when it is actively sending; letting a pull 29 | resistor handle pulling the line to idle. This line can be used to determine when the 30 | line should be driven. 31 | 32 | stream: input stream 33 | The stream carrying the data to be sent. 34 | 35 | idle: Signal(), output 36 | Asserted when the transmitter is idle; and thus pulsing `send_active` 37 | will start a new transmission. 38 | 39 | Parameters 40 | ------------ 41 | divisor: int 42 | The number of `sync` clock cycles per bit period. 43 | """ 44 | 45 | START_BIT = 0 46 | STOP_BIT = 1 47 | 48 | def __init__(self, *, divisor): 49 | self.divisor = divisor 50 | 51 | # 52 | # I/O port 53 | # 54 | self.tx = Signal(reset=1) 55 | self.driving = Signal() 56 | self.stream = StreamInterface() 57 | 58 | self.idle = Signal() 59 | 60 | 61 | def elaborate(self, platform): 62 | m = Module() 63 | 64 | # Baud generator. 65 | baud_counter = Signal(range(0, self.divisor)) 66 | 67 | # Tx shift register; holds our data, a start, and a stop bit. 68 | bits_per_frame = len(self.stream.payload) + 2 69 | data_shift = Signal(bits_per_frame) 70 | bits_to_send = Signal(range(0, len(data_shift))) 71 | 72 | # Create an internal signal equal to our input data framed with a start/stop bit. 73 | framed_data_in = Cat(self.START_BIT, self.stream.payload, self.STOP_BIT) 74 | 75 | 76 | with m.FSM() as f: 77 | m.d.comb += self.idle.eq(f.ongoing('IDLE')) 78 | 79 | # IDLE: transmitter is waiting for input 80 | with m.State("IDLE"): 81 | m.d.comb += [ 82 | self.tx .eq(1), 83 | self.stream.ready .eq(1) 84 | ] 85 | 86 | 87 | # Once we get a send request, fill in our shift register, and start shifting. 88 | with m.If(self.stream.valid): 89 | m.d.sync += [ 90 | baud_counter .eq(self.divisor - 1), 91 | bits_to_send .eq(len(data_shift) - 1), 92 | data_shift .eq(framed_data_in), 93 | ] 94 | 95 | m.next = "TRANSMIT" 96 | 97 | 98 | # TRANSMIT: actively shift out start/data/stop 99 | with m.State("TRANSMIT"): 100 | m.d.sync += baud_counter .eq(baud_counter - 1) 101 | m.d.comb += [ 102 | self.tx .eq(data_shift[0]), 103 | self.driving .eq(1) 104 | ] 105 | 106 | # If we've finished a bit period... 107 | with m.If(baud_counter == 0): 108 | m.d.sync += baud_counter.eq(self.divisor - 1) 109 | 110 | # ... if we have bits left to send, move to the next one. 111 | with m.If(bits_to_send > 0): 112 | m.d.sync += [ 113 | bits_to_send .eq(bits_to_send - 1), 114 | data_shift .eq(data_shift[1:]) 115 | ] 116 | 117 | # Otherwise, complete the frame. 118 | with m.Else(): 119 | m.d.comb += self.stream.ready.eq(1) 120 | 121 | # If we still have data to send, move to the next byte... 122 | with m.If(self.stream.valid): 123 | m.d.sync += [ 124 | bits_to_send .eq(bits_per_frame - 1), 125 | data_shift .eq(framed_data_in), 126 | ] 127 | 128 | # ... otherwise, move to our idle state. 129 | with m.Else(): 130 | m.next = "IDLE" 131 | 132 | 133 | return m 134 | 135 | 136 | class UARTTransmitterTest(GatewareTestCase): 137 | DIVISOR = 10 138 | 139 | FRAGMENT_UNDER_TEST = UARTTransmitter 140 | FRAGMENT_ARGUMENTS = dict(divisor=DIVISOR) 141 | 142 | 143 | def advance_half_bit(self): 144 | yield from self.advance_cycles(self.DIVISOR // 2) 145 | 146 | def advance_bit(self): 147 | yield from self.advance_cycles(self.DIVISOR) 148 | 149 | 150 | def assert_data_sent(self, byte_expected): 151 | dut = self.dut 152 | 153 | # Our start bit should remain present until the next bit period. 154 | yield from self.advance_half_bit() 155 | self.assertEqual((yield dut.tx), 0) 156 | 157 | # We should then see each bit of our data, LSB first. 158 | bits = [int(i) for i in f"{byte_expected:08b}"] 159 | for bit in bits[::-1]: 160 | yield from self.advance_bit() 161 | self.assertEqual((yield dut.tx), bit) 162 | 163 | # Finally, we should see a stop bit. 164 | yield from self.advance_bit() 165 | self.assertEqual((yield dut.tx), 1) 166 | 167 | 168 | @sync_test_case 169 | def test_burst_transmit(self): 170 | dut = self.dut 171 | stream = dut.stream 172 | 173 | # We should remain idle until a transmit is requested... 174 | yield from self.advance_cycles(10) 175 | self.assertEqual((yield dut.idle), 1) 176 | self.assertEqual((yield dut.stream.ready), 1) 177 | 178 | # ... and our tx line should idle high. 179 | self.assertEqual((yield dut.tx), 1) 180 | 181 | # First, transmit 0x55 (maximum transition rate). 182 | yield stream.payload.eq(0x55) 183 | yield stream.valid.eq(1) 184 | 185 | # We should see our data become accepted; and we 186 | # should see a start bit. 187 | yield 188 | self.assertEqual((yield stream.ready), 1) 189 | yield 190 | self.assertEqual((yield dut.tx), 0) 191 | 192 | # Provide our next byte of data once the current 193 | # one has been accepted. Changing this before the tests 194 | # below ensures that we validate that data is latched properly. 195 | yield stream.payload.eq(0x66) 196 | 197 | # Ensure we get our data correctly. 198 | yield from self.assert_data_sent(0x55) 199 | yield from self.assert_data_sent(0x66) 200 | 201 | # Stop transmitting after the next frame. 202 | yield stream.valid.eq(0) 203 | 204 | # Ensure we actually stop. 205 | yield from self.advance_bit() 206 | self.assertEqual((yield dut.idle), 1) 207 | 208 | 209 | class UARTTransmitterPeripheral(Elaboratable): 210 | """ Wishbone-attached variant of our UARTTransmitter. 211 | 212 | Attributes 213 | ---------- 214 | tx: Signal(), output 215 | The UART line to use for transmission. 216 | bus: wishbone bus 217 | Wishbone interface used for UART connections. 218 | 219 | Parameters 220 | ---------- 221 | divisor: int 222 | number of `sync` clock cycles per bit period 223 | """ 224 | 225 | # TODO: include a variant of misoc/LiteX's autoregister mechanism 226 | 227 | def __init__(self, divisor): 228 | self.divisor = divisor 229 | 230 | # 231 | # I/O port 232 | # 233 | self.tx = Signal() 234 | self.bus = wishbone.Interface(addr_width=0, data_width=8) 235 | self.bus.memory_map = memory.MemoryMap(addr_width=1, data_width=8) 236 | 237 | 238 | def elaborate(self, platform): 239 | m = Module() 240 | 241 | # Create our UART transmitter, and connect it directly to our 242 | # wishbone bus. 243 | m.submodules.tx = tx = UARTTransmitter(divisor=self.divisor) 244 | m.d.comb += [ 245 | tx.stream.valid .eq(self.bus.cyc & self.bus.stb & self.bus.we), 246 | tx.stream.payload .eq(self.bus.dat_w), 247 | 248 | self.bus.ack.eq(tx.stream.ready), 249 | self.tx.eq(tx.tx) 250 | ] 251 | return m 252 | 253 | 254 | class UARTMultibyteTransmitter(Elaboratable): 255 | """ UART transmitter capable of sending wide words. 256 | 257 | Intended for communicating with the debug controller; currently assumes 8n1. 258 | Transmits our words little-endian. 259 | 260 | Attributes 261 | ---------- 262 | 263 | tx: Signal(), output 264 | The UART output. 265 | stream: input stream 266 | The data to be transmitted. 267 | 268 | accepted: Signal(), output 269 | Strobe that indicates when the `data` word has been latched in; 270 | and the next data byte can be presented. 271 | idle: Signal(), output 272 | Asserted when the transmitter is idle; and thus pulsing `send_active` 273 | will start a new transmission. 274 | 275 | Parameters 276 | ------------ 277 | byte_width: int 278 | The number of bytes to be accepted at once. 279 | 280 | divisor: int 281 | The number of `sync` clock cycles per bit period. 282 | """ 283 | def __init__(self, *, byte_width, divisor): 284 | self.byte_width = byte_width 285 | self.divisor = divisor 286 | 287 | # 288 | # I/O port 289 | # 290 | self.tx = Signal(reset=1) 291 | self.stream = StreamInterface(payload_width=byte_width * 8) 292 | 293 | self.idle = Signal() 294 | 295 | 296 | def elaborate(self, platform): 297 | m = Module() 298 | 299 | # Create our core UART transmitter. 300 | m.submodules.uart = uart = UARTTransmitter(divisor=self.divisor) 301 | 302 | # We'll put each word to be sent through an shift register 303 | # that shifts out words a byte at a time. 304 | data_shift = Signal.like(self.stream.payload) 305 | 306 | # Count how many bytes we have left to send. 307 | bytes_to_send = Signal(range(0, self.byte_width + 1)) 308 | 309 | m.d.comb += [ 310 | 311 | # Connect our transmit output directly through. 312 | self.tx.eq(uart.tx), 313 | 314 | # Always provide our UART with the least byte of our shift register. 315 | uart.stream.payload.eq(data_shift[0:8]) 316 | ] 317 | 318 | 319 | 320 | with m.FSM() as f: 321 | m.d.comb += self.idle.eq(f.ongoing('IDLE')) 322 | 323 | # IDLE: transmitter is waiting for input 324 | with m.State("IDLE"): 325 | m.d.comb += self.stream.ready.eq(1) 326 | 327 | # Once we get a send request, fill in our shift register, and start shifting. 328 | with m.If(self.stream.valid): 329 | m.d.sync += [ 330 | data_shift .eq(self.stream.payload), 331 | bytes_to_send .eq(self.byte_width - 1), 332 | ] 333 | m.next = "TRANSMIT" 334 | 335 | 336 | # TRANSMIT: actively send each of the bytes of our word 337 | with m.State("TRANSMIT"): 338 | m.d.comb += uart.stream.valid.eq(1) 339 | 340 | # Once the UART is accepting our input... 341 | with m.If(uart.stream.ready): 342 | 343 | # ... if we have bytes left to send, move to the next one. 344 | with m.If(bytes_to_send > 0): 345 | m.d.sync += [ 346 | bytes_to_send .eq(bytes_to_send - 1), 347 | data_shift .eq(data_shift[8:]), 348 | ] 349 | 350 | # Otherwise, complete the frame. 351 | with m.Else(): 352 | m.d.comb += self.stream.ready.eq(1) 353 | 354 | # If we still have data to send, move to the next byte... 355 | with m.If(self.stream.valid): 356 | m.d.sync += [ 357 | bytes_to_send .eq(self.byte_width - 1), 358 | data_shift .eq(self.stream.payload), 359 | ] 360 | 361 | # ... otherwise, move to our idle state. 362 | with m.Else(): 363 | m.next = "IDLE" 364 | 365 | 366 | return m 367 | 368 | 369 | class UARTMultibyteTransmitterTest(GatewareTestCase): 370 | DIVISOR = 10 371 | 372 | FRAGMENT_UNDER_TEST = UARTMultibyteTransmitter 373 | FRAGMENT_ARGUMENTS = dict(divisor=DIVISOR, byte_width=4) 374 | 375 | 376 | def advance_half_bit(self): 377 | yield from self.advance_cycles(self.DIVISOR // 2) 378 | 379 | def advance_bit(self): 380 | yield from self.advance_cycles(self.DIVISOR) 381 | 382 | 383 | def assert_data_sent(self, byte_expected): 384 | dut = self.dut 385 | 386 | # Our start bit should remain present until the next bit period. 387 | yield from self.advance_half_bit() 388 | self.assertEqual((yield dut.tx), 0) 389 | 390 | # We should then see each bit of our data, LSB first. 391 | bits = [int(i) for i in f"{byte_expected:08b}"] 392 | for bit in bits[::-1]: 393 | yield from self.advance_bit() 394 | self.assertEqual((yield dut.tx), bit) 395 | 396 | # Finally, we should see a stop bit. 397 | yield from self.advance_bit() 398 | self.assertEqual((yield dut.tx), 1) 399 | 400 | yield from self.advance_cycles(2) 401 | 402 | 403 | @sync_test_case 404 | def test_burst_transmit(self): 405 | dut = self.dut 406 | stream = dut.stream 407 | 408 | # We should remain idle until a transmit is requested... 409 | yield from self.advance_cycles(10) 410 | self.assertEqual((yield dut.idle), 1) 411 | self.assertEqual((yield dut.stream.ready), 1) 412 | 413 | # Transmit a four-byte word. 414 | yield stream.payload.eq(0x11223355) 415 | yield stream.valid.eq(1) 416 | 417 | # We should see our data become accepted; and we 418 | # should see a start bit. 419 | yield 420 | self.assertEqual((yield stream.ready), 1) 421 | 422 | # Ensure we get our data correctly, and that our transmitter 423 | # isn't accepting data mid-frame. 424 | yield from self.assert_data_sent(0x55) 425 | self.assertEqual((yield stream.ready), 0) 426 | yield from self.assert_data_sent(0x33) -------------------------------------------------------------------------------- /amlib/test/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been partly adapted from the glasgow project 3 | # 4 | # Copyright (C) 2018 whitequark@whitequark.org 5 | # 6 | # SPDX-License-Identifier: Apache-2.0 7 | 8 | import functools 9 | 10 | from amaranth import Elaboratable 11 | from amaranth.sim import Simulator 12 | from amaranth.compat import Module as CompatModule, run_simulation as compat_run_simulation 13 | 14 | from .utils import GatewareTestCase, sync_test_case 15 | 16 | __all__ = ["GatewareTestCase", "sync_test_case", "simulation_test"] 17 | 18 | def simulation_test(case=None, **kwargs): 19 | def configure_wrapper(case): 20 | @functools.wraps(case) 21 | def wrapper(self): 22 | if hasattr(self, "configure"): 23 | self.configure(self.tb, **kwargs) 24 | def setup_wrapper(): 25 | if hasattr(self, "simulationSetUp"): 26 | yield from self.simulationSetUp(self.tb) 27 | yield from case(self, self.tb) 28 | if isinstance(self.tb, CompatModule): 29 | compat_run_simulation(self.tb, setup_wrapper(), vcd_name="test.vcd") 30 | if isinstance(self.tb, Elaboratable): 31 | sim = Simulator(self.tb) 32 | with sim.write_vcd(vcd_file=open("test.vcd", "w")): 33 | sim.add_clock(1e-8) 34 | sim.add_sync_process(setup_wrapper) 35 | sim.run() 36 | return wrapper 37 | 38 | if case is None: 39 | return configure_wrapper 40 | else: 41 | return configure_wrapper(case) 42 | -------------------------------------------------------------------------------- /amlib/test/utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adapted from the LUNA project 3 | # 4 | # Copyright (c) 2020 Great Scott Gadgets 5 | # Copyright (c) 2021 Hans Baier 6 | # SPDX-License-Identifier: BSD-3-Clause 7 | 8 | """ Boilerplate for amaranth unit tests. """ 9 | 10 | import os 11 | import math 12 | import unittest 13 | 14 | from functools import wraps 15 | 16 | from amaranth import Signal 17 | from amaranth.sim import Simulator 18 | 19 | 20 | def sync_test_case(process_function, *, domain="sync"): 21 | """ Decorator that converts a function into a simple synchronous-process test case. """ 22 | 23 | # This function should automatically transform a given function into a pysim 24 | # synch process _without_ losing the function's binding on self. Accordingly, 25 | # we'll create a wrapper function that has self bound, and then a test case 26 | # that's closed over that wrapper function's context. 27 | # 28 | # This ensure that self is still accessible from the decorated function. 29 | 30 | def run_test(self): 31 | @wraps(process_function) 32 | def test_case(): 33 | yield from self.initialize_signals() 34 | yield from process_function(self) 35 | 36 | self.domain = domain 37 | self._ensure_clocks_present() 38 | self.sim.add_sync_process(test_case, domain=domain) 39 | self.simulate(vcd_suffix=process_function.__name__) 40 | 41 | return run_test 42 | 43 | class GatewareTestCase(unittest.TestCase): 44 | domain = 'sync' 45 | 46 | # Convenience property: if set, instantiate_dut will automatically create 47 | # the relevant fragment with FRAGMENT_ARGUMENTS. 48 | FRAGMENT_UNDER_TEST = None 49 | FRAGMENT_ARGUMENTS = {} 50 | 51 | # Convenience properties: if not None, a clock with the relevant frequency 52 | # will automatically be added. 53 | FAST_CLOCK_FREQUENCY = None 54 | SYNC_CLOCK_FREQUENCY = 100e6 55 | 56 | def instantiate_dut(self): 57 | """ Basic-most function to instantiate a device-under-test. 58 | 59 | By default, instantiates FRAGMENT_UNDER_TEST. 60 | """ 61 | return self.FRAGMENT_UNDER_TEST(**self.FRAGMENT_ARGUMENTS) 62 | 63 | def get_vcd_name(self): 64 | """ Return the name to use for any VCDs generated by this class. """ 65 | return "test_{}".format(self.__class__.__name__) 66 | 67 | def setUp(self): 68 | self.dut = self.instantiate_dut() 69 | self.sim = Simulator(self.dut) 70 | 71 | if self.SYNC_CLOCK_FREQUENCY: 72 | self.sim.add_clock(1 / self.SYNC_CLOCK_FREQUENCY, domain="sync") 73 | 74 | def initialize_signals(self): 75 | """ Provide an opportunity for the test apparatus to initialize signals. """ 76 | yield Signal() 77 | 78 | def traces_of_interest(self): 79 | """ abstract method: returns the traces to include in the generated .vcd files """ 80 | return () 81 | 82 | def simulate(self, *, vcd_suffix=None): 83 | """ Runs our core simulation. """ 84 | 85 | # If we're generating VCDs, run the test under a VCD writer. 86 | if os.getenv('GENERATE_VCDS', default=False): 87 | 88 | # Figure out the name of our VCD files... 89 | vcd_name = self.get_vcd_name() 90 | if vcd_suffix: 91 | vcd_name = "{}_{}".format(vcd_name, vcd_suffix) 92 | 93 | # ... and run the simulation while writing them. 94 | traces = self.traces_of_interest() 95 | with self.sim.write_vcd(vcd_name + ".vcd", vcd_name + ".gtkw", traces=traces): 96 | self.sim.run() 97 | 98 | else: 99 | self.sim.run() 100 | 101 | def shouldBeLow(self, signal): 102 | self.assertEqual((yield signal), 0) 103 | 104 | def shouldBeHigh(self, signal): 105 | self.assertEqual((yield signal), 1) 106 | 107 | def shouldBeZero(self, signal): 108 | self.assertEqual((yield signal), 0) 109 | 110 | def shouldBeNonZero(self, signal): 111 | self.assertGreater((yield signal), 0) 112 | 113 | @staticmethod 114 | def pulse(signal, *, step_after=True): 115 | """ Helper method that asserts a signal for a cycle. """ 116 | yield signal.eq(1) 117 | yield 118 | yield signal.eq(0) 119 | 120 | if step_after: 121 | yield 122 | 123 | @staticmethod 124 | def advance_cycles(cycles): 125 | """ Helper methods that waits for a given number of cycles. """ 126 | 127 | for _ in range(cycles): 128 | yield 129 | 130 | @staticmethod 131 | def wait_until(strobe, *, timeout=None): 132 | """ Helper method that advances time until a strobe signal becomes true. """ 133 | 134 | cycles_passed = 0 135 | 136 | while not (yield strobe): 137 | yield 138 | 139 | cycles_passed += 1 140 | if timeout and cycles_passed > timeout: 141 | raise RuntimeError(f"Timeout waiting for '{strobe.name}' to go high!") 142 | 143 | def _ensure_clocks_present(self): 144 | """ Function that validates that a clock is present for our simulation domain. """ 145 | frequencies = { 146 | 'sync': self.SYNC_CLOCK_FREQUENCY, 147 | } 148 | self.assertIsNotNone(frequencies[self.domain], f"no frequency provied for `{self.domain}`-domain clock!") 149 | 150 | def wait(self, time): 151 | """ Helper method that waits for a given number of seconds in a *_test_case. """ 152 | 153 | # Figure out the period of the clock we want to work with... 154 | if self.domain == 'sync': 155 | period = 1 / self.SYNC_CLOCK_FREQUENCY 156 | 157 | # ... and, accordingly, how many cycles we want to delay. 158 | cycles = math.ceil(time / period) 159 | print(cycles) 160 | 161 | # Finally, wait that many cycles. 162 | yield from self.advance_cycles(cycles) -------------------------------------------------------------------------------- /amlib/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adopted from the LUNA project 3 | # 4 | """ Simple utility constructs """ 5 | 6 | from amaranth import Signal 7 | 8 | from .bits import bits 9 | from .cdc import synchronize, stretch_strobe_signal 10 | from .bus import OneHotMultiplexer 11 | from .lfsr import LinearFeedbackShiftRegister 12 | from .dividingcounter import DividingCounter 13 | from .clockdivider import SimpleClockDivider 14 | from .edgetopulse import EdgeToPulse 15 | from .nrziencoder import NRZIEncoder 16 | from .shiftregister import InputShiftRegister, OutputShiftRegister 17 | from .timer import Timer 18 | from .fifo import TransactionalizedFIFO 19 | 20 | __all__ = [ 21 | 'rising_edge_detected', 'falling_edge_detected', 'any_edge_detected', 22 | 'past_value_of','synchronize', 'EdgeToPulse', 'bits', 23 | 'synchronize', 'stretch_strobe_signal', 24 | 'OneHotMultiplexer', 'LinearFeedbackShiftRegister', 25 | 'DividingCounter', 'SimpleClockDivider', 26 | 'NRZIEncoder', 'InputShiftRegister', 'OutputShiftRegister', 27 | 'Timer', 28 | 'TransactionalizedFIFO' 29 | ] 30 | 31 | def _single_edge_detector(m, signal, *, domain, edge='rising'): 32 | """ Generates and returns a signal that goes high for a cycle upon a given edge of a given signal. """ 33 | 34 | # Create a one-cycle delayed version of our input signal. 35 | delayed = Signal() 36 | m.d[domain] += delayed.eq(signal) 37 | 38 | # And create a signal that detects edges on the relevant signal. 39 | edge_detected = Signal() 40 | if edge == 'falling': 41 | m.d.comb += edge_detected.eq(delayed & ~signal) 42 | elif edge == 'rising': 43 | m.d.comb += edge_detected.eq(~delayed & signal) 44 | elif edge == 'any': 45 | m.d.comb += edge_detected.eq(delayed != signal) 46 | else: 47 | raise ValueError("edge must be one of {rising,falling,any}") 48 | 49 | return edge_detected 50 | 51 | def past_value_of(m, signal, *, domain): 52 | """ Generates and returns a signal that represents the value of another signal a cycle ago. """ 53 | 54 | # Create a one-cycle delayed version of our input signal. 55 | delayed = Signal() 56 | m.d[domain] += delayed.eq(signal) 57 | 58 | return delayed 59 | 60 | def rising_edge_detected(m, signal, *, domain="sync"): 61 | """ Generates and returns a signal that goes high for a cycle each rising edge of a given signal. """ 62 | return _single_edge_detector(m, signal, edge='rising', domain=domain) 63 | 64 | def falling_edge_detected(m, signal, *, domain="sync"): 65 | """ Generates and returns a signal that goes high for a cycle each rising edge of a given signal. """ 66 | return _single_edge_detector(m, signal, edge='falling', domain=domain) 67 | 68 | def any_edge_detected(m, signal, *, domain="sync"): 69 | """ Generates and returns a signal that goes high for a cycle each rising edge of a given signal. """ 70 | return _single_edge_detector(m, signal, edge='any', domain=domain) 71 | -------------------------------------------------------------------------------- /amlib/utils/bus.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adopted from the LUNA project 3 | # 4 | # Copyright (c) 2020 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | """ Utilities for working with busses. """ 8 | 9 | import operator 10 | import functools 11 | 12 | from amaranth import Elaboratable, Signal, Module 13 | from amaranth.lib.coding import Encoder 14 | 15 | 16 | class OneHotMultiplexer(Elaboratable): 17 | """ Gateware that merges a collection of busses into a single bus. 18 | 19 | The busses joined must meet the following conditions: 20 | - The relevant type must have a signal that indicates when its data should be 21 | passed through to the relevant output. This is the 'valid' field. 22 | - Only one of the relevant busses `valid` fields should be high at a time; 23 | this effectively makes all of the high signals together a one-hot encoding. 24 | The implementation's behavior if more than one `valid` signal is undefined. 25 | 26 | I/O port: 27 | O*: output -- Our output interface; carries the signal merged from all input busses. 28 | """ 29 | 30 | def __init__(self, *, interface_type, valid_field='valid', mux_signals=(), or_signals=(), pass_signals=()): 31 | """ 32 | Parameters: 33 | interface_type -- The type of interface we'll be multiplexing. 34 | valid_field -- The name of the field that indicates the relevant object's validity. 35 | mux_signals -- An iterable of {signal names to be multiplexed, or functions that 36 | accept instances of the relevant interface type and return a Signal}. 37 | Signals listed here are passed through iff their one-hot `valid` signal is high. 38 | or_signals -- An itereable of {signals names to be multiplexed, or functions that accept 39 | an instance of the relevant interface type and return a Signal}. Signals listed 40 | here are OR'd together without multiplexing; it's expected that these signals will 41 | only be high when their corresponding `valid` signal is high. 42 | pass_signals -- A list of signals that should be passed back from the output interface to each 43 | of our input interfaces. 44 | """ 45 | 46 | self._valid_field = valid_field 47 | self._mux_signals = mux_signals 48 | self._or_signals = or_signals 49 | self._pass_signals = pass_signals 50 | 51 | # Collection that stores each of the interfaces added to this bus. 52 | self._inputs = [] 53 | 54 | # 55 | # I/O port 56 | # 57 | self.output = interface_type() 58 | 59 | def add_interface(self, input_interface): 60 | """ Adds an interface to the multiplexer. """ 61 | self._inputs.append(input_interface) 62 | 63 | def add_interfaces(self, interfaces): 64 | """ Adds a collection/iterable of interfaces to the multiplexer. """ 65 | for interface in interfaces: 66 | self.add_interface(interface) 67 | 68 | def add_input(self, input_interface): 69 | """ Alias for add_interface. Adds an interface to the multiplexer. """ 70 | self.add_interface(input_interface) 71 | 72 | @staticmethod 73 | def _get_signal(interface, name_or_function): 74 | """ Fetches a signal from the given interface. 75 | 76 | Parameter: 77 | interface -- The interface to fetch the relevant signal from. 78 | name_or_function -- The name of the signal to retrieve; or a function that 79 | returns the relevant signal given the interface. 80 | """ 81 | 82 | if callable(name_or_function): 83 | return name_or_function(interface) 84 | else: 85 | return getattr(interface, name_or_function) 86 | 87 | def elaborate(self, platform): 88 | m = Module() 89 | 90 | # Our module has three core parts: 91 | # - an encoder, which converts from our one-hot signal to a mux select line 92 | # - a multiplexer, which handles multiplexing e.g. payload signals 93 | # - a set of OR'ing logic, which joints together our simple or'd signals 94 | 95 | # Create our encoder... 96 | m.submodules.encoder = encoder = Encoder(len(self._inputs)) 97 | for index, interface in enumerate(self._inputs): 98 | 99 | # ... and tie its inputs to each of our 'valid' signals. 100 | valid_signal = getattr(interface, self._valid_field) 101 | m.d.comb += encoder.i[index].eq(valid_signal) 102 | 103 | # Create our multiplexer, and drive each of our output signals from it. 104 | with m.Switch(encoder.o): 105 | for index, interface in enumerate(self._inputs): 106 | 107 | # If an interface is selected... 108 | with m.Case(index): 109 | for identifier in self._mux_signals: 110 | 111 | # ... connect all of its muxed signals through to the output. 112 | output_signal = self._get_signal(self.output, identifier) 113 | input_signal = self._get_signal(interface, identifier) 114 | m.d.comb += output_signal.eq(input_signal) 115 | 116 | # Create the OR'ing logic for each of or or_signals. 117 | for identifier in self._or_signals: 118 | 119 | # Figure out the signals we want to work with... 120 | output_signal = self._get_signal(self.output, identifier) 121 | input_signals = (self._get_signal(i, identifier) for i in self._inputs) 122 | 123 | # ... and OR them together. 124 | or_reduced = functools.reduce(operator.__or__, input_signals, 0) 125 | m.d.comb += output_signal.eq(or_reduced) 126 | 127 | 128 | # Finally, pass each of our pass-back signals from the output interface 129 | # back to each of our input interfaces. 130 | for identifier in self._pass_signals: 131 | output_signal = self._get_signal(self.output, identifier) 132 | 133 | for interface in self._inputs: 134 | input_signal = self._get_signal(interface, identifier) 135 | m.d.comb += input_signal.eq(output_signal) 136 | 137 | 138 | return m -------------------------------------------------------------------------------- /amlib/utils/cdc.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | # 3 | # This file has been adopted from the LUNA project 4 | # 5 | # Copyright (c) 2020 Great Scott Gadgets 6 | # SPDX-License-Identifier: BSD-3-Clause 7 | 8 | """ Helpers for clock domain crossings. """ 9 | 10 | import unittest 11 | import warnings 12 | 13 | from unittest import TestCase 14 | from amaranth import Record, Module, Signal 15 | from amaranth.lib.cdc import FFSynchronizer 16 | from amaranth.lib.io import Pin 17 | from amaranth.hdl.rec import DIR_FANIN, DIR_FANOUT 18 | 19 | from ..test import GatewareTestCase, sync_test_case 20 | 21 | def synchronize(m, signal, *, output=None, o_domain='sync', stages=2): 22 | """ Convenience function. Synchronizes a signal, or equivalent collection. 23 | 24 | Parameters: 25 | input -- The signal to be synchronized. 26 | output -- The signal to output the result of the synchronization 27 | to, or None to have one created for you. 28 | domain -- The name of the domain to be synchronized to. 29 | stages -- The depth (in FFs) of the synchronization chain. 30 | Longer incurs more delay. Must be >= 2 to avoid metastability. 31 | 32 | Returns: 33 | record -- The post-synchronization signal. Will be equivalent to the 34 | `output` record if provided, or a new, created signal otherwise. 35 | """ 36 | 37 | # Quick function to create a synchronizer with our domain and stages. 38 | def create_synchronizer(signal, output): 39 | return FFSynchronizer(signal, output, o_domain=o_domain, stages=stages) 40 | 41 | if output is None: 42 | if isinstance(signal, Signal): 43 | output = Signal.like(signal) 44 | else: 45 | output = Record.like(signal) 46 | 47 | # If the object knows how to synchronize itself, let it. 48 | if hasattr(signal, '_synchronize_'): 49 | signal._synchronize_(m, output, o_domain=o_domain, stages=stages) 50 | return output 51 | 52 | # Trivial case: if this element doesn't have a layout, 53 | # we can just synchronize it directly. 54 | if not hasattr(signal, 'layout'): 55 | m.submodules += create_synchronizer(signal, output) 56 | return output 57 | 58 | # Otherwise, we'll need to make sure we only synchronize 59 | # elements with non-output directions. 60 | for name, layout, direction in signal.layout: 61 | 62 | # If this is a record itself, we'll need to recurse. 63 | if isinstance(signal[name], (Record, Pin)): 64 | synchronize(m, signal[name], output=output[name], 65 | o_domain=o_domain, stages=stages) 66 | continue 67 | 68 | # Skip any output elements, as they're already 69 | # in our clock domain, and we don't want to drive them. 70 | if (direction == DIR_FANOUT) or (hasattr(signal[name], 'o') and ~hasattr(signal[name], 'i')): 71 | m.d.comb += signal[name].eq(output[name]) 72 | continue 73 | 74 | m.submodules += create_synchronizer(signal[name], output[name]) 75 | 76 | return output 77 | 78 | 79 | class SynchronizedTest(TestCase): 80 | 81 | def test_signal(self): 82 | m = Module() 83 | synchronize(m, Signal()) 84 | 85 | def test_directional_record(self): 86 | m = Module() 87 | 88 | record = Record([ 89 | ('sig_in', 1, DIR_FANIN), 90 | ('sig_out', 1, DIR_FANOUT) 91 | ]) 92 | synchronize(m, record) 93 | 94 | def test_nested_record(self): 95 | m = Module() 96 | 97 | record = Record([ 98 | ('sig_in', 1, DIR_FANIN), 99 | ('sig_out', 1, DIR_FANOUT), 100 | ('nested', [ 101 | ('subsig_in', 1, DIR_FANIN), 102 | ('subsig_out', 1, DIR_FANOUT), 103 | ]) 104 | ]) 105 | synchronize(m, record) 106 | 107 | 108 | def stretch_strobe_signal(m, strobe, *, to_cycles, output=None, domain=None, allow_delay=False): 109 | """ Stretches a given strobe to the given number of cycles. 110 | 111 | Parameters: 112 | strobe -- The strobe signal to stretch. 113 | to_cycles -- The number of cycles to stretch the given strobe to. Must be >= 1. 114 | 115 | output -- If provided, the given signal will be used as the output signal. 116 | domain -- If provided, the given domain _object_ will be used in lieu of the sync domain. 117 | 118 | Returns the output signal. If output is provided, this is the same signal; otherwise, it is the 119 | signal that was created internally. 120 | """ 121 | 122 | # Assume the sync domain if no domain is provided. 123 | if domain is None: 124 | domain = m.d.sync 125 | 126 | # If we're not given an output signal to target, create one. 127 | if output is None: 128 | output = Signal() 129 | 130 | # Special case: if to_cycles is '1', we don't need to modify the strobe. 131 | # Connect it through directly. 132 | if to_cycles == 1: 133 | m.d.comb += output.eq(strobe) 134 | return output 135 | 136 | # Create a signal that shifts in our strobe constantly, so we 137 | # have a memory of its last N values. 138 | if allow_delay: 139 | delayed_strobe = Signal(to_cycles) 140 | domain += delayed_strobe.eq((delayed_strobe << 1) | strobe) 141 | m.d.comb += output.eq(delayed_strobe != 0) 142 | else: 143 | delayed_strobe = Signal(to_cycles - 1) 144 | domain += delayed_strobe.eq((delayed_strobe << 1) | strobe) 145 | m.d.comb += output.eq(strobe | (delayed_strobe != 0)) 146 | 147 | return output 148 | 149 | 150 | class StrobeStretcherTest(GatewareTestCase): 151 | """ Test case for our strobe stretcher function. """ 152 | 153 | def instantiate_dut(self): 154 | m = Module() 155 | 156 | # Create a module that only has our stretched strobe signal. 157 | m.strobe_in = Signal() 158 | m.stretched_strobe = stretch_strobe_signal(m, m.strobe_in, to_cycles=2) 159 | 160 | return m 161 | 162 | def initialize_signals(self): 163 | yield self.dut.strobe_in.eq(0) 164 | 165 | @sync_test_case 166 | def test_stretch(self): 167 | 168 | # Ensure our stretched strobe stays 0 until it sees an input. 169 | yield 170 | self.assertEqual((yield self.dut.stretched_strobe), 0) 171 | yield 172 | self.assertEqual((yield self.dut.stretched_strobe), 0) 173 | 174 | # Apply our strobe, and validate that we immediately see a '1'... 175 | yield self.dut.strobe_in.eq(1) 176 | yield 177 | self.assertEqual((yield self.dut.stretched_strobe), 1) 178 | 179 | # ... ensure that 1 lasts for a second cycle ... 180 | yield self.dut.strobe_in.eq(0) 181 | yield 182 | self.assertEqual((yield self.dut.stretched_strobe), 1) 183 | 184 | # ... and then returns to 0. 185 | yield 186 | self.assertEqual((yield self.dut.stretched_strobe), 0) 187 | 188 | yield 189 | self.assertEqual((yield self.dut.stretched_strobe), 0) 190 | 191 | 192 | if __name__ == "__main__": 193 | warnings.filterwarnings("error") 194 | unittest.main() 195 | 196 | -------------------------------------------------------------------------------- /amlib/utils/clockdivider.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.build import Platform 3 | 4 | class SimpleClockDivider(Elaboratable): 5 | def __init__(self, divisor, clock_polarity=0): 6 | # parameters 7 | self.clock_polarity = clock_polarity 8 | assert divisor % 2 == 0, "divisor must be even" 9 | self._divisor = divisor // 2 10 | 11 | # I/O 12 | self.clock_enable_in = Signal() 13 | self.clock_out = Signal(reset=clock_polarity) 14 | 15 | def elaborate(self, platform: Platform) -> Module: 16 | m = Module() 17 | 18 | clock_counter = Signal(range(self._divisor)) 19 | 20 | with m.If(clock_counter >= (self._divisor - 1)): 21 | with m.If(self.clock_enable_in): 22 | m.d.sync += self.clock_out.eq(~self.clock_out), 23 | with m.Else(): 24 | m.d.sync += self.clock_out.eq(self.clock_polarity) 25 | 26 | m.d.sync += clock_counter.eq(0) 27 | 28 | with m.Else(): 29 | m.d.sync += clock_counter.eq(clock_counter + 1) 30 | 31 | return m -------------------------------------------------------------------------------- /amlib/utils/dividingcounter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2021 Hans Baier 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | # 6 | """Counter which runs a subcounter which divides the maincounter""" 7 | from amaranth import Elaboratable, Signal, Module 8 | from ..test import GatewareTestCase, sync_test_case 9 | 10 | class DividingCounter(Elaboratable): 11 | """Counter which runs a subcounter which divides the maincounter""" 12 | def __init__(self, divisor, width): 13 | self.reset_in = Signal() 14 | self.active_in = Signal() 15 | self.counter_out = Signal(width) 16 | self.divided_counter_out = Signal(width) 17 | self.dividable_out = Signal() 18 | self.divisor = divisor 19 | self.width = width 20 | 21 | self.ports = [self.reset_in, self.active_in, 22 | self.counter_out, self.divided_counter_out, self.dividable_out] 23 | 24 | def elaborate(self, platform) -> Module: 25 | """build the module""" 26 | m = Module() 27 | 28 | dividing_cycle_counter = Signal(range(0, self.divisor)) 29 | 30 | with m.If(self.reset_in): 31 | m.d.sync += [ 32 | self.counter_out.eq(0), 33 | self.divided_counter_out.eq(0), 34 | dividing_cycle_counter.eq(0) 35 | ] 36 | with m.Else(): 37 | with m.If(self.active_in): 38 | with m.If(dividing_cycle_counter == self.divisor - 1): 39 | m.d.sync += [ 40 | dividing_cycle_counter.eq(0), 41 | self.divided_counter_out.eq(self.divided_counter_out + 1), 42 | self.dividable_out.eq(1) 43 | ] 44 | 45 | with m.Else(): 46 | m.d.sync += [ 47 | self.dividable_out.eq(0), 48 | dividing_cycle_counter.eq(dividing_cycle_counter + 1) 49 | ] 50 | 51 | # when the main counter wraps around to zero, the dividing counter needs reset too 52 | with m.If(self.counter_out == (2 ** self.counter_out.width) - 1): 53 | m.d.sync += dividing_cycle_counter.eq(0) 54 | 55 | m.d.sync += [ 56 | self.counter_out.eq(self.counter_out + 1), 57 | ] 58 | 59 | return m 60 | 61 | 62 | class DividingCounterTest(GatewareTestCase): 63 | FRAGMENT_UNDER_TEST = DividingCounter 64 | FRAGMENT_ARGUMENTS = {'divisor': 5, 'width': 5} 65 | 66 | @sync_test_case 67 | def test_basic(self): 68 | dut = self.dut 69 | yield dut.active_in.eq(0) 70 | for _ in range(0, 5): 71 | yield 72 | self.assertEqual((yield dut.counter_out), 0) 73 | 74 | count = 0 75 | divided_count = 1 76 | yield dut.active_in.eq(1) 77 | for _ in range(0, 50): 78 | yield 79 | self.assertEqual((yield dut.counter_out), count % 32) 80 | 81 | dividable = yield dut.dividable_out 82 | if dividable: 83 | self.assertEqual(((count % 32) % 5), 0) 84 | self.assertEqual((yield dut.divided_counter_out), divided_count) 85 | divided_count += 1 86 | 87 | count += 1 88 | 89 | yield dut.reset_in.eq(1) 90 | yield 91 | yield 92 | self.assertEqual((yield dut.counter_out), 0) 93 | yield 94 | yield 95 | yield 96 | yield 97 | yield 98 | yield dut.reset_in.eq(0) 99 | 100 | count = 0 101 | for _ in range(0, 20): 102 | yield 103 | self.assertEqual((yield dut.counter_out), count % 32) 104 | count += 1 105 | 106 | yield dut.active_in.eq(0) 107 | yield 108 | yield 109 | yield 110 | return 111 | -------------------------------------------------------------------------------- /amlib/utils/edgetopulse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2021 Hans Baier 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | # 6 | """converts a rising edge to a single clock pulse""" 7 | from amaranth import Elaboratable, Signal, Module 8 | from ..test import GatewareTestCase, sync_test_case 9 | 10 | class EdgeToPulse(Elaboratable): 11 | """ 12 | each rising edge of the signal edge_in will be 13 | converted to a single clock pulse on pulse_out 14 | """ 15 | def __init__(self): 16 | self.edge_in = Signal() 17 | self.pulse_out = Signal() 18 | 19 | def elaborate(self, platform) -> Module: 20 | m = Module() 21 | 22 | edge_last = Signal() 23 | 24 | m.d.sync += edge_last.eq(self.edge_in) 25 | with m.If(self.edge_in & ~edge_last): 26 | m.d.comb += self.pulse_out.eq(1) 27 | with m.Else(): 28 | m.d.comb += self.pulse_out.eq(0) 29 | 30 | return m 31 | 32 | 33 | class EdgeToPulseTest(GatewareTestCase): 34 | FRAGMENT_UNDER_TEST = EdgeToPulse 35 | FRAGMENT_ARGUMENTS = {} 36 | 37 | @sync_test_case 38 | def test_basic(self): 39 | dut = self.dut 40 | yield dut.edge_in.eq(0) 41 | yield 42 | self.assertEqual((yield dut.pulse_out), 0) 43 | yield 44 | yield 45 | yield dut.edge_in.eq(1) 46 | yield 47 | self.assertEqual((yield dut.pulse_out), 1) 48 | yield 49 | self.assertEqual((yield dut.pulse_out), 0) 50 | yield 51 | yield 52 | yield 53 | self.assertEqual((yield dut.pulse_out), 0) 54 | yield dut.edge_in.eq(0) 55 | yield 56 | yield 57 | yield 58 | yield 59 | yield 60 | self.assertEqual((yield dut.pulse_out), 0) 61 | yield dut.edge_in.eq(1) 62 | yield 63 | self.assertEqual((yield dut.pulse_out), 1) 64 | yield 65 | self.assertEqual((yield dut.pulse_out), 0) 66 | yield dut.edge_in.eq(0) 67 | yield 68 | yield 69 | yield 70 | yield 71 | yield dut.edge_in.eq(1) 72 | yield 73 | self.assertEqual((yield dut.pulse_out), 1) 74 | yield dut.edge_in.eq(0) 75 | yield 76 | self.assertEqual((yield dut.pulse_out), 0) 77 | yield dut.edge_in.eq(1) 78 | yield 79 | self.assertEqual((yield dut.pulse_out), 1) 80 | yield 81 | self.assertEqual((yield dut.pulse_out), 0) 82 | yield 83 | yield dut.edge_in.eq(1) 84 | yield 85 | self.assertEqual((yield dut.pulse_out), 0) 86 | yield dut.edge_in.eq(0) 87 | yield 88 | self.assertEqual((yield dut.pulse_out), 0) 89 | yield 90 | yield dut.edge_in.eq(1) 91 | yield 92 | self.assertEqual((yield dut.pulse_out), 1) 93 | yield dut.edge_in.eq(0) 94 | yield 95 | self.assertEqual((yield dut.pulse_out), 0) 96 | yield 97 | yield 98 | -------------------------------------------------------------------------------- /amlib/utils/lfsr.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file has been adopted from the glasgow project 3 | # 4 | # Copyright (C) 2018 whitequark@whitequark.org 5 | # 6 | # SPDX-License-Identifier: Apache-2.0 7 | 8 | from amaranth import * 9 | 10 | __all__ = ["LinearFeedbackShiftRegister"] 11 | 12 | 13 | class LinearFeedbackShiftRegister(Elaboratable): 14 | """ 15 | A linear feedback shift register. Useful for generating long pseudorandom sequences with 16 | a minimal amount of logic. 17 | 18 | Use ``CEInserter`` and ``ResetInserter`` transformers to control the LFSR. 19 | 20 | :param degree: 21 | Width of register, in bits. 22 | :type degree: int 23 | :param taps: 24 | Feedback taps, with bits numbered starting at 1 (i.e. polynomial degrees). 25 | :type taps: list of int 26 | :param reset: 27 | Initial value loaded into the register. Must be non-zero, or only zeroes will be 28 | generated. 29 | :type reset: int 30 | """ 31 | def __init__(self, degree, taps, reset=1): 32 | assert reset != 0 33 | 34 | self.degree = degree 35 | self.taps = taps 36 | self.reset = reset 37 | 38 | self.value = Signal(degree, reset=reset) 39 | 40 | def elaborate(self, platform): 41 | m = Module() 42 | feedback = 0 43 | for tap in self.taps: 44 | feedback ^= (self.value >> (tap - 1)) & 1 45 | m.d.sync += self.value.eq((self.value << 1) | feedback) 46 | return m 47 | 48 | def generate(self): 49 | """Generate every distinct value the LFSR will take.""" 50 | value = self.reset 51 | mask = (1 << self.degree) - 1 52 | while True: 53 | yield value 54 | feedback = 0 55 | for tap in self.taps: 56 | feedback ^= (value >> (tap - 1)) & 1 57 | value = ((value << 1) & mask) | feedback 58 | if value == self.reset: 59 | break 60 | 61 | # ------------------------------------------------------------------------------------------------- 62 | 63 | import unittest 64 | from ..test import simulation_test 65 | 66 | class LFSRTestbench(Elaboratable): 67 | def __init__(self, **kwargs): 68 | self.dut = LinearFeedbackShiftRegister(**kwargs) 69 | 70 | def elaborate(self, platform): 71 | return self.dut 72 | 73 | 74 | class LFSRTestCase(unittest.TestCase): 75 | def setUp(self): 76 | self.tb = LFSRTestbench(degree=16, taps=(16, 14, 13, 11)) 77 | 78 | @simulation_test 79 | def test_generate(self, tb): 80 | soft_values = list(self.tb.dut.generate()) 81 | hard_values = [] 82 | for _ in range(len(soft_values)): 83 | hard_values.append((yield self.tb.dut.value)) 84 | yield 85 | 86 | self.assertEqual(len(soft_values), 65535) 87 | self.assertEqual(hard_values, soft_values) 88 | -------------------------------------------------------------------------------- /amlib/utils/nrziencoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2021 Hans Baier 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | # 6 | """Encode a stream of bits to NRZI""" 7 | 8 | from amaranth import Elaboratable, Signal, Module, Mux 9 | 10 | class NRZIEncoder(Elaboratable): 11 | """Converts a synchronous stream of bits into a NRZI encoded stream""" 12 | 13 | def __init__(self): 14 | self.nrzi_out = Signal() 15 | self.data_in = Signal() 16 | 17 | def elaborate(self, platform) -> Module: 18 | """ build the module """ 19 | m = Module() 20 | 21 | m.d.sync += self.nrzi_out.eq( 22 | Mux(self.data_in, 23 | ~self.nrzi_out, 24 | self.nrzi_out)), 25 | 26 | return m 27 | -------------------------------------------------------------------------------- /amlib/utils/shiftregister.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (c) 2021 Hans Baier 4 | # SPDX-License-Identifier: CERN-OHL-W-2.0 5 | # 6 | """ 7 | synchronous shift register: bits appear in the output at the next 8 | clock cycle 9 | """ 10 | from amaranth import Elaboratable, Signal, Module, Cat 11 | from ..test import GatewareTestCase, sync_test_case 12 | 13 | # pylint: disable=too-few-public-methods 14 | class InputShiftRegister(Elaboratable): 15 | """shift register with given depth in bits""" 16 | def __init__(self, depth): 17 | self.enable_in = Signal() 18 | self.bit_in = Signal() 19 | self.clear_in = Signal() 20 | self.value_out = Signal(depth) 21 | 22 | def elaborate(self, platform) -> Module: 23 | """build the module""" 24 | m = Module() 25 | 26 | with m.If(self.clear_in): 27 | m.d.sync += self.value_out.eq(0) 28 | with m.Elif(self.enable_in): 29 | m.d.sync += self.value_out.eq((self.value_out << 1) | self.bit_in) 30 | 31 | return m 32 | 33 | # pylint: disable=too-few-public-methods 34 | class OutputShiftRegister(Elaboratable): 35 | """shift register with given depth in bits""" 36 | def __init__(self, depth, rotate=False): 37 | self.enable_in = Signal() 38 | self.we_in = Signal() 39 | self.bit_out = Signal() 40 | self.value_in = Signal(depth) 41 | self.rotate = rotate 42 | 43 | def elaborate(self, platform) -> Module: 44 | """build the module""" 45 | m = Module() 46 | 47 | value = Signal.like(self.value_in) 48 | m.d.comb += self.bit_out.eq(value[0]) 49 | 50 | with m.If(self.we_in): 51 | m.d.sync += value.eq(self.value_in) 52 | with m.Elif(self.enable_in): 53 | m.d.sync += value.eq(Cat(value[1:], value[0])) if self.rotate else value.eq((value >> 1)) 54 | 55 | return m 56 | 57 | class InputShiftRegisterTest(GatewareTestCase): 58 | FRAGMENT_UNDER_TEST = InputShiftRegister 59 | FRAGMENT_ARGUMENTS = {'depth': 8} 60 | 61 | @sync_test_case 62 | def test_basic(self): 63 | dut = self.dut 64 | yield dut.enable_in.eq(0) 65 | yield 66 | self.assertEqual((yield dut.value_out), 0) 67 | yield dut.bit_in.eq(1) 68 | yield 69 | self.assertEqual((yield dut.value_out), 0) 70 | yield dut.bit_in.eq(0) 71 | yield 72 | self.assertEqual((yield dut.value_out), 0) 73 | yield dut.bit_in.eq(1) 74 | yield 75 | self.assertEqual((yield dut.value_out), 0) 76 | yield dut.bit_in.eq(1) 77 | yield 78 | yield dut.bit_in.eq(0) 79 | yield 80 | self.assertEqual((yield dut.value_out), 0) 81 | yield dut.bit_in.eq(1) 82 | yield dut.enable_in.eq(1) 83 | yield 84 | yield dut.enable_in.eq(0) 85 | yield 86 | self.assertEqual((yield dut.value_out), 1) 87 | yield dut.enable_in.eq(1) 88 | yield 89 | self.assertEqual((yield dut.value_out), 0b1) 90 | yield dut.bit_in.eq(0) 91 | yield 92 | self.assertEqual((yield dut.value_out), 0b11) 93 | yield dut.bit_in.eq(1) 94 | yield 95 | self.assertEqual((yield dut.value_out), 0b110) 96 | yield dut.bit_in.eq(1) 97 | yield 98 | self.assertEqual((yield dut.value_out), 0b1101) 99 | yield dut.bit_in.eq(0) 100 | yield 101 | self.assertEqual((yield dut.value_out), 0b11011) 102 | yield dut.bit_in.eq(1) 103 | yield 104 | self.assertEqual((yield dut.value_out), 0b110110) 105 | yield dut.enable_in.eq(0) 106 | yield 107 | self.assertEqual((yield dut.value_out), 0b1101101) 108 | yield 109 | self.assertEqual((yield dut.value_out), 0b1101101) 110 | yield 111 | self.assertEqual((yield dut.value_out), 0b1101101) 112 | yield dut.enable_in.eq(1) 113 | for _ in range(13): 114 | yield dut.bit_in.eq(1) 115 | yield 116 | yield dut.bit_in.eq(0) 117 | yield 118 | yield dut.bit_in.eq(1) 119 | yield 120 | yield dut.bit_in.eq(0) 121 | yield 122 | 123 | class OutputShiftRegisterTest(GatewareTestCase): 124 | FRAGMENT_UNDER_TEST = OutputShiftRegister 125 | FRAGMENT_ARGUMENTS = {'depth': 8, 'rotate': True} 126 | 127 | # Function to right 128 | # rotate n by d bits 129 | @staticmethod 130 | def rightRotate(n, d): 131 | # In n>>d, first d bits are 0. 132 | # To put last 3 bits of at 133 | # first, do bitwise or of n>>d 134 | # with n <<(8 - d) 135 | return (n >> d)|(n << (8 - d)) & 0xFF 136 | 137 | @sync_test_case 138 | def test_basic(self): 139 | dut = self.dut 140 | yield dut.enable_in.eq(0) 141 | value = 0xaa 142 | yield dut.value_in.eq(value) 143 | yield dut.we_in.eq(1) 144 | yield 145 | self.assertEqual((yield dut.bit_out), 0) 146 | 147 | yield dut.we_in.eq(0) 148 | yield 149 | self.assertEqual((yield dut.bit_out), 0) 150 | 151 | yield 152 | self.assertEqual((yield dut.bit_out), 0) 153 | 154 | yield dut.enable_in.eq(1) 155 | yield 156 | self.assertEqual((yield dut.bit_out), value & 0x1) 157 | value = self.rightRotate(value, 1) 158 | 159 | yield 160 | self.assertEqual((yield dut.bit_out), value & 0x1) 161 | value = self.rightRotate(value, 1) 162 | 163 | yield 164 | self.assertEqual((yield dut.bit_out), value & 0x1) 165 | value = self.rightRotate(value, 1) 166 | 167 | yield 168 | self.assertEqual((yield dut.bit_out), value & 0x1) 169 | value = self.rightRotate(value, 1) 170 | 171 | yield 172 | self.assertEqual((yield dut.bit_out), value & 0x1) 173 | value = self.rightRotate(value, 1) 174 | 175 | yield dut.enable_in.eq(0) 176 | yield 177 | yield 178 | yield 179 | yield dut.enable_in.eq(1) 180 | yield 181 | yield 182 | yield dut.value_in.eq(0x55) 183 | yield dut.we_in.eq(1) 184 | yield 185 | yield dut.we_in.eq(0) 186 | yield 187 | yield 188 | yield 189 | 190 | yield dut.we_in.eq(1) 191 | value = 0b10000000 192 | yield dut.value_in.eq(value) 193 | yield 194 | 195 | yield dut.we_in.eq(0) 196 | 197 | for _ in range(13): 198 | yield 199 | self.assertEqual((yield dut.bit_out), value & 0x1) 200 | value = self.rightRotate(value, 1) 201 | 202 | yield dut.we_in.eq(1) 203 | yield dut.value_in.eq(0) 204 | yield 205 | yield dut.we_in.eq(0) 206 | self.assertEqual((yield dut.bit_out), 0) 207 | 208 | yield 209 | self.assertEqual((yield dut.bit_out), 0) 210 | 211 | yield 212 | self.assertEqual((yield dut.bit_out), 0) 213 | 214 | yield 215 | self.assertEqual((yield dut.bit_out), 0) 216 | -------------------------------------------------------------------------------- /amlib/utils/timer.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.build import Platform 3 | from ..test import GatewareTestCase, sync_test_case 4 | 5 | class Timer(Elaboratable): 6 | def __init__(self, *, width=32, load=None, reload=None, allow_restart=True): 7 | self._width = width 8 | self._allow_restart = allow_restart 9 | self.load_in = Signal(width, name="load") if load == None else load 10 | self.reload_in = Signal(width, name="reload") if reload == None else reload 11 | self.counter_out = Signal(width) 12 | self.start = Signal() 13 | self.done = Signal() 14 | 15 | def elaborate(self, platform: Platform) -> Module: 16 | m = Module() 17 | 18 | counter = self.counter_out 19 | 20 | with m.FSM() as fsm: 21 | m.d.comb += self.done.eq(fsm.ongoing("DONE")) 22 | 23 | with m.State("IDLE"): 24 | # if load is nonzero, it takes precedence 25 | with m.If(self.load_in > 0): 26 | m.d.sync += counter.eq(self.load_in) 27 | with m.Else(): 28 | m.d.sync += counter.eq(self.reload_in) 29 | 30 | with m.If(self.start & (counter > 0)): 31 | # done should appear exactly 'load' cycles 32 | # load is one-based, but we stop at zero 33 | m.d.sync += counter.eq(counter - 1) 34 | m.next = "RUNNING" 35 | 36 | with m.State("RUNNING"): 37 | m.d.sync += counter.eq(counter - 1) 38 | with m.If(counter == 1): 39 | m.next = "DONE" 40 | 41 | with m.State("DONE"): 42 | with m.If(self.reload_in > 0): 43 | # we want the next done to appear 44 | # exactly 'reload_in' cycles later 45 | m.d.sync += counter.eq(self.reload_in - 1) 46 | m.next = "RUNNING" 47 | if self._allow_restart: 48 | with m.Else(): 49 | m.next = "IDLE" 50 | 51 | return m 52 | 53 | class TimerTest(GatewareTestCase): 54 | FRAGMENT_UNDER_TEST = Timer 55 | FRAGMENT_ARGUMENTS = {'width': 32} 56 | 57 | @sync_test_case 58 | def test_oneshot(self): 59 | dut = self.dut 60 | yield 61 | 62 | # load initial value 63 | yield dut.load_in.eq(10) 64 | yield 65 | yield from self.shouldBeLow(dut.done) 66 | yield from self.shouldBeZero(dut.counter_out) 67 | 68 | # start timer 69 | yield dut.load_in.eq(0) 70 | yield from self.pulse(dut.start, step_after=True) 71 | self.assertEqual((yield dut.counter_out), 9) 72 | yield from self.shouldBeLow(dut.done) 73 | 74 | yield from self.advance_cycles(8) 75 | self.assertEqual((yield dut.counter_out), 1) 76 | yield from self.shouldBeLow(dut.done) 77 | 78 | # counter is zero, transition to DONE 79 | yield 80 | yield from self.shouldBeHigh(dut.done) 81 | yield from self.shouldBeZero(dut.counter_out) 82 | 83 | for _ in range(5): 84 | yield 85 | yield from self.shouldBeLow(dut.done) 86 | yield from self.shouldBeZero(dut.counter_out) 87 | 88 | yield from self.shouldBeZero(dut.counter_out) 89 | 90 | @sync_test_case 91 | def test_periodic(self): 92 | dut = self.dut 93 | yield 94 | 95 | # load initial value 96 | yield dut.reload_in.eq(3) 97 | yield 98 | yield from self.shouldBeLow(dut.done) 99 | yield from self.shouldBeZero(dut.counter_out) 100 | 101 | yield 102 | yield 103 | 104 | # start timer 105 | yield dut.load_in.eq(0) 106 | yield from self.pulse(dut.start, step_after=True) 107 | self.assertEqual((yield dut.counter_out), 2) 108 | yield from self.shouldBeLow(dut.done) 109 | 110 | yield from self.advance_cycles(1) 111 | self.assertEquals((yield dut.counter_out), 1) 112 | yield from self.shouldBeLow(dut.done) 113 | 114 | # counter is zero, transition to DONE 115 | yield 116 | yield from self.shouldBeHigh(dut.done) 117 | yield from self.shouldBeZero(dut.counter_out) 118 | 119 | for _ in range(4): 120 | for _ in range(2): 121 | yield 122 | yield from self.shouldBeLow(dut.done) 123 | yield from self.shouldBeNonZero(dut.counter_out) 124 | 125 | yield 126 | yield from self.shouldBeZero(dut.counter_out) 127 | yield from self.shouldBeHigh(dut.done) 128 | 129 | class TimerConstReloadTest(GatewareTestCase): 130 | FRAGMENT_UNDER_TEST = Timer 131 | FRAGMENT_ARGUMENTS = {'width': 32, 'load': 10, 'reload': 0} 132 | 133 | @sync_test_case 134 | def test_oneshot(self): 135 | dut = self.dut 136 | # load initial value 137 | yield 138 | yield from self.shouldBeLow(dut.done) 139 | self.assertEqual((yield dut.counter_out), 10) 140 | 141 | # start timer 142 | yield from self.pulse(dut.start, step_after=True) 143 | self.assertEqual((yield dut.counter_out), 9) 144 | yield from self.shouldBeLow(dut.done) 145 | 146 | yield from self.advance_cycles(8) 147 | self.assertEqual((yield dut.counter_out), 1) 148 | yield from self.shouldBeLow(dut.done) 149 | 150 | # counter is zero, transition to DONE 151 | yield 152 | yield from self.shouldBeHigh(dut.done) 153 | yield from self.shouldBeZero(dut.counter_out) 154 | 155 | yield 156 | yield from self.shouldBeZero(dut.counter_out) 157 | 158 | for _ in range(5): 159 | yield 160 | yield from self.shouldBeLow(dut.done) 161 | self.assertEqual((yield dut.counter_out), 10) 162 | 163 | class TimerConstLoadTest(GatewareTestCase): 164 | FRAGMENT_UNDER_TEST = Timer 165 | FRAGMENT_ARGUMENTS = {'width': 32, 'load': 5} 166 | 167 | @sync_test_case 168 | def test_periodic(self): 169 | dut = self.dut 170 | yield 171 | # constant load is automatic 172 | self.assertEqual((yield dut.counter_out), 5) 173 | 174 | # load initial value 175 | yield dut.reload_in.eq(3) 176 | yield 177 | yield from self.shouldBeLow(dut.done) 178 | self.assertEqual((yield dut.counter_out), 5) 179 | yield 180 | yield 181 | 182 | # start timer 183 | yield from self.pulse(dut.start, step_after=True) 184 | self.assertEqual((yield dut.counter_out), 4) 185 | yield from self.shouldBeLow(dut.done) 186 | 187 | yield from self.advance_cycles(3) 188 | self.assertEquals((yield dut.counter_out), 1) 189 | yield from self.shouldBeLow(dut.done) 190 | 191 | # counter is zero, transition to DONE 192 | yield 193 | yield from self.shouldBeHigh(dut.done) 194 | yield from self.shouldBeZero(dut.counter_out) 195 | 196 | for _ in range(4): 197 | for _ in range(2): 198 | yield 199 | yield from self.shouldBeLow(dut.done) 200 | yield from self.shouldBeNonZero(dut.counter_out) 201 | 202 | yield 203 | yield from self.shouldBeZero(dut.counter_out) 204 | yield from self.shouldBeHigh(dut.done) -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, '/devel/HDL/adat/adat/') 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'amaranth library' 21 | copyright = '2021, Hans Baier' 22 | author = 'Hans Baier' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '0.1' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinxcontrib_hdl_diagrams', 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = [] 39 | 40 | # List of patterns, relative to source directory, that match files and 41 | # directories to ignore when looking for source files. 42 | # This pattern also affects html_static_path and html_extra_path. 43 | exclude_patterns = [] 44 | 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = 'alabaster' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | 57 | master_doc = 'amlib' 58 | '' -------------------------------------------------------------------------------- /doc/generate_doc.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import shutil 4 | 5 | from jinja2 import Environment, FileSystemLoader 6 | from sphinx.application import Sphinx 7 | from sphinx.util.docutils import docutils_namespace 8 | 9 | DOCDIR = os.path.join(os.path.abspath("."), "doc") 10 | BUILD_DIR = os.path.join(DOCDIR, "build") 11 | 12 | TEST_JINJA_DICT = { 13 | "hdl_diagrams_path": "'{}'".format(DOCDIR), 14 | "master_doc": "'amlib'", 15 | "custom_variables": "''" 16 | } 17 | 18 | sphinx_dirs = { 19 | "srcdir": DOCDIR, 20 | "confdir": DOCDIR, 21 | "outdir": BUILD_DIR, 22 | "doctreedir": os.path.join(BUILD_DIR, "doctrees") 23 | } 24 | 25 | # Run the Sphinx 26 | with docutils_namespace(): 27 | app = Sphinx(buildername="html", warningiserror=True, **sphinx_dirs) 28 | app.build(force_all=True) 29 | -------------------------------------------------------------------------------- /doc/unofficial-amaranth-library.rst: -------------------------------------------------------------------------------- 1 | amaranth core library 2 | =================== 3 | 4 | Edge To Pulse Converter 5 | ----------------------- 6 | 7 | .. hdl-diagram:: build/util/edgetopulse.v 8 | :type: netlistsvg 9 | :module: edge_to_pulse 10 | 11 | Input Shift Register 12 | -------------------- 13 | 14 | .. hdl-diagram:: build/util/shiftregister.v 15 | :type: netlistsvg 16 | :module: InputShiftRegister 17 | 18 | Output Shift Register 19 | --------------------- 20 | 21 | .. hdl-diagram:: build/util/shiftregister.v 22 | :type: netlistsvg 23 | :module: OutputShiftRegister 24 | 25 | Dividing Counter 26 | ---------------- 27 | 28 | .. hdl-diagram:: build/util/dividingcounter.v 29 | :type: netlistsvg 30 | :module: dividing_counter 31 | 32 | NRZI Encoder 33 | ------------ 34 | 35 | .. hdl-diagram:: build/util/nrziencoder.v 36 | :type: netlistsvg 37 | :module: nrzi_encoder 38 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GENERATE_VCDS=1 4 | 5 | python3 -m unittest amlib.debug.ila.IntegratedLogicAnalyzerBasicTest 6 | python3 -m unittest amlib.debug.ila.IntegratedLogicAnalyzerPretriggerTest 7 | python3 -m unittest amlib.debug.ila.StreamILATest 8 | 9 | python3 -m unittest amlib.io.spi.SPIControllerInterfaceTest 10 | python3 -m unittest amlib.io.spi.SPIDeviceInterfaceTest 11 | python3 -m unittest amlib.io.spi.SPIRegisterInterfaceTest 12 | python3 -m unittest amlib.io.i2s.I2STransmitterTest 13 | python3 -m unittest amlib.io.i2s.I2SLoopbackTest 14 | python3 -m unittest amlib.io.max7219.SerialLEDArrayTest 15 | python3 -m unittest amlib.io.led.NumberToBitBarTest 16 | 17 | python3 -m unittest amlib.dsp.fixedpointfirfilter.FixedPointFIRFilterTest 18 | python3 -m unittest amlib.dsp.fixedpointiirfilter.FixedPointIIRFilterTest 19 | python3 -m unittest amlib.dsp.fixedpointhbfilter.FixedPointHBFilterTest 20 | python3 -m unittest amlib.dsp.fixedpointcicfilter.FixedPointCICFilterTest 21 | python3 -m unittest amlib.dsp.fixedpointfft.FixedPointFFTTest 22 | python3 -m unittest amlib.dsp.resampler.ResamplerTestFIR 23 | python3 -m unittest amlib.dsp.resampler.ResamplerTestIIR 24 | 25 | python3 -m unittest amlib.stream.i2c.I2CStreamTransmitterTest 26 | python3 -m unittest amlib.stream.uart.UARTTransmitterTest 27 | python3 -m unittest amlib.stream.uart.UARTMultibyteTransmitterTest 28 | python3 -m unittest amlib.stream.generator.ConstantStreamGeneratorTest 29 | python3 -m unittest amlib.stream.generator.ConstantStreamGeneratorWideTest 30 | python3 -m unittest amlib.stream.generator.PacketListStreamerTest 31 | 32 | python3 -m unittest amlib.utils.shiftregister.InputShiftRegisterTest 33 | python3 -m unittest amlib.utils.shiftregister.OutputShiftRegisterTest 34 | python3 -m unittest amlib.utils.cdc.StrobeStretcherTest 35 | python3 -m unittest amlib.utils.dividingcounter.DividingCounterTest 36 | python3 -m unittest amlib.utils.edgetopulse.EdgeToPulseTest 37 | python3 -m unittest amlib.utils.timer.TimerTest 38 | python3 -m unittest amlib.utils.fifo.TransactionalizedFIFOTest 39 | 40 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | def scm_version(): 5 | def local_scheme(version): 6 | return version.format_choice("+{node}", "+{node}.dirty") 7 | return { 8 | "relative_to": __file__, 9 | "version_scheme": "guess-next-dev", 10 | "local_scheme": local_scheme, 11 | } 12 | 13 | setup( 14 | name="amlib", 15 | use_scm_version=scm_version(), 16 | author="Hans Baier", 17 | author_email="hansfbaier@gmail.com", 18 | description="library of utility cores for amaranth HDL", 19 | license="Apache License 2.0", 20 | setup_requires=["wheel", "setuptools", "setuptools_scm"], 21 | install_requires=[ 22 | "scipy", 23 | "amaranth>=0.2,<5", 24 | "importlib_metadata; python_version<'3.8'", 25 | "numpy", 26 | ], 27 | packages=find_packages(), 28 | project_urls={ 29 | "Source Code": "https://github.com/hansfbaier/amlib", 30 | "Bug Tracker": "https://github.com/hansfbaier/amlib/issues", 31 | }, 32 | ) 33 | 34 | -------------------------------------------------------------------------------- /test/README.txt: -------------------------------------------------------------------------------- 1 | This directory contains manually created visually pleasing 2 | GTKWave save files for some of the unit tests. 3 | 4 | To run the tests, use run-tests.sh from the root toplevel 5 | director of this project. 6 | 7 | then run GTKWave of the test you are interested in, eg: 8 | gtkwave test/dividing-counter.gtkw -------------------------------------------------------------------------------- /test/dividing-counter.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Thu Aug 19 03:44:29 2021 4 | [*] 5 | [dumpfile] "test_DividingCounterTest_test_basic.vcd" 6 | [dumpfile_mtime] "Thu Aug 19 03:26:56 2021" 7 | [dumpfile_size] 3113 8 | [savefile] "test/dividing-counter.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-15.888720 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [sst_width] 512 14 | [signals_width] 462 15 | [sst_expanded] 1 16 | [sst_vpaned_height] 654 17 | @28 18 | top.clk 19 | top.active_in 20 | @29 21 | [color] 2 22 | top.reset_in 23 | @24 24 | [color] 3 25 | top.counter_out[4:0] 26 | [color] 2 27 | top.dividing_cycle_counter[2:0] 28 | @28 29 | [color] 1 30 | top.dividable_out 31 | @22 32 | [color] 6 33 | top.divided_counter_out[4:0] 34 | [pattern_trace] 1 35 | [pattern_trace] 0 36 | -------------------------------------------------------------------------------- /test/edgetopulse.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Thu Aug 19 02:59:32 2021 4 | [*] 5 | [dumpfile] "test_EdgeToPulseTest_test_basic.vcd" 6 | [dumpfile_mtime] "Thu Aug 19 02:57:34 2021" 7 | [dumpfile_size] 960 8 | [savefile] "test/edgetopulse.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-15.100000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [sst_width] 444 14 | [signals_width] 270 15 | [sst_expanded] 1 16 | [sst_vpaned_height] 654 17 | @28 18 | top.clk 19 | @29 20 | [color] 3 21 | top.edge_last 22 | @28 23 | [color] 3 24 | top.edge_in 25 | [color] 1 26 | top.pulse_out 27 | [pattern_trace] 1 28 | [pattern_trace] 0 29 | -------------------------------------------------------------------------------- /test/fixedpointfirfilter.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Wed Jun 30 21:16:09 2021 4 | [*] 5 | [dumpfile] "test_FixedPointFIRFilterTest_test_fir.vcd" 6 | [dumpfile_mtime] "Sat Jun 26 04:17:37 2021" 7 | [dumpfile_size] 35850 8 | [savefile] "test/fixedpointfirfilter.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-18.784399 1509900 1509900 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [sst_width] 357 15 | [signals_width] 487 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 628 18 | @28 19 | top.clk 20 | top.enable_in 21 | @8420 22 | top.signal_in[17:0] 23 | @20000 24 | - 25 | - 26 | - 27 | - 28 | - 29 | @c08421 30 | top.signal_out[17:0] 31 | @28 32 | (0)top.signal_out[17:0] 33 | (1)top.signal_out[17:0] 34 | (2)top.signal_out[17:0] 35 | (3)top.signal_out[17:0] 36 | (4)top.signal_out[17:0] 37 | (5)top.signal_out[17:0] 38 | (6)top.signal_out[17:0] 39 | (7)top.signal_out[17:0] 40 | (8)top.signal_out[17:0] 41 | (9)top.signal_out[17:0] 42 | (10)top.signal_out[17:0] 43 | (11)top.signal_out[17:0] 44 | (12)top.signal_out[17:0] 45 | (13)top.signal_out[17:0] 46 | (14)top.signal_out[17:0] 47 | (15)top.signal_out[17:0] 48 | (16)top.signal_out[17:0] 49 | (17)top.signal_out[17:0] 50 | @1401201 51 | -group_end 52 | @20000 53 | - 54 | - 55 | - 56 | - 57 | - 58 | - 59 | [pattern_trace] 1 60 | [pattern_trace] 0 61 | -------------------------------------------------------------------------------- /test/fixedpointiirfilter.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Wed Jun 30 21:16:09 2021 4 | [*] 5 | [dumpfile] "test_FixedPointIIRFilterTest_test_iir.vcd" 6 | [dumpfile_mtime] "Sat Jun 26 04:17:37 2021" 7 | [dumpfile_size] 35850 8 | [savefile] "test/fixedpointiirfilter.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-18.784399 1509900 1509900 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [sst_width] 357 15 | [signals_width] 487 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 628 18 | @28 19 | top.clk 20 | top.enable_in 21 | @8420 22 | top.signal_in[17:0] 23 | @20000 24 | - 25 | - 26 | - 27 | - 28 | - 29 | @c08421 30 | top.signal_out[17:0] 31 | @28 32 | (0)top.signal_out[17:0] 33 | (1)top.signal_out[17:0] 34 | (2)top.signal_out[17:0] 35 | (3)top.signal_out[17:0] 36 | (4)top.signal_out[17:0] 37 | (5)top.signal_out[17:0] 38 | (6)top.signal_out[17:0] 39 | (7)top.signal_out[17:0] 40 | (8)top.signal_out[17:0] 41 | (9)top.signal_out[17:0] 42 | (10)top.signal_out[17:0] 43 | (11)top.signal_out[17:0] 44 | (12)top.signal_out[17:0] 45 | (13)top.signal_out[17:0] 46 | (14)top.signal_out[17:0] 47 | (15)top.signal_out[17:0] 48 | (16)top.signal_out[17:0] 49 | (17)top.signal_out[17:0] 50 | @1401201 51 | -group_end 52 | @20000 53 | - 54 | - 55 | - 56 | - 57 | - 58 | - 59 | [pattern_trace] 1 60 | [pattern_trace] 0 61 | -------------------------------------------------------------------------------- /test/i2c_stream_transmitter-bench.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Tue Aug 17 04:33:47 2021 4 | [*] 5 | [dumpfile] "test_I2CStreamTransmitterTest_test_basic.vcd" 6 | [dumpfile_mtime] "Tue Aug 17 04:33:24 2021" 7 | [dumpfile_size] 25235 8 | [savefile] "test/i2c_stream_transmitter-bench.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-18.000000 110100 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [treeopen] top.i2c. 15 | [treeopen] top.i2c.bus. 16 | [treeopen] top.input_fifo. 17 | [sst_width] 357 18 | [signals_width] 322 19 | [sst_expanded] 1 20 | [sst_vpaned_height] 658 21 | @28 22 | top.clk 23 | top.ready 24 | top.valid 25 | top.first 26 | top.last 27 | @22 28 | top.payload[7:0] 29 | @200 30 | -FIFO 31 | @22 32 | top.w_data[9:0] 33 | @28 34 | top.w_en 35 | top.w_rdy 36 | top.r_rdy 37 | @c00022 38 | top.r_data[9:0] 39 | @28 40 | (0)top.r_data[9:0] 41 | (1)top.r_data[9:0] 42 | (2)top.r_data[9:0] 43 | (3)top.r_data[9:0] 44 | (4)top.r_data[9:0] 45 | (5)top.r_data[9:0] 46 | (6)top.r_data[9:0] 47 | (7)top.r_data[9:0] 48 | (8)top.r_data[9:0] 49 | (9)top.r_data[9:0] 50 | @1401200 51 | -group_end 52 | @28 53 | top.r_en 54 | @200 55 | -FSM 56 | @28 57 | top.fsm_state 58 | top.start 59 | top.stop 60 | top.write 61 | @200 62 | -I2C 63 | @22 64 | top.data_i[7:0] 65 | @28 66 | top.i2c.fsm.state 67 | [color] 2 68 | top.busy 69 | @200 70 | - 71 | @28 72 | [color] 2 73 | top.i2c.bus.sda_o 74 | [color] 2 75 | top.i2c.scl_o 76 | [pattern_trace] 1 77 | [pattern_trace] 0 78 | -------------------------------------------------------------------------------- /test/i2s-loopback.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Thu Aug 26 23:33:57 2021 4 | [*] 5 | [dumpfile] "test_I2SLoopbackTest_test_basic.vcd" 6 | [dumpfile_mtime] "Thu Aug 26 23:32:01 2021" 7 | [dumpfile_size] 687954 8 | [savefile] "test/i2s-loopback.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-12.054144 0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [treeopen] top.receiver. 15 | [sst_width] 357 16 | [signals_width] 315 17 | [sst_expanded] 1 18 | [sst_vpaned_height] 642 19 | @28 20 | top.clk 21 | top.transmitter.valid 22 | top.transmitter.ready 23 | @c00022 24 | top.transmitter.payload[23:0] 25 | @28 26 | (0)top.transmitter.payload[23:0] 27 | (1)top.transmitter.payload[23:0] 28 | (2)top.transmitter.payload[23:0] 29 | (3)top.transmitter.payload[23:0] 30 | (4)top.transmitter.payload[23:0] 31 | (5)top.transmitter.payload[23:0] 32 | (6)top.transmitter.payload[23:0] 33 | (7)top.transmitter.payload[23:0] 34 | (8)top.transmitter.payload[23:0] 35 | (9)top.transmitter.payload[23:0] 36 | (10)top.transmitter.payload[23:0] 37 | (11)top.transmitter.payload[23:0] 38 | (12)top.transmitter.payload[23:0] 39 | (13)top.transmitter.payload[23:0] 40 | (14)top.transmitter.payload[23:0] 41 | (15)top.transmitter.payload[23:0] 42 | (16)top.transmitter.payload[23:0] 43 | (17)top.transmitter.payload[23:0] 44 | (18)top.transmitter.payload[23:0] 45 | (19)top.transmitter.payload[23:0] 46 | (20)top.transmitter.payload[23:0] 47 | (21)top.transmitter.payload[23:0] 48 | (22)top.transmitter.payload[23:0] 49 | (23)top.transmitter.payload[23:0] 50 | @1401200 51 | -group_end 52 | @28 53 | top.first 54 | @200 55 | -Transmitter 56 | @28 57 | top.transmitter.underflow_out 58 | @22 59 | top.transmitter.fifo_level_out[4:0] 60 | @200 61 | -I2S 62 | @28 63 | [color] 3 64 | top.bit_clock 65 | [color] 3 66 | top.word_clock 67 | [color] 3 68 | top.receiver.serial_data_in 69 | @200 70 | -Receiver 71 | @28 72 | top.receiver.bit_clock_rose 73 | top.receiver.rx_delay_cnt 74 | @24 75 | top.receiver.rx_cnt[4:0] 76 | @28 77 | top.receiver.fsm_state 78 | @22 79 | top.receiver.level[4:0] 80 | @28 81 | top.receiver.w_en 82 | @c00022 83 | top.receiver.w_data[24:0] 84 | @28 85 | (0)top.receiver.w_data[24:0] 86 | (1)top.receiver.w_data[24:0] 87 | (2)top.receiver.w_data[24:0] 88 | (3)top.receiver.w_data[24:0] 89 | (4)top.receiver.w_data[24:0] 90 | (5)top.receiver.w_data[24:0] 91 | (6)top.receiver.w_data[24:0] 92 | (7)top.receiver.w_data[24:0] 93 | (8)top.receiver.w_data[24:0] 94 | (9)top.receiver.w_data[24:0] 95 | (10)top.receiver.w_data[24:0] 96 | (11)top.receiver.w_data[24:0] 97 | (12)top.receiver.w_data[24:0] 98 | (13)top.receiver.w_data[24:0] 99 | (14)top.receiver.w_data[24:0] 100 | (15)top.receiver.w_data[24:0] 101 | (16)top.receiver.w_data[24:0] 102 | (17)top.receiver.w_data[24:0] 103 | (18)top.receiver.w_data[24:0] 104 | (19)top.receiver.w_data[24:0] 105 | (20)top.receiver.w_data[24:0] 106 | (21)top.receiver.w_data[24:0] 107 | (22)top.receiver.w_data[24:0] 108 | (23)top.receiver.w_data[24:0] 109 | (24)top.receiver.w_data[24:0] 110 | @1401200 111 | -group_end 112 | @22 113 | top.receiver.r_data[24:0] 114 | @28 115 | top.receiver.r_rdy 116 | @22 117 | top.receiver.r_data[24:0] 118 | @200 119 | -Output 120 | @22 121 | [color] 2 122 | top.receiver.fifo_level_out[4:0] 123 | [color] 2 124 | top.receiver.payload[23:0] 125 | @29 126 | [color] 2 127 | top.receiver.valid 128 | @28 129 | [color] 2 130 | top.receiver.first 131 | [color] 2 132 | top.receiver.last 133 | [pattern_trace] 1 134 | [pattern_trace] 0 135 | -------------------------------------------------------------------------------- /test/i2s-transmitter.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Thu Aug 26 22:39:18 2021 4 | [*] 5 | [dumpfile] "test_I2STransmitterTest_test_basic.vcd" 6 | [dumpfile_mtime] "Thu Aug 26 22:38:57 2021" 7 | [dumpfile_size] 117240 8 | [savefile] "test/i2s-transmitter.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-21.152037 2129100 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [sst_width] 357 15 | [signals_width] 270 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 658 18 | @28 19 | top.clk 20 | @22 21 | top.payload[23:0] 22 | @28 23 | top.valid 24 | top.ready 25 | top.first 26 | @22 27 | top.tx_fifo.level[4:0] 28 | @c00022 29 | top.tx_fifo.r_data[24:0] 30 | @28 31 | (0)top.tx_fifo.r_data[24:0] 32 | (1)top.tx_fifo.r_data[24:0] 33 | (2)top.tx_fifo.r_data[24:0] 34 | (3)top.tx_fifo.r_data[24:0] 35 | (4)top.tx_fifo.r_data[24:0] 36 | (5)top.tx_fifo.r_data[24:0] 37 | (6)top.tx_fifo.r_data[24:0] 38 | (7)top.tx_fifo.r_data[24:0] 39 | (8)top.tx_fifo.r_data[24:0] 40 | (9)top.tx_fifo.r_data[24:0] 41 | (10)top.tx_fifo.r_data[24:0] 42 | (11)top.tx_fifo.r_data[24:0] 43 | (12)top.tx_fifo.r_data[24:0] 44 | (13)top.tx_fifo.r_data[24:0] 45 | (14)top.tx_fifo.r_data[24:0] 46 | (15)top.tx_fifo.r_data[24:0] 47 | (16)top.tx_fifo.r_data[24:0] 48 | (17)top.tx_fifo.r_data[24:0] 49 | (18)top.tx_fifo.r_data[24:0] 50 | (19)top.tx_fifo.r_data[24:0] 51 | (20)top.tx_fifo.r_data[24:0] 52 | (21)top.tx_fifo.r_data[24:0] 53 | (22)top.tx_fifo.r_data[24:0] 54 | (23)top.tx_fifo.r_data[24:0] 55 | (24)top.tx_fifo.r_data[24:0] 56 | @1401200 57 | -group_end 58 | @28 59 | top.first_flag 60 | top.r_en 61 | top.fsm_state 62 | (0)top.tx_shifter[23:0] 63 | [color] 2 64 | top.bit_clock 65 | [color] 2 66 | top.word_clock 67 | [color] 2 68 | top.serial_data_out 69 | [color] 2 70 | top.mismatch_out 71 | [color] 2 72 | top.underflow_out 73 | [pattern_trace] 1 74 | [pattern_trace] 0 75 | -------------------------------------------------------------------------------- /test/ila-basic.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.111 (w)1999-2020 BSI 3 | [*] Tue Nov 16 18:21:29 2021 4 | [*] 5 | [dumpfile] "test_IntegratedLogicAnalyzerBasicTest_test_sampling.vcd" 6 | [dumpfile_mtime] "Tue Nov 16 18:14:59 2021" 7 | [dumpfile_size] 18452 8 | [savefile] "test/ila_basic.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-17.000000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [sst_width] 575 14 | [signals_width] 375 15 | [sst_expanded] 1 16 | [sst_vpaned_height] 658 17 | @28 18 | top.clk 19 | @200 20 | -Inputs 21 | @800022 22 | [color] 3 23 | #{Input} top.input_a (0)top.input_b[29:0] (1)top.input_b[29:0] (2)top.input_b[29:0] (3)top.input_b[29:0] (4)top.input_b[29:0] (5)top.input_b[29:0] (6)top.input_b[29:0] (7)top.input_b[29:0] (8)top.input_b[29:0] (9)top.input_b[29:0] (10)top.input_b[29:0] (11)top.input_b[29:0] (12)top.input_b[29:0] (13)top.input_b[29:0] (14)top.input_b[29:0] (15)top.input_b[29:0] (16)top.input_b[29:0] (17)top.input_b[29:0] (18)top.input_b[29:0] (19)top.input_b[29:0] (20)top.input_b[29:0] (21)top.input_b[29:0] (22)top.input_b[29:0] (23)top.input_b[29:0] (24)top.input_b[29:0] (25)top.input_b[29:0] (26)top.input_b[29:0] (27)top.input_b[29:0] (28)top.input_b[29:0] (29)top.input_b[29:0] top.input_c 24 | @1001200 25 | -group_end 26 | @28 27 | [color] 3 28 | top.enable 29 | [color] 3 30 | top.trigger 31 | @200 32 | -ILA 33 | @28 34 | top.ila_fsm_state 35 | @22 36 | top.delayed_inputs[31:0] 37 | @28 38 | top.delayed_enable 39 | @22 40 | top.ila_buffer_w_addr[4:0] 41 | top.ila_buffer_w_data[31:0] 42 | @200 43 | -Outputs 44 | @22 45 | [color] 3 46 | top.ila_buffer_r_addr[4:0] 47 | [color] 2 48 | top.ila_buffer_r_data[31:0] 49 | @28 50 | [color] 2 51 | top.capturing 52 | [color] 2 53 | top.sampling 54 | [pattern_trace] 1 55 | [pattern_trace] 0 56 | -------------------------------------------------------------------------------- /test/ila-pretrigger.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.111 (w)1999-2020 BSI 3 | [*] Mon Nov 22 22:39:33 2021 4 | [*] 5 | [dumpfile] "test_IntegratedLogicAnalyzerPretriggerTest_test_sampling.vcd" 6 | [dumpfile_mtime] "Mon Nov 22 22:36:44 2021" 7 | [dumpfile_size] 42905 8 | [savefile] "test/ila-pretrigger.gtkw" 9 | [timestart] 82280 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-14.000000 645090 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [sst_width] 575 15 | [signals_width] 454 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 972 18 | @28 19 | top.clk 20 | @200 21 | -Inputs 22 | @28 23 | [color] 3 24 | top.trigger 25 | @c00022 26 | [color] 3 27 | #{Input} top.pretrigger_samples.input_c (0)top.pretrigger_samples.input_b[29:0] (1)top.pretrigger_samples.input_b[29:0] (2)top.pretrigger_samples.input_b[29:0] (3)top.pretrigger_samples.input_b[29:0] (4)top.pretrigger_samples.input_b[29:0] (5)top.pretrigger_samples.input_b[29:0] (6)top.pretrigger_samples.input_b[29:0] (7)top.pretrigger_samples.input_b[29:0] (8)top.pretrigger_samples.input_b[29:0] (9)top.pretrigger_samples.input_b[29:0] (10)top.pretrigger_samples.input_b[29:0] (11)top.pretrigger_samples.input_b[29:0] (12)top.pretrigger_samples.input_b[29:0] (13)top.pretrigger_samples.input_b[29:0] (14)top.pretrigger_samples.input_b[29:0] (15)top.pretrigger_samples.input_b[29:0] (16)top.pretrigger_samples.input_b[29:0] (17)top.pretrigger_samples.input_b[29:0] (18)top.pretrigger_samples.input_b[29:0] (19)top.pretrigger_samples.input_b[29:0] (20)top.pretrigger_samples.input_b[29:0] (21)top.pretrigger_samples.input_b[29:0] (22)top.pretrigger_samples.input_b[29:0] (23)top.pretrigger_samples.input_b[29:0] (24)top.pretrigger_samples.input_b[29:0] (25)top.pretrigger_samples.input_b[29:0] (26)top.pretrigger_samples.input_b[29:0] (27)top.pretrigger_samples.input_b[29:0] (28)top.pretrigger_samples.input_b[29:0] (29)top.pretrigger_samples.input_b[29:0] top.pretrigger_samples.input_a 28 | @28 29 | top.pretrigger_samples.input_c 30 | @22 31 | top.pretrigger_samples.input_b[29:0] 32 | @28 33 | top.pretrigger_samples.input_a 34 | @1401200 35 | -group_end 36 | @29 37 | [color] 3 38 | top.pretrigger_enable.enable 39 | @200 40 | -ILA 41 | @28 42 | top.ila_fsm_state 43 | @22 44 | top.synced_inputs[31:0] 45 | @28 46 | top.synced_enable 47 | @200 48 | -Pretrigger FIFO 49 | @22 50 | top.pretrigger_fill_counter[3:0] 51 | @28 52 | top.pretrigger_filled 53 | top.pretrigger_fifo.w_en 54 | @22 55 | top.pretrigger_fifo.w_data[32:0] 56 | top.pretrigger_fifo.w_level[3:0] 57 | top.pretrigger_fifo.r_data[32:0] 58 | @28 59 | top.pretrigger_fifo.r_en 60 | top.delayed_enable 61 | @22 62 | top.delayed_inputs[31:0] 63 | top.ila_buffer_w_addr[4:0] 64 | top.ila_buffer_w_data[31:0] 65 | @200 66 | -Outputs 67 | @22 68 | [color] 3 69 | top.ila_buffer_r_addr[4:0] 70 | [color] 2 71 | top.ila_buffer_r_data[31:0] 72 | @28 73 | [color] 2 74 | top.capturing 75 | [color] 2 76 | top.sampling 77 | [pattern_trace] 1 78 | [pattern_trace] 0 79 | -------------------------------------------------------------------------------- /test/number-to-bitbar.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI 3 | [*] Thu Dec 30 09:04:26 2021 4 | [*] 5 | [dumpfile] "test_NumberToBitBarTest_test_byte_range.vcd" 6 | [dumpfile_mtime] "Thu Dec 30 09:00:20 2021" 7 | [dumpfile_size] 9564 8 | [savefile] "test/number-to-bitbar.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-18.426188 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] bench. 14 | [sst_width] 347 15 | [signals_width] 255 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 658 18 | @28 19 | bench.top.clk 20 | @8022 21 | bench.top.value_in[7:0] 22 | @29 23 | bench.top.bitbar_out[7:0] 24 | [pattern_trace] 1 25 | [pattern_trace] 0 26 | -------------------------------------------------------------------------------- /test/resampler-bench-fir.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Fri Jul 2 04:01:09 2021 4 | [*] 5 | [dumpfile] "test_ResamplerTestFIR_test_fir.vcd" 6 | [dumpfile_mtime] "Fri Jul 2 04:00:45 2021" 7 | [dumpfile_size] 158254 8 | [savefile] "test/resampler-bench-fir.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-18.574320 1344000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [treeopen] top.antialiasingfilter. 15 | [sst_width] 357 16 | [signals_width] 489 17 | [sst_expanded] 1 18 | [sst_vpaned_height] 650 19 | @28 20 | top.clk 21 | @420 22 | top.input_data[15:0] 23 | @28 24 | top.input_valid 25 | top.input_ready 26 | @200 27 | -Filterbank 28 | @28 29 | top.enable_in 30 | @8420 31 | top.antialiasingfilter.signal_in[17:0] 32 | @20000 33 | - 34 | - 35 | - 36 | @c18420 37 | top.antialiasingfilter.signal_out[17:0] 38 | @28 39 | (0)top.antialiasingfilter.signal_out[17:0] 40 | (1)top.antialiasingfilter.signal_out[17:0] 41 | (2)top.antialiasingfilter.signal_out[17:0] 42 | (3)top.antialiasingfilter.signal_out[17:0] 43 | (4)top.antialiasingfilter.signal_out[17:0] 44 | (5)top.antialiasingfilter.signal_out[17:0] 45 | (6)top.antialiasingfilter.signal_out[17:0] 46 | (7)top.antialiasingfilter.signal_out[17:0] 47 | (8)top.antialiasingfilter.signal_out[17:0] 48 | (9)top.antialiasingfilter.signal_out[17:0] 49 | (10)top.antialiasingfilter.signal_out[17:0] 50 | (11)top.antialiasingfilter.signal_out[17:0] 51 | (12)top.antialiasingfilter.signal_out[17:0] 52 | (13)top.antialiasingfilter.signal_out[17:0] 53 | (14)top.antialiasingfilter.signal_out[17:0] 54 | (15)top.antialiasingfilter.signal_out[17:0] 55 | (16)top.antialiasingfilter.signal_out[17:0] 56 | (17)top.antialiasingfilter.signal_out[17:0] 57 | @1401200 58 | -group_end 59 | @20000 60 | - 61 | - 62 | - 63 | @c08421 64 | top.payload$1[15:0] 65 | @29 66 | (0)top.payload$1[15:0] 67 | (1)top.payload$1[15:0] 68 | (2)top.payload$1[15:0] 69 | (3)top.payload$1[15:0] 70 | (4)top.payload$1[15:0] 71 | (5)top.payload$1[15:0] 72 | (6)top.payload$1[15:0] 73 | (7)top.payload$1[15:0] 74 | (8)top.payload$1[15:0] 75 | (9)top.payload$1[15:0] 76 | (10)top.payload$1[15:0] 77 | (11)top.payload$1[15:0] 78 | (12)top.payload$1[15:0] 79 | (13)top.payload$1[15:0] 80 | (14)top.payload$1[15:0] 81 | (15)top.payload$1[15:0] 82 | @1401201 83 | -group_end 84 | @20000 85 | - 86 | - 87 | - 88 | @28 89 | top.valid$1 90 | top.ready$1 91 | top.ready 92 | [pattern_trace] 1 93 | [pattern_trace] 0 94 | -------------------------------------------------------------------------------- /test/resampler-bench-iir.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Wed Jun 30 22:28:35 2021 4 | [*] 5 | [dumpfile] "test_ResamplerTestIIR_test_iir.vcd" 6 | [dumpfile_mtime] "Mon Jun 28 06:01:28 2021" 7 | [dumpfile_size] 379711 8 | [savefile] "test/resampler-bench-iir.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-18.574320 1174900 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [treeopen] top.antialiasingfilter. 15 | [sst_width] 357 16 | [signals_width] 489 17 | [sst_expanded] 1 18 | [sst_vpaned_height] 650 19 | @28 20 | top.clk 21 | @420 22 | top.input_data[15:0] 23 | @28 24 | top.input_valid 25 | top.input_ready 26 | @200 27 | -Filterbank 28 | @28 29 | top.enable_in 30 | @8420 31 | top.antialiasingfilter.signal_in[17:0] 32 | @20000 33 | - 34 | - 35 | - 36 | @c18420 37 | top.antialiasingfilter.signal_out[17:0] 38 | @28 39 | (0)top.antialiasingfilter.signal_out[17:0] 40 | (1)top.antialiasingfilter.signal_out[17:0] 41 | (2)top.antialiasingfilter.signal_out[17:0] 42 | (3)top.antialiasingfilter.signal_out[17:0] 43 | (4)top.antialiasingfilter.signal_out[17:0] 44 | (5)top.antialiasingfilter.signal_out[17:0] 45 | (6)top.antialiasingfilter.signal_out[17:0] 46 | (7)top.antialiasingfilter.signal_out[17:0] 47 | (8)top.antialiasingfilter.signal_out[17:0] 48 | (9)top.antialiasingfilter.signal_out[17:0] 49 | (10)top.antialiasingfilter.signal_out[17:0] 50 | (11)top.antialiasingfilter.signal_out[17:0] 51 | (12)top.antialiasingfilter.signal_out[17:0] 52 | (13)top.antialiasingfilter.signal_out[17:0] 53 | (14)top.antialiasingfilter.signal_out[17:0] 54 | (15)top.antialiasingfilter.signal_out[17:0] 55 | (16)top.antialiasingfilter.signal_out[17:0] 56 | (17)top.antialiasingfilter.signal_out[17:0] 57 | @1401200 58 | -group_end 59 | @20000 60 | - 61 | - 62 | - 63 | @c08421 64 | top.payload$1[15:0] 65 | @28 66 | (0)top.payload$1[15:0] 67 | (1)top.payload$1[15:0] 68 | (2)top.payload$1[15:0] 69 | (3)top.payload$1[15:0] 70 | (4)top.payload$1[15:0] 71 | (5)top.payload$1[15:0] 72 | (6)top.payload$1[15:0] 73 | (7)top.payload$1[15:0] 74 | (8)top.payload$1[15:0] 75 | (9)top.payload$1[15:0] 76 | (10)top.payload$1[15:0] 77 | (11)top.payload$1[15:0] 78 | (12)top.payload$1[15:0] 79 | (13)top.payload$1[15:0] 80 | (14)top.payload$1[15:0] 81 | (15)top.payload$1[15:0] 82 | @1401201 83 | -group_end 84 | @20000 85 | - 86 | - 87 | - 88 | @28 89 | top.valid$1 90 | top.ready$1 91 | top.ready 92 | [pattern_trace] 1 93 | [pattern_trace] 0 94 | -------------------------------------------------------------------------------- /test/serial-led-array.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.111 (w)1999-2020 BSI 3 | [*] Mon Oct 18 20:41:51 2021 4 | [*] 5 | [dumpfile] "test_SerialLEDArrayTest_test_spi_interface.vcd" 6 | [dumpfile_mtime] "Mon Oct 18 20:41:30 2021" 7 | [dumpfile_size] 338477 8 | [savefile] "test/serial-led-array.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-21.863543 13190300 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [treeopen] top.spi_controller. 15 | [sst_width] 505 16 | [signals_width] 315 17 | [sst_expanded] 1 18 | [sst_vpaned_height] 658 19 | @28 20 | top.clk 21 | @200 22 | -Init Delay 23 | @28 24 | top.init_delay.start 25 | top.init_delay.done 26 | @200 27 | -SPI 28 | @28 29 | top.spi_controller__cs 30 | top.spi_controller__sck 31 | top.spi_controller__sdi 32 | top.spi_controller__sdo 33 | top.start_transfer 34 | @c00022 35 | top.word_out[31:0] 36 | @28 37 | (0)top.word_out[31:0] 38 | (1)top.word_out[31:0] 39 | (2)top.word_out[31:0] 40 | (3)top.word_out[31:0] 41 | (4)top.word_out[31:0] 42 | (5)top.word_out[31:0] 43 | (6)top.word_out[31:0] 44 | (7)top.word_out[31:0] 45 | (8)top.word_out[31:0] 46 | (9)top.word_out[31:0] 47 | (10)top.word_out[31:0] 48 | (11)top.word_out[31:0] 49 | (12)top.word_out[31:0] 50 | (13)top.word_out[31:0] 51 | (14)top.word_out[31:0] 52 | (15)top.word_out[31:0] 53 | (16)top.word_out[31:0] 54 | (17)top.word_out[31:0] 55 | (18)top.word_out[31:0] 56 | (19)top.word_out[31:0] 57 | (20)top.word_out[31:0] 58 | (21)top.word_out[31:0] 59 | (22)top.word_out[31:0] 60 | (23)top.word_out[31:0] 61 | (24)top.word_out[31:0] 62 | (25)top.word_out[31:0] 63 | (26)top.word_out[31:0] 64 | (27)top.word_out[31:0] 65 | (28)top.word_out[31:0] 66 | (29)top.word_out[31:0] 67 | (30)top.word_out[31:0] 68 | (31)top.word_out[31:0] 69 | @1401200 70 | -group_end 71 | @200 72 | -MAX7219 73 | @28 74 | top.max7219_state 75 | @22 76 | top.step_counter[3:0] 77 | @28 78 | top.next_step 79 | @22 80 | top.digit_counter[7:0] 81 | @28 82 | top.next_digit 83 | top.valid_in 84 | @800200 85 | -digits 86 | @22 87 | top.digit0[7:0] 88 | top.digit1[7:0] 89 | top.digit2[7:0] 90 | top.digit3[7:0] 91 | top.digit4[7:0] 92 | top.digit5[7:0] 93 | top.digit6[7:0] 94 | top.digit7[7:0] 95 | top.digit8[7:0] 96 | top.digit9[7:0] 97 | top.digit10[7:0] 98 | top.digit11[7:0] 99 | top.digit12[7:0] 100 | top.digit13[7:0] 101 | top.digit14[7:0] 102 | top.digit15[7:0] 103 | @1000200 104 | -digits 105 | [pattern_trace] 1 106 | [pattern_trace] 0 107 | -------------------------------------------------------------------------------- /test/shift-register-in.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Thu Aug 19 03:54:55 2021 4 | [*] 5 | [dumpfile] "test_InputShiftRegisterTest_test_basic.vcd" 6 | [dumpfile_mtime] "Thu Aug 19 03:53:11 2021" 7 | [dumpfile_size] 2437 8 | [savefile] "test/shift-register-in.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-14.478533 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [sst_width] 444 14 | [signals_width] 270 15 | [sst_expanded] 1 16 | [sst_vpaned_height] 654 17 | @28 18 | top.rst 19 | top.clk 20 | [color] 1 21 | top.enable_in 22 | [color] 3 23 | top.bit_in 24 | @29 25 | [color] 1 26 | top.value_out[7:0] 27 | [pattern_trace] 1 28 | [pattern_trace] 0 29 | -------------------------------------------------------------------------------- /test/shift-register-out.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Thu Aug 19 04:11:50 2021 4 | [*] 5 | [dumpfile] "test_OutputShiftRegisterTest_test_basic.vcd" 6 | [dumpfile_mtime] "Thu Aug 19 04:08:49 2021" 7 | [dumpfile_size] 1367 8 | [savefile] "test/shift-register-out.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-14.492319 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [sst_width] 427 14 | [signals_width] 225 15 | [sst_expanded] 1 16 | [sst_vpaned_height] 644 17 | @28 18 | top.clk 19 | [color] 3 20 | top.enable_in 21 | [color] 3 22 | top.we_in 23 | @22 24 | top.value_in[7:0] 25 | @800022 26 | top.value[7:0] 27 | @28 28 | (0)top.value[7:0] 29 | (1)top.value[7:0] 30 | (2)top.value[7:0] 31 | (3)top.value[7:0] 32 | (4)top.value[7:0] 33 | (5)top.value[7:0] 34 | (6)top.value[7:0] 35 | (7)top.value[7:0] 36 | @1001200 37 | -group_end 38 | @29 39 | [color] 2 40 | top.bit_out 41 | [pattern_trace] 1 42 | [pattern_trace] 0 43 | -------------------------------------------------------------------------------- /test/spi-controller.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.111 (w)1999-2020 BSI 3 | [*] Sat Oct 16 06:34:36 2021 4 | [*] 5 | [dumpfile] "test_SPIControllerInterfaceTest_test_spi_interface.vcd" 6 | [dumpfile_mtime] "Sat Oct 16 06:34:31 2021" 7 | [dumpfile_size] 9300 8 | [savefile] "test/spi-controller.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-18.000000 114600 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [sst_width] 357 15 | [signals_width] 315 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 658 18 | @28 19 | top.clk 20 | top.fsm_state 21 | top.spi_controller__cs 22 | top.spi_controller__sck 23 | top.spi_controller__sdi 24 | top.spi_controller__sdo 25 | top.start_transfer 26 | top.word_accepted 27 | @22 28 | top.word_out[15:0] 29 | top.word_in[15:0] 30 | [pattern_trace] 1 31 | [pattern_trace] 0 32 | -------------------------------------------------------------------------------- /test/timer-oneshot.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Fri Aug 20 22:45:24 2021 4 | [*] 5 | [dumpfile] "test_TimerTest_test_oneshot.vcd" 6 | [dumpfile_mtime] "Fri Aug 20 22:44:09 2021" 7 | [dumpfile_size] 893 8 | [savefile] "test/timer-oneshot.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-14.000000 60940 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [sst_width] 357 15 | [signals_width] 285 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 658 18 | @28 19 | top.clk 20 | @22 21 | top.load_in[31:0] 22 | top.reload_in[31:0] 23 | @29 24 | [color] 2 25 | top.fsm_state 26 | @22 27 | [color] 2 28 | top.counter_out[31:0] 29 | @28 30 | top.start 31 | top.done 32 | [pattern_trace] 1 33 | [pattern_trace] 0 34 | -------------------------------------------------------------------------------- /test/timer-periodic.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.109 (w)1999-2020 BSI 3 | [*] Fri Aug 20 22:45:24 2021 4 | [*] 5 | [dumpfile] "test_TimerTest_test_periodic.vcd" 6 | [dumpfile_mtime] "Fri Aug 20 22:44:09 2021" 7 | [dumpfile_size] 893 8 | [savefile] "test/timer-periodic.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-14.000000 60940 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [sst_width] 357 15 | [signals_width] 285 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 658 18 | @28 19 | top.clk 20 | @22 21 | top.load_in[31:0] 22 | top.reload_in[31:0] 23 | @29 24 | [color] 2 25 | top.fsm_state 26 | @22 27 | [color] 2 28 | top.counter_out[31:0] 29 | @28 30 | top.start 31 | top.done 32 | [pattern_trace] 1 33 | [pattern_trace] 0 34 | -------------------------------------------------------------------------------- /test/ws2812.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.111 (w)1999-2020 BSI 3 | [*] Wed Nov 3 23:03:55 2021 4 | [*] 5 | [dumpfile] "test_WS2812Test_test_spi_interface.vcd" 6 | [dumpfile_mtime] "Wed Nov 3 23:02:26 2021" 7 | [dumpfile_size] 30476 8 | [savefile] "test/ws2812.gtkw" 9 | [timestart] 0 10 | [size] 3828 2090 11 | [pos] -1 -1 12 | *-19.000000 2914900 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [sst_width] 463 15 | [signals_width] 405 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 650 18 | @200 19 | -Inputs 20 | @22 21 | top.red_in[7:0] 22 | top.green_in[7:0] 23 | top.blue_in[7:0] 24 | @23 25 | top.led_address_in[1:0] 26 | @28 27 | top.write_enable_in 28 | top.start_in 29 | @200 30 | -FSM 31 | @28 32 | top.fsm_state 33 | @22 34 | top.grb[23:0] 35 | @24 36 | top.led_counter[2:0] 37 | top.bit_counter[4:0] 38 | @28 39 | top.current_bit 40 | @22 41 | top.current_cycle_length[4:0] 42 | top.cycle_counter[4:0] 43 | @200 44 | -Output 45 | @28 46 | [color] 2 47 | top.data_out 48 | [color] 2 49 | top.done_out 50 | [pattern_trace] 1 51 | [pattern_trace] 0 52 | --------------------------------------------------------------------------------