From a11e026e4f4a325423d8d8c7e726b009ace81696 Mon Sep 17 00:00:00 2001 From: Christophe Favergeon Date: Thu, 8 Sep 2022 15:17:04 +0200 Subject: [PATCH] SDF now support connection to multiple outputs. Duplicate nodes are inserted automatically. --- SDFTools/examples/example8/AppNodes.h | 22 ++- SDFTools/examples/example8/appnodes.py | 10 +- .../examples/example8/generated/scheduler.cpp | 36 +++-- SDFTools/examples/example8/graph.py | 63 ++++---- SDFTools/examples/example8/sched.py | 43 +++-- SDFTools/examples/example8/test.dot | 89 ++++++----- SDFTools/examples/example8/test.pdf | Bin 24999 -> 24464 bytes cmsisdsp/sdf/nodes/Duplicate.py | 2 +- cmsisdsp/sdf/scheduler/description.py | 147 +++++++++++++++++- cmsisdsp/sdf/scheduler/node.py | 6 +- cmsisdsp/sdf/scheduler/standard.py | 8 + .../sdf/scheduler/templates/dot_template.dot | 43 ++--- 12 files changed, 346 insertions(+), 123 deletions(-) diff --git a/SDFTools/examples/example8/AppNodes.h b/SDFTools/examples/example8/AppNodes.h index a5f4c6af..f1ca45fa 100644 --- a/SDFTools/examples/example8/AppNodes.h +++ b/SDFTools/examples/example8/AppNodes.h @@ -74,17 +74,29 @@ public: }; -template -class ProcessingNode: public GenericNode +template +class ProcessingNode: public GenericNode12 { public: - ProcessingNode(FIFOBase &src,FIFOBase &dst,int,const char*,int):GenericNode(src,dst){}; + ProcessingNode(FIFOBase &src, + FIFOBase &dst1, + FIFOBase &dst2, + int,const char*,int): + GenericNode12(src,dst1,dst2){}; int run(){ printf("ProcessingNode\n"); IN *a=this->getReadBuffer(); - OUT *b=this->getWriteBuffer(); - b[0] =(OUT)a[3]; + OUT1 *b=this->getWriteBuffer1(); + OUT2 *c=this->getWriteBuffer2(); + b[0] =(OUT1)a[3]; + c[0] =(OUT2)a[3]; return(0); }; diff --git a/SDFTools/examples/example8/appnodes.py b/SDFTools/examples/example8/appnodes.py index df61f266..a020d69e 100644 --- a/SDFTools/examples/example8/appnodes.py +++ b/SDFTools/examples/example8/appnodes.py @@ -44,14 +44,15 @@ class Sink(GenericSink): print("%f + I %f" % (c.re,c.im)) return(0) -class ProcessingNode(GenericNode): - def __init__(self,inputSize,outputSize,fifoin,fifoout,i,s,v): - GenericNode.__init__(self,inputSize,outputSize,fifoin,fifoout) +class ProcessingNode(GenericNode12): + def __init__(self,inputSize,outputSize1,outputSize2,fifoin,fifoout1,fifoout2,i,s,v): + GenericNode12.__init__(self,inputSize,outputSize1,outputSize2,fifoin,fifoout1,fifoout2) def run(self): print("ProcessingNode"); a=self.getReadBuffer() - b=self.getWriteBuffer() + b=self.getWriteBuffer1() + c=self.getWriteBuffer2() # Python objects have reference semantic and not # value semantic. # So in a write buffer, we can change the @@ -59,6 +60,7 @@ class ProcessingNode(GenericNode): # replace the object and risk creating sharing # Duplicating the a object may be ok b[0]=a[3] + c[0]=a[3] return(0) class Source(GenericSource): diff --git a/SDFTools/examples/example8/generated/scheduler.cpp b/SDFTools/examples/example8/generated/scheduler.cpp index 3b320e24..0a75cf66 100644 --- a/SDFTools/examples/example8/generated/scheduler.cpp +++ b/SDFTools/examples/example8/generated/scheduler.cpp @@ -24,6 +24,7 @@ FIFO buffers #define FIFOSIZE2 5 #define FIFOSIZE3 5 #define FIFOSIZE4 5 +#define FIFOSIZE5 5 #define BUFFERSIZE0 11 complex buf0[BUFFERSIZE0]={0}; @@ -40,6 +41,9 @@ complex buf3[BUFFERSIZE3]={0}; #define BUFFERSIZE4 5 complex buf4[BUFFERSIZE4]={0}; +#define BUFFERSIZE5 5 +complex buf5[BUFFERSIZE5]={0}; + uint32_t scheduler(int *error,int someVariable) { @@ -55,15 +59,17 @@ uint32_t scheduler(int *error,int someVariable) FIFO fifo2(buf2); FIFO fifo3(buf3); FIFO fifo4(buf4); + FIFO fifo5(buf5); /* Create node objects */ - Duplicate3 dup(fifo1,fifo2,fifo3,fifo4); - ProcessingNode filter(fifo0,fifo1,4,"Test",someVariable); - Sink sa(fifo2); - Sink sb(fifo3); - Sink sc(fifo4); + Duplicate3 dup0(fifo2,fifo3,fifo4,fifo5); + ProcessingNode filter(fifo0,fifo2,fifo1,4,"Test",someVariable); + Sink sa(fifo3); + Sink sb(fifo4); + Sink sc(fifo5); + Sink sd(fifo1); Source source(fifo0); /* Run several schedule iterations */ @@ -76,7 +82,9 @@ uint32_t scheduler(int *error,int someVariable) CHECKERROR; sdfError = filter.run(); CHECKERROR; - sdfError = dup.run(); + sdfError = sd.run(); + CHECKERROR; + sdfError = dup0.run(); CHECKERROR; sdfError = sc.run(); CHECKERROR; @@ -90,7 +98,9 @@ uint32_t scheduler(int *error,int someVariable) CHECKERROR; sdfError = source.run(); CHECKERROR; - sdfError = dup.run(); + sdfError = sd.run(); + CHECKERROR; + sdfError = dup0.run(); CHECKERROR; sdfError = sc.run(); CHECKERROR; @@ -102,7 +112,9 @@ uint32_t scheduler(int *error,int someVariable) CHECKERROR; sdfError = filter.run(); CHECKERROR; - sdfError = dup.run(); + sdfError = sd.run(); + CHECKERROR; + sdfError = dup0.run(); CHECKERROR; sdfError = sc.run(); CHECKERROR; @@ -116,7 +128,9 @@ uint32_t scheduler(int *error,int someVariable) CHECKERROR; sdfError = source.run(); CHECKERROR; - sdfError = dup.run(); + sdfError = sd.run(); + CHECKERROR; + sdfError = dup0.run(); CHECKERROR; sdfError = sc.run(); CHECKERROR; @@ -126,7 +140,9 @@ uint32_t scheduler(int *error,int someVariable) CHECKERROR; sdfError = filter.run(); CHECKERROR; - sdfError = dup.run(); + sdfError = sd.run(); + CHECKERROR; + sdfError = dup0.run(); CHECKERROR; sdfError = sc.run(); CHECKERROR; diff --git a/SDFTools/examples/example8/graph.py b/SDFTools/examples/example8/graph.py index 7124c645..a8c84851 100644 --- a/SDFTools/examples/example8/graph.py +++ b/SDFTools/examples/example8/graph.py @@ -6,7 +6,8 @@ class Node(GenericNode): def __init__(self,name,theType,inLength,outLength): GenericNode.__init__(self,name) self.addInput("i",theType,inLength) - self.addOutput("o",theType,outLength) + self.addOutput("oa",theType,outLength) + self.addOutput("ob",theType,outLength) class Sink(GenericSink): def __init__(self,name,theType,inLength): @@ -52,44 +53,54 @@ b.addVariableArg("someVariable") na = Sink("sa",complexType,5) nb = Sink("sb",complexType,5) nc = Sink("sc",complexType,5) -dup=Duplicate3("dup",complexType,5) +nd = Sink("sd",complexType,5) + +#dup=Duplicate3("dup",complexType,5) g = Graph() g.connect(src.o,b.i) -g.connect(b.o,dup.i) -g.connect(dup.oa,na.i) -g.connect(dup.ob,nb.i) -g.connect(dup.oc,nc.i) +#g.connect(b.o,dup.i) +#g.connect(dup.oa,na.i) +#g.connect(dup.ob,nb.i) +#g.connect(dup.oc,nc.i) + +g.connect(b.oa,na.i) +g.connect(b.oa,nb.i) +g.connect(b.oa,nc.i) +g.connect(b.ob,nd.i) + +GEN_PYTHON = False print("Generate graphviz and code") +sched = g.computeSchedule() +print("Schedule length = %d" % sched.scheduleLength) +print("Memory usage %d bytes" % sched.memory) + +# Generation of the schedule is modifying the original graph +# (Introduction of duplicate nodes ...) +# So we cannot reuse the graph to compute the Python and the C +# code generation conf=Configuration() conf.debugLimit=1 conf.cOptionalArgs="int someVariable" -#conf.displayFIFOSizes=True -# Prefix for global FIFO buffers -#conf.prefix="sched1" - -#print(g.nullVector()) -sched1 = g.computeSchedule() -#print(sched.schedule) -print("Schedule length = %d" % sched1.scheduleLength) -print("Memory usage %d bytes" % sched1.memory) -# - #conf.codeArray=True -# C++ implementation -sched1.ccode("generated",conf) +conf.memoryOptimization=True + +if not GEN_PYTHON: + # C++ implementation + sched.ccode("generated",conf) +else: + # Python implementation + conf.pyOptionalArgs="someVariable" + sched.pythoncode(".",config=conf) -sched2 = g.computeSchedule() -# Python implementation -#conf.prefix="sched1" -conf.pyOptionalArgs="someVariable" -#conf.dumpFIFO=True -sched2.pythoncode(".",config=conf) +# When true it is displaying the name of the FIFO buffer +# When false it is displaying the size of the FIFO (default) +conf.displayFIFOBuf=False with open("test.dot","w") as f: - sched1.graphviz(f) + sched.graphviz(f,config=conf) diff --git a/SDFTools/examples/example8/sched.py b/SDFTools/examples/example8/sched.py index 9d326446..00603162 100644 --- a/SDFTools/examples/example8/sched.py +++ b/SDFTools/examples/example8/sched.py @@ -51,6 +51,12 @@ buf4=np.empty(FIFOSIZE4,dtype=object) for i in range(FIFOSIZE4): buf4[i] = MyComplex() +FIFOSIZE5=5 + +buf5=np.empty(FIFOSIZE5,dtype=object) +for i in range(FIFOSIZE5): + buf5[i] = MyComplex() + def scheduler(someVariable): sdfError=0 @@ -65,15 +71,17 @@ def scheduler(someVariable): fifo2=FIFO(FIFOSIZE2,buf2) fifo3=FIFO(FIFOSIZE3,buf3) fifo4=FIFO(FIFOSIZE4,buf4) + fifo5=FIFO(FIFOSIZE5,buf5) # # Create node objects # - dup = Duplicate3(5,5,5,5,fifo1,fifo2,fifo3,fifo4) - filter = ProcessingNode(7,5,fifo0,fifo1,4,"Test",someVariable) - sa = Sink(5,fifo2) - sb = Sink(5,fifo3) - sc = Sink(5,fifo4) + dup0 = Duplicate3(5,5,5,5,fifo2,fifo3,fifo4,fifo5) + filter = ProcessingNode(7,5,5,fifo0,fifo2,fifo1,4,"Test",someVariable) + sa = Sink(5,fifo3) + sb = Sink(5,fifo4) + sc = Sink(5,fifo5) + sd = Sink(5,fifo1) source = Source(5,fifo0) while((sdfError==0) and (debugCounter > 0)): @@ -88,7 +96,10 @@ def scheduler(someVariable): sdfError = filter.run() if sdfError < 0: break - sdfError = dup.run() + sdfError = sd.run() + if sdfError < 0: + break + sdfError = dup0.run() if sdfError < 0: break sdfError = sc.run() @@ -109,7 +120,10 @@ def scheduler(someVariable): sdfError = source.run() if sdfError < 0: break - sdfError = dup.run() + sdfError = sd.run() + if sdfError < 0: + break + sdfError = dup0.run() if sdfError < 0: break sdfError = sc.run() @@ -127,7 +141,10 @@ def scheduler(someVariable): sdfError = filter.run() if sdfError < 0: break - sdfError = dup.run() + sdfError = sd.run() + if sdfError < 0: + break + sdfError = dup0.run() if sdfError < 0: break sdfError = sc.run() @@ -148,7 +165,10 @@ def scheduler(someVariable): sdfError = source.run() if sdfError < 0: break - sdfError = dup.run() + sdfError = sd.run() + if sdfError < 0: + break + sdfError = dup0.run() if sdfError < 0: break sdfError = sc.run() @@ -163,7 +183,10 @@ def scheduler(someVariable): sdfError = filter.run() if sdfError < 0: break - sdfError = dup.run() + sdfError = sd.run() + if sdfError < 0: + break + sdfError = dup0.run() if sdfError < 0: break sdfError = sc.run() diff --git a/SDFTools/examples/example8/test.dot b/SDFTools/examples/example8/test.dot index 178f4722..992660b3 100644 --- a/SDFTools/examples/example8/test.dot +++ b/SDFTools/examples/example8/test.dot @@ -1,6 +1,7 @@ + digraph structs { node [shape=plaintext] rankdir=LR @@ -8,33 +9,24 @@ digraph structs { fontname="times" +dup0 [shape=point,label=dup0] + -dup [label=< +filter [label=< - + - - - -
idup
(Duplicate3)
filter
(ProcessingNode)
oa
ob
oc
>]; -filter [label=< - - - - -
filter
(ProcessingNode)
>]; - sa [label=< @@ -56,6 +48,13 @@ sc [label=<
>]; +sd [label=< + + + + +
sd
(Sink)
>]; + source [label=< @@ -65,35 +64,41 @@ source [label=< -source:i -> filter:i [headlabel=< -
7 -
>,taillabel=< -
5 -
>,label="complex(11)"] - -filter:i -> dup:i [headlabel=< -
5 -
>,taillabel=< -
5 -
>,label="complex(5)"] - -dup:oa -> sa:i [headlabel=< -
5 -
>,taillabel=< -
5 -
>,label="complex(5)"] - -dup:ob -> sb:i [headlabel=< -
5 -
>,taillabel=< -
5 -
>,label="complex(5)"] - -dup:oc -> sc:i [headlabel=< -
5 -
>,taillabel=< -
5 -
>,label="complex(5)"] +source:i -> filter:i [label="complex(11)" +,headlabel=<
7 +
> +,taillabel=<
5 +
>] + +filter:ob -> sd:i [label="complex(5)" +,headlabel=<
5 +
> +,taillabel=<
5 +
>] + +filter:oa -> +dup0 [label="complex(5)" + +,taillabel=<
5 +
>] + + +dup0 -> sa:i [label="complex(5)" +,headlabel=<
5 +
> +] + + +dup0 -> sb:i [label="complex(5)" +,headlabel=<
5 +
> +] + + +dup0 -> sc:i [label="complex(5)" +,headlabel=<
5 +
> +] } diff --git a/SDFTools/examples/example8/test.pdf b/SDFTools/examples/example8/test.pdf index 1d2051e5926484c9374ae5ec5d3917841254a776..acfc5690d8dd3d3fbc4028d8aba763552fb97eb7 100644 GIT binary patch delta 23006 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 delta 23575 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 diff --git a/cmsisdsp/sdf/nodes/Duplicate.py b/cmsisdsp/sdf/nodes/Duplicate.py index 4a00e024..ab86284c 100644 --- a/cmsisdsp/sdf/nodes/Duplicate.py +++ b/cmsisdsp/sdf/nodes/Duplicate.py @@ -43,7 +43,7 @@ class Duplicate2(GenericNode12): class Duplicate3(GenericNode13): def __init__(self,inputSize,outputSize1,outputSize2,outputSize3,fifoin,fifoout1,fifoout2,fifoout3): GenericNode13.__init__(self,inputSize,outputSize1,outputSize2,outputSize3,fifoin,fifoout1,fifoout2,fifoout3) - + def run(self): a=self.getReadBuffer() b=self.getWriteBuffer1() diff --git a/cmsisdsp/sdf/scheduler/description.py b/cmsisdsp/sdf/scheduler/description.py index 6a31ddf3..14270466 100755 --- a/cmsisdsp/sdf/scheduler/description.py +++ b/cmsisdsp/sdf/scheduler/description.py @@ -38,6 +38,8 @@ import cmsisdsp.sdf.scheduler.pythoncode from .node import * from .config import * +from .standard import Duplicate2,Duplicate3 + from ..types import * # To debug graph coloring for memory optimization @@ -58,6 +60,9 @@ class DeadlockError(Exception): class CannotDelayConstantError(Exception): pass +class TooManyNodesOnOutput(Exception): + pass + class FifoBuffer: """Buffer used by a FIFO""" def __init__(self,bufferID,theType,length): @@ -165,7 +170,128 @@ class Graph(): self._allFIFOs = None self._allBuffers = None - def connect(self,nodea,nodeb): + def connectDup(self,destination,outputIO,theId): + if (destination[theId][1]!=0): + self.connectWithDelay(outputIO,destination[theId][0],destination[theId][1],dupAllowed=False) + else: + self.connect(outputIO,destination[theId][0],dupAllowed=False) + + + + def insertDuplicates(self): + # Insert dup nodes (Duplicate2 and Duplicate3) to + # ensure that an output is connected to only one input + dupNb = 0 + # Scan all nodes + allNodes = list(self._g) + for n in allNodes: + # For each nodes, get the IOs + for k in n._outputs.keys(): + output = n._outputs[k] + fifo = output.fifo + # Check if the FIFO list has only 1 element + # In this case, we convert it into a value + # since code using the graph is expecting an + # IO to be connected top one and only one other IO + # so the FIFO must be a value and not a list + if len(fifo)==1: + # Extract first element of list + fifo = fifo[0] + fifo[0].fifo = fifo + fifo[1].fifo = fifo + # If the FIFO has more elements, we need to + # restructure the graph and add Duplicate nodes + else: + # Currently the library is only providing + # Duplicate2 and Duplicate3 nodes. + # So an output cannot be connected to more than + # 3 inputs + if (len(fifo)>3): + raise TooManyNodesOnOutput + + dup = None + # We extract from the IO the nb of produced + # samples and the data type + # Duplicate will use the same + inputSize = output.nbSamples + theType = output.theType + + # We create a duplicate node + if len(fifo)==2: + dup = Duplicate2("dup%d" % dupNb,theType,inputSize) + + if len(fifo)==3: + dup = Duplicate3("dup%d" % dupNb,theType,inputSize) + + #print(dup) + + + + dupNb = dupNb + 1 + # We disconnect all the fifo element (a,b) + # and remember the destination b + # We must rewrite the edges of self._g + # self._edges + # self._nodes + # the node fifo + destinations = [] + delays = [] + + self._sortedNodes = None + self._sortedEdges = None + for f in fifo: + # IO connected to the FIFOs + # nodea is the IO belowing to nodea + # but not the node itself + nodea = f[0] + nodeb = f[1] + + if (nodea,nodeb) in self._delays: + delay = self._delays[(nodea,nodeb)] + else: + delay = 0 + + destinations.append((nodeb,delay)) + + nodea.fifo=None + nodeb.fifo=None + # Since we are not using a multi graph + # and there no parallel edges, it will + # remove all edges between two nodes. + # But some edges may come from other IO + # and be valid. + # Anyway, those edges are not used + # in the algorithm at all + # Instead we have our own graph with self._edges + # (This script will have to be rewritten in a much + # cleaner way) + self._g.remove_edge(nodea.owner,nodeb.owner) + del self._edges[(nodea,nodeb)] + if (nodea,nodeb) in self._delays: + del self._delays[(nodea,nodeb)] + + + + # Now for each fifo destination we need + # create a new + # connection dup -> b + # And we create a new connection a -> dup + + self.connect(output,dup.i,dupAllowed=False) + + if len(destinations)==2: + self.connectDup(destinations,dup.oa,0) + self.connectDup(destinations,dup.ob,1) + + if len(destinations)==3: + self.connectDup(destinations,dup.oa,0) + self.connectDup(destinations,dup.ob,1) + self.connectDup(destinations,dup.oc,2) + + + + + def connect(self,nodea,nodeb,dupAllowed=True): # When connecting to a constant node we do nothing # since there is no FIFO in this case # and it does not participate to the scheduling. @@ -178,8 +304,12 @@ class Graph(): self._sortedEdges = None self._g.add_edge(nodea.owner,nodeb.owner) - nodea.fifo = (nodea,nodeb) - nodeb.fifo = (nodea,nodeb) + if dupAllowed: + nodea.fifo.append((nodea,nodeb)) + nodeb.fifo.append((nodea,nodeb)) + else: + nodea.fifo=(nodea,nodeb) + nodeb.fifo=(nodea,nodeb) self._edges[(nodea,nodeb)]=True if not (nodea.owner in self._nodes): self._nodes[nodea.owner]=True @@ -188,12 +318,12 @@ class Graph(): else: raise IncompatibleIO - def connectWithDelay(self,nodea,nodeb,delay): + def connectWithDelay(self,nodea,nodeb,delay,dupAllowed=True): # We cannot connect with delay to a constant node if (isinstance(nodea,Constant)): raise CannotDelayConstantError else: - self.connect(nodea,nodeb) + self.connect(nodea,nodeb,dupAllowed=dupAllowed) self._delays[(nodea,nodeb)] = delay def __str__(self): @@ -426,7 +556,14 @@ class Graph(): v[nodeID] = 1 return(v) + + def computeSchedule(self,config=Configuration()): + # First we must rewrite the graph and insert duplication + # nodes when an ouput is connected to several inputs. + # After this transform, each output should be connected to + # only one output. + self.insertDuplicates() # Init values initB = self.initEvolutionVector initN = self.nullVector() diff --git a/cmsisdsp/sdf/scheduler/node.py b/cmsisdsp/sdf/scheduler/node.py index f7c0ee21..9b405a2b 100755 --- a/cmsisdsp/sdf/scheduler/node.py +++ b/cmsisdsp/sdf/scheduler/node.py @@ -54,7 +54,7 @@ class IO: self._nbSamples = nbSamples self._owner = owner self._name = name - self._fifo = None + self._fifo = [] self.constantNode = None @property @@ -214,6 +214,10 @@ class BaseNode: def isConstantNode(self): return False + @property + def isDuplicateNode(self): + return False + @property def hasState(self): """False if the node is a pure functiom with no state diff --git a/cmsisdsp/sdf/scheduler/standard.py b/cmsisdsp/sdf/scheduler/standard.py index 0d63dfc0..21d66bc1 100755 --- a/cmsisdsp/sdf/scheduler/standard.py +++ b/cmsisdsp/sdf/scheduler/standard.py @@ -144,6 +144,10 @@ class Duplicate2(GenericNode): self.addOutput("oa",theType,inLength) self.addOutput("ob",theType,inLength) + @property + def isDuplicateNode(self): + return True + @property def typeName(self): return "Duplicate2" @@ -157,6 +161,10 @@ class Duplicate3(GenericNode): self.addOutput("ob",theType,inLength) self.addOutput("oc",theType,inLength) + @property + def isDuplicateNode(self): + return True + @property def typeName(self): return "Duplicate3" diff --git a/cmsisdsp/sdf/scheduler/templates/dot_template.dot b/cmsisdsp/sdf/scheduler/templates/dot_template.dot index abfa5a74..083a6c05 100755 --- a/cmsisdsp/sdf/scheduler/templates/dot_template.dot +++ b/cmsisdsp/sdf/scheduler/templates/dot_template.dot @@ -1,5 +1,14 @@ {% macro io(node,theio) -%} +{% if node.isDuplicateNode %} +{{node.nodeName}}{% else %} {% if not node.hasManyIOs %}{{node.nodeID}}:i{% else %}{{node.nodeID}}:{{theio.name}}{% endif %} +{% endif %} +{%- endmacro %} + +{% macro edgelabel(ioport,name) -%} +{% if not ioport.owner.isDuplicateNode %} +,{{name}}=<
{{ioport.nbSamples}} +
>{% endif %} {%- endmacro %} {% macro constdst(theID) -%} @@ -29,6 +38,9 @@ digraph structs { {% for item in nodes %} +{% if item.isDuplicateNode %} +{{item.nodeID}} [shape=point,label={{item.nodeName}}] +{% else %} {% if not item.hasManyIOs %} {{item.nodeID}} [label=< @@ -104,6 +116,7 @@ digraph structs {
>]; {% endif %} {% endif %} +{% endif %} {% endfor %} @@ -111,33 +124,25 @@ digraph structs { {{delayBox(id)}} {% if fifos[id].hasDelay %} -{{io(fifos[id].src.owner,fifos[id].src)}} -> {{delayBoxID(id)}}:i [taillabel=< -
{{fifos[id].src.nbSamples}} -
>] +{{io(fifos[id].src.owner,fifos[id].src)}} -> {{delayBoxID(id)}}:i [label=""{{edgelabel(fifos[id].src,"taillabel")}}] {% if config.displayFIFOBuf %} -{{delayBoxID(id)}}:i -> {{io(fifos[id].dst.owner,fifos[id].dst)}} [headlabel=< -
{{fifos[id].dst.nbSamples}} -
>,label="buf{{fifos[id].bufferID}}"] +{{delayBoxID(id)}}:i -> {{io(fifos[id].dst.owner,fifos[id].dst)}} [label="buf{{fifos[id].bufferID}}" +{{edgelabel(fifos[id].src,"headlabel")}}] {% else %} -{{delayBoxID(id)}}:i -> {{io(fifos[id].dst.owner,fifos[id].dst)}} [headlabel=< -
{{fifos[id].dst.nbSamples}} -
>,label="{{fifos[id].theType.graphViztype}}({{fifos[id].length}})"] +{{delayBoxID(id)}}:i -> {{io(fifos[id].dst.owner,fifos[id].dst)}} [label="{{fifos[id].theType.graphViztype}}({{fifos[id].length}})" +{{edgelabel(fifos[id].dst,"headlabel")}}] {% endif %} {% else %} {% if config.displayFIFOBuf %} -{{io(fifos[id].src.owner,fifos[id].src)}} -> {{io(fifos[id].dst.owner,fifos[id].dst)}} [headlabel=< -
{{fifos[id].dst.nbSamples}} -
>,taillabel=< -
{{fifos[id].src.nbSamples}} -
>,label="buf{{fifos[id].bufferID}}"] +{{io(fifos[id].src.owner,fifos[id].src)}} -> {{io(fifos[id].dst.owner,fifos[id].dst)}} [label="buf{{fifos[id].bufferID}}" +{{edgelabel(fifos[id].dst,"headlabel")}} +{{edgelabel(fifos[id].src,"taillabel")}}] {% else %} -{{io(fifos[id].src.owner,fifos[id].src)}} -> {{io(fifos[id].dst.owner,fifos[id].dst)}} [headlabel=< -
{{fifos[id].dst.nbSamples}} -
>,taillabel=< -
{{fifos[id].src.nbSamples}} -
>,label="{{fifos[id].theType.graphViztype}}({{fifos[id].length}})"] +{{io(fifos[id].src.owner,fifos[id].src)}} -> {{io(fifos[id].dst.owner,fifos[id].dst)}} [label="{{fifos[id].theType.graphViztype}}({{fifos[id].length}})" +{{edgelabel(fifos[id].dst,"headlabel")}} +{{edgelabel(fifos[id].src,"taillabel")}}] {% endif %} {% endif %} {% endfor %}