Source code for hwtLib.avalon.st_comp.avalonStLatencyAdapter

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from typing import Optional

from hwt.code import If
from hwt.doc_markers import hwt_expr_producer
from hwt.hdl.commonConstants import b1
from hwt.hdl.types.bits import HBits
from hwt.hwIOs.utils import addClkRstn, propagateClkRstn
from hwt.hwModule import HwModule
from hwt.hwParam import HwParam
from hwt.math import log2ceil
from hwt.mathAutoExt import addAutoExt
from hwt.pyUtils.typingFuture import override
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwtLib.avalon.st import AvalonST
from hwtLib.clocking.clkBuilder import ClkBuilder
from hwtLib.handshaked.fifo import HandshakedFifo


[docs] class AvalonST_latencyAdapter(HwModule): """ .. hwt-autodoc:: """ @override def hwConfig(self) -> None: AvalonST.hwConfig(self) self.OUT_readyLatency:int = HwParam(0) self.OUT_readyAllowance: Optional[int] = HwParam(None) @override def hwDeclr(self) -> None: addClkRstn(self) with self._hwParamsShared(): self.dataIn = AvalonST() self.dataOut: AvalonST = AvalonST()._m() self.dataOut.readyLatency = self.OUT_readyLatency self.dataOut.readyAllowance = self.OUT_readyAllowance
[docs] @hwt_expr_producer def _constructAllowanceCreditCounter(self, ready: RtlSignal, valid: RtlSignal, maxVal): dstCreditT = HBits(log2ceil(maxVal + 1)) # :note: the dstCredit guards the maximum number beats send to dst # it is necessary becase we can not rely only on dst.rd or its delayed value because # readyAllowance specifies that the data may be transferred even if rd==0 dstCredit = self._reg(f"dst_credit", dstCreditT, def_val=0) If(ready, dstCredit(maxVal) # restart credit counter if ready=1 ).Elif(~ready & valid, # decrement dstCredit if we did not ceceive rd=1 but dst.readyAllowance specifies that we can send data anyway # :note: should never underflow because dst.vld can not be set if there is no dstCredit or dstRdDelayed dstCredit(dstCredit - 1) ) return dstCredit
[docs] @override def hwImpl(self) -> None: """ https://cdrdv2.intel.com/v1/dl/getContent/667068?fileName=mnl_avalon_spec-683091-667068.pdf Based on Avalon Interface Specifications Updated for Intel Quartus Prime Design Suite: 20.1 Table 19. Source/Sink Adaptation Requirement Functionality equivalent to Quartus avst_latency_adapter :note: ready cycle is a cycle during which the sink can accept a transfer :note: ready is asserted by the sink on cycle <n> to mark cycle <n + readyLatency> as a ready cycle. If readyLatency=0 and readyAllowance=0, data transfers occur only when both ready and valid are asserted. If readyLatency>0 or readyAllowance>0 the source may only assert valid and during ready cycles. :note: The sink samples data and on ready cycles where valid is asserted 1. :note: if readyLatency==0 the interface works as a typical (AXI4 Stream) ready/valid handshake. if >0 the ready signaling is delayed (inside of the source). The ready cycle is when source received delayed ready=1 from the sink, The source may assert valid only in ready cycle (when it is receiving delayed ready=1) :note: if readyLatency > the ready is delayed inside of the source and Avalon-ST itself will use non-delayed value :note: readyAllowance defines how many data can sink capture with ready=False, if it is set to None then readyAllowance=readyLatency :note: readyLatency/readyAllowance are parameters of FIFO implemented on sink side the "ready" represents "almost-full" of this FIFO, readyLatency=almost-full-remaining-size - 1, readyAllowance=FIFO total size-1. e.g. readyLatency=0, readyAllowance=1 can buffer 1 item with ready=0 :note: When readyLatency=1 and readyAllowance=2 the dst can accept data 1 cycle after rd=1, and 2 more cycles of transfers are allowed after rd=0. """ src: AvalonST = self.dataIn dst = self.dataOut assert src.__class__ is dst.__class__, (src, dst) # srcExpectsLargerBuff = src.readyAllowance > dst.readyAllowance srcCanSendBeforeDstCanAccept = src.readyLatency < dst.readyLatency srcExpectedBuffSize = src.readyAllowance if srcCanSendBeforeDstCanAccept: # must be able to store additional items before dst can accept them # because src ready is sampled in later time han dst expects srcExpectedBuffSize += dst.readyLatency - src.readyLatency dstProvidedBuffSize = dst.readyAllowance if srcExpectedBuffSize > dstProvidedBuffSize: # :note: fifo is used to accomodate extra items which we have to buffer # in the case that the dst will stall but we already confirmed to src that we will be able # to receive the data using rd (and because readyLatency we had to assume the optimistic case that # the dst will not stall because otherwise we would severeli limit the troughput) fifo = HandshakedFifo(dst.__class__) fifo._updateHwParamsFrom(src) fifo.readyAllowance = 0 fifo.readyLatency = 0 fifo.EXPORT_SPACE = True # :note: +1 is there to compensate for the fact that 1 item is output register # thus minimal fill during fifo continuous operation is 2 # :note: 2* +1 to compensate for latency of in/out # :note: +1 because for latency of credit logic fifo.DEPTH = srcExpectedBuffSize - dstProvidedBuffSize + src.readyLatency + 4 self.latencyMatchFifo = fifo propagateClkRstn(self) dst(fifo.dataOut, exclude=(dst.vld, dst.rd)) # :note: the fifo may be read on ready cycle (defined by dst readyAllowance,readyLatency), # which is when delayed dst.ready==1 if dst.readyLatency: dstRdDelayed = ClkBuilder(self, dst.rd).regPipe( dst.rd, dst.readyLatency - 1, "rd_delay", def_val=[0 for _ in range(dst.readyLatency - 1)])[-1] dstCredit = self._constructAllowanceCreditCounter(dstRdDelayed, dst.vld, dst.readyAllowance) isReadyCycle = dstCredit != 0 # :note: fifo.space defines how many empty slots in fifo are currently available, # while dstCredit specifies how many slots are available inside of dst actualSpace = addAutoExt(fifo.space, dstCredit) else: actualSpace = fifo.space isReadyCycle = b1 # connect fifo.dataOut -> dst ready/valid handshake fifo.dataOut.rd(isReadyCycle & dst.rd) dst.vld(isReadyCycle & fifo.dataOut.vld) # :note: src.vld should never be 1 if it is not possible to store the beat fifo.dataIn(src, exclude=(src.rd,)) # src.rd=1 if there is a enough space in fifo or in dst buffer # enough means that in the case of dst stall it will be still possile to store the data src.rd(actualSpace > (src.readyAllowance + 1)) else: if srcCanSendBeforeDstCanAccept: dst(src, exclude=(dst.rd, dst.vld)) if dst.readyAllowance: # add delay regs for ready to extend src.readyLatency to dst.readyLatency dstRdDelayed = ClkBuilder(self, dst.rd).regPipe( dst.rd, dst.readyLatency - 1, "rd_delayForDst", def_val=[0 for _ in range(dst.readyLatency - 1)]) dstCredit = self._constructAllowanceCreditCounter( dstRdDelayed[-1], dst.vld, dst.readyAllowance) # there is a ready enable = dstCredit != 0 else: enable = ClkBuilder(self, dst.rd).regPipe( dst.rd, dst.readyLatency - src.readyLatency, "rd_delayForDst", def_val=[0 for _ in range(dst.readyLatency)])[-1] src.rd(enable) if dst.readyLatency == 0: # in this case vld is allowed to be 1 if this is not ready cycle dst.vld(src.vld) else: # dst.vld must be 0 if this is not ready cycle dst.vld(src.vld & enable) elif src.readyAllowance == 0 and dst.readyAllowance != 0: # special case where src can set vld with rd=0 and dst disallows that dst(src, exclude=(src.vld,)) dst.vld(src.vld & dst.rd) else: dst(src)
# if src.readyLatency >= dst.readyLatency: # if src.readyAllowance > dst.readyAllowance: # # After ready is deasserted, the source can send more transfers # # than the sink can capture. # else: # # src.readyLatency < dst.readyLatency # if src.readyAllowance <= dst.readyAllowance: # # The source can start sending # # transfers before sink can capture. # raise NotImplementedError() # dst(src) if __name__ == "__main__": from hwt.synth import to_rtl_str m = AvalonST_latencyAdapter() m.OUT_readyAllowance = 1 m.OUT_readyLatency = 1 print(to_rtl_str(m))