mirror of
https://github.com/fpganinja/taxi.git
synced 2025-12-07 16:28:40 -08:00
dma: Add PSDPRAM simulation model
Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
906
src/dma/tb/dma_psdp_ram.py
Normal file
906
src/dma/tb/dma_psdp_ram.py
Normal file
@@ -0,0 +1,906 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
"""
|
||||
|
||||
Copyright (c) 2020-2025 FPGA Ninja, LLC
|
||||
|
||||
Authors:
|
||||
- Alex Forencich
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import NamedTuple
|
||||
|
||||
import cocotb
|
||||
from cocotb.queue import Queue
|
||||
from cocotb.triggers import Event, RisingEdge
|
||||
from cocotb_bus.bus import Bus
|
||||
|
||||
from cocotbext.axi.memory import Memory
|
||||
from cocotbext.axi import Region
|
||||
|
||||
|
||||
# master write helper objects
|
||||
class WriteCmd(NamedTuple):
|
||||
address: int
|
||||
data: bytes
|
||||
event: Event
|
||||
|
||||
|
||||
class SegWriteData:
|
||||
def __int__(self):
|
||||
self.addr = 0
|
||||
self.data = 0
|
||||
self.be = 0
|
||||
|
||||
|
||||
class WriteRespCmd(NamedTuple):
|
||||
address: int
|
||||
length: int
|
||||
segments: int
|
||||
first_seg: int
|
||||
event: Event
|
||||
|
||||
|
||||
class WriteResp(NamedTuple):
|
||||
address: int
|
||||
length: int
|
||||
|
||||
|
||||
# master read helper objects
|
||||
class ReadCmd(NamedTuple):
|
||||
address: int
|
||||
length: int
|
||||
event: Event
|
||||
|
||||
|
||||
class SegReadCmd:
|
||||
def __int__(self):
|
||||
self.addr = 0
|
||||
|
||||
|
||||
class ReadRespCmd(NamedTuple):
|
||||
address: int
|
||||
length: int
|
||||
segments: int
|
||||
first_seg: int
|
||||
event: Event
|
||||
|
||||
|
||||
class ReadResp(NamedTuple):
|
||||
address: int
|
||||
data: bytes
|
||||
|
||||
def __bytes__(self):
|
||||
return self.data
|
||||
|
||||
|
||||
class BaseBus(Bus):
|
||||
|
||||
_signals = ["data"]
|
||||
_optional_signals = []
|
||||
|
||||
def __init__(self, entity=None, prefix=None, **kwargs):
|
||||
super().__init__(entity, prefix, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
return cls(entity, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
return cls(entity, prefix, **kwargs)
|
||||
|
||||
|
||||
class PsdpRamWriteBus(BaseBus):
|
||||
_signals = ["wr_cmd_be", "wr_cmd_addr", "wr_cmd_data", "wr_cmd_valid", "wr_cmd_ready", "wr_done"]
|
||||
|
||||
|
||||
class PsdpRamReadBus(BaseBus):
|
||||
_signals = ["rd_cmd_addr", "rd_cmd_valid", "rd_cmd_ready", "rd_resp_data", "rd_resp_valid", "rd_resp_ready"]
|
||||
|
||||
|
||||
class PsdpRamBus:
|
||||
def __init__(self, write=None, read=None, **kwargs):
|
||||
self.write = write
|
||||
self.read = read
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
write = PsdpRamWriteBus.from_entity(entity, **kwargs)
|
||||
read = PsdpRamReadBus.from_entity(entity, **kwargs)
|
||||
return cls(write, read)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
write = PsdpRamWriteBus.from_prefix(entity, prefix, **kwargs)
|
||||
read = PsdpRamReadBus.from_prefix(entity, prefix, **kwargs)
|
||||
return cls(write, read)
|
||||
|
||||
@classmethod
|
||||
def from_channels(cls, wr, rd):
|
||||
write = PsdpRamWriteBus.from_channels(wr)
|
||||
read = PsdpRamReadBus.from_channels(rd)
|
||||
return cls(write, read)
|
||||
|
||||
|
||||
class PsdpRamMasterWrite(Region):
|
||||
|
||||
def __init__(self, bus, clock, reset=None, **kwargs):
|
||||
self.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
if bus._name:
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
||||
else:
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}")
|
||||
|
||||
self.log.info("Parallel Simple Dual Port RAM master model (write)")
|
||||
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle = Event()
|
||||
self._idle.set()
|
||||
|
||||
self.width = len(self.bus.wr_cmd_data)
|
||||
self.byte_size = 8
|
||||
self.byte_lanes = len(self.bus.wr_cmd_be)
|
||||
|
||||
self.seg_count = len(self.bus.wr_cmd_valid)
|
||||
self.seg_data_width = self.width // self.seg_count
|
||||
self.seg_byte_lanes = self.seg_data_width // self.byte_size
|
||||
self.seg_addr_width = len(self.bus.wr_cmd_addr) // self.seg_count
|
||||
self.seg_be_width = self.seg_data_width // self.byte_size
|
||||
|
||||
self.seg_data_mask = 2**self.seg_data_width-1
|
||||
self.seg_addr_mask = 2**self.seg_addr_width-1
|
||||
self.seg_be_mask = 2**self.seg_be_width-1
|
||||
|
||||
self.address_width = self.seg_addr_width + (self.seg_byte_lanes*self.seg_count-1).bit_length()
|
||||
|
||||
self.write_command_queue = Queue()
|
||||
self.write_command_queue.queue_occupancy_limit = 2
|
||||
self.current_write_command = None
|
||||
|
||||
self.seg_write_queue = [Queue() for x in range(self.seg_count)]
|
||||
self.seg_write_resp_queue = [Queue() for x in range(self.seg_count)]
|
||||
|
||||
self.int_write_resp_command_queue = Queue()
|
||||
self.current_write_resp_command = None
|
||||
|
||||
super().__init__(2**self.address_width, **kwargs)
|
||||
|
||||
self.log.info("Parallel Simple Dual Port RAM master model configuration:")
|
||||
self.log.info(" Address width: %d bits", self.address_width)
|
||||
self.log.info(" Segment count: %d", self.seg_count)
|
||||
self.log.info(" Segment addr width: %d bits", self.seg_addr_width)
|
||||
self.log.info(" Segment data width: %d bits (%d bytes)", self.seg_data_width, self.seg_byte_lanes)
|
||||
self.log.info(" Total data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
|
||||
assert self.seg_be_width*self.seg_count == len(self.bus.wr_cmd_be)
|
||||
|
||||
self.bus.wr_cmd_valid.setimmediatevalue(0)
|
||||
|
||||
cocotb.start_soon(self._process_write())
|
||||
cocotb.start_soon(self._process_write_resp())
|
||||
cocotb.start_soon(self._run())
|
||||
|
||||
def set_pause_generator(self, generator=None):
|
||||
if self._pause_cr is not None:
|
||||
self._pause_cr.kill()
|
||||
self._pause_cr = None
|
||||
|
||||
self._pause_generator = generator
|
||||
|
||||
if self._pause_generator is not None:
|
||||
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||
|
||||
def clear_pause_generator(self):
|
||||
self.set_pause_generator(None)
|
||||
|
||||
def idle(self):
|
||||
return not self.in_flight_operations
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
await self._idle.wait()
|
||||
|
||||
async def write(self, address, data):
|
||||
if address < 0 or address >= 2**self.address_width:
|
||||
raise ValueError("Address out of range")
|
||||
|
||||
if isinstance(data, int):
|
||||
raise ValueError("Expected bytes or bytearray for data")
|
||||
|
||||
if address+len(data) > 2**self.address_width:
|
||||
raise ValueError("Requested transfer overruns end of address space")
|
||||
|
||||
event = Event()
|
||||
data = bytes(data)
|
||||
|
||||
self.in_flight_operations += 1
|
||||
self._idle.clear()
|
||||
|
||||
await self.write_command_queue.put(WriteCmd(address, data, event))
|
||||
await event.wait()
|
||||
return event.data
|
||||
|
||||
async def _process_write(self):
|
||||
while True:
|
||||
cmd = await self.write_command_queue.get()
|
||||
self.current_write_command = cmd
|
||||
|
||||
seg_start_offset = cmd.address % self.seg_byte_lanes
|
||||
seg_end_offset = ((cmd.address + len(cmd.data) - 1) % self.seg_byte_lanes) + 1
|
||||
|
||||
seg_be_start = (self.seg_be_mask << seg_start_offset) & self.seg_be_mask
|
||||
seg_be_end = self.seg_be_mask >> (self.seg_byte_lanes - seg_end_offset)
|
||||
|
||||
first_seg = (cmd.address // self.seg_byte_lanes) % self.seg_count
|
||||
segments = (len(cmd.data) + (cmd.address % self.seg_byte_lanes) + self.seg_byte_lanes-1) // self.seg_byte_lanes
|
||||
|
||||
resp_cmd = WriteRespCmd(cmd.address, len(cmd.data), segments, first_seg, cmd.event)
|
||||
await self.int_write_resp_command_queue.put(resp_cmd)
|
||||
|
||||
offset = 0
|
||||
|
||||
if self.log.isEnabledFor(logging.INFO):
|
||||
self.log.info("Write start addr: 0x%08x data: %s",
|
||||
cmd.address, ' '.join((f'{c:02x}' for c in cmd.data)))
|
||||
|
||||
seg = first_seg
|
||||
for k in range(segments):
|
||||
start = 0
|
||||
stop = self.seg_byte_lanes
|
||||
be = self.seg_be_mask
|
||||
|
||||
if k == 0:
|
||||
start = seg_start_offset
|
||||
be &= seg_be_start
|
||||
if k == segments-1:
|
||||
stop = seg_end_offset
|
||||
be &= seg_be_end
|
||||
|
||||
val = 0
|
||||
for j in range(start, stop):
|
||||
val |= cmd.data[offset] << j*8
|
||||
offset += 1
|
||||
|
||||
op = SegWriteData()
|
||||
op.addr = (cmd.address + k*self.seg_byte_lanes) // self.byte_lanes
|
||||
op.data = val
|
||||
op.be = be
|
||||
|
||||
await self.seg_write_queue[seg].put(op)
|
||||
|
||||
seg = (seg + 1) % self.seg_count
|
||||
|
||||
self.current_write_command = None
|
||||
|
||||
async def _process_write_resp(self):
|
||||
while True:
|
||||
cmd = await self.int_write_resp_command_queue.get()
|
||||
self.current_write_resp_command = cmd
|
||||
|
||||
seg = cmd.first_seg
|
||||
for k in range(cmd.segments):
|
||||
await self.seg_write_resp_queue[seg].get()
|
||||
|
||||
seg = (seg + 1) % self.seg_count
|
||||
|
||||
if self.log.isEnabledFor(logging.INFO):
|
||||
self.log.info("Write complete addr: 0x%08x length: %d", cmd.address, cmd.length)
|
||||
|
||||
write_resp = WriteResp(cmd.address, cmd.length)
|
||||
|
||||
cmd.event.set(write_resp)
|
||||
|
||||
self.current_write_resp_command = None
|
||||
|
||||
self.in_flight_operations -= 1
|
||||
|
||||
if self.in_flight_operations == 0:
|
||||
self._idle.set()
|
||||
|
||||
async def _run(self):
|
||||
cmd_valid = 0
|
||||
cmd_addr = 0
|
||||
cmd_data = 0
|
||||
cmd_be = 0
|
||||
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
cmd_ready_sample = self.bus.wr_cmd_ready.value
|
||||
done_sample = self.bus.wr_done.value
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
self.bus.wr_cmd_valid.setimmediatevalue(0)
|
||||
continue
|
||||
|
||||
# process segments
|
||||
for seg in range(self.seg_count):
|
||||
seg_mask = 1 << seg
|
||||
|
||||
if (cmd_ready_sample & seg_mask) or not (cmd_valid & seg_mask):
|
||||
if not self.seg_write_queue[seg].empty() and not self.pause:
|
||||
op = await self.seg_write_queue[seg].get()
|
||||
cmd_addr &= ~(self.seg_addr_mask << self.seg_addr_width*seg)
|
||||
cmd_addr |= ((op.addr & self.seg_addr_mask) << self.seg_addr_width*seg)
|
||||
cmd_data &= ~(self.seg_data_mask << self.seg_data_width*seg)
|
||||
cmd_data |= ((op.data & self.seg_data_mask) << self.seg_data_width*seg)
|
||||
cmd_be &= ~(self.seg_be_mask << self.seg_be_width*seg)
|
||||
cmd_be |= ((op.be & self.seg_be_mask) << self.seg_be_width*seg)
|
||||
cmd_valid |= seg_mask
|
||||
|
||||
if self.log.isEnabledFor(logging.INFO):
|
||||
self.log.info("Write word seg: %d addr: 0x%08x be 0x%02x data %s",
|
||||
seg, op.addr, op.be, ' '.join((f'{c:02x}' for c in op.data.to_bytes(self.seg_byte_lanes, 'little'))))
|
||||
else:
|
||||
cmd_valid &= ~seg_mask
|
||||
|
||||
if done_sample & seg_mask:
|
||||
await self.seg_write_resp_queue[seg].put(None)
|
||||
|
||||
self.bus.wr_cmd_valid.value = cmd_valid
|
||||
self.bus.wr_cmd_addr.value = cmd_addr
|
||||
self.bus.wr_cmd_data.value = cmd_data
|
||||
self.bus.wr_cmd_be.value = cmd_be
|
||||
|
||||
async def _run_pause(self):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await clock_edge_event
|
||||
|
||||
|
||||
class PsdpRamMasterRead(Region):
|
||||
|
||||
def __init__(self, bus, clock, reset=None, **kwargs):
|
||||
self.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
if bus._name:
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
||||
else:
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}")
|
||||
|
||||
self.log.info("Parallel Simple Dual Port RAM master model (read)")
|
||||
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle = Event()
|
||||
self._idle.set()
|
||||
|
||||
self.width = len(self.bus.rd_resp_data)
|
||||
self.byte_size = 8
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
self.seg_count = len(self.bus.rd_cmd_valid)
|
||||
self.seg_data_width = self.width // self.seg_count
|
||||
self.seg_byte_lanes = self.seg_data_width // self.byte_size
|
||||
self.seg_addr_width = len(self.bus.rd_cmd_addr) // self.seg_count
|
||||
|
||||
self.seg_data_mask = 2**self.seg_data_width-1
|
||||
self.seg_addr_mask = 2**self.seg_addr_width-1
|
||||
|
||||
self.address_width = self.seg_addr_width + (self.seg_byte_lanes*self.seg_count-1).bit_length()
|
||||
|
||||
self.read_command_queue = Queue()
|
||||
self.read_command_queue.queue_occupancy_limit = 2
|
||||
self.current_read_command = None
|
||||
|
||||
self.seg_read_queue = [Queue() for x in range(self.seg_count)]
|
||||
self.seg_read_resp_queue = [Queue() for x in range(self.seg_count)]
|
||||
|
||||
self.int_read_resp_command_queue = Queue()
|
||||
self.current_read_resp_command = None
|
||||
|
||||
super().__init__(2**self.address_width, **kwargs)
|
||||
|
||||
self.log.info("Parallel Simple Dual Port RAM master model configuration:")
|
||||
self.log.info(" Address width: %d bits", self.address_width)
|
||||
self.log.info(" Segment count: %d", self.seg_count)
|
||||
self.log.info(" Segment addr width: %d bits", self.seg_addr_width)
|
||||
self.log.info(" Segment data width: %d bits (%d bytes)", self.seg_data_width, self.seg_byte_lanes)
|
||||
self.log.info(" Total data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
|
||||
self.bus.rd_cmd_valid.setimmediatevalue(0)
|
||||
self.bus.rd_resp_ready.setimmediatevalue(0)
|
||||
|
||||
cocotb.start_soon(self._process_read())
|
||||
cocotb.start_soon(self._process_read_resp())
|
||||
cocotb.start_soon(self._run())
|
||||
|
||||
def set_pause_generator(self, generator=None):
|
||||
if self._pause_cr is not None:
|
||||
self._pause_cr.kill()
|
||||
self._pause_cr = None
|
||||
|
||||
self._pause_generator = generator
|
||||
|
||||
if self._pause_generator is not None:
|
||||
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||
|
||||
def clear_pause_generator(self):
|
||||
self.set_pause_generator(None)
|
||||
|
||||
def idle(self):
|
||||
return not self.in_flight_operations
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
await self._idle.wait()
|
||||
|
||||
async def read(self, address, length):
|
||||
if address < 0 or address >= 2**self.address_width:
|
||||
raise ValueError("Address out of range")
|
||||
|
||||
if length < 0:
|
||||
raise ValueError("Read length must be positive")
|
||||
|
||||
if address+length > 2**self.address_width:
|
||||
raise ValueError("Requested transfer overruns end of address space")
|
||||
|
||||
event = Event()
|
||||
|
||||
self.in_flight_operations += 1
|
||||
self._idle.clear()
|
||||
|
||||
await self.read_command_queue.put(ReadCmd(address, length, event))
|
||||
|
||||
await event.wait()
|
||||
return event.data
|
||||
|
||||
async def _process_read(self):
|
||||
while True:
|
||||
cmd = await self.read_command_queue.get()
|
||||
self.current_read_command = cmd
|
||||
|
||||
first_seg = (cmd.address // self.seg_byte_lanes) % self.seg_count
|
||||
segments = (cmd.length + (cmd.address % self.seg_byte_lanes) + self.seg_byte_lanes-1) // self.seg_byte_lanes
|
||||
|
||||
resp_cmd = ReadRespCmd(cmd.address, cmd.length, segments, first_seg, cmd.event)
|
||||
await self.int_read_resp_command_queue.put(resp_cmd)
|
||||
|
||||
if self.log.isEnabledFor(logging.INFO):
|
||||
self.log.info("Read start addr: 0x%08x length: %d", cmd.address, cmd.length)
|
||||
|
||||
seg = first_seg
|
||||
for k in range(segments):
|
||||
op = SegReadCmd()
|
||||
op.addr = (cmd.address + k*self.seg_byte_lanes) // self.byte_lanes
|
||||
|
||||
await self.seg_read_queue[seg].put(op)
|
||||
|
||||
seg = (seg + 1) % self.seg_count
|
||||
|
||||
self.current_read_command = None
|
||||
|
||||
async def _process_read_resp(self):
|
||||
while True:
|
||||
cmd = await self.int_read_resp_command_queue.get()
|
||||
self.current_read_resp_command = cmd
|
||||
|
||||
seg_start_offset = cmd.address % self.seg_byte_lanes
|
||||
seg_end_offset = ((cmd.address + cmd.length - 1) % self.seg_byte_lanes) + 1
|
||||
|
||||
data = bytearray()
|
||||
|
||||
seg = cmd.first_seg
|
||||
for k in range(cmd.segments):
|
||||
seg_data = await self.seg_read_resp_queue[seg].get()
|
||||
|
||||
start = 0
|
||||
stop = self.seg_byte_lanes
|
||||
|
||||
if k == 0:
|
||||
start = seg_start_offset
|
||||
if k == cmd.segments-1:
|
||||
stop = seg_end_offset
|
||||
|
||||
for j in range(start, stop):
|
||||
data.extend(bytearray([(seg_data >> j*8) & 0xff]))
|
||||
|
||||
seg = (seg + 1) % self.seg_count
|
||||
|
||||
if self.log.isEnabledFor(logging.INFO):
|
||||
self.log.info("Read complete addr: 0x%08x data: %s",
|
||||
cmd.address, ' '.join((f'{c:02x}' for c in data)))
|
||||
|
||||
read_resp = ReadResp(cmd.address, bytes(data))
|
||||
|
||||
cmd.event.set(read_resp)
|
||||
|
||||
self.current_read_resp_command = None
|
||||
|
||||
self.in_flight_operations -= 1
|
||||
|
||||
if self.in_flight_operations == 0:
|
||||
self._idle.set()
|
||||
|
||||
async def _run(self):
|
||||
cmd_valid = 0
|
||||
cmd_addr = 0
|
||||
resp_ready = 0
|
||||
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
cmd_ready_sample = self.bus.rd_cmd_ready.value
|
||||
resp_valid_sample = self.bus.rd_resp_valid.value
|
||||
|
||||
if resp_valid_sample:
|
||||
resp_data_sample = self.bus.rd_resp_data.value
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
self.bus.rd_cmd_valid.setimmediatevalue(0)
|
||||
self.bus.rd_resp_ready.setimmediatevalue(0)
|
||||
cmd_valid = 0
|
||||
resp_ready = 0
|
||||
continue
|
||||
|
||||
# process segments
|
||||
for seg in range(self.seg_count):
|
||||
seg_mask = 1 << seg
|
||||
|
||||
if (cmd_ready_sample & seg_mask) or not (cmd_valid & seg_mask):
|
||||
if not self.seg_read_queue[seg].empty() and not self.pause:
|
||||
op = await self.seg_read_queue[seg].get()
|
||||
cmd_addr &= ~(self.seg_addr_mask << self.seg_addr_width*seg)
|
||||
cmd_addr |= ((op.addr & self.seg_addr_mask) << self.seg_addr_width*seg)
|
||||
cmd_valid |= seg_mask
|
||||
|
||||
if self.log.isEnabledFor(logging.INFO):
|
||||
self.log.info("Read word seg: %d addr: 0x%08x", seg, op.addr)
|
||||
else:
|
||||
cmd_valid &= ~seg_mask
|
||||
|
||||
if resp_ready & resp_valid_sample & (1 << seg):
|
||||
seg_data = (resp_data_sample >> self.seg_data_width*seg) & self.seg_data_mask
|
||||
|
||||
await self.seg_read_resp_queue[seg].put(seg_data)
|
||||
|
||||
resp_ready = 2**self.seg_count-1
|
||||
|
||||
if self.pause:
|
||||
resp_ready = 0
|
||||
|
||||
self.bus.rd_cmd_valid.value = cmd_valid
|
||||
self.bus.rd_cmd_addr.value = cmd_addr
|
||||
|
||||
self.bus.rd_resp_ready.value = resp_ready
|
||||
|
||||
async def _run_pause(self):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await clock_edge_event
|
||||
|
||||
|
||||
class PsdpRamMaster(Region):
|
||||
def __init__(self, bus, clock, reset=None, **kwargs):
|
||||
self.write_if = None
|
||||
self.read_if = None
|
||||
|
||||
self.write_if = PsdpRamMasterWrite(bus.write, clock, reset)
|
||||
self.read_if = PsdpRamMasterRead(bus.read, clock, reset)
|
||||
|
||||
super().__init__(max(self.write_if.size, self.read_if.size), **kwargs)
|
||||
|
||||
def init_read(self, address, length, event=None):
|
||||
return self.read_if.init_read(address, length, event)
|
||||
|
||||
def init_write(self, address, data, event=None):
|
||||
return self.write_if.init_write(address, data, event)
|
||||
|
||||
def idle(self):
|
||||
return (not self.read_if or self.read_if.idle()) and (not self.write_if or self.write_if.idle())
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
await self.write_if.wait()
|
||||
await self.read_if.wait()
|
||||
|
||||
async def wait_read(self):
|
||||
await self.read_if.wait()
|
||||
|
||||
async def wait_write(self):
|
||||
await self.write_if.wait()
|
||||
|
||||
async def read(self, address, length):
|
||||
return await self.read_if.read(address, length)
|
||||
|
||||
async def write(self, address, data):
|
||||
return await self.write_if.write(address, data)
|
||||
|
||||
|
||||
class PsdpRamWrite(Memory):
|
||||
|
||||
def __init__(self, bus, clock, reset=None, size=1024, mem=None, *args, **kwargs):
|
||||
self.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
||||
|
||||
self.log.info("Parallel Simple Dual Port RAM model (write)")
|
||||
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||
|
||||
super().__init__(size, mem, *args, **kwargs)
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
|
||||
self.width = len(self.bus.wr_cmd_data)
|
||||
self.byte_size = 8
|
||||
self.byte_lanes = len(self.bus.wr_cmd_be)
|
||||
|
||||
self.seg_count = len(self.bus.wr_cmd_valid)
|
||||
self.seg_data_width = self.width // self.seg_count
|
||||
self.seg_byte_lanes = self.seg_data_width // self.byte_size
|
||||
self.seg_addr_width = len(self.bus.wr_cmd_addr) // self.seg_count
|
||||
self.seg_be_width = self.seg_data_width // self.byte_size
|
||||
|
||||
self.seg_data_mask = 2**self.seg_data_width-1
|
||||
self.seg_addr_mask = 2**self.seg_addr_width-1
|
||||
self.seg_be_mask = 2**self.seg_be_width-1
|
||||
|
||||
self.log.info("Parallel Simple Dual Port RAM model configuration:")
|
||||
self.log.info(" Memory size: %d bytes", len(self.mem))
|
||||
self.log.info(" Segment count: %d", self.seg_count)
|
||||
self.log.info(" Segment addr width: %d bits", self.seg_addr_width)
|
||||
self.log.info(" Segment data width: %d bits (%d bytes)", self.seg_data_width, self.seg_byte_lanes)
|
||||
self.log.info(" Total data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
|
||||
assert self.seg_be_width*self.seg_count == len(self.bus.wr_cmd_be)
|
||||
|
||||
self.bus.wr_cmd_ready.setimmediatevalue(0)
|
||||
self.bus.wr_done.setimmediatevalue(0)
|
||||
|
||||
cocotb.start_soon(self._run())
|
||||
|
||||
def set_pause_generator(self, generator=None):
|
||||
if self._pause_cr is not None:
|
||||
self._pause_cr.kill()
|
||||
self._pause_cr = None
|
||||
|
||||
self._pause_generator = generator
|
||||
|
||||
if self._pause_generator is not None:
|
||||
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||
|
||||
def clear_pause_generator(self):
|
||||
self.set_pause_generator(None)
|
||||
|
||||
async def _run(self):
|
||||
cmd_ready = 0
|
||||
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
wr_done = 0
|
||||
|
||||
cmd_valid_sample = self.bus.wr_cmd_valid.value
|
||||
|
||||
if cmd_valid_sample:
|
||||
cmd_be_sample = self.bus.wr_cmd_be.value
|
||||
cmd_addr_sample = self.bus.wr_cmd_addr.value
|
||||
cmd_data_sample = self.bus.wr_cmd_data.value
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
self.bus.wr_cmd_ready.setimmediatevalue(0)
|
||||
self.bus.wr_done.setimmediatevalue(0)
|
||||
continue
|
||||
|
||||
# process segments
|
||||
for seg in range(self.seg_count):
|
||||
if cmd_ready & cmd_valid_sample & (1 << seg):
|
||||
seg_addr = (cmd_addr_sample >> self.seg_addr_width*seg) & self.seg_addr_mask
|
||||
seg_data = (cmd_data_sample >> self.seg_data_width*seg) & self.seg_data_mask
|
||||
seg_be = (cmd_be_sample >> self.seg_be_width*seg) & self.seg_be_mask
|
||||
|
||||
addr = (seg_addr*self.seg_count+seg)*self.seg_byte_lanes
|
||||
|
||||
# generate operation list
|
||||
offset = 0
|
||||
start_offset = None
|
||||
write_ops = []
|
||||
|
||||
data = seg_data.to_bytes(self.seg_byte_lanes, 'little')
|
||||
|
||||
for i in range(self.byte_lanes):
|
||||
if seg_be & (1 << i):
|
||||
if start_offset is None:
|
||||
start_offset = offset
|
||||
else:
|
||||
if start_offset is not None and offset != start_offset:
|
||||
write_ops.append((addr+start_offset, data[start_offset:offset]))
|
||||
start_offset = None
|
||||
|
||||
offset += 1
|
||||
|
||||
if start_offset is not None and offset != start_offset:
|
||||
write_ops.append((addr+start_offset, data[start_offset:offset]))
|
||||
|
||||
# perform writes
|
||||
for addr, data in write_ops:
|
||||
self.write(addr, data)
|
||||
|
||||
wr_done |= 1 << seg
|
||||
|
||||
self.log.info("Write word seg: %d addr: 0x%08x be 0x%02x data %s",
|
||||
seg, addr, seg_be, ' '.join((f'{c:02x}' for c in data)))
|
||||
|
||||
cmd_ready = 2**self.seg_count-1
|
||||
|
||||
if self.pause:
|
||||
cmd_ready = 0
|
||||
|
||||
self.bus.wr_cmd_ready.value = cmd_ready
|
||||
self.bus.wr_done.value = wr_done
|
||||
|
||||
async def _run_pause(self):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await clock_edge_event
|
||||
|
||||
|
||||
class PsdpRamRead(Memory):
|
||||
|
||||
def __init__(self, bus, clock, reset=None, size=1024, mem=None, *args, **kwargs):
|
||||
self.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
||||
|
||||
self.log.info("Parallel Simple Dual Port RAM model (read)")
|
||||
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||
|
||||
super().__init__(size, mem, *args, **kwargs)
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
|
||||
self.width = len(self.bus.rd_resp_data)
|
||||
self.byte_size = 8
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
self.seg_count = len(self.bus.rd_cmd_valid)
|
||||
self.seg_data_width = self.width // self.seg_count
|
||||
self.seg_byte_lanes = self.seg_data_width // self.byte_size
|
||||
self.seg_addr_width = len(self.bus.rd_cmd_addr) // self.seg_count
|
||||
|
||||
self.seg_data_mask = 2**self.seg_data_width-1
|
||||
self.seg_addr_mask = 2**self.seg_addr_width-1
|
||||
|
||||
self.log.info("Parallel Simple Dual Port RAM model configuration:")
|
||||
self.log.info(" Memory size: %d bytes", len(self.mem))
|
||||
self.log.info(" Segment count: %d", self.seg_count)
|
||||
self.log.info(" Segment addr width: %d bits", self.seg_addr_width)
|
||||
self.log.info(" Segment data width: %d bits (%d bytes)", self.seg_data_width, self.seg_byte_lanes)
|
||||
self.log.info(" Total data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
|
||||
self.bus.rd_cmd_ready.setimmediatevalue(0)
|
||||
self.bus.rd_resp_valid.setimmediatevalue(0)
|
||||
|
||||
cocotb.start_soon(self._run())
|
||||
|
||||
def set_pause_generator(self, generator=None):
|
||||
if self._pause_cr is not None:
|
||||
self._pause_cr.kill()
|
||||
self._pause_cr = None
|
||||
|
||||
self._pause_generator = generator
|
||||
|
||||
if self._pause_generator is not None:
|
||||
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||
|
||||
def clear_pause_generator(self):
|
||||
self.set_pause_generator(None)
|
||||
|
||||
async def _run(self):
|
||||
pipeline = [[None for x in range(1)] for seg in range(self.seg_count)]
|
||||
|
||||
cmd_ready = 0
|
||||
resp_valid = 0
|
||||
resp_data = 0
|
||||
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
cmd_valid_sample = self.bus.rd_cmd_valid.value
|
||||
|
||||
if cmd_valid_sample:
|
||||
cmd_addr_sample = self.bus.rd_cmd_addr.value
|
||||
|
||||
resp_ready_sample = self.bus.rd_resp_ready.value
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
self.bus.rd_cmd_ready.setimmediatevalue(0)
|
||||
self.bus.rd_resp_valid.setimmediatevalue(0)
|
||||
cmd_ready = 0
|
||||
resp_valid = 0
|
||||
continue
|
||||
|
||||
# process segments
|
||||
for seg in range(self.seg_count):
|
||||
seg_mask = 1 << seg
|
||||
|
||||
if (resp_ready_sample & seg_mask) or not (resp_valid & seg_mask):
|
||||
if pipeline[seg][-1] is not None:
|
||||
resp_data &= ~(self.seg_data_mask << self.seg_data_width*seg)
|
||||
resp_data |= ((pipeline[seg][-1] & self.seg_data_mask) << self.seg_data_width*seg)
|
||||
resp_valid |= seg_mask
|
||||
pipeline[seg][-1] = None
|
||||
else:
|
||||
resp_valid &= ~seg_mask
|
||||
|
||||
for i in range(len(pipeline[seg])-1, 0, -1):
|
||||
if pipeline[seg][i] is None:
|
||||
pipeline[i] = pipeline[i-1]
|
||||
pipeline[i-1] = None
|
||||
|
||||
if cmd_ready & cmd_valid_sample & seg_mask:
|
||||
seg_addr = (cmd_addr_sample >> self.seg_addr_width*seg) & self.seg_addr_mask
|
||||
|
||||
addr = (seg_addr*self.seg_count+seg)*self.seg_byte_lanes
|
||||
|
||||
data = self.read(addr % self.size, self.seg_byte_lanes)
|
||||
pipeline[seg][0] = int.from_bytes(data, 'little')
|
||||
|
||||
self.log.info("Read word seg: %d addr: 0x%08x data %s",
|
||||
seg, addr, ' '.join((f'{c:02x}' for c in data)))
|
||||
|
||||
if (not resp_valid & seg_mask) or None in pipeline[seg]:
|
||||
cmd_ready |= seg_mask
|
||||
else:
|
||||
cmd_ready &= ~seg_mask
|
||||
|
||||
if self.pause:
|
||||
cmd_ready = 0
|
||||
|
||||
self.bus.rd_cmd_ready.value = cmd_ready
|
||||
|
||||
self.bus.rd_resp_data.value = resp_data
|
||||
self.bus.rd_resp_valid.value = resp_valid
|
||||
|
||||
async def _run_pause(self):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await clock_edge_event
|
||||
|
||||
|
||||
class PsdpRam(Memory):
|
||||
def __init__(self, bus, clock, reset=None, size=1024, mem=None, *args, **kwargs):
|
||||
self.write_if = None
|
||||
self.read_if = None
|
||||
|
||||
super().__init__(size, mem, *args, **kwargs)
|
||||
|
||||
self.write_if = PsdpRamWrite(bus.write, clock, reset, mem=self.mem)
|
||||
self.read_if = PsdpRamRead(bus.read, clock, reset, mem=self.mem)
|
||||
Reference in New Issue
Block a user