From 58d041757169358827127436fa4bf2eb6016975b Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Tue, 7 Nov 2023 18:01:22 +0100 Subject: [PATCH 01/15] Add pipelining API documentation --- source/SpinalHDL/Libraries/Pipeline/index.rst | 8 + .../Libraries/Pipeline/introduction.rst | 299 ++++++++++++++++++ source/SpinalHDL/Libraries/index.rst | 1 + source/asset/image/pipeline/intro_pip.png | Bin 0 -> 40000 bytes source/asset/image/pipeline/simple_pip.png | Bin 0 -> 34923 bytes source/asset/image/pipeline/simple_wave.png | Bin 0 -> 24249 bytes 6 files changed, 308 insertions(+) create mode 100644 source/SpinalHDL/Libraries/Pipeline/index.rst create mode 100644 source/SpinalHDL/Libraries/Pipeline/introduction.rst create mode 100644 source/asset/image/pipeline/intro_pip.png create mode 100644 source/asset/image/pipeline/simple_pip.png create mode 100644 source/asset/image/pipeline/simple_wave.png diff --git a/source/SpinalHDL/Libraries/Pipeline/index.rst b/source/SpinalHDL/Libraries/Pipeline/index.rst new file mode 100644 index 00000000000..ce3841e91cb --- /dev/null +++ b/source/SpinalHDL/Libraries/Pipeline/index.rst @@ -0,0 +1,8 @@ +============ +Pipeline +============ + +.. toctree:: + :glob: + + * diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst new file mode 100644 index 00000000000..9cf634c61ad --- /dev/null +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -0,0 +1,299 @@ + +Introduction +============ + +spinal.lib.misc.pipeline provide a pipelining API. The main advantages over manual pipelining are : + +- You don't have to pre define data structure with all the element which goes through each individual stage +- You can compose the pipeline (as a consequence of the above point) +- Manual retiming is much easier, as you don't have to handle the registers / arbitration manualy +- Manage the arbitration by itself + +The API is composed of 4 main things : + +- Node : which represent a layer in the pipeline +- Connector : which allows to connect nodes to each others +- Builder : which will generate the hardware required for a whole pipeline +- Stageable : which represent a data thing that you can retrieve Nodes the pipeline + +It is important to understande that Stageable isn't a hardware data instance, but a key to retrieve a data along the pipeline on nodes. + +Here is an example to illustrate : + +.. image:: /asset/image/pipeline/intro_pip.png + :scale: 70 % + +Simple example +---------------- + +Here is a simple example + + +.. code-block:: scala + + import spinal.core._ + import spinal.core.sim._ + import spinal.lib._ + import spinal.lib.misc.pipeline._ + + class TopLevel extends Component { + val io = new Bundle{ + val up = slave Stream (UInt(16 bits)) + val down = master Stream (UInt(16 bits)) + } + + // Let's define 3 Nodes for our pipeline + val n0, n1, n2 = Node() + + // Let's connect those nodes by using simples registers + val s01 = StageConnector(n0, n1) + val s12 = StageConnector(n1, n2) + + // Let's define a few stageable things that can go through the pipeline + val VALUE = Stageable(UInt(16 bits)) + val RESULT = Stageable(UInt(16 bits)) + + // Let's bind io.up to n0 + io.up.ready := n0.ready + n0.valid := io.up.valid + n0(VALUE) := io.up.payload + + // Let's do some processing on n1 + n1(RESULT) := n1(VALUE) + 0x1200 + + // Let's bind n2 to io.down + n2.ready := io.down.ready + io.down.valid := n2.valid + io.down.payload := n2(RESULT) + + // Let's ask the builder to generate all the required hardware + Builder(s01, s12) + } + +This will produce the following hardware : + +.. image:: /asset/image/pipeline/simple_pip.png + :scale: 70 % + +Here is a simulation wave : + +.. image:: /asset/image/pipeline/simple_wave.png + + + +Stageable +============ + +Stageable class can be instanciated to represent some data which can go through the pipeline. Technicaly speaking, Stageable is a HardType which has a name and is used as a "key" to retrieve stuff. + +.. code-block:: scala + + val PC = Stageable(UInt(32 bits)) + val PC_PLUS_4 = Stageable(UInt(32 bits)) + + val n0, n1 = Node() + val s01 = StageConnector(n0, n1) + + n0(PC) := 0x42 + n1(PC_PLUS_4) := n1(PC) + 4 + + +Node +============ + +Node mostly host the valid/ready arbitration signal, and the hardware signal required for all the Stageable values going through it. + +You can access its arbitration signals via : + +- node.valid / node.isValid +- node.ready / node.isReady +- node.isFireing which is a shortcut for `valid && ready` + +You can access its stageable's signals via : + +.. list-table:: + :header-rows: 1 + :widths: 2 5 + + * - API + - Description + * - node(Stageable) + - Return the corresponding hardware signal + * - node(Stageable, Any) + - Same as above, but include a second argument which is used as a "secondary key". This ease the construction of multi lane hardware. For instance, when you have a multi issue CPU pipeline, you can use the lane Int id as secondary key + * - node.insert(Data) + - Return a new Stageable instance which is connected to the given Data hardware signal + + + +.. code-block:: scala + + val n0, n1 = Node() + + val PC = Stageable(UInt(32 bits)) + n0(PC) := 0x42 + n0(PC, "true") := 0x42 + n0(PC, 0x666) := 0xEE + val SOMETHING = n0.insert(myHardwareSignal) //This create a new Stageable + when(n1(SOMETHING) === 0xFFAA){ ... } + + +Connectors +============ + +There is few different connectors already implemented (but you could also create your own custom one) : + +DirectConnector +------------------ + +Very simple, it connect two nodes with wires only. Here is an example : + + +.. code-block:: scala + + val c01 = DirectConnector(n0, n1) + + + +StageConnector +------------------ + +This connect two nodes using registers on the data / valid signals and some arbitration on the ready. + +.. code-block:: scala + + val c01 = StageConnector(n0, n1) + + +S2mConnector +------------------ + +This connect two nodes using registers on the ready signal, which can be usefull to improve backpresure combinatorial timings. + +.. code-block:: scala + + val c01 = S2mConnector(n0, n1) + +CtrlConnector +------------------ + +This is kind of a special connector, as connect two nodes with optional flow control / bypass logic. Its API should be flexible enough to implement a CPU stage with it. + +Here is its flow control API (The Bool argument enable the feature) : + +.. list-table:: + :header-rows: 1 + :widths: 2 5 + + * - API + - Description + * - haltWhen(Bool) + - Allows to block the current transaction (clear up.ready down.valid) + * - throwWhen(Bool) + - Allows to remove the current transaction from the pipeline (clear down.valid and remove the transaction driver) + * - removeSeedWhen(Bool) + - Allows to remove the transaction driver (but doesn't clear the down.valid) + * - duplicateWhen(Bool) + - Allows to duplicate the current transaction (clear up.ready) + * - terminateWhen(Bool) + - Allows to hide the current transaction from downstream (clear down.valid) + +Also note that if you want to do flow control in a conditional scope (ex in a when statement), you can call the following functions : + +- haltIt(), duplicateIt(), terminateIt(), removeSeedIt(), throwIt() + +.. code-block:: scala + + val c01 = CtrlConnector(n0, n1) + + c01.haltWhen(something) + when(somethingElse){ + c01.haltIt() + } + +You can retrieve which node are connected using node.up / node.down. + +The CtrlConnector also provide an API to access stageable : + +.. list-table:: + :header-rows: 1 + :widths: 2 5 + + * - API + - Description + * - connector(Stageable) + - Same as connector.down(Stageable) + * - connector(Stageable, Any) + - Same as connector.down(Stageable, Any) + * - connector.insert(Data) + - Same as connector.down.insert(Data) + * - connector.bypass(Stageable) + - Allows to conditionaly override a stageable value between connector.up -> connector.down. This can be used to fix data hazard in CPU pipelines for instance. + + +.. code-block:: scala + + val c01 = CtrlConnector(n0, n1) + + val PC = Stageable(UInt(32 bits)) + c01(PC) := 0x42 + c01(PC, 0x666) := 0xEE + + val DATA = Stageable(UInt(32 bits)) + // Let's say Data is inserted in the pipeline before c01 + when(hazard){ + c01.bypass(DATA) := fixedValue + } + + // c01(DATA) and below will get the hazard patch + +Note that if you create a CtrlConnector without node arguements, it will create its own nodes internaly. + +.. code-block:: scala + + val decode = CtrlConnector() + val execute = CtrlConnector() + + val d2e = StageConnector(decode.down, execute.up) + + +Other connectors +------------------------------------ + +There is also a JoinConnector / ForkConnector implemented. + +Your custom connector +------------------------------------ + +You can implement your custom connectors by implementing the Connector base class. + +.. code-block:: scala + + trait Connector extends Area{ + def ups : Seq[Node] + def downs : Seq[Node] + + def propagateDown(): Unit + def propagateUp(): Unit + def build() : Unit + } + + +Builder +============ + +To generate the hardware of your pipeline, you need to give a list of all the connectors used in your pipeline. + + +.. code-block:: scala + + // Let's define 3 Nodes for our pipeline + val n0, n1, n2 = Node() + + // Let's connect those nodes by using simples registers + val s01 = StageConnector(n0, n1) + val s12 = StageConnector(n1, n2) + + // Let's ask the builder to generate all the required hardware + Builder(s01, s12) + diff --git a/source/SpinalHDL/Libraries/index.rst b/source/SpinalHDL/Libraries/index.rst index 136f2684478..e7dd368b68e 100644 --- a/source/SpinalHDL/Libraries/index.rst +++ b/source/SpinalHDL/Libraries/index.rst @@ -37,5 +37,6 @@ To use features introduced in followings chapter you need, in most of cases, to IO/index Graphics/index EDA/index + Pipeline/index Misc/index diff --git a/source/asset/image/pipeline/intro_pip.png b/source/asset/image/pipeline/intro_pip.png new file mode 100644 index 0000000000000000000000000000000000000000..a9b7b04e9fe9669b448ba91fa9ba31866e1be2c6 GIT binary patch literal 40000 zcmeEv2UJu`x~^c8bCyh#QE~=J-DHW9Bq%vbjy6$gkQ{9gkf@@9qC`c>AVEZe7>JUS z0+K;c(}CxKO(Hu3fu!)n9-3zHcvM^>x)r2pI^E9Xm#%si9(U z>=>3a_&bb`3yuiPzj*+DVfh-UBac1mVfuLNm~e=ns;Qql%F)Hm;TV^g^3P9PqQcIe zzJ6R{DqNzXs%{Q;em?KCE^cl%T%u|cg2JFHUQ-bfE-@tdr0wG2j{?6@(vnt^;(Xu} z!pqCe;hcl5mWv#At)*>1J3;ZP->zQfc8tD9dc)vg|ho5ci9RghJ zz(_>7#8kP25#W00M@-q?#m33U#vOd|umL0eaTk3sX96NWdvH0N=u6&GW(G2W-U0sl zNEIV{M4+2C=;V*R9rfvsQund(a@O&*cW?t8+oKK#FAg3Y8o7Po;Q?v!p9h?Lj&5^! zSLlGDOVHtXM8H^~>zw>u>>Yf6UVGTk&(qV*&&BJH2kksPJb=mkp`neBkLRU7HgoiJ zJAAmq&b+{L|KTF&fT5j@+g~0%=VI^Y{IjjJ#NqIu)*22jPR>Vn77;!?<8E`*`0#+Q zvyHvyrJvXTIP~kH{G7(4$@KB`1pWQ8X1|_3YUGYmc5s8{`e+^x?*nY<|Nh$FX2a*V zo%8?F9auSN@qGOPkLJ5H`(){Xd zp>uyfEdKC0|Ckq%IGmZk;YIv>Tx>j?pn!<{JG1QL=?7xSj|5OaR0;}W|D1V03>kl8 z-jC9_lMAQoug-0jBCnu zuQW7FQ1rSw990@eImF*dC_uUB=K@L=gqw>KlxVoS*xUbbGyfE?apZ~*?MCd#j(+jh zM~T3%UgLk&TOX#*e|6Cycl!_cg#Xkhf4e>r7Z(=%y*L4%{uk;KaY;};9;QV9PHycG zL;1yAq1f}kALNcA|G#gPlMw#*w1}gb|Nj!{K(_gxNcS7`@H^)6r%3m!H~Ci%b^!4H zQ4{~-K7LP8{uAvW)b4*Ajgt`lOTY3z4|j1Mz{%;YR}Zhh!gOIev+jf6VCq4(Rkx z!G->W;)6=-=WT!SBS#7KzYH!UatKQNt(pFJgYo~`#2;f4C$7 zS+5MfgA(lMJ2(V2_wiHq^Z;*JFJd=+2X%h8sd;=`^B&R$I^fW{>xdK zxahxwr5(byzgXH29*Nf%sQ4V94Q>h~g#Ynuz+*u~*T0fy1Hj79l{q3q|4Q<5)Z||# zh8{6$f3i2^f9ws?viwU&(!a1bfY}@oRlfw&e?5~sgqHtga?1ZPxqmy81KQEwV8VX^ zllvhW`7dt(7QN zd9ly*u}CD`zw=`tlvN7Hf7}Q~jm71h%%Jwp0P0nVz&syIl?@MOd3oRe<4AR>xJ$o^ zS`t&SG#|E$x)Ooxa*Ato7-Ay*oMq}_X`|(%+Rw7|VvY$`Z%yQnM_!@yyOL0>>`fQ$ zJ>A~XS51~XbEOu94wJxfu zE^uR}Zled&p2*mR#~Lyrv1OD`&Q<_p#^zWgr&6|VM&tNw4>@T)k0P0!PFB<{p{UoQ z(7?vxTHtJYV-bL%pwv>{tPbT}hWFt11}B)|Jn{IGvpBHp0v6o&aszI> zeRRR1;mZ_}P*I_5Br&)=h5mdr<7wbEsmyqyy)CXY(Y4%0LqkM z?NxHqO2@<;)Ksj&LvsnU`XK*R+#-&3IjDcB)RUQfu=k}_LOeNMXruc^n(rT4WbjE; zjgxbL@!L)>_W7z$bg*7dn@&2d`!YjFh*8SJ=qYu06*!$6@Zzy@BU_n67cHOR-Gns3 z3+7?pcbyuG9tPp!gV7zMH;kSV#BTh)_kbyM^R>Y(6@sCL(AwHWSJyZtVqkC`$F5mY z>cRq+hq+YaY1289G{UiQUgb9)d_VoC#iPUCU7y7qeE+KVeSgPlcl_Wx-J|A3 zXR#z;JncFKgedxOG!D_r@B3dJ#v4L?7H&odnHj$mxB^Cp*H!I}kjK9dqtH|9m-IHd&Wx$x60X|-X}Dfwdg zwE@c)DM25%{R#DwqChPpjhd&noJ+-di-oG}P0NEH=*t=Jt-d zZd&j{jRtu3*+IMH#3JJgd(GY13@dwJ<36uMIx_QpQ!eK#ghozlq~6iWxM8?rM2%Gq zW+i~}tb6YzeDRBKHRgKrJvV#u6&ug{c1!r|e7K~Q$RJVZHdIj&xZ$|_@m~I`{NR=P zb7Pke_FtUH7I$G!sf%MzAq0kR7=HiB$5Sv`0dr2LT>tk7Swnu>l}v$g^0gcpU(y;} z&%IIK-mP~iW3vNb=gX`X#drBsCa!4-QrNy+!+&SOc8Nxn%@|C@Jwh}0fphp?YzLh@ z`Aiu-hWB)7VlJpsXwU%jD@K}(Y%yb>vkHBjz$hiiT_3Aw=&dSM+6v3COk)EL2(a%} zkS=R5xqUe4-dkQsZq}hB=3Xdc_+oML)GGwjoe6;aR zB+l;V+Lc%I%qQcNPJ%hn<*FHevO+#sh3=5BEZI|o}ukJ@*p{zP^ zyUTFz1eW9jViq~+5zE1n3YRzB@);a$l6`XrLf|fYoV?g)@s|e6pTA|FAx?6pm-Z^y z{QSW}#1Phx2F7G={a*K5RFg89Jm>z`o!(oLcsysbQyPt_!~MVv<&vg7CL4!0ee|a} zW%9}(KE-`3S#CXMrTrG+S32M_4~BIm^hB_gqli;k}+WwOw%Ch`ZAM z!%h1rUs2xElb(e6YV^BTf8f?r+^@pEi(g2CwN>zjusUE)3T7F$>j0Mu+TYu9oYV)C z%mW7gQN`lxOByWF_G)9z9Q7*(k74B__uuRvd|hd@VhLGoPT2D7&_aa!%)abYXALd9 zc|E00>r5Y9q-r4gnRPSvv-Ov!99m;2`fsTcdFREadLXxbF28$lF2l27w>=Z%TP*Z+ z0=qQZWJ~MFpM6li(r?HV(#xI7lZR@lk2?Oge|vevVc^jPgJJ`&a-vK(t(DPdzE{+M zZwFE8VQ+*Jd8S-oMU}^gVX-}wSt@WQCrTK^o!LS{i(pj1kysDEle*1b=sjo?-RVDY zVV*I!{?-DpT&Mf74`5*W7AA(@({W6npEZ790?aIg-6=2du8`mMwQG^`gC9&EmG>DB1)B((nYaYxL2?I&Wjrot}}^JV=2$c6y?g*kU;}^^1+4rH<3tHp8$pXFfyp$ zAtQBac9>i9Fqz^vVU^pYz53||&+T0Gk~YKfScHRy8s&=%Pu{_|huj@8R_TZDRycGQqoyM<$yN+wT%; z)EKI8;E*6MeL6)*uNW5MMO?z6NeBi~IPoSMquNB4zfjD%fQY~)D2_tCULu8&B}vKe zE!?(4n}8mhXGY@F3PN3Lta(`i$Z9UWyT86%J^m7GZ~MVAn{}{#Zum;pTv}nlgzpPC zw~3S2^ICuh*=kgI-l$ z3?H6)Pup)UysmH?65R!%@0G}1D3HmR7U^V5;(e&Rd=TcV@dnz{o87PT=5u_LWE?ju zdlkAwaO>SiBm>twc^rVNoNEZ#UC&bf{_*%V0fRz~q%S?$QhGh7SLC3$Q~X}krUm9Q z@F=&eb{(V@}q+;8{p4u#`e%tkz0KK zwtEM8m9RZtz_JciSQ>sDk5KjeT3m+U1%}<-qO-RP?K+a?EMqD_;7=V#JVmv+#dD|i z)dl))oQ}z!)*X_pcYe(-vpa*~*qL}dO&~@uO|S<7P`aV6OdB~0bVEFO`xLr^bhjUG z-W)vmBA!i^T?eVm_MqL>3UfDqp10wCi|=)!$XErX#!$Dj($*;x5%X758ZVc^mOa1j zT7J!ubJpr3UcHlHDO$Lg1{~W7cKMkU<%Dx?cR)yA2f^xP-%aINXr1s`Uu+lO%ew|L znU|+m>g>Xj@xH%2RhqeqH-Ab!-u;bcpO{lmnq&73`sA<=HU4vQ#P>j$QUwWzXISek zRx4~>L}Tse8Z8D%H)n3jQ`+p>vQ^P^c@{B|!Q!~fU(c}(V1a-=($K)1iz&!uxywur z(~v&4yV0wdxTA%d1i_cGg~@f`k%UdObC!r*)LIUKO&^{?rK1kj2G@6O6sI{pLM2+7 zmtPpQYTYRy@CamM751H!lR1kZX>@SeF2v}Hrk}l~EPft_P<~s^)Lts$JgzV$!uJTq zOr;%k_C9$`TLd>J#qv#Af}9lOWXn|tka%z}T`5a3El>)Nc+1UclRWngnCB~kpcGl} zaWaK3N(@pSA${_ZLNUsxU?cix&Q8=#Q=HT85EQNl`hMRtk>E0@phKq;Qu1Sbs7z9rCi2PR(5H!F4;?E_GNl^ zvh#7h8jyl=Ygc$pB4rj!DoIf-GfSdFViy96Lp*YS|pktV|7E{GIK6Yjq+1l1}{Ixo4hD? zDhKwV_L67dx3!LLQl<3_JDLilNA0pC)s7>wQ*-bsGL9|l)Iz7qwOnUnS(BOlz+B2I z#bw$P4?k=cU|oGGz!%6rk;+9OFr(DC+Ct>0jGt|m^59X;%bd%IyZH1Ry1T#~zp|Z< zB4MmR^+`%3ON)9tP7S_BN9zdsZGC#j(a5f*JIKznzjRew)a@<&GNb(Kb6g=H;fd~@ z%VQ|d7IDUH$4z&A^sIdbQF+2?BYv0pqgxKO({%R}Ty~W+FVHy8lNI(fKD{lr6Gn4( zJ`S79a*D)hTaa^}>OeJbs^{$WDU&5yyH3CFV7`>I))RZPazfn^FFs@F&X_7=)Zo_b zWQB_XjKIZ6S=71^4;v;|F23YN)RGoUo*={a2=3eL<|gYOU^}1v;M<2w;QhPZ_4yw8`weGe|@ zQVWeOLzoXj$*!+COfIb;!riJjSy$enrGPm5wgam8_gPabQqA{ZXo zCa4Segy3(ZP7lmGUHa2TwLDgOUX$x%Dna8j7JVluf;2gr323oHIHR*lKU-z zC5uHX%^GiwWRRgxGJ}%VoE5j1O-Y8A^wm77XZW#eb|0RxF8*b&IiH#XJ%5@#sG4As z_F`M!S5Mx#o9!W*Zp+(-wpmGK<(o`hD!@8u?l* zt?Z4a(v_Yp*h}!fv=Gk7>#z+Tr3?Jczb$F4IT%ybW*XT-P=e9-=8r0WcF-9R~HGai> z^w%?pSrta9KQfCsYFlPF&g>MkIdh9h8kcti+f#K+#J-7I)#SYu+%Kjl=;C-V^%L_OyI=B|$R z_TBFsZT`DE-|^r374_DXCVOOX|EJCEi2fiCmN?YX(SvOy7;iV zJ+e5c&-;0yR3t^X3O)n-(s*NI-OgBW|6|v4hFi5p#RlB&t5J*|F-+{@(#33856iRtS?;q|P~X`m)~0m3#16hX(}NG`v+PqSSajPklNv($zzIW09g zsyZ}&r>MK`^vlRro=(%ea&hi0nn~8Lh#=b({t9^-6s=-oyibiwEiV9&glBp9u~^2e zgybfiEGM|=nr;yqU6+tw3WJdafil6dJxcP&}L1of4(-x;_{3TEksc(ccc8hRlb zgIknRLLxI-XF|Xt{Vf&hAh|BS5L{N(8Tkvvg~4_Ge#-J zoide_$dAo5hV2JYSZx5^G+a>Eo4L^IIBxUC4$kx?YgMZ)WQ^V?Og_!~-)P_O&}9 zzbpb%&`gKzbeo!rm5PgsZo19Yv8B8P);wcapoC@UpQ}lhd&>c&kawp}Pj`G?i=p(B zbN=}FCX($e`t#_s%2fjc8m7rvL92#32>k-35H|>?8Lw}vXB#fKk^=W{v$+Pc;{SUvHXZcfQyDB>Ntw62R= zVSyA-Tz1$svngojGc$a_v|a7X6~(2JD&$j}_5}L%ZmDswq~&(U8|=y36L%DYx7}R& z3pHucxTm;PU*rijZ=5U;hSq)kjr#Kbwy)8QX+#bh^Sx7d2CX%j1iK%XaTzu^0 zz-n;tUCDv9Vyj2zWv0U|1BM*9`LV(&Ji?DXRMICu#iEG5q%ocn3J|{TUQq2iNfMXa zrG9Y%yS@7$H~-^nSYk%vd?zeYiZIILi)?{jo#AHm(}xqPV(>}CG**YGB&`D6o?8LQ z6%xyqG7HjVc35{SEY-YO5+Gv|k+D+Cn+3b;H<>N)o5v#X!a(3wZxCGS#Uo>JYtYEd zWD60suAuU>y-i`I`paU|qvtF&PL#vkFR4)8<6!KB=6U4NOu6Aqc4mDnG+^skP3fY;C*8Qw9jfWOx z*5M(b0^~k#H$C3-_4af1@&=7FN88(oLVQg_Lt;3BTQ`BObBKdn*xUIY9*GUuAj?q& zaIZ&jN$pVy^VPvsgV^KK#v_>}nAX_imFX-M-8~UW9+<&hDm^*4P~*1>xJZoTje;!V zRV6dd$F_Q7m9TF41@(pIxMfy!`$n&;TO?~{0h|4N2G%5%oG%YTtbFi`)c%lHQtq)_ zn2vU?Fll_uYvg4~H(00GO~g!!V)VH+|E5$JFov%8ET0h3Nl3q@Vih)HSobX|PQ2K` zOs@Qex^F1|kS7=sC58+!T%AuNBc6@SW}L0AoKJK{2MP07)=X%5+&_!Or}MX~64{^8 zz!n<0cKt%(v^lNBT2W_08TRfiu{==`?-#TyQ6v^CAbkAp_q|kJF_>ri_M84kK(*u$0U$Rr8Fa>6P zGg^2baxBF$829Xe*sMy+Re-x^t#RB|OQd|yiceLJa#pqLD-O|myxhm0auV&Bnxq#Z zrz&TUq?DW`r93^CY4-{VFT9%3(bdyJXLDRsVu>AY-|ZwtPhO?^9tTFYyS7X6n%vIe zbyC|iXE@{WWQ?kC3lZuH*lX2^PBH2P^r>^v!t~^f(NCY{J>_SSg>~B_XpaXW_fVav z(bkgK{0)aDPURQJ`*{Miwh6`rc2eIv63CX8(+Hj)EaVn#u7mPVQS=1-?9_N9!xt$l z@<^7Z6m9xCI}Pe&y8f$?m0eTTXE8X+EsBv6F&np8;pA>AaV*7{!A`!`?JT$5ma=Dk zlJUny=ZW}OUX!aIx=mvkNlXfE{@hG2CE2VmJ<^ef0o#S@auoTR`49||BnHEqL>A$% zU9@fCk3^WG%2L?l%~93%<|@$!B@}Rp7)Er5>wIsO%NuQr0C>t5K_Z(JdK>!92vfKt zjJ=&hEhbg@hMQ%x9ryQFx41E6W3qPK3a_LWF#5;V0_#jQ@==2OBb{v3<*!*ewNcTH zySCOoQx77@DaXVIDJjvBYf~RFrL=oCl;!qW7g)q+w9VP2y_oG^&8RvbUt#&0VsL`_ zfuk+w=JD&1MdZV_ls2!$EzhEp=tSxj#PK1(bN#{V0-0p-~mK% zS&iVR`f@E4m>P6YNMVor?@FOyZ zmZMUTZMXz9s!i^o)cBxgG_t{rox|QCD0M1l+&cC~8BT4R^fY?ZIwtErhsogNyqx5l zOA+U;$|nNM@m}}smv@e11*4KGh2$B+#O}TWsbwrQFh9W zJZAfr9hPSOys3V_>ot&)%hGRdjy170O)4-IQ6-bFmt9q#ex_Gy46}te5|hHsnkJ5> z)U+HNig+*CY)6k)bebiG7?ca01^0Gv3B%1Q9kV#N>?jiDJJ8oGG5pXO_M#m;LM$Y0 zil+y5tS@;Ifc-Cm2Qy48d$Mv0=5wmzBHpdB%2Mr5t_}cpbXPD|puwM+Q+=?LL)iMb zI|m{fos=nJkXQCD7xp>ue3D_Sc{8kJ?p6l5n!!eM9UX)*6&h=z`lc$V1le4kHZMP9iCcO zc#EnQsmZz>4*oB6xC$k7ZnAY18Gebb!q*9|FUwroi3Iw>? z3`n9i?hDk*1jD8tmriAxL-P{vIbp6hgA)*8`hjz=iN383i_6xEu6E$=Eods#*#%*g zf#Dz6&Piz~JPME6oKjh0G$`s9b>mv7YGTUKr3?+nz)hv_6{Kl3mNh6JdtQ7@=(%{N zZ}M>{l0$D3M>onI1K)|nrJu?MCu87RAN$e^3<*6~8T96GbR(TH@a>74`Fj*xcH?hd z=^9wl3Z6S{6sqOCBFe!Z;zoYbBoUsG;;|b~QO>wFc?xk4_I^V0#u5<|*(5h|p;=Wy zo!^hX6a`gb9=y?AqL41f#nx#K6`qc&c97)Pcd9}#vzdz^o0xVA2sl@0P$MJk{ z@n)hz#frivYE00iIhuy2*`zEdOkYURQtB;hJRhoI+$Q1iDW-o+>37kK#O;AcebX}v zFMb${=@l_K;p(OANgLSRV6Fw~Y#&f-8UXH*&*Djm+vwA?>!5(R(*d&iGZ4tUOW!DD zd_OHGZ11Dbc<7EMfFuk{Y(lzgx3K5cT0Y5lI8jNJ26Q!ee4?Ivl2*X{-gD&1Lq6J| z{jGu8ZEe6s$s>0QA@u)Mme?Il4EGS_Ch)a|UXv~BD~$&)0f5V^98H?OwK$OdJUr?2 zXX$}W%_ndvtBIu;)B1UPfwa+mk6|epolOt0o&j=rfNi=4>`BoEJr`Mh`TMRqAvLeH z`RsU#Ych*Gv1SIJp&Eolxe@GDkB4Dw+u~Hi@I;iYtv}~Y9%%te>(=AWgEo1A<{v`B zD>>}8^2A&(N-bv(rNVCS6cXIXSSs&e!LWvXK_n@DM>X5|vEb~kcL{Y(G)Fi93>4tp z6wd%Qv+8NY#;1IRSbXDY1rpT1jRmiC0%E|!@44Xy^J0^SH?(u4&k8nv^XZjy z_h@X=MLMQXVkIDDFM=w5u=-AJs)F9wU# zj*1_+a48qA+k%(k1**PTkZ`7f=X;xC`^;xHp`$aD`E-Cd?0uPyvJ1c~pu+qZZI?s} ztNlxP5(e>nyt9Vv1V6~F`JUXUoP#gZWPBHDs$TcfBgkAl!0{l*M+P+G)5`?JO-4| zPzf}48WT5ZmW^_612XW!PdOadlpHm_|0p&%>p}7^#Fnw zwKcT))tUW`3O(h#kPKCLu^fOM6~?>;Is~s2Jm!5~(&s)9ip9sG$;tBuI5-Wm5( zd-L}^>{m`N3tJi#BAN_8aclRXf^djv(*Of#1l+8DU%~h9Up=RHw^qi-Tm2upjXdd& z6kO%>d^Ss~+=sdmi744z8Zx}An|l(Z+wPGCzO}fN9&d|MUqQT#{LZMx=}1O4{kk{v zdBHuAw%>v6xsjAzS{dVqF+ccTm7GDec6t^-tQnvn?gAW(b}fEu$ZJ(Pda@zGZ)Ri+ zAKVv?hsK(1s5Z@Xv^a=T?)P(T92nI#y^nWHFc;vW*W$TcD)!dt!5rsC9cSs;rxqHS z1)z6#+n15B9B)e^9u`iP**AKM0@WV^EMN{X$(hSwpOAzj`cNeRX?cO{GrX&v3LsBG zWZ$z3cJ)5P{tDLG?3+8F#ynBM2qY-*#U;)g1$OUP(NSJ>{H7^)%s{zCr{s4zA)Q*5 z^WF*Z#@fA44`wlX3aNmd=G;o-{)(1a(ZfNAN!K}Z4po?HV%?E)3cL(7CvXmTGGw7 z=k>D}=d*M1nB%_>QB-w@IyU!58I~w;s3pWiURY;$ww;Z1&m>Nko{D*;C{Vcnt?~X? z`FqX;W0yK-6<3n>IMa)dw9-QjZ@aHr=wudzg9VdyZ0@C3>V1e?hAMdp|F&5v(%)Nm$KASU8G+DV6S1S0JU z2N5rN^p;FFdy@*_#YK4Z2#fE+Q zYV!3i%QPxTBd)+bfllbgTTz9MPKAm2lrBckIvd+2RB2({T5(Uy1wf$8f>{s`s+mN* zAvI&#RClWdcn7)d;NUJrHEsEN)J)yyBXy*MDml6`m6JTx4{ z-FC)vUmZtbX4GJeE+4@qX@FH0y@uC~hMS!*Sq16%6afaWTP;CLsQ&G@agUc?@ddeo zeLx*jxmJ`Vp2@T{#kl(-WgD=m#PIw}V}Vg@S;FKmQp&JvQa`ti2YzJL=?f81YG4iB zOeLtb=do6)T_DgrT^-~r|1rSco4od3sMC4*H_6EjO<7NrE`B}t^)Br=%0ll(!Su3z z>5dfk9`@Ifut1m6?QWu*o8K)7Wb0918>B+RLp@v0;}vXgT@U?%vG%&&7Al+9NlQat z950;k-FM`29)ObsMC<{yJ|gi-wc0({vOqA(l93C*-@JS{?;13l=l8nL^gU8$Si;@H zG5Lzih~=56OE7G)y;>&;35L!Z5Z({PpnJEn(+e&{iw}}~A}ZM?k;EQj01Qj@(qy1J z5R7@=Dx^00%8ir1D0K>O5-w$oZ|O{z8QoxY60%&*oGQTJ^%PDdb=+6nt}duHg(9O@eh{>9JZw_(c) zcZY42$P4arw#D(qxBpOQKm~tn@nHeruZX+yL63$+0)e>$V2^YV%N$c>;IR6M$y ziDUPjy+E2knk3-QDafHzZu809K;8Fc8lPbGqi#OzK&VG4wIEduicCN?ihGWohw!yG z_w>m42xXVFLE)0Cx%t>rk3(r|2ROeofaSN}UK+}KZ=6oV4YKp*ss~9gabODHR+U{w zpO)tYe|{$FIVMME{+63M{0q2q5}8oFtO`E=`KS7T5aorUA#mQ2&NH$`rH~wlMyrS= zPk913z;xiuH9<1r(0Pq3?~NV}p#s!@b$OC7;?JtGc$mfO5$f<(NG`*4x?XGozvTQg z1E34wl5l+wvAeL4Dp@%psO(aGP^6ouWDTN}4*>PffaKX8WOsRen(6p-p#RB=>Lf{0 zn55ev8_1(XF9S&#T}N(EG5Z85IQw~`=pl?e9Ps~|V1sw(15g*&D^ixT6e_{|iHgJM zm8}=wKQyTEbJ>^&EL=|}%dqz$+vz_O$W{hOYCgcb?^c2!-3D}Yr$MJK(dSQZ5@7>P zLNrt0Oe&N+4FNCqXCYyI@XpK0XjZ{!=77wlp~{XA?T;l?*>ItHG0{cx>>x1w6EsoRygmawyM(ybyg&hGC)0u-RK z%EVCxwWSUi$Ojd}k#%P5w~Q)#J1d?&A7ZXgwZ(UNHtus5jxzhdJ!=*w?>;OJi7Aa9 znj5>)?)9vH*9Y}3Yk+}(VdOCzyrxL(oUVz z1<5&&&BzXvSc0rD9Y|3XeFScj$hiQm7Y$&X?qM6dzX4jW9yLg;m04w2bQjW;Fsx+N zM84%sxDJp^D6tQfurz+J2-<(`FuCtSNbz)|=d=URoOo|3)wcjzob6HYrmZR)3mDKe zLaUU7AGv6z)ZbSw9}o?%`pdC^#T~Z8J?k$Tf6SsLRu%Is6_dbx+GM!eGv7@0@s2*&YRrUU zi)_;f>o2R5{Xk;k<_U*Y0KPz8nvX?kYd8|xODAOOMT+pfe3&i8vkh-K%)ChLFE+ea zKzgBLRs=kvyd$}B2P1wRP|^4-KOS&A3&g6TcTxOKDi3(EKPmzHV&-*aZBV`T#MD^ed9A}ypui;hI2B2tKGgPfh;1zu{ih8oXcGHq{y`4W&qF; zaXXe$%7H#2oD8iJohO>YD?G;)0w==pLhe-4rOnRB*Lt@L@&_S=-ZjgEW*?p#SQxZRS37I)7mFk2wIJQ;%f*1RBwxa0$1hUiB zd6ip*q0%s889M0H8aNfUTF{X|&mlGH6;V9VJ(prXQ@=BY0Z6>`2sa_kNlu^#r6uei zLro|4gF;KLwx2*yFD4{3oU*F4``*iY$HR@t{eYsX5U6x0w#;TY+&|yT54(08k2EuX z9O&yrQL7W@ql-iq{N%8*7L^HPeFt58*;AKDggOcg5q&`W8o4x9|FytrBEu>y6;hB| z27Tn(INih3eA@&#Q4j&DYMs=UqxClZD8-|0nkoTD?*NFlKo;NoBF?>XFZs@@5Xf?s zUA$e+o?=M62jl?4{#d6>N}S`W4A7s3obn~I&7lHBX04S<$Dhjf`D^6UD-qY#0B+ND zT>t}|fpQ$psUMnXzt5v)Kq2PaClA$pKyiq)4Baxm>8YD^8u+ali2CkT_{!gLViQF0 z8K4fNeq!m%dNUqy%FUb2&)8Mts5|>JfU;=@F%r)RqA3xbAoY4Hps8{>a>6nvY(r@` zYa)PMy}#Fj!FT&m^Nv3=o!`vB1C&C6K#)tKdHtmE8Iwon`KUd)Oo#}80d1v>Q_qqQexlS0@{4q^1qHjF0#@`C2>zmsia>sD^F&|y^QJ$b zdT(q9-l8LrF;03jBYmm{NG*_ZC{U!X1IjErzg&xwY#>nFnID!2>BvfAH6aDhKpQd< z5xleYC}Ay+9$i4d||Hg+cT_@J#B%UGW9*SwI!P89lxWM0!FLrdxSeAV*2Ayyt&H zCiIpC+VMPpCZa~ytlai0ykD7ArWMs@@*?32fIb`+`ff%o+`_4tjp_MKDVB)WBl9*= z5t8;sd@gsFB?y{(l{GgbFSqBv_?`ex1PR63T2m=R#lBD~@gt}RWxg&|a#T<3Rw_NU zd5$Z_-GGh%OxR_Hn;~L*5yP521{kEt!gF`X_>dX!&%StZPzK>+Sb(KR05{wBGuz^%a{9SFVqH@A(q(I98xjK*l=n#(5oI4Mn z?nv&X&E4Bt>;h_j`}LX5yKdJp1-=m&5&h;nK#nlCG72P5d_ltU{UwxD>clrM+N(Bw zL{MFe+1Af(Sbfr5b})RG#fuBEwBCrn<;_j+xx~{-@XA_RaC?Ye+@)jI*Z|x zd)zH_tv7R7HXSl9IL7P%Y1%bVxN%r)On#{F!hh!LQHd$2wBS^65|vu50J}nnU&+8_ z4k$W%*4Fw?_?3+_0ZHx1z{c(PwurGxr)0BL-!lQQdeoY&%*HD*GQwc#6%MAe&wLy< zIEI@rkP?9alOo{_unXo$dZM&kS^t+vh}}yyYl>F|-TqLH<-wNS;>k%dBrKxpagI9@ z8s zeq!K-?jXP2@WMvwN_cx%lu0s(zwic;*$n^2=nQy=;k-s~wy+KIm2`)4pNUWlQ)_c2 z6_B(M*9!>&%=pd_y0l6xlHvg#$w^>Y5|Bd7`?_jX%JKacA|VZ+<7YR1URv|%WM!ZF z-Ix!y?kitGcB+jEUn0DR=We($7=z5ok`Abvu)z@NaCw|6rxbUun7sNO0!RRW<^M!& z0GfN8-$(CQ=BEE_?uM&2%O*$~E9@_)(0wS>#AwuIp(LnA}mP;VHR3nc^wLzY!JJZgOMf?k$r+bG>KLOtKHV#ibiL zYM(E6-U*vYRo5=E15~-=WJ)3@P+FG_J?Xyw8R$_?l#cR|OFMa&xuyu?w{}Uo2XObF z^V?SSm`C^uIb9(ScC3}L7_B7AmHrAQcfvB3|F{8~^hv0{AC@|#?~*Ko$b-e**Sj}w z#mys(aE`}=ti)PpF**DrPmoVQ{|Ud}RAIamd&PLUIpO|jEmFnO@rOT)kvJtme4rp4 zOpZS6sTglLkg6C>4-rxk6Rrccq{opOiH5PPSiqE$v+_R!i^n|1E_x8u*GX^8^K@o) zWA6hb-Aeg%4}S)IEZb(#^m!ojFKV%(-5r3BbpfbR=$vKXol?JZ4m9CLP!V}C^6~z8 zih|E9^XI+HvxCCdFm7EuN|g-HCQ*j2Hsf^{d*>+Mq-4YfIhGjB zb10yaI>tScrm{E{l>S?8#LCm=ZB2nkJxPDxUiHdt9T~pl+Zq%|B}|D9AXZQD4t2|V2+$SHY)N-)hhS#yZIDYY zVkY@uprGZ>d3e4y!2KD}6qCp(_cO)ie?Ax?fM5Rp%?D&kRXeQmV-W5SczP+r3axra zJ^3d9PbZLd=)3qlOnm|X_L9a1(yjUIRp9lF8sI{7rKX1SJ;_%Y5LCkj8Um`=Aj4hA z^m2Q5KV3UlR>DVsiWCQ@h)wxKF^C0b!#Agh+X#xHPh7|sxl1Y^Kw)zedhrH&%Y{ke zxYqqAJIfHa6LDr-4U*<@xtoCOA`1)>s(6jJFZ|I*XE6l6VrVLwh`p*D}Y`t^AnrFa?Wt9U3{~fhk42j;sReB>@qxF=L{YM?ChdgRf4R>-umx6?RyXpR zYY(jxi6AF{FRsnVr1PO8?IEmw(t zXbJ%)n=Km!++RBImvRPYb5u1IYT;UPk{78BjO)P5J7!2G&$_iJVkYT6BT^xglFah} zY7=$tD~Q?Ais3kohLkC@kSaVDUtp(Qj2L)&+VS0g{P7Y8IPTh#?Zq5pzm{ zvsn5tMudTMju;DZEad!!7p0z2Lg*RS?+C&F=SPl3I5nVl(rK zN5ih8SS;c_`M1_ss_sl%dPbBm{k2(N0Q+|V!QCAI!8)Eh8F`T)2mnE9>W@^v_^>fL zf~ds3G{Nb-dbO$Zxzm*0E&D0kI#hQL2|}A(TLFiVn*4sd)uj*q?P{+@FMkg)T+3av z+d+ZUouz7XSRbCki?{@`ZxmlKVbZEkhLDxzUA=rkfC^zgf2i$Rczs*vF{pGDN6%M6 zkPS$$;1n9Q08onZGd)p-ja3H#bP9C4VTARn&qJkGD4)nvYc z=q*b!c+n3~%8Vyr*Wk_y58|o$Y*$<2XU6L$NOLgi^Ip>A6{J{gOgDX>%?ZEbRp)eRBI zQW#7<;AH_)5j=r$2lS@)EHkzBCcws-tW z0bKae9kOgy>(U6*{NT?rRL6Vya1+B&A5qs*_$)1D6Yn| zPkVu8=(L7Otwzj%hFkX2avC}LsfYZ!>@pPM1ICZe523D*-{BId)##|_zc_UkLvMq# zuIeP`U^|r}Fb20+rcV>($)r#8c6hS!oO0|ds=`1^S^LV6q4y+r^Z6&)fw|Ofo$8Dra>s8Jcy0l^M zWdiHuE84%NAma|c@~eW3V>$6EQiDh+IV1O~Pz$d8MN@QC$?g*OarBcHHCHC!iVBUO zKzG9*es=ci2@$H}RIo{`7)$~oj4Wjp-F>A=to3$eg2sk&CUH!n^5}WJ^Vc&a4<%$3 z_%Rs?9XhXWe37xj)fSDEV+*ydw$GpV;KSu`Z1&=q#w)T z{4exlv^)5&y zepltIO84;@$l)Xk-2_79e_czaejT=E9*L3=v7v2YvzIf~NL3Z!tmP+-q3WeaHyb`i z&-d6ES2@sO$F2Ykau&sPgklVhts0xHoNeR@nZnLFnwb1AV(~Y$6(SW*h^^VQHy0v# zuizK~AsK7#!niJOU?K@6cir>x5D{USwRZ@70#61v0s=$}YHhP~MnzGZ{W^bTS433K z5Enk_c{~$s%Sg#4;(uI4Hr9|^Xz=c`?1;lHHm#`6@U}N7)!zj9lIZp_REK>=YY?qE zJ6Xj349{!=b66y7unHaRQU6|Lcxy@?)n7P1=?-LOmQ^XAW-MSW8hsySyOXX;Pl5U` zB^9}Jy^r`bT?BjNWes)Cx34e&1hV4oTb)Wk$YjfFEVd6a)qAYI%Oa-$ zP_?JRi!B4-iHFH*cR`}rQ-xD?xfh~1%-%$*iEu6SWt!tLBp!=CrUS|yCTn{=V_!=u zB1C18nmGfbTE*#3?X%}#MWhuyiS<4UH?M0Bow&T*!~O<^l~;fa?g5qP6b zvUPP76EM+WOVM+_$H-4N4u*BlVQXYQzbef|g55Fwq{YPXenJs1b}?|$FU z=lA*kuHSXLU4LBH?drcn&Ul~q`}KN0pO5?F@orJ(>BujiPvfzxIeuj0L@-l$)|K06 zfy=ODXHb?#1Pq|}+^-V`PS1B3ZXGH_K;+K zxmA^5i5IJw^u`VLVB0$1e5r2N9Rbj@;vfWBQuRQ~#C?VaQG?MQU8u_43k; z936SS{HWw0GWAW2pbyfvvsdbV!Mdh1 za~!pqYLfO+#_965i6^YSM30=oZZ-rz8*}Pb&%=jp-_u^=aqKoz@fDfB-Nw_@nd~3L zh8=f`P$$k8-_-6IRoWa6eMl=ZzdEK?!Xmm(DN1-8nGj&Mmtm(5emOf>Eqp zAB+1=*`-waW_LA0Dx49vZUzB?1Sf)+pnr@r@BM>s57loY4FWd;6# z$c>c`XJ4co$|xEE9pNQ3dPaOr(BL3A5AI~Ch0L5WbZ$Bbjz)Qi&3(9h-w`F&Q!C0c z6ZKwqZMLpx?eoymM(_Ssr?wb$`IkM%;OIV#eE2TnPP|tRZh!hK%QOsJ0?32}cnR5I za8IZfLTE`?YU7nJI0#Tk0kKgJ#+E_}RxrQ*2%^{)gF)jC!#_L#=Bvv~KOh5J2CG5uFhn8HbRV2K*&xRS+O)2kct2U|dpMVi&j2 zuW0&e_=+iK3z9gR(DZ-Vl6;)bmzWhXq^ZPb+Je369cYQwFk~#Cw-fUKZ;_0n7-|~P z-lz~4=S!f5&W#g6LP2KsF}6z30LoP~ZJdRXC^B)DJ=e*wFjiB{E@^cfGEky<(Ypwj zfuugbU>}E&Ry!G&X}R(&{V2Ug9-cj#z%M^@g;n>(Dg`Xp96^}R&*P>mzRiJS>ye)@5`!e?&W71 zAG;R73`F+ovaKuNSuqg*G5*-Yy^ZSnE`oVbdYys@6@F+gk&kp9n!B(m0b9Eb7-ezYv*Ea;Hoy~^2(p(ZlG9OvUA_jr^D&6@K*kAS_J7J88L6L^5A6;i zc?$^rdZ_N5quLZeN6Lgj#9}7gL4hN-1AufJQUp?SU?!(?y3W(1X%0rFuWU?pna1mX zQmQt)JbAsIE%%+P-s3}WT<7m*Q6t1|Ew4>SL8cZ;z)(fZ(J8N$JOg_$o8=>KFd*x% zk)NLjB!LC!r|+9z_BGst$%psY`Mcc1VY#YTmoK6=!SSy+qS*wO&E17p#XNkgq_tWl z&vysGO(r+;V|ymP^Vm(8PZWXL5vDC+ao;{-obFo{@haF^xdwj0C@_X}#A-JBxkz2Pa{R@HF!G-=e)TxO^AENwPlwOy@U;&+o^-1WWOLi03et#0?X z4`c#l{hPqf4C<|@T$S{6p<({nCr-a|qV|mj48@!+D=Pdf7Eyl2{cOeKMs%{jo#U62 z4_}~zNa*xbiJZ?9b(aPndpOohdLufC_03yAx%MEW=gMR=OE6B?c(m1EkVLhwU(>hz;k$`DQi0K;21IjdKfiRnyR< zVBNrUd=zBkGeRz$qLHfcTry+?`_|sMJX8F;v!BJUC3S~j+^-23^GjAa zc;B!_37wB^nuPJCHZTuP)tCpBtDSQ{zUSJ8Hje&v_}T!&e)37;}> zqp@{v)Kc77Dni5z&=S)0aor$lvc5ek8tg2qb)ZejPlz6Spjx%t-XY(M4x7our$VLJ z*Z=IH@nr>YCZ~A7r_LzCXSzWjg;QrrnrQGT21;z1js>48Id$jYi8j)HE2C@Ry6h8e z26rzHxzd`~DXen!Dx4b`l9=H+{YD`wf?Dcjl$|`4;V12H%kTRJ4890Skm}{0NC#Es z37_~`pi~+-1d>(ncI44lw61}usw;k6toc}eMuU}!x&hMB!KF_bmr|zN!>@fR8QM20 zcTYc3(Cx$Rmh{U7Xf}EEB(b1#)$JjPdP_sM*IwBXcK39S0biE1vK3|Jjf=o1L38gzu`#^tGe$1+vDnJrO)(>f~ou$4VS(gDiGEgoZKJfmdr#2 zJUZ9hKBe@m&{fhZq~5Yeo3Hd#NP;d^R!jB#iqah*NpH;5M<;gC4G(cp&ccR5|KhP5|Y|#>BR9Vu#6_fX=mc%p1tr9SmCr4EM z7G0wDApnOi)Q0aP+;2U_2acz+Zxr=^0*5o1r}^a26rzgq+I-fx)eRJEL!!-)*`~MC zzt6l=vWA`0RLu8BOdG`<5y7v8<%76!$BQ+8*$`h}ilzO)! zzfSdgbD|-z1w{FSh~XBN7fV2X<_OW_Btgsp=wa5Ti!8%YL4c!ZBPl&deFHp`w6@Sl zUH~q_}}GKY*&h^4*@7k^@y7U zg|hp|MNZ*}_g| z`QK2q#;YqS02iblg`xD<=BNT6xGMj*A>|A|0jB=8~j+W(b6+e1E6=a~h9zmOh-m4H)Kf)3#q-yvA?a*j7v z0+!_s5?mPPF+QRjyh#(rit$oYjLWNgu|Hq{J6vWdrM%L#&NQ6P-iWx53&++j6&qzJ z8t=mX@Hp7LkC4Ol?t+^*{(G-|X+2;>T17Gx>?0)F~=5($66i?iRt81|Hac6&8G}=>D5_Zf4pcj_W$O$} zs$WOpUF^yu^Ak<+Ga#GwT^iep{f1Oj5_VCVXcsK@+(HJWsprt>uQf^k)z))n5DiH?T|zkjeKF8VX6I&>jDNHSRam3CWP9 z&=r$sH-8IxhnL!0VglFie0fuLq4O0K1>Fl?nQvl7W3daroy<{39=s+*PZ=CpzF(6b5p&|e`DA<59SkhwTCfIKZA>gW&+iJgV8~59x(2K7I z%l#5{;WNHMJ@&gIO?k287jUaiu-YNSt+=+AptS3-u({U{klLcwO`6{^BP?I5A;nuS zfT5~A->c-!-ZuP!(!P+=@gaC}`aC>U^F_Wl#w^`?DL5@zzSmK5O2W@V&YOOS#RafM ztxR6{4+mkC0eZ|?I^=Soh`@?rAhU82D@OKO*J%wP|fjG!W+wT_4 z<0q4e6*2&zrwQBA`K!}p(fu(XAl50_E1zFSRK+WQ;u5pCpMd=g4e`W}A4rlS^iSQ? z`)n~4;RHP%xL!{m2#XP%+BEg8Lk9qoGn|XxYyJ3Q8g>?;(>$Bjbn{Nr6Y>JbYA$5N zs`3tNZ*rQkNI?yCajB5sSNFh&+gB^7vb-Z6g}lS81~80!1brmssahVn zq@s%0({24f-XRPH!iJFc0%3?p@pl7m1ivaistkuRx!rWdgoP4;@&G>6we$bTu^#yW zG9G=NyhmIV8b6VQ#mqp<3fqPd`IdDg9Rs$_LEHG^7l)rCczKOPwU7z{jf4Qgk?h8X z^E&o7%tOz+qgux$Ke+@u@rC}VNvL1P0&W0}V*r)tN9e=)^CzHy4=tU#=uua^^^xWS zQi%54ylWRwVtaGv+b+e&p!WL&PDwt-{|C)g9rp546RbNMK} zD~N;&+7Kspa{XIgIRE~jOAz~qgr%h(Zw0%Vkb>K5=?-**kDm?*aI+!I8q)j^I8dBq z-BgBBixS7jIRj~OdI%0wgBEVoA^08jPU8K^7JLv~Rj?(TMw;CIiZGH#V{Mcs+mbxoNg`q$PfknwH}h& zag$n^yq4r4<3-j+@)c(JyBnY-ulD|EIKw@`Hnpv6w*>m*M<|<b8F&LOF)w?73;0fCB+QNtc|gp)i^kI=Ly0{)`HT zH#~X3`|!y4zCsNZEHJ9v&{X5CE*ptW$3Umt10KEJ6;stE`DDH|aTW_ZFt65=H^#1t$NnKjMp!E>suU>bK;38;zylp-U*@5!U1ZRWm z`bgwcBJ7*FAwSdq#HVE+I0L7~vCRIOmZ|VUX31EB~{t-p}1a^L7Kqzh~L-kR^ zr%FwMitSC6X~($0${$;I19s+&sKY~)6gj@b2G*zmAU?y0iQp016S z7mrS@!NS4Bcq5rNi{ddD)x)Q2?^Aff_JcDX=&ZIrD4ZpAdbPGE$1h-zB{QiY7V$zt1lKYJB3U0df5d1I?mnif6 z?PTVOs9m_&6ss*VY&xr-as^?B){Saxm;`eQuAiY+Efi2K|wNswFW1tWm6UKIfy9ji=ja- zON?w(AS9ZRpG(eH8SfvstD`JIqU+Aj-LEL)5piI8uJEgu>Bh2#{!^$W#y;U~2A=3A z3gQY0<5?VL+W@9`Hu-xuj2$^g{g7uEm(ku?WG(Ncs?@d;89Bq9+}R~46rHX7yy;Cy z|NKnG*M4w5GgbK|@KAbo<}QTC3czZUgar6n#gu{VbZptUH;wTyab(t1b-h$M_5Q8j zzjXWHR5}A8QB-5#7Uc}?yDOE0H7!2kQwW2KcKV5iv6AjQeI!7S_|jwy@_fe}<{3&b zJY8CT+0L}C$&T~Fzg;91X!sVt9Yd>(Zf!2jvqs`4S`S-SYtul&N|S}zX8{dAE{L(- z{FFN2(l)E%oxA2Y7V9!V7H}en^V>9FuV=(gQ*A=K=2qJSg2L}MD z7R6jM0Tv@C6@4DUpq<^7my*S;0E+S1qM5t~Kk<2#7cltrM#nibTeJ!;btB&bJGHr< zYp&qwVaXB!P1QV-24EHzcr#y=wj9v1>AB5Duy`S{qI2*co0*=OT!wfs?_Duuzn&~d zIk7`5tQ_5~7pj)QCdwV-cu(NiPUkS|2wZ^8YI6ZF^4-X6-XH(Izvn=7np~yH<-eaF z`^J=|o0=kOu(}~MEB2q%no654vm3LwNf?qw{sXDUHeKOKc|AMgnlnw3U*7lTUukk^ z4;u#GvgCyuMBDQxrI5N0$<)5OkD~uUt{@%1JVA&3g+a+|+@ABzN6hY!8!T=N(M`8r zcZAZ!)$(jy)H!vrA5oUEdVLLC(1Xb+AjxwayT8r#64RqI*Ml&uWDAY{<>Tmo`KF}w zuJnl9MFvDh%^Go2wGdAV`L&-ur0qUCv?rnD*V4AKX5_22iw%d*?zg@ZM-KEu{PSc*=E=%%~fIZgc&8^a`DFLQa8!vg_ejyJ|39$5D02+_R9jME}D` zC2&4&|HthUOdFObsD=?C=?Tlouj@#odC7>wN(VFz_tSh<)kxZzn83?1k=eK|Aq)Nk zvQ80*hx#DBZl@j?W_k%1A;X19TwD_^MSjn&k-Ht3L;c|Xbsu2nc*9vrrOXqBLMJ!cMhpBZIa# zlTe!bH74`uip{Yoly!!?J3r} ziEOE*bN)R*9vL6fbvqIkncMminP&DU1;v1^TpaA7DKHa9Su6eMr(+XV5Zwum{I92v z#K?0Wbh`Ni_U%rs=Qc&@M--TM_z;JMPak+fT%&qZ#LPRJ2Ei+2UzoZC?3%H|7?}UL z$=5c9^o)yE08`Z=@LDjh3>AdKR%nhoH?@=GN~p3WeYktb=B;h}Kn+_&BSd`SdaJ-h z=JOM5oKByyg7$;OrU%lb9fDm~QqRRDA~o4o{l2eN1iu~_uoxvyB0_yLhW=Yy-Eu}KvF%)Z#J5EDHJL;)2a^XUMO`xt#vt^u>~A!f&g(W(Of*0Ch$2;I$Z zb576A$|avQe!x0V25xj5zfqM#NP}VFD!z35>3917TzFy0h^9fOZu#RdPPGGgdr9{=8fq_hoHA;Q0DeXAUH2WgC z?CkZm(48BJf;x!t25~`gD|;8F-9{$8;^kUTNz|B#pU}d|C3B_9Xeg483tt7nOeQw| z^SsePqtlDpAMJ32)j);LWVypQC)L&|bbW`NvVYom+OY%-POOBHD~KJ8tq+S>P(F%2mQh8{x*2y8(ek+Bw~$O)j$tx}PmR zsySE{6K@ce{6pXwoUR#u+e!9G5Ovjr(33P0ogd%5Yqw-%!$gPAK2b%dSLG|I%CF@k zP~7y}#;cA!`>2iJRjX=jnw;>H;#icl2x8xB)c;Cy0yhFc)}PdvO%-^ z3Na~ZgW()@w#y;BJq%p(E`#`n5n@3C1&PuhsQ_@K;I3t1)@rF#%qnFXZv>lGgg@G2 zeaytAjV@=$>DsF5uiVD<2e%hd=`zyOQ_5PzQ(Ole_&5&&@9WmahN-&)AQ~4XDfb-r zfp2Sf_Vf&kT3Jn#I^gmIX^M1a(UM{W6F0@n_S7kpiV-c>I?XuYcGT81Gdlt0kjpA! zsJ-iO-6-Ga0T_>-RJr?AUF+5sR*gzIru@6&T4O3<#sa%>JGIbN+o3t|Dv$U z>wuTHnnjbFBBu_U{L#8J@r|o6z<9}`$Lp{G=$7?9r}LHg9)sHJS*vQ7r8zFNTRD6U zzMW=|4xiFlzr%Lux|qGq;y(yMGe{nACJE=OyJ+-8N@?xsxE7hLyJANo8jJ-WU(L2= zm{~1!>49ubbogombEF+Wm$=M?tOOIi2udkwRpxPjW`1!e zse@d=5y5qdJNR*-$-8m?LAIYY2kG!#vKmii>zN*YxT!O`Y9NpIVT?{ZmCB3OVM)Kt zx&dVG#ithqZ0D%lR@%QjP8PJPbr~W!4`h3AibRGm^CIeY89ll!Q=s^cRgfsON_gS}$KnMop{Oz-t#_n#;Cr(CpVCpE2B zV3b|3H&3K@9lKp*tU{s=5}?uW0$}887{S-oyF&^`Xka$kojj$62&!roH35$q<*X z%B0njkOitRIF}+};0)klEHn6|WAxm|>jN!RI9ai(0%mx^UUX z2j;gvmpx2>A4YsZQi~X4bq;iK4X;Y@C)9^;t$JeZa0khcIxeN%7SQH2+6fSNFx5-$ z_P9>0EPR;A*=@Nek4??~sY!P!5j5r4xWci-=al}*!}^NUKZluBMIg^ipqJ{fbw^3CF6iKx=1P-9CLg=u(Ur!Jt!e(Op?irEvqD~s>x_SIRfEzZX+Ba`b+{XBR*-E zW-U=9P|sr~Tk(-QM00jt3$+k%Y&!4Qi26pRCRh+d6I5moWElir=whjm)F3yr7Ch$> zeR#Jrm;*;st-czEwVI%Hb(7>J|J1t2OHV(Ra0q|#Zt9_AhG=(UoRNkTKEV;yI{lG6 z#@5!__yf!ipzsUmO29oO*&x@*#qa(H=!};oXl@U&1$3cO7M`{RcbVao_sK(of3$$%L7d z!J^fct^8PVDt1lY`fs@R;p7Qn`hu#^3(i_-Yxdczv^tB{yw~|U8ly1`P@aAjo zZk2()fMl|I&su&F)cO=TNn0hCbROYP6~$vk+`dbP)lekl)&UMH@fPYXD--$(y%?CE za#Pwh#=^|7oH;}=VJ|XJb#dAcMX8aE=Jx?EN{eUqUlhd7_j|OQ%CmwvQ@P<0eaiwf zxB<`On;>oh%-?g2}u$S91)BC zERuO;n!P(?Z^-LwaeFT|%OeWSpA1fM${{;n$(dTwm(RV+?FvxB$Za$P?=PqaX$4xz z&+YY*z9t#`t(QVGbMjt3st~!efd#f~X_tv&cu7AkT1S41m)gx2V67!auimss}@NExmvn g+n-1O|6lMoEvY?9uDPVteGmLI&@s`jAlL{02SDl&mjD0& literal 0 HcmV?d00001 diff --git a/source/asset/image/pipeline/simple_pip.png b/source/asset/image/pipeline/simple_pip.png new file mode 100644 index 0000000000000000000000000000000000000000..5f874f62796733ad13a074871a6f4ee9a1791bf2 GIT binary patch literal 34923 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 IKJSG80*Ww8ApigX literal 0 HcmV?d00001 diff --git a/source/asset/image/pipeline/simple_wave.png b/source/asset/image/pipeline/simple_wave.png new file mode 100644 index 0000000000000000000000000000000000000000..53a98be67ac7acdc9f4fbe23e110740920cdb41b GIT binary patch literal 24249 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⪼+XJitOu)+(ra`^Q65|D@;@ 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 literal 0 HcmV?d00001 From 6cb3049da34b510a8f4e8b229f84a0ba0e765d85 Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Wed, 8 Nov 2023 11:34:51 +0100 Subject: [PATCH 02/15] Add arbitrate API --- .../Libraries/Pipeline/introduction.rst | 108 +++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index 9cf634c61ad..2905c06968d 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -26,7 +26,7 @@ Here is an example to illustrate : Simple example ---------------- -Here is a simple example +Here is a simple example which only use the basics of the API : .. code-block:: scala @@ -79,8 +79,46 @@ Here is a simulation wave : .. image:: /asset/image/pipeline/simple_wave.png +Here is the same example but using more of the API : +.. code-block:: scala + + import spinal.core._ + import spinal.core.sim._ + import spinal.lib._ + import spinal.lib.misc.pipeline._ + + class TopLevel extends Component { + val VALUE = Stageable(UInt(16 bits)) + + val io = new Bundle{ + val up = slave Stream(VALUE) //VALUE can also be used as a HardType + val down = master Stream(VALUE) + } + + // Let's define 3 Nodes for our pipeline + val n0, n1, n2 = Node() + + // Let's connect those nodes by using simples registers + val s01 = StageConnector(n0, n1) + val s12 = StageConnector(n1, n2) + + // Let's bind io.up to n0 + n0.arbitrateFrom(io.up) + n0(VALUE) := io.up.payload + + // Let's do some processing on n1 + val RESULT = n1.insert(n1(VALUE) + 0x1200) + + // Let's bind n2 to io.down + n2.arbitrateTo(io.down) + io.down.payload := n2(RESULT) + + // Let's ask the builder to generate all the required hardware + Builder(s01, s12) + } + Stageable ============ @@ -97,6 +135,7 @@ Stageable class can be instanciated to represent some data which can go through n0(PC) := 0x42 n1(PC_PLUS_4) := n1(PC) + 4 +Note that I got used to name the Stageable instances using uppercase. This is to make it very explicit that the thing isn't a hardware signal, but are more like a "key" to access things. Node ============ @@ -138,6 +177,73 @@ You can access its stageable's signals via : when(n1(SOMETHING) === 0xFFAA){ ... } +Also, there is an API to define nodes which are always valid / ready + +.. list-table:: + :header-rows: 1 + :widths: 2 5 + + * - API + - Description + * - node.setAlwaysValid() + - Specify that the valid signal of the given node is always True. To use on the first node of a pipeline + * - node.setAlwaysReady() + - Specify that the ready signal of the given node is always True. To use on the last node of a pipeline, usefull if you don't have to implement backpresure. + +.. code-block:: scala + + val n0, n1, n2 = Node() + val OUT = Stageable(UInt(16 bits)) + + val outputFlow = master Flow(UInt(16 bits)) + outputFlow.valid := n2.valid + outputFlow.payload := n2(OUT) + n2.setAlwaysReady() // Equivalent to n2.ready := True, but also notify the pipeline elaboration about it, leading to eventual optimisations + +While you can manualy drive/read the arbitration/data of the first/last stage of your pipeline, there is a few utilities to connect its boundaries. + + +.. list-table:: + :header-rows: 1 + :widths: 5 5 + + * - API + - Description + * - node.arbitrateFrom(Stream[T]]) + - Drive a node arbitration from a stream. + * - node.arbitrateFrom(Flow[T]]) + - Drive a node arbitration from the Flow. + * - node.arbitrateTo(Stream[T]]) + - Drive a stream arbitration from the node. + * - node.arbitrateTo(Flow[T]]) + - Drive a Flow arbitration from the node. + * - node.driveFrom(Stream[T]])((Node, T) => Unit) + - Drive a node from a stream. The provided landa function can be use to connect the data + * - node.driveFrom(Flow[T]])((Node, T) => Unit) + - Same as above but for Flow + * - node.driveTo(Stream[T]])((T, Node) => Unit) + - Drive a stream from the node. The provided landa function can be use to connect the data + * - node.driveTo(Flow[T]])((T, Node) => Unit) + - Same as above but for Flow + + +.. code-block:: scala + + val n0, n1, n2 = Node() + + val IN = Stageable(UInt(16 bits)) + val OUT = Stageable(UInt(16 bits)) + + n1(OUT) := n1(IN) + 0x42 + + // Define the input / output stream that will be later connected to the pipeline + val up = slave Stream(UInt(16 bits)) + val down = master Stream(UInt(16 bits)) //Note master Stream(OUT) is good aswell + + n0.driveFrom(up)((self, payload) => self(IN) := payload) + n2.driveTo(down)((payload, self) => payload := self(OUT)) + + Connectors ============ From 779aaa8a6d6d577a53b5726350b8638be9a2ea9b Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Wed, 8 Nov 2023 15:26:18 +0100 Subject: [PATCH 03/15] Add doc about isMoving / isRemoved --- .../Libraries/Pipeline/introduction.rst | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index 2905c06968d..4505c9f90a9 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -93,7 +93,7 @@ Here is the same example but using more of the API : val VALUE = Stageable(UInt(16 bits)) val io = new Bundle{ - val up = slave Stream(VALUE) //VALUE can also be used as a HardType + val up = slave Stream(VALUE) // Stageable can also be used as a HardType val down = master Stream(VALUE) } @@ -142,11 +142,29 @@ Node Node mostly host the valid/ready arbitration signal, and the hardware signal required for all the Stageable values going through it. -You can access its arbitration signals via : +You can access its arbitration via : -- node.valid / node.isValid -- node.ready / node.isReady -- node.isFireing which is a shortcut for `valid && ready` + +.. list-table:: + :header-rows: 1 + :widths: 1 5 + + * - API + - Description + * - node.valid + - Is the signal which specify if a transaction is present on the node + * - node.ready + - Is the signal which specify if the node transaction can move away. + * - node.isValid + - node.valid's read only accessor + * - node.isReady + - node.ready's read only accessor + * - node.isFireing + - True when the node transaction is successfuly moving futher (isValid && isReady && !isRemoved). Usefull to commit state changes + * - node.isMoving + - True when the node transaction is moving (isValid && (isReady || isRemoved)). Usefull to "reset" states + * - node.isRemoved + - True when the node is being flushed You can access its stageable's signals via : From f1f68ce7fe12a7d6d910dd5f1924dcff8b06e63d Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Wed, 8 Nov 2023 20:13:53 +0100 Subject: [PATCH 04/15] fix typo --- source/SpinalHDL/Libraries/Pipeline/introduction.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index 4505c9f90a9..c36636cfb8b 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -159,7 +159,7 @@ You can access its arbitration via : - node.valid's read only accessor * - node.isReady - node.ready's read only accessor - * - node.isFireing + * - node.isFiring - True when the node transaction is successfuly moving futher (isValid && isReady && !isRemoved). Usefull to commit state changes * - node.isMoving - True when the node transaction is moving (isValid && (isReady || isRemoved)). Usefull to "reset" states From 1b8815c33c774eda5fde476b9d7aee8381e9377a Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Thu, 9 Nov 2023 12:47:43 +0100 Subject: [PATCH 05/15] Moar pipeline doc --- .../Libraries/Pipeline/introduction.rst | 377 +++++++++++++++++- .../asset/image/pipeline/composable_lanes.png | Bin 0 -> 97456 bytes .../asset/image/pipeline/rgbToSomething.png | Bin 0 -> 27133 bytes 3 files changed, 358 insertions(+), 19 deletions(-) create mode 100644 source/asset/image/pipeline/composable_lanes.png create mode 100644 source/asset/image/pipeline/rgbToSomething.png diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index c36636cfb8b..621aef0a859 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -91,32 +91,34 @@ Here is the same example but using more of the API : class TopLevel extends Component { val VALUE = Stageable(UInt(16 bits)) - + val io = new Bundle{ - val up = slave Stream(VALUE) // Stageable can also be used as a HardType + val up = slave Stream(VALUE) //VALUE can also be used as a HardType val down = master Stream(VALUE) } + + // NodesBuilder will be used to register all the nodes created, connect them via stages and generate the hardware + val builder = new NodesBuilder() - // Let's define 3 Nodes for our pipeline - val n0, n1, n2 = Node() - - // Let's connect those nodes by using simples registers - val s01 = StageConnector(n0, n1) - val s12 = StageConnector(n1, n2) - - // Let's bind io.up to n0 - n0.arbitrateFrom(io.up) - n0(VALUE) := io.up.payload + // Let's define a Node which connect from io.up + val n0 = new builder.Node{ + arbitrateFrom(io.up) + VALUE := io.up.payload + } - // Let's do some processing on n1 - val RESULT = n1.insert(n1(VALUE) + 0x1200) + // Let's define a Node which do some processing + val n1 = new builder.Node{ + val RESULT = insert(VALUE + 0x1200) + } - // Let's bind n2 to io.down - n2.arbitrateTo(io.down) - io.down.payload := n2(RESULT) + // Let's define a Node which connect to io.down + val n2 = new builder.Node { + arbitrateTo(io.down) + io.down.payload := n1.RESULT + } - // Let's ask the builder to generate all the required hardware - Builder(s01, s12) + // Let's connect those nodes by using registers stages and generate the related hardware + builder.genStagedPipeline() } Stageable @@ -262,6 +264,42 @@ While you can manualy drive/read the arbitration/data of the first/last stage of n2.driveTo(down)((payload, self) => payload := self(OUT)) +In order to reduce verbosity, there is a set of implicit convertions between stageable toward their data representation which can be used when you are in the context of a Node : + +.. code-block:: scala + + val VALUE = Stageable(UInt(16 bits)) + val n1 = new Node{ + val PLUS_ONE = insert(VALUE + 1) // VALUE is implicitly converted into its n1(VALUE) representation + } + +You can also use those implicit convertions by importing them : + +.. code-block:: scala + + val VALUE = Stageable(UInt(16 bits)) + val n1 = Node() + + val n1Stuff = new Area { + import n1._ + val PLUS_ONE = insert(VALUE) + 1 // Equivalent to n1.insert(n1(VALUE)) + 1 + } + + +There is also an API which alows you to create new Area which provide the whole API of a given node instance (including implicit convertion) without import : + +.. code-block:: scala + + val n1 = Node() + val VALUE = Stageable(UInt(16 bits)) + + val n1Stuff = new n1.Area{ + val PLUS_ONE = insert(VALUE) + 1 // Equivalent to n1.insert(n1(VALUE)) + 1 + } + +Such feature is very usefull when you have parametrable pipelines locations for your hardware (see retiming example). + + Connectors ============ @@ -421,3 +459,304 @@ To generate the hardware of your pipeline, you need to give a list of all the co // Let's ask the builder to generate all the required hardware Builder(s01, s12) +There is also a set of "all in one" builders that you can instanciate to help yourself. + +For instance there is the NodesBuilder class which can be used to create sequencialy staged pipelines : + +.. code-block:: scala + + val builder = new NodesBuilder() + + // Let's define a few nodes + val n0, n1, n2 = new builder.Node + + // Let's connect those nodes by using registers and generate the related hardware + builder.genStagedPipeline() + +Composability +======================== + +One good thing about the API, is that it easily allows to compose a pipeline with multiple parallel things. What i mean by "compose" is that sometime the pipeline you need to design has parallel processing to do. + +Imagine you need to do floating point multiplication on 4 pairs of numbers (to later sum them). If those 4 pairs a provided at the same time by a single stream of data, then you don't want 4 different pipeline to multiple them, instead you want to process them all in parallel in the same pipeline. + +The example below show a pattern which compose a pipeline with multiple lanes to process them in parallel. + + +.. code-block:: scala + + // This area allows to take a input value and do +1 +1 +1 over 3 stages. + // I know that's useless, but let's pretend that instead it does a multiplication between two numbers over 3 stages (for FMax reasons) + class PLus3(INPUT: Stageable[UInt], stage1: Node, stage2: Node, stage3: Node) extends Area { + val ONE = stage1.insert(stage1(INPUT) + 1) + val TWO = stage2.insert(stage2(ONE) + 1) + val THREE = stage3.insert(stage3(TWO) + 1) + } + + // Let's define a component which takes a stream as input, + // which carries 'lanesCount' values that we want to process in parallel + // and put the result on an output stream + class TopLevel(lanesCount : Int) extends Component { + val io = new Bundle{ + val up = slave Stream(Vec.fill(lanesCount)(UInt(16 bits))) + val down = master Stream(Vec.fill(lanesCount)(UInt(16 bits))) + } + + // Let's define 3 Nodes for our pipeline + val n0, n1, n2 = Node() + + // Let's connect those nodes by using simples registers + val s01 = StageConnector(n0, n1) + val s12 = StageConnector(n1, n2) + + // Let's bind io.up to n0 + n0.arbitrateFrom(io.up) + val LANES_INPUT = io.up.payload.map(n0.insert(_)) + + // Let's use our "reusable" Plus3 area to generate each processing lane + val lanes = for(i <- 0 until lanesCount) yield new PLus3(LANES_INPUT(i), n0, n1, n2) + + // Let's bind n2 to io.down + n2.arbitrateTo(io.down) + for(i <- 0 until lanesCount) io.down.payload(i) := n2(lanes(i).THREE) + + // Let's ask the builder to generate all the required hardware + Builder(s01, s12) + } + +This will produce the following data path (assuming lanesCount = 2), abitration not being shown : + +.. image:: /asset/image/pipeline/composable_lanes.png + :scale: 70 % + + +Retiming / Variable lenth +================================================ + +Sometime you want to design a pipeline, but you don't realy know where will be the critical paths / right balance between stages, and you can't realy rely on the synthesis tool doing a good job with automatic retiming. + +So, you kind of need a easy way to move around the logic of your pipeline. + +Here is how it can be done with this pipelining API : + + +.. code-block:: scala + + // Define a component which will take a input stream of RGB value + // Process (~(R + G + B)) * 0xEE + // And provide that result into an output stream + class RgbToSomething(addAt : Int, + invAt : Int, + mulAt : Int, + resultAt : Int) extends Component { + + val io = new Bundle { + val up = slave Stream(spinal.lib.graphic.Rgb(8, 8, 8)) + val down = master Stream (UInt(16 bits)) + } + + // Let's define the Nodes for our pipeline + val nodes = Array.fill(resultAt+1)(Node()) + + // Let's specify which node will be used for what part of the pipeline + val insertNode = nodes(0) + val addNode = nodes(addAt) + val invNode = nodes(invAt) + val mulNode = nodes(mulAt) + val resultNode = nodes(resultAt) + + // Define the hardware which will feed the io.up stream into the pipeline + val inserter = new insertNode.Area { + arbitrateFrom(io.up) + val RGB = insert(io.up.payload) + } + + // sum the r g b values of the color + val adder = new addNode.Area { + val SUM = insert(inserter.RGB.r + inserter.RGB.g + inserter.RGB.b) + } + + // flip all the bit of the RGB sum + val inverter = new invNode.Area { + val INV = insert(~adder.SUM) + } + + // multiplie the inverted bits with 0xEE + val multiplier = new mulNode.Area { + val MUL = insert(inverter.INV*0xEE) + } + + // Connect the end of the pipeline to the io.down stream + val resulter = new resultNode.Area { + arbitrateTo(io.down) + io.down.payload := multiplier.MUL + } + + // Let's connect those nodes sequencialy by using simples registers + val connectors = for (i <- 0 to resultAt - 1) yield StageConnector(nodes(i), nodes(i + 1)) + + // Let's ask the builder to generate all the required hardware + Builder(connectors) + } + +If then you generate this component like this : + +.. code-block:: scala + + SpinalVerilog( + new RgbToSomething( + addAt = 0, + invAt = 1, + mulAt = 2, + resultAt = 3 + ) + ) + +You will get a 4 stages separated by 3 layer of flip flop doing your processing : + +.. image:: /asset/image/pipeline/rgbToSomething.png + :scale: 70 % + +Note the generated hardware verilog is kinda clean (by my standards at least :P) : + +.. code-block:: verilog + + // Generator : SpinalHDL dev git head : 1259510dd72697a4f2c388ad22b269d4d2600df7 + // Component : RgbToSomething + // Git hash : 63da021a1cd082d22124888dd6c1e5017d4a37b2 + + `timescale 1ns/1ps + + module RgbToSomething ( + input wire io_up_valid, + output wire io_up_ready, + input wire [7:0] io_up_payload_r, + input wire [7:0] io_up_payload_g, + input wire [7:0] io_up_payload_b, + output wire io_down_valid, + input wire io_down_ready, + output wire [15:0] io_down_payload, + input wire clk, + input wire reset + ); + + wire [7:0] _zz_nodes_0_adder_SUM; + reg [15:0] nodes_3_multiplier_MUL; + wire [15:0] nodes_2_multiplier_MUL; + reg [7:0] nodes_2_inverter_INV; + wire [7:0] nodes_1_inverter_INV; + reg [7:0] nodes_1_adder_SUM; + wire [7:0] nodes_0_adder_SUM; + wire [7:0] nodes_0_inserter_RGB_r; + wire [7:0] nodes_0_inserter_RGB_g; + wire [7:0] nodes_0_inserter_RGB_b; + wire nodes_0_valid; + reg nodes_0_ready; + reg nodes_1_valid; + reg nodes_1_ready; + reg nodes_2_valid; + reg nodes_2_ready; + reg nodes_3_valid; + wire nodes_3_ready; + wire when_StageConnector_l56; + wire when_StageConnector_l56_1; + wire when_StageConnector_l56_2; + + assign _zz_nodes_0_adder_SUM = (nodes_0_inserter_RGB_r + nodes_0_inserter_RGB_g); + assign nodes_0_valid = io_up_valid; + assign io_up_ready = nodes_0_ready; + assign nodes_0_inserter_RGB_r = io_up_payload_r; + assign nodes_0_inserter_RGB_g = io_up_payload_g; + assign nodes_0_inserter_RGB_b = io_up_payload_b; + assign nodes_0_adder_SUM = (_zz_nodes_0_adder_SUM + nodes_0_inserter_RGB_b); + assign nodes_1_inverter_INV = (~ nodes_1_adder_SUM); + assign nodes_2_multiplier_MUL = (nodes_2_inverter_INV * 8'hee); + assign io_down_valid = nodes_3_valid; + assign nodes_3_ready = io_down_ready; + assign io_down_payload = nodes_3_multiplier_MUL; + always @(*) begin + nodes_0_ready = nodes_1_ready; + if(when_StageConnector_l56) begin + nodes_0_ready = 1'b1; + end + end + + assign when_StageConnector_l56 = (! nodes_1_valid); + always @(*) begin + nodes_1_ready = nodes_2_ready; + if(when_StageConnector_l56_1) begin + nodes_1_ready = 1'b1; + end + end + + assign when_StageConnector_l56_1 = (! nodes_2_valid); + always @(*) begin + nodes_2_ready = nodes_3_ready; + if(when_StageConnector_l56_2) begin + nodes_2_ready = 1'b1; + end + end + + assign when_StageConnector_l56_2 = (! nodes_3_valid); + always @(posedge clk or posedge reset) begin + if(reset) begin + nodes_1_valid <= 1'b0; + nodes_2_valid <= 1'b0; + nodes_3_valid <= 1'b0; + end else begin + if(nodes_0_ready) begin + nodes_1_valid <= nodes_0_valid; + end + if(nodes_1_ready) begin + nodes_2_valid <= nodes_1_valid; + end + if(nodes_2_ready) begin + nodes_3_valid <= nodes_2_valid; + end + end + end + + always @(posedge clk) begin + if(nodes_0_ready) begin + nodes_1_adder_SUM <= nodes_0_adder_SUM; + end + if(nodes_1_ready) begin + nodes_2_inverter_INV <= nodes_1_inverter_INV; + end + if(nodes_2_ready) begin + nodes_3_multiplier_MUL <= nodes_2_multiplier_MUL; + end + end + + + endmodule + + +Also, you can easily tweek how many stages and where you want the processing to be done, for instance you may want to move the invertion hardware in the same stage as the adder. This can be done the following way : + + +.. code-block:: scala + + SpinalVerilog( + new RgbToSomething( + addAt = 0, + invAt = 0, + mulAt = 1, + resultAt = 2 + ) + ) + +Then you may want to remove the output register stage : + +.. code-block:: scala + + SpinalVerilog( + new RgbToSomething( + addAt = 0, + invAt = 0, + mulAt = 1, + resultAt = 1 + ) + ) + + diff --git a/source/asset/image/pipeline/composable_lanes.png b/source/asset/image/pipeline/composable_lanes.png new file mode 100644 index 0000000000000000000000000000000000000000..17712eb33cf81d42ba745cb8e1ca6eb1d47c4ea6 GIT binary patch literal 97456 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 KZwWhCoZ#;)vZma(=;$WFF4Yaxk}B2sn| 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 v;Yp~uDhRnz4sSYt2%0|-B=ux)XNQ Date: Thu, 9 Nov 2023 14:34:34 +0100 Subject: [PATCH 06/15] Stageable -> NamedType --- .../Libraries/Pipeline/introduction.rst | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index 621aef0a859..2f6732fc786 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -14,9 +14,9 @@ The API is composed of 4 main things : - Node : which represent a layer in the pipeline - Connector : which allows to connect nodes to each others - Builder : which will generate the hardware required for a whole pipeline -- Stageable : which represent a data thing that you can retrieve Nodes the pipeline +- NamedType : which are used to define a hardware signal which can go through the pipeline -It is important to understande that Stageable isn't a hardware data instance, but a key to retrieve a data along the pipeline on nodes. +It is important to understande that NamedType isn't a hardware data instance, but a key to retrieve a data along the pipeline on nodes. Here is an example to illustrate : @@ -49,9 +49,9 @@ Here is a simple example which only use the basics of the API : val s01 = StageConnector(n0, n1) val s12 = StageConnector(n1, n2) - // Let's define a few stageable things that can go through the pipeline - val VALUE = Stageable(UInt(16 bits)) - val RESULT = Stageable(UInt(16 bits)) + // Let's define a few NamedType things that can go through the pipeline + val VALUE = NamedType(UInt(16 bits)) + val RESULT = NamedType(UInt(16 bits)) // Let's bind io.up to n0 io.up.ready := n0.ready @@ -90,7 +90,7 @@ Here is the same example but using more of the API : import spinal.lib.misc.pipeline._ class TopLevel extends Component { - val VALUE = Stageable(UInt(16 bits)) + val VALUE = NamedType(UInt(16 bits)) val io = new Bundle{ val up = slave Stream(VALUE) //VALUE can also be used as a HardType @@ -121,15 +121,15 @@ Here is the same example but using more of the API : builder.genStagedPipeline() } -Stageable +NamedType ============ -Stageable class can be instanciated to represent some data which can go through the pipeline. Technicaly speaking, Stageable is a HardType which has a name and is used as a "key" to retrieve stuff. +NamedType class can be instanciated to represent some data which can go through the pipeline. Technicaly speaking, NamedType is a HardType which has a name and is used as a "key" to retrieve stuff. .. code-block:: scala - val PC = Stageable(UInt(32 bits)) - val PC_PLUS_4 = Stageable(UInt(32 bits)) + val PC = NamedType(UInt(32 bits)) + val PC_PLUS_4 = NamedType(UInt(32 bits)) val n0, n1 = Node() val s01 = StageConnector(n0, n1) @@ -137,12 +137,12 @@ Stageable class can be instanciated to represent some data which can go through n0(PC) := 0x42 n1(PC_PLUS_4) := n1(PC) + 4 -Note that I got used to name the Stageable instances using uppercase. This is to make it very explicit that the thing isn't a hardware signal, but are more like a "key" to access things. +Note that I got used to name the NamedType instances using uppercase. This is to make it very explicit that the thing isn't a hardware signal, but are more like a "key/type" to access things. Node ============ -Node mostly host the valid/ready arbitration signal, and the hardware signal required for all the Stageable values going through it. +Node mostly host the valid/ready arbitration signal, and the hardware signal required for all the NamedType values going through it. You can access its arbitration via : @@ -168,7 +168,7 @@ You can access its arbitration via : * - node.isRemoved - True when the node is being flushed -You can access its stageable's signals via : +You can access its NamedType's signals via : .. list-table:: :header-rows: 1 @@ -176,12 +176,12 @@ You can access its stageable's signals via : * - API - Description - * - node(Stageable) + * - node(NamedType) - Return the corresponding hardware signal - * - node(Stageable, Any) + * - node(NamedType, Any) - Same as above, but include a second argument which is used as a "secondary key". This ease the construction of multi lane hardware. For instance, when you have a multi issue CPU pipeline, you can use the lane Int id as secondary key * - node.insert(Data) - - Return a new Stageable instance which is connected to the given Data hardware signal + - Return a new NamedType instance which is connected to the given Data hardware signal @@ -189,11 +189,11 @@ You can access its stageable's signals via : val n0, n1 = Node() - val PC = Stageable(UInt(32 bits)) + val PC = NamedType(UInt(32 bits)) n0(PC) := 0x42 n0(PC, "true") := 0x42 n0(PC, 0x666) := 0xEE - val SOMETHING = n0.insert(myHardwareSignal) //This create a new Stageable + val SOMETHING = n0.insert(myHardwareSignal) //This create a new NamedType when(n1(SOMETHING) === 0xFFAA){ ... } @@ -213,7 +213,7 @@ Also, there is an API to define nodes which are always valid / ready .. code-block:: scala val n0, n1, n2 = Node() - val OUT = Stageable(UInt(16 bits)) + val OUT = NamedType(UInt(16 bits)) val outputFlow = master Flow(UInt(16 bits)) outputFlow.valid := n2.valid @@ -251,8 +251,8 @@ While you can manualy drive/read the arbitration/data of the first/last stage of val n0, n1, n2 = Node() - val IN = Stageable(UInt(16 bits)) - val OUT = Stageable(UInt(16 bits)) + val IN = NamedType(UInt(16 bits)) + val OUT = NamedType(UInt(16 bits)) n1(OUT) := n1(IN) + 0x42 @@ -264,11 +264,11 @@ While you can manualy drive/read the arbitration/data of the first/last stage of n2.driveTo(down)((payload, self) => payload := self(OUT)) -In order to reduce verbosity, there is a set of implicit convertions between stageable toward their data representation which can be used when you are in the context of a Node : +In order to reduce verbosity, there is a set of implicit convertions between NamedType toward their data representation which can be used when you are in the context of a Node : .. code-block:: scala - val VALUE = Stageable(UInt(16 bits)) + val VALUE = NamedType(UInt(16 bits)) val n1 = new Node{ val PLUS_ONE = insert(VALUE + 1) // VALUE is implicitly converted into its n1(VALUE) representation } @@ -277,7 +277,7 @@ You can also use those implicit convertions by importing them : .. code-block:: scala - val VALUE = Stageable(UInt(16 bits)) + val VALUE = NamedType(UInt(16 bits)) val n1 = Node() val n1Stuff = new Area { @@ -291,7 +291,7 @@ There is also an API which alows you to create new Area which provide the whole .. code-block:: scala val n1 = Node() - val VALUE = Stageable(UInt(16 bits)) + val VALUE = NamedType(UInt(16 bits)) val n1Stuff = new n1.Area{ val PLUS_ONE = insert(VALUE) + 1 // Equivalent to n1.insert(n1(VALUE)) + 1 @@ -375,7 +375,7 @@ Also note that if you want to do flow control in a conditional scope (ex in a wh You can retrieve which node are connected using node.up / node.down. -The CtrlConnector also provide an API to access stageable : +The CtrlConnector also provide an API to access NamedType : .. list-table:: :header-rows: 1 @@ -383,25 +383,25 @@ The CtrlConnector also provide an API to access stageable : * - API - Description - * - connector(Stageable) - - Same as connector.down(Stageable) - * - connector(Stageable, Any) - - Same as connector.down(Stageable, Any) + * - connector(NamedType) + - Same as connector.down(NamedType) + * - connector(NamedType, Any) + - Same as connector.down(NamedType, Any) * - connector.insert(Data) - Same as connector.down.insert(Data) - * - connector.bypass(Stageable) - - Allows to conditionaly override a stageable value between connector.up -> connector.down. This can be used to fix data hazard in CPU pipelines for instance. + * - connector.bypass(NamedType) + - Allows to conditionaly override a NamedType value between connector.up -> connector.down. This can be used to fix data hazard in CPU pipelines for instance. .. code-block:: scala val c01 = CtrlConnector(n0, n1) - val PC = Stageable(UInt(32 bits)) + val PC = NamedType(UInt(32 bits)) c01(PC) := 0x42 c01(PC, 0x666) := 0xEE - val DATA = Stageable(UInt(32 bits)) + val DATA = NamedType(UInt(32 bits)) // Let's say Data is inserted in the pipeline before c01 when(hazard){ c01.bypass(DATA) := fixedValue @@ -487,7 +487,7 @@ The example below show a pattern which compose a pipeline with multiple lanes to // This area allows to take a input value and do +1 +1 +1 over 3 stages. // I know that's useless, but let's pretend that instead it does a multiplication between two numbers over 3 stages (for FMax reasons) - class PLus3(INPUT: Stageable[UInt], stage1: Node, stage2: Node, stage3: Node) extends Area { + class PLus3(INPUT: NamedType[UInt], stage1: Node, stage2: Node, stage3: Node) extends Area { val ONE = stage1.insert(stage1(INPUT) + 1) val TWO = stage2.insert(stage2(ONE) + 1) val THREE = stage3.insert(stage3(TWO) + 1) From 78a9c2f45ce8bac74643eaf91b656b60d0db1539 Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Thu, 9 Nov 2023 17:48:34 +0100 Subject: [PATCH 07/15] Better doc, thanks for the feedback you gived me <3 --- .../Libraries/Pipeline/introduction.rst | 138 +++++++++++------- 1 file changed, 85 insertions(+), 53 deletions(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index 2f6732fc786..e95ddef7dfa 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -4,19 +4,19 @@ Introduction spinal.lib.misc.pipeline provide a pipelining API. The main advantages over manual pipelining are : -- You don't have to pre define data structure with all the element which goes through each individual stage -- You can compose the pipeline (as a consequence of the above point) +- You don't have to pre define upfront all the signal elements needed for the entire staged system, you can create and consume stagable signals in a more adhoc fashion as your design requires without needing to refactor all the intervening stages to know about the signal +- Signals of the pipeline can utilize the powerful parametrization capabilities of SpinalHDL and be subject to optimization/removal if a specific design build does not require a particular parametrized feature, without any need to modify the staging system design or project code base in a significant way. - Manual retiming is much easier, as you don't have to handle the registers / arbitration manualy - Manage the arbitration by itself The API is composed of 4 main things : -- Node : which represent a layer in the pipeline +- Node : which represents a layer in the pipeline - Connector : which allows to connect nodes to each others - Builder : which will generate the hardware required for a whole pipeline -- NamedType : which are used to define a hardware signal which can go through the pipeline +- SignalKey : which are used to retrieve hardware signals on nodes along the pipeline -It is important to understande that NamedType isn't a hardware data instance, but a key to retrieve a data along the pipeline on nodes. +It is important to understand that SignalKey isn't a hardware data/signal instance, but a key to retrieve a data/signal on nodes along the pipeline, and that the pipeline builder will then automatically interconnect/pipeline every occurrence of a given SignalKey between nodes. Here is an example to illustrate : @@ -49,9 +49,9 @@ Here is a simple example which only use the basics of the API : val s01 = StageConnector(n0, n1) val s12 = StageConnector(n1, n2) - // Let's define a few NamedType things that can go through the pipeline - val VALUE = NamedType(UInt(16 bits)) - val RESULT = NamedType(UInt(16 bits)) + // Let's define a few SignalKey things that can go through the pipeline + val VALUE = SignalKey(UInt(16 bits)) + val RESULT = SignalKey(UInt(16 bits)) // Let's bind io.up to n0 io.up.ready := n0.ready @@ -90,7 +90,7 @@ Here is the same example but using more of the API : import spinal.lib.misc.pipeline._ class TopLevel extends Component { - val VALUE = NamedType(UInt(16 bits)) + val VALUE = SignalKey(UInt(16 bits)) val io = new Bundle{ val up = slave Stream(VALUE) //VALUE can also be used as a HardType @@ -121,15 +121,15 @@ Here is the same example but using more of the API : builder.genStagedPipeline() } -NamedType +SignalKey ============ -NamedType class can be instanciated to represent some data which can go through the pipeline. Technicaly speaking, NamedType is a HardType which has a name and is used as a "key" to retrieve stuff. +SignalKey class can be instanciated to represent some data which can go through the pipeline. Technicaly speaking, SignalKey is a HardType which has a name and is used as a "key" to retrieve stuff. .. code-block:: scala - val PC = NamedType(UInt(32 bits)) - val PC_PLUS_4 = NamedType(UInt(32 bits)) + val PC = SignalKey(UInt(32 bits)) + val PC_PLUS_4 = SignalKey(UInt(32 bits)) val n0, n1 = Node() val s01 = StageConnector(n0, n1) @@ -137,38 +137,68 @@ NamedType class can be instanciated to represent some data which can go through n0(PC) := 0x42 n1(PC_PLUS_4) := n1(PC) + 4 -Note that I got used to name the NamedType instances using uppercase. This is to make it very explicit that the thing isn't a hardware signal, but are more like a "key/type" to access things. +Note that I got used to name the SignalKey instances using uppercase. This is to make it very explicit that the thing isn't a hardware signal, but are more like a "key/type" to access things. Node ============ -Node mostly host the valid/ready arbitration signal, and the hardware signal required for all the NamedType values going through it. +Node mostly host the valid/ready arbitration signal, and the hardware signal required for all the SignalKey values going through it. You can access its arbitration via : .. list-table:: :header-rows: 1 - :widths: 1 5 + :widths: 2 1 10 * - API + - Access - Description * - node.valid - - Is the signal which specify if a transaction is present on the node + - RW + - Is the signal which specify if a transaction is present on the node. It is driven by the upstream. Once asserted, it can only be dropped the cycle after which ready is high or node.isRemoved. * - node.ready - - Is the signal which specify if the node transaction can move away. + - RW + - Is the signal which specify if the node's transaction should move away. It is driven by the downstream to create backpresure. The signal has no meaning when there is no transaction (node.valid being deasserted) * - node.isValid + - RO - node.valid's read only accessor * - node.isReady + - RO - node.ready's read only accessor * - node.isFiring - - True when the node transaction is successfuly moving futher (isValid && isReady && !isRemoved). Usefull to commit state changes + - RO + - True when the node transaction is successfuly moving futher (isValid && isReady && !isRemoved). Useful to commit state changes. * - node.isMoving - - True when the node transaction is moving (isValid && (isReady || isRemoved)). Usefull to "reset" states + - RO + - True when the node transaction is moving away from the node (will not be in the node anymore starting from the next cycle), + either because downstream is ready to take the transaction, + either because the transaction is removed/flushed from the while pipeline. (isValid && (isReady || isRemoved)). Useful to "reset" states. * - node.isRemoved - - True when the node is being flushed + - RO + - True when the node is being marked to be removed/flushed. Meaning that it will not appear anywhere in the pipeline in future cycles. -You can access its NamedType's signals via : +Note that the node.valid/node.ready signals follows the same conventions than the Stream's ones. + +Here is a list of arbitration cases you can have on a node. valid/ready/isRemoved define the state we are in, while isFiring/isMoving result of those : + ++-------+-------+-----------+------------------------------+----------+----------+ +| valid | ready | isRemoved | Description | isFiring | isMoving | ++=======+=======+===========+==============================+==========+==========+ +| 0 | X | 0 | No transaction | 0 | 0 | ++-------+-------+-----------+------------------------------+----------+----------+ +| 1 | 1 | 0 | Going through | 1 | 1 | ++-------+-------+-----------+------------------------------+----------+----------+ +| 1 | 0 | 0 | Blocked | 0 | 0 | ++-------+-------+-----------+------------------------------+----------+----------+ +| 1 | X | 1 | Removed | 0 | 1 | ++-------+-------+-----------+------------------------------+----------+----------+ +| 1 | 0 | 1 | Blocked and Removed | 0 | 1 | ++-------+-------+-----------+------------------------------+----------+----------+ + +Note that if you want to model things like for instance a CPU stage which can block and flush stuff, take a look a the CtrlConnector, as it provide the API to do such things. + +You can access its SignalKey's signals via : .. list-table:: :header-rows: 1 @@ -176,12 +206,12 @@ You can access its NamedType's signals via : * - API - Description - * - node(NamedType) + * - node(SignalKey) - Return the corresponding hardware signal - * - node(NamedType, Any) + * - node(SignalKey, Any) - Same as above, but include a second argument which is used as a "secondary key". This ease the construction of multi lane hardware. For instance, when you have a multi issue CPU pipeline, you can use the lane Int id as secondary key * - node.insert(Data) - - Return a new NamedType instance which is connected to the given Data hardware signal + - Return a new SignalKey instance which is connected to the given Data hardware signal @@ -189,11 +219,11 @@ You can access its NamedType's signals via : val n0, n1 = Node() - val PC = NamedType(UInt(32 bits)) + val PC = SignalKey(UInt(32 bits)) n0(PC) := 0x42 n0(PC, "true") := 0x42 n0(PC, 0x666) := 0xEE - val SOMETHING = n0.insert(myHardwareSignal) //This create a new NamedType + val SOMETHING = n0.insert(myHardwareSignal) //This create a new SignalKey when(n1(SOMETHING) === 0xFFAA){ ... } @@ -208,12 +238,12 @@ Also, there is an API to define nodes which are always valid / ready * - node.setAlwaysValid() - Specify that the valid signal of the given node is always True. To use on the first node of a pipeline * - node.setAlwaysReady() - - Specify that the ready signal of the given node is always True. To use on the last node of a pipeline, usefull if you don't have to implement backpresure. + - Specify that the ready signal of the given node is always True. To use on the last node of a pipeline, useful if you don't have to implement backpresure. .. code-block:: scala val n0, n1, n2 = Node() - val OUT = NamedType(UInt(16 bits)) + val OUT = SignalKey(UInt(16 bits)) val outputFlow = master Flow(UInt(16 bits)) outputFlow.valid := n2.valid @@ -238,11 +268,11 @@ While you can manualy drive/read the arbitration/data of the first/last stage of * - node.arbitrateTo(Flow[T]]) - Drive a Flow arbitration from the node. * - node.driveFrom(Stream[T]])((Node, T) => Unit) - - Drive a node from a stream. The provided landa function can be use to connect the data + - Drive a node from a stream. The provided lambda function can be use to connect the data * - node.driveFrom(Flow[T]])((Node, T) => Unit) - Same as above but for Flow * - node.driveTo(Stream[T]])((T, Node) => Unit) - - Drive a stream from the node. The provided landa function can be use to connect the data + - Drive a stream from the node. The provided lambda function can be use to connect the data * - node.driveTo(Flow[T]])((T, Node) => Unit) - Same as above but for Flow @@ -251,8 +281,8 @@ While you can manualy drive/read the arbitration/data of the first/last stage of val n0, n1, n2 = Node() - val IN = NamedType(UInt(16 bits)) - val OUT = NamedType(UInt(16 bits)) + val IN = SignalKey(UInt(16 bits)) + val OUT = SignalKey(UInt(16 bits)) n1(OUT) := n1(IN) + 0x42 @@ -264,20 +294,20 @@ While you can manualy drive/read the arbitration/data of the first/last stage of n2.driveTo(down)((payload, self) => payload := self(OUT)) -In order to reduce verbosity, there is a set of implicit convertions between NamedType toward their data representation which can be used when you are in the context of a Node : +In order to reduce verbosity, there is a set of implicit conversions between SignalKey toward their data representation which can be used when you are in the context of a Node : .. code-block:: scala - val VALUE = NamedType(UInt(16 bits)) + val VALUE = SignalKey(UInt(16 bits)) val n1 = new Node{ val PLUS_ONE = insert(VALUE + 1) // VALUE is implicitly converted into its n1(VALUE) representation } -You can also use those implicit convertions by importing them : +You can also use those implicit conversions by importing them : .. code-block:: scala - val VALUE = NamedType(UInt(16 bits)) + val VALUE = SignalKey(UInt(16 bits)) val n1 = Node() val n1Stuff = new Area { @@ -291,19 +321,21 @@ There is also an API which alows you to create new Area which provide the whole .. code-block:: scala val n1 = Node() - val VALUE = NamedType(UInt(16 bits)) + val VALUE = SignalKey(UInt(16 bits)) val n1Stuff = new n1.Area{ val PLUS_ONE = insert(VALUE) + 1 // Equivalent to n1.insert(n1(VALUE)) + 1 } -Such feature is very usefull when you have parametrable pipelines locations for your hardware (see retiming example). +Such feature is very useful when you have parametrizable pipeline locations for your hardware (see retiming example). Connectors ============ -There is few different connectors already implemented (but you could also create your own custom one) : +There is few different connectors already implemented (but you could also create your own custom one). +The idea of connectors is to connect two nodes together in various ways. +They generally have a `up` Node and a `down` Node. DirectConnector ------------------ @@ -330,7 +362,7 @@ This connect two nodes using registers on the data / valid signals and some arbi S2mConnector ------------------ -This connect two nodes using registers on the ready signal, which can be usefull to improve backpresure combinatorial timings. +This connect two nodes using registers on the ready signal, which can be useful to improve backpresure combinatorial timings. .. code-block:: scala @@ -375,7 +407,7 @@ Also note that if you want to do flow control in a conditional scope (ex in a wh You can retrieve which node are connected using node.up / node.down. -The CtrlConnector also provide an API to access NamedType : +The CtrlConnector also provide an API to access SignalKey : .. list-table:: :header-rows: 1 @@ -383,25 +415,25 @@ The CtrlConnector also provide an API to access NamedType : * - API - Description - * - connector(NamedType) - - Same as connector.down(NamedType) - * - connector(NamedType, Any) - - Same as connector.down(NamedType, Any) + * - connector(SignalKey) + - Same as connector.down(SignalKey) + * - connector(SignalKey, Any) + - Same as connector.down(SignalKey, Any) * - connector.insert(Data) - Same as connector.down.insert(Data) - * - connector.bypass(NamedType) - - Allows to conditionaly override a NamedType value between connector.up -> connector.down. This can be used to fix data hazard in CPU pipelines for instance. + * - connector.bypass(SignalKey) + - Allows to conditionaly override a SignalKey value between connector.up -> connector.down. This can be used to fix data hazard in CPU pipelines for instance. .. code-block:: scala val c01 = CtrlConnector(n0, n1) - val PC = NamedType(UInt(32 bits)) + val PC = SignalKey(UInt(32 bits)) c01(PC) := 0x42 c01(PC, 0x666) := 0xEE - val DATA = NamedType(UInt(32 bits)) + val DATA = SignalKey(UInt(32 bits)) // Let's say Data is inserted in the pipeline before c01 when(hazard){ c01.bypass(DATA) := fixedValue @@ -409,7 +441,7 @@ The CtrlConnector also provide an API to access NamedType : // c01(DATA) and below will get the hazard patch -Note that if you create a CtrlConnector without node arguements, it will create its own nodes internaly. +Note that if you create a CtrlConnector without node arguments, it will create its own nodes internally. .. code-block:: scala @@ -461,7 +493,7 @@ To generate the hardware of your pipeline, you need to give a list of all the co There is also a set of "all in one" builders that you can instanciate to help yourself. -For instance there is the NodesBuilder class which can be used to create sequencialy staged pipelines : +For instance there is the NodesBuilder class which can be used to create sequentially staged pipelines : .. code-block:: scala @@ -487,7 +519,7 @@ The example below show a pattern which compose a pipeline with multiple lanes to // This area allows to take a input value and do +1 +1 +1 over 3 stages. // I know that's useless, but let's pretend that instead it does a multiplication between two numbers over 3 stages (for FMax reasons) - class PLus3(INPUT: NamedType[UInt], stage1: Node, stage2: Node, stage3: Node) extends Area { + class PLus3(INPUT: SignalKey[UInt], stage1: Node, stage2: Node, stage3: Node) extends Area { val ONE = stage1.insert(stage1(INPUT) + 1) val TWO = stage2.insert(stage2(ONE) + 1) val THREE = stage3.insert(stage3(TWO) + 1) From 369123fa54bf6380529c738b25957ce72bf276be Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Thu, 9 Nov 2023 21:10:39 +0100 Subject: [PATCH 08/15] Apply suggestions from code review Co-authored-by: Andreas Wallner --- .../Libraries/Pipeline/introduction.rst | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index e95ddef7dfa..9336732818f 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -2,9 +2,9 @@ Introduction ============ -spinal.lib.misc.pipeline provide a pipelining API. The main advantages over manual pipelining are : +spinal.lib.misc.pipeline provides a pipelining API. The main advantages over manual pipelining are : -- You don't have to pre define upfront all the signal elements needed for the entire staged system, you can create and consume stagable signals in a more adhoc fashion as your design requires without needing to refactor all the intervening stages to know about the signal +- You don't have to predefine all the signal elements needed for the entire staged system upfront. You can create and consume stagable signals in a more ad hoc fashion as your design requires - without needing to refactor all the intervening stages to know about the signal - Signals of the pipeline can utilize the powerful parametrization capabilities of SpinalHDL and be subject to optimization/removal if a specific design build does not require a particular parametrized feature, without any need to modify the staging system design or project code base in a significant way. - Manual retiming is much easier, as you don't have to handle the registers / arbitration manualy - Manage the arbitration by itself @@ -12,7 +12,7 @@ spinal.lib.misc.pipeline provide a pipelining API. The main advantages over manu The API is composed of 4 main things : - Node : which represents a layer in the pipeline -- Connector : which allows to connect nodes to each others +- Connector : which allows to connect nodes to each other - Builder : which will generate the hardware required for a whole pipeline - SignalKey : which are used to retrieve hardware signals on nodes along the pipeline @@ -26,7 +26,7 @@ Here is an example to illustrate : Simple example ---------------- -Here is a simple example which only use the basics of the API : +Here is a simple example which only uses the basics of the API : .. code-block:: scala @@ -124,7 +124,7 @@ Here is the same example but using more of the API : SignalKey ============ -SignalKey class can be instanciated to represent some data which can go through the pipeline. Technicaly speaking, SignalKey is a HardType which has a name and is used as a "key" to retrieve stuff. +SignalKey objects are used to refer to data which can go through the pipeline. Technicaly speaking, SignalKey is a HardType which has a name and is used as a "key" to retrieve the signals in a certain pipeline stage. .. code-block:: scala @@ -142,7 +142,7 @@ Note that I got used to name the SignalKey instances using uppercase. This is to Node ============ -Node mostly host the valid/ready arbitration signal, and the hardware signal required for all the SignalKey values going through it. +Node mostly hosts the valid/ready arbitration signals, and the hardware signals required for all the SignalKey values going through it. You can access its arbitration via : @@ -156,7 +156,7 @@ You can access its arbitration via : - Description * - node.valid - RW - - Is the signal which specify if a transaction is present on the node. It is driven by the upstream. Once asserted, it can only be dropped the cycle after which ready is high or node.isRemoved. + - Is the signal which specifies if a transaction is present on the node. It is driven by the upstream. Once asserted, it must only be de-asserted the cycle after which either both valid and ready or node.isRemoved are high. valid must not depend on ready. * - node.ready - RW - Is the signal which specify if the node's transaction should move away. It is driven by the downstream to create backpresure. The signal has no meaning when there is no transaction (node.valid being deasserted) @@ -173,7 +173,7 @@ You can access its arbitration via : - RO - True when the node transaction is moving away from the node (will not be in the node anymore starting from the next cycle), either because downstream is ready to take the transaction, - either because the transaction is removed/flushed from the while pipeline. (isValid && (isReady || isRemoved)). Useful to "reset" states. + or because the transaction is removed/flushed from the pipeline. (isValid && (isReady || isRemoved)). Useful to "reset" states. * - node.isRemoved - RO - True when the node is being marked to be removed/flushed. Meaning that it will not appear anywhere in the pipeline in future cycles. @@ -185,7 +185,7 @@ Here is a list of arbitration cases you can have on a node. valid/ready/isRemove +-------+-------+-----------+------------------------------+----------+----------+ | valid | ready | isRemoved | Description | isFiring | isMoving | +=======+=======+===========+==============================+==========+==========+ -| 0 | X | 0 | No transaction | 0 | 0 | +| 0 | X | X | No transaction | 0 | 0 | +-------+-------+-----------+------------------------------+----------+----------+ | 1 | 1 | 0 | Going through | 1 | 1 | +-------+-------+-----------+------------------------------+----------+----------+ @@ -196,9 +196,9 @@ Here is a list of arbitration cases you can have on a node. valid/ready/isRemove | 1 | 0 | 1 | Blocked and Removed | 0 | 1 | +-------+-------+-----------+------------------------------+----------+----------+ -Note that if you want to model things like for instance a CPU stage which can block and flush stuff, take a look a the CtrlConnector, as it provide the API to do such things. +Note that if you want to model things like for instance a CPU stage which can block and flush stuff, take a look a the CtrlConnector, as it provides the API to do such things. -You can access its SignalKey's signals via : +You can access signals referenced by a SignalKey via: .. list-table:: :header-rows: 1 @@ -209,7 +209,7 @@ You can access its SignalKey's signals via : * - node(SignalKey) - Return the corresponding hardware signal * - node(SignalKey, Any) - - Same as above, but include a second argument which is used as a "secondary key". This ease the construction of multi lane hardware. For instance, when you have a multi issue CPU pipeline, you can use the lane Int id as secondary key + - Same as above, but include a second argument which is used as a "secondary key". This eases the construction of multi-lane hardware. For instance, when you have a multi issue CPU pipeline, you can use the lane Int id as secondary key * - node.insert(Data) - Return a new SignalKey instance which is connected to the given Data hardware signal @@ -373,7 +373,7 @@ CtrlConnector This is kind of a special connector, as connect two nodes with optional flow control / bypass logic. Its API should be flexible enough to implement a CPU stage with it. -Here is its flow control API (The Bool argument enable the feature) : +Here is its flow control API (The Bool arguments enable the features) : .. list-table:: :header-rows: 1 @@ -405,7 +405,7 @@ Also note that if you want to do flow control in a conditional scope (ex in a wh c01.haltIt() } -You can retrieve which node are connected using node.up / node.down. +You can retrieve which nodes are connected to the connector using node.up / node.down. The CtrlConnector also provide an API to access SignalKey : @@ -508,18 +508,18 @@ For instance there is the NodesBuilder class which can be used to create sequent Composability ======================== -One good thing about the API, is that it easily allows to compose a pipeline with multiple parallel things. What i mean by "compose" is that sometime the pipeline you need to design has parallel processing to do. +One good thing about the API is that it easily allows to compose a pipeline with multiple parallel things. What i mean by "compose" is that sometime the pipeline you need to design has parallel processing to do. -Imagine you need to do floating point multiplication on 4 pairs of numbers (to later sum them). If those 4 pairs a provided at the same time by a single stream of data, then you don't want 4 different pipeline to multiple them, instead you want to process them all in parallel in the same pipeline. +Imagine you need to do floating point multiplication on 4 pairs of numbers (to later sum them). If those 4 pairs a provided at the same time by a single stream of data, then you don't want 4 different pipelines to multiply them, instead you want to process them all in parallel in the same pipeline. -The example below show a pattern which compose a pipeline with multiple lanes to process them in parallel. +The example below show a pattern which composes a pipeline with multiple lanes to process them in parallel. .. code-block:: scala // This area allows to take a input value and do +1 +1 +1 over 3 stages. // I know that's useless, but let's pretend that instead it does a multiplication between two numbers over 3 stages (for FMax reasons) - class PLus3(INPUT: SignalKey[UInt], stage1: Node, stage2: Node, stage3: Node) extends Area { + class Plus3(INPUT: SignalKey[UInt], stage1: Node, stage2: Node, stage3: Node) extends Area { val ONE = stage1.insert(stage1(INPUT) + 1) val TWO = stage2.insert(stage2(ONE) + 1) val THREE = stage3.insert(stage3(TWO) + 1) @@ -546,7 +546,7 @@ The example below show a pattern which compose a pipeline with multiple lanes to val LANES_INPUT = io.up.payload.map(n0.insert(_)) // Let's use our "reusable" Plus3 area to generate each processing lane - val lanes = for(i <- 0 until lanesCount) yield new PLus3(LANES_INPUT(i), n0, n1, n2) + val lanes = for(i <- 0 until lanesCount) yield new Plus3(LANES_INPUT(i), n0, n1, n2) // Let's bind n2 to io.down n2.arbitrateTo(io.down) @@ -565,9 +565,9 @@ This will produce the following data path (assuming lanesCount = 2), abitration Retiming / Variable lenth ================================================ -Sometime you want to design a pipeline, but you don't realy know where will be the critical paths / right balance between stages, and you can't realy rely on the synthesis tool doing a good job with automatic retiming. +Sometime you want to design a pipeline, but you don't really know where the critical paths will be and what the right balance between stages is. And often you can't rely on the synthesis tool doing a good job with automatic retiming. -So, you kind of need a easy way to move around the logic of your pipeline. +So, you kind of need a easy way to move the logic of your pipeline around. Here is how it can be done with this pipelining API : @@ -613,7 +613,7 @@ Here is how it can be done with this pipelining API : val INV = insert(~adder.SUM) } - // multiplie the inverted bits with 0xEE + // multiply the inverted bits with 0xEE val multiplier = new mulNode.Area { val MUL = insert(inverter.INV*0xEE) } @@ -764,7 +764,7 @@ Note the generated hardware verilog is kinda clean (by my standards at least :P) endmodule -Also, you can easily tweek how many stages and where you want the processing to be done, for instance you may want to move the invertion hardware in the same stage as the adder. This can be done the following way : +Also, you can easily tweak how many stages and where you want the processing to be done, for instance you may want to move the inversion hardware in the same stage as the adder. This can be done the following way : .. code-block:: scala From b6df338e3c3c600c8a73c0895c6ffcb1f62dcc4a Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Fri, 10 Nov 2023 13:59:04 +0100 Subject: [PATCH 09/15] syncup --- .../Libraries/Pipeline/introduction.rst | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index 9336732818f..63ad9d861e1 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -156,10 +156,13 @@ You can access its arbitration via : - Description * - node.valid - RW - - Is the signal which specifies if a transaction is present on the node. It is driven by the upstream. Once asserted, it must only be de-asserted the cycle after which either both valid and ready or node.isRemoved are high. valid must not depend on ready. + - Is the signal which specifies if a transaction is present on the node. It is driven by the upstream. Once asserted, it must only be de-asserted the cycle after which either both valid and ready or node.cancel are high. valid must not depend on ready. * - node.ready - RW - Is the signal which specify if the node's transaction should move away. It is driven by the downstream to create backpresure. The signal has no meaning when there is no transaction (node.valid being deasserted) + * - node.cancel + - RW + - Is the signal which specify if the node's transaction in being canceled from the pipeline. It is driven by the downstream. The signal has no meaning when there is no transaction (node.valid being deasserted) * - node.isValid - RO - node.valid's read only accessor @@ -168,22 +171,22 @@ You can access its arbitration via : - node.ready's read only accessor * - node.isFiring - RO - - True when the node transaction is successfuly moving futher (isValid && isReady && !isRemoved). Useful to commit state changes. + - True when the node transaction is successfuly moving futher (valid && ready && !cancel). Useful to commit state changes. * - node.isMoving - RO - True when the node transaction is moving away from the node (will not be in the node anymore starting from the next cycle), either because downstream is ready to take the transaction, - or because the transaction is removed/flushed from the pipeline. (isValid && (isReady || isRemoved)). Useful to "reset" states. - * - node.isRemoved + or because the transaction is canceled from the pipeline. (valid && (ready || cancel)). Useful to "reset" states. + * - node.isCanceling - RO - - True when the node is being marked to be removed/flushed. Meaning that it will not appear anywhere in the pipeline in future cycles. + - True when the node transaction is being canceled. Meaning that it will not appear anywhere in the pipeline in future cycles. Note that the node.valid/node.ready signals follows the same conventions than the Stream's ones. -Here is a list of arbitration cases you can have on a node. valid/ready/isRemoved define the state we are in, while isFiring/isMoving result of those : +Here is a list of arbitration cases you can have on a node. valid/ready/cancel define the state we are in, while isFiring/isMoving result of those : +-------+-------+-----------+------------------------------+----------+----------+ -| valid | ready | isRemoved | Description | isFiring | isMoving | +| valid | ready | cancel | Description | isFiring | isMoving | +=======+=======+===========+==============================+==========+==========+ | 0 | X | X | No transaction | 0 | 0 | +-------+-------+-----------+------------------------------+----------+----------+ @@ -191,11 +194,10 @@ Here is a list of arbitration cases you can have on a node. valid/ready/isRemove +-------+-------+-----------+------------------------------+----------+----------+ | 1 | 0 | 0 | Blocked | 0 | 0 | +-------+-------+-----------+------------------------------+----------+----------+ -| 1 | X | 1 | Removed | 0 | 1 | -+-------+-------+-----------+------------------------------+----------+----------+ -| 1 | 0 | 1 | Blocked and Removed | 0 | 1 | +| 1 | X | 1 | Canceled | 0 | 1 | +-------+-------+-----------+------------------------------+----------+----------+ + Note that if you want to model things like for instance a CPU stage which can block and flush stuff, take a look a the CtrlConnector, as it provides the API to do such things. You can access signals referenced by a SignalKey via: @@ -384,9 +386,11 @@ Here is its flow control API (The Bool arguments enable the features) : * - haltWhen(Bool) - Allows to block the current transaction (clear up.ready down.valid) * - throwWhen(Bool) - - Allows to remove the current transaction from the pipeline (clear down.valid and remove the transaction driver) - * - removeSeedWhen(Bool) - - Allows to remove the transaction driver (but doesn't clear the down.valid) + - Allows to cancel the current transaction from the pipeline (clear down.valid and c the transaction driver) + * - forgetOneWhen(Bool) + - Allows to request the upstream to forget the current transaction (but doesn't clear the down.valid) + * - ignoreReadyWhen(Bool) + - Allows to ignore the downstream ready (set up.ready) * - duplicateWhen(Bool) - Allows to duplicate the current transaction (clear up.ready) * - terminateWhen(Bool) @@ -394,7 +398,7 @@ Here is its flow control API (The Bool arguments enable the features) : Also note that if you want to do flow control in a conditional scope (ex in a when statement), you can call the following functions : -- haltIt(), duplicateIt(), terminateIt(), removeSeedIt(), throwIt() +- haltIt(), duplicateIt(), terminateIt(), forgetOneNow(), ignoreReadyNow(), throwIt() .. code-block:: scala From d69182a4effc8d406a0cbd02905610fbf8187a1c Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Fri, 10 Nov 2023 14:02:58 +0100 Subject: [PATCH 10/15] use wavedrom --- .../Libraries/Pipeline/introduction.rst | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index 63ad9d861e1..1d7b67dff5f 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -77,7 +77,25 @@ This will produce the following hardware : Here is a simulation wave : -.. image:: /asset/image/pipeline/simple_wave.png +.. wavedrom:: + + {signal: [ + {name: 'clk', wave: '0...p........'}, + {name: 'reset', wave: '1..0.........'}, + {name: 'io_up_valid', wave: '0.....10.....'}, + {}, + {name: 'n0_valid', wave: '0.....10.....'}, + {name: 'n0_VALUE', wave: 'x.....2......', data: ['0042']}, + {}, + {name: 'n1_valid', wave: '0......10....'}, + {name: 'n1_VALUE', wave: 'x......2.....', data: ['0042']}, + {name: 'n1_RESULT', wave: 'x......2.....', data: ['1242']}, + {}, + {name: 'n2_valid', wave: '0.......10...'}, + {name: 'n2_RESULT', wave: 'x.......2....', data: ['1242']}, + {}, + {name: 'io_down_valid', wave: '0.......10...'}, + ]} Here is the same example but using more of the API : From 9b2c90d27d1282ad23e66d28e017c04b7b7c30ee Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Fri, 10 Nov 2023 14:15:57 +0100 Subject: [PATCH 11/15] Add link to stream doc --- source/SpinalHDL/Libraries/Pipeline/introduction.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index 1d7b67dff5f..8d3895240ca 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -177,9 +177,9 @@ You can access its arbitration via : - Is the signal which specifies if a transaction is present on the node. It is driven by the upstream. Once asserted, it must only be de-asserted the cycle after which either both valid and ready or node.cancel are high. valid must not depend on ready. * - node.ready - RW - - Is the signal which specify if the node's transaction should move away. It is driven by the downstream to create backpresure. The signal has no meaning when there is no transaction (node.valid being deasserted) + - Is the signal which specifies if the node's transaction can proceed downstream. It is driven by the downstream to create backpresure. The signal has no meaning when there is no transaction (node.valid being deasserted) * - node.cancel - - RW + - RO - Is the signal which specify if the node's transaction in being canceled from the pipeline. It is driven by the downstream. The signal has no meaning when there is no transaction (node.valid being deasserted) * - node.isValid - RO @@ -192,14 +192,14 @@ You can access its arbitration via : - True when the node transaction is successfuly moving futher (valid && ready && !cancel). Useful to commit state changes. * - node.isMoving - RO - - True when the node transaction is moving away from the node (will not be in the node anymore starting from the next cycle), + - True when the node transaction will not be present anymore on the node (starting from the next cycle), either because downstream is ready to take the transaction, or because the transaction is canceled from the pipeline. (valid && (ready || cancel)). Useful to "reset" states. * - node.isCanceling - RO - True when the node transaction is being canceled. Meaning that it will not appear anywhere in the pipeline in future cycles. -Note that the node.valid/node.ready signals follows the same conventions than the Stream's ones. +Note that the node.valid/node.ready signals follows the same conventions than the :doc:`../stream`'s ones . Here is a list of arbitration cases you can have on a node. valid/ready/cancel define the state we are in, while isFiring/isMoving result of those : From 548da05178bca727544d472ccb8d003b754ea76b Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Tue, 21 Nov 2023 11:03:37 +0100 Subject: [PATCH 12/15] pipeline SignalKey / Connector refractoring --- .../Libraries/Pipeline/introduction.rst | 163 +++++++++--------- 1 file changed, 82 insertions(+), 81 deletions(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index 8d3895240ca..607f270ae96 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -12,11 +12,11 @@ spinal.lib.misc.pipeline provides a pipelining API. The main advantages over man The API is composed of 4 main things : - Node : which represents a layer in the pipeline -- Connector : which allows to connect nodes to each other +- Link : which allows to connect nodes to each other - Builder : which will generate the hardware required for a whole pipeline -- SignalKey : which are used to retrieve hardware signals on nodes along the pipeline +- Payload : which are used to retrieve hardware signals on nodes along the pipeline -It is important to understand that SignalKey isn't a hardware data/signal instance, but a key to retrieve a data/signal on nodes along the pipeline, and that the pipeline builder will then automatically interconnect/pipeline every occurrence of a given SignalKey between nodes. +It is important to understand that Payload isn't a hardware data/signal instance, but a key to retrieve a data/signal on nodes along the pipeline, and that the pipeline builder will then automatically interconnect/pipeline every occurrence of a given Payload between nodes. Here is an example to illustrate : @@ -46,12 +46,12 @@ Here is a simple example which only uses the basics of the API : val n0, n1, n2 = Node() // Let's connect those nodes by using simples registers - val s01 = StageConnector(n0, n1) - val s12 = StageConnector(n1, n2) + val s01 = StageLink(n0, n1) + val s12 = StageLink(n1, n2) - // Let's define a few SignalKey things that can go through the pipeline - val VALUE = SignalKey(UInt(16 bits)) - val RESULT = SignalKey(UInt(16 bits)) + // Let's define a few Payload things that can go through the pipeline + val VALUE = Payload(UInt(16 bits)) + val RESULT = Payload(UInt(16 bits)) // Let's bind io.up to n0 io.up.ready := n0.ready @@ -108,7 +108,7 @@ Here is the same example but using more of the API : import spinal.lib.misc.pipeline._ class TopLevel extends Component { - val VALUE = SignalKey(UInt(16 bits)) + val VALUE = Payload(UInt(16 bits)) val io = new Bundle{ val up = slave Stream(VALUE) //VALUE can also be used as a HardType @@ -139,28 +139,28 @@ Here is the same example but using more of the API : builder.genStagedPipeline() } -SignalKey +Payload ============ -SignalKey objects are used to refer to data which can go through the pipeline. Technicaly speaking, SignalKey is a HardType which has a name and is used as a "key" to retrieve the signals in a certain pipeline stage. +Payload objects are used to refer to data which can go through the pipeline. Technicaly speaking, Payload is a HardType which has a name and is used as a "key" to retrieve the signals in a certain pipeline stage. .. code-block:: scala - val PC = SignalKey(UInt(32 bits)) - val PC_PLUS_4 = SignalKey(UInt(32 bits)) + val PC = Payload(UInt(32 bits)) + val PC_PLUS_4 = Payload(UInt(32 bits)) val n0, n1 = Node() - val s01 = StageConnector(n0, n1) + val s01 = StageLink(n0, n1) n0(PC) := 0x42 n1(PC_PLUS_4) := n1(PC) + 4 -Note that I got used to name the SignalKey instances using uppercase. This is to make it very explicit that the thing isn't a hardware signal, but are more like a "key/type" to access things. +Note that I got used to name the Payload instances using uppercase. This is to make it very explicit that the thing isn't a hardware signal, but are more like a "key/type" to access things. Node ============ -Node mostly hosts the valid/ready arbitration signals, and the hardware signals required for all the SignalKey values going through it. +Node mostly hosts the valid/ready arbitration signals, and the hardware signals required for all the Payload values going through it. You can access its arbitration via : @@ -216,9 +216,9 @@ Here is a list of arbitration cases you can have on a node. valid/ready/cancel d +-------+-------+-----------+------------------------------+----------+----------+ -Note that if you want to model things like for instance a CPU stage which can block and flush stuff, take a look a the CtrlConnector, as it provides the API to do such things. +Note that if you want to model things like for instance a CPU stage which can block and flush stuff, take a look a the CtrlLink, as it provides the API to do such things. -You can access signals referenced by a SignalKey via: +You can access signals referenced by a Payload via: .. list-table:: :header-rows: 1 @@ -226,12 +226,12 @@ You can access signals referenced by a SignalKey via: * - API - Description - * - node(SignalKey) + * - node(Payload) - Return the corresponding hardware signal - * - node(SignalKey, Any) + * - node(Payload, Any) - Same as above, but include a second argument which is used as a "secondary key". This eases the construction of multi-lane hardware. For instance, when you have a multi issue CPU pipeline, you can use the lane Int id as secondary key * - node.insert(Data) - - Return a new SignalKey instance which is connected to the given Data hardware signal + - Return a new Payload instance which is connected to the given Data hardware signal @@ -239,11 +239,11 @@ You can access signals referenced by a SignalKey via: val n0, n1 = Node() - val PC = SignalKey(UInt(32 bits)) + val PC = Payload(UInt(32 bits)) n0(PC) := 0x42 n0(PC, "true") := 0x42 n0(PC, 0x666) := 0xEE - val SOMETHING = n0.insert(myHardwareSignal) //This create a new SignalKey + val SOMETHING = n0.insert(myHardwareSignal) //This create a new Payload when(n1(SOMETHING) === 0xFFAA){ ... } @@ -263,7 +263,7 @@ Also, there is an API to define nodes which are always valid / ready .. code-block:: scala val n0, n1, n2 = Node() - val OUT = SignalKey(UInt(16 bits)) + val OUT = Payload(UInt(16 bits)) val outputFlow = master Flow(UInt(16 bits)) outputFlow.valid := n2.valid @@ -301,8 +301,8 @@ While you can manualy drive/read the arbitration/data of the first/last stage of val n0, n1, n2 = Node() - val IN = SignalKey(UInt(16 bits)) - val OUT = SignalKey(UInt(16 bits)) + val IN = Payload(UInt(16 bits)) + val OUT = Payload(UInt(16 bits)) n1(OUT) := n1(IN) + 0x42 @@ -314,11 +314,11 @@ While you can manualy drive/read the arbitration/data of the first/last stage of n2.driveTo(down)((payload, self) => payload := self(OUT)) -In order to reduce verbosity, there is a set of implicit conversions between SignalKey toward their data representation which can be used when you are in the context of a Node : +In order to reduce verbosity, there is a set of implicit conversions between Payload toward their data representation which can be used when you are in the context of a Node : .. code-block:: scala - val VALUE = SignalKey(UInt(16 bits)) + val VALUE = Payload(UInt(16 bits)) val n1 = new Node{ val PLUS_ONE = insert(VALUE + 1) // VALUE is implicitly converted into its n1(VALUE) representation } @@ -327,7 +327,7 @@ You can also use those implicit conversions by importing them : .. code-block:: scala - val VALUE = SignalKey(UInt(16 bits)) + val VALUE = Payload(UInt(16 bits)) val n1 = Node() val n1Stuff = new Area { @@ -341,7 +341,7 @@ There is also an API which alows you to create new Area which provide the whole .. code-block:: scala val n1 = Node() - val VALUE = SignalKey(UInt(16 bits)) + val VALUE = Payload(UInt(16 bits)) val n1Stuff = new n1.Area{ val PLUS_ONE = insert(VALUE) + 1 // Equivalent to n1.insert(n1(VALUE)) + 1 @@ -350,14 +350,14 @@ There is also an API which alows you to create new Area which provide the whole Such feature is very useful when you have parametrizable pipeline locations for your hardware (see retiming example). -Connectors +Links ============ -There is few different connectors already implemented (but you could also create your own custom one). -The idea of connectors is to connect two nodes together in various ways. +There is few different Links already implemented (but you could also create your own custom one). +The idea of Links is to connect two nodes together in various ways. They generally have a `up` Node and a `down` Node. -DirectConnector +DirectLink ------------------ Very simple, it connect two nodes with wires only. Here is an example : @@ -365,33 +365,33 @@ Very simple, it connect two nodes with wires only. Here is an example : .. code-block:: scala - val c01 = DirectConnector(n0, n1) + val c01 = DirectLink(n0, n1) -StageConnector +StageLink ------------------ This connect two nodes using registers on the data / valid signals and some arbitration on the ready. .. code-block:: scala - val c01 = StageConnector(n0, n1) + val c01 = StageLink(n0, n1) -S2mConnector +S2mLink ------------------ This connect two nodes using registers on the ready signal, which can be useful to improve backpresure combinatorial timings. .. code-block:: scala - val c01 = S2mConnector(n0, n1) + val c01 = S2mLink(n0, n1) -CtrlConnector +CtrlLink ------------------ -This is kind of a special connector, as connect two nodes with optional flow control / bypass logic. Its API should be flexible enough to implement a CPU stage with it. +This is kind of a special Link, as connect two nodes with optional flow control / bypass logic. Its API should be flexible enough to implement a CPU stage with it. Here is its flow control API (The Bool arguments enable the features) : @@ -420,16 +420,16 @@ Also note that if you want to do flow control in a conditional scope (ex in a wh .. code-block:: scala - val c01 = CtrlConnector(n0, n1) + val c01 = CtrlLink(n0, n1) c01.haltWhen(something) when(somethingElse){ c01.haltIt() } -You can retrieve which nodes are connected to the connector using node.up / node.down. +You can retrieve which nodes are connected to the Link using node.up / node.down. -The CtrlConnector also provide an API to access SignalKey : +The CtrlLink also provide an API to access Payload : .. list-table:: :header-rows: 1 @@ -437,25 +437,25 @@ The CtrlConnector also provide an API to access SignalKey : * - API - Description - * - connector(SignalKey) - - Same as connector.down(SignalKey) - * - connector(SignalKey, Any) - - Same as connector.down(SignalKey, Any) - * - connector.insert(Data) - - Same as connector.down.insert(Data) - * - connector.bypass(SignalKey) - - Allows to conditionaly override a SignalKey value between connector.up -> connector.down. This can be used to fix data hazard in CPU pipelines for instance. + * - link(Payload) + - Same as Link.down(Payload) + * - link(Payload, Any) + - Same as Link.down(Payload, Any) + * - link.insert(Data) + - Same as Link.down.insert(Data) + * - link.bypass(Payload) + - Allows to conditionaly override a Payload value between link.up -> link.down. This can be used to fix data hazard in CPU pipelines for instance. .. code-block:: scala - val c01 = CtrlConnector(n0, n1) + val c01 = CtrlLink(n0, n1) - val PC = SignalKey(UInt(32 bits)) + val PC = Payload(UInt(32 bits)) c01(PC) := 0x42 c01(PC, 0x666) := 0xEE - val DATA = SignalKey(UInt(32 bits)) + val DATA = Payload(UInt(32 bits)) // Let's say Data is inserted in the pipeline before c01 when(hazard){ c01.bypass(DATA) := fixedValue @@ -463,29 +463,29 @@ The CtrlConnector also provide an API to access SignalKey : // c01(DATA) and below will get the hazard patch -Note that if you create a CtrlConnector without node arguments, it will create its own nodes internally. +Note that if you create a CtrlLink without node arguments, it will create its own nodes internally. .. code-block:: scala - val decode = CtrlConnector() - val execute = CtrlConnector() + val decode = CtrlLink() + val execute = CtrlLink() - val d2e = StageConnector(decode.down, execute.up) + val d2e = StageLink(decode.down, execute.up) -Other connectors +Other Links ------------------------------------ -There is also a JoinConnector / ForkConnector implemented. +There is also a JoinLink / ForkLink implemented. -Your custom connector +Your custom Link ------------------------------------ -You can implement your custom connectors by implementing the Connector base class. +You can implement your custom links by implementing the Link base class. .. code-block:: scala - trait Connector extends Area{ + trait Link extends Area{ def ups : Seq[Node] def downs : Seq[Node] @@ -494,11 +494,12 @@ You can implement your custom connectors by implementing the Connector base clas def build() : Unit } +But that API may change a bit, as it is still fresh. Builder ============ -To generate the hardware of your pipeline, you need to give a list of all the connectors used in your pipeline. +To generate the hardware of your pipeline, you need to give a list of all the Links used in your pipeline. .. code-block:: scala @@ -507,8 +508,8 @@ To generate the hardware of your pipeline, you need to give a list of all the co val n0, n1, n2 = Node() // Let's connect those nodes by using simples registers - val s01 = StageConnector(n0, n1) - val s12 = StageConnector(n1, n2) + val s01 = StageLink(n0, n1) + val s12 = StageLink(n1, n2) // Let's ask the builder to generate all the required hardware Builder(s01, s12) @@ -541,7 +542,7 @@ The example below show a pattern which composes a pipeline with multiple lanes t // This area allows to take a input value and do +1 +1 +1 over 3 stages. // I know that's useless, but let's pretend that instead it does a multiplication between two numbers over 3 stages (for FMax reasons) - class Plus3(INPUT: SignalKey[UInt], stage1: Node, stage2: Node, stage3: Node) extends Area { + class Plus3(INPUT: Payload[UInt], stage1: Node, stage2: Node, stage3: Node) extends Area { val ONE = stage1.insert(stage1(INPUT) + 1) val TWO = stage2.insert(stage2(ONE) + 1) val THREE = stage3.insert(stage3(TWO) + 1) @@ -560,8 +561,8 @@ The example below show a pattern which composes a pipeline with multiple lanes t val n0, n1, n2 = Node() // Let's connect those nodes by using simples registers - val s01 = StageConnector(n0, n1) - val s12 = StageConnector(n1, n2) + val s01 = StageLink(n0, n1) + val s12 = StageLink(n1, n2) // Let's bind io.up to n0 n0.arbitrateFrom(io.up) @@ -647,10 +648,10 @@ Here is how it can be done with this pipelining API : } // Let's connect those nodes sequencialy by using simples registers - val connectors = for (i <- 0 to resultAt - 1) yield StageConnector(nodes(i), nodes(i + 1)) + val links = for (i <- 0 to resultAt - 1) yield StageLink(nodes(i), nodes(i + 1)) // Let's ask the builder to generate all the required hardware - Builder(connectors) + Builder(links) } If then you generate this component like this : @@ -712,9 +713,9 @@ Note the generated hardware verilog is kinda clean (by my standards at least :P) reg nodes_2_ready; reg nodes_3_valid; wire nodes_3_ready; - wire when_StageConnector_l56; - wire when_StageConnector_l56_1; - wire when_StageConnector_l56_2; + wire when_StageLink_l56; + wire when_StageLink_l56_1; + wire when_StageLink_l56_2; assign _zz_nodes_0_adder_SUM = (nodes_0_inserter_RGB_r + nodes_0_inserter_RGB_g); assign nodes_0_valid = io_up_valid; @@ -730,28 +731,28 @@ Note the generated hardware verilog is kinda clean (by my standards at least :P) assign io_down_payload = nodes_3_multiplier_MUL; always @(*) begin nodes_0_ready = nodes_1_ready; - if(when_StageConnector_l56) begin + if(when_StageLink_l56) begin nodes_0_ready = 1'b1; end end - assign when_StageConnector_l56 = (! nodes_1_valid); + assign when_StageLink_l56 = (! nodes_1_valid); always @(*) begin nodes_1_ready = nodes_2_ready; - if(when_StageConnector_l56_1) begin + if(when_StageLink_l56_1) begin nodes_1_ready = 1'b1; end end - assign when_StageConnector_l56_1 = (! nodes_2_valid); + assign when_StageLink_l56_1 = (! nodes_2_valid); always @(*) begin nodes_2_ready = nodes_3_ready; - if(when_StageConnector_l56_2) begin + if(when_StageLink_l56_2) begin nodes_2_ready = 1'b1; end end - assign when_StageConnector_l56_2 = (! nodes_3_valid); + assign when_StageLink_l56_2 = (! nodes_3_valid); always @(posedge clk or posedge reset) begin if(reset) begin nodes_1_valid <= 1'b0; From 68205397d351dd85ff60a8dd8dba274a6e33560d Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Wed, 22 Nov 2023 13:42:09 +0100 Subject: [PATCH 13/15] Improve pipeline doc --- source/SpinalHDL/Libraries/Pipeline/introduction.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index 607f270ae96..df38232b3db 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -404,9 +404,9 @@ Here is its flow control API (The Bool arguments enable the features) : * - haltWhen(Bool) - Allows to block the current transaction (clear up.ready down.valid) * - throwWhen(Bool) - - Allows to cancel the current transaction from the pipeline (clear down.valid and c the transaction driver) + - Allows to cancel the current transaction from the pipeline (clear down.valid and make the transaction driver forget its current state) * - forgetOneWhen(Bool) - - Allows to request the upstream to forget the current transaction (but doesn't clear the down.valid) + - Allows to request the upstream to forget its current transaction (but doesn't clear the down.valid) * - ignoreReadyWhen(Bool) - Allows to ignore the downstream ready (set up.ready) * - duplicateWhen(Bool) @@ -422,9 +422,10 @@ Also note that if you want to do flow control in a conditional scope (ex in a wh val c01 = CtrlLink(n0, n1) - c01.haltWhen(something) + c01.haltWhen(something) // Explicit halt request + when(somethingElse){ - c01.haltIt() + c01.haltIt() // Conditional scope sensitive halt request, same as c01.haltWhen(somethingElse) } You can retrieve which nodes are connected to the Link using node.up / node.down. From 7e5a3069f7f91a1d99961284452c3f0161c442c1 Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Wed, 3 Jan 2024 15:09:13 +0100 Subject: [PATCH 14/15] Update control signal sementic --- .../Libraries/Pipeline/introduction.rst | 33 +++++-------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index df38232b3db..aa4083ce587 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -179,14 +179,17 @@ You can access its arbitration via : - RW - Is the signal which specifies if the node's transaction can proceed downstream. It is driven by the downstream to create backpresure. The signal has no meaning when there is no transaction (node.valid being deasserted) * - node.cancel - - RO - - Is the signal which specify if the node's transaction in being canceled from the pipeline. It is driven by the downstream. The signal has no meaning when there is no transaction (node.valid being deasserted) + - RW + - Is the signal which specifies if the node's transaction in being canceled from the pipeline. It is driven by the downstream. The signal has no meaning when there is no transaction (node.valid being deasserted) * - node.isValid - RO - node.valid's read only accessor * - node.isReady - RO - node.ready's read only accessor + * - node.isCancel + - RO + - node.cancel's read only accessor * - node.isFiring - RO - True when the node transaction is successfuly moving futher (valid && ready && !cancel). Useful to commit state changes. @@ -201,6 +204,9 @@ You can access its arbitration via : Note that the node.valid/node.ready signals follows the same conventions than the :doc:`../stream`'s ones . +The Node controls (valid/ready/cancel) and status (isValid, isReady, isCancel, isFiring, ...) signals are created on demande. +So for instance you can create pipeline with no backpresure by never refering to the ready signal. That's why it is important to use status signals when you want to read the status of something and only use control signals when you to drive something. + Here is a list of arbitration cases you can have on a node. valid/ready/cancel define the state we are in, while isFiring/isMoving result of those : +-------+-------+-----------+------------------------------+----------+----------+ @@ -247,29 +253,6 @@ You can access signals referenced by a Payload via: when(n1(SOMETHING) === 0xFFAA){ ... } -Also, there is an API to define nodes which are always valid / ready - -.. list-table:: - :header-rows: 1 - :widths: 2 5 - - * - API - - Description - * - node.setAlwaysValid() - - Specify that the valid signal of the given node is always True. To use on the first node of a pipeline - * - node.setAlwaysReady() - - Specify that the ready signal of the given node is always True. To use on the last node of a pipeline, useful if you don't have to implement backpresure. - -.. code-block:: scala - - val n0, n1, n2 = Node() - val OUT = Payload(UInt(16 bits)) - - val outputFlow = master Flow(UInt(16 bits)) - outputFlow.valid := n2.valid - outputFlow.payload := n2(OUT) - n2.setAlwaysReady() // Equivalent to n2.ready := True, but also notify the pipeline elaboration about it, leading to eventual optimisations - While you can manualy drive/read the arbitration/data of the first/last stage of your pipeline, there is a few utilities to connect its boundaries. From 82977815450355c81cfbc2280bdf4b1fd50c912a Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Wed, 3 Jan 2024 18:22:57 +0100 Subject: [PATCH 15/15] Add link to video --- source/SpinalHDL/Libraries/Pipeline/introduction.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/SpinalHDL/Libraries/Pipeline/introduction.rst b/source/SpinalHDL/Libraries/Pipeline/introduction.rst index aa4083ce587..de7473eb06b 100644 --- a/source/SpinalHDL/Libraries/Pipeline/introduction.rst +++ b/source/SpinalHDL/Libraries/Pipeline/introduction.rst @@ -23,6 +23,11 @@ Here is an example to illustrate : .. image:: /asset/image/pipeline/intro_pip.png :scale: 70 % + +Here is a video about this API : + +- https://www.youtube.com/watch?v=74h_-FMWWIM + Simple example ----------------