├── bucatini ├── backends │ ├── __init__.py │ ├── soft.py │ ├── ecp5.py │ └── artix7.py ├── __init__.py ├── phy.py ├── alignment.py ├── ctc.py ├── lfps.py └── datapath.py ├── pyproject.toml ├── README.md ├── LICENSE.txt ├── .gitignore └── CODE_OF_CONDUCT.md /bucatini/backends/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Bucatini. 3 | # 4 | # Copyright (c) 2021 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | """ SerDes-backends for various platforms. """ 7 | -------------------------------------------------------------------------------- /bucatini/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Bucatini. 3 | # 4 | # Copyright (c) 2021 Great Scott Gadgets 5 | # 6 | # Code based in part on ``usb3_pipe``. 7 | # SPDX-License-Identifier: BSD-3-Clause 8 | """ SerDes-based PIPE PHY. """ 9 | 10 | # 11 | # Quick-use aliases 12 | # 13 | __all__ = ['SerDesPHY', 'LunaECP5SerDes'] 14 | 15 | # Backends. 16 | from .backends.ecp5 import LunaECP5SerDes 17 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "bucatini" 3 | version = "0.0.1-dev.0" 4 | description = "an nMigen soft-PIPE implementation for FPGA SerDes" 5 | authors = ["Katherine Temkin "] 6 | license = "BSD" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | nmigen = {git = "https://github.com/nmigen/nmigen.git"} 11 | 12 | [tool.poetry.dev-dependencies] 13 | tox = "^3.22.0" 14 | 15 | [build-system] 16 | requires = ["poetry-core>=1.0.0"] 17 | build-backend = "poetry.core.masonry.api" 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bucatini: a soft PIPE PHY for FPGA SerDes 2 | 3 | **Bucatini** is a noodly gateware layer capable of transforming an FPGA SerDes into a PIPE PHY, 4 | allowing you to use existing e.g. PCI-e or USB3 gateware without an expensive, discrete PHY. 5 | Like its namesake, Bucatini is designed to be a flexible, soft PIPE with a pleasant mouthfeel. 6 | 7 | ## Status 8 | 9 | Bucatini is currently in its very early development, and thus is only partially present. This 10 | document will be updated once more concrete information is available. 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) Katherine J. Temkin 4 | Copyright (c) 2019-2021, Great Scott Gadgets 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | # C object files and binaries 133 | *.o 134 | *.elf 135 | *.bin 136 | 137 | # CCLS completion cache 138 | .ccls-cache/ 139 | 140 | # Bitstream and SVF files 141 | *.bit 142 | *.svf 143 | 144 | # GDB debugging setups 145 | .gdbinit 146 | *.vcd 147 | *.gtkw 148 | 149 | # Editor / IDE files 150 | .vscode 151 | 152 | # SimpleSoc generated files 153 | soc.ld 154 | resources.h 155 | -------------------------------------------------------------------------------- /bucatini/phy.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Bucatini. 3 | # 4 | # Copyright (c) 2021 Great Scott Gadgets 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import logging 8 | 9 | from abc import ABC 10 | from nmigen import * 11 | 12 | class BucatiniPHY(Elaboratable, ABC): 13 | """ Abstract base class for Bucatini soft PIPE implementations. 14 | 15 | Currently compliant with the PHY Interface for PCI Express, revision 3.0, 16 | with the following tweaks: 17 | 18 | - Following nMigen conventions, reset is active high, rather than active low. 19 | 20 | See Table 5-2 in the PIPE specification r3 for a definition of these signals. Documenting them 21 | locally is pending; and should be completed once we've settled on a spec version. 22 | """ 23 | 24 | # Default to implementing the 32-bit PIPE standard, but allow subclasses to override this. 25 | INTERFACE_WIDTH = 32 26 | 27 | # Mappings of interface widths to DataBusWidth parameters. 28 | _DATA_BUS_WIDTHS = { 29 | 32: 0b00, 30 | 16: 0b01, 31 | 8 : 0b10 32 | } 33 | 34 | def __init__(self, invert_reset=True): 35 | 36 | # Ensure we have a valid interface width. 37 | if self.INTERFACE_WIDTH not in self._DATA_BUS_WIDTHS: 38 | raise ValueError(f"Bucatini does not support a data bus width of {self.INTERFACE_WIDTH}!") 39 | 40 | # Compute the width of our data and control signals for this class. 41 | data_width = self.INTERFACE_WIDTH * 8 42 | ctrl_width = self.INTERFACE_WIDTH * 1 43 | 44 | # 45 | # PIPE interface standard. 46 | # 47 | 48 | # Full-PHY Control and status. 49 | self.rate = Signal() 50 | self.reset = Signal() 51 | self.phy_mode = Signal(2) 52 | self.phy_status = Signal() 53 | self.elas_buf_mode = Signal() 54 | self.power_down = Signal(2) 55 | self.pwrpresent = Signal() 56 | self.data_bus_width = Const(self._DATA_BUS_WIDTHS[self.INTERFACE_WIDTH], width=2) 57 | 58 | # Transmit bus. 59 | self.tx_clk = Signal() 60 | self.tx_data = Signal(data_width) 61 | self.tx_datak = Signal(ctrl_width) 62 | self.tx_data_valid = Signal() 63 | 64 | # Transmit configuration & status. 65 | self.tx_compliance = Signal() 66 | self.tx_oneszeroes = Signal() 67 | self.tx_deemph = Signal(2) 68 | self.tx_margin = Signal(3) 69 | self.tx_swing = Signal() 70 | self.tx_detrx_lpbk = Signal() 71 | self.tx_elecidle = Signal() 72 | 73 | # Receive bus. 74 | self.pclk = Signal() 75 | self.rx_data = Signal(data_width) 76 | self.rx_datak = Signal(ctrl_width) 77 | self.rx_valid = Signal() 78 | 79 | # Receiver configuration & status. 80 | self.rx_status = Array((Signal(3), Signal(3))) 81 | self.rx_polarity = Signal() 82 | self.rx_elecidle = Signal() 83 | self.rx_termination = Signal() 84 | -------------------------------------------------------------------------------- /bucatini/alignment.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Bucatini. 3 | # 4 | # Copyright (c) 2021 Great Scott Gadgets 5 | # Copyright (c) 2020 Florent Kermarrec 6 | # 7 | # Code based in part on ``usb3_pipe``. 8 | # SPDX-License-Identifier: BSD-3-Clause 9 | """ SerDes stream word alignment code. """ 10 | 11 | from nmigen import * 12 | from nmigen.lib.fifo import AsyncFIFOBuffered 13 | from nmigen.hdl.ast import Past 14 | 15 | from ...usb.stream import USBRawSuperSpeedStream 16 | from ...usb.usb3.physical.coding import COM 17 | 18 | 19 | class RxWordAligner(Elaboratable): 20 | """ Receiver word alignment. 21 | 22 | Uses the location of COM signals in the data stream to re-position data so that the 23 | relevant commas always fall in the data's MSB (big endian). 24 | """ 25 | 26 | def __init__(self): 27 | self._is_big_endian = True 28 | 29 | # 30 | # I/O port 31 | # 32 | self.align = Signal() 33 | self.sink = USBRawSuperSpeedStream() 34 | self.source = USBRawSuperSpeedStream() 35 | 36 | # Debug signals 37 | self.alignment_offset = Signal(range(4)) 38 | 39 | 40 | def elaborate(self, platform): 41 | m = Module() 42 | 43 | # Aliases 44 | stream_in = self.sink 45 | stream_out = self.source 46 | 47 | # Values from previous cycles. 48 | previous_data = Past(stream_in.data, domain="ss") 49 | previous_ctrl = Past(stream_in.ctrl, domain="ss") 50 | 51 | # Alignment register: stores how many words the data must be shifted by in order to 52 | # have correctly aligned data. 53 | shift_to_apply = Signal(range(4)) 54 | 55 | # 56 | # Alignment detector. 57 | # 58 | 59 | if self._is_big_endian: 60 | alignment_precedence = range(4) 61 | else: 62 | alignment_precedence = reversed(range(4)) 63 | 64 | 65 | # Apply new alignments only if we're the first seen COM (since USB3 packets can contain multiple 66 | # consecutive COMs), and if alignment is enabled. 67 | following_data_byte = (previous_ctrl == 0) 68 | with m.If(self.align & following_data_byte): 69 | 70 | # Detect any alignment markers by looking for a COM signal in any of the four positions. 71 | for i in alignment_precedence: 72 | data_matches = (stream_in.data.word_select(i, 8) == COM.value) 73 | ctrl_matches = stream_in.ctrl[i] 74 | 75 | # If the current position has a comma in it, mark this as our alignment position; 76 | # and compute how many times we'll need to rotate our data _right_ in order to achieve 77 | # proper alignment. 78 | with m.If(data_matches & ctrl_matches): 79 | if self._is_big_endian: 80 | m.d.ss += [ 81 | shift_to_apply .eq(3 - i), 82 | #stream_out.valid .eq(shift_to_apply == (3 - i)) 83 | ] 84 | else: 85 | m.d.ss += [ 86 | shift_to_apply .eq(i), 87 | #stream_out.valid.eq(shift_to_apply == i) 88 | ] 89 | 90 | 91 | 92 | # 93 | # Aligner. 94 | # 95 | 96 | # To align our data, we'll create a conglomeration of two consecutive words; 97 | # and then select the chunk between those words that has the alignment correct. 98 | # (These words should always be in chronological order; so we'll need different 99 | # orders for big endian and little endian output.) 100 | if self._is_big_endian: 101 | data = Cat(stream_in.data, previous_data) 102 | ctrl = Cat(stream_in.ctrl, previous_ctrl) 103 | else: 104 | data = Cat(previous_data, stream_in.data) 105 | ctrl = Cat(previous_ctrl, stream_in.ctrl) 106 | 107 | # Create two multiplexers that allow us to select from each of our four possible 108 | # alignments... 109 | shifted_data_slices = Array(data[8*i:] for i in range(4)) 110 | shifted_ctrl_slices = Array(ctrl[i:] for i in range(4)) 111 | 112 | # ... and output our data accordingly. 113 | m.d.ss += [ 114 | stream_out.data .eq(shifted_data_slices[shift_to_apply]), 115 | stream_out.ctrl .eq(shifted_ctrl_slices[shift_to_apply]), 116 | 117 | # Debug output. 118 | self.alignment_offset.eq(shift_to_apply) 119 | 120 | ] 121 | 122 | # XXX: test 123 | m.d.comb += [ 124 | self.source.valid.eq(1) 125 | ] 126 | 127 | 128 | return m 129 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | straithe@greatscottgadgets.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /bucatini/ctc.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Bucatini. 3 | # 4 | # Copyright (c) 2021 Great Scott Gadgets 5 | # Copyright (c) 2020 Florent Kermarrec 6 | # 7 | # Code based in part on ``usb3_pipe``. 8 | # SPDX-License-Identifier: BSD-3-Clause 9 | """ Clock Tolerance Compensation (CTC) gateware. """ 10 | 11 | from nmigen import * 12 | 13 | from ...usb.usb3.physical.coding import SKP, stream_word_matches_symbol 14 | from ...usb.stream import USBRawSuperSpeedStream 15 | 16 | 17 | 18 | class CTCReceiveBuffer(Elaboratable): 19 | """ Clock Tolerance Compensation (CTC) elastic receive buffer gateware. 20 | 21 | It's functionally impossible to precisely synchronize the clocks for two independent 22 | systems -- every specification has to allow for some difference in frequency between 23 | the system's clocks (the "clock tolerance") 24 | 25 | To compensate, high speed serial protocols inject 'filler' bytes called "SKP ordered sets", 26 | which can be safely discarded. This allows the slower clock to catch up to the faster one. 27 | 28 | This module automatically removes those SKP ordered sets 29 | """ 30 | 31 | def __init__(self): 32 | 33 | # 34 | # I/O port 35 | # 36 | self.sink = USBRawSuperSpeedStream() 37 | self.source = USBRawSuperSpeedStream() 38 | 39 | self.skp_removed = Signal() 40 | 41 | 42 | def elaborate(self, platform): 43 | m = Module() 44 | 45 | sink = self.sink 46 | source = self.source 47 | 48 | bytes_in_stream = len(sink.ctrl) 49 | 50 | # 51 | # Find SKP symbols 52 | # 53 | 54 | # Identify the locations of any SKP symbols present in the stream. 55 | skp_locations = Signal(bytes_in_stream) 56 | for i in range(bytes_in_stream): 57 | m.d.comb += skp_locations[i].eq(stream_word_matches_symbol(sink, i, symbol=SKP)) 58 | 59 | # If we've found one, indicate that we're removing it. 60 | skip_found = self.sink.valid & self.sink.ready & (skp_locations != 0) 61 | m.d.comb += self.skp_removed.eq(skip_found) 62 | 63 | 64 | # 65 | # Data Extractor 66 | # 67 | 68 | # We'll first extract the data and control bits for every position that doesn't contain a SKP. 69 | valid_data = Signal.like(sink.data) 70 | valid_ctrl = Signal.like(sink.ctrl) 71 | valid_byte_count = Signal(range(0, bytes_in_stream + 1)) 72 | 73 | # We have a SKIP location for each byte; and each locations has two possible values 74 | # (SKP, no SKP); and therefore we have 2 ** distinct arrangements. 75 | possible_arrangement_count = 2 ** bytes_in_stream 76 | possible_arrangements = range(possible_arrangement_count) 77 | 78 | # We'll handle each possibility with a programmatically generated case. 79 | with m.Switch(skp_locations): 80 | 81 | # We'll generate a single case for each possible "skip mask". 82 | for skip_mask in possible_arrangements: 83 | with m.Case(skip_mask): 84 | data_fragments = [] 85 | ctrl_fragments = [] 86 | 87 | # We'll iterate over each of our possible positions, and gather 88 | # the nMigen signals associated with the non-skip values in the 89 | # relevant position. 90 | for position in range(bytes_in_stream): 91 | 92 | # If this case would have a valid byte at the given position, grab it. 93 | position_mask = 1 << position 94 | if (position_mask & position) == 0: 95 | data_signal_at_position = sink.data.word_select(position, 8) 96 | ctrl_signal_at_position = sink.ctrl.word_select(position, 8) 97 | data_fragments.append(data_signal_at_position) 98 | ctrl_fragments.append(ctrl_signal_at_position) 99 | 100 | 101 | # If there are any valid data signals associated with the given position, 102 | # coalesce the data and control signals into a single word, which we'll handle below. 103 | if data_fragments: 104 | m.d.comb += [ 105 | valid_data.eq(Cat(*data_fragments)), 106 | valid_ctrl.eq(Cat(*ctrl_fragments)), 107 | valid_byte_count.eq(len(data_fragments)), 108 | ] 109 | 110 | 111 | # 112 | # Elastic Buffer / Valid Data Coalescence 113 | # 114 | 115 | # We now have a signal that contains up to a valid word of data. We'll need to 116 | # stitch this data together before we can use it. To do so, we'll use a shift 117 | # register long enough to store two complete words of data -- one for the word 118 | # we're outputting, and one for a word-in-progress. 119 | 120 | # This shift register serves as our "elastic buffer" -- we can add in data in 121 | # bits and pieces, and remove it in bits and pieces. 122 | buffer_size_bytes = bytes_in_stream * 2 123 | 124 | data_buffer = Signal(buffer_size_bytes * 8) 125 | ctrl_buffer = Signal(buffer_size_bytes) 126 | bytes_in_buffer = Signal(range(0, buffer_size_bytes + 1)) 127 | 128 | m.d.comb += sink.ready.eq(bytes_in_buffer <= buffer_size_bytes) 129 | 130 | 131 | with m.If(sink.valid & sink.ready): 132 | 133 | with m.If(source.valid & source.ready): 134 | m.d.ss += sr_bytes.eq(sr_bytes + frag_bytes - 4) 135 | with m.Else(): 136 | m.d.ss += sr_bytes.eq(sr_bytes + frag_bytes) 137 | 138 | with m.Switch(frag_bytes): 139 | 140 | with m.Case(0): 141 | m.d.ss += [ 142 | sr_data.eq(sr_data), 143 | sr_ctrl.eq(sr_ctrl), 144 | ] 145 | 146 | for i in range(1, 5): 147 | with m.Case(i): 148 | m.d.ss += [ 149 | sr_data.eq(Cat(sr_data[8*i:], frag_data[0:8*i])), 150 | sr_ctrl.eq(Cat(sr_ctrl[1*i:], frag_ctrl[0:1*i])), 151 | ] 152 | 153 | 154 | with m.Elif(source.valid & source.ready): 155 | m.d.ss += sr_bytes.eq(sr_bytes - 4) 156 | 157 | 158 | # Output Data/Ctrl when there is a full 32/4-bit word -------------------------------------- 159 | m.d.comb += source.valid.eq(sr_bytes >= 4) 160 | cases = {} 161 | 162 | with m.Switch(sr_bytes): 163 | for i in range(4, 8): 164 | with m.Case(i): 165 | m.d.comb += [ 166 | source.data.eq(sr_data[8*(8-i):8*(8-i+4)]), 167 | source.ctrl.eq(sr_ctrl[1*(8-i):1*(8-i+4)]), 168 | ] 169 | 170 | return m 171 | 172 | 173 | 174 | class TXSKPInserter(Elaboratable): 175 | """TX SKP Inserter 176 | 177 | SKP Ordered Sets are inserted in the stream for clock compensation between partners with an 178 | average of 1 SKP Ordered Set every 354 symbols. This module inserts SKP Ordered Sets to the 179 | TX stream. SKP Ordered Sets shall not be inserted inside a packet, so this packet delimiters 180 | (first/last) should be used to ensure SKP are inserted only between packets and not inside. 181 | 182 | Note: To simplify implementation and keep alignment, 2 SKP Ordered Sets are inserted every 708 183 | symbols, which is a small deviation from the specification (good average, but 2x interval between 184 | SKPs). More tests need to be done to ensure this deviation is acceptable with all hardwares. 185 | """ 186 | def __init__(self): 187 | # 188 | # I/O port 189 | # 190 | self.sink = USBRawSuperSpeedStream() 191 | self.source = USBRawSuperSpeedStream() 192 | 193 | 194 | def elaborate(self, platform): 195 | m = Module() 196 | 197 | sink = self.sink 198 | source = self.source 199 | 200 | data_count = Signal(8) 201 | skip_grant = Signal(reset=1) 202 | skip_queue = Signal() 203 | skip_dequeue = Signal() 204 | skip_count = Signal(16) 205 | 206 | # Queue one 2 SKP Ordered Set every 176 Data/Ctrl words 207 | m.d.ss += skip_queue.eq(0) 208 | with m.If(sink.valid & sink.ready): 209 | m.d.ss += data_count.eq(data_count + 1), 210 | with m.If(data_count == 175): 211 | m.d.ss += [ 212 | data_count.eq(0), 213 | skip_queue.eq(1) 214 | ] 215 | 216 | # SKP grant: SKP should not be inserted inside packets 217 | with m.If(sink.valid & sink.ready): 218 | with m.If(sink.last): 219 | m.d.ss += skip_grant.eq(1) 220 | with m.Elif(sink.first): 221 | m.d.ss += skip_grant.eq(0) 222 | 223 | 224 | # SKP counter 225 | with m.If(skip_queue & ~skip_dequeue): 226 | m.d.ss += skip_count.eq(skip_count + 1) 227 | with m.If(~skip_queue & skip_dequeue): 228 | m.d.ss += skip_count.eq(skip_count - 1) 229 | 230 | 231 | # SKP insertion 232 | with m.If(skip_grant & (skip_count != 0)): 233 | m.d.comb += [ 234 | source.valid.eq(1), 235 | source.data.eq(Repl(Signal(8, reset=SKP.value), 4)), 236 | source.ctrl.eq(Repl(Signal(1, reset=1) , 4)), 237 | skip_dequeue.eq(source.ready) 238 | ] 239 | with m.Else(): 240 | m.d.comb += sink.connect(source) 241 | 242 | return m 243 | -------------------------------------------------------------------------------- /bucatini/backends/soft.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Bucatini. 3 | # 4 | # Copyright (c) 2021 Great Scott Gadgets 5 | # Copyright (c) 2019-2020 Florent Kermarrec 6 | # Copyright (c) 2016-2017 Sebastien Bourdeauducq 7 | # 8 | # Code initially ported to nMigen from ``MiSoC/LiteX`` and adapted for Bucatini. 9 | # SPDX-License-Identifier: BSD-3-Clause 10 | """ Components for replicating pieces of SerDes functionality in gateware. 11 | 12 | These can be used to facilitate porting to new SerDes; as they can be used in 13 | place of SerDes components. Eventually, they may prove useful in moving components 14 | into ASICs. 15 | """ 16 | 17 | 18 | from functools import reduce 19 | from operator import add 20 | 21 | from nmigen.compat import * 22 | 23 | 24 | def disparity(word, nbits): 25 | n0 = 0 26 | n1 = 0 27 | for i in range(nbits): 28 | if word & (1 << i): 29 | n1 += 1 30 | else: 31 | n0 += 1 32 | return n1 - n0 33 | 34 | 35 | def reverse_table_flip(inputs, flips, nbits): 36 | outputs = [None]*2**nbits 37 | 38 | for i, (word, flip) in enumerate(zip(inputs, flips)): 39 | if outputs[word] is not None: 40 | raise ValueError 41 | outputs[word] = i 42 | if flip: 43 | word_n = ~word & (2**nbits-1) 44 | if outputs[word_n] is not None: 45 | raise ValueError 46 | outputs[word_n] = i 47 | 48 | for i in range(len(outputs)): 49 | if outputs[i] is None: 50 | outputs[i] = 0 51 | 52 | return outputs 53 | 54 | 55 | def reverse_table(inputs, nbits): 56 | outputs = [None]*2**nbits 57 | for i, word in enumerate(inputs): 58 | if outputs[word] is not None: 59 | raise ValueError 60 | outputs[word] = i 61 | for i in range(len(outputs)): 62 | if outputs[i] is None: 63 | outputs[i] = 0 64 | return outputs 65 | 66 | 67 | # 5b6b 68 | 69 | table_5b6b = [ 70 | 0b011000, 71 | 0b100010, 72 | 0b010010, 73 | 0b110001, 74 | 0b001010, 75 | 0b101001, 76 | 0b011001, 77 | 0b000111, 78 | 0b000110, 79 | 0b100101, 80 | 0b010101, 81 | 0b110100, 82 | 0b001101, 83 | 0b101100, 84 | 0b011100, 85 | 0b101000, 86 | 0b100100, 87 | 0b100011, 88 | 0b010011, 89 | 0b110010, 90 | 0b001011, 91 | 0b101010, 92 | 0b011010, 93 | 0b000101, 94 | 0b001100, 95 | 0b100110, 96 | 0b010110, 97 | 0b001001, 98 | 0b001110, 99 | 0b010001, 100 | 0b100001, 101 | 0b010100, 102 | ] 103 | table_5b6b_unbalanced = [bool(disparity(c, 6)) for c in table_5b6b] 104 | table_5b6b_flip = list(table_5b6b_unbalanced) 105 | table_5b6b_flip[7] = True 106 | 107 | table_6b5b = reverse_table_flip(table_5b6b, table_5b6b_flip, 6) 108 | # K.28 109 | table_6b5b[0b001111] = 0b11100 110 | table_6b5b[0b110000] = 0b11100 111 | 112 | # 3b4b 113 | 114 | table_3b4b = [ 115 | 0b0100, 116 | 0b1001, 117 | 0b0101, 118 | 0b0011, 119 | 0b0010, 120 | 0b1010, 121 | 0b0110, 122 | 0b0001, # primary D.x.7 123 | ] 124 | table_3b4b_unbalanced = [bool(disparity(c, 4)) for c in table_3b4b] 125 | table_3b4b_flip = list(table_3b4b_unbalanced) 126 | table_3b4b_flip[3] = True 127 | 128 | table_4b3b = reverse_table_flip(table_3b4b, table_3b4b_flip, 4) 129 | # alternative D.x.7 130 | table_4b3b[0b0111] = 0b0111 131 | table_4b3b[0b1000] = 0b0111 132 | 133 | table_4b3b_kn = reverse_table(table_3b4b, 4) 134 | table_4b3b_kp = reverse_table([~x & 0b1111 for x in table_3b4b], 4) 135 | # primary D.x.7 is not used 136 | table_4b3b_kn[0b0001] = 0b000 137 | table_4b3b_kn[0b1000] = 0b111 138 | table_4b3b_kp[0b1110] = 0b000 139 | table_4b3b_kp[0b0111] = 0b111 140 | 141 | 142 | class SingleEncoder(Module): 143 | def __init__(self, lsb_first=False): 144 | self.d = Signal(8) 145 | self.k = Signal() 146 | self.disp_in = Signal() 147 | 148 | self.output = Signal(10) 149 | self.disp_out = Signal() 150 | 151 | # # # 152 | 153 | # stage 1: 5b/6b and 3b/4b encoding 154 | code5b = self.d[:5] 155 | code6b = Signal(6, reset_less=True) 156 | code6b_unbalanced = Signal(reset_less=True) 157 | code6b_flip = Signal() 158 | self.sync += [ 159 | If(self.k & (code5b == 28), 160 | code6b.eq(0b110000), 161 | code6b_unbalanced.eq(1), 162 | code6b_flip.eq(1) 163 | ).Else( 164 | code6b.eq(Array(table_5b6b)[code5b]), 165 | code6b_unbalanced.eq(Array(table_5b6b_unbalanced)[code5b]), 166 | code6b_flip.eq(Array(table_5b6b_flip)[code5b]) 167 | ) 168 | ] 169 | 170 | code3b = self.d[5:] 171 | code4b = Signal(4, reset_less=True) 172 | code4b_unbalanced = Signal(reset_less=True) 173 | code4b_flip = Signal() 174 | self.sync += [ 175 | code4b.eq(Array(table_3b4b)[code3b]), 176 | code4b_unbalanced.eq(Array(table_3b4b_unbalanced)[code3b]), 177 | If(self.k, 178 | code4b_flip.eq(1) 179 | ).Else( 180 | code4b_flip.eq(Array(table_3b4b_flip)[code3b]) 181 | ) 182 | ] 183 | 184 | alt7_rd0 = Signal(reset_less=True) # if disparity is -1, use alternative D.x.7 185 | alt7_rd1 = Signal(reset_less=True) # if disparity is +1, use alternative D.x.7 186 | self.sync += [ 187 | alt7_rd0.eq(0), 188 | alt7_rd1.eq(0), 189 | If(code3b == 7, 190 | If((code5b == 17) | (code5b == 18) | (code5b == 20), 191 | alt7_rd0.eq(1)), 192 | If((code5b == 11) | (code5b == 13) | (code5b == 14), 193 | alt7_rd1.eq(1)), 194 | If(self.k, 195 | alt7_rd0.eq(1), 196 | alt7_rd1.eq(1) 197 | ) 198 | ) 199 | ] 200 | 201 | # stage 2 (combinatorial): disparity control 202 | output_6b = Signal(6) 203 | disp_inter = Signal() 204 | self.comb += [ 205 | disp_inter.eq(self.disp_in ^ code6b_unbalanced), 206 | If(~self.disp_in & code6b_flip, 207 | output_6b.eq(~code6b) 208 | ).Else( 209 | output_6b.eq(code6b) 210 | ) 211 | ] 212 | 213 | output_4b = Signal(4) 214 | self.comb += [ 215 | If(~disp_inter & alt7_rd0, 216 | self.disp_out.eq(~disp_inter), 217 | output_4b.eq(0b0111) 218 | ).Elif(disp_inter & alt7_rd1, 219 | self.disp_out.eq(~disp_inter), 220 | output_4b.eq(0b1000) 221 | ).Else( 222 | self.disp_out.eq(disp_inter ^ code4b_unbalanced), 223 | If(~disp_inter & code4b_flip, 224 | output_4b.eq(~code4b) 225 | ).Else( 226 | output_4b.eq(code4b) 227 | ) 228 | ) 229 | ] 230 | 231 | output_msb_first = Signal(10) 232 | self.comb += output_msb_first.eq(Cat(output_4b, output_6b)) 233 | if lsb_first: 234 | for i in range(10): 235 | self.comb += self.output[i].eq(output_msb_first[9-i]) 236 | else: 237 | self.comb += self.output.eq(output_msb_first) 238 | 239 | 240 | class Encoder(Module): 241 | def __init__(self, nwords=1, lsb_first=False): 242 | self.d = [Signal(8) for _ in range(nwords)] 243 | self.k = [Signal() for _ in range(nwords)] 244 | self.output = [Signal(10, reset_less=True) for _ in range(nwords)] 245 | self.disparity = [Signal() for _ in range(nwords)] 246 | 247 | # # # 248 | 249 | encoders = [SingleEncoder(lsb_first) for _ in range(nwords)] 250 | self.submodules += encoders 251 | 252 | self.sync += encoders[0].disp_in.eq(encoders[-1].disp_out) 253 | for e1, e2 in zip(encoders, encoders[1:]): 254 | self.comb += e2.disp_in.eq(e1.disp_out) 255 | 256 | for d, k, output, disparity, encoder in \ 257 | zip(self.d, self.k, self.output, self.disparity, encoders): 258 | self.comb += [ 259 | encoder.d.eq(d), 260 | encoder.k.eq(k) 261 | ] 262 | output.reset_less = True 263 | self.sync += [ 264 | output.eq(encoder.output), 265 | disparity.eq(encoder.disp_out) 266 | ] 267 | 268 | 269 | class Decoder(Module): 270 | def __init__(self, lsb_first=False): 271 | self.input = Signal(10) 272 | self.d = Signal(8) 273 | self.k = Signal() 274 | self.invalid = Signal() 275 | 276 | # # # 277 | 278 | input_msb_first = Signal(10) 279 | if lsb_first: 280 | for i in range(10): 281 | self.comb += input_msb_first[i].eq(self.input[9-i]) 282 | else: 283 | self.comb += input_msb_first.eq(self.input) 284 | 285 | code6b = input_msb_first[4:] 286 | code5b = Signal(5) 287 | code4b = input_msb_first[:4] 288 | code3b = Signal(3, reset_less=True) 289 | 290 | mem_6b5b = Memory(5, len(table_6b5b), init=table_6b5b) 291 | port_6b5b = mem_6b5b.get_port() 292 | self.specials += mem_6b5b, port_6b5b 293 | self.comb += port_6b5b.adr.eq(code6b) 294 | 295 | self.sync += [ 296 | self.k.eq(0), 297 | If(code6b == 0b001111, 298 | self.k.eq(1), 299 | code3b.eq(Array(table_4b3b_kn)[code4b]) 300 | ).Elif(code6b == 0b110000, 301 | self.k.eq(1), 302 | code3b.eq(Array(table_4b3b_kp)[code4b]) 303 | ).Else( 304 | If((code4b == 0b0111) | (code4b == 0b1000), # D.x.A7/K.x.7 305 | If((code6b != 0b100011) & 306 | (code6b != 0b010011) & 307 | (code6b != 0b001011) & 308 | (code6b != 0b110100) & 309 | (code6b != 0b101100) & 310 | (code6b != 0b011100), self.k.eq(1)) 311 | ), 312 | code3b.eq(Array(table_4b3b)[code4b]) 313 | ), 314 | ] 315 | self.comb += code5b.eq(port_6b5b.dat_r) 316 | self.comb += self.d.eq(Cat(code5b, code3b)) 317 | 318 | # Basic invalid symbols detection: check that we have 4,5 or 6 ones in the symbol. This does 319 | # not report all invalid symbols but still allow detecting issues with the link. 320 | ones = Signal(4, reset_less=True) 321 | self.sync += ones.eq(reduce(add, [self.input[i] for i in range(10)])) 322 | self.comb += self.invalid.eq((ones != 4) & (ones != 5) & (ones != 6)) 323 | -------------------------------------------------------------------------------- /bucatini/lfps.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Bucatini. 3 | # 4 | # Copyright (c) 2021 Great Scott Gadgets 5 | # Copyright (c) 2019-2020 Florent Kermarrec 6 | # 7 | # Code initially ported to nMigen from ``usb3_pipe`` and adapted for Bucatini. 8 | # SPDX-License-Identifier: BSD-3-Clause 9 | """ Low-frequency periodic signaling gateware. 10 | 11 | LFPS is the first signaling to happen during the initialization of the USB3.0 link. 12 | 13 | LFPS allows partners to exchange Out Of Band (OOB) controls/commands and consists of bursts where 14 | a "slow" clock is generated (between 10M-50MHz) for a specific duration and with a specific repeat 15 | period. After the burst, the transceiver is put in electrical idle mode (same electrical level on 16 | P/N pairs while in nominal mode P/N pairs always have an opposite level): 17 | 18 | Transceiver level/mode: _=0, -=1 x=electrical idle 19 | |-_-_-_-xxxxxxxxxxxxxxxxxxxx|-_-_-_-xxxxxxxxxxxxxxxxxxxx|... 20 | | | |... 21 | |<-----repeat period------->|<-----repeat period------->|... 22 | 23 | A LFPS pattern is identified by a burst duration and repeat period. 24 | 25 | To be able generate and receive LFPS, a transceiver needs to be able put its TX in electrical idle 26 | and to detect RX electrical idle. 27 | """ 28 | 29 | import unittest 30 | from math import ceil 31 | 32 | from nmigen import * 33 | from nmigen.lib.cdc import FFSynchronizer 34 | from nmigen.hdl.ast import Rose, Past 35 | 36 | from ...test.utils import LunaSSGatewareTestCase, ss_domain_test_case 37 | from ...utils.cdc import synchronize 38 | 39 | __all__ = ['LFPSTransceiver'] 40 | 41 | # 42 | # LPFS timing "constants", and collection classes that represent them. 43 | # 44 | 45 | class LFPSTiming: 46 | """LPFS timings with typical, minimum and maximum timing values.""" 47 | 48 | def __init__(self, t_typ=None, t_min=None, t_max=None): 49 | self.t_typ = t_typ 50 | self.t_min = t_min 51 | self.t_max = t_max 52 | assert t_min is not None 53 | assert t_max is not None 54 | 55 | self.range = (t_min, t_max) 56 | 57 | 58 | class LFPS: 59 | """LPFS patterns with burst and repeat timings.""" 60 | 61 | def __init__(self, burst, repeat=None, cycles=None): 62 | self.burst = burst 63 | self.repeat = repeat 64 | self.cycles = None 65 | 66 | 67 | def _ns_to_cycles(clk_freq, t): 68 | return ceil(t*clk_freq) 69 | 70 | 71 | _DEFAULT_LFPS_FREQ = 30e6 72 | 73 | _LFPS_CLK_FREQ_MIN = 1/100e-9 74 | _LFPS_CLK_FREQ_MAX = 1/20e-9 75 | 76 | 77 | # Our actual pattern constants; as specified by the USB3 specification. 78 | # [USB 3.2r1: Table 6-30] 79 | _PollingLFPSBurst = LFPSTiming(t_typ=1.0e-6, t_min=0.6e-6, t_max=1.4e-6) 80 | _PollingLFPSRepeat = LFPSTiming(t_typ=10.0e-6, t_min=6.0e-6, t_max=14.0e-6) 81 | _PollingLFPS = LFPS(burst=_PollingLFPSBurst, repeat=_PollingLFPSRepeat) 82 | 83 | _ResetLFPSBurst = LFPSTiming(t_typ=100.0e-3, t_min=80.0e-3, t_max=120.0e-3) 84 | _ResetLFPS = LFPS(burst=_ResetLFPSBurst) 85 | 86 | 87 | class LFPSSquareWaveDetector(Elaboratable): 88 | """ Detector that identifies LFPS square-wave patterns. 89 | 90 | Operates in the ``fast`` domain. 91 | 92 | Attributes 93 | ---------- 94 | rx_gpio: Signal(), input 95 | The current state of the Rx lines, as retrieved by our SerDes. 96 | present: Signal(), output 97 | High whenever we detect an LFPS toggling. 98 | """ 99 | 100 | # From [USB3.2: Table 6-29]; the maximum and minimum 101 | PERIOD_MIN = 20e-9 102 | PERIOD_MAX = 100e-9 103 | 104 | def __init__(self, fast_clock_frequency=250e6): 105 | 106 | # Compute the minimum and maximum cycles we're allowed to see. 107 | # Our multipliers allow for up to a 10% devication in duty cycle. 108 | self._half_cycle_min = _ns_to_cycles(fast_clock_frequency, (self.PERIOD_MIN / 2) * 0.8) - 1 109 | self._half_cycle_max = _ns_to_cycles(fast_clock_frequency, (self.PERIOD_MAX / 2) * 1.2) + 1 110 | assert(self._half_cycle_min >= 1) 111 | 112 | 113 | # 114 | # I/O port 115 | # 116 | self.rx_gpio = Signal() 117 | self.present = Signal() 118 | 119 | 120 | def elaborate(self, platform): 121 | m = Module() 122 | 123 | # Our mechanism is simple: we measure the length of any periods of consecutive highs and lows 124 | # we see, and then check to see when they're both in acceptable ranges. Theoretically, we should 125 | # also check the duty cycle, but as of now, that doesn't seem necessary. [USB3.2: Table 6-29] 126 | 127 | # Keep track of the GPIO's value a cycle ago, so we can easily detect rising and falling edges. 128 | last_gpio = Signal() 129 | m.d.fast += last_gpio.eq(self.rx_gpio) 130 | 131 | # We'll allow each timer to go one cycle past our half-cycle-max, so it can saturate at an unacceptable 132 | # level, and mark the ranges as invalid. 133 | timer_max = self._half_cycle_max + 1 134 | 135 | # 136 | # Time-high detection. 137 | # 138 | 139 | # Keep track of our current/total time high. 140 | current_time_high = Signal(range(0, timer_max + 1)) 141 | total_time_high = Signal.like(current_time_high) 142 | 143 | # If our GPIO is high, count it. 144 | with m.If(self.rx_gpio): 145 | 146 | # Count only when we've reached a value lower than the timer's max, 147 | # so we saturate once we're outside the acceptable range. 148 | with m.If(current_time_high != timer_max): 149 | m.d.fast += current_time_high.eq(current_time_high + 1) 150 | 151 | # If we've saturated our count, immediately set the total time 152 | # to the saturation value. This prevents false detections after long 153 | # strings of constant value. 154 | with m.Else(): 155 | m.d.fast += total_time_high.eq(timer_max) 156 | 157 | 158 | # If we were still counting last cycle, we'll latch our observed time 159 | # high before our timer gets cleared. This value represents our total 160 | # time high, and thus the value we'll use for comparison. 161 | with m.Elif(last_gpio): 162 | 163 | m.d.fast += [ 164 | total_time_high .eq(current_time_high), 165 | current_time_high .eq(0) 166 | ] 167 | 168 | 169 | # 170 | # Time-low detection. 171 | # 172 | 173 | # Keep track of our current/total time low. 174 | current_time_low = Signal(range(0, timer_max + 1)) 175 | total_time_low = Signal.like(current_time_low) 176 | 177 | # If our GPIO is low, count it. 178 | with m.If(~self.rx_gpio): 179 | 180 | # Count only when we've reached a value lower than the timer's max, 181 | # so we saturate once we're outside the acceptable range. 182 | with m.If(current_time_low != timer_max): 183 | m.d.fast += current_time_low.eq(current_time_low + 1) 184 | 185 | # If we've saturated our count, immediately set the total time 186 | # to the saturation value. This prevents false detections after long 187 | # strings of constant value. 188 | with m.Else(): 189 | m.d.fast += total_time_low.eq(timer_max) 190 | 191 | # If we were still counting last cycle, we'll latch our observed time 192 | # low before our timer gets cleared. This value represents our total 193 | # time high, and thus the value we'll use for comparison. 194 | with m.Elif(~last_gpio): 195 | m.d.fast += [ 196 | total_time_low .eq(current_time_low), 197 | current_time_low .eq(0) 198 | ] 199 | 200 | 201 | # 202 | # Final detection. 203 | # 204 | 205 | # Whenever both our time high and time low are in range, we have a valid period. 206 | time_high_valid = ((total_time_high >= self._half_cycle_min) & (total_time_high <= self._half_cycle_max)) 207 | time_low_valid = ((total_time_low >= self._half_cycle_min) & (total_time_low <= self._half_cycle_max)) 208 | m.d.comb += self.present.eq(time_high_valid & time_low_valid) 209 | 210 | return m 211 | 212 | 213 | 214 | # 215 | # Core LFPS gateware. 216 | # 217 | class LFPSDetector(Elaboratable): 218 | """ LFPS signaling detector; detects LFPS signaling in particular patterns. 219 | 220 | Attributes 221 | ---------- 222 | rx_gpio: Signal(), input 223 | The current state of the Rx lines, as retrieved by our SerDes. 224 | detect: Signal(), output 225 | Strobes high when a valid LFPS burst is detected. 226 | 227 | """ 228 | def __init__(self, lfps_pattern, ss_clk_frequency=125e6, fast_clk_frequency=250e6): 229 | self._pattern = lfps_pattern 230 | self._clock_frequency = ss_clk_frequency 231 | self._fast_clock_frequency = fast_clk_frequency 232 | 233 | # 234 | # I/O port 235 | # 236 | self.signaling_detected = Signal() # i 237 | self.detect = Signal() # o 238 | 239 | 240 | def elaborate(self, platform): 241 | m = Module() 242 | 243 | # Create an in-domain version of our square-wave-detector signal. 244 | present = synchronize(m, self.signaling_detected) 245 | 246 | # Figure out how large of a counter we're going to need... 247 | burst_cycles_min = _ns_to_cycles(self._clock_frequency, self._pattern.burst.t_min) 248 | repeat_cycles_min = _ns_to_cycles(self._clock_frequency, self._pattern.repeat.t_min) 249 | burst_cycles_max = _ns_to_cycles(self._clock_frequency, self._pattern.burst.t_max) 250 | repeat_cycles_max = _ns_to_cycles(self._clock_frequency, self._pattern.repeat.t_max) 251 | counter_max = max(burst_cycles_max, repeat_cycles_max) 252 | 253 | # ... and create our counter. 254 | count = Signal(range(0, counter_max + 1)) 255 | m.d.ss += count.eq(count + 1) 256 | 257 | # Keep track of whether our previous iteration matched; as we're interested in detecting 258 | # sequences of two correct LFPS cycles in a row. 259 | last_iteration_matched = Signal() 260 | 261 | # 262 | # Detector state machine. 263 | # 264 | with m.FSM(domain="ss"): 265 | 266 | # WAIT_FOR_NEXT_BURST -- we're not currently in a measurement; but are waiting for a 267 | # burst to begin, so we can perform a full measurement. 268 | with m.State("WAIT_FOR_NEXT_BURST"): 269 | m.d.ss += last_iteration_matched.eq(0) 270 | 271 | # If we've just seen the start of a burst, start measuring it. 272 | with m.If(Rose(present)): 273 | m.d.ss += count.eq(1), 274 | m.next = "MEASURE_BURST" 275 | 276 | # MEASURE_BURST -- we're seeing something we believe to be a burst; and measuring its length. 277 | with m.State("MEASURE_BURST"): 278 | 279 | # Failing case: if our counter has gone longer than our maximum burst time, this isn't 280 | # a relevant burst. We'll wait for the next one. 281 | with m.If(count == burst_cycles_max): 282 | m.next = 'WAIT_FOR_NEXT_BURST' 283 | 284 | # Once our burst is over, we'll need to decide if the burst matches our pattern. 285 | with m.If(~present): 286 | 287 | # Failing case: if our burst is over, but we've not yet reached our minimum burst time, 288 | # then this isn't a relevant burst. We'll wait for the next one. 289 | with m.If(count < burst_cycles_min): 290 | m.next = 'WAIT_FOR_NEXT_BURST' 291 | 292 | # If our burst ended within a reasonable span, move on to measuring the spacing between 293 | # bursts ("repeat interval"). 294 | with m.Else(): 295 | m.next = "MEASURE_REPEAT" 296 | 297 | 298 | # MEASURE_REPEAT -- we've just finished seeing a burst; and now we're measuring the gap between 299 | # successive bursts, which the USB specification calls the "repeat interval". [USB3.2r1: Fig 6-32] 300 | with m.State("MEASURE_REPEAT"): 301 | 302 | # Failing case: if our counter has gone longer than our maximum burst time, this isn't 303 | # a relevant burst. We'll wait for the next one. 304 | with m.If(count == repeat_cycles_max): 305 | m.next = 'WAIT_FOR_NEXT_BURST' 306 | 307 | # Once we see another potential burst, we'll start our detection back from the top. 308 | with m.If(present): 309 | m.d.ss += count.eq(1) 310 | m.next = 'MEASURE_BURST' 311 | 312 | # If this lasted for a reasonable repeat interval, we've seen a correct burst! 313 | with m.If(count >= repeat_cycles_min): 314 | 315 | # Mark this as a correct iteration, and if the previous iteration was also 316 | # a correct one, indicate that we've detected our output. 317 | m.d.ss += last_iteration_matched.eq(1) 318 | m.d.comb += self.detect.eq(last_iteration_matched) 319 | 320 | with m.Else(): 321 | m.d.ss += last_iteration_matched.eq(0) 322 | 323 | return m 324 | 325 | 326 | 327 | class LFPSBurstGenerator(Elaboratable): 328 | """LFPS Burst Generator 329 | 330 | Generate a LFPS burst of configurable length on the TX lane. The LFPS clock is generated by 331 | sending an alternating ones/zeroes data pattern on the parallel interface of the transceiver. 332 | """ 333 | def __init__(self, sys_clk_freq, lfps_clk_freq): 334 | self._clock_frequency = sys_clk_freq 335 | self._lfps_clk_freq = lfps_clk_freq 336 | 337 | # Validate that our frequency is within the allowed bounds. 338 | assert lfps_clk_freq >= _LFPS_CLK_FREQ_MIN 339 | assert lfps_clk_freq <= _LFPS_CLK_FREQ_MAX 340 | 341 | # 342 | # I/O ports 343 | # 344 | self.start = Signal() # i 345 | self.done = Signal() # o 346 | self.length = Signal(32) # i 347 | 348 | 349 | self.tx_idle = Signal(reset=1) # o 350 | self.tx_gpio = Signal() # o 351 | 352 | 353 | def elaborate(self, platform): 354 | m = Module() 355 | 356 | # 357 | # LFPS square-wave generator. 358 | # 359 | timer_max = ceil(self._clock_frequency/(2*self._lfps_clk_freq)) - 1 360 | 361 | square_wave = Signal() 362 | period_timer = Signal(range(0, timer_max + 1)) 363 | 364 | with m.If(period_timer == 0): 365 | m.d.ss += [ 366 | square_wave .eq(~square_wave), 367 | period_timer .eq(timer_max - 1) 368 | ] 369 | with m.Else(): 370 | m.d.ss += period_timer.eq(period_timer - 1) 371 | 372 | 373 | # 374 | # Burst generator. 375 | # 376 | cycles_left_in_burst = Signal.like(self.length) 377 | 378 | with m.FSM(domain="ss"): 379 | 380 | # IDLE -- we're currently waiting for an LFPS burst to be requested. 381 | with m.State("IDLE"): 382 | m.d.comb += self.done.eq(1) 383 | m.d.ss += [ 384 | period_timer .eq(0), 385 | cycles_left_in_burst .eq(self.length) 386 | ] 387 | 388 | with m.If(self.start): 389 | m.next = "BURST" 390 | 391 | # BURST -- we've started a burst; and now we'll wait here until the burst is complete. 392 | with m.State("BURST"): 393 | m.d.comb += [ 394 | self.tx_idle .eq(0), 395 | self.tx_gpio .eq(square_wave), 396 | ] 397 | m.d.ss += cycles_left_in_burst.eq(cycles_left_in_burst - 1) 398 | 399 | with m.If(cycles_left_in_burst == 0): 400 | m.next = "IDLE" 401 | 402 | return m 403 | 404 | class LFPSBurstGeneratorTest(LunaSSGatewareTestCase): 405 | FRAGMENT_UNDER_TEST = LFPSBurstGenerator 406 | FRAGMENT_ARGUMENTS = dict( 407 | sys_clk_freq = 125e6, 408 | lfps_clk_freq = _DEFAULT_LFPS_FREQ 409 | ) 410 | 411 | def stimulus(self): 412 | """ Test stimulus for our burst clock generator. """ 413 | dut = self.dut 414 | 415 | # Create eight bursts of 256 pulses. 416 | for _ in range(8): 417 | yield dut.length.eq(256) 418 | yield dut.start.eq(1) 419 | yield 420 | yield dut.start.eq(0) 421 | while not (yield dut.done): 422 | yield 423 | for __ in range(256): 424 | yield 425 | 426 | 427 | def setUp(self): 428 | # Hook our setup function, and add in our stimulus. 429 | super().setUp() 430 | self.sim.add_sync_process(self.stimulus, domain="ss") 431 | 432 | 433 | @ss_domain_test_case 434 | def test_lpfs_burst_duty_cycle(self): 435 | dut = self.dut 436 | 437 | transitions = 0 438 | ones_ticks = 0 439 | zeroes_ticks = 0 440 | tx_gpio = 0 441 | 442 | # Wait a couple of cycles for our stimulus to set up our burst. 443 | yield from self.advance_cycles(2) 444 | 445 | # Wait for a burst cycle... 446 | while not (yield dut.done): 447 | 448 | # While we're bursting... 449 | if not (yield dut.tx_idle): 450 | 451 | # ... measure how many clock transitions we see... 452 | if (yield dut.tx_gpio != tx_gpio): 453 | transitions += 1 454 | 455 | # ... as well as what percentage of the time the clock is 1 vs 0. 456 | if (yield dut.tx_gpio != 0): 457 | ones_ticks += 1 458 | else: 459 | zeroes_ticks += 1 460 | 461 | tx_gpio = (yield dut.tx_gpio) 462 | yield 463 | 464 | # Figure out the total length that we've run for. 465 | total_ticks = ones_ticks + zeroes_ticks 466 | 467 | # We should measure a duty cycle that's within 10% of 50%... 468 | self.assertLess(abs(ones_ticks / (total_ticks) - 50e-2), 10e-2) 469 | self.assertLess(abs(zeroes_ticks / (total_ticks) - 50e-2), 10e-2) 470 | 471 | # ... and our total length should be within 10% of nominal. 472 | expected_cycles = self.SS_CLOCK_FREQUENCY/_DEFAULT_LFPS_FREQ 473 | computed_cycles = 2*total_ticks/transitions 474 | self.assertLess(abs(expected_cycles/computed_cycles - 1.0), 10e-2) 475 | 476 | 477 | class LFPSGenerator(Elaboratable): 478 | """LFPS Generator 479 | 480 | Generate a specific LFPS pattern on the TX lane. This module handles LFPS clock generation, LFPS 481 | burst generation and repetition. 482 | """ 483 | def __init__(self, lfps_pattern, sys_clk_freq, lfps_clk_freq): 484 | self._pattern = lfps_pattern 485 | self._clock_frequency = sys_clk_freq 486 | self._lpfs_frequency = lfps_clk_freq 487 | 488 | # 489 | # I/O port 490 | # 491 | 492 | # Control 493 | self.generate = Signal() # i 494 | self.count = Signal(16) # o 495 | 496 | # Transceiver 497 | self.tx_idle = Signal() # o 498 | self.tx_gpio = Signal() # o 499 | 500 | # Diagnostic 501 | self.busy = Signal() 502 | 503 | 504 | def elaborate(self, platform): 505 | m = Module() 506 | 507 | # 508 | # LFPS burst generator. 509 | # 510 | m.submodules.burst_gen = burst_generator = LFPSBurstGenerator( 511 | sys_clk_freq=self._clock_frequency, 512 | lfps_clk_freq=self._lpfs_frequency 513 | ) 514 | 515 | # 516 | # Controller 517 | # 518 | burst_repeat_count = Signal(32) 519 | full_burst_length = int(self._clock_frequency*self._pattern.burst.t_typ) 520 | full_repeat_length = int(self._clock_frequency*self._pattern.repeat.t_typ) 521 | 522 | with m.FSM(domain="ss"): 523 | 524 | # IDLE -- wait for an LFPS burst request. 525 | with m.State("IDLE"): 526 | m.d.comb += self.tx_idle .eq(0) 527 | m.d.ss += self.count .eq(0) 528 | 529 | # Once we get one, start a burst. 530 | with m.If(self.generate): 531 | m.d.comb += self.tx_idle.eq(1) 532 | m.d.ss += [ 533 | burst_generator.start .eq(1), 534 | burst_generator.length .eq(full_burst_length), 535 | burst_repeat_count .eq(full_repeat_length), 536 | ] 537 | m.next = "BURST_AND_WAIT" 538 | 539 | 540 | # BURST_AND_WAIT -- we've now triggered a burst; we'll wait until 541 | # the interval between bursts (the "repeat" interal) before allowing 542 | # another burst. 543 | with m.State("BURST_AND_WAIT"): 544 | m.d.comb += [ 545 | self.busy .eq(1), 546 | self.tx_idle .eq(burst_generator.tx_idle), 547 | self.tx_gpio .eq(burst_generator.tx_gpio) 548 | ] 549 | m.d.ss += [ 550 | burst_generator.start .eq(0), 551 | burst_repeat_count .eq(burst_repeat_count - 1) 552 | ] 553 | 554 | # Once we've waited the repeat interval, return to ready. 555 | with m.If(burst_repeat_count == 0): 556 | m.d.ss += self.count.eq(self.count + 1) 557 | m.next = "IDLE" 558 | 559 | return m 560 | 561 | 562 | class LFPSGeneratorTest(LunaSSGatewareTestCase): 563 | FRAGMENT_UNDER_TEST = LFPSGenerator 564 | FRAGMENT_ARGUMENTS = dict( 565 | lfps_pattern = _PollingLFPS, 566 | sys_clk_freq = 125e6, 567 | lfps_clk_freq= _DEFAULT_LFPS_FREQ 568 | ) 569 | 570 | @ss_domain_test_case 571 | def test_polling_lfps_sequence(self): 572 | dut = self.dut 573 | 574 | burst_length=int(self.SS_CLOCK_FREQUENCY * _PollingLFPSBurst.t_typ) 575 | burst_repeat=int(self.SS_CLOCK_FREQUENCY * _PollingLFPSRepeat.t_typ) 576 | 577 | # Trigger a burst... 578 | yield dut.generate.eq(1) 579 | yield 580 | yield 581 | 582 | # Wait for a whole burst-repeat cycle... 583 | burst_ticks = 0 584 | total_ticks = 0 585 | while (yield dut.busy): 586 | 587 | # ... and measure how long our burst lasts... 588 | if not (yield dut.tx_idle): 589 | burst_ticks += 1 590 | 591 | # ... as well as the length of our whole interval. 592 | total_ticks += 1 593 | yield 594 | 595 | # Our observed burst length should be within 10% of our specification... 596 | self.assertLess(abs(burst_ticks)/burst_length - 1.0, 10e-2) 597 | 598 | # ... as should our observed total length between bursts. 599 | self.assertLess(abs(total_ticks)/burst_repeat - 1.0, 10e-2) 600 | 601 | 602 | 603 | class LFPSTransceiver(Elaboratable): 604 | """ Low-Frequency Periodic Signaling (LFPS) Transciever. 605 | 606 | Transmits and receives the LPFS required for a USB3.0 link. 607 | 608 | Attributes 609 | ---------- 610 | 611 | rx_polling: Signal(), output 612 | Strobes high when Polling LFPS is detected. 613 | 614 | tx_idle: Signal(), input 615 | When asserted, indicates that the output lines should be held in electrical idle. 616 | tx_polling: Signal(), input 617 | Strobe. When asserted, begins an LFPS burst. 618 | 619 | tx_count: Signal(16), output 620 | [FIXME Document better] -> Indicates the current position in the LFPS transmission. 621 | """ 622 | 623 | def __init__(self, ss_clk_freq=125e6, fast_clock_frequency=250e6, lfps_clk_freq=_DEFAULT_LFPS_FREQ): 624 | self._clock_frequency = ss_clk_freq 625 | self._fast_clock_frequency = fast_clock_frequency 626 | self._lpfs_frequency = lfps_clk_freq 627 | 628 | # 629 | # I/O port 630 | # 631 | self.drive_tx_gpio = Signal() 632 | self.tx_gpio = Signal() 633 | self.lfps_signaling_detected = Signal() 634 | 635 | self.rx_polling = Signal() 636 | self.tx_idle = Signal() 637 | self.tx_polling = Signal() 638 | self.tx_count = Signal(16) 639 | 640 | 641 | def elaborate(self, platform): 642 | m = Module() 643 | 644 | # 645 | # LFPS Receiver. 646 | # 647 | polling_checker = LFPSDetector(_PollingLFPS, self._clock_frequency, self._fast_clock_frequency) 648 | m.submodules += polling_checker 649 | m.d.comb += [ 650 | polling_checker.signaling_detected .eq(self.lfps_signaling_detected), 651 | self.rx_polling .eq(polling_checker.detect) 652 | ] 653 | 654 | # 655 | # LFPS Transmitter(s). 656 | # 657 | polling_generator = LFPSGenerator(_PollingLFPS, self._clock_frequency, self._lpfs_frequency) 658 | m.submodules += polling_generator 659 | 660 | m.d.comb += [ 661 | polling_generator.generate .eq(self.tx_polling), 662 | 663 | # Drive our outputs with the outputs generated by our generator... 664 | self.tx_idle .eq(polling_generator.tx_idle), 665 | self.tx_gpio .eq(polling_generator.tx_gpio), 666 | self.tx_count .eq(polling_generator.count), 667 | 668 | # ... and take control of the Tx GPIO whenever we're driving our polling pattern. 669 | self.drive_tx_gpio .eq(self.tx_polling & ~self.tx_idle) 670 | ] 671 | 672 | return m 673 | 674 | 675 | if __name__ == "__main__": 676 | unittest.main() 677 | -------------------------------------------------------------------------------- /bucatini/datapath.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Bucatini. 3 | # 4 | # Copyright (c) 2021 Great Scott Gadgets 5 | # Copyright (c) 2020 Florent Kermarrec 6 | # 7 | # Code based in part on ``usb3_pipe``. 8 | # SPDX-License-Identifier: BSD-3-Clause 9 | """ Abstract SerDes interfacing code. 10 | 11 | See also the FPGA family-specific SerDes backends located in the `backends` subfolder. 12 | """ 13 | 14 | import unittest 15 | 16 | from nmigen import * 17 | from nmigen.lib.cdc import FFSynchronizer, PulseSynchronizer, ResetSynchronizer 18 | from nmigen.lib.fifo import AsyncFIFOBuffered 19 | from nmigen.hdl.ast import Past 20 | 21 | from ...test.utils import LunaGatewareTestCase, sync_test_case 22 | from ...usb.stream import USBRawSuperSpeedStream 23 | 24 | from ...usb.usb3.physical.coding import K, COM, SKP 25 | 26 | 27 | # 28 | # Receiver gateware 29 | # 30 | 31 | class ReceiverGearbox(Elaboratable): 32 | """ Simple rate-halving gearbox for our receive path. 33 | 34 | Designed specifically for our receive path; always halves the frequency of the input; 35 | and ignores "readiness", as the SerDes is always constantly producing output. 36 | 37 | If :attr:``output_domain`` is provided, clock domain crossing is handled automatically. 38 | 39 | Attributes 40 | ---------- 41 | sink: input stream 42 | The full-rate stream to be geared down. 43 | source: output stream 44 | The half-rate stream, post-gearing. 45 | 46 | Parameters 47 | ---------- 48 | words_in: int, optional 49 | The number of words of data to be processed at once; each word includes one byte of data and one 50 | bit of control. Output will always be twice this value. Defaults to 2. 51 | input_domain: str 52 | The name of the clock domain that our :attr:``sink`` resides in. Defaults to `rx`. 53 | output_domain: str, or None 54 | The name of the clock domain that our :attr:``source`` resides in. If not provided, no CDC 55 | hardware will be generated. 56 | """ 57 | 58 | def __init__(self, words_in=2, input_domain="rx", output_domain=None, flip_bytes=True): 59 | self._flip_bytes = flip_bytes 60 | self._words_in = words_in 61 | self._data_bits_in = words_in * 8 62 | self._ctrl_bits_in = words_in 63 | self._input_domain = input_domain 64 | self._output_domain = output_domain if output_domain else input_domain 65 | 66 | # 67 | # I/O port 68 | # 69 | self.sink = USBRawSuperSpeedStream(payload_words=words_in) 70 | self.source = USBRawSuperSpeedStream(payload_words=words_in * 2) 71 | 72 | 73 | def elaborate(self, platform): 74 | m = Module() 75 | 76 | # Buffer our stream inputs here to improve timing. 77 | stream_in_data = Signal.like(self.sink.data) 78 | stream_in_ctrl = Signal.like(self.sink.ctrl) 79 | stream_in_valid = Signal.like(self.sink.valid) 80 | m.d.sync += [ 81 | stream_in_data .eq(self.sink.data), 82 | stream_in_ctrl .eq(self.sink.ctrl), 83 | stream_in_valid .eq(self.sink.valid), 84 | ] 85 | 86 | # Aliases. 87 | stream_in = self.sink 88 | if self._flip_bytes: 89 | stream_in_data = stream_in.data.rotate_right((self._words_in // 2) * 8) 90 | stream_in_ctrl = stream_in.ctrl.rotate_right(self._words_in // 2) 91 | else: 92 | stream_in_data = stream_in.data 93 | stream_in_ctrl = stream_in.ctrl 94 | 95 | 96 | # If our output domain is the same as our input domain, we'll directly drive our output stream. 97 | # Otherwise, we'll drive an internal signal; and then cross that into our output domain. 98 | if self._output_domain == self._input_domain: 99 | stream_out = self.source 100 | else: 101 | stream_out = USBRawSuperSpeedStream() 102 | 103 | 104 | # Create proxies that allow us access to the upper and lower halves of our output data stream. 105 | data_out_halves = Array(stream_out.data.word_select(i, self._data_bits_in) for i in range(2)) 106 | ctrl_out_halves = Array(stream_out.ctrl.word_select(i, self._ctrl_bits_in) for i in range(2)) 107 | 108 | # Word select -- selects whether we're targeting the upper or lower half of the output word. 109 | # Toggles every input-domain cycle. 110 | targeting_upper_half = Signal(reset=1 if self._flip_bytes else 0) 111 | m.d.sync += targeting_upper_half.eq(~targeting_upper_half) 112 | 113 | # Pass through our data and control every cycle. 114 | m.d.sync += [ 115 | data_out_halves[targeting_upper_half] .eq(stream_in_data), 116 | ctrl_out_halves[targeting_upper_half] .eq(stream_in_ctrl), 117 | ] 118 | 119 | # Set our valid signal high only if both the current and previously captured word are valid. 120 | m.d.comb += [ 121 | stream_out.valid .eq(stream_in.valid & Past(stream_in.valid, domain=self._input_domain)) 122 | ] 123 | 124 | if self._input_domain != self._output_domain: 125 | in_domain_signals = Cat( 126 | stream_out.data, 127 | stream_out.ctrl, 128 | stream_out.valid 129 | ) 130 | out_domain_signals = Cat( 131 | self.source.data, 132 | self.source.ctrl, 133 | self.source.valid 134 | ) 135 | 136 | # Create our async FIFO... 137 | m.submodules.cdc = fifo = AsyncFIFOBuffered( 138 | width=len(in_domain_signals), 139 | depth=8, 140 | w_domain="sync", 141 | r_domain=self._output_domain 142 | ) 143 | 144 | m.d.comb += [ 145 | # ... fill it from our in-domain stream... 146 | fifo.w_data .eq(in_domain_signals), 147 | fifo.w_en .eq(targeting_upper_half), 148 | 149 | # ... and output it into our output stream. 150 | out_domain_signals .eq(fifo.r_data), 151 | self.source.valid .eq(fifo.r_level > 2), 152 | fifo.r_en .eq(1), 153 | ] 154 | 155 | 156 | # If our source domain isn't `sync`, translate `sync` to the proper domain name. 157 | if self._input_domain != "sync": 158 | m = DomainRenamer({'sync': self._input_domain})(m) 159 | 160 | return m 161 | 162 | 163 | class ReceiverSkipRemover(Elaboratable): 164 | """ Clock Tolerance Compensation (CTC) Skip remover. 165 | 166 | SKP Ordered Sets are inserted in the stream for clock compensation between partners with an 167 | average of 1 SKP Ordered Set every 354 symbols. This module removes SKP Ordered Sets from 168 | the RX stream. 169 | """ 170 | 171 | def __init__(self): 172 | 173 | # 174 | # I/O port 175 | # 176 | self.sink = USBRawSuperSpeedStream() 177 | self.source = USBRawSuperSpeedStream() 178 | self.skip = Signal() 179 | 180 | 181 | def elaborate(self, platform): 182 | m = Module() 183 | 184 | sink = self.sink 185 | source = self.source 186 | 187 | # 188 | # Find SKP symbols 189 | # 190 | skp = Signal(4) 191 | for i in range(4): 192 | m.d.comb += skp[i].eq(sink.ctrl[i] & (sink.data[8*i:8*(i+1)] == SKP.value)) 193 | m.d.comb += self.skip.eq(self.sink.valid & self.sink.ready & (skp != 0)) 194 | 195 | # 196 | # Select valid Data/Ctrl fragments 197 | # 198 | frag_data = Signal(32) 199 | frag_ctrl = Signal(4) 200 | frag_bytes = Signal(3) 201 | 202 | with m.Switch(skp): 203 | for i in range(2**4): 204 | with m.Case(i): 205 | 206 | datas = [] 207 | ctrls = [] 208 | 209 | for j in range(4): 210 | if (i & 2**j) == 0: 211 | datas.append(sink.data[8*j:8*(j+1)]) 212 | ctrls.append(sink.ctrl[1*j:1*(j+1)]) 213 | 214 | m.d.comb += [ 215 | frag_data.eq(Cat(*datas) if len(datas) else 0), 216 | frag_ctrl.eq(Cat(*ctrls) if len(ctrls) else 0), 217 | frag_bytes.eq(len(ctrls)), 218 | ] 219 | 220 | # Store Data/Ctrl in a 64/8-bit Shift Register --------------------------------------------- 221 | sr_data = Signal(64) 222 | sr_ctrl = Signal(8) 223 | sr_bytes = Signal(4) 224 | 225 | 226 | m.d.comb += sink.ready.eq(sr_bytes <= 7) 227 | 228 | 229 | with m.If(sink.valid & sink.ready): 230 | 231 | with m.If(source.valid & source.ready): 232 | m.d.ss += sr_bytes.eq(sr_bytes + frag_bytes - 4) 233 | with m.Else(): 234 | m.d.ss += sr_bytes.eq(sr_bytes + frag_bytes) 235 | 236 | with m.Switch(frag_bytes): 237 | 238 | with m.Case(0): 239 | m.d.ss += [ 240 | sr_data.eq(sr_data), 241 | sr_ctrl.eq(sr_ctrl), 242 | ] 243 | 244 | for i in range(1, 5): 245 | with m.Case(i): 246 | m.d.ss += [ 247 | sr_data.eq(Cat(sr_data[8*i:], frag_data[0:8*i])), 248 | sr_ctrl.eq(Cat(sr_ctrl[1*i:], frag_ctrl[0:1*i])), 249 | ] 250 | 251 | 252 | with m.Elif(source.valid & source.ready): 253 | m.d.ss += sr_bytes.eq(sr_bytes - 4) 254 | 255 | 256 | # Output Data/Ctrl when there is a full 32/4-bit word -------------------------------------- 257 | m.d.comb += source.valid.eq(sr_bytes >= 4) 258 | cases = {} 259 | 260 | with m.Switch(sr_bytes): 261 | for i in range(4, 8): 262 | with m.Case(i): 263 | m.d.comb += [ 264 | source.data.eq(sr_data[8*(8-i):8*(8-i+4)]), 265 | source.ctrl.eq(sr_ctrl[1*(8-i):1*(8-i+4)]), 266 | ] 267 | 268 | return m 269 | 270 | 271 | 272 | 273 | class RxWordAligner(Elaboratable): 274 | """ Receiver word alignment. 275 | 276 | Uses the location of COM signals in the data stream to re-position data so that the 277 | relevant commas always fall in the data's MSB (big endian). 278 | """ 279 | 280 | def __init__(self): 281 | self._is_big_endian = True 282 | 283 | # 284 | # I/O port 285 | # 286 | self.align = Signal() 287 | self.sink = USBRawSuperSpeedStream() 288 | self.source = USBRawSuperSpeedStream() 289 | 290 | # Debug signals 291 | self.alignment_offset = Signal(range(4)) 292 | 293 | 294 | def elaborate(self, platform): 295 | m = Module() 296 | 297 | # Aliases 298 | stream_in = self.sink 299 | stream_out = self.source 300 | 301 | # Values from previous cycles. 302 | previous_data = Past(stream_in.data, domain="ss") 303 | previous_ctrl = Past(stream_in.ctrl, domain="ss") 304 | 305 | # Alignment register: stores how many words the data must be shifted by in order to 306 | # have correctly aligned data. 307 | shift_to_apply = Signal(range(4)) 308 | 309 | # 310 | # Alignment detector. 311 | # 312 | if self._is_big_endian: 313 | alignment_byte_precedence = reversed(range(4)) 314 | else: 315 | alignment_byte_precedence = range(4) 316 | 317 | 318 | # Apply new alignments only if we're the first seen COM (since USB3 packets can contain multiple 319 | # consecutive COMs), and if alignment is enabled. 320 | following_data_byte = (previous_ctrl == 0) 321 | with m.If(self.align & following_data_byte): 322 | 323 | # Detect any alignment markers by looking for a COM signal in any of the four positions. 324 | for i in alignment_byte_precedence: 325 | data_matches = (stream_in.data.word_select(i, 8) == COM.value) 326 | ctrl_matches = stream_in.ctrl[i] 327 | 328 | # If the current position has a comma in it, mark this as our alignment position; 329 | # and compute how many times we'll need to rotate our data _right_ in order to achieve 330 | # proper alignment. 331 | with m.If(data_matches & ctrl_matches): 332 | if self._is_big_endian: 333 | m.d.ss += [ 334 | shift_to_apply .eq(3 - i), 335 | stream_out.valid .eq(shift_to_apply == (3 - i)) 336 | ] 337 | else: 338 | m.d.ss += [ 339 | shift_to_apply .eq(i), 340 | stream_out.valid.eq(shift_to_apply == i) 341 | ] 342 | 343 | 344 | # 345 | # Aligner. 346 | # 347 | 348 | # To align our data, we'll create a conglomeration of two consecutive words; 349 | # and then select the chunk between those words that has the alignment correct. 350 | # (These words should always be in chronological order; so we'll need different 351 | # orders for big endian and little endian output.) 352 | if self._is_big_endian: 353 | data = Cat(stream_in.data, previous_data) 354 | ctrl = Cat(stream_in.ctrl, previous_ctrl) 355 | else: 356 | data = Cat(previous_data, stream_in.data) 357 | ctrl = Cat(previous_ctrl, stream_in.ctrl) 358 | 359 | # Create two multiplexers that allow us to select from each of our four possible 360 | # alignments... 361 | shifted_data_slices = Array(data[8*i:] for i in range(4)) 362 | shifted_ctrl_slices = Array(ctrl[i:] for i in range(4)) 363 | 364 | # ... and output our data accordingly. 365 | m.d.ss += [ 366 | stream_out.data .eq(shifted_data_slices[shift_to_apply]), 367 | stream_out.ctrl .eq(shifted_ctrl_slices[shift_to_apply]), 368 | 369 | # Debug output. 370 | self.alignment_offset.eq(shift_to_apply) 371 | ] 372 | 373 | return m 374 | 375 | 376 | class RxTrainingWordAligner(Elaboratable): 377 | """ Receiver word alignment (TS-based variant). 378 | 379 | Uses the location of COM signals in the data stream to re-position data so that the 380 | relevant commas always fall in the data's MSB (big endian). 381 | """ 382 | 383 | def __init__(self): 384 | self._is_big_endian = True 385 | 386 | # 387 | # I/O port 388 | # 389 | self.align = Signal() 390 | self.sink = USBRawSuperSpeedStream() 391 | self.source = USBRawSuperSpeedStream() 392 | 393 | # Debug signals 394 | self.alignment_offset = Signal(range(4)) 395 | 396 | 397 | def elaborate(self, platform): 398 | m = Module() 399 | 400 | # Aliases 401 | stream_in = self.sink 402 | stream_out = self.source 403 | 404 | # Values from previous cycles. 405 | previous_data = Past(stream_in.data, domain="ss") 406 | previous_ctrl = Past(stream_in.ctrl, domain="ss") 407 | 408 | # Alignment register: stores how many words the data must be shifted by in order to 409 | # have correctly aligned data. 410 | shift_to_apply = Signal(range(4)) 411 | 412 | # 413 | # Alignment shift register. 414 | # 415 | # To align our data, we'll create a conglomeration of two consecutive words; 416 | # and then select the chunk between those words that has the alignment correct. 417 | # (These words should always be in chronological order; so we'll need different 418 | # orders for big endian and little endian output.) 419 | if self._is_big_endian: 420 | data = Cat(stream_in.data, previous_data) 421 | ctrl = Cat(stream_in.ctrl, previous_ctrl) 422 | else: 423 | data = Cat(previous_data, stream_in.data) 424 | ctrl = Cat(previous_ctrl, stream_in.ctrl) 425 | 426 | # Create two multiplexers that allow us to select from each of our four possible alignments. 427 | shifted_data_slices = Array(data[8*i:] for i in range(4)) 428 | shifted_ctrl_slices = Array(ctrl[i:] for i in range(4)) 429 | 430 | 431 | # 432 | # Alignment detection. 433 | # 434 | 435 | # Our aligner is simple: we'll search for the start of a TS1/TS2 training set; which we know 436 | # starts with a burst of four consecutive commas. 437 | four_comma_data = Repl(COM.value_const(), 4) 438 | four_comma_ctrl = Repl(COM.ctrl_const(), 4) 439 | 440 | # We'll check each possible alignment to see if it would produce a valid start-of-TS1/TS2. 441 | possible_alignments = len(shifted_data_slices) 442 | for i in range(possible_alignments): 443 | data_matches = (shifted_data_slices[i][0:32] == four_comma_data) 444 | ctrl_matches = (shifted_ctrl_slices[i][0:4] == four_comma_ctrl) 445 | 446 | # If it would, we'll accept that as our alignment going forward. 447 | with m.If(data_matches & ctrl_matches): 448 | m.d.ss += shift_to_apply.eq(i) 449 | 450 | 451 | # 452 | # Alignment application. 453 | # 454 | 455 | # Grab the shifted data/ctrl associated with our alignment. 456 | m.d.ss += [ 457 | stream_out.data .eq(shifted_data_slices[shift_to_apply]), 458 | stream_out.ctrl .eq(shifted_ctrl_slices[shift_to_apply]), 459 | 460 | # We're part of a chain that always produces data (currently); 461 | # so for now, we'll always indicate our data is valid. This may 462 | # need to be changed if we add in nicer CTC. 463 | stream_out.valid .eq(1), 464 | 465 | # Debug output. 466 | self.alignment_offset.eq(shift_to_apply) 467 | ] 468 | 469 | return m 470 | 471 | 472 | 473 | 474 | class ReceivePostprocessing(Elaboratable): 475 | """RX Datapath 476 | 477 | This module realizes the: 478 | - Data-width adaptation (from transceiver's data-width to 32-bit). 479 | - Clock domain crossing (from transceiver's RX clock to system clock). 480 | - Clock compensation (SKP removing). 481 | - Words alignment. 482 | """ 483 | def __init__(self, clock_domain="rx", buffer_clock_domain="fast", 484 | output_clock_domain="ss", serdes_is_little_endian=True): 485 | 486 | self._clock_domain = clock_domain 487 | self._buffer_clock_domain = buffer_clock_domain 488 | self._output_clock_domain = output_clock_domain 489 | self._serdes_is_little_endian = serdes_is_little_endian 490 | 491 | # 492 | # I/O port 493 | # 494 | self.align = Signal() 495 | self.sink = USBRawSuperSpeedStream(payload_words=2) 496 | self.source = USBRawSuperSpeedStream(payload_words=4) 497 | 498 | # Debug signals 499 | self.alignment_offset = Signal(range(4)) 500 | 501 | 502 | def elaborate(self, platfrom): 503 | m = Module() 504 | 505 | # 506 | # 1:2 Gearing (and clock domain crossing). 507 | # 508 | m.submodules.gearing = gearing = ReceiverGearbox( 509 | input_domain = self._clock_domain, 510 | output_domain = "ss", 511 | flip_bytes = self._serdes_is_little_endian 512 | ) 513 | m.d.comb += gearing.sink.stream_eq(self.sink) 514 | 515 | # 516 | # Clock tolerance compensation. 517 | # 518 | #m.submodules.skip_remover = skip_remover = ReceiverSkipRemover() 519 | #m.d.comb += skip_remover.sink.stream_eq(gearing.source) 520 | 521 | # 522 | # Word aligner. 523 | # 524 | #m.submodules.word_aligner = word_aligner = RxWordAligner() 525 | m.submodules.word_aligner = word_aligner = RxTrainingWordAligner() 526 | m.d.comb += [ 527 | # Core outputs 528 | word_aligner.align .eq(self.align), 529 | word_aligner.sink .stream_eq(gearing.source), 530 | 531 | # Debug output. 532 | self.alignment_offset .eq(word_aligner.alignment_offset) 533 | 534 | ] 535 | 536 | # 537 | # Final output. 538 | # 539 | m.d.comb += self.source.stream_eq(word_aligner.source) 540 | 541 | return m 542 | 543 | 544 | # 545 | # Transmitter gateware 546 | # 547 | 548 | 549 | class TransmitterGearbox(Elaboratable): 550 | """ Simple rate-doubling gearbox for our transmit path. 551 | 552 | Designed specifically for our transmit path; always doubles the frequency of the input; 553 | and ignores "readiness", as the SerDes is always constantly producing output. 554 | 555 | If :attr:``output_domain`` is provided, clock domain crossing is handled automatically. 556 | 557 | Attributes 558 | ---------- 559 | sink: input stream 560 | The single-rate stream to be geared up. 561 | source: output stream 562 | The double-rate stream, post-gearing. 563 | 564 | Parameters 565 | ---------- 566 | words_in: int, optional 567 | The number of words of data to be processed at once; each word includes one byte of data and one 568 | bit of control. Output will always be twice this value. Defaults to 4. 569 | output_domain: str 570 | The name of the clock domain that our :attr:``source`` resides in. Defaults to `tx`. 571 | input_domain: str, or None 572 | The name of the clock domain that our :attr:``sink`` resides in. If not provided, no CDC 573 | hardware will be generated. 574 | """ 575 | 576 | def __init__(self, words_in=4, output_domain="tx", input_domain=None): 577 | self._ratio = 2 578 | self._flip_bytes = True 579 | self._words_in = words_in 580 | self._words_out = words_in // 2 581 | self._output_domain = output_domain 582 | self._input_domain = input_domain if input_domain else output_domain 583 | 584 | # 585 | # I/O port 586 | # 587 | self.sink = USBRawSuperSpeedStream(payload_words=self._words_in) 588 | self.source = USBRawSuperSpeedStream(payload_words=self._words_out) 589 | 590 | 591 | def elaborate(self, platform): 592 | m = Module() 593 | 594 | # If we're receiving data from an domain other than our output domain, 595 | # cross it over nicely. 596 | if self._input_domain != self._output_domain: 597 | stream_in = USBRawSuperSpeedStream(payload_words=self._words_in) 598 | in_domain_signals = Cat( 599 | self.sink.data, 600 | self.sink.ctrl, 601 | ) 602 | out_domain_signals = Cat( 603 | stream_in.data, 604 | stream_in.ctrl, 605 | ) 606 | 607 | # Advance our FIFO only ever other cycle. 608 | advance_fifo = Signal() 609 | m.d.tx += advance_fifo.eq(~advance_fifo) 610 | 611 | # Create our async FIFO... 612 | m.submodules.cdc = fifo = AsyncFIFOBuffered( 613 | width=len(in_domain_signals), 614 | depth=8, 615 | w_domain=self._input_domain, 616 | r_domain="tx" 617 | ) 618 | 619 | m.d.comb += [ 620 | # ... fill it from our in-domain stream... 621 | fifo.w_data .eq(in_domain_signals), 622 | fifo.w_en .eq(1), 623 | self.sink.ready .eq(1), 624 | 625 | # ... and output it into our output stream. 626 | out_domain_signals .eq(fifo.r_data), 627 | stream_in.valid .eq(fifo.r_rdy), 628 | fifo.r_en .eq(advance_fifo), 629 | ] 630 | 631 | # Otherwise, use our data-stream directly. 632 | else: 633 | stream_in = self.sink 634 | m.d.comb += self.sink.ready.eq(1) 635 | 636 | 637 | # Word select -- selects whether we're targeting the upper or lower half of the input word. 638 | # Toggles every input-domain cycle. 639 | next_half_targeted = Signal() 640 | targeting_upper_half = Signal() 641 | 642 | # If our data has just changed, we should always be targeting the upper word. 643 | # This "locks" us to the data's changes. 644 | data_changed = stream_in.data != Past(stream_in.data, domain="tx") 645 | ctrl_changed = stream_in.ctrl != Past(stream_in.ctrl, domain="tx") 646 | with m.If(data_changed | ctrl_changed): 647 | m.d.comb += targeting_upper_half .eq(1 if self._flip_bytes else 0) 648 | m.d.tx += next_half_targeted .eq(0 if self._flip_bytes else 1) 649 | with m.Else(): 650 | m.d.comb += targeting_upper_half .eq(next_half_targeted) 651 | m.d.tx += next_half_targeted .eq(~next_half_targeted) 652 | 653 | 654 | # If we're flipping the bytes in our output stream (e.g. to maintain endianness), 655 | # create a flipped version; otherwise, use our output stream directly. 656 | stream_out = self.source 657 | 658 | # Create proxies that allow us access to the upper and lower halves of our input data stream. 659 | data_in_halves = Array(stream_in.data.word_select(i, len(stream_in.data) // 2) for i in range(2)) 660 | ctrl_in_halves = Array(stream_in.ctrl.word_select(i, len(stream_in.ctrl) // 2) for i in range(2)) 661 | 662 | # Pass through our data and control every cycle. 663 | # This is registered to loosen timing. 664 | if self._flip_bytes: 665 | stream_out_data = data_in_halves[targeting_upper_half] 666 | stream_out_ctrl = ctrl_in_halves[targeting_upper_half] 667 | m.d.tx += [ 668 | stream_out.data .eq(stream_out_data.rotate_right(len(stream_out_data) // 2)), 669 | stream_out.ctrl .eq(stream_out_ctrl.rotate_right(len(stream_out_ctrl) // 2)), 670 | stream_out.valid .eq(1), 671 | ] 672 | else: 673 | m.d.tx += [ 674 | stream_out.data .eq(data_in_halves[targeting_upper_half]), 675 | stream_out.ctrl .eq(ctrl_in_halves[targeting_upper_half]), 676 | stream_out.valid .eq(1), 677 | ] 678 | 679 | # If our output domain isn't `sync`, translate `sync` to the proper domain name. 680 | if self._output_domain != "tx": 681 | m = DomainRenamer({'tx': self._output_domain})(m) 682 | 683 | return m 684 | 685 | 686 | class TransmitterGearboxTest(LunaGatewareTestCase): 687 | FRAGMENT_UNDER_TEST = TransmitterGearbox 688 | FRAGMENT_ARGUMENTS = {'output_domain': 'sync'} 689 | 690 | @sync_test_case 691 | def test_basic_gearbox(self): 692 | dut = self.dut 693 | sink = dut.sink 694 | source = dut.source 695 | 696 | # If we start off with our stream presenting COM, 0xFF, 0x17, 0xC0. 697 | yield sink.data.eq(0xBCFF17C0) 698 | yield sink.ctrl.eq(0b1000) 699 | 700 | # After a two-cycle pipeline delay, we should see the first word on our output... 701 | yield from self.advance_cycles(2) 702 | self.assertEqual((yield source.data), 0xFFBC) 703 | self.assertEqual((yield source.ctrl), 0b01) 704 | yield sink.data.eq(0x14B2E702) 705 | yield sink.ctrl.eq(0b0000) 706 | 707 | # ... followed by the second... 708 | yield from self.advance_cycles(1) 709 | self.assertEqual((yield source.data), 0xC017) 710 | self.assertEqual((yield source.ctrl), 0b00) 711 | 712 | # ... and our data should continue changing with the input. 713 | yield from self.advance_cycles(1) 714 | self.assertEqual((yield source.data), 0xB214) 715 | self.assertEqual((yield source.ctrl), 0b00) 716 | yield from self.advance_cycles(1) 717 | self.assertEqual((yield source.data), 0x02E7) 718 | self.assertEqual((yield source.ctrl), 0b00) 719 | 720 | 721 | 722 | 723 | 724 | # RXErrorSubstitution (6.3.5) ---------------------------------------------------------------------- 725 | 726 | #class RXErrorSubstitution(Module): 727 | # """RX Error Substitution 728 | # 729 | # Substitutes 8b/10b decoder errors with K28.4 symbols. 730 | # """ 731 | # def __init__(self, serdes, clock_domain): 732 | # self.sink = USBRawSuperSpeedStream(payload_words=2) 733 | # self.source = USBRawSuperSpeedStream(payload_words=2) 734 | # 735 | # # # # 736 | # 737 | # self.comb += self.sink.connect(self.source) 738 | # for i in range(2): 739 | # self.comb += [ 740 | # If(serdes.decoders[i].invalid, 741 | # self.source.ctrl[i].eq(1), 742 | # self.source.data[8*i:8*(i+1)].eq(K(28, 4)), 743 | # ) 744 | # ] 745 | 746 | # TX SKP Inserter (6.4.3) -------------------------------------------------------------------------- 747 | 748 | class TXSKPInserter(Elaboratable): 749 | """TX SKP Inserter 750 | 751 | SKP Ordered Sets are inserted in the stream for clock compensation between partners with an 752 | average of 1 SKP Ordered Set every 354 symbols. This module inserts SKP Ordered Sets to the 753 | TX stream. SKP Ordered Sets shall not be inserted inside a packet, so this packet delimiters 754 | (first/last) should be used to ensure SKP are inserted only between packets and not inside. 755 | 756 | Note: To simplify implementation and keep alignment, 2 SKP Ordered Sets are inserted every 708 757 | symbols, which is a small deviation from the specification (good average, but 2x interval between 758 | SKPs). More tests need to be done to ensure this deviation is acceptable with all hardwares. 759 | """ 760 | def __init__(self): 761 | # 762 | # I/O port 763 | # 764 | self.sink = USBRawSuperSpeedStream() 765 | self.source = USBRawSuperSpeedStream() 766 | 767 | 768 | def elaborate(self, platform): 769 | m = Module() 770 | 771 | sink = self.sink 772 | source = self.source 773 | 774 | data_count = Signal(8) 775 | skip_grant = Signal(reset=1) 776 | skip_queue = Signal() 777 | skip_dequeue = Signal() 778 | skip_count = Signal(16) 779 | 780 | # Queue one 2 SKP Ordered Set every 176 Data/Ctrl words 781 | m.d.ss += skip_queue.eq(0) 782 | with m.If(sink.valid & sink.ready): 783 | m.d.ss += data_count.eq(data_count + 1), 784 | with m.If(data_count == 175): 785 | m.d.ss += [ 786 | data_count.eq(0), 787 | skip_queue.eq(1) 788 | ] 789 | 790 | # SKP grant: SKP should not be inserted inside packets 791 | with m.If(sink.valid & sink.ready): 792 | with m.If(sink.last): 793 | m.d.ss += skip_grant.eq(1) 794 | with m.Elif(sink.first): 795 | m.d.ss += skip_grant.eq(0) 796 | 797 | 798 | # SKP counter 799 | with m.If(skip_queue & ~skip_dequeue): 800 | m.d.ss += skip_count.eq(skip_count + 1) 801 | with m.If(~skip_queue & skip_dequeue): 802 | m.d.ss += skip_count.eq(skip_count - 1) 803 | 804 | 805 | # SKP insertion 806 | with m.If(skip_grant & (skip_count != 0)): 807 | m.d.comb += [ 808 | source.valid.eq(1), 809 | source.data.eq(Repl(Signal(8, reset=SKP.value), 4)), 810 | source.ctrl.eq(Repl(Signal(1, reset=1) , 4)), 811 | skip_dequeue.eq(source.ready) 812 | ] 813 | with m.Else(): 814 | m.d.comb += sink.connect(source) 815 | 816 | return m 817 | 818 | 819 | class TransmitPreprocessing(Elaboratable): 820 | """TX Datapath 821 | 822 | This module realizes the: 823 | - Clock compensation (SKP insertion). 824 | - Clock domain crossing (from system clock to transceiver's TX clock). 825 | - Data-width adaptation (from 32-bit to transceiver's data-width). 826 | """ 827 | def __init__(self, clock_domain="sync"): 828 | self.sink = USBRawSuperSpeedStream(payload_words=4) 829 | self.source = USBRawSuperSpeedStream(payload_words=2) 830 | 831 | 832 | def elaborate(self, platform): 833 | m = Module() 834 | 835 | # 836 | # Clock tolerance compensation 837 | # 838 | #m.submodules.skip_inserter = skip_inserter = TXSKPInserter() 839 | #m.d.comb += skip_inserter.sink.stream_eq(self.sink) 840 | 841 | 842 | # 843 | # Output gearing (& clock-domain crossing) 844 | # 845 | m.submodules.gearing = gearing = TransmitterGearbox( 846 | output_domain = "tx", 847 | input_domain = "ss", 848 | ) 849 | m.d.comb += gearing.sink.stream_eq(self.sink) 850 | #self.comb += gearing.sink.stream_eq(skip_inserter.source) 851 | 852 | # 853 | # Final output 854 | # 855 | m.d.comb += self.source.stream_eq(gearing.source) 856 | 857 | return m 858 | 859 | 860 | 861 | if __name__ == "__main__": 862 | unittest.main() 863 | -------------------------------------------------------------------------------- /bucatini/backends/ecp5.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Bucatini. 3 | # 4 | # Copyright (c) 2021 Great Scott Gadgets 5 | # Copyright (c) 2019-2020 Florent Kermarrec 6 | # Copyright (c) 2019-2020 whitequark@whitequark.org 7 | # 8 | # The ECP5's DCU parameters/signals/instance have been partially documented by whitequark 9 | # as part of the Yumewatari project: https://github.com/whitequark/Yumewatari. 10 | # 11 | # Code initially ported to nMigen from ``LiteICLink`` and adapted for Bucatini. 12 | # SPDX-License-Identifier: BSD-3-Clause 13 | """ SerDes backend for the ECP5. """ 14 | 15 | 16 | from nmigen import * 17 | from nmigen.lib.cdc import FFSynchronizer, ResetSynchronizer 18 | 19 | from ....usb.stream import USBRawSuperSpeedStream 20 | from ..datapath import TransmitPreprocessing, ReceivePostprocessing 21 | from ..lfps import LFPSSquareWaveDetector 22 | 23 | 24 | class ECP5SerDesPLLConfiguration: 25 | def __init__(self, refclk, refclk_freq, linerate): 26 | self.refclk = refclk 27 | self.config = self.compute_config(refclk_freq, linerate) 28 | 29 | @staticmethod 30 | def compute_config(refclk_freq, linerate): 31 | for mult in [8, 10, 16, 20, 25]: 32 | current_linerate = refclk_freq*mult 33 | if current_linerate == linerate: 34 | return { 35 | "mult": mult, 36 | "refck_freq": refclk_freq, 37 | "linerate": linerate, 38 | } 39 | msg = "No config found for {:3.2f} MHz refclk / {:3.2f} Gbps linerate." 40 | raise ValueError(msg.format(refclk_freq/1e6, linerate/1e9)) 41 | 42 | 43 | 44 | class ECP5SerDesConfigInterface(Elaboratable): 45 | """ Module that interfaces with the ECP5's SerDes Client Interface (SCI). """ 46 | 47 | def __init__(self, serdes): 48 | self._serdes = serdes 49 | 50 | # 51 | # I/O port 52 | # 53 | 54 | # Control interface. 55 | self.dual_sel = Signal() 56 | self.chan_sel = Signal() 57 | self.re = Signal() 58 | self.we = Signal() 59 | self.done = Signal() 60 | self.adr = Signal(6) 61 | self.dat_w = Signal(8) 62 | self.dat_r = Signal(8) 63 | 64 | # SCI interface. 65 | self.sci_rd = Signal() 66 | self.sci_wrn = Signal() 67 | self.sci_addr = Signal(6) 68 | self.sci_wdata = Signal(8) 69 | self.sci_rdata = Signal(8) 70 | 71 | 72 | 73 | def elaborate(self, platform): 74 | m = Module() 75 | 76 | m.d.comb += [ 77 | self.sci_wrn.eq(1), 78 | 79 | self.sci_addr.eq(self.adr), 80 | self.sci_wdata.eq(self.dat_w) 81 | ] 82 | 83 | with m.FSM(domain="ss"): 84 | 85 | with m.State("IDLE"): 86 | m.d.comb += self.done.eq(1) 87 | 88 | with m.If(self.we): 89 | m.next = "WRITE" 90 | with m.Elif(self.re): 91 | m.d.comb += self.sci_rd.eq(1), 92 | m.next = "READ" 93 | 94 | with m.State("WRITE"): 95 | m.d.comb += self.sci_wrn.eq(0) 96 | m.next = "IDLE" 97 | 98 | 99 | with m.State("READ"): 100 | m.d.comb += self.sci_rd.eq(1) 101 | m.d.ss += self.dat_r.eq(self.sci_rdata) 102 | m.next = "IDLE" 103 | 104 | return m 105 | 106 | 107 | class ECP5SerDesRegisterTranslator(Elaboratable): 108 | """ Interface that converts control signals into SerDes register reads and writes. """ 109 | 110 | def __init__(self, serdes, sci): 111 | self._serdes = serdes 112 | self._sci = sci 113 | 114 | # 115 | # I/O port 116 | # 117 | self.loopback = Signal() 118 | self.rx_polarity = Signal() 119 | self.tx_idle = Signal() 120 | self.tx_polarity = Signal() 121 | 122 | 123 | def elaborate(self, platform): 124 | m = Module() 125 | sci = self._sci 126 | 127 | first = Signal() 128 | data = Signal(8) 129 | 130 | 131 | with m.FSM(domain="ss"): 132 | 133 | with m.State("IDLE"): 134 | m.d.ss += first.eq(1) 135 | m.next = "READ-CH_01" 136 | 137 | with m.State("READ-CH_01"): 138 | m.d.ss += first.eq(0) 139 | m.d.comb += [ 140 | sci.chan_sel.eq(1), 141 | sci.re.eq(1), 142 | sci.adr.eq(0x01), 143 | ] 144 | 145 | with m.If(~first & sci.done): 146 | m.d.comb += sci.re.eq(0) 147 | m.d.ss += [ 148 | data.eq(sci.dat_r), 149 | first.eq(1) 150 | ] 151 | m.next = "WRITE-CH_01" 152 | 153 | 154 | with m.State("WRITE-CH_01"): 155 | m.d.ss += first.eq(0) 156 | m.d.comb += [ 157 | sci.chan_sel.eq(1), 158 | sci.we.eq(1), 159 | sci.adr.eq(0x01), 160 | sci.dat_w.eq(data), 161 | sci.dat_w[0].eq(self.rx_polarity), 162 | sci.dat_w[1].eq(self.tx_polarity), 163 | ] 164 | with m.If(~first & sci.done): 165 | m.d.comb += sci.we.eq(0) 166 | m.d.ss += first.eq(1) 167 | m.next = "READ-CH_02" 168 | 169 | with m.State("READ-CH_02"): 170 | m.d.ss += first.eq(0) 171 | m.d.comb += [ 172 | sci.chan_sel.eq(1), 173 | sci.re.eq(1), 174 | sci.adr.eq(0x02), 175 | ] 176 | 177 | with m.If(~first & sci.done): 178 | m.d.comb += sci.re.eq(0) 179 | m.d.ss += data.eq(sci.dat_r) 180 | m.next = "WRITE-CH_02" 181 | 182 | with m.State("WRITE-CH_02"): 183 | m.d.ss += first.eq(0) 184 | m.d.comb += [ 185 | sci.chan_sel.eq(1), 186 | sci.we.eq(1), 187 | sci.adr.eq(0x02), 188 | sci.dat_w.eq(data), 189 | sci.dat_w[6].eq(self.tx_idle), # pcie_ei_en 190 | ] 191 | 192 | with m.If(~first & sci.done): 193 | m.d.ss += first.eq(1) 194 | m.d.comb += sci.we.eq(0) 195 | m.next = "READ-CH_15" 196 | 197 | with m.State("READ-CH_15"): 198 | m.d.ss += first.eq(0) 199 | m.d.comb += [ 200 | sci.chan_sel.eq(1), 201 | sci.re.eq(1), 202 | sci.adr.eq(0x15), 203 | ] 204 | 205 | with m.If(~first & sci.done): 206 | m.d.ss += first.eq(1) 207 | m.d.comb += sci.re.eq(0) 208 | m.d.ss += data.eq(sci.dat_r) 209 | m.next = "WRITE-CH_15" 210 | 211 | with m.State("WRITE-CH_15"): 212 | m.d.ss += first.eq(0) 213 | m.d.comb += [ 214 | sci.chan_sel.eq(1), 215 | sci.we.eq(1), 216 | sci.adr.eq(0x15), 217 | sci.dat_w.eq(data), 218 | ] 219 | with m.If(self.loopback): 220 | m.d.comb += sci.dat_w[0:4].eq(0b0001) # lb_ctl 221 | 222 | with m.If(~first & sci.done): 223 | m.d.comb += sci.we.eq(0) 224 | m.next = "IDLE" 225 | 226 | return m 227 | 228 | 229 | 230 | class ECP5SerDesEqualizerInterface(Elaboratable): 231 | """ Interface that controls the ECP5 SerDes' equalization settings via SCI. 232 | 233 | Currently takes full ownership of the SerDes Client Interface. 234 | 235 | This unit allows runtime changing of the SerDes' equalizer settings. 236 | 237 | Attributes 238 | ---------- 239 | enable_equalizer: Signal(), input 240 | Assert to enable the SerDes' equalizer. 241 | equalizer_pole: Signal(4), input 242 | Selects the pole used for the input equalization; ostensibly shifting the knee on the 243 | linear equalizer. The meaning of these values are not documented by Lattice. 244 | equalizer_level: Signal(2), input 245 | Selects the equalizer's gain. 0 = 6dB, 1 = 9dB, 2 = 12dB, 3 = undocumented. 246 | Note that the value `3` is marked as "not used" in the SerDes manual; but then used anyway 247 | by Lattice's reference designs. 248 | """ 249 | 250 | SERDES_EQUALIZATION_REGISTER = 0x19 251 | 252 | def __init__(self, sci, serdes_channel): 253 | self._sci = sci 254 | self._channel = serdes_channel 255 | 256 | # 257 | # I/O port 258 | # 259 | self.enable_equalizer = Signal() 260 | self.equalizer_pole = Signal(4) 261 | self.equalizer_level = Signal(2) 262 | 263 | 264 | def elaborate(self, platform): 265 | m = Module() 266 | sci = self._sci 267 | 268 | # Build the value to be written into the SCI equalizer register. 269 | m.d.comb += [ 270 | sci.dat_w[0] .eq(self.enable_equalizer), 271 | sci.dat_w[1:5] .eq(self.equalizer_pole), 272 | sci.dat_w[5:7] .eq(self.equalizer_level), 273 | 274 | # Set up a write to the equalizer control register. 275 | sci.chan_sel .eq(self._channel), 276 | sci.we .eq(1), 277 | sci.adr .eq(self.SERDES_EQUALIZATION_REGISTER), 278 | ] 279 | 280 | return m 281 | 282 | 283 | 284 | 285 | class ECP5SerDesEqualizer(Elaboratable): 286 | """ Interface that controls the ECP5 SerDes' equalization settings via SCI. 287 | 288 | Currently takes full ownership of the SerDes Client Interface. 289 | 290 | Ideally, an analog-informed receiver equalization would occur during USB3 link training. However, 291 | we're at best a simulacrum of a USB3 PHY built on an undocumented SerDes; so we'll do the best we 292 | can by measuring 8b10b encoding errors and trying various equalization settings until we've "minimized" 293 | bit error rate. 294 | 295 | Attributes 296 | ---------- 297 | train_equalizer: Signal(), input 298 | When high, this unit attempts to train the Rx linear equalizer in order to minimize errors. 299 | This should be held only when a spectrally-rich data set is present, such as a training sequence. 300 | 301 | encoding_error_detected: Signal(), input 302 | Strobe; should be high each time the SerDes encounters an 8b10b encoding error. 303 | """ 304 | 305 | # We'll try each equalizer setting for ~1024 cycles. 306 | # This value could easily be higher; but the higher this goes, the slower our counters 307 | # get; and we're operating in our fast, edge domain. 308 | CYCLES_PER_TRIAL = 127 309 | 310 | 311 | def __init__(self, sci, channel): 312 | self._sci = sci 313 | self._channel = channel 314 | 315 | # 316 | # I/O port 317 | # 318 | self.train_equalizer = Signal() 319 | self.encoding_error_detected = Signal() 320 | 321 | 322 | def elaborate(self, platform): 323 | m = Module() 324 | 325 | # 326 | # Equalizer interface. 327 | # 328 | m.submodules.interface = interface = ECP5SerDesEqualizerInterface( 329 | sci=self._sci, 330 | serdes_channel=self._channel 331 | ) 332 | 333 | # 334 | # Bit error counter. 335 | # 336 | clear_errors = Signal() 337 | bit_errors_seen = Signal(range(self.CYCLES_PER_TRIAL + 1)) 338 | 339 | with m.If(clear_errors): 340 | m.d.tx += bit_errors_seen.eq(0) 341 | with m.Elif(self.encoding_error_detected): 342 | m.d.tx += bit_errors_seen.eq(bit_errors_seen + 1) 343 | 344 | 345 | # 346 | # Naive equalization trainer. 347 | # 348 | 349 | # We'll use the naive-est possible algorithm: we'll try every setting and see what 350 | # minimizes bit error rate. This could definitely be improved upon, but without documentation 351 | # for the equalizer, we're best going for an exhaustive approach. 352 | 353 | # We'll track six bits, as we have four bits of pole and two bits of gain we want to try. 354 | current_settings = Signal(6) 355 | m.d.comb += [ 356 | interface.enable_equalizer .eq(1), 357 | Cat(interface.equalizer_level, interface.equalizer_pole) .eq(current_settings) 358 | ] 359 | 360 | # Keep track of the best equalizer setting seen thus far. 361 | best_equalizer_setting = Signal.like(current_settings) 362 | best_bit_error_count = Signal.like(bit_errors_seen) 363 | 364 | # Keep track of how long we've been in this trial. 365 | cycles_spent_in_trial = Signal(range(self.CYCLES_PER_TRIAL)) 366 | 367 | 368 | # If we're actively training the equalizer... 369 | with m.If(self.train_equalizer): 370 | m.d.tx += cycles_spent_in_trial.eq(cycles_spent_in_trial + 1) 371 | 372 | # If we're finishing a trial... 373 | with m.If(cycles_spent_in_trial == (self.CYCLES_PER_TRIAL - 1)): 374 | 375 | # ... clear our error count... 376 | m.d.comb += clear_errors.eq(1) 377 | 378 | # ... move to the next set of settings ... 379 | m.d.tx += current_settings.eq(current_settings + 1) 380 | 381 | # ... and if this is a new best, store it. 382 | with m.If(bit_errors_seen < best_bit_error_count): 383 | m.d.tx += [ 384 | best_bit_error_count .eq(bit_errors_seen), 385 | best_equalizer_setting .eq(current_settings) 386 | ] 387 | 388 | # If we're not currently in training, always apply our known best settings. 389 | with m.Else(): 390 | m.d.tx += current_settings.eq(best_equalizer_setting) 391 | 392 | 393 | return m 394 | 395 | 396 | class ECP5ResetSequencer(Elaboratable): 397 | """ Reset sequencer; ensures that the PLL starts in the correct state. """ 398 | 399 | def __init__(self): 400 | 401 | # 402 | # I/O port 403 | # 404 | 405 | # Status in. 406 | self.rx_pll_locked = Signal() 407 | self.tx_pll_locked = Signal() 408 | 409 | # Reset out. 410 | self.serdes_tx_reset = Signal() 411 | self.serdes_rx_reset = Signal() 412 | self.pcs_reset = Signal() 413 | 414 | # Status out. 415 | self.complete = Signal() 416 | 417 | 418 | def elaborate(self, platform): 419 | m = Module() 420 | 421 | # Per [TN1261-21: "Reset Sequence"], the SerDes requires the following bring-up ordering: 422 | # 1. Start the SerDes Tx, and then wait for its PLL lock. 423 | # 2. Release the SerDes Rx reset, and then wait for the SerDes' internal bit clock to be asserted. 424 | # We see this as the Rx PLL locking. 425 | # 3. Release the PCS reset. 426 | 427 | with m.FSM(domain="fast"): 428 | 429 | # Hold everything in reset, initially. 430 | with m.State("INITIAL_RESET"): 431 | m.d.comb += [ 432 | self.serdes_tx_reset .eq(1), 433 | self.serdes_rx_reset .eq(1), 434 | self.pcs_reset .eq(1) 435 | ] 436 | 437 | # Once we've strobed our reset, wait for the transmitter to start up. 438 | m.next = "WAIT_FOR_TRANSMITTER_STARTUP" 439 | 440 | 441 | # Hold the receiver and PLL in reset until the transmitter starts up. 442 | with m.State("WAIT_FOR_TRANSMITTER_STARTUP"): 443 | m.d.comb += [ 444 | self.serdes_rx_reset .eq(1), 445 | self.pcs_reset .eq(1), 446 | ] 447 | 448 | # We know the transmitter has started up once its PLL is locked. 449 | with m.If(self.tx_pll_locked): 450 | m.next = "WAIT_FOR_RECEIVER_STARTUP" 451 | 452 | 453 | # Hold the protocol engine in reset until the receiver starts up. 454 | with m.State("WAIT_FOR_RECEIVER_STARTUP"): 455 | m.d.comb += self.pcs_reset.eq(1) 456 | 457 | # We know the receiver has started up once its PLL is locked. 458 | with m.If(self.rx_pll_locked): 459 | m.next = "STARTED_UP" 460 | 461 | 462 | # Finally, we're all started up. Assert no resets. 463 | with m.State("STARTED_UP"): 464 | m.d.comb += self.complete.eq(1) 465 | 466 | 467 | return m 468 | 469 | 470 | class ECP5SerDes(Elaboratable): 471 | """ Abstraction layer for working with the ECP5 SerDes. """ 472 | 473 | def __init__(self, pll_config, tx_pads, rx_pads, dual=0, channel=0): 474 | assert dual in [0, 1] 475 | assert channel in [0, 1] 476 | 477 | self._pll = pll_config 478 | self._tx_pads = tx_pads 479 | self._rx_pads = rx_pads 480 | self._dual = dual 481 | self._channel = channel 482 | 483 | # For now, we'll always operate with 2x gearing -- which means that we internally are working 484 | # with 20 bits of 8b10b encoded data. 485 | self._io_words = 2 486 | self._io_data_width = 8 * self._io_words 487 | 488 | # 489 | # I/O port. 490 | # 491 | 492 | self.train_equalizer = Signal() 493 | 494 | # Core Rx and Tx lines. 495 | self.sink = USBRawSuperSpeedStream(payload_words=self._io_words) 496 | self.source = USBRawSuperSpeedStream(payload_words=self._io_words) 497 | 498 | # TX controls 499 | self.tx_enable = Signal(reset=1) 500 | self.tx_ready = Signal() 501 | self.tx_inhibit = Signal() # FIXME 502 | self.tx_produce_square_wave = Signal() 503 | self.tx_produce_pattern = Signal() 504 | self.tx_pattern = Signal(20) 505 | self.tx_idle = Signal() 506 | self.tx_invert = Signal() 507 | self.tx_gpio_en = Signal() 508 | self.tx_gpio = Signal() 509 | 510 | # RX controls 511 | self.rx_enable = Signal(reset=1) 512 | self.rx_ready = Signal() 513 | self.rx_align = Signal(reset=1) 514 | self.rx_idle = Signal() 515 | self.rx_invert = Signal() 516 | self.rx_gpio = Signal() 517 | 518 | # Loopback 519 | self.loopback = Signal() # FIXME: reconfigure lb_ctl to 0b0001 but does not seem enough 520 | 521 | 522 | def elaborate(self, platform): 523 | m = Module() 524 | 525 | # The ECP5 SerDes uses a simple feedback mechanism to keep its FIFO clocks in sync 526 | # with the FPGA's fabric. Accordingly, we'll need to capture the output clocks and then 527 | # pass them back to the SerDes; this allows the placer to handle clocking correctly, allows us 528 | # to attach clock constraints for analysis, and allows us to use these clocks for -very- simple tasks. 529 | txoutclk = Signal() 530 | rxoutclk = Signal() 531 | 532 | 533 | # Internal state. 534 | rx_los = Signal() 535 | rx_lol = Signal() 536 | rx_lsm = Signal() 537 | rx_align = Signal() 538 | rx_bus = Signal(24) 539 | 540 | tx_lol = Signal() 541 | tx_bus = Signal(24) 542 | 543 | 544 | # 545 | # Clock domain crossing. 546 | # 547 | tx_produce_square_wave = Signal() 548 | tx_produce_pattern = Signal() 549 | tx_pattern = Signal(20) 550 | 551 | m.submodules += [ 552 | # Transmit control synchronization. 553 | FFSynchronizer(self.tx_produce_square_wave, tx_produce_square_wave, o_domain="tx"), 554 | FFSynchronizer(self.tx_produce_pattern, tx_produce_pattern, o_domain="tx"), 555 | FFSynchronizer(self.tx_pattern, tx_pattern, o_domain="tx"), 556 | 557 | # Receive control synchronization. 558 | FFSynchronizer(self.rx_align, rx_align, o_domain="rx"), 559 | FFSynchronizer(rx_los, self.rx_idle, o_domain="sync"), 560 | ] 561 | 562 | # 563 | # Clocking / reset control. 564 | # 565 | 566 | # The SerDes needs to be brought up gradually; we'll do that here. 567 | m.submodules.reset_sequencer = reset = ECP5ResetSequencer() 568 | m.d.comb += [ 569 | reset.tx_pll_locked .eq(~tx_lol), 570 | reset.rx_pll_locked .eq(~rx_lol) 571 | ] 572 | 573 | 574 | # Create a local transmit domain, for our transmit-side hardware. 575 | m.domains.tx = ClockDomain() 576 | m.d.comb += ClockSignal("tx").eq(txoutclk) 577 | m.submodules += [ 578 | ResetSynchronizer(ResetSignal("sync"), domain="tx"), 579 | FFSynchronizer(~ResetSignal("tx"), self.tx_ready) 580 | ] 581 | 582 | # Create the same setup, buf for the receive side. 583 | m.domains.rx = ClockDomain() 584 | m.d.comb += ClockSignal("rx").eq(rxoutclk) 585 | m.submodules += [ 586 | ResetSynchronizer(ResetSignal("sync"), domain="rx"), 587 | FFSynchronizer(~ResetSignal("rx"), self.rx_ready) 588 | ] 589 | 590 | # 591 | # Core SerDes instantiation. 592 | # 593 | serdes_params = dict( 594 | # DCU — power management 595 | p_D_MACROPDB = "0b1", 596 | p_D_IB_PWDNB = "0b1", # undocumented (required for RX) 597 | p_D_TXPLL_PWDNB = "0b1", 598 | i_D_FFC_MACROPDB = 1, 599 | 600 | # DCU — reset 601 | i_D_FFC_MACRO_RST = ResetSignal("sync"), 602 | i_D_FFC_DUAL_RST = ResetSignal("sync"), 603 | 604 | # DCU — clocking 605 | i_D_REFCLKI = self._pll.refclk, 606 | o_D_FFS_PLOL = tx_lol, 607 | p_D_REFCK_MODE = { 608 | 25: "0b100", 609 | 20: "0b000", 610 | 16: "0b010", 611 | 10: "0b001", 612 | 8: "0b011"}[self._pll.config["mult"]], 613 | p_D_TX_MAX_RATE = "5.0", # 5.0 Gbps 614 | p_D_TX_VCO_CK_DIV = { 615 | 32: "0b111", 616 | 16: "0b110", 617 | 8: "0b101", 618 | 4: "0b100", 619 | 2: "0b010", 620 | 1: "0b000"}[1], # DIV/1 621 | p_D_BITCLK_LOCAL_EN = "0b1", # Use clock from local PLL 622 | 623 | 624 | # Clock multiplier unit configuration 625 | p_D_CMUSETBIASI = "0b00", # begin undocumented (10BSER sample code used) 626 | p_D_CMUSETI4CPP = "0d3", 627 | p_D_CMUSETI4CPZ = "0d3", 628 | p_D_CMUSETI4VCO = "0b00", 629 | p_D_CMUSETICP4P = "0b01", 630 | p_D_CMUSETICP4Z = "0b101", 631 | p_D_CMUSETINITVCT = "0b00", 632 | p_D_CMUSETISCL4VCO = "0b000", 633 | p_D_CMUSETP1GM = "0b000", 634 | p_D_CMUSETP2AGM = "0b000", 635 | p_D_CMUSETZGM = "0b000", 636 | 637 | p_D_SETIRPOLY_AUX = "0b01", 638 | p_D_SETICONST_AUX = "0b01", 639 | p_D_SETIRPOLY_CH = "0b01", 640 | p_D_SETICONST_CH = "0b10", 641 | p_D_SETPLLRC = "0d1", 642 | p_D_RG_EN = "0b0", 643 | p_D_RG_SET = "0b00", 644 | p_D_REQ_ISET = "0b011", 645 | p_D_PD_ISET = "0b11", # end undocumented 646 | 647 | # DCU — FIFOs 648 | p_D_LOW_MARK = "0d4", # Clock compensation FIFO low water mark (mean=8) 649 | p_D_HIGH_MARK = "0d12", # Clock compensation FIFO high water mark (mean=8) 650 | 651 | # CHX common --------------------------------------------------------------------------- 652 | # CHX — protocol 653 | p_CHX_PROTOCOL = "10BSER", 654 | p_CHX_UC_MODE = "0b1", 655 | 656 | p_CHX_ENC_BYPASS = "0b0", # Use the 8b10b encoder 657 | p_CHX_DEC_BYPASS = "0b0", # Use the 8b10b decoder 658 | 659 | # CHX receive -------------------------------------------------------------------------- 660 | # CHX RX ­— power management 661 | p_CHX_RPWDNB = "0b1", 662 | i_CHX_FFC_RXPWDNB = 1, 663 | 664 | # CHX RX ­— reset 665 | i_CHX_FFC_RRST = ~self.rx_enable | reset.serdes_rx_reset, 666 | i_CHX_FFC_LANE_RX_RST = ~self.rx_enable | reset.pcs_reset, 667 | 668 | # CHX RX ­— input 669 | i_CHX_HDINP = self._rx_pads.p, 670 | i_CHX_HDINN = self._rx_pads.n, 671 | 672 | p_CHX_REQ_EN = "0b1", # Enable equalizer 673 | p_CHX_REQ_LVL_SET = "0b01", 674 | p_CHX_RX_RATE_SEL = "0d09", # Equalizer pole position 675 | p_CHX_RTERM_RX = { 676 | "5k-ohms": "0b00000", 677 | "80-ohms": "0b00001", 678 | "75-ohms": "0b00100", 679 | "70-ohms": "0b00110", 680 | "60-ohms": "0b01011", 681 | "50-ohms": "0b10011", 682 | "46-ohms": "0b11001", 683 | "wizard-50-ohms": "0d22"}["wizard-50-ohms"], 684 | p_CHX_RXIN_CM = "0b11", # CMFB (wizard value used) 685 | p_CHX_RXTERM_CM = "0b10", # RX Input (wizard value used) 686 | 687 | # CHX RX ­— clocking 688 | i_CHX_RX_REFCLK = self._pll.refclk, 689 | o_CHX_FF_RX_PCLK = rxoutclk, 690 | i_CHX_FF_RXI_CLK = ClockSignal("rx"), 691 | 692 | p_CHX_CDR_MAX_RATE = "5.0", # 5.0 Gbps 693 | p_CHX_RX_DCO_CK_DIV = { 694 | 32: "0b111", 695 | 16: "0b110", 696 | 8: "0b101", 697 | 4: "0b100", 698 | 2: "0b010", 699 | 1: "0b000"}[1], # DIV/1 700 | p_CHX_RX_GEAR_MODE = "0b1", # 1:2 gearbox 701 | p_CHX_FF_RX_H_CLK_EN = "0b1", # enable DIV/2 output clock 702 | p_CHX_FF_RX_F_CLK_DIS = "0b1", # disable DIV/1 output clock 703 | p_CHX_SEL_SD_RX_CLK = "0b1", # FIFO driven by recovered clock 704 | 705 | p_CHX_AUTO_FACQ_EN = "0b1", # undocumented (wizard value used) 706 | p_CHX_AUTO_CALIB_EN = "0b1", # undocumented (wizard value used) 707 | p_CHX_PDEN_SEL = "0b0", # phase detector disabled on LOS 708 | 709 | p_CHX_DCOATDCFG = "0b00", # begin undocumented (sample code used) 710 | p_CHX_DCOATDDLY = "0b00", 711 | p_CHX_DCOBYPSATD = "0b1", 712 | p_CHX_DCOCALDIV = "0b000", 713 | p_CHX_DCOCTLGI = "0b011", 714 | p_CHX_DCODISBDAVOID = "0b0", 715 | p_CHX_DCOFLTDAC = "0b00", 716 | p_CHX_DCOFTNRG = "0b001", 717 | p_CHX_DCOIOSTUNE = "0b010", 718 | p_CHX_DCOITUNE = "0b00", 719 | p_CHX_DCOITUNE4LSB = "0b010", 720 | p_CHX_DCOIUPDNX2 = "0b1", 721 | p_CHX_DCONUOFLSB = "0b100", 722 | p_CHX_DCOSCALEI = "0b01", 723 | p_CHX_DCOSTARTVAL = "0b010", 724 | p_CHX_DCOSTEP = "0b11", # end undocumented 725 | 726 | # CHX RX — loss of signal 727 | o_CHX_FFS_RLOS = rx_los, 728 | p_CHX_RLOS_SEL = "0b1", 729 | p_CHX_RX_LOS_EN = "0b0", 730 | p_CHX_RX_LOS_LVL = "0b101", # Lattice "TBD" (wizard value used) 731 | p_CHX_RX_LOS_CEQ = "0b11", # Lattice "TBD" (wizard value used) 732 | p_CHX_RX_LOS_HYST_EN = "0b1", 733 | 734 | # CHX RX — loss of lock 735 | o_CHX_FFS_RLOL = rx_lol, 736 | 737 | # CHX RX — link state machine 738 | # Note that Lattice Diamond needs these in their provided bases (and string lengths!). 739 | # Changing their bases will work with the open toolchain, but will make Diamond mad. 740 | i_CHX_FFC_SIGNAL_DETECT = rx_align, 741 | 742 | o_CHX_FFS_LS_SYNC_STATUS= rx_lsm, 743 | 744 | p_CHX_ENABLE_CG_ALIGN = "0b1", 745 | 746 | p_CHX_UDF_COMMA_MASK = "0x0ff", # compare the 8 lsbs 747 | p_CHX_UDF_COMMA_A = "0x003", # "0b0000000011", # K28.1, K28.5 and K28.7 748 | p_CHX_UDF_COMMA_B = "0x07c", # "0b0001111100", # K28.1, K28.5 and K28.7 749 | 750 | 751 | p_CHX_CTC_BYPASS = "0b1", # bypass CTC FIFO 752 | p_CHX_MIN_IPG_CNT = "0b11", # minimum interpacket gap of 4 753 | p_CHX_MATCH_2_ENABLE = "0b0", # 2 character skip matching 754 | p_CHX_MATCH_4_ENABLE = "0b0", # 4 character skip matching 755 | p_CHX_CC_MATCH_1 = "0x000", 756 | p_CHX_CC_MATCH_2 = "0x000", 757 | p_CHX_CC_MATCH_3 = "0x000", 758 | p_CHX_CC_MATCH_4 = "0x000", 759 | 760 | # CHX RX — data 761 | **{"o_CHX_FF_RX_D_%d" % n: rx_bus[n] for n in range(len(rx_bus))}, 762 | 763 | # CHX transmit ------------------------------------------------------------------------- 764 | # CHX TX — power management 765 | p_CHX_TPWDNB = "0b1", 766 | i_CHX_FFC_TXPWDNB = 1, 767 | 768 | # CHX TX ­— reset 769 | i_D_FFC_TRST = ~self.tx_enable | reset.serdes_tx_reset, 770 | i_CHX_FFC_LANE_TX_RST = ~self.tx_enable | reset.pcs_reset, 771 | 772 | # CHX TX ­— output 773 | o_CHX_HDOUTP = self._tx_pads.p, 774 | o_CHX_HDOUTN = self._tx_pads.n, 775 | 776 | p_CHX_TXAMPLITUDE = "0d1000", # 1000 mV 777 | p_CHX_RTERM_TX = { 778 | "5k-ohms": "0b00000", 779 | "80-ohms": "0b00001", 780 | "75-ohms": "0b00100", 781 | "70-ohms": "0b00110", 782 | "60-ohms": "0b01011", 783 | "50-ohms": "0b10011", 784 | "46-ohms": "0b11001", 785 | "wizard-50-ohms": "0d19"}["50-ohms"], 786 | 787 | p_CHX_TDRV_SLICE0_CUR = "0b011", # 400 uA 788 | p_CHX_TDRV_SLICE0_SEL = "0b01", # main data 789 | p_CHX_TDRV_SLICE1_CUR = "0b000", # 100 uA 790 | p_CHX_TDRV_SLICE1_SEL = "0b00", # power down 791 | p_CHX_TDRV_SLICE2_CUR = "0b11", # 3200 uA 792 | p_CHX_TDRV_SLICE2_SEL = "0b01", # main data 793 | p_CHX_TDRV_SLICE3_CUR = "0b10", # 2400 uA 794 | p_CHX_TDRV_SLICE3_SEL = "0b01", # main data 795 | p_CHX_TDRV_SLICE4_CUR = "0b00", # 800 uA 796 | p_CHX_TDRV_SLICE4_SEL = "0b00", # power down 797 | p_CHX_TDRV_SLICE5_CUR = "0b00", # 800 uA 798 | p_CHX_TDRV_SLICE5_SEL = "0b00", # power down 799 | 800 | # CHX TX ­— clocking 801 | o_CHX_FF_TX_PCLK = txoutclk, 802 | i_CHX_FF_TXI_CLK = ClockSignal("tx"), 803 | 804 | p_CHX_TX_GEAR_MODE = "0b1", # 1:2 gearbox 805 | p_CHX_FF_TX_H_CLK_EN = "0b1", # enable DIV/2 output clock 806 | p_CHX_FF_TX_F_CLK_DIS = "0b1", # disable DIV/1 output clock 807 | 808 | # CHX TX — data 809 | **{"i_CHX_FF_TX_D_%d" % n: tx_bus[n] for n in range(len(tx_bus))}, 810 | 811 | # SCI interface. 812 | #**{"i_D_SCIWDATA%d" % n: sci.sci_wdata[n] for n in range(8)}, 813 | #**{"i_D_SCIADDR%d" % n: sci.sci_addr[n] for n in range(6)}, 814 | #**{"o_D_SCIRDATA%d" % n: sci.sci_rdata[n] for n in range(8)}, 815 | #i_D_SCIENAUX = sci.dual_sel, 816 | #i_D_SCISELAUX = sci.dual_sel, 817 | #i_CHX_SCIEN = sci.chan_sel, 818 | #i_CHX_SCISEL = sci.chan_sel, 819 | #i_D_SCIRD = sci.sci_rd, 820 | #i_D_SCIWSTN = sci.sci_wrn, 821 | 822 | # Out-of-band signaling Rx support. 823 | p_CHX_LDR_RX2CORE_SEL = "0b1", # Enables low-speed out-of-band input. 824 | o_CHX_LDR_RX2CORE = self.rx_gpio, 825 | 826 | # Out-of-band signaling Tx support. 827 | p_CHX_LDR_CORE2TX_SEL = "0b0", # Uses CORE2TX_EN to enable out-of-band output. 828 | i_CHX_LDR_CORE2TX = self.tx_gpio, 829 | i_CHX_FFC_LDR_CORE2TX_EN = self.tx_gpio_en 830 | ) 831 | 832 | # Translate the 'CHX' string to the correct channel name in each of our SerDes parameters, 833 | # and create our SerDes instance. 834 | serdes_params = {k.replace("CHX", f"CH{self._channel}"):v for (k,v) in serdes_params.items()} 835 | m.submodules.serdes = serdes = Instance("DCUA", **serdes_params) 836 | 837 | # Bind our SerDes to the correct location inside the FPGA. 838 | serdes.attrs["LOC"] = "DCU{}".format(self._dual) 839 | serdes.attrs["CHAN"] = "CH{}".format(self._channel) 840 | serdes.attrs["BEL"] = "X42/Y71/DCU" 841 | 842 | # 843 | # TX and RX datapaths (SerDes <-> stream conversion) 844 | # 845 | sink = self.sink 846 | source = self.source 847 | 848 | m.d.comb += [ 849 | # Grab our received data directly from our SerDes; modifying things to match the 850 | # SerDes Rx bus layout, which squishes status signals between our two geared words. 851 | source.data[0: 8] .eq(rx_bus[ 0: 8]), 852 | source.data[8:16] .eq(rx_bus[12:20]), 853 | source.ctrl[0] .eq(rx_bus[8]), 854 | source.ctrl[1] .eq(rx_bus[20]), 855 | source.valid .eq(1), 856 | 857 | # Stick the data we'd like to transmit into the SerDes; again modifying things to match 858 | # the transmit bus layout. 859 | tx_bus[ 0: 8] .eq(sink.data[0: 8]), 860 | tx_bus[12:20] .eq(sink.data[8:16]), 861 | tx_bus[8] .eq(sink.ctrl[0]), 862 | tx_bus[20] .eq(sink.ctrl[1]), 863 | sink.ready .eq(1) 864 | ] 865 | 866 | 867 | return m 868 | 869 | 870 | 871 | class LunaECP5SerDes(Elaboratable): 872 | """ Wrapper around the core ECP5 SerDes that optimizes the SerDes for USB3 use. """ 873 | 874 | def __init__(self, platform, sys_clk, sys_clk_freq, refclk_pads, refclk_freq, 875 | tx_pads, rx_pads, channel, dual=0, refclk_num=None, fast_clock_frequency=250e6): 876 | self._primary_clock = sys_clk 877 | self._primary_clock_frequency = sys_clk_freq 878 | self._refclk = refclk_pads 879 | self._refclk_frequency = refclk_freq 880 | self._tx_pads = tx_pads 881 | self._rx_pads = rx_pads 882 | self._channel = channel 883 | self._dual = dual 884 | self._refclk_num = refclk_num if refclk_num else dual 885 | self._fast_clock_frequency = 250e6 886 | 887 | # 888 | # I/O port 889 | # 890 | self.sink = USBRawSuperSpeedStream() 891 | self.source = USBRawSuperSpeedStream() 892 | 893 | self.enable = Signal(reset=1) # i 894 | self.ready = Signal() # o 895 | 896 | self.train_equalizer = Signal() 897 | 898 | self.tx_polarity = Signal() # i 899 | self.tx_idle = Signal() # i 900 | self.tx_pattern = Signal(20) # i 901 | 902 | self.rx_polarity = Signal() # i 903 | self.rx_idle = Signal() # o 904 | self.rx_align = Signal() # i 905 | 906 | # GPIO interface. 907 | self.use_tx_as_gpio = Signal() 908 | self.tx_gpio = Signal() 909 | self.rx_gpio = Signal() 910 | 911 | # LFPS detection interface. 912 | self.lfps_signaling_detected = Signal() 913 | 914 | # Debug interface. 915 | self.raw_rx_data = Signal(16) 916 | self.raw_rx_ctrl = Signal(2) 917 | 918 | 919 | def elaborate(self, platform): 920 | m = Module() 921 | 922 | # 923 | # Reference clock selection. 924 | # 925 | 926 | # If we seem to have a raw pin record, we'll assume we're being passed the external REFCLK. 927 | # We'll instantiate an instance that captures the reference clock signal. 928 | if hasattr(self._refclk, 'p'): 929 | refclk = Signal() 930 | m.submodules.refclk_input = refclk_in = Instance("EXTREFB", 931 | i_REFCLKP = self._refclk.p, 932 | i_REFCLKN = self._refclk.n, 933 | o_REFCLKO = refclk, 934 | p_REFCK_PWDNB = "0b1", 935 | p_REFCK_RTERM = "0b1", # 100 Ohm 936 | ) 937 | refclk_in.attrs["LOC"] = f"EXTREF{self._refclk_num}" 938 | 939 | # Otherwise, we'll accept the reference clock directly. 940 | else: 941 | refclk = self._refclk 942 | 943 | # 944 | # Raw serdes. 945 | # 946 | pll_config = ECP5SerDesPLLConfiguration(refclk, refclk_freq=self._refclk_frequency, linerate=5e9) 947 | serdes = ECP5SerDes( 948 | pll_config = pll_config, 949 | tx_pads = self._tx_pads, 950 | rx_pads = self._rx_pads, 951 | channel = self._channel, 952 | ) 953 | m.submodules.serdes = serdes 954 | m.d.comb += [ 955 | serdes.train_equalizer .eq(self.train_equalizer), 956 | self.ready .eq(serdes.tx_ready & serdes.rx_ready) 957 | ] 958 | 959 | 960 | # 961 | # Transmit datapath. 962 | # 963 | m.submodules.tx_datapath = tx_datapath = TransmitPreprocessing() 964 | m.d.comb += [ 965 | serdes.tx_idle .eq(self.tx_idle), 966 | serdes.tx_enable .eq(self.enable), 967 | 968 | tx_datapath.sink .stream_eq(self.sink), 969 | serdes.sink .stream_eq(tx_datapath.source), 970 | 971 | serdes.tx_gpio_en .eq(self.use_tx_as_gpio), 972 | serdes.tx_gpio .eq(self.tx_gpio) 973 | ] 974 | 975 | 976 | # 977 | # Receive datapath. 978 | # 979 | m.submodules.rx_datapath = rx_datapath = ReceivePostprocessing() 980 | m.d.comb += [ 981 | self.rx_idle .eq(serdes.rx_idle), 982 | 983 | serdes.rx_enable .eq(self.enable), 984 | serdes.rx_align .eq(self.rx_align), 985 | rx_datapath.align .eq(self.rx_align), 986 | 987 | rx_datapath.sink .stream_eq(serdes.source), 988 | self.source .stream_eq(rx_datapath.source) 989 | ] 990 | 991 | # Pass through a synchronized version of our SerDes' rx-gpio. 992 | m.submodules += FFSynchronizer(serdes.rx_gpio, self.rx_gpio, o_domain="fast") 993 | 994 | 995 | # 996 | # LFPS Detection 997 | # 998 | m.submodules.lfps_detector = lfps_detector = LFPSSquareWaveDetector(self._fast_clock_frequency) 999 | m.d.comb += [ 1000 | lfps_detector.rx_gpio .eq(self.rx_gpio), 1001 | self.lfps_signaling_detected .eq(lfps_detector.present) 1002 | ] 1003 | 1004 | 1005 | # debug signals 1006 | m.d.comb += [ 1007 | self.raw_rx_data.eq(serdes.source.data), 1008 | self.raw_rx_ctrl.eq(serdes.source.ctrl), 1009 | ] 1010 | 1011 | return m 1012 | -------------------------------------------------------------------------------- /bucatini/backends/artix7.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Bucatini. 3 | # 4 | # Copyright (c) 2021 Great Scott Gadgets 5 | # Copyright (c) 2017-2020 Florent Kermarrec 6 | # Copyright (c) 2017 Sebastien Bourdeauducq 7 | # 8 | # Code initially ported to nMigen from ``LiteICLink`` and adapted for Bucatini. 9 | # SPDX-License-Identifier: BSD-3-Clause 10 | """ SerDes backend for the Artix7. """ 11 | 12 | from nmigen import * 13 | from nmigen.hdl.rec import DIR_FANIN, DIR_FANOUT 14 | from nmigen.lib.cdc import FFSynchronizer, ResetSynchronizer 15 | 16 | 17 | from .soft import Encoder 18 | from ..datapath import ReceivePostprocessing, TransmitPreprocessing 19 | 20 | from ....usb.stream import USBRawSuperSpeedStream 21 | from ....usb.usb3.physical.coding import * 22 | 23 | 24 | DIR_C_TO_P = DIR_FANOUT 25 | DIR_P_TO_C = DIR_FANIN 26 | 27 | 28 | class WaitTimer(Elaboratable): 29 | def __init__(self, t): 30 | self._t = t 31 | self.wait = Signal() 32 | self.done = Signal() 33 | 34 | 35 | def elaborate(self, platform): 36 | m = Module() 37 | 38 | count = Signal(range(self._t + 1), reset=self._t) 39 | m.d.comb += self.done.eq(count == 0) 40 | 41 | with m.If(self.wait): 42 | with m.If(~self.done): 43 | m.d.ss += count.eq(count + 1) 44 | with m.Else(): 45 | m.d.ss += count.eq(count.reset) 46 | 47 | return m 48 | 49 | 50 | class DRPInterface(Record): 51 | def __init__(self, address_width=9, data_width=16): 52 | super().__init__([ 53 | ("clk", 1, DIR_C_TO_P), 54 | ("en", 1, DIR_C_TO_P), 55 | ("we", 1, DIR_C_TO_P), 56 | ("rdy", 1, DIR_P_TO_C), 57 | ("addr", address_width, DIR_C_TO_P), 58 | ("di", data_width, DIR_C_TO_P), 59 | ("do", data_width, DIR_P_TO_C), 60 | ]) 61 | 62 | 63 | 64 | class DRPMux(Elaboratable, DRPInterface): 65 | def __init__(self, **kwargs): 66 | DRPInterface.__init__(self, **kwargs) 67 | self.sel = Signal(4) 68 | self.interfaces = [] 69 | 70 | def add_interface(self, interface): 71 | self.interfaces.append(interface) 72 | 73 | def elaborate(self, platform): 74 | assert len(self.interfaces) <= 16 75 | 76 | m = Module() 77 | 78 | with m.Switch(self.sel): 79 | for i, interface in enumerate(self.interfaces): 80 | with m.Case(i): 81 | m.d.comb += interface.connect(self) 82 | 83 | return m 84 | 85 | 86 | class GTPTXInit(Elaboratable): 87 | def __init__(self, ss_clock_frequency=125e6): 88 | self._ss_clock_frequency = ss_clock_frequency 89 | 90 | # 91 | # I/O port 92 | # 93 | self.done = Signal() 94 | self.restart = Signal() 95 | 96 | # GTP signals 97 | self.plllock = Signal() 98 | self.pllreset = Signal() 99 | self.gttxreset = Signal() 100 | self.gttxpd = Signal() 101 | self.txresetdone = Signal() 102 | self.txdlysreset = Signal() 103 | self.txdlysresetdone = Signal() 104 | self.txphinit = Signal() 105 | self.txphinitdone = Signal() 106 | self.txphalign = Signal() 107 | self.txphaligndone = Signal() 108 | self.txdlyen = Signal() 109 | self.txuserrdy = Signal() 110 | 111 | # DRP (optional) 112 | self.drp_start = Signal() 113 | self.drp_done = Signal(reset=1) 114 | 115 | 116 | def elaborate(self, platform): 117 | m = Module() 118 | 119 | # Double-latch transceiver asynch outputs 120 | plllock = Signal() 121 | txresetdone = Signal() 122 | txdlysresetdone = Signal() 123 | txphinitdone = Signal() 124 | txphaligndone = Signal() 125 | m.submodules += [ 126 | FFSynchronizer(self.plllock, plllock, o_domain="ss"), 127 | FFSynchronizer(self.txresetdone, txresetdone, o_domain="ss"), 128 | FFSynchronizer(self.txdlysresetdone, txdlysresetdone, o_domain="ss"), 129 | FFSynchronizer(self.txphinitdone, txphinitdone, o_domain="ss"), 130 | FFSynchronizer(self.txphaligndone, txphaligndone, o_domain="ss") 131 | ] 132 | 133 | # Deglitch FSM outputs driving transceiver asynch inputs 134 | gttxreset = Signal() 135 | gttxpd = Signal() 136 | txdlysreset = Signal() 137 | txphinit = Signal() 138 | txphalign = Signal() 139 | txdlyen = Signal() 140 | txuserrdy = Signal() 141 | m.d.ss += [ 142 | self.gttxreset .eq(gttxreset), 143 | self.gttxpd .eq(gttxpd), 144 | self.txdlysreset .eq(txdlysreset), 145 | self.txphinit .eq(txphinit), 146 | self.txphalign .eq(txphalign), 147 | self.txdlyen .eq(txdlyen), 148 | self.txuserrdy .eq(txuserrdy) 149 | ] 150 | 151 | # Detect txphaligndone rising edge 152 | txphaligndone_r = Signal(reset=1) 153 | txphaligndone_rising = Signal() 154 | m.d.ss += txphaligndone_r.eq(txphaligndone) 155 | m.d.comb += txphaligndone_rising.eq(txphaligndone & ~txphaligndone_r) 156 | 157 | # Wait 500ns after configuration before releasing 158 | # GTP reset (to follow AR43482) 159 | init_delay = WaitTimer(int(500e-9*self._ss_clock_frequency)) 160 | m.submodules += init_delay 161 | m.d.comb += init_delay.wait.eq(1) 162 | 163 | 164 | with m.FSM(domain="ss"): 165 | with m.State("POWER-DOWN"): 166 | m.d.comb += [ 167 | gttxreset.eq(1), 168 | gttxpd.eq(1), 169 | self.pllreset.eq(1), 170 | ] 171 | m.next = "DRP" 172 | 173 | with m.State("DRP",): 174 | m.d.comb += [ 175 | gttxreset.eq(1), 176 | self.pllreset.eq(1), 177 | self.drp_start.eq(1), 178 | ] 179 | with m.If(self.drp_done): 180 | m.next = "WAIT-PLL-RESET" 181 | 182 | 183 | with m.State("WAIT-PLL-RESET"): 184 | m.d.comb += gttxreset.eq(1) 185 | with m.If(plllock): 186 | m.next = "WAIT-INIT-DELAY" 187 | 188 | with m.State("WAIT-INIT-DELAY"): 189 | m.d.comb += gttxreset.eq(1) 190 | with m.If(init_delay.done): 191 | m.next = "WAIT-GTP-RESET" 192 | 193 | with m.State("WAIT-GTP-RESET"): 194 | m.d.comb += txuserrdy.eq(1) 195 | with m.If(txresetdone): 196 | m.next = "READY" 197 | 198 | 199 | with m.State("READY"): 200 | m.d.comb += [ 201 | txuserrdy.eq(1), 202 | txdlyen.eq(1), 203 | self.done.eq(1), 204 | ] 205 | with m.If(self.restart): 206 | m.next = "POWER-DOWN" 207 | 208 | 209 | # FSM watchdog / restart 210 | m.submodules.watchdog = watchdog = WaitTimer(int(1e-3*self._ss_clock_frequency)) 211 | reset_self = self.restart | watchdog.done 212 | m.d.comb += watchdog.wait.eq(~reset_self & ~self.done), 213 | 214 | return ResetInserter(reset_self)(m) 215 | 216 | 217 | class GTPRXInit(Elaboratable): 218 | def __init__(self, ss_clock_frequency): 219 | self._ss_clock_frequency = ss_clock_frequency 220 | 221 | # 222 | # I/O port 223 | # 224 | self.done = Signal() 225 | self.restart = Signal() 226 | 227 | # GTP signals 228 | self.plllock = Signal() 229 | self.gtrxreset = Signal() 230 | self.gtrxpd = Signal() 231 | self.rxresetdone = Signal() 232 | self.rxdlysreset = Signal() 233 | self.rxdlysresetdone = Signal() 234 | self.rxphalign = Signal() 235 | self.rxuserrdy = Signal() 236 | self.rxsyncdone = Signal() 237 | self.rxpmaresetdone = Signal() 238 | 239 | self.drp = DRPInterface() 240 | 241 | 242 | def elaborate(self, platform): 243 | m = Module() 244 | 245 | drpvalue = Signal(16) 246 | drpmask = Signal() 247 | 248 | m.d.comb += [ 249 | self.drp.clk.eq(ClockSignal("ss")), 250 | self.drp.addr.eq(0x011), 251 | ] 252 | 253 | with m.If(drpmask): 254 | m.d.comb += self.drp.di.eq(drpvalue & 0xf7ff) 255 | with m.Else(): 256 | m.d.comb += self.drp.di.eq(drpvalue) 257 | 258 | 259 | rxpmaresetdone = Signal() 260 | m.submodules += FFSynchronizer(self.rxpmaresetdone, rxpmaresetdone) 261 | rxpmaresetdone_r = Signal() 262 | m.d.ss += rxpmaresetdone_r.eq(rxpmaresetdone) 263 | 264 | # Double-latch transceiver asynch outputs 265 | plllock = Signal() 266 | rxresetdone = Signal() 267 | rxdlysresetdone = Signal() 268 | rxsyncdone = Signal() 269 | m.submodules += [ 270 | FFSynchronizer(self.plllock, plllock, o_domain="ss"), 271 | FFSynchronizer(self.rxresetdone, rxresetdone, o_domain="ss"), 272 | FFSynchronizer(self.rxdlysresetdone, rxdlysresetdone, o_domain="ss"), 273 | FFSynchronizer(self.rxsyncdone, rxsyncdone, o_domain="ss") 274 | ] 275 | 276 | # Deglitch FSM outputs driving transceiver asynch inputs 277 | gtrxreset = Signal() 278 | gtrxpd = Signal() 279 | rxdlysreset = Signal() 280 | rxphalign = Signal() 281 | rxuserrdy = Signal() 282 | m.d.ss += [ 283 | self.gtrxreset .eq(gtrxreset), 284 | self.gtrxpd .eq(gtrxpd), 285 | self.rxdlysreset .eq(rxdlysreset), 286 | self.rxphalign .eq(rxphalign), 287 | self.rxuserrdy .eq(rxuserrdy) 288 | ] 289 | 290 | # Wait 500ns after configuration before releasing 291 | # GTP reset (to follow AR43482) 292 | init_delay = WaitTimer(int(500e-9*self._ss_clock_frequency)) 293 | m.submodules += init_delay 294 | m.d.comb += init_delay.wait.eq(1) 295 | 296 | with m.FSM(domain="ss"): 297 | with m.State("POWER-DOWN"): 298 | m.d.comb += [ 299 | gtrxreset.eq(1), 300 | gtrxpd.eq(1), 301 | ] 302 | m.next = "DRP_READ_ISSUE" 303 | 304 | with m.State("DRP_READ_ISSUE"): 305 | m.d.comb += gtrxreset.eq(1) 306 | with m.If(init_delay.done): 307 | m.next = "DRP_READ_ISSUE_POST" 308 | 309 | with m.State("DRP_READ_ISSUE_POST"): 310 | m.d.comb += [ 311 | gtrxreset.eq(1), 312 | self.drp.en.eq(1), 313 | ] 314 | m.next = "DRP_READ_WAIT" 315 | 316 | with m.State("DRP_READ_WAIT"): 317 | m.d.comb += gtrxreset.eq(1) 318 | with m.If(self.drp.rdy): 319 | m.d.ss += drpvalue.eq(self.drp.do) 320 | m.next = "DRP_MOD_ISSUE" 321 | 322 | with m.State("DRP_MOD_ISSUE"): 323 | m.d.comb += [ 324 | gtrxreset.eq(1), 325 | drpmask.eq(1), 326 | self.drp.en.eq(1), 327 | self.drp.we.eq(1), 328 | ] 329 | m.next = "DRP_MOD_WAIT" 330 | 331 | 332 | with m.State("DRP_MOD_WAIT"): 333 | m.d.comb += gtrxreset.eq(1) 334 | with m.If(self.drp.rdy): 335 | m.next = "WAIT_PMARST_FALL" 336 | 337 | with m.State("WAIT_PMARST_FALL"): 338 | m.d.comb += rxuserrdy.eq(1) 339 | with m.If(rxpmaresetdone_r & ~rxpmaresetdone): 340 | m.next = "DRP_RESTORE_ISSUE" 341 | 342 | 343 | with m.State("DRP_RESTORE_ISSUE"): 344 | m.d.comb += [ 345 | rxuserrdy.eq(1), 346 | self.drp.en.eq(1), 347 | self.drp.we.eq(1), 348 | ] 349 | m.next = "DRP_RESTORE_WAIT" 350 | 351 | with m.State("DRP_RESTORE_WAIT"): 352 | m.d.comb += rxuserrdy.eq(1) 353 | with m.If(self.drp.rdy): 354 | 355 | m.next = "WAIT-GTP-RESET" 356 | with m.State("WAIT-GTP-RESET"): 357 | m.d.comb += rxuserrdy.eq(1) 358 | with m.If(rxresetdone): 359 | m.next = "READY" 360 | 361 | with m.State("READY"): 362 | m.d.comb += [ 363 | rxuserrdy.eq(1), 364 | self.done.eq(1), 365 | ] 366 | with m.If(self.restart): 367 | m.next = "POWER-DOWN" 368 | 369 | # FSM watchdog / restart 370 | m.submodules.watchdog = watchdog = WaitTimer(int(4e-3*self._ss_clock_frequency)) 371 | reset_self = watchdog.done | self.restart 372 | m.d.comb += watchdog.wait.eq(~reset_self & ~self.done), 373 | 374 | return ResetInserter(reset_self)(m) 375 | 376 | 377 | 378 | class Open(Signal): 379 | pass 380 | 381 | 382 | class GTPQuadPLL(Elaboratable): 383 | def __init__(self, refclk, refclk_freq, linerate, channel=0, shared=False): 384 | assert channel in [0, 1] 385 | self.channel = channel 386 | 387 | self._refclk = refclk 388 | self._refclk_freq = refclk_freq 389 | self._linerate = linerate 390 | self._shared = shared 391 | 392 | # 393 | # I/O port 394 | # 395 | self.clk = Signal() 396 | self.refclk = Signal() 397 | self.reset = Signal() 398 | self.lock = Signal() 399 | self.config = self.compute_config(refclk_freq, linerate) 400 | 401 | # DRP 402 | self.drp = DRPInterface() 403 | 404 | 405 | def elaborate(self, platform): 406 | m = Module() 407 | config = self.config 408 | 409 | if not self._shared: 410 | gtpe2_common_params = dict( 411 | # common 412 | i_GTREFCLK0 = self._refclk, 413 | i_BGBYPASSB = 1, 414 | i_BGMONITORENB = 1, 415 | i_BGPDB = 1, 416 | i_BGRCALOVRD = 0b11111, 417 | i_RCALENB = 1, 418 | 419 | i_DRPADDR = self.drp.addr, 420 | i_DRPCLK = self.drp.clk, 421 | i_DRPDI = self.drp.di, 422 | o_DRPDO = self.drp.do, 423 | i_DRPEN = self.drp.en, 424 | o_DRPRDY = self.drp.rdy, 425 | i_DRPWE = self.drp.we, 426 | ) 427 | 428 | if self.channel == 0: 429 | gtpe2_common_params.update( 430 | # pll0 431 | p_PLL0_FBDIV = config["n2"], 432 | p_PLL0_FBDIV_45 = config["n1"], 433 | p_PLL0_REFCLK_DIV = config["m"], 434 | i_PLL0LOCKEN = 1, 435 | i_PLL0PD = 0, 436 | i_PLL0REFCLKSEL = 0b001, 437 | i_PLL0RESET = self.reset, 438 | o_PLL0LOCK = self.lock, 439 | o_PLL0OUTCLK = self.clk, 440 | o_PLL0OUTREFCLK = self.refclk, 441 | 442 | # pll1 (not used: power down) 443 | i_PLL1PD = 1, 444 | ) 445 | else: 446 | gtpe2_common_params.update( 447 | # pll0 (not used: power down) 448 | i_PLL0PD = 1, 449 | 450 | # pll0 451 | p_PLL1_FBDIV = config["n2"], 452 | p_PLL1_FBDIV_45 = config["n1"], 453 | p_PLL1_REFCLK_DIV = config["m"], 454 | i_PLL1LOCKEN = 1, 455 | i_PLL1PD = 0, 456 | i_PLL1REFCLKSEL = 0b001, 457 | i_PLL1RESET = self.reset, 458 | o_PLL1LOCK = self.lock, 459 | o_PLL1OUTCLK = self.clk, 460 | o_PLL1OUTREFCLK = self.refclk, 461 | ) 462 | 463 | m.submodules += Instance("GTPE2_COMMON", **gtpe2_common_params) 464 | else: 465 | self.gtrefclk = self._refclk 466 | self.gtgrefclk = 0 467 | self.refclksel = 0b010 468 | 469 | return m 470 | 471 | 472 | @staticmethod 473 | def compute_config(refclk_freq, linerate): 474 | for n1 in 4, 5: 475 | for n2 in 1, 2, 3, 4, 5: 476 | for m in 1, 2: 477 | vco_freq = refclk_freq*(n1*n2)/m 478 | if 1.6e9 <= vco_freq <= 3.3e9: 479 | for d in 1, 2, 4, 8, 16: 480 | current_linerate = vco_freq*2/d 481 | if current_linerate == linerate: 482 | return {"n1": n1, "n2": n2, "m": m, "d": d, 483 | "vco_freq": vco_freq, 484 | "clkin": refclk_freq, 485 | "linerate": linerate} 486 | msg = "No config found for {:3.2f} MHz refclk / {:3.2f} Gbps linerate." 487 | raise ValueError(msg.format(refclk_freq/1e6, linerate/1e9)) 488 | 489 | def __repr__(self): 490 | config = self.config 491 | r = """ 492 | GTPQuadPLL 493 | ============== 494 | overview: 495 | --------- 496 | +--------------------------------------------------+ 497 | | | 498 | | +-----+ +---------------------------+ +-----+ | 499 | | | | | Phase Frequency Detector | | | | 500 | CLKIN +----> /M +--> Charge Pump +-> VCO +---> CLKOUT 501 | | | | | Loop Filter | | | | 502 | | +-----+ +---------------------------+ +--+--+ | 503 | | ^ | | 504 | | | +-------+ +-------+ | | 505 | | +----+ /N2 <----+ /N1 <----+ | 506 | | +-------+ +-------+ | 507 | +--------------------------------------------------+ 508 | +-------+ 509 | CLKOUT +-> 2/D +-> LINERATE 510 | +-------+ 511 | config: 512 | ------- 513 | CLKIN = {clkin}MHz 514 | CLKOUT = CLKIN x (N1 x N2) / M = {clkin}MHz x ({n1} x {n2}) / {m} 515 | = {vco_freq}GHz 516 | LINERATE = CLKOUT x 2 / D = {vco_freq}GHz x 2 / {d} 517 | = {linerate}GHz 518 | """.format(clkin = config["clkin"]/1e6, 519 | n1 = config["n1"], 520 | n2 = config["n2"], 521 | m = config["m"], 522 | vco_freq = config["vco_freq"]/1e9, 523 | d = config["d"], 524 | linerate = config["linerate"]/1e9) 525 | return r 526 | 527 | 528 | class GTP(Elaboratable): 529 | def __init__(self, qpll, tx_pads, rx_pads, ss_clock_frequency): 530 | self._qpll = qpll 531 | self._tx_pads = tx_pads 532 | self._rx_pads = rx_pads 533 | self._ss_clock_frequency = ss_clock_frequency 534 | 535 | self.data_width = 20 536 | self.nwords = self.data_width // 10 537 | 538 | # Streams 539 | self.sink = USBRawSuperSpeedStream(payload_words=2) 540 | self.source = USBRawSuperSpeedStream(payload_words=2) 541 | 542 | # TX controls 543 | self.tx_enable = Signal(reset=1) 544 | self.tx_polarity = Signal() 545 | self.tx_ready = Signal() 546 | self.tx_idle = Signal() 547 | self.tx_inhibit = Signal() 548 | self.tx_gpio_en = Signal() 549 | self.tx_gpio = Signal() 550 | 551 | # RX controls 552 | self.rx_enable = Signal(reset=1) 553 | self.rx_polarity = Signal() 554 | self.rx_ready = Signal() 555 | self.rx_align = Signal(reset=1) 556 | self.rx_idle = Signal() 557 | self.train_equalizer = Signal() 558 | 559 | 560 | # DRP 561 | self.drp = DRPInterface() 562 | 563 | # Loopback 564 | self.loopback = Signal(3) 565 | 566 | # Transceiver direct clock outputs (useful to specify clock constraints) 567 | self.txoutclk = Signal() 568 | self.rxoutclk = Signal() 569 | 570 | 571 | def elaborate(self, platorm): 572 | m = Module() 573 | 574 | # Aliases. 575 | qpll = self._qpll 576 | data_width = self.data_width 577 | nwords = self.nwords 578 | 579 | # Ensure we have a valid PLL/CDR configuration. 580 | assert qpll.config["linerate"] < 6.6e9 581 | rxcdr_cfgs = { 582 | 1 : 0x0000107FE406001041010, 583 | 2 : 0x0000107FE206001041010, 584 | 4 : 0x0000107FE106001041010, 585 | 8 : 0x0000107FE086001041010, 586 | 16 : 0x0000107FE086001041010, 587 | } 588 | 589 | 590 | # 591 | # Transciever GPIO synchronization 592 | # 593 | tx_gpio_en = Signal() 594 | tx_gpio = Signal() 595 | m.submodules += [ 596 | FFSynchronizer(self.tx_gpio_en, tx_gpio_en, o_domain="tx"), 597 | FFSynchronizer(self.tx_gpio, tx_gpio, o_domain="tx"), 598 | ] 599 | 600 | 601 | 602 | # 603 | # Transmitter bringup. 604 | # 605 | m.submodules.tx_init = tx_init = GTPTXInit(self._ss_clock_frequency) 606 | m.d.comb += [ 607 | self.tx_ready .eq(tx_init.done), 608 | tx_init.restart .eq(~self.tx_enable) 609 | ] 610 | 611 | # 612 | # Receiver bringup. 613 | # 614 | m.submodules.rx_init = rx_init = GTPRXInit(self._ss_clock_frequency) 615 | m.d.comb += [ 616 | self.rx_ready.eq(rx_init.done), 617 | rx_init.restart.eq(~self.rx_enable) 618 | ] 619 | 620 | # 621 | # PLL interconnection 622 | # 623 | m.d.comb += [ 624 | tx_init.plllock.eq(qpll.lock), 625 | rx_init.plllock.eq(qpll.lock), 626 | qpll.reset.eq(tx_init.pllreset) 627 | ] 628 | 629 | # 630 | # DRP 631 | # 632 | m.submodules.drp_mux = drp_mux = DRPMux() 633 | drp_mux.add_interface(rx_init.drp) 634 | drp_mux.add_interface(self.drp) 635 | 636 | # 637 | # LFPS "logic clock" 638 | # 639 | 640 | # The OOB unit requires a psuedo-clock that operates at 50% duty cycle, 641 | # and which is slower than the clocks used by the rest of units. 642 | lfps_counter = Signal(3) 643 | lfps_logic_clk = lfps_counter[-1] 644 | m.d.ss += lfps_counter.eq(lfps_counter + 1) 645 | 646 | 647 | # 648 | # Core SerDes-chnannel IP instance 649 | # 650 | rxphaligndone = Signal() 651 | 652 | # Transmitter data signals. 653 | tx_enable_8b10b = Signal() 654 | tx_data = Signal(8 * nwords) 655 | tx_ctrl = Signal(nwords) 656 | tx_char_disp_mode = Signal(nwords) 657 | tx_char_disp_val = Signal(nwords) 658 | 659 | # Receiver data signals. 660 | rx_data = Signal(8 * nwords) 661 | rx_ctrl = Signal(nwords) 662 | rx_disp_error = Signal(nwords) 663 | rx_code_error = Signal(nwords) 664 | 665 | 666 | m.submodules.gtp = Instance("GTPE2_CHANNEL", 667 | # Simulation-Only Attributes 668 | p_SIM_RECEIVER_DETECT_PASS = "TRUE", 669 | p_SIM_TX_EIDLE_DRIVE_LEVEL = "X", 670 | p_SIM_RESET_SPEEDUP = "FALSE", 671 | p_SIM_VERSION = "2.0", 672 | 673 | # RX Byte and Word Alignment Attributes 674 | p_ALIGN_COMMA_DOUBLE = "FALSE", 675 | p_ALIGN_COMMA_ENABLE = 0b11_1111_1111, 676 | #p_ALIGN_COMMA_WORD = 2, 677 | p_ALIGN_COMMA_WORD = 1, 678 | p_ALIGN_MCOMMA_DET = "TRUE", 679 | p_ALIGN_MCOMMA_VALUE = 0b10_1000_0011, 680 | p_ALIGN_PCOMMA_DET = "TRUE", 681 | p_ALIGN_PCOMMA_VALUE = 0b01_0111_1100, 682 | p_SHOW_REALIGN_COMMA = "TRUE", 683 | p_RXSLIDE_AUTO_WAIT = 7, 684 | p_RXSLIDE_MODE = "OFF", 685 | p_RX_SIG_VALID_DLY = 10, 686 | 687 | # RX 8B/10B Decoder Attributes 688 | p_RX_DISPERR_SEQ_MATCH = "FALSE", 689 | p_DEC_MCOMMA_DETECT = "TRUE", 690 | p_DEC_PCOMMA_DETECT = "TRUE", 691 | p_DEC_VALID_COMMA_ONLY = "TRUE", 692 | 693 | # RX Clock Correction Attributes 694 | p_CBCC_DATA_SOURCE_SEL = "DECODED", 695 | p_CLK_COR_SEQ_2_USE = "FALSE", 696 | p_CLK_COR_KEEP_IDLE = "FALSE", 697 | p_CLK_COR_MAX_LAT = 10, 698 | p_CLK_COR_MIN_LAT = 8, 699 | p_CLK_COR_PRECEDENCE = "TRUE", 700 | p_CLK_COR_REPEAT_WAIT = 0, 701 | p_CLK_COR_SEQ_LEN = 2, 702 | p_CLK_COR_SEQ_1_ENABLE = 0b1100, 703 | p_CLK_COR_SEQ_1_1 = 0b0000000000, 704 | p_CLK_COR_SEQ_1_2 = 0b0000000000, 705 | p_CLK_COR_SEQ_1_3 = 0b0000000000, 706 | p_CLK_COR_SEQ_1_4 = 0b0000000000, 707 | p_CLK_CORRECT_USE = "FALSE", 708 | p_CLK_COR_SEQ_2_ENABLE = 0b1111, 709 | p_CLK_COR_SEQ_2_1 = 0b0000000000, 710 | p_CLK_COR_SEQ_2_2 = 0b0000000000, 711 | p_CLK_COR_SEQ_2_3 = 0b0000000000, 712 | p_CLK_COR_SEQ_2_4 = 0b0000000000, 713 | 714 | # RX Channel Bonding Attributes 715 | p_CHAN_BOND_KEEP_ALIGN = "FALSE", 716 | p_CHAN_BOND_MAX_SKEW = 1, 717 | p_CHAN_BOND_SEQ_LEN = 1, 718 | p_CHAN_BOND_SEQ_1_1 = 0b0000000000, 719 | p_CHAN_BOND_SEQ_1_2 = 0b0000000000, 720 | p_CHAN_BOND_SEQ_1_3 = 0b0000000000, 721 | p_CHAN_BOND_SEQ_1_4 = 0b0000000000, 722 | p_CHAN_BOND_SEQ_1_ENABLE = 0b1111, 723 | p_CHAN_BOND_SEQ_2_1 = 0b0000000000, 724 | p_CHAN_BOND_SEQ_2_2 = 0b0000000000, 725 | p_CHAN_BOND_SEQ_2_3 = 0b0000000000, 726 | p_CHAN_BOND_SEQ_2_4 = 0b0000000000, 727 | p_CHAN_BOND_SEQ_2_ENABLE = 0b1111, 728 | p_CHAN_BOND_SEQ_2_USE = "FALSE", 729 | p_FTS_DESKEW_SEQ_ENABLE = 0b1111, 730 | p_FTS_LANE_DESKEW_CFG = 0b1111, 731 | p_FTS_LANE_DESKEW_EN = "FALSE", 732 | 733 | # RX Margin Analysis Attributes 734 | p_ES_CONTROL = 0b000000, 735 | p_ES_ERRDET_EN = "FALSE", 736 | p_ES_EYE_SCAN_EN = "TRUE", 737 | p_ES_HORZ_OFFSET = 0x000, 738 | p_ES_PMA_CFG = 0b0000000000, 739 | p_ES_PRESCALE = 0b00000, 740 | p_ES_QUALIFIER = 0x00000000000000000000, 741 | p_ES_QUAL_MASK = 0x00000000000000000000, 742 | p_ES_SDATA_MASK = 0x00000000000000000000, 743 | p_ES_VERT_OFFSET = 0b000000000, 744 | 745 | # FPGA RX Interface Attributes 746 | p_RX_DATA_WIDTH = data_width, 747 | 748 | # PMA Attributes 749 | p_OUTREFCLK_SEL_INV = 0b11, 750 | p_PMA_RSV = 0x00000333, 751 | p_PMA_RSV2 = 0x00002040, 752 | p_PMA_RSV3 = 0b00, 753 | p_PMA_RSV4 = 0b0000, 754 | p_RX_BIAS_CFG = 0b0000111100110011, 755 | p_DMONITOR_CFG = 0x000A00, 756 | p_RX_DEBUG_CFG = 0b00000000000000, 757 | p_RX_OS_CFG = 0b0000010000000, 758 | p_TERM_RCAL_CFG = 0b100001000010000, 759 | p_TERM_RCAL_OVRD = 0b000, 760 | p_TST_RSV = 0x00000000, 761 | p_RX_CLK25_DIV = 5, 762 | p_TX_CLK25_DIV = 5, 763 | p_UCODEER_CLR = 0b0, 764 | 765 | # PCI Express Attributes 766 | p_PCS_PCIE_EN = "FALSE", 767 | 768 | # PCS Attributes 769 | p_PCS_RSVD_ATTR = 0x000000000100, 770 | 771 | # RX Buffer Attributes 772 | p_RXBUF_ADDR_MODE = "FAST", 773 | p_RXBUF_EIDLE_HI_CNT = 0b1000, 774 | p_RXBUF_EIDLE_LO_CNT = 0b0000, 775 | p_RXBUF_EN = "TRUE", 776 | p_RX_BUFFER_CFG = 0b000000, 777 | p_RXBUF_RESET_ON_CB_CHANGE = "TRUE", 778 | p_RXBUF_RESET_ON_COMMAALIGN = "FALSE", 779 | p_RXBUF_RESET_ON_EIDLE = "FALSE", 780 | p_RXBUF_RESET_ON_RATE_CHANGE = "TRUE", 781 | p_RXBUFRESET_TIME = 0b00001, 782 | p_RXBUF_THRESH_OVFLW = 61, 783 | p_RXBUF_THRESH_OVRD = "FALSE", 784 | p_RXBUF_THRESH_UNDFLW = 4, 785 | p_RXDLY_CFG = 0x001F, 786 | p_RXDLY_LCFG = 0x030, 787 | p_RXDLY_TAP_CFG = 0x0000, 788 | p_RXPH_CFG = 0xC00002, 789 | p_RXPHDLY_CFG = 0x084020, 790 | p_RXPH_MONITOR_SEL = 0b00000, 791 | p_RX_XCLK_SEL = "RXREC", 792 | p_RX_DDI_SEL = 0b000000, 793 | p_RX_DEFER_RESET_BUF_EN = "TRUE", 794 | 795 | # CDR Attributes 796 | p_RXCDR_CFG = rxcdr_cfgs[qpll.config["d"]], 797 | p_RXCDR_FR_RESET_ON_EIDLE = 0b0, 798 | p_RXCDR_HOLD_DURING_EIDLE = 0b0, 799 | p_RXCDR_PH_RESET_ON_EIDLE = 0b0, 800 | p_RXCDR_LOCK_CFG = 0b001001, 801 | 802 | # RX Initialization and Reset Attributes 803 | p_RXCDRFREQRESET_TIME = 0b00001, 804 | p_RXCDRPHRESET_TIME = 0b00001, 805 | p_RXISCANRESET_TIME = 0b00001, 806 | p_RXPCSRESET_TIME = 0b00001, 807 | p_RXPMARESET_TIME = 0b00011, 808 | 809 | # RX OOB Signaling Attributes 810 | p_RXOOB_CFG = 0b0000110, 811 | 812 | # RX Gearbox Attributes 813 | p_RXGEARBOX_EN = "FALSE", 814 | p_GEARBOX_MODE = 0b000, 815 | 816 | # PRBS Detection Attribute 817 | p_RXPRBS_ERR_LOOPBACK = 0b0, 818 | 819 | # Power-Down Attributes 820 | p_PD_TRANS_TIME_FROM_P2 = 0x03c, 821 | p_PD_TRANS_TIME_NONE_P2 = 0x3c, 822 | p_PD_TRANS_TIME_TO_P2 = 0x64, 823 | 824 | # RX OOB Signaling Attributes 825 | p_SAS_MAX_COM = 64, 826 | p_SAS_MIN_COM = 36, 827 | p_SATA_BURST_SEQ_LEN = 0b0101, 828 | p_SATA_BURST_VAL = 0b100, 829 | p_SATA_EIDLE_VAL = 0b100, 830 | p_SATA_MAX_BURST = 8, 831 | p_SATA_MAX_INIT = 21, 832 | p_SATA_MAX_WAKE = 7, 833 | p_SATA_MIN_BURST = 4, 834 | p_SATA_MIN_INIT = 12, 835 | p_SATA_MIN_WAKE = 4, 836 | 837 | # RX Fabric Clock Output Control Attributes 838 | p_TRANS_TIME_RATE = 0x0E, 839 | 840 | # TX Buffer Attributes 841 | p_TXBUF_EN = "TRUE", 842 | p_TXBUF_RESET_ON_RATE_CHANGE = "TRUE", 843 | p_TXDLY_CFG = 0x001F, 844 | p_TXDLY_LCFG = 0x030, 845 | p_TXDLY_TAP_CFG = 0x0000, 846 | p_TXPH_CFG = 0x0780, 847 | p_TXPHDLY_CFG = 0x084020, 848 | p_TXPH_MONITOR_SEL = 0b00000, 849 | p_TX_XCLK_SEL = "TXOUT", 850 | 851 | # FPGA TX Interface Attributes 852 | p_TX_DATA_WIDTH = data_width, 853 | 854 | # TX Configurable Driver Attributes 855 | p_TX_DEEMPH0 = 0b000000, 856 | p_TX_DEEMPH1 = 0b000000, 857 | p_TX_EIDLE_ASSERT_DELAY = 0b110, 858 | p_TX_EIDLE_DEASSERT_DELAY = 0b100, 859 | p_TX_LOOPBACK_DRIVE_HIZ = "FALSE", 860 | p_TX_MAINCURSOR_SEL = 0b0, 861 | p_TX_DRIVE_MODE = "DIRECT", 862 | p_TX_MARGIN_FULL_0 = 0b1001110, 863 | p_TX_MARGIN_FULL_1 = 0b1001001, 864 | p_TX_MARGIN_FULL_2 = 0b1000101, 865 | p_TX_MARGIN_FULL_3 = 0b1000010, 866 | p_TX_MARGIN_FULL_4 = 0b1000000, 867 | p_TX_MARGIN_LOW_0 = 0b1000110, 868 | p_TX_MARGIN_LOW_1 = 0b1000100, 869 | p_TX_MARGIN_LOW_2 = 0b1000010, 870 | p_TX_MARGIN_LOW_3 = 0b1000000, 871 | p_TX_MARGIN_LOW_4 = 0b1000000, 872 | 873 | # TX Gearbox Attributes 874 | p_TXGEARBOX_EN = "FALSE", 875 | 876 | # TX Initialization and Reset Attributes 877 | p_TXPCSRESET_TIME = 0b00001, 878 | p_TXPMARESET_TIME = 0b00001, 879 | 880 | # TX Receiver Detection Attributes 881 | p_TX_RXDETECT_CFG = 0x1832, 882 | p_TX_RXDETECT_REF = 0b100, 883 | 884 | # JTAG Attributes 885 | p_ACJTAG_DEBUG_MODE = 0b0, 886 | p_ACJTAG_MODE = 0b0, 887 | p_ACJTAG_RESET = 0b0, 888 | 889 | # CDR Attributes 890 | p_CFOK_CFG = 0x49000040E80, 891 | p_CFOK_CFG2 = 0b0100000, 892 | p_CFOK_CFG3 = 0b0100000, 893 | p_CFOK_CFG4 = 0b0, 894 | p_CFOK_CFG5 = 0x0, 895 | p_CFOK_CFG6 = 0b0000, 896 | p_RXOSCALRESET_TIME = 0b00011, 897 | p_RXOSCALRESET_TIMEOUT = 0b00000, 898 | 899 | # PMA Attributes 900 | p_CLK_COMMON_SWING = 0b0, 901 | p_RX_CLKMUX_EN = 0b1, 902 | p_TX_CLKMUX_EN = 0b1, 903 | p_ES_CLK_PHASE_SEL = 0b0, 904 | p_USE_PCS_CLK_PHASE_SEL = 0b0, 905 | p_PMA_RSV6 = 0b0, 906 | p_PMA_RSV7 = 0b0, 907 | 908 | # TX Configuration Driver Attributes 909 | p_TX_PREDRIVER_MODE = 0b0, 910 | p_PMA_RSV5 = 0b0, 911 | p_SATA_PLL_CFG = "VCO_3000MHZ", 912 | 913 | # RX Fabric Clock Output Control Attributes 914 | p_RXOUT_DIV = qpll.config["d"], 915 | 916 | # TX Fabric Clock Output Control Attributes 917 | p_TXOUT_DIV = qpll.config["d"], 918 | 919 | # RX Phase Interpolator Attributes 920 | p_RXPI_CFG0 = 0b000, 921 | p_RXPI_CFG1 = 0b1, 922 | p_RXPI_CFG2 = 0b1, 923 | 924 | # RX Equalizer Attributes 925 | p_ADAPT_CFG0 = 0x00000, 926 | p_RXLPMRESET_TIME = 0b0001111, 927 | p_RXLPM_BIAS_STARTUP_DISABLE = 0b0, 928 | p_RXLPM_CFG = 0b0110, 929 | p_RXLPM_CFG1 = 0b0, 930 | p_RXLPM_CM_CFG = 0b0, 931 | p_RXLPM_GC_CFG = 0b111100010, 932 | p_RXLPM_GC_CFG2 = 0b001, 933 | p_RXLPM_HF_CFG = 0b00001111110000, 934 | p_RXLPM_HF_CFG2 = 0b01010, 935 | p_RXLPM_HF_CFG3 = 0b0000, 936 | p_RXLPM_HOLD_DURING_EIDLE = 0b0, 937 | p_RXLPM_INCM_CFG = 0b1, 938 | p_RXLPM_IPCM_CFG = 0b0, 939 | p_RXLPM_LF_CFG = 0b000000001111110000, 940 | p_RXLPM_LF_CFG2 = 0b01010, 941 | p_RXLPM_OSINT_CFG = 0b100, 942 | 943 | # TX Phase Interpolator PPM Controller Attributes 944 | p_TXPI_CFG0 = 0b00, 945 | p_TXPI_CFG1 = 0b00, 946 | p_TXPI_CFG2 = 0b00, 947 | p_TXPI_CFG3 = 0b0, 948 | p_TXPI_CFG4 = 0b0, 949 | p_TXPI_CFG5 = 0b000, 950 | p_TXPI_GREY_SEL = 0b0, 951 | p_TXPI_INVSTROBE_SEL = 0b0, 952 | p_TXPI_PPMCLK_SEL = "TXUSRCLK2", 953 | p_TXPI_PPM_CFG = 0x00, 954 | p_TXPI_SYNFREQ_PPM = 0b001, 955 | 956 | # LOOPBACK Attributes 957 | p_LOOPBACK_CFG = 0b0, 958 | p_PMA_LOOPBACK_CFG = 0b0, 959 | 960 | p_RX_CM_SEL = 0b11, 961 | p_RX_CM_TRIM = 0b1010, 962 | 963 | # RX OOB Signalling Attributes 964 | p_RXOOB_CLK_CFG = "FABRIC", 965 | 966 | # TX OOB Signalling Attributes 967 | p_TXOOB_CFG = 0b0, 968 | 969 | # RX Buffer Attributes 970 | p_RXSYNC_MULTILANE = 0b0, 971 | p_RXSYNC_OVRD = 0b0, 972 | p_RXSYNC_SKIP_DA = 0b0, 973 | 974 | # TX Buffer Attributes 975 | p_TXSYNC_MULTILANE = 0b0, 976 | p_TXSYNC_OVRD = 0b1, 977 | p_TXSYNC_SKIP_DA = 0b0, 978 | 979 | # CPLL Ports 980 | i_GTRSVD = 0b0000000000000000, 981 | i_PCSRSVDIN = 0b0000000000000000, 982 | i_TSTIN = 0b11111111111111111111, 983 | 984 | # Channel - DRP Ports 985 | i_DRPADDR = drp_mux.addr, 986 | i_DRPCLK = drp_mux.clk, 987 | i_DRPDI = drp_mux.di, 988 | o_DRPDO = drp_mux.do, 989 | i_DRPEN = drp_mux.en, 990 | o_DRPRDY = drp_mux.rdy, 991 | i_DRPWE = drp_mux.we, 992 | 993 | # Clocking Ports 994 | i_RXSYSCLKSEL = 0b00 if qpll.channel == 0 else 0b11, 995 | i_TXSYSCLKSEL = 0b00 if qpll.channel == 0 else 0b11, 996 | 997 | # FPGA TX Interface Datapath Configuration 998 | i_TX8B10BEN = tx_enable_8b10b, 999 | 1000 | # GTPE2_CHANNEL Clocking Ports 1001 | i_PLL0CLK = qpll.clk if qpll.channel == 0 else 0, 1002 | i_PLL0REFCLK = qpll.refclk if qpll.channel == 0 else 0, 1003 | i_PLL1CLK = qpll.clk if qpll.channel == 1 else 0, 1004 | i_PLL1REFCLK = qpll.refclk if qpll.channel == 1 else 0, 1005 | 1006 | # Loopback Ports 1007 | i_LOOPBACK = self.loopback, 1008 | 1009 | # PCI Express Ports 1010 | o_PHYSTATUS = Open(), 1011 | i_RXRATE = 0, 1012 | o_RXVALID = Open(), 1013 | 1014 | # PMA Reserved Ports 1015 | i_PMARSVDIN3 = 0b0, 1016 | i_PMARSVDIN4 = 0b0, 1017 | 1018 | # Power-Down Ports 1019 | i_RXPD = Cat(rx_init.gtrxpd, rx_init.gtrxpd), 1020 | i_TXPD = 0b00, 1021 | 1022 | # RX 8B/10B Decoder Ports 1023 | i_SETERRSTATUS = 0, 1024 | 1025 | # RX Initialization and Reset Ports 1026 | i_EYESCANRESET = 0, 1027 | i_RXUSERRDY = rx_init.rxuserrdy, 1028 | 1029 | # RX Margin Analysis Ports 1030 | o_EYESCANDATAERROR = Open(), 1031 | i_EYESCANMODE = 0, 1032 | i_EYESCANTRIGGER = 0, 1033 | 1034 | # Receive Ports 1035 | i_CLKRSVD0 = 0, 1036 | i_CLKRSVD1 = 0, 1037 | i_DMONFIFORESET = 0, 1038 | i_DMONITORCLK = 0, 1039 | o_RXPMARESETDONE = rx_init.rxpmaresetdone, 1040 | i_SIGVALIDCLK = lfps_logic_clk, 1041 | 1042 | # Receive Ports - CDR Ports 1043 | i_RXCDRFREQRESET = 0, 1044 | i_RXCDRHOLD = 0, 1045 | o_RXCDRLOCK = Open(), 1046 | i_RXCDROVRDEN = 0, 1047 | i_RXCDRRESET = 0, 1048 | i_RXCDRRESETRSV = 0, 1049 | i_RXOSCALRESET = 0, 1050 | i_RXOSINTCFG = 0b0010, 1051 | o_RXOSINTDONE = Open(), 1052 | i_RXOSINTHOLD = 0, 1053 | i_RXOSINTOVRDEN = 0, 1054 | i_RXOSINTPD = 0, 1055 | o_RXOSINTSTARTED = Open(), 1056 | i_RXOSINTSTROBE = 0, 1057 | o_RXOSINTSTROBESTARTED = Open(), 1058 | i_RXOSINTTESTOVRDEN = 0, 1059 | 1060 | # Receive Ports - Clock Correction Ports 1061 | o_RXCLKCORCNT = Open(), 1062 | 1063 | # Receive Ports - FPGA RX Interface Datapath Configuration 1064 | i_RX8B10BEN = 1, 1065 | 1066 | # Receive Ports - FPGA RX Interface Ports 1067 | o_RXDATA = rx_data, 1068 | i_RXUSRCLK = ClockSignal("rx"), 1069 | i_RXUSRCLK2 = ClockSignal("rx"), 1070 | 1071 | # Receive Ports - Pattern Checker Ports 1072 | o_RXPRBSERR = Open(), 1073 | i_RXPRBSSEL = 0, 1074 | 1075 | # Receive Ports - Pattern Checker ports 1076 | i_RXPRBSCNTRESET = 0, 1077 | 1078 | # Receive Ports - RX 8B/10B Decoder Ports 1079 | o_RXCHARISCOMMA = Open(), 1080 | o_RXCHARISK = rx_ctrl, 1081 | o_RXDISPERR = rx_disp_error, 1082 | o_RXNOTINTABLE = rx_code_error, 1083 | 1084 | # Receive Ports - RX AFE Ports 1085 | i_GTPRXN = self._rx_pads.n, 1086 | i_GTPRXP = self._rx_pads.p, 1087 | i_PMARSVDIN2 = 0b0, 1088 | o_PMARSVDOUT0 = Open(), 1089 | o_PMARSVDOUT1 = Open(), 1090 | 1091 | # Receive Ports - RX Buffer Bypass Ports 1092 | i_RXBUFRESET = 0, 1093 | o_RXBUFSTATUS = Open(), 1094 | i_RXDDIEN = 0, 1095 | i_RXDLYBYPASS = 1, 1096 | i_RXDLYEN = 0, 1097 | i_RXDLYOVRDEN = 0, 1098 | i_RXDLYSRESET = rx_init.rxdlysreset, 1099 | o_RXDLYSRESETDONE = rx_init.rxdlysresetdone, 1100 | i_RXPHALIGN = 0, 1101 | o_RXPHALIGNDONE = rxphaligndone, 1102 | i_RXPHALIGNEN = 0, 1103 | i_RXPHDLYPD = 0, 1104 | i_RXPHDLYRESET = 0, 1105 | o_RXPHMONITOR = Open(), 1106 | i_RXPHOVRDEN = 0, 1107 | o_RXPHSLIPMONITOR = Open(), 1108 | o_RXSTATUS = Open(), 1109 | i_RXSYNCALLIN = rxphaligndone, 1110 | o_RXSYNCDONE = rx_init.rxsyncdone, 1111 | i_RXSYNCIN = 0, 1112 | i_RXSYNCMODE = 0, 1113 | o_RXSYNCOUT = Open(), 1114 | 1115 | # Receive Ports - RX Byte and Word Alignment Ports 1116 | o_RXBYTEISALIGNED = Open(), 1117 | o_RXBYTEREALIGN = Open(), 1118 | o_RXCOMMADET = Open(), 1119 | i_RXCOMMADETEN = 1, 1120 | i_RXMCOMMAALIGNEN = self.rx_align, 1121 | i_RXPCOMMAALIGNEN = self.rx_align, 1122 | i_RXSLIDE = 0, 1123 | 1124 | # Receive Ports - RX Channel Bonding Ports 1125 | o_RXCHANBONDSEQ = Open(), 1126 | i_RXCHBONDEN = 0, 1127 | i_RXCHBONDI = 0b0000, 1128 | i_RXCHBONDLEVEL = 0, 1129 | i_RXCHBONDMASTER = 0, 1130 | o_RXCHBONDO = Open(), 1131 | i_RXCHBONDSLAVE = 0, 1132 | 1133 | # Receive Ports - RX Channel Bonding Ports 1134 | o_RXCHANISALIGNED = Open(), 1135 | o_RXCHANREALIGN = Open(), 1136 | 1137 | # Receive Ports - RX Decision Feedback Equalizer 1138 | o_DMONITOROUT = Open(), 1139 | i_RXADAPTSELTEST = 0, 1140 | i_RXDFEXYDEN = 0, 1141 | i_RXOSINTEN = 0b1, 1142 | i_RXOSINTID0 = 0, 1143 | i_RXOSINTNTRLEN = 0, 1144 | o_RXOSINTSTROBEDONE = Open(), 1145 | 1146 | # Receive Ports - RX Driver,OOB signalling,Coupling and Eq.,CDR 1147 | i_RXLPMLFOVRDEN = 0, 1148 | i_RXLPMOSINTNTRLEN = 0, 1149 | 1150 | # Receive Ports - RX Equalizer Ports 1151 | i_RXLPMHFHOLD = ~self.train_equalizer, 1152 | i_RXLPMHFOVRDEN = 0, 1153 | i_RXLPMLFHOLD = ~self.train_equalizer, 1154 | i_RXOSHOLD = 0, 1155 | i_RXOSOVRDEN = 0, 1156 | 1157 | # Receive Ports - RX Fabric ClocK Output Control Ports 1158 | o_RXRATEDONE = Open(), 1159 | 1160 | # Receive Ports - RX Fabric Clock Output Control Ports 1161 | i_RXRATEMODE = 0b0, 1162 | 1163 | # Receive Ports - RX Fabric Output Control Ports 1164 | o_RXOUTCLK = self.rxoutclk, 1165 | o_RXOUTCLKFABRIC = Open(), 1166 | o_RXOUTCLKPCS = Open(), 1167 | i_RXOUTCLKSEL = 0b010, 1168 | 1169 | # Receive Ports - RX Gearbox Ports 1170 | o_RXDATAVALID = Open(), 1171 | o_RXHEADER = Open(), 1172 | o_RXHEADERVALID = Open(), 1173 | o_RXSTARTOFSEQ = Open(), 1174 | i_RXGEARBOXSLIP = 0, 1175 | 1176 | # Receive Ports - RX Initialization and Reset Ports 1177 | i_GTRXRESET = rx_init.gtrxreset, 1178 | i_RXLPMRESET = 0, 1179 | i_RXOOBRESET = 0, 1180 | i_RXPCSRESET = 0, 1181 | i_RXPMARESET = 0, 1182 | 1183 | # Receive Ports - RX OOB Signaling ports 1184 | o_RXCOMSASDET = Open(), 1185 | o_RXCOMWAKEDET = Open(), 1186 | o_RXCOMINITDET = Open(), 1187 | o_RXELECIDLE = self.rx_idle, 1188 | i_RXELECIDLEMODE = 0b00, 1189 | 1190 | # Receive Ports - RX Polarity Control Ports 1191 | i_RXPOLARITY = self.rx_polarity, 1192 | 1193 | # Receive Ports -RX Initialization and Reset Ports 1194 | o_RXRESETDONE = rx_init.rxresetdone, 1195 | 1196 | # TX Buffer Bypass Ports 1197 | i_TXPHDLYTSTCLK = 0, 1198 | 1199 | # TX Configurable Driver Ports 1200 | i_TXPOSTCURSOR = 0b00000, 1201 | i_TXPOSTCURSORINV = 0, 1202 | i_TXPRECURSOR = 0b00000, 1203 | i_TXPRECURSORINV = 0, 1204 | 1205 | # TX Fabric Clock Output Control Ports 1206 | i_TXRATEMODE = 0, 1207 | 1208 | # TX Initialization and Reset Ports 1209 | i_CFGRESET = 0, 1210 | i_GTTXRESET = tx_init.gttxreset, 1211 | #o_PCSRSVDOUT = Open(), 1212 | i_TXUSERRDY = tx_init.txuserrdy, 1213 | 1214 | # TX Phase Interpolator PPM Controller Ports 1215 | i_TXPIPPMEN = 0, 1216 | i_TXPIPPMOVRDEN = 0, 1217 | i_TXPIPPMPD = 0, 1218 | i_TXPIPPMSEL = 1, 1219 | i_TXPIPPMSTEPSIZE = 0, 1220 | 1221 | # Transceiver Reset Mode Operation 1222 | i_GTRESETSEL = 0, 1223 | i_RESETOVRD = 0, 1224 | 1225 | # Transmit Ports 1226 | #o_TXPMARESETDONE = Open(), 1227 | 1228 | # Transmit Ports - Configurable Driver Ports 1229 | i_PMARSVDIN0 = 0b0, 1230 | i_PMARSVDIN1 = 0b0, 1231 | 1232 | # Transmit Ports - FPGA TX Interface Ports 1233 | i_TXDATA = tx_data, 1234 | i_TXUSRCLK = ClockSignal("tx"), 1235 | i_TXUSRCLK2 = ClockSignal("tx"), 1236 | 1237 | # Transmit Ports - PCI Express Ports 1238 | i_TXELECIDLE = self.tx_idle, 1239 | i_TXMARGIN = 0, 1240 | i_TXRATE = 0, 1241 | i_TXSWING = 0, 1242 | 1243 | # Transmit Ports - Pattern Generator Ports 1244 | i_TXPRBSFORCEERR = 0, 1245 | 1246 | # Transmit Ports - TX 8B/10B Encoder Ports 1247 | i_TX8B10BBYPASS = 0, 1248 | i_TXCHARDISPMODE = tx_char_disp_mode, 1249 | i_TXCHARDISPVAL = tx_char_disp_val, 1250 | i_TXCHARISK = tx_ctrl, 1251 | 1252 | # Transmit Ports - TX Buffer Bypass Ports 1253 | i_TXDLYBYPASS = 1, 1254 | i_TXDLYEN = tx_init.txdlyen, 1255 | i_TXDLYHOLD = 0, 1256 | i_TXDLYOVRDEN = 0, 1257 | i_TXDLYSRESET = tx_init.txdlysreset, 1258 | o_TXDLYSRESETDONE = tx_init.txdlysresetdone, 1259 | i_TXDLYUPDOWN = 0, 1260 | i_TXPHALIGN = tx_init.txphalign, 1261 | o_TXPHALIGNDONE = tx_init.txphaligndone, 1262 | i_TXPHALIGNEN = 0, 1263 | i_TXPHDLYPD = 0, 1264 | i_TXPHDLYRESET = 0, 1265 | i_TXPHINIT = tx_init.txphinit, 1266 | o_TXPHINITDONE = tx_init.txphinitdone, 1267 | i_TXPHOVRDEN = 0, 1268 | 1269 | # Transmit Ports - TX Buffer Ports 1270 | o_TXBUFSTATUS = Open(), 1271 | 1272 | # Transmit Ports - TX Buffer and Phase Alignment Ports 1273 | i_TXSYNCALLIN = 0, 1274 | o_TXSYNCDONE = Open(), 1275 | i_TXSYNCIN = 0, 1276 | i_TXSYNCMODE = 0, 1277 | o_TXSYNCOUT = Open(), 1278 | 1279 | # Transmit Ports - TX Configurable Driver Ports 1280 | o_GTPTXN = self._tx_pads.n, 1281 | o_GTPTXP = self._tx_pads.p, 1282 | i_TXBUFDIFFCTRL = 0b100, 1283 | i_TXDEEMPH = 0, 1284 | i_TXDIFFCTRL = 0b1000, 1285 | i_TXDIFFPD = 0, 1286 | i_TXINHIBIT = self.tx_inhibit, 1287 | i_TXMAINCURSOR = 0b0000000, 1288 | i_TXPISOPD = 0, 1289 | 1290 | # Transmit Ports - TX Fabric Clock Output Control Ports 1291 | o_TXOUTCLK = self.txoutclk, 1292 | o_TXOUTCLKFABRIC = Open(), 1293 | o_TXOUTCLKPCS = Open(), 1294 | i_TXOUTCLKSEL = 0b010, 1295 | o_TXRATEDONE = Open(), 1296 | 1297 | # Transmit Ports - TX Gearbox Ports 1298 | o_TXGEARBOXREADY = Open(), 1299 | i_TXHEADER = 0, 1300 | i_TXSEQUENCE = 0, 1301 | i_TXSTARTSEQ = 0, 1302 | 1303 | # Transmit Ports - TX Initialization and Reset Ports 1304 | i_TXPCSRESET = 0, 1305 | i_TXPMARESET = 0, 1306 | o_TXRESETDONE = tx_init.txresetdone, 1307 | 1308 | # Transmit Ports - TX OOB signalling Ports 1309 | o_TXCOMFINISH = Open(), 1310 | i_TXCOMINIT = 0, 1311 | i_TXCOMSAS = 0, 1312 | i_TXCOMWAKE = 0, 1313 | i_TXPDELECIDLEMODE = 0, 1314 | 1315 | # Transmit Ports - TX Polarity Control Ports 1316 | i_TXPOLARITY = self.tx_polarity, 1317 | 1318 | # Transmit Ports - TX Receiver Detection Ports 1319 | i_TXDETECTRX = 0, 1320 | 1321 | # Transmit Ports - pattern Generator Ports 1322 | i_TXPRBSSEL = 0, 1323 | ) 1324 | 1325 | # 1326 | # TX clocking 1327 | # 1328 | tx_reset_deglitched = Signal() 1329 | #tx_reset_deglitched.attr.add("no_retiming") 1330 | m.d.ss += tx_reset_deglitched.eq(~tx_init.done) 1331 | m.domains.tx = ClockDomain() 1332 | 1333 | m.submodules += Instance("BUFG", 1334 | i_I = self.txoutclk, 1335 | o_O = ClockSignal("tx") 1336 | ) 1337 | m.submodules += ResetSynchronizer(tx_reset_deglitched, domain="tx") 1338 | 1339 | # 1340 | # RX clocking 1341 | # 1342 | rx_reset_deglitched = Signal() 1343 | #rx_reset_deglitched.attr.add("no_retiming") 1344 | m.d.tx += rx_reset_deglitched.eq(~rx_init.done) 1345 | m.domains.rx = ClockDomain() 1346 | m.submodules += [ 1347 | Instance("BUFG", 1348 | i_I = self.rxoutclk, 1349 | o_O = ClockSignal("rx") 1350 | ), 1351 | ResetSynchronizer(rx_reset_deglitched, domain="rx") 1352 | ] 1353 | 1354 | # 1355 | # Tx Datapath 1356 | # 1357 | 1358 | # We're always ready to accept data, since our inner SerDes spews out data at a fixed rate. 1359 | m.d.comb += self.sink.ready.eq(1) 1360 | 1361 | # If we're in Tx-GPIO mode, we'll allow the user to drive a value directly onto the 1362 | # Tx output buffer. This is necessary to allow LFPS signaling. 1363 | with m.If(tx_gpio_en): 1364 | m.d.comb += [ 1365 | # Disable 8B10B drive, so we can control our transmit value directly. 1366 | tx_enable_8b10b .eq(0), 1367 | 1368 | # Constantly drive a full bus of our Tx GPIO value, so we scan out its value, 1369 | # effectively driving the Tx lines to that value. 1370 | tx_data .eq(Repl(tx_gpio, len(tx_data))), 1371 | tx_ctrl .eq(0), 1372 | tx_char_disp_mode .eq(Repl(tx_gpio, len(tx_char_disp_mode))), 1373 | tx_char_disp_val .eq(Repl(tx_gpio, len(tx_char_disp_val))) 1374 | ] 1375 | with m.Else(): 1376 | m.d.comb += [ 1377 | # Enable 8b10b for our normal data... 1378 | tx_enable_8b10b .eq(1), 1379 | 1380 | # ... and provide that data to the SerDes' transmitter. 1381 | tx_data .eq(self.sink.data), 1382 | tx_ctrl .eq(self.sink.ctrl), 1383 | tx_char_disp_mode .eq(0), 1384 | tx_char_disp_val .eq(0) 1385 | ] 1386 | 1387 | 1388 | # 1389 | # Rx Datapath 1390 | # 1391 | 1392 | # We're always grabbing data, so our output should always be valid. 1393 | # (Decoding errors are still valid stream words; but are replaced with SUB). 1394 | m.d.comb += self.source.valid.eq(1) 1395 | 1396 | # ... and then assign their values to our output bus. 1397 | for i in range(nwords): 1398 | 1399 | # If 8B10B decoding failed on the given byte, replace it with our substitution character. 1400 | with m.If(rx_code_error[i]): 1401 | m.d.comb += [ 1402 | self.source.data.word_select(i, 8) .eq(SUB.value), 1403 | self.source.ctrl[i] .eq(SUB.ctrl), 1404 | ] 1405 | 1406 | # Otherwise, pass it through directly. 1407 | with m.Else(): 1408 | m.d.comb += [ 1409 | self.source.data.word_select(i, 8) .eq(rx_data.word_select(i, 8)), 1410 | self.source.ctrl[i] .eq(rx_ctrl[i]) 1411 | ] 1412 | 1413 | return m 1414 | 1415 | 1416 | 1417 | class LunaArtix7SerDes(Elaboratable): 1418 | def __init__(self, ss_clock_frequency, refclk_pads, refclk_frequency, tx_pads, rx_pads): 1419 | """ Wrapper around the core Artix7 SerDes that optimizes the SerDes for USB3 use. """ 1420 | 1421 | self._ss_clock_frequency = ss_clock_frequency 1422 | self._refclk_pads = refclk_pads 1423 | self._refclk_frequency = refclk_frequency 1424 | self._tx_pads = tx_pads 1425 | self._rx_pads = rx_pads 1426 | 1427 | # 1428 | # I/O port 1429 | # 1430 | self.sink = USBRawSuperSpeedStream() 1431 | self.source = USBRawSuperSpeedStream() 1432 | 1433 | self.enable = Signal(reset=1) # i 1434 | self.ready = Signal() # o 1435 | 1436 | self.train_equalizer = Signal(reset=1) 1437 | 1438 | self.tx_polarity = Signal() # i 1439 | self.tx_idle = Signal() # i 1440 | self.tx_pattern = Signal(20) # i 1441 | 1442 | self.rx_polarity = Signal() # i 1443 | self.rx_idle = Signal() # o 1444 | self.rx_align = Signal() # i 1445 | 1446 | # GPIO interface. 1447 | self.use_tx_as_gpio = Signal() 1448 | self.tx_gpio = Signal() 1449 | self.rx_gpio = Signal() 1450 | 1451 | self.lfps_signaling_detected = Signal() 1452 | 1453 | # Debug interface. 1454 | self.alignment_offset = Signal(2) 1455 | self.raw_rx_data = Signal(16) 1456 | self.raw_rx_ctrl = Signal(2) 1457 | 1458 | 1459 | def elaborate(self, platform): 1460 | m = Module() 1461 | 1462 | # 1463 | # Clock 1464 | # 1465 | if isinstance(self._refclk_pads, (Signal, ClockSignal)): 1466 | refclk = self._refclk_pads 1467 | else: 1468 | refclk = Signal() 1469 | m.submodules += [ 1470 | Instance("IBUFDS_GTE2", 1471 | i_CEB=0, 1472 | i_I=self._refclk_pads.p, 1473 | i_IB=self._refclk_pads.n, 1474 | o_O=refclk 1475 | ) 1476 | ] 1477 | 1478 | # 1479 | # PLL 1480 | # 1481 | m.submodules.qpll = qpll = GTPQuadPLL(refclk, self._refclk_frequency, 5e9) 1482 | 1483 | 1484 | # 1485 | # Core Serdes 1486 | # 1487 | m.submodules.serdes = serdes = GTP( 1488 | qpll = qpll, 1489 | tx_pads = self._tx_pads, 1490 | rx_pads = self._rx_pads, 1491 | ss_clock_frequency = self._ss_clock_frequency 1492 | ) 1493 | m.d.comb += self.ready.eq(serdes.tx_ready & serdes.rx_ready), 1494 | 1495 | 1496 | # 1497 | # Transmit datapath. 1498 | # 1499 | m.submodules.tx_datapath = tx_datapath = TransmitPreprocessing() 1500 | m.d.comb += [ 1501 | serdes.tx_idle .eq(self.tx_idle), 1502 | serdes.tx_enable .eq(self.enable), 1503 | serdes.tx_polarity .eq(self.tx_polarity), 1504 | 1505 | tx_datapath.sink .stream_eq(self.sink), 1506 | serdes.sink .stream_eq(tx_datapath.source), 1507 | 1508 | serdes.tx_gpio_en .eq(self.use_tx_as_gpio), 1509 | serdes.tx_gpio .eq(self.tx_gpio) 1510 | ] 1511 | 1512 | 1513 | # 1514 | # Receive datapath. 1515 | # 1516 | m.submodules.rx_datapath = rx_datapath = ReceivePostprocessing() 1517 | m.d.comb += [ 1518 | self.rx_idle .eq(serdes.rx_idle), 1519 | 1520 | serdes.rx_enable .eq(self.enable), 1521 | serdes.rx_polarity .eq(self.rx_polarity), 1522 | serdes.rx_align .eq(self.rx_align), 1523 | rx_datapath.align .eq(self.rx_align), 1524 | serdes.train_equalizer .eq(self.train_equalizer), 1525 | 1526 | rx_datapath.sink .stream_eq(serdes.source), 1527 | self.source .stream_eq(rx_datapath.source), 1528 | 1529 | self.lfps_signaling_detected .eq(~serdes.rx_idle), 1530 | 1531 | # XXX 1532 | self.alignment_offset .eq(rx_datapath.alignment_offset) 1533 | ] 1534 | 1535 | return m 1536 | --------------------------------------------------------------------------------