[init] 10BASE-T implementation

This commit is contained in:
Charles-Henri Mousset 2021-04-04 19:23:20 +02:00
parent 694cc81d77
commit 526761a4d9
3 changed files with 323 additions and 0 deletions

102
bench/ulx3s.py Executable file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
#
# This file is part of LiteX-Boards.
#
# Copyright (c) 2018-2019 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2018 David Shah <dave@ds0.me>
# SPDX-License-Identifier: BSD-2-Clause
import os
import argparse
import sys
from migen import *
from litex_boards.platforms import ulx3s
from litex_boards.targets.ulx3s import _CRG
from litex.build.lattice.trellis import trellis_args, trellis_argdict
from litex.soc.cores.clock import *
from litex.soc.integration.soc_core import *
from litex.soc.integration.builder import *
from litex.soc.cores.gpio import GPIOOut
from litedram import modules as litedram_modules
from litedram.phy import GENSDRPHY, HalfRateGENSDRPHY
from litex.build.generic_platform import *
from liteeth.phy import LiteEthPHYETHERNET
# IOs ----------------------------------------------------------------------------------------------
_eth_io = [
# Direct connect 10BASE-T, full-duplex Ethernet
("eth", 0,
Subsignal("td_p", Pins("A2")), # J1 GP9 - Green/White
Subsignal("td_n", Pins("B1")), # J1 GN9 - Green
Subsignal("rd_p", Pins("C4"), IOStandard("LVDS"), Misc("DIFFRESISTOR=100")), # J1 GP10 - Orange/White
Subsignal("rd_n", Pins("B4")), # J1 GN10 - Orange
IOStandard("LVCMOS33"),
),
]
# Bench SoC ----------------------------------------------------------------------------------------
class BenchSoC(SoCCore):
def __init__(self, device="LFE5U-45F", revision="2.0", toolchain="trellis",
sys_clk_freq=int(40e6), **kwargs):
platform = ulx3s.Platform(device=device, revision=revision, toolchain=toolchain)
platform.add_extension(_eth_io)
# SoCCore ----------------------------------------------------------------------------------
SoCMini.__init__(self, platform, sys_clk_freq,
ident = "LiteEth bench on ULX3S",
ident_version = True,
**kwargs)
# CRG --------------------------------------------------------------------------------------
self.submodules.crg = _CRG(platform, sys_clk_freq)
# Etherbone --------------------------------------------------------------------------------
self.submodules.ethphy = LiteEthPHYETHERNET(
pads = self.platform.request("eth"),
refclk_cd = "sys",
with_hw_init_reset = False)
self.add_csr("ethphy")
self.add_etherbone(phy=self.ethphy, buffer_depth=255)
# Build --------------------------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description="LiteX SoC on ULX3S")
parser.add_argument("--build", action="store_true", help="Build bitstream")
parser.add_argument("--load", action="store_true", help="Load bitstream")
parser.add_argument("--toolchain", default="trellis", help="FPGA toolchain: trellis (default) or diamond")
parser.add_argument("--device", default="LFE5U-45F", help="FPGA device: LFE5U-12F, LFE5U-25F, LFE5U-45F (default) or LFE5U-85F")
parser.add_argument("--revision", default="2.0", help="Board revision: 2.0 (default) or 1.7")
parser.add_argument("--sys-clk-freq", default=40e6, help="System clock frequency (default: 50MHz)")
builder_args(parser)
trellis_args(parser)
args = parser.parse_args()
soc = BenchSoC(
device = args.device,
revision = args.revision,
toolchain = args.toolchain,
sys_clk_freq = int(float(args.sys_clk_freq)))
builder = Builder(soc, **builder_argdict(args))
builder_kargs = trellis_argdict(args) if args.toolchain == "trellis" else {}
builder.build(**builder_kargs, run=args.build)
if args.load:
prog = soc.platform.create_programmer()
prog.load_bitstream(os.path.join(builder.gateware_dir, soc.build_name + (".svf" if args.toolchain == "trellis" else ".bit")))
if __name__ == "__main__":
main()

View File

@ -26,6 +26,7 @@ from liteeth.phy.mii import LiteEthPHYMII
from liteeth.phy.rmii import LiteEthPHYRMII from liteeth.phy.rmii import LiteEthPHYRMII
from liteeth.phy.gmii import LiteEthPHYGMII from liteeth.phy.gmii import LiteEthPHYGMII
from liteeth.phy.gmii_mii import LiteEthPHYGMIIMII from liteeth.phy.gmii_mii import LiteEthPHYGMIIMII
from liteeth.phy.ethernet import LiteEthPHYETHERNET
from liteeth.phy.s6rgmii import LiteEthPHYRGMII as LiteEthS6PHYRGMII from liteeth.phy.s6rgmii import LiteEthPHYRGMII as LiteEthS6PHYRGMII
from liteeth.phy.s7rgmii import LiteEthPHYRGMII as LiteEthS7PHYRGMII from liteeth.phy.s7rgmii import LiteEthPHYRGMII as LiteEthS7PHYRGMII

220
liteeth/phy/ethernet.py Normal file
View File

@ -0,0 +1,220 @@
#
# This file is part of LiteEth.
#
# Copyright (c) 2015-2018 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2021 Charles-Henri Mousset <ch.mousset@gmail.com>
# SPDX-License-Identifier: BSD-2-Clause
from migen import *
from migen.genlib.cdc import MultiReg
from migen.genlib.resetsync import AsyncResetSynchronizer
from litex.build.io import DDROutput
from liteeth.common import *
from liteeth.phy.common import *
from litex.build.io import DifferentialInput
from litex.soc.integration.doc import AutoDoc, ModuleDoc
def converter_description(dw):
payload_layout = [("data", dw)]
return EndpointDescription(payload_layout)
class LiteEthPHYETHERNETTX(Module):
def __init__(self, pads):
self.sink = sink = stream.Endpoint(eth_phy_description(8))
self.submodules.fsm = fsm = FSM("IDLE")
# # #
# deserializing
converter = stream.StrideConverter(converter_description(8),
converter_description(1))
self.submodules += converter
self.comb += [
converter.sink.valid.eq(sink.valid),
converter.sink.data.eq(sink.data),
sink.ready.eq(converter.sink.ready),
converter.source.ready.eq(fsm.ongoing("TX"))
]
# Manchester encoding
tx_bit = Signal()
tx_cnt = Signal(2)
tx_bit_strb = Signal()
tx = Signal(reset_less=True)
txe = Signal(reset_less=True)
self.comb += tx_bit_strb.eq(tx_cnt == 0b11)
self.sync += [tx_cnt.eq(tx_cnt+1)]
# Output logic
if hasattr(pads, "tx") and hasattr(pads, "tx_en"): # RS485 half duplex mode
self.comb += [pads.tx.eq(tx), pads.txe.eq(txe)]
else: # A true differential output buffer is not necessary at 20Mbps(manchester)
self.comb += [pads.td_p.eq(tx & txe), pads.td_n.eq(~tx & txe)]
# Normal Link Pulse generation timer
NLP_PERIOD = int(40e6/1000*16)
nlp_timeout = Signal()
nlp_counter = Signal(max=NLP_PERIOD+1)
self.comb += [
nlp_timeout.eq(nlp_counter == NLP_PERIOD-1),
]
self.sync += [
If(nlp_timeout,
nlp_counter.eq(0),
).Else(
nlp_counter.eq(nlp_counter+1),
),
]
# Main FSM
fsm.act("IDLE",
# send the Normal Link Pulses
If(nlp_timeout & (tx_bit_strb),
NextState("NLP1"),
),
converter.sink.ready.eq(tx_bit_strb),
If(converter.sink.valid & converter.sink.ready,
NextState("TX"),
NextValue(tx_bit, converter.sink.data),
),
)
fsm.act("NLP1",
# we emit a '1' for 1 bit time
tx.eq(1),
txe.eq(1),
If(tx_bit_strb,
NextState("NLP2"),
)
)
fsm.act("NLP2",
# we emit a '0' for 1 bit time
tx.eq(0),
txe.eq(1),
If(tx_bit_strb,
NextState("IDLE"),
)
)
fsm.act("TX",
tx.eq(tx_bit ^ tx_cnt[1]),
txe.eq(converter.sink.valid), # should stay at 1
If(tx_bit_strb,
converter.sink.ready.eq(1),
NextValue(tx_bit, converter.sink.data),
),
If(sink.last_be,
NextState("IDLE"),
)
)
class LiteEthPHYETHERNETRX(Module):
def __init__(self, pads):
self.source = source = stream.Endpoint(eth_phy_description(8))
# # #
# Single Ended / Differential input
rx = Signal()
if not hasattr(pads, "rx"):
self.specials += DifferentialInput(pads.rd_p, pads.rd_n, rx)
else:
self.comb += rx.eq(pads.rx)
# Manchester input
mc_in_data = Signal(3)
mc_cnt = Signal(2)
bit_valid = Signal()
bit_value = Signal()
self.comb += [
bit_valid.eq((mc_in_data[2] ^ mc_in_data[1]) & (mc_cnt == 0b00)),
bit_value.eq(mc_in_data[2]),
]
self.sync += [
mc_in_data.eq(Cat(rx, mc_in_data[0:1])),
If(bit_valid,
mc_cnt.eq(3),
).Elif(mc_cnt,
mc_cnt.eq(mc_cnt-1),
),
]
# Receive timeout / NLP and noise filter
timeout_cnt = Signal(3)
timeout = Signal()
self.comb += [
timeout.eq(timeout_cnt == 0),
]
self.sync += [
If(bit_valid,
timeout_cnt.eq(0b111),
).Elif(timeout_cnt,
timeout_cnt.eq(timeout_cnt - 1),
)
]
# bit to byte logic
bit_cnt = Signal(3)
byte = Signal(8)
self.sync += [
If(timeout,
bit_cnt.eq(0),
).Elif(bit_valid,
bit_cnt.eq(bit_cnt+1),
byte.eq(Cat(byte[1:], bit_value)),
),
]
self.comb += [
self.source.valid.eq((bit_cnt == 7) & bit_valid),
self.source.data.eq(byte),
]
class LiteEthPHYETHERNETCRG(Module, AutoCSR):
def __init__(self, refclk_cd, with_hw_init_reset):
self._reset = CSRStorage()
# # #
# RX/TX clocks
self.clock_domains.cd_eth_rx = ClockDomain()
self.clock_domains.cd_eth_tx = ClockDomain()
# This is entirely clocked internally
assert refclk_cd
self.comb += self.cd_eth_rx.clk.eq(ClockSignal(refclk_cd))
self.comb += self.cd_eth_tx.clk.eq(ClockSignal(refclk_cd))
# Reset
self.reset = reset = Signal()
if with_hw_init_reset:
self.submodules.hw_reset = LiteEthPHYHWReset()
self.comb += reset.eq(self._reset.storage | self.hw_reset.reset)
else:
self.comb += reset.eq(self._reset.storage)
self.specials += [
AsyncResetSynchronizer(self.cd_eth_tx, reset),
AsyncResetSynchronizer(self.cd_eth_rx, reset),
]
class LiteEthPHYETHERNET(Module, AutoCSR, ModuleDoc):
"""
Direct connection to a 10Base-T network, using only series capacitors with FPGIO IOs.
This probably violates some parts of the IEEE802.3 standard. Use at your own risk!
"""
dw = 8
tx_clk_freq = 40e6
rx_clk_freq = 40e6
def __init__(self, pads, refclk_cd="eth", with_hw_init_reset=True):
self.submodules.crg = LiteEthPHYETHERNETCRG(refclk_cd, with_hw_init_reset)
self.submodules.tx = ClockDomainsRenamer("eth_tx")(LiteEthPHYETHERNETTX(pads))
self.submodules.rx = ClockDomainsRenamer("eth_rx")(LiteEthPHYETHERNETRX(pads))
self.sink, self.source = self.tx.sink, self.rx.source