#!/usr/bin/env python3
# -*- coding: utf-8 -
from hwt.code import If, In, Concat
from hwt.hdl.types.bits import Bits
from hwt.interfaces.std import Handshaked, RegCntrl, VectSignal
from hwt.interfaces.utils import addClkRstn, propagateClkRstn
from hwt.math import log2ceil
from hwt.synthesizer.param import Param
from hwt.synthesizer.unit import Unit
from hwt.synthesizer.vectorUtils import fitTo
from hwtLib.amba.datapump.intf import AxiRDatapumpIntf
from hwtLib.handshaked.fifo import HandshakedFifo
from hwtLib.handshaked.streamNode import StreamNode
[docs]class CLinkedListReader(Unit):
"""
This unit reads items from (circular) linked list like structure
.. code-block:: c
struct node {
item_t items[ITEMS_IN_BLOCK],
struct node * next;
};
synchronization is obtained by rdPtr/wrPtr (tail/head) pointer
baseAddr is address of actual node
:attention: device reads only chunks of size <= BUFFER_CAPACITY/2,
.. hwt-autodoc::
"""
def _config(self):
self.ID_WIDTH = Param(4)
self.ID = Param(3)
# id of packet where last item is next addr
self.ID_LAST = Param(4)
self.BUFFER_CAPACITY = Param(32)
self.ITEMS_IN_BLOCK = Param(4096 // 8 - 1)
self.ADDR_WIDTH = Param(32)
self.DATA_WIDTH = Param(64)
self.PTR_WIDTH = Param(16)
def _declr(self):
addClkRstn(self)
with self._paramsShared():
# interface which sending requests to download data
# and interface which is collecting all data and only data with specified id are processed
self.rDatapump = AxiRDatapumpIntf()._m()
self.rDatapump.MAX_BYTES = self.BUFFER_CAPACITY // 2 * self.DATA_WIDTH // 8
self.dataOut = Handshaked()._m()
# (how much of items remains in block)
self.inBlockRemain = VectSignal(log2ceil(self.ITEMS_IN_BLOCK + 1))._m()
# interface to control internal register
a = self.baseAddr = RegCntrl()
a.DATA_WIDTH = self.ADDR_WIDTH
self.rdPtr = RegCntrl()
self.wrPtr = RegCntrl()
for ptr in [self.rdPtr, self.wrPtr]:
ptr.DATA_WIDTH = self.PTR_WIDTH
f = self.dataFifo = HandshakedFifo(Handshaked)
f.EXPORT_SIZE = True
f.DATA_WIDTH = self.DATA_WIDTH
f.DEPTH = self.BUFFER_CAPACITY
[docs] def addrAlignBits(self):
return log2ceil(self.DATA_WIDTH // 8)
def _impl(self):
propagateClkRstn(self)
r, s = self._reg, self._sig
req = self.rDatapump.req
f = self.dataFifo
dIn = self.rDatapump.r
dBuffIn = f.dataIn
ALIGN_BITS = self.addrAlignBits()
ID = self.ID
BUFFER_CAPACITY = self.BUFFER_CAPACITY
BURST_LEN = BUFFER_CAPACITY // 2
ID_LAST = self.ID_LAST
bufferHasSpace = s("bufferHasSpace")
bufferHasSpace(f.size < (BURST_LEN + 1))
# we are counting base next addr as item as well
inBlock_t = Bits(log2ceil(self.ITEMS_IN_BLOCK + 1))
ringSpace_t = Bits(self.PTR_WIDTH)
downloadPending = r("downloadPending", def_val=0)
baseIndex = r("baseIndex", Bits(self.ADDR_WIDTH - ALIGN_BITS))
inBlockRemain = r("inBlockRemain_reg", inBlock_t, def_val=self.ITEMS_IN_BLOCK)
self.inBlockRemain(inBlockRemain)
# Logic of tail/head
rdPtr = r("rdPtr", ringSpace_t, def_val=0)
wrPtr = r("wrPtr", ringSpace_t, def_val=0)
If(self.wrPtr.dout.vld,
wrPtr(self.wrPtr.dout.data)
)
self.wrPtr.din(wrPtr)
self.rdPtr.din(rdPtr)
# this means items are present in memory
hasSpace = s("hasSpace")
hasSpace(wrPtr != rdPtr)
doReq = s("doReq")
doReq(bufferHasSpace & hasSpace & ~downloadPending & req.rd)
req.rem(0)
self.dataOut(f.dataOut)
# logic of baseAddr and baseIndex
baseAddr = Concat(baseIndex, Bits(ALIGN_BITS).from_py(0))
req.addr(baseAddr)
self.baseAddr.din(baseAddr)
dataAck = dIn.valid & In(dIn.id, [ID, ID_LAST]) & dBuffIn.rd
If(self.baseAddr.dout.vld,
baseIndex(self.baseAddr.dout.data[:ALIGN_BITS])
).Elif(dataAck & downloadPending,
If(dIn.last & dIn.id._eq(ID_LAST),
baseIndex(dIn.data[self.ADDR_WIDTH:ALIGN_BITS])
).Else(
baseIndex(baseIndex + 1)
)
)
sizeByPtrs = s("sizeByPtrs", ringSpace_t)
sizeByPtrs(wrPtr - rdPtr)
inBlockRemain_asPtrSize = fitTo(inBlockRemain, sizeByPtrs)
constraingSpace = s("constraingSpace", ringSpace_t)
If(inBlockRemain_asPtrSize < sizeByPtrs,
constraingSpace(inBlockRemain_asPtrSize)
).Else(
constraingSpace(sizeByPtrs)
)
constrainedByInBlockRemain = s("constrainedByInBlockRemain")
constrainedByInBlockRemain(fitTo(sizeByPtrs, inBlockRemain) >= inBlockRemain)
If(constraingSpace > BURST_LEN,
# download full burst
req.id(ID),
req.len(BURST_LEN - 1),
If(doReq,
inBlockRemain(inBlockRemain - BURST_LEN)
)
).Elif(constrainedByInBlockRemain & (inBlockRemain < BURST_LEN),
# we know that sizeByPtrs <= inBlockRemain thats why we can resize it
# we will download next* as well
req.id(ID_LAST),
req.len(constraingSpace, fit=True),
If(doReq,
inBlockRemain(self.ITEMS_IN_BLOCK)
)
).Else(
# download data leftover
req.id(ID),
req.len(constraingSpace - 1, fit=True),
If(doReq,
inBlockRemain(inBlockRemain - fitTo(constraingSpace, inBlockRemain))
)
)
# logic of req dispatching
If(downloadPending,
req.vld(0),
If(dataAck & dIn.last,
downloadPending(0)
)
).Else(
req.vld(bufferHasSpace & hasSpace),
If(req.rd & bufferHasSpace & hasSpace,
downloadPending(1)
)
)
# into buffer pushing logic
dBuffIn.data(dIn.data)
isMyData = s("isMyData")
isMyData(dIn.id._eq(ID) | (~dIn.last & dIn.id._eq(ID_LAST)))
If(self.rdPtr.dout.vld,
rdPtr(self.rdPtr.dout.data)
).Else(
If(dIn.valid & downloadPending & dBuffIn.rd & isMyData,
rdPtr(rdPtr + 1)
)
)
# push data into buffer and increment rdPtr
StreamNode(masters=[dIn],
slaves=[dBuffIn],
extraConds={dIn: downloadPending,
dBuffIn: (dIn.id._eq(ID)
| (dIn.id._eq(ID_LAST)
& ~dIn.last)
) & downloadPending
}).sync()
if __name__ == "__main__":
from hwt.synthesizer.utils import to_rtl_str
u = CLinkedListReader()
u.BUFFER_CAPACITY = 8
u.ITEMS_IN_BLOCK = 31
u.PTR_WIDTH = 8
print(to_rtl_str(u))