Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions edg/abstract_parts/UsbConnectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ class UsbConnector(Connector):

@abstract_block
class UsbHostConnector(UsbConnector):
"""Abstract base class for a USB 2.0 device-side port connector"""
"""Abstract base class for a USB 2.0 device-side port connector.
By default, generates a ESD diode on the data line if the data line is used."""

def __init__(self) -> None:
def __init__(self, *, generate_esd_diode: BoolLike = True) -> None:
super().__init__()

self.generate_esd_diode = self.ArgParameter(generate_esd_diode)

self.pwr = self.Port(VoltageSink.empty(), optional=True)
self.gnd = self.Port(Ground.empty())

Expand All @@ -24,10 +28,14 @@ def __init__(self) -> None:

@abstract_block
class UsbDeviceConnector(UsbConnector, PowerSource):
"""Abstract base class for a USB 2.0 device-side port connector"""
"""Abstract base class for a USB 2.0 device-side port connector.
By default, generates a ESD diode on the data line if the data line is used."""

def __init__(self) -> None:
def __init__(self, *, generate_esd_diode: BoolLike = True) -> None:
super().__init__()

self.generate_esd_diode = self.ArgParameter(generate_esd_diode)

self.pwr = self.Port(VoltageSource.empty(), optional=True)
self.gnd = self.Port(Ground.empty())

Expand Down
2 changes: 1 addition & 1 deletion edg/circuits/UsbBitBang.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def digital_external_from_link(link_port: DigitalBidir) -> DigitalBidir:

def __init__(self) -> None:
super().__init__()
self.usb = self.Port(UsbDevicePort.empty(), [Output])
self.usb = self.Port(UsbDevicePort(speed=(UsbLink.UsbLowSpeed, UsbLink.UsbFullSpeed)), [Output])

# Internally, this behaves like a bridge, with defined 'external' (USB) and 'internal' (FPGA)
# sides and propagating port data from internal to external as with bridge semantics.
Expand Down
4 changes: 3 additions & 1 deletion edg/circuits/UsbSeriesResistor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ def __init__(self, resistance: RangeLike) -> None:
super().__init__()
self.resistance = self.ArgParameter(resistance)
self.interior = self.Port(UsbHostPort(), [Input])
self.exterior = self.Port(UsbDevicePort(), [Output])
self.exterior = self.Port(
UsbDevicePort(speed=self.interior.link().speed, _passive_speed=self.interior.link().passive_speed), [Output]
)

@override
def contents(self) -> None:
Expand Down
44 changes: 38 additions & 6 deletions edg/electronics_interfaces/UsbPort.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,25 @@


class UsbLink(Link):
UsbLowSpeed = 1_500_000
UsbFullSpeed = 12_000_000
UsbHighSpeed = 480_000_000

AllUsb2Speeds = Range(UsbLowSpeed, UsbHighSpeed) # all USB2.0 (single diffpair) speeds
UsbFullSpeedOnly = Range.exact(UsbFullSpeed)

def __init__(self) -> None:
super().__init__()
self.host = self.Port(UsbHostPort())
self.device = self.Port(UsbDevicePort())
self.passive = self.Port(Vector(UsbPassivePort()), optional=True)

self.speed = self.Parameter(RangeExpr()) # link speed between host and device
self.passive_speed = self.Parameter(RangeExpr())

@override
def contents(self) -> None:
super().contents()
# TODO write protocol-level signal constraints?

self.d_P = self.connect(
self.host.dp, self.device.dp, self.passive.map_extract(lambda port: port.dp), flatten=True
Expand All @@ -26,12 +35,24 @@ def contents(self) -> None:
self.host.dm, self.device.dm, self.passive.map_extract(lambda port: port.dm), flatten=True
)

self.assign(self.speed, self.host.speed.intersect(self.device.speed))
self.require(self.speed != RangeExpr.EMPTY, "incompatible host and device speeds")
self.assign(
self.passive_speed,
self.passive.intersection(lambda port: port.speed).intersect(
self.host._passive_speed.intersect(self.device._passive_speed)
),
)
self.require(
self.speed.within(self.passive_speed), "passive device speed limits must not limit data link speed"
)


class UsbHostBridge(PortBridge):
def __init__(self) -> None:
super().__init__()
self.outer_port = self.Port(UsbHostPort.empty())
self.inner_link = self.Port(UsbDevicePort.empty())
self.inner_link = self.Port(UsbDevicePort(speed=Range.all())) # dummy

@override
def contents(self) -> None:
Expand All @@ -45,13 +66,18 @@ def contents(self) -> None:
self.connect(self.outer_port.dp, self.dp_bridge.outer_port)
self.connect(self.dp_bridge.inner_link, self.inner_link.dp)

self.assign(self.outer_port.speed, self.inner_link.link().speed)
self.assign(self.outer_port._passive_speed, self.inner_link.link().passive_speed)


class UsbHostPort(Port[UsbLink]):
link_type = UsbLink
bridge_type = UsbHostBridge

def __init__(self) -> None:
def __init__(self, *, speed: RangeLike = UsbLink.AllUsb2Speeds, _passive_speed: RangeLike = RangeExpr.ALL) -> None:
super().__init__()
self.speed = self.Parameter(RangeExpr(speed))
self._passive_speed = self.Parameter(RangeExpr(_passive_speed)) # used to propagate through bridges
self.dp = self.Port(Passive())
self.dm = self.Port(Passive())

Expand All @@ -60,7 +86,7 @@ class UsbDeviceBridge(PortBridge):
def __init__(self) -> None:
super().__init__()
self.outer_port = self.Port(UsbDevicePort.empty())
self.inner_link = self.Port(UsbHostPort.empty())
self.inner_link = self.Port(UsbHostPort(speed=Range.all())) # dummy

@override
def contents(self) -> None:
Expand All @@ -74,22 +100,28 @@ def contents(self) -> None:
self.connect(self.outer_port.dp, self.dp_bridge.outer_port)
self.connect(self.dp_bridge.inner_link, self.inner_link.dp)

self.assign(self.outer_port.speed, self.inner_link.link().speed)
self.assign(self.outer_port._passive_speed, self.inner_link.link().passive_speed)


class UsbDevicePort(Port[UsbLink]):
link_type = UsbLink
bridge_type = UsbDeviceBridge

def __init__(self) -> None:
def __init__(self, *, speed: RangeLike = UsbLink.AllUsb2Speeds, _passive_speed: RangeLike = RangeExpr.ALL) -> None:
super().__init__()
self.speed = self.Parameter(RangeExpr(speed))
self._passive_speed = self.Parameter(RangeExpr(_passive_speed)) # used to propagate through bridges
self.dp = self.Port(Passive())
self.dm = self.Port(Passive())


class UsbPassivePort(Port[UsbLink]):
link_type = UsbLink

def __init__(self) -> None:
def __init__(self, *, speed: RangeLike = UsbLink.AllUsb2Speeds) -> None:
super().__init__()
self.speed = self.Parameter(RangeExpr(speed))
self.dp = self.Port(Passive())
self.dm = self.Port(Passive())

Expand Down
93 changes: 93 additions & 0 deletions edg/electronics_interfaces/test_usb_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import unittest

from ..electronics_model import *
from .UsbPort import UsbLink, UsbHostPort, UsbDevicePort, UsbPassivePort


class DummyUsbHost(Block):
def __init__(self, speed: RangeLike = UsbLink.AllUsb2Speeds) -> None:
super().__init__()
self.io = self.Port(UsbHostPort(speed=speed))


class DummyUsbDevice(Block):
def __init__(self, speed: RangeLike = UsbLink.AllUsb2Speeds) -> None:
super().__init__()
self.io = self.Port(UsbDevicePort(speed=speed))


class DummyUsbPassive(Block):
def __init__(self, speed: RangeLike = UsbLink.AllUsb2Speeds) -> None:
super().__init__()
self.io = self.Port(UsbPassivePort(speed=speed))


class SimpleUsbTestTop(DesignTop):
"""Test design with host and device and default operating ranges"""

def __init__(self) -> None:
super().__init__()
self.host = self.Block(DummyUsbHost())
self.device = self.Block(DummyUsbDevice())
self.connect(self.host.io, self.device.io)


class PassiveUsbTestTop(DesignTop):
"""Test design with host, device, and passive and default operating ranges"""

def __init__(self) -> None:
super().__init__()
self.host = self.Block(DummyUsbHost())
self.device = self.Block(DummyUsbDevice())
self.passive = self.Block(DummyUsbPassive())
self.connect(self.host.io, self.device.io, self.passive.io)


class FullSpeedDeviceTestTop(DesignTop):
"""Test design with unlimited host (eg, upstream-facing connector) and full-speed device (eg, microcontroller)"""

def __init__(self) -> None:
super().__init__()
self.host = self.Block(DummyUsbHost())
self.device = self.Block(DummyUsbDevice(speed=(UsbLink.UsbLowSpeed, UsbLink.UsbFullSpeed)))
self.connect(self.host.io, self.device.io)


class IncompatibleSpeedTestTop(DesignTop):
"""Test design with incompatible speeds (high speed only host, full speed device)"""

def __init__(self) -> None:
super().__init__()
self.host = self.Block(DummyUsbHost(speed=(UsbLink.UsbHighSpeed, UsbLink.UsbHighSpeed)))
self.device = self.Block(DummyUsbDevice(speed=(UsbLink.UsbLowSpeed, UsbLink.UsbFullSpeed)))
self.connect(self.host.io, self.device.io)


class LimitingPassiveUsbTestTop(DesignTop):
"""Test design with passive that limits the link speed, eg low-speed / high-capacitance ESD diode"""

def __init__(self) -> None:
super().__init__()
self.host = self.Block(DummyUsbHost())
self.device = self.Block(DummyUsbDevice())
self.passive = self.Block(DummyUsbPassive(speed=(UsbLink.UsbLowSpeed, UsbLink.UsbFullSpeed)))
self.connect(self.host.io, self.device.io, self.passive.io)


class VoltageLinkTestCase(unittest.TestCase):
def test_simple(self) -> None:
ScalaCompiler.compile(SimpleUsbTestTop)

def test_passive(self) -> None:
ScalaCompiler.compile(PassiveUsbTestTop)

def test_full_speed_device(self) -> None:
ScalaCompiler.compile(FullSpeedDeviceTestTop)

def test_incompatible_speed(self) -> None:
with self.assertRaises(CompilerCheckError):
ScalaCompiler.compile(IncompatibleSpeedTestTop)

def test_limiting_passive(self) -> None:
with self.assertRaises(CompilerCheckError):
ScalaCompiler.compile(LimitingPassiveUsbTestTop)
Loading
Loading