cocotbext-umi 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ # Version number
2
+ __version__ = "0.0.2"
3
+
4
+ # Sub-modules
5
+ from . import sumi, tumi
6
+
7
+ __all__ = ["sumi", "tumi"]
File without changes
@@ -0,0 +1,92 @@
1
+ from typing import Any
2
+
3
+ from cocotb.types import LogicArray
4
+ from cocotb.triggers import RisingEdge
5
+ from cocotb.handle import SimHandleBase
6
+
7
+ from cocotb_bus.drivers import ValidatedBusDriver
8
+
9
+ from cocotbext.umi.sumi import SumiTransaction
10
+
11
+
12
+ class SumiDriver(ValidatedBusDriver):
13
+
14
+ _signals = [
15
+ "valid",
16
+ "cmd",
17
+ "dstaddr",
18
+ "srcaddr",
19
+ "data",
20
+ "ready"
21
+ ]
22
+
23
+ _optional_signals = []
24
+
25
+ def __init__(
26
+ self,
27
+ entity: SimHandleBase,
28
+ name: str,
29
+ clock: SimHandleBase,
30
+ *,
31
+ config={},
32
+ **kwargs: Any
33
+ ):
34
+ ValidatedBusDriver.__init__(self, entity, name, clock, **kwargs)
35
+
36
+ self.clock = clock
37
+ self.bus.valid.value = 0
38
+
39
+ self.addr_width = len(self.bus.dstaddr)
40
+ self.data_width = len(self.bus.data)
41
+
42
+ def get_bus_width(self) -> int:
43
+ return self.data_width
44
+
45
+ def get_addr_width(self) -> int:
46
+ return self.addr_width
47
+
48
+ async def _driver_send(self, transaction: SumiTransaction, sync: bool = True) -> None:
49
+ """Implementation for BusDriver.
50
+ Args:
51
+ transaction: The transaction to send.
52
+ sync: Synchronize the transfer by waiting for a rising edge.
53
+ """
54
+
55
+ clk_re = RisingEdge(self.clock)
56
+
57
+ if sync:
58
+ await clk_re
59
+
60
+ # Insert a gap where valid is low
61
+ if not self.on:
62
+ self.bus.valid.value = 0
63
+ for _ in range(self.off):
64
+ await clk_re
65
+
66
+ # Grab the next set of on/off values
67
+ self._next_valids()
68
+
69
+ # Consume a valid cycle
70
+ if self.on is not True and self.on:
71
+ self.on -= 1
72
+
73
+ def ready() -> bool:
74
+ return bool(self.bus.ready.value)
75
+
76
+ bus_size = len(self.bus.data)//8
77
+
78
+ while True:
79
+ self.bus.valid.value = 1
80
+ self.bus.cmd.value = int(transaction.cmd)
81
+ self.bus.data.value = LogicArray.from_bytes(
82
+ value=transaction.data + bytearray([0]*(bus_size - len(transaction.data))),
83
+ range=len(self.bus.data),
84
+ byteorder="little"
85
+ )
86
+ self.bus.dstaddr.value = int(transaction.da)
87
+ self.bus.srcaddr.value = int(transaction.sa)
88
+ await clk_re
89
+ if ready():
90
+ break
91
+
92
+ self.bus.valid.value = 0
File without changes
@@ -0,0 +1,123 @@
1
+ from cocotbext.umi.sumi import SumiCmd, SumiCmdType, SumiTransaction
2
+ from cocotbext.umi.tumi import TumiTransaction
3
+ from cocotbext.umi.drivers.sumi_driver import SumiDriver
4
+ from cocotbext.umi.monitors.sumi_monitor import SumiMonitor
5
+
6
+
7
+ class UmiMemoryDevice:
8
+ """
9
+ Virtual memory device that responds to UMI read/write requests.
10
+
11
+ Uses a SumiMonitor to receive requests and a SumiDriver to send responses.
12
+ """
13
+
14
+ def __init__(
15
+ self,
16
+ monitor: SumiMonitor,
17
+ driver: SumiDriver,
18
+ log=None
19
+ ):
20
+ self.monitor = monitor
21
+ self.driver = driver
22
+ self.log = log
23
+ self.memory: dict[int, int] = {}
24
+
25
+ self.dw = self.driver.get_bus_width()
26
+ self.aw = self.driver.get_addr_width()
27
+
28
+ self.monitor.add_callback(self._on_transaction)
29
+
30
+ def _on_transaction(self, transaction: SumiTransaction):
31
+ """Callback invoked when the monitor receives a transaction."""
32
+ cmd_type = int(transaction.cmd.cmd_type)
33
+
34
+ if cmd_type == SumiCmdType.UMI_REQ_WRITE:
35
+ self._handle_write(transaction, send_response=True)
36
+ elif cmd_type == SumiCmdType.UMI_REQ_POSTED:
37
+ self._handle_write(transaction, send_response=False)
38
+ elif cmd_type == SumiCmdType.UMI_REQ_READ:
39
+ self._handle_read(transaction)
40
+ else:
41
+ if self.log:
42
+ self.log.warning(f"Unhandled UMI command type: 0x{cmd_type:02x}")
43
+
44
+ def _handle_write(self, transaction: SumiTransaction, send_response: bool = True):
45
+ """Handle a write request by storing data and optionally sending response."""
46
+ dstaddr = int(transaction.da)
47
+ data = transaction.data
48
+ size = int(transaction.cmd.size)
49
+ length = int(transaction.cmd.len)
50
+ data_size = (length + 1) << size
51
+
52
+ if self.log:
53
+ self.log.info(
54
+ f"MEM WRITE: addr=0x{dstaddr:08x} size={data_size} "
55
+ f"data={data[:data_size].hex()}"
56
+ )
57
+
58
+ for i in range(data_size):
59
+ self.memory[dstaddr + i] = data[i]
60
+
61
+ if send_response:
62
+ resp_cmd = SumiCmd.from_fields(
63
+ cmd_type=SumiCmdType.UMI_RESP_WRITE,
64
+ size=0,
65
+ len=0,
66
+ eom=1
67
+ )
68
+ resp = SumiTransaction(
69
+ cmd=resp_cmd,
70
+ da=int(transaction.sa),
71
+ sa=int(transaction.da),
72
+ data=bytes([0]),
73
+ addr_width=transaction._addr_width
74
+ )
75
+ self.driver.append(resp)
76
+
77
+ def _handle_read(self, transaction: SumiTransaction):
78
+ """Handle a read request by returning data from memory."""
79
+ srcaddr = int(transaction.da)
80
+ size = int(transaction.cmd.size)
81
+ length = int(transaction.cmd.len)
82
+ data_size = (length + 1) << size
83
+
84
+ data = bytes(self.memory.get(srcaddr + i, 0) for i in range(data_size))
85
+
86
+ if self.log:
87
+ self.log.info(
88
+ f"MEM READ: addr=0x{srcaddr:08x} size={data_size} "
89
+ f"data={data.hex()}"
90
+ )
91
+
92
+ resp_cmd = SumiCmd.from_fields(
93
+ cmd_type=SumiCmdType.UMI_RESP_READ,
94
+ size=size,
95
+ len=length,
96
+ eom=1
97
+ )
98
+
99
+ tumi_trans = TumiTransaction(
100
+ cmd=resp_cmd,
101
+ da=int(transaction.sa),
102
+ sa=int(transaction.da),
103
+ data=data
104
+ )
105
+ for sumi_trans in tumi_trans.to_sumi(data_bus_size=self.dw//8, addr_width=self.aw):
106
+ self.driver.append(sumi_trans)
107
+
108
+ def read(self, address: int, length: int = 1) -> bytes:
109
+ """Read bytes from virtual memory directly."""
110
+ return bytes(self.memory.get(address + i, 0) for i in range(length))
111
+
112
+ def write(self, address: int, data: bytes):
113
+ """Write bytes to virtual memory directly (for test setup)."""
114
+ for i, byte in enumerate(data):
115
+ self.memory[address + i] = byte
116
+
117
+ def dump_memory(self) -> list[tuple[int, int]]:
118
+ """Return a sorted list of (address, value) tuples."""
119
+ return sorted(self.memory.items())
120
+
121
+ def clear(self):
122
+ """Clear all memory contents."""
123
+ self.memory.clear()
File without changes
@@ -0,0 +1,68 @@
1
+ from cocotb.types import LogicArray
2
+ from cocotb.triggers import RisingEdge
3
+
4
+ from cocotb_bus.monitors import BusMonitor
5
+
6
+ from cocotbext.umi.sumi import SumiCmd, SumiTransaction
7
+
8
+
9
+ class SumiMonitor(BusMonitor):
10
+
11
+ _signals = [
12
+ "valid",
13
+ "cmd",
14
+ "dstaddr",
15
+ "srcaddr",
16
+ "data",
17
+ "ready"
18
+ ]
19
+ _optional_signals = []
20
+
21
+ def __init__(self, entity, name, clock, **kwargs):
22
+ BusMonitor.__init__(self, entity, name, clock, **kwargs)
23
+ self.addr_width = len(self.bus.dstaddr)
24
+ self.data_width = len(self.bus.data)
25
+
26
+ def get_bus_width(self) -> int:
27
+ return self.data_width
28
+
29
+ def get_addr_width(self) -> int:
30
+ return self.addr_width
31
+
32
+ async def _monitor_recv(self):
33
+ clk_re = RisingEdge(self.clock)
34
+
35
+ def valid_handshake() -> bool:
36
+ return bool(self.bus.valid.value) and bool(self.bus.ready.value)
37
+
38
+ while True:
39
+ await clk_re
40
+
41
+ if self.in_reset:
42
+ continue
43
+
44
+ if valid_handshake():
45
+ sumi_cmd: SumiCmd = SumiCmd.from_int(int(self.bus.cmd.value))
46
+
47
+ # Only slice data if the command type carries data
48
+ if sumi_cmd.has_data():
49
+ data: LogicArray = self.bus.data.value
50
+ # Limit slice to actual data size, but don't exceed bus width
51
+ data_bits = min(
52
+ (((int(sumi_cmd.len) + 1) << int(sumi_cmd.size)) * 8),
53
+ self.data_width
54
+ )
55
+ data = data[data_bits - 1:0]
56
+ data_bytes = data.to_bytes(byteorder="little") if data.is_resolvable else None
57
+ else:
58
+ data_bytes = None
59
+
60
+ self._recv(SumiTransaction(
61
+ cmd=sumi_cmd,
62
+ da=int(self.bus.dstaddr.value) if self.bus.dstaddr.value.is_resolvable
63
+ else None,
64
+ sa=int(self.bus.srcaddr.value) if self.bus.srcaddr.value.is_resolvable
65
+ else None,
66
+ data=data_bytes,
67
+ addr_width=self.addr_width
68
+ ))
cocotbext/umi/sumi.py ADDED
@@ -0,0 +1,335 @@
1
+ from enum import IntEnum
2
+ from typing import Optional
3
+ import dataclasses
4
+ import copy
5
+
6
+ from cocotbext.umi.utils.bit_utils import BitField, BitVector
7
+ from cocotbext.umi.utils.vrd_transaction import VRDTransaction
8
+
9
+
10
+ class SumiCmdType(IntEnum):
11
+ """UMI Command Opcodes (CMD[4:0])"""
12
+ # Invalid transaction indicator
13
+ UMI_INVALID = 0x00
14
+
15
+ # Requests (host -> device)
16
+ UMI_REQ_READ = 0x01 # read/load
17
+ UMI_REQ_WRITE = 0x03 # write/store with ack
18
+ UMI_REQ_POSTED = 0x05 # posted write (no response)
19
+ UMI_REQ_RDMA = 0x07 # remote DMA command
20
+ UMI_REQ_ATOMIC = 0x09 # atomic read-modify-write
21
+ UMI_REQ_USER0 = 0x0B # reserved for user
22
+ UMI_REQ_FUTURE0 = 0x0D # reserved for future use
23
+ UMI_REQ_ERROR = 0x0F # error message (SIZE=0x0)
24
+ UMI_REQ_LINK = 0x2F # link ctrl (SIZE=0x1)
25
+
26
+ # Responses (device -> host)
27
+ UMI_RESP_READ = 0x02 # response to read request (with data)
28
+ UMI_RESP_WRITE = 0x04 # response (ack) from write request
29
+ UMI_RESP_USER0 = 0x06 # reserved for user (no data)
30
+ UMI_RESP_USER1 = 0x08 # reserved for user (with data)
31
+ UMI_RESP_FUTURE0 = 0x0A # reserved for future use (no data)
32
+ UMI_RESP_FUTURE1 = 0x0C # reserved for future use (with data)
33
+ UMI_RESP_LINK = 0x0E # link ctrl response (SIZE=0x0)
34
+
35
+ @classmethod
36
+ def supports_streaming(cls, value):
37
+ """Check if the command type supports streaming (multiple data transfers)."""
38
+ return value in [
39
+ SumiCmdType.UMI_REQ_WRITE,
40
+ SumiCmdType.UMI_REQ_POSTED,
41
+ SumiCmdType.UMI_RESP_READ
42
+ ]
43
+
44
+ @classmethod
45
+ def is_request(cls, value):
46
+ """Check if the command is a request (host -> device)."""
47
+ return value in [
48
+ SumiCmdType.UMI_REQ_READ,
49
+ SumiCmdType.UMI_REQ_WRITE,
50
+ SumiCmdType.UMI_REQ_POSTED,
51
+ SumiCmdType.UMI_REQ_RDMA,
52
+ SumiCmdType.UMI_REQ_ATOMIC,
53
+ SumiCmdType.UMI_REQ_USER0,
54
+ SumiCmdType.UMI_REQ_FUTURE0,
55
+ SumiCmdType.UMI_REQ_ERROR,
56
+ SumiCmdType.UMI_REQ_LINK,
57
+ ]
58
+
59
+ @classmethod
60
+ def is_response(cls, value):
61
+ """Check if the command is a response (device -> host)."""
62
+ return value in [
63
+ SumiCmdType.UMI_RESP_READ,
64
+ SumiCmdType.UMI_RESP_WRITE,
65
+ SumiCmdType.UMI_RESP_USER0,
66
+ SumiCmdType.UMI_RESP_USER1,
67
+ SumiCmdType.UMI_RESP_FUTURE0,
68
+ SumiCmdType.UMI_RESP_FUTURE1,
69
+ SumiCmdType.UMI_RESP_LINK,
70
+ ]
71
+
72
+ @classmethod
73
+ def has_data(cls, value):
74
+ """Check if the command type carries data."""
75
+ return value in [
76
+ SumiCmdType.UMI_REQ_WRITE,
77
+ SumiCmdType.UMI_REQ_POSTED,
78
+ SumiCmdType.UMI_REQ_ATOMIC,
79
+ SumiCmdType.UMI_REQ_USER0,
80
+ SumiCmdType.UMI_REQ_FUTURE0,
81
+ SumiCmdType.UMI_RESP_READ,
82
+ SumiCmdType.UMI_RESP_USER1,
83
+ SumiCmdType.UMI_RESP_FUTURE1,
84
+ ]
85
+
86
+ @classmethod
87
+ def has_source_addr(cls, value):
88
+ """Check if the command type includes source address (SA)."""
89
+ return value in [
90
+ SumiCmdType.UMI_REQ_READ,
91
+ SumiCmdType.UMI_REQ_WRITE,
92
+ SumiCmdType.UMI_REQ_POSTED,
93
+ SumiCmdType.UMI_REQ_RDMA,
94
+ SumiCmdType.UMI_REQ_ATOMIC,
95
+ SumiCmdType.UMI_REQ_USER0,
96
+ SumiCmdType.UMI_REQ_FUTURE0,
97
+ SumiCmdType.UMI_REQ_ERROR,
98
+ ]
99
+
100
+
101
+ class SumiAtomicType(IntEnum):
102
+ """Atomic Transaction Types (ATYPE[7:0]) - used in LEN field for REQ_ATOMIC"""
103
+ UMI_ATOMIC_ADD = 0x00
104
+ UMI_ATOMIC_AND = 0x01
105
+ UMI_ATOMIC_OR = 0x02
106
+ UMI_ATOMIC_XOR = 0x03
107
+ UMI_ATOMIC_MAX = 0x04
108
+ UMI_ATOMIC_MIN = 0x05
109
+ UMI_ATOMIC_MAXU = 0x06
110
+ UMI_ATOMIC_MINU = 0x07
111
+ UMI_ATOMIC_SWAP = 0x08
112
+
113
+
114
+ class SumiErrorCode(IntEnum):
115
+ """Error Codes (ERR[1:0]) - used in U field for responses"""
116
+ UMI_ERR_OK = 0b00 # OK (no error)
117
+ UMI_ERR_EXOK = 0b01 # Successful exclusive access
118
+ UMI_ERR_DEVERR = 0b10 # Device error
119
+ UMI_ERR_NETERR = 0b11 # Network error
120
+
121
+
122
+ class SumiProtMode(IntEnum):
123
+ """Protection Mode (PROT[1:0])"""
124
+ UMI_PROT_UNPRIVILEGED_SECURE = 0b00
125
+ UMI_PROT_PRIVILEGED_SECURE = 0b01
126
+ UMI_PROT_UNPRIVILEGED_NONSECURE = 0b10
127
+ UMI_PROT_PRIVILEGED_NONSECURE = 0b11
128
+
129
+
130
+ class SumiSize(IntEnum):
131
+ """Transaction Word Size (SIZE[2:0]) - bytes per word = 2^SIZE"""
132
+ UMI_SIZE_1 = 0b000 # 1 byte
133
+ UMI_SIZE_2 = 0b001 # 2 bytes
134
+ UMI_SIZE_4 = 0b010 # 4 bytes
135
+ UMI_SIZE_8 = 0b011 # 8 bytes
136
+ UMI_SIZE_16 = 0b100 # 16 bytes
137
+ UMI_SIZE_32 = 0b101 # 32 bytes
138
+ UMI_SIZE_64 = 0b110 # 64 bytes
139
+ UMI_SIZE_128 = 0b111 # 128 bytes
140
+
141
+ def bytes_per_word(self) -> int:
142
+ """Return the number of bytes per word for this SIZE value."""
143
+ return 1 << self.value
144
+
145
+
146
+ @dataclasses.dataclass
147
+ class SumiCmd(BitVector):
148
+ """
149
+ UMI Command Header (32 bits)
150
+
151
+ Bit layout:
152
+ [4:0] - opcode (cmd_type): Command opcode
153
+ [7:5] - size: Word size (bytes = 2^SIZE)
154
+ [15:8] - len: Word transfers per message (transfers = LEN+1)
155
+ For REQ_ATOMIC, this field is ATYPE (atomic operation type)
156
+ [19:16] - qos: Quality of service
157
+ [21:20] - prot: Protection mode
158
+ [22] - eom: End of message indicator
159
+ [23] - eof: End of frame indicator
160
+ [24] - ex: Exclusive access indicator
161
+ [26:25] - u: User bits (for requests) or ERR (for responses)
162
+ [31:27] - hostid: Host ID
163
+ """
164
+
165
+ # Bits [4:0] - Command opcode
166
+ cmd_type: BitField = dataclasses.field(
167
+ default_factory=lambda: BitField(value=0, width=5, offset=0)
168
+ )
169
+
170
+ # Bits [7:5] - Word size (bytes per word = 2^SIZE)
171
+ size: BitField = dataclasses.field(
172
+ default_factory=lambda: BitField(value=0, width=3, offset=5)
173
+ )
174
+
175
+ # Bits [15:8] - Transfer count (LEN+1 words) or ATYPE for atomics
176
+ len: BitField = dataclasses.field(
177
+ default_factory=lambda: BitField(value=0, width=8, offset=8)
178
+ )
179
+
180
+ # Bits [19:16] - Quality of service
181
+ qos: BitField = dataclasses.field(
182
+ default_factory=lambda: BitField(value=0, width=4, offset=16)
183
+ )
184
+
185
+ # Bits [21:20] - Protection mode
186
+ prot: BitField = dataclasses.field(
187
+ default_factory=lambda: BitField(value=0, width=2, offset=20)
188
+ )
189
+
190
+ # Bit [22] - End of message
191
+ eom: BitField = dataclasses.field(
192
+ default_factory=lambda: BitField(value=0, width=1, offset=22)
193
+ )
194
+
195
+ # Bit [23] - End of frame
196
+ eof: BitField = dataclasses.field(
197
+ default_factory=lambda: BitField(value=0, width=1, offset=23)
198
+ )
199
+
200
+ # Bit [24] - Exclusive access
201
+ ex: BitField = dataclasses.field(
202
+ default_factory=lambda: BitField(value=0, width=1, offset=24)
203
+ )
204
+
205
+ # Bits [26:25] - User bits (requests) or error code (responses)
206
+ u: BitField = dataclasses.field(
207
+ default_factory=lambda: BitField(value=0, width=2, offset=25)
208
+ )
209
+
210
+ # Bits [31:27] - Host ID
211
+ hostid: BitField = dataclasses.field(
212
+ default_factory=lambda: BitField(value=0, width=5, offset=27)
213
+ )
214
+
215
+ @property
216
+ def opcode(self) -> int:
217
+ """Alias for cmd_type field."""
218
+ return int(self.cmd_type)
219
+
220
+ @property
221
+ def atype(self) -> int:
222
+ """Get ATYPE field (alias for len, used in atomic operations)."""
223
+ return int(self.len)
224
+
225
+ @atype.setter
226
+ def atype(self, value: int):
227
+ """Set ATYPE field (alias for len, used in atomic operations)."""
228
+ self.len.from_int(value)
229
+
230
+ @property
231
+ def err(self) -> int:
232
+ """Get ERR field (alias for u, used in responses)."""
233
+ return int(self.u)
234
+
235
+ @err.setter
236
+ def err(self, value: int):
237
+ """Set ERR field (alias for u, used in responses)."""
238
+ self.u.from_int(value)
239
+
240
+ def bytes_per_word(self) -> int:
241
+ """Return the number of bytes per word based on SIZE field."""
242
+ return 1 << int(self.size)
243
+
244
+ def transfer_count(self) -> int:
245
+ """Return the number of word transfers (LEN+1)."""
246
+ return int(self.len) + 1
247
+
248
+ def total_bytes(self) -> int:
249
+ """Return the total number of bytes in the transaction."""
250
+ return self.bytes_per_word() * self.transfer_count()
251
+
252
+ def is_request(self) -> bool:
253
+ """Check if this command is a request."""
254
+ return SumiCmdType.is_request(int(self.cmd_type))
255
+
256
+ def is_response(self) -> bool:
257
+ """Check if this command is a response."""
258
+ return SumiCmdType.is_response(int(self.cmd_type))
259
+
260
+ def has_data(self) -> bool:
261
+ """Check if this command carries data."""
262
+ return SumiCmdType.has_data(int(self.cmd_type))
263
+
264
+ def has_source_addr(self) -> bool:
265
+ """Check if this command includes source address."""
266
+ return SumiCmdType.has_source_addr(int(self.cmd_type))
267
+
268
+ def __repr__(self):
269
+ return f"SumiCmd({super().__repr__()})"
270
+
271
+
272
+ class SumiTransaction:
273
+
274
+ def __init__(
275
+ self,
276
+ cmd: SumiCmd,
277
+ da: Optional[int],
278
+ sa: Optional[int],
279
+ data: Optional[bytes],
280
+ addr_width: int = 64
281
+ ):
282
+ self.cmd = copy.deepcopy(cmd)
283
+ self.da = BitField(value=da, width=addr_width, offset=0)
284
+ self.sa = BitField(value=sa, width=addr_width, offset=0)
285
+ self.data = data
286
+ self._addr_width = addr_width
287
+
288
+ def header_to_bytes(self) -> bytes:
289
+ return (bytes(self.cmd)
290
+ + int.to_bytes(int(self.da), length=self._addr_width//8, byteorder='little')
291
+ + int.to_bytes(int(self.sa), length=self._addr_width//8, byteorder='little'))
292
+
293
+ def to_lumi(self, lumi_size, inc_header=True, override_last=None):
294
+ raw = self.data[:(int(self.cmd.len)+1 << int(self.cmd.size))]
295
+ if inc_header:
296
+ raw = self.header_to_bytes() + raw
297
+ # Break raw into LUMI bus sized chunks
298
+ chunks = [raw[i:i+lumi_size] for i in range(0, len(raw), lumi_size)]
299
+ # Zero pad last chunk
300
+ chunks[-1] = chunks[-1] + bytes([0] * (lumi_size - len(chunks[-1])))
301
+ vrd_transactions = []
302
+ for i, chunk in enumerate(chunks):
303
+ # Set last true for the last chunk
304
+ last = (i == len(chunks)-1)
305
+ # Allow user to override last (useful for simulating streaming mode)
306
+ if last and (override_last is not None):
307
+ last = override_last
308
+ # Convert data to a valid ready transaction type
309
+ vrd_transactions.append(VRDTransaction(
310
+ data=chunk,
311
+ last=last
312
+ ))
313
+ return vrd_transactions
314
+
315
+ def trunc_and_pad_zeros(self):
316
+ data_len = ((int(self.cmd.len)+1) << int(self.cmd.size))
317
+ self.data = bytes([0] * (len(self.data) - data_len)) + self.data[:data_len]
318
+
319
+ def __eq__(self, other):
320
+ if isinstance(other, SumiTransaction):
321
+ # For all command types CMD's must match
322
+ if int(self.cmd) == int(other.cmd):
323
+ # For RESP_WRITE only compare header fields DA
324
+ if int(self.cmd.cmd_type) == SumiCmdType.UMI_RESP_WRITE:
325
+ return int(self.da) == int(other.da)
326
+ else:
327
+ my_pkt = self.header_to_bytes() + self.data
328
+ other_pkt = other.header_to_bytes() + other.data
329
+ return my_pkt == other_pkt
330
+ return False
331
+ else:
332
+ return False
333
+
334
+ def __repr__(self):
335
+ return f"header = {self.header_to_bytes().hex()} data = {self.data.hex()} {self.cmd}"
cocotbext/umi/tumi.py ADDED
@@ -0,0 +1,55 @@
1
+ from typing import List
2
+ from cocotbext.umi.sumi import SumiCmd, SumiTransaction
3
+
4
+
5
+ class TumiTransaction:
6
+
7
+ def __init__(
8
+ self,
9
+ cmd: SumiCmd,
10
+ da: int,
11
+ sa: int,
12
+ data: bytes
13
+ ):
14
+ self._cmd = cmd
15
+ self._data = data
16
+ self._da = da
17
+ self._sa = sa
18
+
19
+ def to_sumi(self, data_bus_size: int, addr_width: int = 64) -> List[SumiTransaction]:
20
+ sa = self._sa
21
+ da = self._da
22
+
23
+ sumi_size = 0
24
+
25
+ data_grouped = [
26
+ self._data[i:i+data_bus_size]
27
+ for i in range(0, len(self._data), data_bus_size)
28
+ ]
29
+
30
+ rtn = []
31
+ for idx, grouping in enumerate(data_grouped):
32
+ group_len = len(grouping)
33
+
34
+ for size in reversed(range(0, (1 << 3)-1)):
35
+ if group_len % (2**size) == 0:
36
+ sumi_size = size
37
+ break
38
+
39
+ group_len = int(group_len / (2**sumi_size))
40
+
41
+ self._cmd.size.from_int(sumi_size)
42
+ self._cmd.len.from_int(group_len-1)
43
+ self._cmd.eom.from_int(1 if idx == len(data_grouped)-1 else 0)
44
+
45
+ trans = SumiTransaction(
46
+ cmd=self._cmd,
47
+ da=da,
48
+ sa=sa,
49
+ data=grouping,
50
+ addr_width=addr_width
51
+ )
52
+ rtn.append(trans)
53
+ da += (int(self._cmd.len) + 1) << int(self._cmd.size)
54
+ sa += (int(self._cmd.len) + 1) << int(self._cmd.size)
55
+ return rtn