├── yumewatari ├── __init__.py ├── gateware │ ├── __init__.py │ ├── platform │ │ ├── __init__.py │ │ └── lattice_ecp5.py │ ├── protocol │ │ ├── __init__.py │ │ ├── emitter.py │ │ ├── parser.py │ │ └── engine.py │ ├── struct.py │ ├── debug.py │ ├── align.py │ ├── phy_tx.py │ ├── serdes.py │ ├── phy_rx.py │ └── phy.py ├── testbench │ ├── __init__.py │ ├── ltssm.py │ └── serdes.py ├── vendor │ ├── __init__.py │ ├── pads.py │ └── uart.py └── test │ ├── __init__.py │ ├── test_debug.py │ ├── test_align.py │ ├── test_phy_tx.py │ └── test_phy_rx.py ├── .gitignore ├── LICENSE-0BSD.txt └── ispclock ├── versa5g-pcie-ispCLOCK-100MHz.jed └── versa5g-pcie-ispCLOCK-200MHz.jed /yumewatari/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /yumewatari/gateware/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /yumewatari/testbench/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /yumewatari/vendor/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /yumewatari/gateware/platform/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.pyc 3 | 4 | _trial_temp/ 5 | 6 | /build/ 7 | *.vcd 8 | *.sdc 9 | -------------------------------------------------------------------------------- /yumewatari/gateware/protocol/__init__.py: -------------------------------------------------------------------------------- 1 | from .engine import Memory, NextMemory 2 | from .parser import Parser 3 | from .emitter import Emitter 4 | -------------------------------------------------------------------------------- /LICENSE-0BSD.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 whitequark@whitequark.org 2 | 3 | Permission to use, copy, modify, and/or distribute this software for 4 | any purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 7 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 8 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 9 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 10 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 11 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 12 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /yumewatari/test/__init__.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from migen import * 3 | 4 | 5 | __all__ = ["simulation_test"] 6 | 7 | 8 | def simulation_test(case=None, **kwargs): 9 | def configure_wrapper(case): 10 | @functools.wraps(case) 11 | def wrapper(self): 12 | if hasattr(self, "configure"): 13 | self.configure(self.tb, **kwargs) 14 | def setup_wrapper(): 15 | if hasattr(self, "simulationSetUp"): 16 | yield from self.simulationSetUp(self.tb) 17 | yield from case(self, self.tb) 18 | run_simulation(self.tb, setup_wrapper(), vcd_name="test.vcd") 19 | return wrapper 20 | 21 | if case is None: 22 | return configure_wrapper 23 | else: 24 | return configure_wrapper(case) 25 | -------------------------------------------------------------------------------- /yumewatari/gateware/struct.py: -------------------------------------------------------------------------------- 1 | __all__ = ["ts_layout"] 2 | 3 | 4 | ts_layout = [ 5 | ("valid", 1), 6 | ("link", [ 7 | ("valid", 1), 8 | ("number", 8), 9 | ]), 10 | ("lane", [ 11 | ("valid", 1), 12 | ("number", 5), 13 | ]), 14 | ("n_fts", 8), 15 | ("rate", [ 16 | ("reserved0", 1), 17 | ("gen1", 1), 18 | ("gen2", 1), 19 | ("reserved1", 3), 20 | ("autonomous_change", 1), 21 | ("speed_change", 1), 22 | ]), 23 | ("ctrl", [ 24 | ("hot_reset", 1), 25 | ("disable_link", 1), 26 | ("loopback", 1), 27 | ("disable_scrambling", 1), 28 | ("compliance_receive", 1), 29 | ]), 30 | ("ts_id", 1), # 0: TS1, 1: TS2 31 | ] 32 | -------------------------------------------------------------------------------- /ispclock/versa5g-pcie-ispCLOCK-100MHz.jed: -------------------------------------------------------------------------------- 1 |  2 | * 3 | NOTE Version: PAC-Designer 6.32.1347 * 4 | NOTE Copyright (C), 1995-2011, Lattice Semiconductor Corporation. * 5 | NOTE All Rights Reserved * 6 | NOTE DATE CREATED: 11/8/2018 * 7 | NOTE DESIGN NAME: versa5g-pcie.PAC * 8 | NOTE DEVICE NAME: ispPAC-CLK5406D * 9 | NOTE PIN ASSIGNMENTS * 10 | 11 | 12 | 13 | QF420* 14 | QP64* 15 | G0* 16 | F0* 17 | L00000 100011111111111111111110010111111110111111* 18 | L00042 100011011111011000011111110111001111111111* 19 | L00084 110011011111111000011110101111001101011111* 20 | L00126 110011011111111000011111001111001100011111* 21 | L00168 100011011111001000001111101110000000011111* 22 | L00210 100010011111101111101111100110110010000111* 23 | L00252 100010011111001111101111101110110010000111* 24 | L00294 110000011100001111101111011110110010000111* 25 | L00336 110000011100000111101111111110110000000111* 26 | L00378 110000011100001111111111111111001110000111* 27 | C2212* 28 | U11111111111111111111111111111111* 29 | B7CD -------------------------------------------------------------------------------- /ispclock/versa5g-pcie-ispCLOCK-200MHz.jed: -------------------------------------------------------------------------------- 1 |  2 | * 3 | NOTE Version: PAC-Designer 6.32.1347 * 4 | NOTE Copyright (C), 1995-2011, Lattice Semiconductor Corporation. * 5 | NOTE All Rights Reserved * 6 | NOTE DATE CREATED: 11/8/2018 * 7 | NOTE DESIGN NAME: versa5g-pcie.PAC * 8 | NOTE DEVICE NAME: ispPAC-CLK5406D * 9 | NOTE PIN ASSIGNMENTS * 10 | 11 | 12 | 13 | QF420* 14 | QP64* 15 | G0* 16 | F0* 17 | L00000 100011111111111111111110010111111110111111* 18 | L00042 100011011111011000011111110111001111111111* 19 | L00084 110011011111111000011110101111001101011111* 20 | L00126 110011011111111000011111001111001100011111* 21 | L00168 100011011111001000001111101110000000011111* 22 | L00210 100010011111101111101111100110110010000111* 23 | L00252 100010011111001111101111101110110010011111* 24 | L00294 110000011100001111101111011110110010011111* 25 | L00336 110000011100000111101111111110110000000111* 26 | L00378 110000011100001111111111111111001100000111* 27 | C2220* 28 | U11111111111111111111111111111111* 29 | B7CF -------------------------------------------------------------------------------- /yumewatari/test/test_debug.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from migen import * 3 | 4 | from ..gateware.debug import * 5 | from . import simulation_test 6 | 7 | 8 | class RingLogTestbench(Module): 9 | def __init__(self): 10 | self.submodules.dut = RingLog(timestamp_width=8, data_width=8, depth=4) 11 | 12 | def read_out(self): 13 | yield self.dut.trigger.eq(1) 14 | yield 15 | result = [] 16 | for _ in range(self.dut.depth): 17 | yield self.dut.next.eq(1) 18 | yield 19 | result.append(((yield self.dut.time_o), (yield self.dut.data_o))) 20 | yield self.dut.next.eq(0) 21 | yield 22 | yield self.dut.trigger.eq(0) 23 | yield 24 | return result 25 | 26 | 27 | class RingLogTestCase(unittest.TestCase): 28 | def setUp(self): 29 | self.tb = RingLogTestbench() 30 | 31 | @simulation_test 32 | def test_basic(self, tb): 33 | yield 34 | yield 35 | yield 36 | yield tb.dut.data_i.eq(0x55) 37 | yield 38 | yield 39 | yield 40 | yield 41 | yield 42 | yield tb.dut.data_i.eq(0xaa) 43 | yield 44 | self.assertEqual((yield from tb.read_out()), [ 45 | (0, 0), 46 | (0, 0), 47 | (4, 0x55), 48 | (9, 0xaa), 49 | ]) 50 | -------------------------------------------------------------------------------- /yumewatari/gateware/debug.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | 3 | 4 | __all__ = ["RingLog"] 5 | 6 | 7 | class RingLog(Module): 8 | def __init__(self, timestamp_width, data_width, depth): 9 | self.width = timestamp_width + data_width 10 | self.depth = depth 11 | 12 | self.data_i = Signal(data_width) 13 | self.trigger = Signal() 14 | 15 | self.time_o = Signal(timestamp_width) 16 | self.data_o = Signal(data_width) 17 | self.next = Signal() 18 | 19 | ### 20 | 21 | timestamp = Signal(timestamp_width) 22 | self.sync += timestamp.eq(timestamp + 1) 23 | 24 | data_i_l = Signal.like(self.data_i) 25 | self.sync += data_i_l.eq(self.data_i) 26 | 27 | storage = Memory(width=self.width, depth=self.depth) 28 | self.specials += storage 29 | 30 | wrport = storage.get_port(write_capable=True) 31 | self.specials += wrport 32 | self.comb += [ 33 | wrport.we.eq(~self.trigger & (self.data_i != data_i_l)), 34 | wrport.dat_w.eq(Cat(timestamp, self.data_i)) 35 | ] 36 | self.sync += [ 37 | If(~self.trigger, 38 | If(self.data_i != data_i_l, 39 | wrport.adr.eq(wrport.adr + 1) 40 | ) 41 | ) 42 | ] 43 | 44 | trigger_s = Signal.like(self.trigger) 45 | self.sync += trigger_s.eq(self.trigger) 46 | 47 | rdport = storage.get_port() 48 | self.specials += rdport 49 | self.comb += [ 50 | Cat(self.time_o, self.data_o).eq(rdport.dat_r), 51 | ] 52 | self.sync += [ 53 | If(~self.trigger, 54 | rdport.adr.eq(wrport.adr + 1) 55 | ).Elif(self.next, 56 | rdport.adr.eq(rdport.adr + 1) 57 | ) 58 | ] 59 | -------------------------------------------------------------------------------- /yumewatari/gateware/align.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | 3 | 4 | __all__ = ["SymbolSlip"] 5 | 6 | 7 | class SymbolSlip(Module): 8 | """ 9 | Symbol slip based comma aligner. Accepts and emits a sequence of words, shifting it such 10 | that if a comma symbol is encountered, it is always placed at the start of a word. 11 | 12 | If the input word contains multiple commas, the behavior is undefined. 13 | 14 | Parameters 15 | ---------- 16 | symbol_size : int 17 | Symbol width, in bits. 18 | word_size : int 19 | Word size, in symbols. 20 | comma : int 21 | Comma symbol, ``symbol_size`` bit wide. 22 | 23 | Attributes 24 | ---------- 25 | i : Signal(symbol_size * word_size) 26 | Input word. 27 | o : Signal(symbol_size * word_size) 28 | Output word. 29 | en : Signal 30 | Enable input. If asserted (the default), comma symbol affects alignment. Otherwise, 31 | comma symbol does nothing. 32 | """ 33 | def __init__(self, symbol_size, word_size, comma): 34 | width = symbol_size * word_size 35 | 36 | self.i = Signal(width) 37 | self.o = Signal(width) 38 | self.en = Signal(reset=1) 39 | 40 | ### 41 | 42 | shreg = Signal(width * 2) 43 | offset = Signal(max=symbol_size * (word_size - 1)) 44 | self.sync += shreg.eq(Cat(shreg[width:], self.i)) 45 | self.comb += self.o.eq(shreg.part(offset, width)) 46 | 47 | commas = Signal(word_size) 48 | self.sync += [ 49 | commas[n].eq(self.i.part(symbol_size * n, symbol_size) == comma) 50 | for n in range(word_size) 51 | ] 52 | 53 | self.sync += [ 54 | If(self.en, 55 | Case(commas, { 56 | (1 << n): offset.eq(symbol_size * n) 57 | for n in range(word_size) 58 | }) 59 | ) 60 | ] 61 | -------------------------------------------------------------------------------- /yumewatari/gateware/protocol/emitter.py: -------------------------------------------------------------------------------- 1 | import os 2 | from migen import * 3 | 4 | from .engine import _ProtocolFSM, _ProtocolEngine 5 | 6 | 7 | _DEBUG = os.getenv("DEBUG_EMITTER") 8 | 9 | 10 | class Emitter(_ProtocolEngine): 11 | def __init__(self, symbol_size, word_size, reset_rule, layout=None): 12 | super().__init__(symbol_size, word_size, reset_rule) 13 | 14 | self.o = Signal(symbol_size * word_size) 15 | 16 | ### 17 | 18 | self._o = [Signal(symbol_size) for n in range(word_size)] 19 | self.comb += self.o.eq(Cat(self._o)) 20 | if layout is not None: 21 | for n in range(word_size): 22 | irec = Record(layout) 23 | self.comb += self._o[n].eq(irec.raw_bits()) 24 | self._o[n] = irec 25 | 26 | def do_finalize(self): 27 | self.submodules.fsm = _ProtocolFSM() 28 | 29 | if _DEBUG: 30 | print("Emitter layout:") 31 | worklist = {self._reset_rule} 32 | processed = set() 33 | while worklist: 34 | rule_name = worklist.pop() 35 | processed.add(rule_name) 36 | 37 | if _DEBUG: 38 | print(" State %s" % rule_name) 39 | 40 | rule_tuples = set() 41 | self._get_rule_tuples(rule_name, rule_tuples) 42 | 43 | conds = [] 44 | actions = [] 45 | for i, rule_tuple in enumerate(rule_tuples): 46 | if _DEBUG: 47 | print(" Output #%d %s -> %s" % 48 | (i, rule_name, " -> ".join(rule.succ for rule in rule_tuple))) 49 | 50 | succ = rule_tuple[-1].succ 51 | action = [NextState(succ)] 52 | for j, rule in enumerate(reversed(rule_tuple)): 53 | symbol = self._o[self._word_size - j - 1] 54 | action = [ 55 | If(rule.cond(), 56 | rule.action(symbol), 57 | *action 58 | ), 59 | ] 60 | 61 | actions.append(action) 62 | if succ not in processed: 63 | worklist.add(succ) 64 | 65 | self.fsm.act(rule_name, actions) 66 | -------------------------------------------------------------------------------- /yumewatari/gateware/protocol/parser.py: -------------------------------------------------------------------------------- 1 | import os 2 | from migen import * 3 | 4 | from .engine import _ProtocolFSM, _ProtocolEngine 5 | 6 | 7 | _DEBUG = os.getenv("DEBUG_PARSER") 8 | 9 | 10 | class Parser(_ProtocolEngine): 11 | def __init__(self, symbol_size, word_size, reset_rule, layout=None): 12 | super().__init__(symbol_size, word_size, reset_rule) 13 | 14 | self.reset = Signal() 15 | self.error = Signal() 16 | self.i = Signal(symbol_size * word_size) 17 | 18 | ### 19 | 20 | self._i = [self.i.part(n * symbol_size, symbol_size) for n in range(word_size)] 21 | if layout is not None: 22 | for n in range(word_size): 23 | irec = Record(layout) 24 | self.comb += irec.raw_bits().eq(self._i[n]) 25 | self._i[n] = irec 26 | 27 | def do_finalize(self): 28 | self.submodules.fsm = ResetInserter()(_ProtocolFSM()) 29 | self.comb += self.fsm.reset.eq(self.reset | self.error) 30 | 31 | if _DEBUG: 32 | print("Parser layout:") 33 | worklist = {self._reset_rule} 34 | processed = set() 35 | while worklist: 36 | rule_name = worklist.pop() 37 | processed.add(rule_name) 38 | 39 | if _DEBUG: 40 | print(" State %s" % rule_name) 41 | 42 | rule_tuples = set() 43 | self._get_rule_tuples(rule_name, rule_tuples) 44 | 45 | actions = [] 46 | for i, rule_tuple in enumerate(rule_tuples): 47 | if _DEBUG: 48 | print(" Input #%d %s -> %s" % 49 | (i, rule_name, " -> ".join(rule.succ for rule in rule_tuple))) 50 | 51 | succ = rule_tuple[-1].succ 52 | action = [ 53 | self.error.eq(0), 54 | NextState(succ) 55 | ] 56 | for j, rule in enumerate(reversed(rule_tuple)): 57 | symbol = self._i[self._word_size - j - 1] 58 | action = [ 59 | If(rule.cond(symbol), 60 | rule.action(symbol), 61 | *action 62 | ), 63 | ] 64 | 65 | actions.append(action) 66 | if succ not in processed: 67 | worklist.add(succ) 68 | 69 | self.fsm.act(rule_name, [ 70 | self.error.eq(1), 71 | *actions 72 | ]) 73 | -------------------------------------------------------------------------------- /yumewatari/test/test_align.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from migen import * 3 | 4 | from ..gateware.align import * 5 | from . import simulation_test 6 | 7 | 8 | class SymbolSlipTestbench(Module): 9 | def __init__(self): 10 | self.submodules.dut = SymbolSlip(symbol_size=8, word_size=4, comma=0xaa) 11 | 12 | 13 | class SymbolSlipTestCase(unittest.TestCase): 14 | def setUp(self): 15 | self.tb = SymbolSlipTestbench() 16 | 17 | @simulation_test 18 | def test_no_slip(self, tb): 19 | yield tb.dut.i.eq(0x04030201) 20 | yield 21 | self.assertEqual((yield tb.dut.o), 0) 22 | yield tb.dut.i.eq(0x08070605) 23 | yield 24 | self.assertEqual((yield tb.dut.o), 0) 25 | yield tb.dut.i.eq(0x0c0b0a09) 26 | yield 27 | self.assertEqual((yield tb.dut.o), 0x04030201) 28 | yield tb.dut.i.eq(0) 29 | yield 30 | self.assertEqual((yield tb.dut.o), 0x08070605) 31 | yield 32 | self.assertEqual((yield tb.dut.o), 0x0c0b0a09) 33 | 34 | @simulation_test 35 | def test_slip(self, tb): 36 | yield tb.dut.i.eq(0x0403aa01) 37 | yield 38 | yield tb.dut.i.eq(0x08070605) 39 | yield 40 | self.assertEqual((yield tb.dut.o), 0) 41 | yield tb.dut.i.eq(0x0c0b0a09) 42 | yield 43 | self.assertEqual((yield tb.dut.o), 0x050403aa) 44 | yield tb.dut.i.eq(0x000f0e0d) 45 | yield 46 | self.assertEqual((yield tb.dut.o), 0x09080706) 47 | 48 | @simulation_test 49 | def test_slip_2(self, tb): 50 | yield tb.dut.i.eq(0x0403aa01) 51 | yield 52 | yield tb.dut.i.eq(0x080706aa) 53 | yield 54 | self.assertEqual((yield tb.dut.o), 0) 55 | yield tb.dut.i.eq(0x0c0b0a09) 56 | yield 57 | self.assertEqual((yield tb.dut.o), 0xaa0403aa) 58 | yield tb.dut.i.eq(0x000f0e0d) 59 | yield 60 | self.assertEqual((yield tb.dut.o), 0x080706aa) 61 | yield tb.dut.i.eq(0x14131210) 62 | yield 63 | self.assertEqual((yield tb.dut.o), 0x0c0b0a09) 64 | 65 | @simulation_test 66 | def test_enable(self, tb): 67 | yield tb.dut.en.eq(0) 68 | yield tb.dut.i.eq(0x0403aa01) 69 | yield 70 | yield tb.dut.i.eq(0x08070605) 71 | yield 72 | self.assertEqual((yield tb.dut.o), 0) 73 | yield tb.dut.i.eq(0x0c0b0a09) 74 | yield 75 | self.assertEqual((yield tb.dut.o), 0x0403aa01) 76 | yield tb.dut.i.eq(0x000f0e0d) 77 | yield 78 | self.assertEqual((yield tb.dut.o), 0x08070605) 79 | -------------------------------------------------------------------------------- /yumewatari/gateware/protocol/engine.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple, defaultdict 2 | from migen import * 3 | from migen.fhdl.structure import _Value, _Statement 4 | from migen.genlib.fsm import _LowerNext, FSM 5 | 6 | 7 | class Memory(_Value): 8 | def __init__(self, target): 9 | self.target = target 10 | 11 | 12 | class NextMemory(_Statement): 13 | def __init__(self, target, value): 14 | self.target = target 15 | self.value = value 16 | 17 | 18 | class _LowerMemory(_LowerNext): 19 | def __init__(self, *args): 20 | super().__init__(*args) 21 | # (target, next_value_ce, next_value) 22 | self.memories = [] 23 | 24 | def _get_memory_control(self, memory): 25 | for target, next_value_ce, next_value in self.memories: 26 | if target is memory: 27 | break 28 | else: 29 | next_value_ce = Signal(related=memory) 30 | next_value = Signal(memory.nbits, related=memory) 31 | self.memories.append((memory, next_value_ce, next_value)) 32 | return next_value_ce, next_value 33 | 34 | def visit_unknown(self, node): 35 | if isinstance(node, Memory): 36 | next_value_ce, next_value = self._get_memory_control(node.target) 37 | return Mux(next_value_ce, next_value, node.target) 38 | elif isinstance(node, NextMemory): 39 | next_value_ce, next_value = self._get_memory_control(node.target) 40 | return next_value_ce.eq(1), next_value.eq(node.value) 41 | else: 42 | return super().visit_unknown(node) 43 | 44 | 45 | class _ProtocolFSM(FSM): 46 | def _lower_controls(self): 47 | return _LowerMemory(self.next_state, self.encoding, self.state_aliases) 48 | 49 | def _finalize_sync(self, ls): 50 | super()._finalize_sync(ls) 51 | for memory, next_value_ce, next_value in ls.memories: 52 | self.sync += If(next_value_ce, memory.eq(next_value)) 53 | 54 | 55 | _Rule = namedtuple("_Rule", ("name", "cond", "succ", "action")) 56 | 57 | 58 | class _ProtocolEngine(Module): 59 | def __init__(self, symbol_size, word_size, reset_rule): 60 | self._symbol_size = symbol_size 61 | self._word_size = word_size 62 | self._reset_rule = reset_rule 63 | # name -> [(cond, succ, action)] 64 | self._grammar = defaultdict(lambda: []) 65 | 66 | def rule(self, name, succ, cond=lambda *_: True, action=lambda symbol: []): 67 | self._grammar[name].append(_Rule(name, cond, succ, action)) 68 | 69 | def _get_rule_tuples(self, rule_name, rule_tuples, rule_path=()): 70 | if len(rule_path) == self._word_size: 71 | rule_tuples.add(rule_path) 72 | return 73 | 74 | for rule in self._grammar[rule_name]: 75 | self._get_rule_tuples(rule.succ, rule_tuples, rule_path + (rule,)) 76 | -------------------------------------------------------------------------------- /yumewatari/vendor/pads.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | 3 | 4 | __all__ = ['Pads'] 5 | 6 | 7 | class Pads(Module): 8 | """ 9 | Pad adapter. 10 | 11 | Provides a common interface to device pads, wrapping either a Migen platform request, 12 | or a Glasgow I/O port slice. 13 | 14 | Construct a pad adapter providing signals, records, or tristate triples; name may 15 | be specified explicitly with keyword arguments. For each signal, record field, or 16 | triple with name ``n``, the pad adapter will have an attribute ``n_t`` containing 17 | a tristate triple. ``None`` may also be provided, and is ignored; no attribute 18 | is added to the adapter. 19 | 20 | For example, if a Migen platform file contains the definitions :: 21 | 22 | _io = [ 23 | ("i2c", 0, 24 | Subsignal("scl", Pins("39")), 25 | Subsignal("sda", Pins("40")), 26 | ), 27 | # ... 28 | ] 29 | 30 | then a pad adapter constructed as ``Pads(platform.request("i2c"))`` will have 31 | attributes ``scl_t`` and ``sda_t`` containing tristate triples for their respective 32 | pins. 33 | 34 | If a Glasgow applet contains the code :: 35 | 36 | port = target.get_port(args.port) 37 | pads = Pads(tx=port[args.pin_tx], rx=port[args.pin_rx]) 38 | target.submodules += pads 39 | 40 | then the pad adapter ``pads`` will have attributes ``tx_t`` and ``rx_t`` containing 41 | tristate triples for their respective pins; since Glasgow I/O ports return tristate 42 | triples when slicing, the results of slicing are unchanged. 43 | """ 44 | def __init__(self, *args, **kwargs): 45 | for (i, elem) in enumerate(args): 46 | self._add_elem(elem, index=i) 47 | for name, elem in kwargs.items(): 48 | self._add_elem(elem, name) 49 | 50 | def _add_elem(self, elem, name=None, index=None): 51 | if elem is None: 52 | return 53 | elif isinstance(elem, Record): 54 | for field in elem.layout: 55 | if name is None: 56 | field_name = field[0] 57 | else: 58 | field_name = "{}_{}".format(name, field[0]) 59 | self._add_elem(getattr(elem, field[0]), field_name) 60 | return 61 | elif isinstance(elem, Signal): 62 | triple = TSTriple() 63 | self.specials += triple.get_tristate(elem) 64 | 65 | if name is None: 66 | name = elem.backtrace[-1][0] 67 | elif isinstance(elem, TSTriple): 68 | triple = elem 69 | 70 | if name is None and index is None: 71 | raise ValueError("Name must be provided for {!r}".format(elem)) 72 | elif name is None: 73 | raise ValueError("Name must be provided for {!r} (argument {})" 74 | .format(elem, index + 1)) 75 | 76 | triple_name = "{}_t".format(name) 77 | if hasattr(self, triple_name): 78 | raise ValueError("Cannot add {!r} as attribute {}; attribute already exists") 79 | 80 | setattr(self, triple_name, triple) 81 | -------------------------------------------------------------------------------- /yumewatari/gateware/phy_tx.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | 3 | from .serdes import K, D 4 | from .protocol import * 5 | from .struct import * 6 | 7 | 8 | __all__ = ["PCIePHYTX"] 9 | 10 | 11 | class PCIePHYTX(Module): 12 | def __init__(self, lane): 13 | self.e_idle = Signal() 14 | self.comma = Signal() 15 | self.ts = Record(ts_layout) 16 | 17 | ### 18 | 19 | self.submodules.emitter = Emitter( 20 | symbol_size=12, 21 | word_size=lane.ratio, 22 | reset_rule="IDLE", 23 | layout=[ 24 | ("data", 8), 25 | ("ctrl", 1), 26 | ("set_disp", 1), 27 | ("disp", 1), 28 | ("e_idle", 1), 29 | ]) 30 | self.comb += [ 31 | lane.tx_symbol.eq(Cat( 32 | (self.emitter._o[n].data, self.emitter._o[n].ctrl) 33 | for n in range(lane.ratio) 34 | )), 35 | lane.tx_set_disp.eq(Cat(self.emitter._o[n].set_disp for n in range(lane.ratio))), 36 | lane.tx_disp .eq(Cat(self.emitter._o[n].disp for n in range(lane.ratio))), 37 | lane.tx_e_idle .eq(Cat(self.emitter._o[n].e_idle for n in range(lane.ratio))), 38 | ] 39 | self.emitter.rule( 40 | name="IDLE", 41 | cond=lambda: self.e_idle, 42 | succ="IDLE", 43 | action=lambda symbol: [ 44 | symbol.e_idle.eq(1) 45 | ] 46 | ) 47 | self.emitter.rule( 48 | name="IDLE", 49 | cond=lambda: self.ts.valid, 50 | succ="TSn-LINK", 51 | action=lambda symbol: [ 52 | self.comma.eq(1), 53 | symbol.raw_bits().eq(K(28,5)), 54 | symbol.set_disp.eq(1), 55 | symbol.disp.eq(0) 56 | ] 57 | ) 58 | self.emitter.rule( 59 | name="TSn-LINK", 60 | succ="TSn-LANE", 61 | action=lambda symbol: [ 62 | If(self.ts.link.valid, 63 | symbol.data.eq(self.ts.link.number) 64 | ).Else( 65 | symbol.raw_bits().eq(K(23,7)) 66 | ), 67 | ] 68 | ) 69 | self.emitter.rule( 70 | name="TSn-LANE", 71 | succ="TSn-FTS", 72 | action=lambda symbol: [ 73 | If(self.ts.lane.valid, 74 | symbol.data.eq(self.ts.lane.number) 75 | ).Else( 76 | symbol.raw_bits().eq(K(23,7)) 77 | ), 78 | ] 79 | ) 80 | self.emitter.rule( 81 | name="TSn-FTS", 82 | succ="TSn-RATE", 83 | action=lambda symbol: [ 84 | symbol.data.eq(self.ts.n_fts), 85 | ] 86 | ) 87 | self.emitter.rule( 88 | name="TSn-RATE", 89 | succ="TSn-CTRL", 90 | action=lambda symbol: [ 91 | symbol.data.eq(self.ts.rate.raw_bits()), 92 | ] 93 | ) 94 | self.emitter.rule( 95 | name="TSn-CTRL", 96 | succ="TSn-ID0", 97 | action=lambda symbol: [ 98 | symbol.data.eq(self.ts.ctrl.raw_bits()), 99 | ] 100 | ) 101 | for n in range(0, 10): 102 | self.emitter.rule( 103 | name="TSn-ID%d" % n, 104 | succ="IDLE" if n == 9 else "TSn-ID%d" % (n + 1), 105 | action=lambda symbol: [ 106 | If(self.ts.ts_id == 0, 107 | symbol.raw_bits().eq(D(10,2)) 108 | ).Else( 109 | symbol.raw_bits().eq(D(5,2)) 110 | ) 111 | ] 112 | ) 113 | -------------------------------------------------------------------------------- /yumewatari/test/test_phy_tx.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from migen import * 3 | 4 | from ..gateware.serdes import * 5 | from ..gateware.serdes import K, D 6 | from ..gateware.phy_tx import * 7 | from . import simulation_test 8 | 9 | 10 | class PCIePHYTXTestbench(Module): 11 | def __init__(self, ratio=1): 12 | self.submodules.lane = PCIeSERDESInterface(ratio) 13 | self.submodules.phy = PCIePHYTX(self.lane) 14 | 15 | def do_finalize(self): 16 | self.states = {v: k for k, v in self.phy.emitter.fsm.encoding.items()} 17 | 18 | def phy_state(self): 19 | return self.states[(yield self.phy.emitter.fsm.state)] 20 | 21 | def receive(self, count): 22 | symbols = [] 23 | for _ in range(count): 24 | word = yield self.lane.tx_symbol 25 | if self.lane.ratio == 1: 26 | symbols.append(word) 27 | else: 28 | symbols.append(tuple((word >> (9 * n)) & 0x1ff 29 | for n in range(self.lane.ratio))) 30 | yield 31 | return symbols 32 | 33 | 34 | class _PCIePHYTXTestCase(unittest.TestCase): 35 | def assertReceive(self, tb, symbols): 36 | self.assertEqual((yield from tb.receive(len(symbols))), symbols) 37 | 38 | 39 | class PCIePHYTXGear1xTestCase(_PCIePHYTXTestCase): 40 | def setUp(self): 41 | self.tb = PCIePHYTXTestbench() 42 | 43 | def assertReceive(self, tb, symbols): 44 | self.assertEqual((yield from tb.receive(len(symbols))), symbols) 45 | 46 | @simulation_test 47 | def test_tx_ts1_pad(self, tb): 48 | yield tb.phy.ts.valid.eq(1) 49 | yield tb.phy.ts.n_fts.eq(0xff) 50 | yield tb.phy.ts.rate.gen1.eq(1) 51 | yield 52 | yield from self.assertReceive(tb, [ 53 | K(28,5), K(23,7), K(23,7), 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 54 | K(28,5) 55 | ]) 56 | 57 | @simulation_test 58 | def test_tx_ts1_link(self, tb): 59 | yield tb.phy.ts.valid.eq(1) 60 | yield tb.phy.ts.link.valid.eq(1) 61 | yield tb.phy.ts.link.number.eq(0xaa) 62 | yield tb.phy.ts.n_fts.eq(0xff) 63 | yield tb.phy.ts.rate.gen1.eq(1) 64 | yield 65 | yield from self.assertReceive(tb, [ 66 | K(28,5), 0xaa, K(23,7), 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 67 | K(28,5) 68 | ]) 69 | 70 | @simulation_test 71 | def test_tx_ts1_link_lane(self, tb): 72 | yield tb.phy.ts.valid.eq(1) 73 | yield tb.phy.ts.link.valid.eq(1) 74 | yield tb.phy.ts.link.number.eq(0xaa) 75 | yield tb.phy.ts.lane.valid.eq(1) 76 | yield tb.phy.ts.lane.number.eq(0x01) 77 | yield tb.phy.ts.n_fts.eq(0xff) 78 | yield tb.phy.ts.rate.gen1.eq(1) 79 | yield 80 | yield from self.assertReceive(tb, [ 81 | K(28,5), 0xaa, 0x01, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 82 | K(28,5) 83 | ]) 84 | 85 | @simulation_test 86 | def test_tx_ts1_reset(self, tb): 87 | yield tb.phy.ts.valid.eq(1) 88 | yield tb.phy.ts.n_fts.eq(0xff) 89 | yield tb.phy.ts.rate.gen1.eq(1) 90 | yield tb.phy.ts.ctrl.hot_reset.eq(1) 91 | yield 92 | yield from self.assertReceive(tb, [ 93 | K(28,5), K(23,7), K(23,7), 0xff, 0b0010, 0b0001, *[D(10,2) for _ in range(10)], 94 | K(28,5) 95 | ]) 96 | 97 | @simulation_test 98 | def test_tx_ts2(self, tb): 99 | yield tb.phy.ts.valid.eq(1) 100 | yield tb.phy.ts.n_fts.eq(0xff) 101 | yield tb.phy.ts.rate.gen1.eq(1) 102 | yield tb.phy.ts.ts_id.eq(1) 103 | yield 104 | yield from self.assertReceive(tb, [ 105 | K(28,5), K(23,7), K(23,7), 0xff, 0b0010, 0b0000, *[D(5,2) for _ in range(10)], 106 | K(28,5) 107 | ]) 108 | 109 | 110 | class PCIePHYTXGear2xTestCase(_PCIePHYTXTestCase): 111 | def setUp(self): 112 | self.tb = PCIePHYTXTestbench(ratio=2) 113 | 114 | @simulation_test 115 | def test_tx_ts1_link_lane(self, tb): 116 | yield tb.phy.ts.valid.eq(1) 117 | yield tb.phy.ts.link.valid.eq(1) 118 | yield tb.phy.ts.link.number.eq(0xaa) 119 | yield tb.phy.ts.lane.valid.eq(1) 120 | yield tb.phy.ts.lane.number.eq(0x01) 121 | yield tb.phy.ts.n_fts.eq(0xff) 122 | yield tb.phy.ts.rate.gen1.eq(1) 123 | yield 124 | yield from self.assertReceive(tb, [ 125 | (K(28,5), 0xaa), (0x01, 0xff), (0b0010, 0b0000), 126 | *[(D(10,2), D(10,2)) for _ in range(5)], 127 | ]) 128 | -------------------------------------------------------------------------------- /yumewatari/gateware/serdes.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | 3 | from .align import SymbolSlip 4 | 5 | 6 | __all__ = ["PCIeSERDESInterface", "PCIeSERDESAligner"] 7 | 8 | 9 | def K(x, y): return (1 << 8) | (y << 5) | x 10 | def D(x, y): return (0 << 8) | (y << 5) | x 11 | 12 | 13 | class PCIeSERDESInterface(Module): 14 | """ 15 | Interface of a single PCIe SERDES pair, connected to a single lane. Uses 1:**ratio** gearing 16 | for configurable **ratio**, i.e. **ratio** symbols are transmitted per clock cycle. 17 | 18 | Parameters 19 | ---------- 20 | ratio : int 21 | Gearbox ratio. 22 | 23 | rx_invert : Signal 24 | Assert to invert the received bits before 8b10b decoder. 25 | rx_align : Signal 26 | Assert to enable comma alignment state machine, deassert to lock alignment. 27 | rx_present : Signal 28 | Asserted if the receiver has detected signal. 29 | rx_locked : Signal 30 | Asserted if the receiver has recovered a valid clock. 31 | rx_aligned : Signal 32 | Asserted if the receiver has aligned to the comma symbol. 33 | 34 | rx_symbol : Signal(9 * ratio) 35 | Two 8b10b-decoded received symbols, with 9th bit indicating a control symbol. 36 | rx_valid : Signal(ratio) 37 | Asserted if the received symbol has no coding errors. If not asserted, ``rx_data`` and 38 | ``rx_control`` must be ignored, and may contain symbols that do not exist in 8b10b coding 39 | space. 40 | 41 | tx_locked : Signal 42 | Asserted if the transmitter is generating a valid clock. 43 | 44 | tx_symbol : Signal(9 * ratio) 45 | Symbol to 8b10b-encode and transmit, with 9th bit indicating a control symbol. 46 | tx_set_disp : Signal(ratio) 47 | Assert to indicate that the 8b10b encoder should choose an encoding with a specific 48 | running disparity instead of using its state, specified by ``tx_disp``. 49 | tx_disp : Signal(ratio) 50 | Assert to transmit a symbol with positive running disparity, deassert for negative 51 | running disparity. 52 | tx_e_idle : Signal(ratio) 53 | Assert to transmit Electrical Idle for that symbol. 54 | 55 | det_enable : Signal 56 | Rising edge starts the Receiver Detection test. Transmitter must be in Electrical Idle 57 | when ``det_enable`` is asserted. 58 | det_valid : Signal 59 | Asserted to indicate that the Receiver Detection test has finished, deasserted together 60 | with ``det_enable``. 61 | det_status : Signal 62 | Valid when ``det_valid`` is asserted. Indicates whether a receiver has been detected 63 | on this lane. 64 | """ 65 | def __init__(self, ratio=1): 66 | self.ratio = ratio 67 | 68 | self.rx_invert = Signal() 69 | self.rx_align = Signal() 70 | self.rx_present = Signal() 71 | self.rx_locked = Signal() 72 | self.rx_aligned = Signal() 73 | 74 | self.rx_symbol = Signal(ratio * 9) 75 | self.rx_valid = Signal(ratio) 76 | 77 | self.tx_symbol = Signal(ratio * 9) 78 | self.tx_set_disp = Signal(ratio) 79 | self.tx_disp = Signal(ratio) 80 | self.tx_e_idle = Signal(ratio) 81 | 82 | self.det_enable = Signal() 83 | self.det_valid = Signal() 84 | self.det_status = Signal() 85 | 86 | 87 | class PCIeSERDESAligner(PCIeSERDESInterface): 88 | """ 89 | A multiplexer that aligns commas to the first symbol of the word, for SERDESes that only 90 | perform bit alignment and not symbol alignment. 91 | """ 92 | def __init__(self, lane): 93 | self.ratio = lane.ratio 94 | 95 | self.rx_invert = lane.rx_invert 96 | self.rx_align = lane.rx_align 97 | self.rx_present = lane.rx_present 98 | self.rx_locked = lane.rx_locked 99 | self.rx_aligned = lane.rx_aligned 100 | 101 | self.rx_symbol = Signal(lane.ratio * 9) 102 | self.rx_valid = Signal(lane.ratio) 103 | 104 | self.tx_symbol = lane.tx_symbol 105 | self.tx_set_disp = lane.tx_set_disp 106 | self.tx_disp = lane.tx_disp 107 | self.tx_e_idle = lane.tx_e_idle 108 | 109 | self.det_enable = lane.det_enable 110 | self.det_valid = lane.det_valid 111 | self.det_status = lane.det_status 112 | 113 | ### 114 | 115 | self.submodules.slip = SymbolSlip(symbol_size=10, word_size=lane.ratio, 116 | comma=(1<<9)|K(28,5)) 117 | self.comb += [ 118 | self.slip.en.eq(self.rx_align), 119 | self.slip.i.eq(Cat( 120 | (lane.rx_symbol.part(9 * n, 9), lane.rx_valid[n]) 121 | for n in range(lane.ratio) 122 | )), 123 | self.rx_symbol.eq(Cat( 124 | self.slip.o.part(10 * n, 9) 125 | for n in range(lane.ratio) 126 | )), 127 | self.rx_valid.eq(Cat( 128 | self.slip.o[10 * n + 9] 129 | for n in range(lane.ratio) 130 | )), 131 | ] 132 | -------------------------------------------------------------------------------- /yumewatari/gateware/phy_rx.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | 3 | from .serdes import K, D 4 | from .protocol import * 5 | from .struct import * 6 | 7 | 8 | __all__ = ["PCIePHYRX"] 9 | 10 | 11 | class PCIePHYRX(Module): 12 | def __init__(self, lane): 13 | self.error = Signal() 14 | self.comma = Signal() 15 | self.ts = Record(ts_layout) 16 | 17 | ### 18 | 19 | self.comb += lane.rx_align.eq(1) 20 | 21 | self._tsY = Record(ts_layout) # previous TS received 22 | self._tsZ = Record(ts_layout) # TS being received 23 | self.sync += If(self.error, self._tsZ.valid.eq(0)) 24 | 25 | ts_id = Signal(9) 26 | ts_inv = Signal() 27 | 28 | self.submodules.parser = Parser( 29 | symbol_size=9, 30 | word_size=lane.ratio, 31 | reset_rule="COMMA", 32 | layout=[ 33 | ("data", 8), 34 | ("ctrl", 1), 35 | ]) 36 | self.comb += [ 37 | self.parser.reset.eq(~lane.rx_valid), 38 | self.parser.i.eq(lane.rx_symbol), 39 | self.error.eq(self.parser.error) 40 | ] 41 | self.parser.rule( 42 | name="COMMA", 43 | cond=lambda symbol: symbol.raw_bits() == K(28,5), 44 | succ="TSn-LINK/SKP-0", 45 | action=lambda symbol: [ 46 | self.comma.eq(1), 47 | NextValue(self._tsZ.valid, 1), 48 | NextValue(self._tsY.raw_bits(), self._tsZ.raw_bits()), 49 | ] 50 | ) 51 | self.parser.rule( 52 | name="TSn-LINK/SKP-0", 53 | cond=lambda symbol: symbol.raw_bits() == K(28,0), 54 | succ="SKP-1" 55 | ) 56 | self.parser.rule( 57 | name="TSn-LINK/SKP-0", 58 | cond=lambda symbol: symbol.raw_bits() == K(23,7), 59 | succ="TSn-LANE", 60 | action=lambda symbol: [ 61 | NextValue(self._tsZ.link.valid, 0) 62 | ] 63 | ) 64 | self.parser.rule( 65 | name="TSn-LINK/SKP-0", 66 | cond=lambda symbol: ~symbol.ctrl, 67 | succ="TSn-LANE", 68 | action=lambda symbol: [ 69 | NextValue(self._tsZ.link.number, symbol.data), 70 | NextValue(self._tsZ.link.valid, 1) 71 | ] 72 | ) 73 | for n in range(1, 3): 74 | self.parser.rule( 75 | name="SKP-%d" % n, 76 | cond=lambda symbol: symbol.raw_bits() == K(28,0), 77 | succ="COMMA" if n == 2 else "SKP-%d" % (n + 1), 78 | ) 79 | self.parser.rule( 80 | name="TSn-LANE", 81 | cond=lambda symbol: symbol.raw_bits() == K(23,7), 82 | succ="TSn-FTS", 83 | action=lambda symbol: [ 84 | NextValue(self._tsZ.lane.valid, 0) 85 | ] 86 | ) 87 | self.parser.rule( 88 | name="TSn-LANE", 89 | cond=lambda symbol: ~symbol.ctrl, 90 | succ="TSn-FTS", 91 | action=lambda symbol: [ 92 | NextValue(self._tsZ.lane.number, symbol.data), 93 | NextValue(self._tsZ.lane.valid, 1) 94 | ] 95 | ) 96 | self.parser.rule( 97 | name="TSn-FTS", 98 | cond=lambda symbol: ~symbol.ctrl, 99 | succ="TSn-RATE", 100 | action=lambda symbol: [ 101 | NextValue(self._tsZ.n_fts, symbol.data) 102 | ] 103 | ) 104 | self.parser.rule( 105 | name="TSn-RATE", 106 | cond=lambda symbol: ~symbol.ctrl, 107 | succ="TSn-CTRL", 108 | action=lambda symbol: [ 109 | NextValue(self._tsZ.rate.raw_bits(), symbol.data) 110 | ] 111 | ) 112 | self.parser.rule( 113 | name="TSn-CTRL", 114 | cond=lambda symbol: ~symbol.ctrl, 115 | succ="TSn-ID0", 116 | action=lambda symbol: [ 117 | NextValue(self._tsZ.ctrl.raw_bits(), symbol.data) 118 | ] 119 | ) 120 | self.parser.rule( 121 | name="TSn-ID0", 122 | cond=lambda symbol: symbol.raw_bits() == D(10,2), 123 | succ="TSn-ID1", 124 | action=lambda symbol: [ 125 | NextMemory(ts_id, symbol.raw_bits()), 126 | NextValue(ts_inv, 0), 127 | NextValue(self._tsZ.ts_id, 0), 128 | ] 129 | ) 130 | self.parser.rule( 131 | name="TSn-ID0", 132 | cond=lambda symbol: symbol.raw_bits() == D(5,2), 133 | succ="TSn-ID1", 134 | action=lambda symbol: [ 135 | NextMemory(ts_id, symbol.raw_bits()), 136 | NextValue(ts_inv, 0), 137 | NextValue(self._tsZ.ts_id, 1), 138 | ] 139 | ) 140 | self.parser.rule( 141 | name="TSn-ID0", 142 | cond=lambda symbol: symbol.raw_bits() == D(21,5), 143 | succ="TSn-ID1", 144 | action=lambda symbol: [ 145 | NextMemory(ts_id, symbol.raw_bits()), 146 | NextValue(ts_inv, 1), 147 | ] 148 | ) 149 | self.parser.rule( 150 | name="TSn-ID0", 151 | cond=lambda symbol: symbol.raw_bits() == D(26,5), 152 | succ="TSn-ID1", 153 | action=lambda symbol: [ 154 | NextMemory(ts_id, symbol.raw_bits()), 155 | NextValue(ts_inv, 1), 156 | ] 157 | ) 158 | for n in range(1, 9): 159 | self.parser.rule( 160 | name="TSn-ID%d" % n, 161 | cond=lambda symbol: symbol.raw_bits() == Memory(ts_id), 162 | succ="TSn-ID%d" % (n + 1) 163 | ) 164 | self.parser.rule( 165 | name="TSn-ID9", 166 | cond=lambda symbol: symbol.raw_bits() == Memory(ts_id), 167 | succ="COMMA", 168 | action=lambda symbol: [ 169 | NextValue(self.ts.valid, 0), 170 | If(ts_inv, 171 | NextValue(lane.rx_invert, ~lane.rx_invert) 172 | ).Elif(self._tsZ.raw_bits() == self._tsY.raw_bits(), 173 | NextValue(self.ts.raw_bits(), self._tsY.raw_bits()) 174 | ), 175 | NextState("COMMA") 176 | ] 177 | ) 178 | -------------------------------------------------------------------------------- /yumewatari/testbench/ltssm.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | from migen.build.generic_platform import * 3 | from migen.build.platforms.versaecp55g import Platform 4 | from migen.genlib.io import CRG 5 | from migen.genlib.cdc import MultiReg 6 | from microscope import * 7 | 8 | from ..gateware.platform.lattice_ecp5 import * 9 | from ..gateware.serdes import * 10 | from ..gateware.phy import * 11 | from ..vendor.pads import * 12 | from ..vendor.uart import * 13 | 14 | 15 | class LTSSMTestbench(Module): 16 | def __init__(self, **kwargs): 17 | self.platform = Platform(**kwargs) 18 | self.platform.add_extension([ 19 | ("tp0", 0, Pins("X3:5"), IOStandard("LVCMOS33")), 20 | ]) 21 | 22 | self.clock_domains.cd_serdes = ClockDomain() 23 | self.submodules.serdes = serdes = \ 24 | LatticeECP5PCIeSERDES(self.platform.request("pcie_x1")) 25 | self.comb += [ 26 | self.cd_serdes.clk.eq(serdes.rx_clk_o), 27 | serdes.rx_clk_i.eq(self.cd_serdes.clk), 28 | serdes.tx_clk_i.eq(self.cd_serdes.clk), 29 | ] 30 | 31 | with open("top.sdc", "w") as f: 32 | f.write("define_clock -name {n:serdes_ref_clk} -freq 100.000\n") 33 | f.write("define_clock -name {n:serdes_rx_clk_o} -freq 150.000\n") 34 | self.platform.add_source("top.sdc") 35 | # self.platform.add_platform_command("""FREQUENCY NET "serdes_ref_clk" 100 MHz;""") 36 | # self.platform.add_platform_command("""FREQUENCY NET "serdes_rx_clk_o" 125 MHz;""") 37 | 38 | self.submodules.aligner = aligner = \ 39 | ClockDomainsRenamer("rx")(PCIeSERDESAligner(serdes.lane)) 40 | self.submodules.phy = phy = \ 41 | ClockDomainsRenamer("rx")(PCIePHY(aligner, ms_cyc=125000)) 42 | 43 | led_att1 = self.platform.request("user_led") 44 | led_att2 = self.platform.request("user_led") 45 | led_sta1 = self.platform.request("user_led") 46 | led_sta2 = self.platform.request("user_led") 47 | led_err1 = self.platform.request("user_led") 48 | led_err2 = self.platform.request("user_led") 49 | led_err3 = self.platform.request("user_led") 50 | led_err4 = self.platform.request("user_led") 51 | self.comb += [ 52 | led_att1.eq(~(phy.rx.ts.link.valid)), 53 | led_att2.eq(~(phy.rx.ts.lane.valid)), 54 | led_sta1.eq(~(phy.rx.ts.valid)), 55 | led_sta2.eq(~(0)), 56 | led_err1.eq(~(~serdes.lane.rx_present)), 57 | led_err2.eq(~(~serdes.lane.rx_locked)), 58 | led_err3.eq(~(~serdes.lane.rx_aligned)), 59 | led_err4.eq(~(phy.rx.error)), 60 | ] 61 | 62 | tp0 = self.platform.request("tp0") 63 | self.comb += tp0.eq(phy.rx.ts.link.valid) 64 | 65 | uart_pads = Pads(self.platform.request("serial")) 66 | self.submodules += uart_pads 67 | self.submodules.uart = uart = ClockDomainsRenamer("rx")( 68 | UART(uart_pads, bit_cyc=uart_bit_cyc(125e6, 115200)[0]) 69 | ) 70 | 71 | self.comb += [ 72 | uart.rx_ack.eq(uart.rx_rdy), 73 | ] 74 | 75 | index = Signal(max=phy.ltssm_log.depth) 76 | offset = Signal(8) 77 | size = Signal(16) 78 | entry = Signal(phy.ltssm_log.width) 79 | self.comb += [ 80 | size.eq(phy.ltssm_log.width * phy.ltssm_log.depth // 8), 81 | entry.eq(Cat(phy.ltssm_log.data_o, phy.ltssm_log.time_o)), 82 | ] 83 | 84 | self.submodules.uart_fsm = ClockDomainsRenamer("rx")(FSM()) 85 | self.uart_fsm.act("WAIT", 86 | NextValue(uart.tx_ack, 0), 87 | If(uart.rx_rdy, 88 | NextValue(phy.ltssm_log.trigger, 1), 89 | NextValue(offset, 1), 90 | NextState("WIDTH") 91 | ) 92 | ) 93 | self.uart_fsm.act("WIDTH", 94 | NextValue(uart.tx_ack, 0), 95 | If(uart.tx_rdy & ~uart.tx_ack, 96 | NextValue(uart.tx_data, size.part(offset << 3, 8)), 97 | NextValue(uart.tx_ack, 1), 98 | If(offset == 0, 99 | NextValue(offset, phy.ltssm_log.width // 8 - 1), 100 | NextValue(index, phy.ltssm_log.depth - 1), 101 | NextState("DATA") 102 | ).Else( 103 | NextValue(offset, offset - 1) 104 | ) 105 | ) 106 | ) 107 | self.uart_fsm.act("DATA", 108 | NextValue(uart.tx_ack, 0), 109 | If(uart.tx_rdy & ~uart.tx_ack, 110 | NextValue(uart.tx_data, entry.part(offset << 3, 8)), 111 | NextValue(uart.tx_ack, 1), 112 | If(offset == 0, 113 | phy.ltssm_log.next.eq(1), 114 | NextValue(offset, phy.ltssm_log.width // 8 - 1), 115 | If(index == 0, 116 | NextValue(phy.ltssm_log.trigger, 0), 117 | NextState("WAIT") 118 | ).Else( 119 | NextValue(index, index - 1) 120 | ) 121 | ).Else( 122 | NextValue(offset, offset - 1) 123 | ) 124 | ) 125 | ) 126 | 127 | # ------------------------------------------------------------------------------------------------- 128 | 129 | import sys 130 | import serial 131 | import struct 132 | import subprocess 133 | 134 | 135 | if __name__ == "__main__": 136 | for arg in sys.argv[1:]: 137 | if arg == "build": 138 | toolchain = "diamond" 139 | if toolchain == "trellis": 140 | toolchain_path = "/usr/local/share/trellis" 141 | elif toolchain == "diamond": 142 | toolchain_path = "/usr/local/diamond/3.10_x64/bin/lin64" 143 | 144 | design = LTSSMTestbench(toolchain=toolchain) 145 | design.platform.build(design, toolchain_path=toolchain_path) 146 | 147 | if arg == "load": 148 | subprocess.call(["/home/whitequark/Projects/prjtrellis/tools/bit_to_svf.py", 149 | "build/top.bit", 150 | "build/top.svf"]) 151 | subprocess.call(["openocd", 152 | "-f", "/home/whitequark/Projects/" 153 | "prjtrellis/misc/openocd/ecp5-versa5g.cfg", 154 | "-c", "init; svf -quiet build/top.svf; exit"]) 155 | 156 | if arg == "sample": 157 | design = LTSSMTestbench() 158 | design.finalize() 159 | 160 | port = serial.Serial(port='/dev/ttyUSB1', baudrate=115200) 161 | port.write(b"\x00") 162 | length, = struct.unpack(">H", port.read(2)) 163 | data = port.read(length) 164 | 165 | offset = 0 166 | start = None 167 | while offset < len(data): 168 | time, state = struct.unpack_from(">LB", data, offset) 169 | offset += struct.calcsize(">LB") 170 | 171 | if start is not None: 172 | delta = time - start 173 | else: 174 | delta = 0 175 | 176 | print("%+10d cyc (%+10d us): %s" % 177 | (delta, delta / 125, design.phy.ltssm.decoding[state])) 178 | 179 | start = time 180 | 181 | -------------------------------------------------------------------------------- /yumewatari/testbench/serdes.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | from migen.build.generic_platform import * 3 | from migen.build.platforms.versaecp55g import Platform 4 | from migen.genlib.cdc import MultiReg 5 | from migen.genlib.fifo import AsyncFIFO 6 | from migen.genlib.fsm import FSM 7 | 8 | from ..gateware.serdes import * 9 | from ..gateware.phy import K, PCIePHYTX 10 | from ..gateware.align import * 11 | from ..gateware.platform.lattice_ecp5 import * 12 | from ..vendor.pads import * 13 | from ..vendor.uart import * 14 | 15 | 16 | class SERDESTestbench(Module): 17 | def __init__(self, capture_depth, **kwargs): 18 | self.platform = Platform(**kwargs) 19 | self.platform.add_extension([ 20 | ("tp0", 0, Pins("X3:5"), IOStandard("LVCMOS33")), 21 | ]) 22 | 23 | self.clock_domains.cd_ref = ClockDomain() 24 | self.clock_domains.cd_rx = ClockDomain() 25 | self.clock_domains.cd_tx = ClockDomain() 26 | 27 | self.submodules.serdes = serdes = \ 28 | LatticeECP5PCIeSERDES(self.platform.request("pcie_x1")) 29 | self.submodules.aligner = aligner = \ 30 | ClockDomainsRenamer("rx")(PCIeSERDESAligner(serdes.lane)) 31 | self.comb += [ 32 | self.cd_ref.clk.eq(serdes.ref_clk), 33 | serdes.rx_clk_i.eq(serdes.rx_clk_o), 34 | self.cd_rx.clk.eq(serdes.rx_clk_i), 35 | serdes.tx_clk_i.eq(serdes.tx_clk_o), 36 | self.cd_tx.clk.eq(serdes.tx_clk_i), 37 | ] 38 | 39 | self.submodules.tx_phy = ClockDomainsRenamer("tx")(PCIePHYTX(aligner)) 40 | self.comb += [ 41 | self.aligner.rx_align.eq(1), 42 | self.tx_phy.ts.n_fts.eq(0xff), 43 | self.tx_phy.ts.rate.gen1.eq(1), 44 | ] 45 | 46 | with open("top.sdc", "w") as f: 47 | f.write("define_clock -name {n:serdes_ref_clk} -freq 100.000\n") 48 | f.write("define_clock -name {n:serdes_tx_clk_o} -freq 150.000\n") 49 | f.write("define_clock -name {n:serdes_rx_clk_o} -freq 150.000\n") 50 | self.platform.add_source("top.sdc") 51 | self.platform.add_platform_command("""FREQUENCY NET "serdes_ref_clk" 100 MHz;""") 52 | self.platform.add_platform_command("""FREQUENCY NET "serdes_rx_clk_o" 125 MHz;""") 53 | self.platform.add_platform_command("""FREQUENCY NET "serdes_tx_clk_o" 125 MHz;""") 54 | 55 | refclkcounter = Signal(32) 56 | self.sync.ref += refclkcounter.eq(refclkcounter + 1) 57 | rxclkcounter = Signal(32) 58 | self.sync.rx += rxclkcounter.eq(rxclkcounter + 1) 59 | txclkcounter = Signal(32) 60 | self.sync.tx += txclkcounter.eq(txclkcounter + 1) 61 | 62 | led_att1 = self.platform.request("user_led") 63 | led_att2 = self.platform.request("user_led") 64 | led_sta1 = self.platform.request("user_led") 65 | led_sta2 = self.platform.request("user_led") 66 | led_err1 = self.platform.request("user_led") 67 | led_err2 = self.platform.request("user_led") 68 | led_err3 = self.platform.request("user_led") 69 | led_err4 = self.platform.request("user_led") 70 | self.comb += [ 71 | led_att1.eq(~(refclkcounter[25])), 72 | led_att2.eq(~(0)), 73 | led_sta1.eq(~(rxclkcounter[25])), 74 | led_sta2.eq(~(txclkcounter[25])), 75 | led_err1.eq(~(~serdes.lane.rx_present)), 76 | led_err2.eq(~(~serdes.lane.rx_locked)), 77 | led_err3.eq(~(~serdes.lane.rx_aligned)), 78 | led_err4.eq(~(0)), 79 | ] 80 | 81 | trigger_rx = Signal() 82 | trigger_ref = Signal() 83 | self.specials += MultiReg(trigger_ref, trigger_rx, odomain="rx") 84 | 85 | capture = Signal() 86 | self.submodules.symbols = symbols = ClockDomainsRenamer({ 87 | "write": "rx", "read": "ref" 88 | })( 89 | AsyncFIFO(width=18, depth=capture_depth) 90 | ) 91 | self.comb += [ 92 | symbols.din.eq(Cat(aligner.rx_symbol)), 93 | symbols.we.eq(capture) 94 | ] 95 | self.sync.rx += [ 96 | If(trigger_rx, 97 | capture.eq(1) 98 | ).Elif(~symbols.writable, 99 | capture.eq(0) 100 | ) 101 | ] 102 | 103 | uart_pads = Pads(self.platform.request("serial")) 104 | self.submodules += uart_pads 105 | self.submodules.uart = uart = ClockDomainsRenamer("ref")( 106 | UART(uart_pads, bit_cyc=uart_bit_cyc(100e6, 115200)[0]) 107 | ) 108 | 109 | self.comb += [ 110 | uart.rx_ack.eq(uart.rx_rdy), 111 | trigger_ref.eq(uart.rx_rdy) 112 | ] 113 | 114 | self.submodules.fsm = ClockDomainsRenamer("ref")(FSM(reset_state="WAIT")) 115 | self.fsm.act("WAIT", 116 | If(uart.rx_rdy, 117 | NextState("SYNC-1") 118 | ) 119 | ) 120 | self.fsm.act("SYNC-1", 121 | If(uart.tx_rdy, 122 | uart.tx_ack.eq(1), 123 | uart.tx_data.eq(0xff), 124 | NextState("SYNC-2") 125 | ) 126 | ) 127 | self.fsm.act("SYNC-2", 128 | If(uart.tx_rdy, 129 | uart.tx_ack.eq(1), 130 | uart.tx_data.eq(0xff), 131 | NextState("BYTE-0") 132 | ) 133 | ) 134 | self.fsm.act("BYTE-0", 135 | If(symbols.readable & uart.tx_rdy, 136 | uart.tx_ack.eq(1), 137 | uart.tx_data.eq(symbols.dout[16:]), 138 | NextState("BYTE-1") 139 | ).Elif(~symbols.readable, 140 | NextState("WAIT") 141 | ) 142 | ) 143 | self.fsm.act("BYTE-1", 144 | If(symbols.readable & uart.tx_rdy, 145 | uart.tx_ack.eq(1), 146 | uart.tx_data.eq(symbols.dout[8:]), 147 | NextState("BYTE-2") 148 | ) 149 | ) 150 | self.fsm.act("BYTE-2", 151 | If(symbols.readable & uart.tx_rdy, 152 | uart.tx_ack.eq(1), 153 | uart.tx_data.eq(symbols.dout[0:]), 154 | symbols.re.eq(1), 155 | NextState("BYTE-0") 156 | ) 157 | ) 158 | 159 | tp0 = self.platform.request("tp0") 160 | # self.comb += tp0.eq(serdes.rx_clk_o) 161 | 162 | # ------------------------------------------------------------------------------------------------- 163 | 164 | import sys 165 | import serial 166 | import subprocess 167 | 168 | 169 | CAPTURE_DEPTH = 1024 170 | 171 | 172 | if __name__ == "__main__": 173 | for arg in sys.argv[1:]: 174 | if arg == "build": 175 | toolchain = "diamond" 176 | if toolchain == "trellis": 177 | toolchain_path = "/usr/local/share/trellis" 178 | elif toolchain == "diamond": 179 | toolchain_path = "/usr/local/diamond/3.10_x64/bin/lin64" 180 | 181 | design = SERDESTestbench(CAPTURE_DEPTH, toolchain=toolchain) 182 | design.platform.build(design, toolchain_path=toolchain_path) 183 | 184 | if arg == "load": 185 | subprocess.call(["/home/whitequark/Projects/prjtrellis/tools/bit_to_svf.py", 186 | "build/top.bit", 187 | "build/top.svf"]) 188 | subprocess.call(["openocd", 189 | "-f", "/home/whitequark/Projects/" 190 | "prjtrellis/misc/openocd/ecp5-versa5g.cfg", 191 | "-c", "init; svf -quiet build/top.svf; exit"]) 192 | 193 | if arg == "sample": 194 | port = serial.Serial(port='/dev/ttyUSB1', baudrate=115200) 195 | port.write(b"\x00") 196 | 197 | while True: 198 | while True: 199 | if port.read(1) == b"\xff": break 200 | if port.read(1) == b"\xff": break 201 | 202 | for x in range(CAPTURE_DEPTH): 203 | b2, b1, b0 = port.read(3) 204 | dword = (b2 << 16) | (b1 << 8) | b0 205 | for word in (((dword >> 0) & 0x1ff), ((dword >> 9) & 0x1ff)): 206 | if word & 0x1ff == 0x1ee: 207 | print("KEEEEEEEE", end=" ") 208 | else: 209 | print("{}{:08b}".format( 210 | "K" if word & (1 << 8) else " ", 211 | word & 0xff, 212 | ), end=" ") 213 | if x % 4 == 3: 214 | print() 215 | -------------------------------------------------------------------------------- /yumewatari/vendor/uart.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | from migen.genlib.fsm import * 3 | from migen.genlib.cdc import MultiReg 4 | 5 | 6 | __all__ = ['UART', 'uart_bit_cyc'] 7 | 8 | 9 | class UARTBus(Module): 10 | """ 11 | UART bus. 12 | 13 | Provides synchronization. 14 | """ 15 | def __init__(self, pads): 16 | self.has_rx = hasattr(pads, "rx_t") 17 | if self.has_rx: 18 | self.rx_t = pads.rx_t 19 | self.rx_i = Signal() 20 | 21 | self.has_tx = hasattr(pads, "tx_t") 22 | if self.has_tx: 23 | self.tx_t = pads.tx_t 24 | self.tx_o = Signal(reset=1) 25 | 26 | ### 27 | 28 | if self.has_tx: 29 | self.comb += [ 30 | self.tx_t.oe.eq(1), 31 | self.tx_t.o.eq(self.tx_o) 32 | ] 33 | 34 | if self.has_rx: 35 | self.specials += [ 36 | MultiReg(self.rx_t.i, self.rx_i, reset=1) 37 | ] 38 | 39 | 40 | def uart_bit_cyc(clk_freq, baud_rate, max_deviation=50000): 41 | """ 42 | Calculate bit time from clock frequency and baud rate. 43 | 44 | :param clk_freq: 45 | Input clock frequency, in Hz. 46 | :type clk_freq: int or float 47 | :param baud_rate: 48 | Baud rate, in bits per second. 49 | :type baud_rate: int or float 50 | :param max_deviation: 51 | Maximum deviation of actual baud rate from ``baud_rate``, in parts per million. 52 | :type max_deviation: int or float 53 | 54 | :returns: (int, int or float) -- bit time as a multiple of clock period, and actual baud rate 55 | as calculated based on bit time. 56 | :raises: ValueError -- if the baud rate is too high for the specified clock frequency, 57 | or if actual baud rate deviates from requested baud rate by more than a specified amount. 58 | """ 59 | 60 | bit_cyc = round(clk_freq // baud_rate) 61 | if bit_cyc <= 0: 62 | raise ValueError("baud rate {} is too high for input clock frequency {}" 63 | .format(baud_rate, clk_freq)) 64 | 65 | actual_baud_rate = round(clk_freq // bit_cyc) 66 | deviation = round(1000000 * (actual_baud_rate - baud_rate) // baud_rate) 67 | if deviation > max_deviation: 68 | raise ValueError("baud rate {} deviation from {} ({} ppm) is higher than {} ppm" 69 | .format(actual_baud_rate, baud_rate, deviation, max_deviation)) 70 | 71 | return bit_cyc + 1, actual_baud_rate 72 | 73 | 74 | class UART(Module): 75 | """ 76 | Asynchronous serial receiver-transmitter. 77 | 78 | Any number of data bits, any parity, and 1 stop bit are supported. 79 | 80 | :param bit_cyc: 81 | Bit time expressed as a multiple of system clock periods. Use :func:`uart_bit_cyc` 82 | to calculate bit time from system clock frequency and baud rate. 83 | :type bit_cyc: int 84 | :param data_bits: 85 | Data bit count. 86 | :type data_bits: int 87 | :param parity: 88 | Parity, one of ``"none"`` (default), ``"zero"``, ``"one"``, ``"even"``, ``"odd"``. 89 | :type parity: str 90 | 91 | :attr rx_data: 92 | Received data. Valid when ``rx_rdy`` is active. 93 | :attr rx_rdy: 94 | Receive ready flag. Becomes active after a stop bit of a valid frame is received. 95 | :attr rx_ack: 96 | Receive acknowledgement. If active when ``rx_rdy`` is active, ``rx_rdy`` is reset, 97 | and the receive state machine becomes ready for another frame. 98 | :attr rx_ferr: 99 | Receive frame error flag. Active for one cycle when a frame error is detected. 100 | :attr rx_ovf: 101 | Receive overflow flag. Active for one cycle when a new frame is started while ``rx_rdy`` 102 | is still active. Afterwards, the receive state machine is reset and starts receiving 103 | the new frame. 104 | :attr rx_err: 105 | Receive error flag. Logical OR of all other error flags. 106 | 107 | :attr tx_data: 108 | Data to transmit. Sampled when ``tx_rdy`` is active. 109 | :attr tx_rdy: 110 | Transmit ready flag. Active while the transmit state machine is idle, and can accept 111 | data to transmit. 112 | :attr tx_ack: 113 | Transmit acknowledgement. If active when ``tx_rdy`` is active, ``tx_rdy`` is reset, 114 | ``tx_data`` is sampled, and the transmit state machine starts transmitting a frame. 115 | """ 116 | def __init__(self, pads, bit_cyc, data_bits=8, parity="none"): 117 | self.rx_data = Signal(data_bits) 118 | self.rx_rdy = Signal() 119 | self.rx_ack = Signal() 120 | self.rx_ferr = Signal() 121 | self.rx_ovf = Signal() 122 | self.rx_perr = Signal() 123 | self.rx_err = Signal() 124 | 125 | self.tx_data = Signal(data_bits) 126 | self.tx_rdy = Signal() 127 | self.tx_ack = Signal() 128 | 129 | self.submodules.bus = bus = UARTBus(pads) 130 | 131 | ### 132 | 133 | bit_cyc = int(bit_cyc) 134 | 135 | def calc_parity(sig, kind): 136 | if kind in ("zero", "none"): 137 | return C(0, 1) 138 | elif kind == "one": 139 | return C(1, 1) 140 | else: 141 | bits, _ = value_bits_sign(sig) 142 | even_parity = sum([sig[b] for b in range(bits)]) & 1 143 | if kind == "odd": 144 | return ~even_parity 145 | elif kind == "even": 146 | return even_parity 147 | else: 148 | assert False 149 | 150 | if bus.has_rx: 151 | rx_timer = Signal(max=bit_cyc) 152 | rx_stb = Signal() 153 | rx_shreg = Signal(data_bits) 154 | rx_bitno = Signal(max=rx_shreg.nbits) 155 | 156 | self.comb += self.rx_err.eq(self.rx_ferr | self.rx_ovf | self.rx_perr) 157 | 158 | self.sync += [ 159 | If(rx_timer == 0, 160 | rx_timer.eq(bit_cyc - 1) 161 | ).Else( 162 | rx_timer.eq(rx_timer - 1) 163 | ) 164 | ] 165 | self.comb += rx_stb.eq(rx_timer == 0) 166 | 167 | self.submodules.rx_fsm = FSM(reset_state="IDLE") 168 | self.rx_fsm.act("IDLE", 169 | NextValue(self.rx_rdy, 0), 170 | If(~bus.rx_i, 171 | NextValue(rx_timer, bit_cyc // 2), 172 | NextState("START") 173 | ) 174 | ) 175 | self.rx_fsm.act("START", 176 | If(rx_stb, 177 | NextState("DATA") 178 | ) 179 | ) 180 | self.rx_fsm.act("DATA", 181 | If(rx_stb, 182 | NextValue(rx_shreg, Cat(rx_shreg[1:8], bus.rx_i)), 183 | NextValue(rx_bitno, rx_bitno + 1), 184 | If(rx_bitno == rx_shreg.nbits - 1, 185 | If(parity == "none", 186 | NextState("STOP") 187 | ).Else( 188 | NextState("PARITY") 189 | ) 190 | ) 191 | ) 192 | ) 193 | self.rx_fsm.act("PARITY", 194 | If(rx_stb, 195 | If(bus.rx_i == calc_parity(rx_shreg, parity), 196 | NextState("STOP") 197 | ).Else( 198 | self.rx_perr.eq(1), 199 | NextState("IDLE") 200 | ) 201 | ) 202 | ) 203 | self.rx_fsm.act("STOP", 204 | If(rx_stb, 205 | If(~bus.rx_i, 206 | self.rx_ferr.eq(1), 207 | NextState("IDLE") 208 | ).Else( 209 | NextValue(self.rx_data, rx_shreg), 210 | NextState("READY") 211 | ) 212 | ) 213 | ) 214 | self.rx_fsm.act("READY", 215 | NextValue(self.rx_rdy, 1), 216 | If(self.rx_ack, 217 | NextState("IDLE") 218 | ).Elif(~bus.rx_i, 219 | self.rx_ovf.eq(1), 220 | NextState("IDLE") 221 | ) 222 | ) 223 | 224 | ### 225 | 226 | if bus.has_tx: 227 | tx_timer = Signal(max=bit_cyc) 228 | tx_stb = Signal() 229 | tx_shreg = Signal(data_bits) 230 | tx_bitno = Signal(max=tx_shreg.nbits) 231 | tx_parity = Signal() 232 | 233 | self.sync += [ 234 | If(tx_timer == 0, 235 | tx_timer.eq(bit_cyc - 1) 236 | ).Else( 237 | tx_timer.eq(tx_timer - 1) 238 | ) 239 | ] 240 | self.comb += tx_stb.eq(tx_timer == 0) 241 | 242 | self.submodules.tx_fsm = FSM(reset_state="IDLE") 243 | self.tx_fsm.act("IDLE", 244 | self.tx_rdy.eq(1), 245 | If(self.tx_ack, 246 | NextValue(tx_shreg, self.tx_data), 247 | If(parity != "none", 248 | NextValue(tx_parity, calc_parity(self.tx_data, parity)) 249 | ), 250 | NextValue(tx_timer, bit_cyc - 1), 251 | NextValue(bus.tx_o, 0), 252 | NextState("START") 253 | ).Else( 254 | NextValue(bus.tx_o, 1) 255 | ) 256 | ) 257 | self.tx_fsm.act("START", 258 | If(tx_stb, 259 | NextValue(bus.tx_o, tx_shreg[0]), 260 | NextValue(tx_shreg, Cat(tx_shreg[1:8], 0)), 261 | NextState("DATA") 262 | ) 263 | ) 264 | self.tx_fsm.act("DATA", 265 | If(tx_stb, 266 | NextValue(tx_bitno, tx_bitno + 1), 267 | If(tx_bitno != tx_shreg.nbits - 1, 268 | NextValue(bus.tx_o, tx_shreg[0]), 269 | NextValue(tx_shreg, Cat(tx_shreg[1:8], 0)), 270 | ).Else( 271 | If(parity == "none", 272 | NextValue(bus.tx_o, 1), 273 | NextState("STOP") 274 | ).Else( 275 | NextValue(bus.tx_o, tx_parity), 276 | NextState("PARITY") 277 | ) 278 | ) 279 | ) 280 | ) 281 | self.tx_fsm.act("PARITY", 282 | If(tx_stb, 283 | NextValue(bus.tx_o, 1), 284 | NextState("STOP") 285 | ) 286 | ) 287 | self.tx_fsm.act("STOP", 288 | If(tx_stb, 289 | NextState("IDLE") 290 | ) 291 | ) 292 | -------------------------------------------------------------------------------- /yumewatari/gateware/phy.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | from migen.genlib.fsm import * 3 | 4 | from .protocol import * 5 | from .phy_rx import * 6 | from .phy_tx import * 7 | from .debug import RingLog 8 | 9 | 10 | __all__ = ["PCIePHY"] 11 | 12 | 13 | class PCIePHY(Module): 14 | def __init__(self, lane, ms_cyc): 15 | self.submodules.rx = rx = PCIePHYRX(lane) 16 | self.submodules.tx = tx = PCIePHYTX(lane) 17 | 18 | self.link_up = Signal() 19 | 20 | self.submodules.ltssm_log = RingLog(timestamp_width=32, data_width=8, depth=16) 21 | 22 | ### 23 | 24 | self.comb += [ 25 | tx.ts.rate.gen1.eq(1), 26 | ] 27 | 28 | rx_timer = Signal(max=64 * ms_cyc + 1) 29 | rx_ts_count = Signal(max=16 + 1) 30 | tx_ts_count = Signal(max=1024 + 1) 31 | 32 | # LTSSM implemented according to PCIe Base Specification Revision 2.1. 33 | # The Specification must be read side to side with this code in order to understand it. 34 | # Unfortunately, the Specification is copyrighted and probably cannot be quoted here 35 | # directly at length. 36 | self.submodules.ltssm = ltssm = ResetInserter()(FSM()) 37 | self.ltssm.act("Detect.Quiet", 38 | NextValue(tx.e_idle, 1), 39 | NextValue(self.link_up, 0), 40 | NextValue(rx_timer, 12 * ms_cyc), 41 | NextState("Detect.Quiet:Timeout") 42 | ) 43 | self.ltssm.act("Detect.Quiet:Timeout", 44 | NextValue(rx_timer, rx_timer - 1), 45 | If(lane.rx_present | (rx_timer == 0), 46 | NextValue(lane.det_enable, 1), 47 | NextState("Detect.Active") 48 | ) 49 | ) 50 | self.ltssm.act("Detect.Active", 51 | If(lane.det_valid, 52 | NextValue(lane.det_enable, 0), 53 | If(lane.det_status, 54 | NextState("Polling.Active") 55 | ).Else( 56 | NextState("Detect.Quiet") 57 | ) 58 | ) 59 | ) 60 | self.ltssm.act("Polling.Active", 61 | NextValue(tx.e_idle, 0), 62 | # Transmit TS1 Link=PAD Lane=PAD 63 | NextValue(tx.ts.valid, 1), 64 | NextValue(tx.ts.ts_id, 0), 65 | NextValue(tx.ts.link.valid, 0), 66 | NextValue(tx.ts.lane.valid, 0), 67 | NextValue(rx_timer, 24 * ms_cyc), 68 | NextValue(rx_ts_count, 0), 69 | NextValue(tx_ts_count, 0), 70 | NextState("Polling.Active:TS") 71 | ) 72 | self.ltssm.act("Polling.Active:TS", 73 | If(tx.comma, 74 | If(tx_ts_count != 1024, 75 | NextValue(tx_ts_count, tx_ts_count + 1) 76 | ) 77 | ), 78 | If((tx_ts_count == 1024), 79 | If(rx.comma, 80 | # Accept TS1 Link=PAD Lane=PAD Compliance=0 81 | # Accept TS1 Link=PAD Lane=PAD Loopback=1 82 | # Accept TS2 Link=PAD Lane=PAD 83 | If(rx.ts.valid & ~rx.ts.lane.valid & ~rx.ts.link.valid & 84 | (((rx.ts.ts_id == 0) & ~rx.ts.ctrl.compliance_receive) | 85 | ((rx.ts.ts_id == 0) & rx.ts.ctrl.loopback) | 86 | (rx.ts.ts_id == 1)), 87 | NextValue(rx_ts_count, rx_ts_count + 1), 88 | If(rx_ts_count == 8, 89 | NextState("Polling.Configuration") 90 | ) 91 | ).Else( 92 | NextValue(rx_ts_count, 0) 93 | ) 94 | ) 95 | ), 96 | NextValue(rx_timer, rx_timer - 1), 97 | If(rx_timer == 0, 98 | NextState("Detect.Quiet") 99 | ) 100 | ) 101 | self.ltssm.act("Polling.Configuration", 102 | # Transmit TS2 Link=PAD Lane=PAD 103 | NextValue(tx.ts.valid, 1), 104 | NextValue(tx.ts.ts_id, 1), 105 | NextValue(tx.ts.link.valid, 0), 106 | NextValue(tx.ts.lane.valid, 0), 107 | NextValue(rx_ts_count, 0), 108 | NextValue(tx_ts_count, 0), 109 | NextValue(rx_timer, 48 * ms_cyc), 110 | NextState("Polling.Configuration:TS") 111 | ) 112 | self.ltssm.act("Polling.Configuration:TS", 113 | If(tx.comma, 114 | If(rx_ts_count == 0, 115 | NextValue(tx_ts_count, 0) 116 | ).Else( 117 | NextValue(tx_ts_count, tx_ts_count + 1) 118 | ) 119 | ), 120 | If(rx.comma, 121 | # Accept TS2 Link=PAD Lane=PAD 122 | If(rx.ts.valid & (rx.ts.ts_id == 1) & ~rx.ts.link.valid & ~rx.ts.lane.valid, 123 | If(rx_ts_count == 8, 124 | If(tx_ts_count == 16, 125 | NextValue(rx_timer, 24 * ms_cyc), 126 | NextState("Configuration.Linkwidth.Start") 127 | ) 128 | ).Else( 129 | NextValue(rx_ts_count, rx_ts_count + 1) 130 | ) 131 | ).Else( 132 | NextValue(rx_ts_count, 0) 133 | ), 134 | ), 135 | NextValue(rx_timer, rx_timer - 1), 136 | If(rx_timer == 0, 137 | NextState("Detect.Quiet") 138 | ) 139 | ) 140 | self.ltssm.act("Configuration.Linkwidth.Start", 141 | # Transmit TS1 Link=PAD Lane=PAD 142 | NextValue(tx.ts.valid, 1), 143 | NextValue(tx.ts.ts_id, 0), 144 | NextValue(tx.ts.link.valid, 0), 145 | NextValue(tx.ts.lane.valid, 0), 146 | # Accept TS1 Link=Upstream-Link Lane=PAD 147 | If(rx.ts.valid & (rx.ts.ts_id == 0) & rx.ts.link.valid & ~rx.ts.lane.valid, 148 | # Transmit TS1 Link=Upstream-Link Lane=PAD 149 | NextValue(tx.ts.link.valid, 1), 150 | NextValue(tx.ts.link.number, rx.ts.link.number), 151 | NextValue(rx_timer, 2 * ms_cyc), 152 | NextState("Configuration.Linkwidth.Accept") 153 | ), 154 | NextValue(rx_timer, rx_timer - 1), 155 | If(rx_timer == 0, 156 | NextState("Detect.Quiet") 157 | ) 158 | ) 159 | self.ltssm.act("Configuration.Linkwidth.Accept", 160 | # Accept TS1 Link=Upstream-Link Lane=Upstream-Lane 161 | If(rx.ts.valid & (rx.ts.ts_id == 0) & rx.ts.link.valid & rx.ts.lane.valid, 162 | # Accept Upstream-Lane=0 163 | If(rx.ts.lane.number == 0, 164 | # Transmit TS1 Link=Upstream-Link Lane=Upstream-Lane 165 | NextValue(tx.ts.lane.valid, 1), 166 | NextValue(tx.ts.lane.number, rx.ts.lane.number), 167 | NextValue(rx_timer, 2 * ms_cyc), 168 | NextState("Configuration.Lanenum.Wait") 169 | ) 170 | ), 171 | # Accept TS1 Link=PAD Lane=PAD 172 | If(rx.ts.valid & (rx.ts.ts_id == 0) & ~rx.ts.link.valid & ~rx.ts.lane.valid, 173 | NextState("Detect.Quiet") 174 | ), 175 | NextValue(rx_timer, rx_timer - 1), 176 | If(rx_timer == 0, 177 | NextState("Detect.Quiet") 178 | ) 179 | ) 180 | self.ltssm.act("Configuration.Lanenum.Wait", 181 | # Accept TS1 Link=Upstream-Link Lane=Upstream-Lane 182 | If(rx.ts.valid & (rx.ts.ts_id == 0) & rx.ts.link.valid & rx.ts.lane.valid, 183 | If(rx.ts.lane.number != tx.ts.lane.number, 184 | NextState("Configuration.Lanenum.Accept") 185 | ) 186 | ), 187 | # Accept TS2 188 | If(rx.ts.valid & (rx.ts.ts_id == 1), 189 | NextState("Configuration.Lanenum.Accept") 190 | ), 191 | # Accept TS1 Link=PAD Lane=PAD 192 | If(rx.ts.valid & (rx.ts.ts_id == 0) & ~rx.ts.link.valid & ~rx.ts.lane.valid, 193 | NextState("Detect.Quiet") 194 | ), 195 | NextValue(rx_timer, rx_timer - 1), 196 | If(rx_timer == 0, 197 | NextState("Detect.Quiet") 198 | ) 199 | ) 200 | self.ltssm.act("Configuration.Lanenum.Accept", 201 | # Accept TS2 Link=Upstream-Link Lane=Upstream-Lane 202 | If(rx.ts.valid & (rx.ts.ts_id == 1) & rx.ts.link.valid & rx.ts.lane.valid, 203 | If((rx.ts.link.number == tx.ts.link.number) & 204 | (rx.ts.lane.number == tx.ts.lane.number), 205 | NextState("Configuration.Complete") 206 | ).Else( 207 | NextState("Detect.Quiet") 208 | ) 209 | ), 210 | # Accept TS1 Link=PAD Lane=PAD 211 | If(rx.ts.valid & (rx.ts.ts_id == 0) & ~rx.ts.link.valid & ~rx.ts.lane.valid, 212 | NextState("Detect.Quiet") 213 | ), 214 | ) 215 | self.ltssm.act("Configuration.Complete", 216 | # Transmit TS2 Link=Upstream-Link Lane=Upstream-Lane 217 | NextValue(tx.ts.ts_id, 1), 218 | NextValue(tx.ts.n_fts, 0xff), 219 | NextValue(rx_ts_count, 0), 220 | NextValue(tx_ts_count, 0), 221 | NextValue(rx_timer, 2 * ms_cyc), 222 | NextState("Configuration.Complete:TS") 223 | ) 224 | self.ltssm.act("Configuration.Complete:TS", 225 | If(tx.comma, 226 | If(rx_ts_count == 0, 227 | NextValue(tx_ts_count, 0) 228 | ).Else( 229 | NextValue(tx_ts_count, tx_ts_count + 1) 230 | ) 231 | ), 232 | If(rx.comma, 233 | # Accept TS2 Link=Upstream-Link Lane=Upstream-Lane 234 | If(rx.ts.valid & (rx.ts.ts_id == 1) & rx.ts.link.valid & rx.ts.lane.valid & 235 | (rx.ts.link.number == tx.ts.link.number) & 236 | (rx.ts.lane.number == tx.ts.lane.number), 237 | If(rx_ts_count == 8, 238 | If(tx_ts_count == 16, 239 | NextState("Configuration.Idle") 240 | ) 241 | ).Else( 242 | NextValue(rx_ts_count, rx_ts_count + 1) 243 | ) 244 | ).Else( 245 | NextValue(rx_ts_count, 0) 246 | ), 247 | ), 248 | NextValue(rx_timer, rx_timer - 1), 249 | If(rx_timer == 0, 250 | NextState("Detect.Quiet") 251 | ) 252 | ) 253 | self.ltssm.act("Configuration.Idle", 254 | NextValue(self.link_up, 1) 255 | ) 256 | 257 | def do_finalize(self): 258 | self.comb += self.ltssm_log.data_i.eq(self.ltssm.state) 259 | -------------------------------------------------------------------------------- /yumewatari/test/test_phy_rx.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from migen import * 3 | 4 | from ..gateware.serdes import * 5 | from ..gateware.serdes import K, D 6 | from ..gateware.phy_rx import * 7 | from . import simulation_test 8 | 9 | 10 | class PCIePHYRXTestbench(Module): 11 | def __init__(self, ratio=1): 12 | self.submodules.lane = PCIeSERDESInterface(ratio) 13 | self.submodules.phy = PCIePHYRX(self.lane) 14 | 15 | def do_finalize(self): 16 | self.states = {v: k for k, v in self.phy.parser.fsm.encoding.items()} 17 | 18 | def phy_state(self): 19 | return self.states[(yield self.phy.parser.fsm.state)] 20 | 21 | def transmit(self, symbols): 22 | for i, word in enumerate(symbols): 23 | if i > 0: 24 | assert (yield self.phy.error) == 0 25 | if isinstance(word, tuple): 26 | for j, symbol in enumerate(word): 27 | yield self.lane.rx_symbol.part(j * 9, 9).eq(symbol) 28 | else: 29 | yield self.lane.rx_symbol.eq(word) 30 | yield 31 | 32 | 33 | class _PCIePHYRXTestCase(unittest.TestCase): 34 | def assertState(self, tb, state): 35 | self.assertEqual((yield from tb.phy_state()), state) 36 | 37 | def assertSignal(self, signal, value): 38 | self.assertEqual((yield signal), value) 39 | 40 | 41 | class PCIePHYRXGear1xTestCase(_PCIePHYRXTestCase): 42 | def setUp(self): 43 | self.tb = PCIePHYRXTestbench() 44 | 45 | def simulationSetUp(self, tb): 46 | yield tb.lane.rx_valid.eq(1) 47 | 48 | @simulation_test 49 | def test_rx_tsn_cycle_by_cycle(self, tb): 50 | yield tb.lane.rx_symbol.eq(K(28,5)) 51 | yield 52 | yield from self.assertState(tb, "COMMA") 53 | yield tb.lane.rx_symbol.eq(D(1,0)) 54 | yield 55 | yield from self.assertState(tb, "TSn-LINK/SKP-0") 56 | yield tb.lane.rx_symbol.eq(D(2,0)) 57 | yield 58 | yield from self.assertSignal(tb.phy._tsZ.link.valid, 1) 59 | yield from self.assertSignal(tb.phy._tsZ.link.number, 1) 60 | yield from self.assertState(tb, "TSn-LANE") 61 | yield tb.lane.rx_symbol.eq(0xff) 62 | yield 63 | yield from self.assertSignal(tb.phy._tsZ.lane.valid, 1) 64 | yield from self.assertSignal(tb.phy._tsZ.lane.number, 2) 65 | yield from self.assertState(tb, "TSn-FTS") 66 | yield tb.lane.rx_symbol.eq(0b0010) 67 | yield 68 | yield from self.assertSignal(tb.phy._tsZ.n_fts, 0xff) 69 | yield from self.assertState(tb, "TSn-RATE") 70 | yield tb.lane.rx_symbol.eq(0b1111) 71 | yield 72 | yield from self.assertSignal(tb.phy._tsZ.rate.gen1, 1) 73 | yield from self.assertState(tb, "TSn-CTRL") 74 | yield tb.lane.rx_symbol.eq(D(5,2)) 75 | yield 76 | yield from self.assertSignal(tb.phy._tsZ.ctrl.hot_reset, 1) 77 | yield from self.assertSignal(tb.phy._tsZ.ctrl.disable_link, 1) 78 | yield from self.assertSignal(tb.phy._tsZ.ctrl.loopback, 1) 79 | yield from self.assertSignal(tb.phy._tsZ.ctrl.disable_scrambling, 1) 80 | yield from self.assertState(tb, "TSn-ID0") 81 | yield tb.lane.rx_symbol.eq(D(5,2)) 82 | yield 83 | yield from self.assertSignal(tb.phy._tsZ.ts_id, 1) 84 | yield from self.assertState(tb, "TSn-ID1") 85 | for n in range(2, 10): 86 | yield tb.lane.rx_symbol.eq(D(5,2)) 87 | yield 88 | yield from self.assertState(tb, "TSn-ID%d" % n) 89 | yield tb.lane.rx_symbol.eq(K(28,5)) 90 | yield 91 | yield from self.assertSignal(tb.phy._tsZ.valid, 1) 92 | yield from self.assertState(tb, "COMMA") 93 | 94 | def assertTSnState(self, tsN, valid=1, link_valid=0, link_number=0, 95 | lane_valid=0, lane_number=0, n_fts=0, rate_gen1=0, rate_gen2=0, 96 | ctrl_hot_reset=0, ctrl_disable_link=0, ctrl_loopback=0, 97 | ctrl_disable_scrambling=0, 98 | ts_id=0): 99 | yield from self.assertSignal(tsN.valid, valid) 100 | yield from self.assertSignal(tsN.link.valid, link_valid) 101 | yield from self.assertSignal(tsN.lane.valid, lane_valid) 102 | yield from self.assertSignal(tsN.n_fts, n_fts) 103 | yield from self.assertSignal(tsN.rate.gen1, rate_gen1) 104 | yield from self.assertSignal(tsN.rate.gen2, rate_gen2) 105 | yield from self.assertSignal(tsN.ctrl.hot_reset, ctrl_hot_reset) 106 | yield from self.assertSignal(tsN.ctrl.disable_link, ctrl_disable_link) 107 | yield from self.assertSignal(tsN.ctrl.loopback, ctrl_loopback) 108 | yield from self.assertSignal(tsN.ctrl.disable_scrambling, ctrl_disable_scrambling) 109 | yield from self.assertSignal(tsN.ts_id, ts_id) 110 | 111 | def assertError(self, tb): 112 | yield from self.assertSignal(tb.phy.error, 1) 113 | yield 114 | yield from self.assertSignal(tb.phy._tsZ.valid, 0) 115 | yield from self.assertState(tb, "COMMA") 116 | 117 | @simulation_test 118 | def test_rx_ts1_empty_valid(self, tb): 119 | yield from self.tb.transmit([ 120 | K(28,5), K(23,7), K(23,7), 0, 0b0000, 0b0000, *[D(10,2) for _ in range(10)], 121 | K(28,5), 122 | ]) 123 | yield from self.assertTSnState(tb.phy._tsZ, ts_id=0) 124 | 125 | @simulation_test 126 | def test_rx_ts2_empty_valid(self, tb): 127 | yield from self.tb.transmit([ 128 | K(28,5), K(23,7), K(23,7), 0, 0b0000, 0b0000, *[D(5,2) for _ in range(10)], 129 | K(28,5), 130 | ]) 131 | yield from self.assertTSnState(tb.phy._tsZ, ts_id=1) 132 | 133 | @simulation_test 134 | def test_rx_ts1_inverted_valid(self, tb): 135 | yield from self.tb.transmit([ 136 | K(28,5), K(23,7), K(23,7), D(0,0), D(0,0), D(0,0), *[D(21,5) for _ in range(10)], 137 | K(28,5), 138 | ]) 139 | yield from self.assertSignal(tb.lane.rx_invert, 1) 140 | yield from self.assertTSnState(tb.phy._tsZ) 141 | 142 | @simulation_test 143 | def test_rx_ts2_inverted_valid(self, tb): 144 | yield from self.tb.transmit([ 145 | K(28,5), K(23,7), K(23,7), D(0,0), D(0,0), D(0,0), *[D(26,5) for _ in range(10)], 146 | K(28,5), 147 | ]) 148 | yield from self.assertSignal(tb.lane.rx_invert, 1) 149 | yield from self.assertTSnState(tb.phy._tsZ) 150 | 151 | @simulation_test 152 | def test_rx_ts1_link_valid(self, tb): 153 | yield from self.tb.transmit([ 154 | K(28,5), 0xaa, K(23,7), 0, 0b0000, 0b0000, *[D(10,2) for _ in range(10)], 155 | K(28,5), 156 | ]) 157 | yield from self.assertTSnState(tb.phy._tsZ, 158 | link_valid=1, link_number=0xaa) 159 | 160 | @simulation_test 161 | def test_rx_ts1_link_invalid(self, tb): 162 | yield from self.tb.transmit([ 163 | K(28,5), 0x1ee, 164 | ]) 165 | yield from self.assertError(tb) 166 | 167 | @simulation_test 168 | def test_rx_ts1_lane_valid(self, tb): 169 | yield from self.tb.transmit([ 170 | K(28,5), 0xaa, 0x1a, 0, 0b0000, 0b0000, *[D(10,2) for _ in range(10)], 171 | K(28,5), 172 | ]) 173 | yield from self.assertTSnState(tb.phy._tsZ, 174 | link_valid=1, link_number=0xaa, 175 | lane_valid=1, lane_number=0x1a) 176 | 177 | @simulation_test 178 | def test_rx_ts1_lane_invalid(self, tb): 179 | yield from self.tb.transmit([ 180 | K(28,5), K(23,7), 0x1ee, 181 | ]) 182 | yield from self.assertError(tb) 183 | 184 | @simulation_test 185 | def test_rx_ts1_n_fts_valid(self, tb): 186 | yield from self.tb.transmit([ 187 | K(28,5), 0xaa, 0x1a, 0xff, 0b0000, 0b0000, *[D(10,2) for _ in range(10)], 188 | K(28,5), 189 | ]) 190 | yield from self.assertTSnState(tb.phy._tsZ, 191 | link_valid=1, link_number=0xaa, 192 | lane_valid=1, lane_number=0x1a, 193 | n_fts=255) 194 | 195 | @simulation_test 196 | def test_rx_ts1_n_fts_invalid(self, tb): 197 | yield from self.tb.transmit([ 198 | K(28,5), K(23,7), K(23,7), 0x1ee 199 | ]) 200 | yield from self.assertError(tb) 201 | 202 | @simulation_test 203 | def test_rx_ts1_n_rate_valid(self, tb): 204 | yield from self.tb.transmit([ 205 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 206 | K(28,5), 207 | ]) 208 | yield from self.assertTSnState(tb.phy._tsZ, 209 | link_valid=1, link_number=0xaa, 210 | lane_valid=1, lane_number=0x1a, 211 | n_fts=255, 212 | rate_gen1=1) 213 | 214 | @simulation_test 215 | def test_rx_ts1_n_rate_invalid(self, tb): 216 | yield from self.tb.transmit([ 217 | K(28,5), K(23,7), K(23,7), 0xff, 0x1ee 218 | ]) 219 | yield from self.assertError(tb) 220 | 221 | @simulation_test 222 | def test_rx_ts1_ctrl_valid(self, tb): 223 | for (ctrl, bit) in ( 224 | ("ctrl_hot_reset", 0b0001), 225 | ("ctrl_disable_link", 0b0010), 226 | ("ctrl_loopback", 0b0100), 227 | ("ctrl_disable_scrambling", 0b1000), 228 | ): 229 | yield from self.tb.transmit([ 230 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, bit, *[D(10,2) for _ in range(10)], 231 | ]) 232 | yield from self.assertTSnState(tb.phy._tsZ, 233 | link_valid=1, link_number=0xaa, 234 | lane_valid=1, lane_number=0x1a, 235 | n_fts=255, 236 | rate_gen1=1, 237 | **{ctrl:1}) 238 | 239 | @simulation_test 240 | def test_rx_ts1_ctrl_invalid(self, tb): 241 | yield from self.tb.transmit([ 242 | K(28,5), K(23,7), K(23,7), 0xff, 0b0010, 0x1ee 243 | ]) 244 | yield from self.assertError(tb) 245 | 246 | @simulation_test 247 | def test_rx_ts1_idN_invalid(self, tb): 248 | for n in range(10): 249 | yield self.tb.lane.rx_symbol.eq(0) 250 | yield 251 | yield from self.tb.transmit([ 252 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0001, *[D(10,2) for _ in range(n)], 0x1ee 253 | ]) 254 | yield from self.assertError(tb) 255 | 256 | @simulation_test 257 | def test_rx_ts1_2x_same_valid(self, tb): 258 | yield from self.tb.transmit([ 259 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 260 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 261 | K(28,5) 262 | ]) 263 | yield from self.assertSignal(tb.phy.ts.valid, 1) 264 | 265 | @simulation_test 266 | def test_rx_ts1_2x_different_invalid(self, tb): 267 | yield from self.tb.transmit([ 268 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 269 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0001, *[D(10,2) for _ in range(10)], 270 | K(28,5) 271 | ]) 272 | yield from self.assertSignal(tb.phy.ts.valid, 0) 273 | 274 | @simulation_test 275 | def test_rx_ts1_3x_same_different_invalid(self, tb): 276 | yield from self.tb.transmit([ 277 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 278 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 279 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0001, *[D(10,2) for _ in range(10)], 280 | K(28,5) 281 | ]) 282 | yield from self.assertSignal(tb.phy.ts.valid, 0) 283 | 284 | @simulation_test 285 | def test_rx_ts1_3x_same_different_invalid(self, tb): 286 | yield from self.tb.transmit([ 287 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 288 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 289 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0001, *[D(10,2) for _ in range(10)], 290 | K(28,5) 291 | ]) 292 | yield from self.assertSignal(tb.phy.ts.valid, 0) 293 | 294 | @simulation_test 295 | def test_rx_ts1_skp_ts1_valid(self, tb): 296 | yield from self.tb.transmit([ 297 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 298 | K(28,5), K(28,0), K(28,0), K(28,0), 299 | K(28,5), 0xaa, 0x1a, 0xff, 0b0010, 0b0000, *[D(10,2) for _ in range(10)], 300 | K(28,5) 301 | ]) 302 | yield from self.assertSignal(tb.phy.ts.valid, 1) 303 | 304 | 305 | class PCIePHYRXGear2xTestCase(_PCIePHYRXTestCase): 306 | def setUp(self): 307 | self.tb = PCIePHYRXTestbench(ratio=2) 308 | 309 | def simulationSetUp(self, tb): 310 | yield tb.lane.rx_valid.eq(1) 311 | 312 | @simulation_test 313 | def test_rx_ts1_2x_same_valid(self, tb): 314 | yield from self.tb.transmit([ 315 | (K(28,5), 0xaa), (0x1a, 0xff), (0b0010, 0b0000), 316 | *[(D(10,2), D(10,2)) for _ in range(5)], 317 | (K(28,5), 0xaa), (0x1a, 0xff), (0b0010, 0b0000), 318 | *[(D(10,2), D(10,2)) for _ in range(5)], 319 | (K(28,5), K(28,0)), 320 | ]) 321 | yield from self.assertSignal(tb.phy.ts.valid, 1) 322 | -------------------------------------------------------------------------------- /yumewatari/gateware/platform/lattice_ecp5.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | from migen.genlib.cdc import * 3 | 4 | from ..serdes import * 5 | 6 | 7 | __all__ = ["LatticeECP5PCIeSERDES"] 8 | 9 | 10 | class LatticeECP5PCIeSERDES(Module): 11 | """ 12 | Lattice ECP5 DCU configured in PCIe mode. Assumes 100 MHz reference clock on SERDES clock 13 | input pair. Uses 1:2 gearing. Receiver Detection runs in TX clock domain. Only provides 14 | a single lane. 15 | 16 | Parameters 17 | ---------- 18 | ref_clk : Signal 19 | 100 MHz SERDES reference clock. 20 | 21 | rx_clk_o : Signal 22 | 125 MHz clock recovered from received data. 23 | rx_clk_i : Signal 24 | 125 MHz clock for the receive FIFO. 25 | 26 | tx_clk_o : Signal 27 | 125 MHz clock generated by transmit PLL. 28 | tx_clk_i : Signal 29 | 125 MHz clock for the transmit FIFO. 30 | """ 31 | 32 | def __init__(self, pins): 33 | self.ref_clk = Signal() # reference clock 34 | 35 | self.specials.extref0 = Instance("EXTREFB", 36 | i_REFCLKP=pins.clk_p, 37 | i_REFCLKN=pins.clk_n, 38 | o_REFCLKO=self.ref_clk, 39 | p_REFCK_PWDNB="0b1", 40 | p_REFCK_RTERM="0b1", # 100 Ohm 41 | ) 42 | self.extref0.attr.add(("LOC", "EXTREF0")) 43 | 44 | self.rx_clk_o = Signal() 45 | self.rx_clk_i = Signal() 46 | self.rx_bus = Signal(24) 47 | 48 | self.clock_domains.cd_rx = ClockDomain(reset_less=True) 49 | self.comb += self.cd_rx.clk.eq(self.rx_clk_i) 50 | 51 | rx_los = Signal() 52 | rx_los_s = Signal() 53 | rx_lol = Signal() 54 | rx_lol_s = Signal() 55 | rx_lsm = Signal() 56 | rx_lsm_s = Signal() 57 | rx_inv = Signal() 58 | rx_det = Signal() 59 | self.specials += [ 60 | MultiReg(rx_los, rx_los_s, odomain="rx"), 61 | MultiReg(rx_lol, rx_lol_s, odomain="rx"), 62 | MultiReg(rx_lsm, rx_lsm_s, odomain="rx"), 63 | ] 64 | 65 | self.tx_clk_o = Signal() 66 | self.tx_clk_i = Signal() 67 | self.tx_bus = Signal(24) 68 | 69 | self.clock_domains.cd_tx = ClockDomain(reset_less=True) 70 | self.comb += self.cd_tx.clk.eq(self.tx_clk_i) 71 | 72 | tx_lol = Signal() 73 | tx_lol_s = Signal() 74 | self.specials += [ 75 | MultiReg(tx_lol, tx_lol_s, odomain="tx") 76 | ] 77 | 78 | self.lane = lane = PCIeSERDESInterface(ratio=2) 79 | self.comb += [ 80 | rx_inv.eq(lane.rx_invert), 81 | rx_det.eq(lane.rx_align), 82 | lane.rx_present.eq(~rx_los_s), 83 | lane.rx_locked .eq(~rx_lol_s), 84 | lane.rx_aligned.eq(rx_lsm_s), 85 | lane.rx_symbol.eq(Cat(self.rx_bus[ 0: 9], 86 | self.rx_bus[12:21])), 87 | # In theory, ``rx_bus[9:11]`` has disparity error and coding violation status 88 | # signals, but in practice, they appear to be stuck at 1 and 0 respectively. 89 | # However, the 8b10b decoder replaces errors with a "K14.7", which is not a legal 90 | # point in 8b10b coding space, so we can use that as an indication. 91 | lane.rx_valid.eq(Cat(self.rx_bus[ 0: 9] != 0x1EE, 92 | self.rx_bus[12:21] != 0x1EE)), 93 | ] 94 | self.comb += [ 95 | self.tx_bus.eq(Cat(lane.tx_symbol[0: 9], 96 | lane.tx_set_disp[0], lane.tx_disp[0], lane.tx_e_idle[0], 97 | lane.tx_symbol[9:18], 98 | lane.tx_set_disp[1], lane.tx_disp[1], lane.tx_e_idle[1])), 99 | ] 100 | 101 | pcie_det_en = Signal() 102 | pcie_ct = Signal() 103 | pcie_done = Signal() 104 | pcie_done_s = Signal() 105 | pcie_con = Signal() 106 | pcie_con_s = Signal() 107 | self.specials += [ 108 | MultiReg(pcie_done, pcie_done_s, odomain="tx"), 109 | MultiReg(pcie_con, pcie_con_s, odomain="tx"), 110 | ] 111 | 112 | det_timer = Signal(max=16) 113 | 114 | # All comments below from TN1261. 115 | self.submodules.det_fsm = ResetInserter()(ClockDomainsRenamer("tx")(FSM())) 116 | self.comb += self.det_fsm.reset.eq(~lane.det_enable) 117 | # The PCIeSERDESInterface contract states that at det_enable rising edge the transmitter 118 | # is already in Electrical Idle state, but not how long it is there. 119 | self.det_fsm.act("START", 120 | # Before starting a Receiver Detection test, the transmitter must be put into 121 | # electrical idle by setting the tx_idle_ch#_c input high. The Receiver Detection 122 | # test can begin 120 ns after tx_elec_idle is set high by driving the appropriate 123 | # pci_det_en_ch#_c high. 124 | NextValue(det_timer, 15), 125 | NextState("SET-DETECT-H") 126 | ) 127 | self.det_fsm.act("SET-DETECT-H", 128 | # 1. The user drives pcie_det_en high, putting the corresponding TX driver into 129 | # receiver detect mode. [...] The TX driver takes some time to enter this state 130 | # so the pcie_det_en must be driven high for at least 120ns before pcie_ct 131 | # is asserted. 132 | If(det_timer == 0, 133 | NextValue(pcie_det_en, 1), 134 | NextValue(det_timer, 15), 135 | NextState("SET-STROBE-H") 136 | ).Else( 137 | NextValue(det_timer, det_timer - 1) 138 | ) 139 | ) 140 | self.det_fsm.act("SET-STROBE-H", 141 | # 2. The user drives pcie_ct high for four byte clocks. 142 | If(det_timer == 0, 143 | NextValue(pcie_ct, 1), 144 | NextValue(det_timer, 3), 145 | NextState("SET-STROBE-L") 146 | ).Else( 147 | NextValue(det_timer, det_timer - 1) 148 | ) 149 | ) 150 | self.det_fsm.act("SET-STROBE-L", 151 | # 3. SERDES drives the corresponding pcie_done low. 152 | # (this happens asynchronously, so we're going to observe a few samples of pcie_done 153 | # as high) 154 | If(det_timer == 0, 155 | NextValue(pcie_ct, 0), 156 | NextState("WAIT-DONE-L") 157 | ).Else( 158 | NextValue(det_timer, det_timer - 1) 159 | ) 160 | ) 161 | self.det_fsm.act("WAIT-DONE-L", 162 | # 6. SERDES drives the corresponding pcie_done high 163 | If(~pcie_done_s, 164 | NextState("WAIT-DONE-H") 165 | ) 166 | ) 167 | self.det_fsm.act("WAIT-DONE-H", 168 | # 7. The user can use this asserted state of pcie_done to sample the pcie_con status 169 | # to determine if the receiver detection was successful. 170 | If(pcie_done_s, 171 | NextValue(lane.det_status, pcie_con_s), 172 | NextState("DONE") 173 | ) 174 | ) 175 | self.det_fsm.act("DONE", 176 | # TN1261 Figure 17 specifies "tdeth" (Transmitter Detect hold time?) but never 177 | # elaborates on what value it should take. We currently assume tdeth=0. 178 | NextValue(lane.det_valid, 1), 179 | NextState("DONE") 180 | ) 181 | 182 | self.specials.dcu0 = Instance("DCUA", 183 | #============================ DCU 184 | # DCU — power management 185 | p_D_MACROPDB = "0b1", 186 | p_D_IB_PWDNB = "0b1", # undocumented (required for RX) 187 | p_D_TXPLL_PWDNB = "0b1", 188 | i_D_FFC_MACROPDB = 1, 189 | 190 | # DCU — reset 191 | i_D_FFC_MACRO_RST = 0, 192 | i_D_FFC_DUAL_RST = 0, 193 | i_D_FFC_TRST = 0, 194 | 195 | # DCU — clocking 196 | i_D_REFCLKI = self.ref_clk, 197 | o_D_FFS_PLOL = tx_lol, 198 | p_D_REFCK_MODE = "0b100", # 25x REFCLK 199 | p_D_TX_MAX_RATE = "2.5", # 2.5 Gbps 200 | p_D_TX_VCO_CK_DIV = "0b000", # DIV/1 201 | p_D_BITCLK_LOCAL_EN = "0b1", # undocumented (PCIe sample code used) 202 | 203 | # DCU ­— unknown 204 | p_D_CMUSETBIASI = "0b00", # begin undocumented (PCIe sample code used) 205 | p_D_CMUSETI4CPP = "0d4", 206 | p_D_CMUSETI4CPZ = "0d3", 207 | p_D_CMUSETI4VCO = "0b00", 208 | p_D_CMUSETICP4P = "0b01", 209 | p_D_CMUSETICP4Z = "0b101", 210 | p_D_CMUSETINITVCT = "0b00", 211 | p_D_CMUSETISCL4VCO = "0b000", 212 | p_D_CMUSETP1GM = "0b000", 213 | p_D_CMUSETP2AGM = "0b000", 214 | p_D_CMUSETZGM = "0b100", 215 | p_D_SETIRPOLY_AUX = "0b10", 216 | p_D_SETICONST_AUX = "0b01", 217 | p_D_SETIRPOLY_CH = "0b10", 218 | p_D_SETICONST_CH = "0b10", 219 | p_D_SETPLLRC = "0d1", 220 | p_D_RG_EN = "0b1", 221 | p_D_RG_SET = "0b00", # end undocumented 222 | 223 | # DCU — FIFOs 224 | p_D_LOW_MARK = "0d4", 225 | p_D_HIGH_MARK = "0d12", 226 | 227 | #============================ CH0 common 228 | # CH0 — protocol 229 | p_CH0_PROTOCOL = "PCIE", 230 | p_CH0_PCIE_MODE = "0b1", 231 | 232 | #============================ CH0 receive 233 | # CH0 RX ­— power management 234 | p_CH0_RPWDNB = "0b1", 235 | i_CH0_FFC_RXPWDNB = 1, 236 | 237 | # CH0 RX ­— reset 238 | i_CH0_FFC_RRST = 0, 239 | i_CH0_FFC_LANE_RX_RST = 0, 240 | 241 | # CH0 RX ­— input 242 | i_CH0_HDINP = pins.rx_p, 243 | i_CH0_HDINN = pins.rx_n, 244 | i_CH0_FFC_SB_INV_RX = rx_inv, 245 | 246 | p_CH0_RTERM_RX = "0d22", # 50 Ohm (wizard value used, does not match D/S) 247 | p_CH0_RXIN_CM = "0b11", # CMFB (wizard value used) 248 | p_CH0_RXTERM_CM = "0b11", # RX Input (wizard value used) 249 | 250 | # CH0 RX ­— clocking 251 | i_CH0_RX_REFCLK = self.ref_clk, 252 | o_CH0_FF_RX_PCLK = self.rx_clk_o, 253 | i_CH0_FF_RXI_CLK = self.rx_clk_i, 254 | 255 | p_CH0_CDR_MAX_RATE = "2.5", # 2.5 Gbps 256 | p_CH0_RX_DCO_CK_DIV = "0b000", # DIV/1 257 | p_CH0_RX_GEAR_MODE = "0b1", # 1:2 gearbox 258 | p_CH0_FF_RX_H_CLK_EN = "0b1", # enable DIV/2 output clock 259 | p_CH0_FF_RX_F_CLK_DIS = "0b1", # disable DIV/1 output clock 260 | p_CH0_SEL_SD_RX_CLK = "0b1", # FIFO driven by recovered clock 261 | 262 | p_CH0_AUTO_FACQ_EN = "0b1", # undocumented (wizard value used) 263 | p_CH0_AUTO_CALIB_EN = "0b1", # undocumented (wizard value used) 264 | p_CH0_PDEN_SEL = "0b1", # phase detector disabled on LOS 265 | 266 | p_CH0_DCOATDCFG = "0b00", # begin undocumented (PCIe sample code used) 267 | p_CH0_DCOATDDLY = "0b00", 268 | p_CH0_DCOBYPSATD = "0b1", 269 | p_CH0_DCOCALDIV = "0b010", 270 | p_CH0_DCOCTLGI = "0b011", 271 | p_CH0_DCODISBDAVOID = "0b1", 272 | p_CH0_DCOFLTDAC = "0b00", 273 | p_CH0_DCOFTNRG = "0b010", 274 | p_CH0_DCOIOSTUNE = "0b010", 275 | p_CH0_DCOITUNE = "0b00", 276 | p_CH0_DCOITUNE4LSB = "0b010", 277 | p_CH0_DCOIUPDNX2 = "0b1", 278 | p_CH0_DCONUOFLSB = "0b101", 279 | p_CH0_DCOSCALEI = "0b01", 280 | p_CH0_DCOSTARTVAL = "0b010", 281 | p_CH0_DCOSTEP = "0b11", # end undocumented 282 | 283 | # CH0 RX — loss of signal 284 | o_CH0_FFS_RLOS = rx_los, 285 | p_CH0_RLOS_SEL = "0b1", 286 | p_CH0_RX_LOS_EN = "0b1", 287 | p_CH0_RX_LOS_LVL = "0b100", # Lattice "TBD" (wizard value used) 288 | p_CH0_RX_LOS_CEQ = "0b11", # Lattice "TBD" (wizard value used) 289 | 290 | # CH0 RX — loss of lock 291 | o_CH0_FFS_RLOL = rx_lol, 292 | 293 | # CH0 RX — link state machine 294 | i_CH0_FFC_SIGNAL_DETECT = rx_det, 295 | o_CH0_FFS_LS_SYNC_STATUS= rx_lsm, 296 | p_CH0_ENABLE_CG_ALIGN = "0b1", 297 | p_CH0_UDF_COMMA_MASK = "0x3ff", # compare all 10 bits 298 | p_CH0_UDF_COMMA_A = "0x283", # K28.5 inverted 299 | p_CH0_UDF_COMMA_B = "0x17C", # K28.5 300 | 301 | p_CH0_CTC_BYPASS = "0b1", # bypass CTC FIFO 302 | p_CH0_MIN_IPG_CNT = "0b11", # minimum interpacket gap of 4 303 | p_CH0_MATCH_4_ENABLE = "0b1", # 4 character skip matching 304 | p_CH0_CC_MATCH_1 = "0x1BC", # K28.5 305 | p_CH0_CC_MATCH_2 = "0x11C", # K28.0 306 | p_CH0_CC_MATCH_3 = "0x11C", # K28.0 307 | p_CH0_CC_MATCH_4 = "0x11C", # K28.0 308 | 309 | # CH0 RX — data 310 | **{"o_CH0_FF_RX_D_%d" % n: self.rx_bus[n] for n in range(self.rx_bus.nbits)}, 311 | 312 | #============================ CH0 transmit 313 | # CH0 TX — power management 314 | p_CH0_TPWDNB = "0b1", 315 | i_CH0_FFC_TXPWDNB = 1, 316 | 317 | # CH0 TX ­— reset 318 | i_CH0_FFC_LANE_TX_RST = 0, 319 | 320 | # CH0 TX ­— output 321 | o_CH0_HDOUTP = pins.tx_p, 322 | o_CH0_HDOUTN = pins.tx_n, 323 | 324 | p_CH0_TXAMPLITUDE = "0d1000", # 1000 mV 325 | p_CH0_RTERM_TX = "0d19", # 50 Ohm 326 | 327 | p_CH0_TDRV_SLICE0_CUR = "0b011", # 400 uA 328 | p_CH0_TDRV_SLICE0_SEL = "0b01", # main data 329 | p_CH0_TDRV_SLICE1_CUR = "0b000", # 100 uA 330 | p_CH0_TDRV_SLICE1_SEL = "0b00", # power down 331 | p_CH0_TDRV_SLICE2_CUR = "0b11", # 3200 uA 332 | p_CH0_TDRV_SLICE2_SEL = "0b01", # main data 333 | p_CH0_TDRV_SLICE3_CUR = "0b11", # 3200 uA 334 | p_CH0_TDRV_SLICE3_SEL = "0b01", # main data 335 | p_CH0_TDRV_SLICE4_CUR = "0b11", # 3200 uA 336 | p_CH0_TDRV_SLICE4_SEL = "0b01", # main data 337 | p_CH0_TDRV_SLICE5_CUR = "0b00", # 800 uA 338 | p_CH0_TDRV_SLICE5_SEL = "0b00", # power down 339 | 340 | # CH0 TX ­— clocking 341 | o_CH0_FF_TX_PCLK = self.tx_clk_o, 342 | i_CH0_FF_TXI_CLK = self.tx_clk_i, 343 | 344 | p_CH0_TX_GEAR_MODE = "0b1", # 1:2 gearbox 345 | p_CH0_FF_TX_H_CLK_EN = "0b1", # enable DIV/2 output clock 346 | p_CH0_FF_TX_F_CLK_DIS = "0b1", # disable DIV/1 output clock 347 | 348 | # CH0 TX — data 349 | **{"o_CH0_FF_TX_D_%d" % n: self.tx_bus[n] for n in range(self.tx_bus.nbits)}, 350 | 351 | # CH0 DET 352 | i_CH0_FFC_PCIE_DET_EN = pcie_det_en, 353 | i_CH0_FFC_PCIE_CT = pcie_ct, 354 | o_CH0_FFS_PCIE_DONE = pcie_done, 355 | o_CH0_FFS_PCIE_CON = pcie_con, 356 | ) 357 | self.dcu0.attr.add(("LOC", "DCU0")) 358 | self.dcu0.attr.add(("CHAN", "CH0")) 359 | self.dcu0.attr.add(("BEL", "X42/Y71/DCU")) 360 | --------------------------------------------------------------------------------