Skip to content
Open
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
Empty file added umi/cocotb/__init__.py
Empty file.
Empty file added umi/cocotb/drivers/__init__.py
Empty file.
83 changes: 83 additions & 0 deletions umi/cocotb/drivers/sumi_driver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from typing import Any

from cocotb.types import LogicArray
from cocotb.triggers import RisingEdge
from cocotb.handle import SimHandleBase

from cocotb_bus.drivers import ValidatedBusDriver

from umi.cocotb.sumi import SumiTransaction


class SumiDriver(ValidatedBusDriver):

_signals = [
"valid",
"cmd",
"dstaddr",
"srcaddr",
"data",
"ready"
]

_optional_signals = []

def __init__(
self,
entity: SimHandleBase,
name: str,
clock: SimHandleBase,
*,
config={},
**kwargs: Any
):
ValidatedBusDriver.__init__(self, entity, name, clock, **kwargs)

self.clock = clock
self.bus.valid.value = 0

async def _driver_send(self, transaction: SumiTransaction, sync: bool = True) -> None:
"""Implementation for BusDriver.
Args:
transaction: The transaction to send.
sync: Synchronize the transfer by waiting for a rising edge.
"""

clk_re = RisingEdge(self.clock)

if sync:
await clk_re

# Insert a gap where valid is low
if not self.on:
self.bus.valid.value = 0
for _ in range(self.off):
await clk_re

# Grab the next set of on/off values
self._next_valids()

# Consume a valid cycle
if self.on is not True and self.on:
self.on -= 1

def ready() -> bool:
return bool(self.bus.ready.value)

bus_size = len(self.bus.data)//8

while True:
self.bus.valid.value = 1
self.bus.cmd.value = int(transaction.cmd)
self.bus.data.value = LogicArray.from_bytes(
value=transaction.data + bytearray([0]*(bus_size - len(transaction.data))),
range=len(self.bus.data),
byteorder="little"
)
self.bus.dstaddr.value = int(transaction.da)
self.bus.srcaddr.value = int(transaction.sa)
await clk_re
if ready():
break

self.bus.valid.value = 0
Empty file added umi/cocotb/monitors/__init__.py
Empty file.
49 changes: 49 additions & 0 deletions umi/cocotb/monitors/sumi_monitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from cocotb.types import LogicArray
from cocotb.triggers import RisingEdge

from cocotb_bus.monitors import BusMonitor

from umi.cocotb.sumi import SumiCmd, SumiTransaction


class SumiMonitor(BusMonitor):

_signals = [
"valid",
"cmd",
"dstaddr",
"srcaddr",
"data",
"ready"
]
_optional_signals = []

def __init__(self, entity, name, clock, **kwargs):
BusMonitor.__init__(self, entity, name, clock, **kwargs)
self.addr_width = len(self.bus.dstaddr)

async def _monitor_recv(self):
clk_re = RisingEdge(self.clock)

def valid_handshake() -> bool:
return bool(self.bus.valid.value) and bool(self.bus.ready.value)

while True:
await clk_re

if self.in_reset:
continue

if valid_handshake():
sumi_cmd: SumiCmd = SumiCmd.from_int(int(self.bus.cmd.value))
data: LogicArray = self.bus.data.value
data = data[(((int(sumi_cmd.len)+1) << (int(sumi_cmd.size)))*8)-1:0]
self._recv(SumiTransaction(
cmd=sumi_cmd,
da=int(self.bus.dstaddr.value) if self.bus.dstaddr.value.is_resolvable
else None,
sa=int(self.bus.srcaddr.value) if self.bus.srcaddr.value.is_resolvable
else None,
data=data.to_bytes(byteorder="little") if data.is_resolvable else None,
addr_width=self.addr_width
))
120 changes: 120 additions & 0 deletions umi/cocotb/sumi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from enum import IntEnum
from typing import Optional
import dataclasses
import copy

from umi.cocotb.utils.bit_utils import BitField, BitVector
from umi.cocotb.utils.vrd_transaction import VRDTransaction


class SumiCmdType(IntEnum):
# Invalid transaction indicator (cmd[7:0])
UMI_INVALID = 0x00

# Requests (host -> device) (cmd[7:0])
UMI_REQ_READ = 0x01 # read/load
UMI_REQ_WRITE = 0x03 # write/store with ack
UMI_REQ_POSTED = 0x05 # posted write
UMI_REQ_RDMA = 0x07 # remote DMA command
UMI_REQ_ATOMIC = 0x09 # alias for all atomics
UMI_REQ_USER0 = 0x0B # reserved for user
UMI_REQ_FUTURE0 = 0x0D # reserved fur future use
UMI_REQ_ERROR = 0x0F # reserved for error message
UMI_REQ_LINK = 0x2F # reserved for link ctrl

# Response (device -> host) (cmd[7:0])
UMI_RESP_READ = 0x02 # response to read request
UMI_RESP_WRITE = 0x04 # response (ack) from write request
UMI_RESP_USER0 = 0x06 # signal write without ack
UMI_RESP_USER1 = 0x08 # reserved for user
UMI_RESP_FUTURE0 = 0x0A # reserved for future use
UMI_RESP_FUTURE1 = 0x0C # reserved for future use
UMI_RESP_LINK = 0x0E # reserved for link ctrl

@classmethod
def supports_streaming(cls, value):
return value in [
SumiCmdType.UMI_REQ_WRITE,
SumiCmdType.UMI_REQ_POSTED,
SumiCmdType.UMI_RESP_READ
]


@dataclasses.dataclass
class SumiCmd(BitVector):

cmd_type: BitField = dataclasses.field(
default_factory=lambda: BitField(value=0, width=5, offset=0)
)
size: BitField = dataclasses.field(default_factory=lambda: BitField(value=0, width=3, offset=5))
len: BitField = dataclasses.field(default_factory=lambda: BitField(value=0, width=8, offset=8))
eom: BitField = dataclasses.field(default_factory=lambda: BitField(value=0, width=1, offset=22))

def __repr__(self):
return f"Sumi CMD ({super().__repr__()})"


class SumiTransaction:

def __init__(
self,
cmd: SumiCmd,
da: Optional[int],
sa: Optional[int],
data: Optional[bytes],
addr_width: int = 64
):
self.cmd = copy.deepcopy(cmd)
self.da = BitField(value=da, width=addr_width, offset=0)
self.sa = BitField(value=sa, width=addr_width, offset=0)
self.data = data
self._addr_width = addr_width

def header_to_bytes(self) -> bytes:
return (bytes(self.cmd)
+ int.to_bytes(int(self.da), length=self._addr_width//8, byteorder='little')
+ int.to_bytes(int(self.sa), length=self._addr_width//8, byteorder='little'))

def to_lumi(self, lumi_size, inc_header=True, override_last=None):
raw = self.data[:(int(self.cmd.len)+1 << int(self.cmd.size))]
if inc_header:
raw = self.header_to_bytes() + raw
# Break raw into LUMI bus sized chunks
chunks = [raw[i:i+lumi_size] for i in range(0, len(raw), lumi_size)]
# Zero pad last chunk
chunks[-1] = chunks[-1] + bytes([0] * (lumi_size - len(chunks[-1])))
vrd_transactions = []
for i, chunk in enumerate(chunks):
# Set last true for the last chunk
last = (i == len(chunks)-1)
# Allow user to override last (useful for simulating streaming mode)
if last and (override_last is not None):
last = override_last
# Convert data to a valid ready transaction type
vrd_transactions.append(VRDTransaction(
data=chunk,
last=last
))
return vrd_transactions

def trunc_and_pad_zeros(self):
data_len = ((int(self.cmd.len)+1) << int(self.cmd.size))
self.data = bytes([0] * (len(self.data) - data_len)) + self.data[:data_len]

def __eq__(self, other):
if isinstance(other, SumiTransaction):
# For all command types CMD's must match
if int(self.cmd) == int(other.cmd):
# For RESP_WRITE only compare header fields DA
if int(self.cmd.cmd_type) == SumiCmdType.UMI_RESP_WRITE:
return int(self.da) == int(other.da)
else:
my_pkt = self.header_to_bytes() + self.data
other_pkt = other.header_to_bytes() + other.data
return my_pkt == other_pkt
return False
else:
return False

def __repr__(self):
return f"header = {self.header_to_bytes().hex()} data = {self.data.hex()} {self.cmd}"
55 changes: 55 additions & 0 deletions umi/cocotb/tumi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from typing import List
from umi.cocotb.sumi import SumiCmd, SumiTransaction


class TumiTransaction:

def __init__(
self,
cmd: SumiCmd,
da: int,
sa: int,
data: bytes
):
self._cmd = cmd
self._data = data
self._da = da
self._sa = sa

def to_sumi(self, data_bus_size: int, addr_width: int = 64) -> List[SumiTransaction]:
sa = self._sa
da = self._da

sumi_size = 0

data_grouped = [
self._data[i:i+data_bus_size]
for i in range(0, len(self._data), data_bus_size)
]

rtn = []
for idx, grouping in enumerate(data_grouped):
group_len = len(grouping)

for size in reversed(range(0, (1 << 3)-1)):
if group_len % (2**size) == 0:
sumi_size = size
break

group_len = int(group_len / (2**sumi_size))

self._cmd.size.from_int(sumi_size)
self._cmd.len.from_int(group_len-1)
self._cmd.eom.from_int(1 if idx == len(data_grouped)-1 else 0)

trans = SumiTransaction(
cmd=self._cmd,
da=da,
sa=sa,
data=grouping,
addr_width=addr_width
)
rtn.append(trans)
da += (int(self._cmd.len) + 1) << int(self._cmd.size)
sa += (int(self._cmd.len) + 1) << int(self._cmd.size)
return rtn
Empty file added umi/cocotb/utils/__init__.py
Empty file.
Loading