Initial commit
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
8
README.md
Normal file
8
README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Ethernet interface modules for Cocotb
|
||||||
|
|
||||||
|
GitHub repository: https://github.com/alexforencich/cocotbext-eth
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Ethernet interface models for cocotb.
|
||||||
|
|
||||||
26
cocotbext/eth/__init__.py
Normal file
26
cocotbext/eth/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .xgmii import XgmiiFrame, XgmiiSource, XgmiiSink
|
||||||
|
|
||||||
83
cocotbext/eth/constants.py
Normal file
83
cocotbext/eth/constants.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import enum
|
||||||
|
|
||||||
|
class EthPre(enum.IntEnum):
|
||||||
|
PRE = 0x55
|
||||||
|
SFD = 0xD5
|
||||||
|
|
||||||
|
ETH_PREAMBLE = b'\x55\x55\x55\x55\x55\x55\x55\xd5'
|
||||||
|
|
||||||
|
class XgmiiCtrl(enum.IntEnum):
|
||||||
|
IDLE = 0x07
|
||||||
|
LPI = 0x06
|
||||||
|
START = 0xfb
|
||||||
|
TERM = 0xfd
|
||||||
|
ERROR = 0xfe
|
||||||
|
SEQ_OS = 0x9c
|
||||||
|
RES0 = 0x1c
|
||||||
|
RES1 = 0x3c
|
||||||
|
RES2 = 0x7c
|
||||||
|
RES3 = 0xbc
|
||||||
|
RES4 = 0xdc
|
||||||
|
RES5 = 0xf7
|
||||||
|
SIG_OS = 0x5c
|
||||||
|
|
||||||
|
class BaseRCtrl(enum.IntEnum):
|
||||||
|
IDLE = 0x00
|
||||||
|
LPI = 0x06
|
||||||
|
ERROR = 0x1e
|
||||||
|
RES_0 = 0x2d
|
||||||
|
RES_1 = 0x33
|
||||||
|
RES_2 = 0x4b
|
||||||
|
RES_3 = 0x55
|
||||||
|
RES_4 = 0x66
|
||||||
|
RES_5 = 0x78
|
||||||
|
|
||||||
|
class BaseRO(enum.IntEnum):
|
||||||
|
SEQ_OS = 0x0
|
||||||
|
SIG_OS = 0xf
|
||||||
|
|
||||||
|
class BaseRSync(enum.IntEnum):
|
||||||
|
DATA = 0b10
|
||||||
|
CTRL = 0b01
|
||||||
|
|
||||||
|
class BaseRBlockType(enum.IntEnum):
|
||||||
|
CTRL = 0x1e # C7 C6 C5 C4 C3 C2 C1 C0 BT
|
||||||
|
OS_4 = 0x2d # D7 D6 D5 O4 C3 C2 C1 C0 BT
|
||||||
|
START_4 = 0x33 # D7 D6 D5 C3 C2 C1 C0 BT
|
||||||
|
OS_START = 0x66 # D7 D6 D5 O0 D3 D2 D1 BT
|
||||||
|
OS_04 = 0x55 # D7 D6 D5 O4 O0 D3 D2 D1 BT
|
||||||
|
START_0 = 0x78 # D7 D6 D5 D4 D3 D2 D1 BT
|
||||||
|
OS_0 = 0x4b # C7 C6 C5 C4 O0 D3 D2 D1 BT
|
||||||
|
TERM_0 = 0x87 # C7 C6 C5 C4 C3 C2 C1 BT
|
||||||
|
TERM_1 = 0x99 # C7 C6 C5 C4 C3 C2 D0 BT
|
||||||
|
TERM_2 = 0xaa # C7 C6 C5 C4 C3 D1 D0 BT
|
||||||
|
TERM_3 = 0xb4 # C7 C6 C5 C4 D2 D1 D0 BT
|
||||||
|
TERM_4 = 0xcc # C7 C6 C5 D3 D2 D1 D0 BT
|
||||||
|
TERM_5 = 0xd2 # C7 C6 D4 D3 D2 D1 D0 BT
|
||||||
|
TERM_6 = 0xe1 # C7 D5 D4 D3 D2 D1 D0 BT
|
||||||
|
TERM_7 = 0xff # D6 D5 D4 D3 D2 D1 D0 BT
|
||||||
|
|
||||||
1
cocotbext/eth/version.py
Normal file
1
cocotbext/eth/version.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
__version__ = 0.1
|
||||||
350
cocotbext/eth/xgmii.py
Normal file
350
cocotbext/eth/xgmii.py
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.triggers import RisingEdge, ReadOnly, Timer, First, Event
|
||||||
|
from cocotb.bus import Bus
|
||||||
|
from cocotb.log import SimLog
|
||||||
|
from cocotb.utils import get_sim_time
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
from .constants import EthPre, ETH_PREAMBLE, XgmiiCtrl
|
||||||
|
|
||||||
|
class XgmiiFrame(object):
|
||||||
|
def __init__(self, data=None, ctrl=None):
|
||||||
|
self.data = bytearray()
|
||||||
|
self.ctrl = None
|
||||||
|
self.rx_sim_time = None
|
||||||
|
self.rx_start_lane = None
|
||||||
|
|
||||||
|
if type(data) is XgmiiFrame:
|
||||||
|
self.data = bytearray(data.data)
|
||||||
|
self.ctrl = data.ctrl
|
||||||
|
self.rx_sim_time = data.rx_sim_time
|
||||||
|
self.rx_start_lane = data.rx_start_lane
|
||||||
|
else:
|
||||||
|
self.data = bytearray(data)
|
||||||
|
self.ctrl = ctrl
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_payload(cls, payload):
|
||||||
|
data = bytearray(ETH_PREAMBLE)
|
||||||
|
data.extend(payload)
|
||||||
|
return cls(data)
|
||||||
|
|
||||||
|
def get_preamble(self):
|
||||||
|
return self.data[0:8]
|
||||||
|
|
||||||
|
def get_payload(self):
|
||||||
|
return self.data[8:]
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
n = len(self.data)
|
||||||
|
|
||||||
|
if self.ctrl is not None:
|
||||||
|
self.ctrl = self.ctrl[:n] + [self.ctrl[-1]]*(n-len(self.ctrl))
|
||||||
|
else:
|
||||||
|
self.ctrl = [0]*n
|
||||||
|
|
||||||
|
def compact(self):
|
||||||
|
if not any(self.ctrl):
|
||||||
|
self.ctrl = None
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if type(other) is XgmiiFrame:
|
||||||
|
return self.data == other.data
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return (
|
||||||
|
f"{type(self).__name__}(data={repr(self.data)}, " +
|
||||||
|
f"ctrl={repr(self.ctrl)}, " +
|
||||||
|
f"rx_sim_time={repr(self.rx_sim_time)}, " +
|
||||||
|
f"rx_start_lane={repr(self.rx_start_lane)})"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.data)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.data.__iter__()
|
||||||
|
|
||||||
|
|
||||||
|
class XgmiiSource(object):
|
||||||
|
|
||||||
|
_signals = ["d", "c"]
|
||||||
|
_optional_signals = []
|
||||||
|
|
||||||
|
def __init__(self, entity, name, clock, reset=None, enable=None, *args, **kwargs):
|
||||||
|
self.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
||||||
|
self.entity = entity
|
||||||
|
self.clock = clock
|
||||||
|
self.reset = reset
|
||||||
|
self.enable = enable
|
||||||
|
self.bus = Bus(self.entity, name, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.active = False
|
||||||
|
self.queue = deque()
|
||||||
|
|
||||||
|
self.enable_dic = True
|
||||||
|
self.ifg = 12
|
||||||
|
self.force_offset_start = False
|
||||||
|
|
||||||
|
self.queue_occupancy_bytes = 0
|
||||||
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
|
self.width = len(self.bus.d)
|
||||||
|
self.byte_width = len(self.bus.c)
|
||||||
|
|
||||||
|
self.reset = reset
|
||||||
|
|
||||||
|
assert self.width == self.byte_width * 8
|
||||||
|
|
||||||
|
self.idle_d = 0
|
||||||
|
self.idle_c = 0
|
||||||
|
|
||||||
|
for k in range(self.byte_width):
|
||||||
|
self.idle_d |= XgmiiCtrl.IDLE << k*8
|
||||||
|
self.idle_c |= 1 << k
|
||||||
|
|
||||||
|
self.bus.d.setimmediatevalue(0)
|
||||||
|
self.bus.c.setimmediatevalue(0)
|
||||||
|
|
||||||
|
cocotb.fork(self._run())
|
||||||
|
|
||||||
|
def send(self, frame):
|
||||||
|
frame = XgmiiFrame(frame)
|
||||||
|
self.queue_occupancy_bytes += len(frame)
|
||||||
|
self.queue_occupancy_frames += 1
|
||||||
|
self.queue.append(frame)
|
||||||
|
|
||||||
|
def count(self):
|
||||||
|
return len(self.queue)
|
||||||
|
|
||||||
|
def empty(self):
|
||||||
|
return not self.queue
|
||||||
|
|
||||||
|
def idle(self):
|
||||||
|
return self.empty() and not self.active
|
||||||
|
|
||||||
|
async def wait(self):
|
||||||
|
while not self.idle():
|
||||||
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
frame = None
|
||||||
|
ifg_cnt = 0
|
||||||
|
deficit_idle_cnt = 0
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await ReadOnly()
|
||||||
|
|
||||||
|
if self.reset is not None and self.reset.value:
|
||||||
|
await RisingEdge(self.clock)
|
||||||
|
frame = None
|
||||||
|
ifg_cnt = 0
|
||||||
|
deficit_idle_cnt = 0
|
||||||
|
self.active = False
|
||||||
|
self.bus.d <= 0
|
||||||
|
self.bus.c <= 0
|
||||||
|
continue
|
||||||
|
|
||||||
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
|
if self.enable is None or self.enable.value:
|
||||||
|
if ifg_cnt + deficit_idle_cnt > self.byte_width-1 or (not self.enable_dic and ifg_cnt > 4):
|
||||||
|
# in IFG
|
||||||
|
ifg_cnt = ifg_cnt - self.byte_width
|
||||||
|
if ifg_cnt < 0:
|
||||||
|
if self.enable_dic:
|
||||||
|
deficit_idle_cnt = max(deficit_idle_cnt+ifg_cnt, 0)
|
||||||
|
ifg_cnt = 0
|
||||||
|
|
||||||
|
elif frame is None:
|
||||||
|
# idle
|
||||||
|
if self.queue:
|
||||||
|
# send frame
|
||||||
|
frame = self.queue.popleft()
|
||||||
|
self.queue_occupancy_bytes -= len(frame)
|
||||||
|
self.queue_occupancy_frames -= 1
|
||||||
|
self.log.info(f"TX frame: {frame}")
|
||||||
|
frame.normalize()
|
||||||
|
assert frame.data[0] == EthPre.PRE
|
||||||
|
assert frame.ctrl[0] == 0
|
||||||
|
frame.data[0] = XgmiiCtrl.START
|
||||||
|
frame.ctrl[0] = 1
|
||||||
|
frame.data.append(XgmiiCtrl.TERM)
|
||||||
|
frame.ctrl.append(1)
|
||||||
|
|
||||||
|
# offset start
|
||||||
|
if (self.byte_width > 4 and ifg_cnt > (3-deficit_idle_cnt if self.enable_dic else 0)) or self.force_offset_start:
|
||||||
|
ifg_cnt = ifg_cnt-4
|
||||||
|
frame.data = bytearray([XgmiiCtrl.IDLE]*4)+frame.data
|
||||||
|
frame.ctrl = [1]*4+frame.ctrl
|
||||||
|
|
||||||
|
if self.enable_dic:
|
||||||
|
deficit_idle_cnt = max(deficit_idle_cnt+ifg_cnt, 0)
|
||||||
|
ifg_cnt = 0
|
||||||
|
self.active = True
|
||||||
|
else:
|
||||||
|
# clear counters
|
||||||
|
deficit_idle_cnt = 0
|
||||||
|
ifg_cnt = 0
|
||||||
|
|
||||||
|
if frame is not None:
|
||||||
|
d_val = 0
|
||||||
|
c_val = 0
|
||||||
|
|
||||||
|
for k in range(self.byte_width):
|
||||||
|
if frame is not None:
|
||||||
|
d_val |= frame.data.pop(0) << k*8
|
||||||
|
c_val |= frame.ctrl.pop(0) << k
|
||||||
|
|
||||||
|
if not frame.data:
|
||||||
|
ifg_cnt = max(self.ifg - (self.byte_width-k), 0)
|
||||||
|
frame = None
|
||||||
|
else:
|
||||||
|
d_val |= XgmiiCtrl.IDLE << k*8
|
||||||
|
c_val |= 1 << k
|
||||||
|
|
||||||
|
self.bus.d <= d_val
|
||||||
|
self.bus.c <= c_val
|
||||||
|
else:
|
||||||
|
self.bus.d <= self.idle_d
|
||||||
|
self.bus.c <= self.idle_c
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
|
||||||
|
class XgmiiSink(object):
|
||||||
|
|
||||||
|
_signals = ["d", "c"]
|
||||||
|
_optional_signals = []
|
||||||
|
|
||||||
|
def __init__(self, entity, name, clock, reset=None, enable=None, *args, **kwargs):
|
||||||
|
self.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
||||||
|
self.entity = entity
|
||||||
|
self.clock = clock
|
||||||
|
self.reset = reset
|
||||||
|
self.enable = enable
|
||||||
|
self.bus = Bus(self.entity, name, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.active = False
|
||||||
|
self.queue = deque()
|
||||||
|
self.sync = Event()
|
||||||
|
|
||||||
|
self.queue_occupancy_bytes = 0
|
||||||
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
|
self.width = len(self.bus.d)
|
||||||
|
self.byte_width = len(self.bus.c)
|
||||||
|
|
||||||
|
self.reset = reset
|
||||||
|
|
||||||
|
assert self.width == self.byte_width * 8
|
||||||
|
|
||||||
|
cocotb.fork(self._run())
|
||||||
|
|
||||||
|
def recv(self):
|
||||||
|
if self.queue:
|
||||||
|
frame = self.queue.popleft()
|
||||||
|
self.queue_occupancy_bytes -= len(frame)
|
||||||
|
self.queue_occupancy_frames -= 1
|
||||||
|
return frame
|
||||||
|
return None
|
||||||
|
|
||||||
|
def count(self):
|
||||||
|
return len(self.queue)
|
||||||
|
|
||||||
|
def empty(self):
|
||||||
|
return not self.queue
|
||||||
|
|
||||||
|
def idle(self):
|
||||||
|
return not self.active
|
||||||
|
|
||||||
|
async def wait(self, timeout=0, timeout_unit=None):
|
||||||
|
if not self.empty():
|
||||||
|
return
|
||||||
|
self.sync.clear()
|
||||||
|
if timeout:
|
||||||
|
await First(self.sync.wait(), Timer(timeout, timeout_unit))
|
||||||
|
else:
|
||||||
|
await self.sync.wait()
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
frame = None
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await ReadOnly()
|
||||||
|
|
||||||
|
if self.reset is not None and self.reset.value:
|
||||||
|
await RisingEdge(self.clock)
|
||||||
|
frame = None
|
||||||
|
self.active = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.enable is None or self.enable.value:
|
||||||
|
for offset in range(self.byte_width):
|
||||||
|
d_val = (self.bus.d.value.integer >> (offset*8)) & 0xff
|
||||||
|
c_val = (self.bus.c.value.integer >> offset) & 1
|
||||||
|
|
||||||
|
if frame is None:
|
||||||
|
if c_val and d_val == XgmiiCtrl.START:
|
||||||
|
# start
|
||||||
|
frame = XgmiiFrame(bytearray([EthPre.PRE]), [0])
|
||||||
|
frame.rx_sim_time = get_sim_time()
|
||||||
|
frame.rx_start_lane = offset
|
||||||
|
else:
|
||||||
|
if c_val:
|
||||||
|
# got a control character; terminate frame reception
|
||||||
|
if d_val != XgmiiCtrl.TERM:
|
||||||
|
# store control character if it's not a termination
|
||||||
|
frame.data.append(d_val)
|
||||||
|
frame.ctrl.append(c_val)
|
||||||
|
|
||||||
|
frame.compact()
|
||||||
|
self.log.info(f"RX frame: {frame}")
|
||||||
|
|
||||||
|
self.queue_occupancy_bytes += len(frame)
|
||||||
|
self.queue_occupancy_frames += 1
|
||||||
|
|
||||||
|
self.queue.append(frame)
|
||||||
|
self.sync.set()
|
||||||
|
|
||||||
|
frame = None
|
||||||
|
else:
|
||||||
|
frame.data.append(d_val)
|
||||||
|
frame.ctrl.append(c_val)
|
||||||
|
|
||||||
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
|
|
||||||
|
class XgmiiMonitor(XgmiiSink):
|
||||||
|
pass
|
||||||
|
|
||||||
32
setup.py
Normal file
32
setup.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from setuptools import setup, find_namespace_packages
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
version_py = os.path.join(os.path.dirname(__file__), 'cocotbext', 'eth', 'version.py')
|
||||||
|
with open(version_py, 'r') as f:
|
||||||
|
d = dict()
|
||||||
|
exec(f.read(), d)
|
||||||
|
version = d['__version__']
|
||||||
|
|
||||||
|
with open("README.md", "r") as f:
|
||||||
|
long_description = f.read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name = "cocotbext-eth",
|
||||||
|
author="Alex Forencich",
|
||||||
|
author_email="alex@alexforencich.com",
|
||||||
|
description="Ethernet modules for Cocotb",
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
|
url="https://github.com/alexforencich/cocotbext-eth",
|
||||||
|
download_url = 'http://github.com/alexforencich/cocotbext-eth/tarball/master',
|
||||||
|
version = version,
|
||||||
|
packages = find_namespace_packages(include=['cocotbext.*']),
|
||||||
|
install_requires = ['cocotb', 'cocotbext-axi'],
|
||||||
|
python_requires = '>=3.6',
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)"
|
||||||
|
]
|
||||||
|
)
|
||||||
30
tests/Makefile
Normal file
30
tests/Makefile
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Copyright (c) 2020 Alex Forencich
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
TOPTARGETS := all clean
|
||||||
|
|
||||||
|
SUBDIRS := $(wildcard */.)
|
||||||
|
|
||||||
|
$(TOPTARGETS): $(SUBDIRS)
|
||||||
|
$(SUBDIRS):
|
||||||
|
$(MAKE) -C $@ $(MAKECMDGOALS)
|
||||||
|
|
||||||
|
.PHONY: $(TOPTARGETS) $(SUBDIRS)
|
||||||
|
|
||||||
76
tests/xgmii/Makefile
Normal file
76
tests/xgmii/Makefile
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Copyright (c) 2020 Alex Forencich
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
TOPLEVEL_LANG = verilog
|
||||||
|
|
||||||
|
SIM ?= icarus
|
||||||
|
WAVES ?= 0
|
||||||
|
|
||||||
|
COCOTB_HDL_TIMEUNIT = 1ns
|
||||||
|
COCOTB_HDL_TIMEPRECISION = 1ns
|
||||||
|
|
||||||
|
DUT = test_xgmii
|
||||||
|
TOPLEVEL = $(DUT)
|
||||||
|
MODULE = $(DUT)
|
||||||
|
VERILOG_SOURCES += $(DUT).v
|
||||||
|
|
||||||
|
# module parameters
|
||||||
|
export PARAM_DATA_WIDTH ?= 64
|
||||||
|
export PARAM_CTRL_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
||||||
|
|
||||||
|
SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH)
|
||||||
|
|
||||||
|
ifeq ($(SIM), icarus)
|
||||||
|
PLUSARGS += -fst
|
||||||
|
|
||||||
|
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
|
||||||
|
COMPILE_ARGS += -P $(TOPLEVEL).CTRL_WIDTH=$(PARAM_CTRL_WIDTH)
|
||||||
|
|
||||||
|
ifeq ($(WAVES), 1)
|
||||||
|
VERILOG_SOURCES += iverilog_dump.v
|
||||||
|
COMPILE_ARGS += -s iverilog_dump
|
||||||
|
endif
|
||||||
|
else ifeq ($(SIM), verilator)
|
||||||
|
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
||||||
|
|
||||||
|
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH)
|
||||||
|
COMPILE_ARGS += -GCTRL_WIDTH=$(PARAM_CTRL_WIDTH)
|
||||||
|
|
||||||
|
ifeq ($(WAVES), 1)
|
||||||
|
COMPILE_ARGS += --trace
|
||||||
|
#COMPILE_ARGS += --trace-fst
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
iverilog_dump.v:
|
||||||
|
echo 'module iverilog_dump();' > $@
|
||||||
|
echo 'initial begin' >> $@
|
||||||
|
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||||
|
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||||
|
echo 'end' >> $@
|
||||||
|
echo 'endmodule' >> $@
|
||||||
|
|
||||||
|
clean::
|
||||||
|
@rm -rf sim_build_*
|
||||||
|
@rm -rf iverilog_dump.v
|
||||||
|
@rm -rf dump.fst $(TOPLEVEL).fst
|
||||||
|
|
||||||
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
0
tests/xgmii/__init__.py
Normal file
0
tests/xgmii/__init__.py
Normal file
217
tests/xgmii/test_xgmii.py
Normal file
217
tests/xgmii/test_xgmii.py
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import os
|
||||||
|
|
||||||
|
import cocotb_test.simulator
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.clock import Clock
|
||||||
|
from cocotb.triggers import RisingEdge
|
||||||
|
from cocotb.regression import TestFactory
|
||||||
|
|
||||||
|
from cocotbext.eth import XgmiiFrame, XgmiiSource, XgmiiSink
|
||||||
|
|
||||||
|
class TB(object):
|
||||||
|
def __init__(self, dut):
|
||||||
|
self.dut = dut
|
||||||
|
|
||||||
|
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
||||||
|
|
||||||
|
self.source = XgmiiSource(dut, "xgmii", dut.clk, dut.rst)
|
||||||
|
self.sink = XgmiiSink(dut, "xgmii", dut.clk, dut.rst)
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
self.dut.rst.setimmediatevalue(0)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
self.dut.rst <= 1
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
self.dut.rst <= 0
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
|
||||||
|
async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_dic=True, force_offset_start=False):
|
||||||
|
|
||||||
|
tb = TB(dut)
|
||||||
|
|
||||||
|
byte_width = tb.source.width // 8
|
||||||
|
|
||||||
|
tb.source.ifg = ifg
|
||||||
|
tb.source.enable_dic = enable_dic
|
||||||
|
tb.source.force_offset_start = force_offset_start
|
||||||
|
|
||||||
|
await tb.reset()
|
||||||
|
|
||||||
|
test_frames = [payload_data(l) for l in payload_lengths()]
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
test_frame = XgmiiFrame.from_payload(test_data)
|
||||||
|
tb.source.send(test_frame)
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
await tb.sink.wait()
|
||||||
|
rx_frame = tb.sink.recv()
|
||||||
|
|
||||||
|
assert rx_frame.get_payload() == test_data
|
||||||
|
assert rx_frame.ctrl is None
|
||||||
|
|
||||||
|
assert tb.sink.empty()
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
async def run_test_alignment(dut, payload_data=None, ifg=12, enable_dic=True, force_offset_start=False):
|
||||||
|
|
||||||
|
tb = TB(dut)
|
||||||
|
|
||||||
|
byte_width = tb.source.width // 8
|
||||||
|
|
||||||
|
tb.source.ifg = ifg
|
||||||
|
tb.source.enable_dic = enable_dic
|
||||||
|
tb.source.force_offset_start = force_offset_start
|
||||||
|
|
||||||
|
await tb.reset()
|
||||||
|
|
||||||
|
for length in range(64, 96):
|
||||||
|
|
||||||
|
test_frames = [payload_data(length) for k in range(10)]
|
||||||
|
start_lane = []
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
test_frame = XgmiiFrame.from_payload(test_data)
|
||||||
|
tb.source.send(test_frame)
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
await tb.sink.wait()
|
||||||
|
rx_frame = tb.sink.recv()
|
||||||
|
|
||||||
|
assert rx_frame.get_payload() == test_data
|
||||||
|
assert rx_frame.ctrl is None
|
||||||
|
|
||||||
|
start_lane.append(rx_frame.rx_start_lane)
|
||||||
|
|
||||||
|
print(length)
|
||||||
|
print(start_lane)
|
||||||
|
|
||||||
|
start_lane_ref = []
|
||||||
|
|
||||||
|
# compute expected starting lanes
|
||||||
|
lane = 0
|
||||||
|
deficit_idle_count = 0
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
if ifg == 0:
|
||||||
|
lane = 0
|
||||||
|
if force_offset_start and byte_width > 4:
|
||||||
|
lane = 4
|
||||||
|
|
||||||
|
start_lane_ref.append(lane)
|
||||||
|
lane = (lane + len(test_data)+ifg) % byte_width
|
||||||
|
|
||||||
|
if enable_dic:
|
||||||
|
offset = lane % 4
|
||||||
|
if deficit_idle_count+offset >= 4:
|
||||||
|
offset += 4
|
||||||
|
lane = (lane - offset) % byte_width
|
||||||
|
deficit_idle_count = (deficit_idle_count + offset) % 4
|
||||||
|
else:
|
||||||
|
offset = lane % 4
|
||||||
|
if offset > 0:
|
||||||
|
offset += 4
|
||||||
|
lane = (lane - offset) % byte_width
|
||||||
|
|
||||||
|
print(start_lane_ref)
|
||||||
|
|
||||||
|
assert start_lane_ref == start_lane
|
||||||
|
|
||||||
|
for k in range(10):
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
assert tb.sink.empty()
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
def size_list():
|
||||||
|
return list(range(64, 128))+[512, 1514, 9214]+[64]*10+[65]*10+[66]*10+[67]*10
|
||||||
|
|
||||||
|
def incrementing_payload(length):
|
||||||
|
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||||
|
|
||||||
|
if cocotb.SIM_NAME:
|
||||||
|
|
||||||
|
factory = TestFactory(run_test)
|
||||||
|
factory.add_option("payload_lengths", [size_list])
|
||||||
|
factory.add_option("payload_data", [incrementing_payload])
|
||||||
|
factory.add_option("ifg", [12, 0])
|
||||||
|
factory.add_option("enable_dic", [True, False])
|
||||||
|
factory.add_option("force_offset_start", [False, True])
|
||||||
|
factory.generate_tests()
|
||||||
|
|
||||||
|
factory = TestFactory(run_test_alignment)
|
||||||
|
factory.add_option("payload_data", [incrementing_payload])
|
||||||
|
factory.add_option("ifg", [12, 0])
|
||||||
|
factory.add_option("enable_dic", [True, False])
|
||||||
|
factory.add_option("force_offset_start", [False, True])
|
||||||
|
factory.generate_tests()
|
||||||
|
|
||||||
|
|
||||||
|
tests_dir = os.path.dirname(__file__)
|
||||||
|
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("data_width", [32, 64])
|
||||||
|
def test_xgmii(request, data_width):
|
||||||
|
dut = "test_xgmii"
|
||||||
|
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||||
|
toplevel = dut
|
||||||
|
|
||||||
|
verilog_sources = [
|
||||||
|
os.path.join(tests_dir, f"{dut}.v"),
|
||||||
|
]
|
||||||
|
|
||||||
|
parameters = {}
|
||||||
|
|
||||||
|
parameters['DATA_WIDTH'] = data_width
|
||||||
|
parameters['CTRL_WIDTH'] = parameters['DATA_WIDTH'] // 8
|
||||||
|
|
||||||
|
extra_env = {f'PARAM_{k}' : str(v) for k, v in parameters.items()}
|
||||||
|
|
||||||
|
sim_build = os.path.join(tests_dir,
|
||||||
|
"sim_build_"+request.node.name.replace('[', '-').replace(']', ''))
|
||||||
|
|
||||||
|
cocotb_test.simulator.run(
|
||||||
|
python_search=[tests_dir],
|
||||||
|
verilog_sources=verilog_sources,
|
||||||
|
toplevel=toplevel,
|
||||||
|
module=module,
|
||||||
|
parameters=parameters,
|
||||||
|
sim_build=sim_build,
|
||||||
|
extra_env=extra_env,
|
||||||
|
)
|
||||||
|
|
||||||
45
tests/xgmii/test_xgmii.v
Normal file
45
tests/xgmii/test_xgmii.v
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Language: Verilog 2001
|
||||||
|
|
||||||
|
`timescale 1ns / 1ns
|
||||||
|
|
||||||
|
/*
|
||||||
|
* XGMII test
|
||||||
|
*/
|
||||||
|
module test_xgmii #
|
||||||
|
(
|
||||||
|
parameter DATA_WIDTH = 8,
|
||||||
|
parameter CTRL_WIDTH = (DATA_WIDTH/8)
|
||||||
|
)
|
||||||
|
(
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
|
||||||
|
inout wire [DATA_WIDTH-1:0] xgmii_d,
|
||||||
|
inout wire [CTRL_WIDTH-1:0] xgmii_c
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
Reference in New Issue
Block a user