From 93c4a026c721d71a9ebb44ce279bd4d5d3b92638 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:11:33 +0000 Subject: [PATCH 1/6] Initial plan From 32701c4bfe6e461b61bd98e7ea5e5eab6317afed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:24:00 +0000 Subject: [PATCH 2/6] Document StreamPipe, StreamAccessibleFifo, StreamShiftChain, PackedBundle, and missing BusSlaveFactory methods Co-authored-by: Readon <3614708+Readon@users.noreply.github.com> --- .../SpinalHDL/Libraries/bus_slave_factory.rst | 50 +++++++ source/SpinalHDL/Libraries/index.rst | 1 + source/SpinalHDL/Libraries/packed_bundle.rst | 128 ++++++++++++++++++ source/SpinalHDL/Libraries/stream.rst | 115 ++++++++++++++++ 4 files changed, 294 insertions(+) create mode 100644 source/SpinalHDL/Libraries/packed_bundle.rst diff --git a/source/SpinalHDL/Libraries/bus_slave_factory.rst b/source/SpinalHDL/Libraries/bus_slave_factory.rst index 50b68ec4287..6301caca6ed 100644 --- a/source/SpinalHDL/Libraries/bus_slave_factory.rst +++ b/source/SpinalHDL/Libraries/bus_slave_factory.rst @@ -90,4 +90,54 @@ Functionality - | Instantiate an internal register which at each cycle do : | reg := reg | that | Then when a read occur, the register is cleared. This register is readable at ``address`` and placed at ``bitOffset`` in the word + * - setOnSet(that,address,bitOffset) + - T + - | Set bits of ``that`` when the corresponding write bit is ``1``. + | Writing a ``1`` to a bit position sets that bit; writing ``0`` has no effect. + * - clearOnSet(that,address,bitOffset) + - T + - | Clear bits of ``that`` when the corresponding write bit is ``1``. + | Writing a ``1`` to a bit position clears that bit; writing ``0`` has no effect. + * - readAndSetOnSet(that,address,bitOffset) + - T + - Map ``that`` as readable at ``address`` and apply the ``setOnSet`` behaviour on write + * - readAndClearOnSet(that,address,bitOffset) + - T + - Map ``that`` as readable at ``address`` and apply the ``clearOnSet`` behaviour on write + * - createReadAndSetOnSet(dataType,address,bitOffset) + - T + - Create a register of ``dataType``, make it readable at ``address``, and apply the ``setOnSet`` behaviour on write + * - createReadAndClearOnSet(dataType,address,bitOffset) + - T + - Create a register of ``dataType``, make it readable at ``address``, and apply the ``clearOnSet`` behaviour on write + * - createReadMultiWord(that,address) + - T + - | Create a register initialised from ``that``, and map it as a multi-word read starting at ``address``. + | Extends across consecutive addresses when ``that`` is wider than one bus word. + * - createWriteMultiWord(that,address) + - T + - | Create a register initialised from ``that``, and map it as a multi-word write starting at ``address``. + | Extends across consecutive addresses when ``that`` is wider than one bus word. + * - createWriteAndReadMultiWord(that,address) + - T + - | Create a register initialised from ``that``, and map it as both readable and writable across multiple words starting at ``address``. + * - multiCycleRead(address,cycles) + - Unit + - | Insert a read latency of ``cycles`` bus clock cycles for accesses to ``address``. + | Useful when the read data requires more than one cycle to become available (e.g. synchronous RAM reads). + * - readSyncMemWordAligned(mem,addressOffset,bitOffset,memOffset) + - Mem[T] + - | Memory-map a synchronous-read ``Mem`` at ``addressOffset`` for word-aligned bus access. + | Each bus word corresponds to one memory word. Automatically inserts a 2-cycle read latency. + * - readSyncMemMultiWord(mem,addressOffset,memOffset) + - Mem[T] + - | Memory-map a synchronous-read ``Mem`` at ``addressOffset`` when each memory element spans multiple bus words. + | Automatically inserts a 2-cycle read latency. + * - writeMemWordAligned(mem,addressOffset,bitOffset,memOffset) + - Mem[T] + - Memory-map a ``Mem`` for word-aligned bus write access at ``addressOffset``. Supports byte-enable masks when available. + * - writeMemMultiWord(mem,addressOffset) + - Mem[T] + - | Memory-map a ``Mem`` at ``addressOffset`` for write access when each memory element spans multiple bus words. + | The memory element width must be a multiple of the bus data width. diff --git a/source/SpinalHDL/Libraries/index.rst b/source/SpinalHDL/Libraries/index.rst index 6b2cb41f3ca..8a14dfc6f38 100644 --- a/source/SpinalHDL/Libraries/index.rst +++ b/source/SpinalHDL/Libraries/index.rst @@ -32,6 +32,7 @@ To use features introduced in followings chapter you need, in most of cases, to fiber binarySystem regIf + packed_bundle Bus/index Com/index IO/index diff --git a/source/SpinalHDL/Libraries/packed_bundle.rst b/source/SpinalHDL/Libraries/packed_bundle.rst new file mode 100644 index 00000000000..5732fef95d6 --- /dev/null +++ b/source/SpinalHDL/Libraries/packed_bundle.rst @@ -0,0 +1,128 @@ +.. _packed_bundle: + +PackedBundle +============ + +Introduction +------------ + +``PackedBundle`` is an extension of ``Bundle`` that allows fields to be placed at exact bit positions within a word. +This is useful when mapping hardware registers, protocol frames, or any other structure with a defined bit layout. + +Unlike a plain ``Bundle``, which places fields consecutively and always starts from bit 0, ``PackedBundle`` lets you annotate each field with its target bit range using implicit helper methods. +Fields that are not annotated are placed immediately after the previous field, just like in a plain ``Bundle``. + +Placement methods +----------------- + +Inside a ``PackedBundle`` class body, each ``Data`` field can be annotated with one of the following helpers: + +.. list-table:: + :header-rows: 1 + :widths: 3 5 + + * - Method + - Description + * - ``.pack(range)`` + - Place the field at the given bit range. If the field is wider than the range, extra bits are lost. + * - ``.pack(range, endianness)`` + - Place the field at the given range, aligning to ``LITTLE`` (ascending) or ``BIG`` (descending) endianness. + * - ``.packFrom(pos)`` + - Place the field with its LSB at bit position ``pos``. Uses the full width of the field. + * - ``.packTo(pos)`` + - Place the field with its MSB at bit position ``pos``. Uses the full width of the field. + +Fields with no placement annotation are placed sequentially starting at the next free bit position after the last annotated or sequential field. + +Example +------- + +.. code-block:: scala + + import spinal.lib._ + + val regWord = new PackedBundle { + val init = Bool().packFrom(0) // Bit 0 + val stop = Bool() // Bit 1 (next available) + val result = Bits(16 bits).packTo(31) // Bits 31 downto 16 + } + + // Obtain the packed Bits representation + val packed: Bits = regWord.packed + + // Unpack from a Bits value + regWord.unpack(someReadData) + +Operations +---------- + +.. list-table:: + :header-rows: 1 + :widths: 2 2 5 + + * - Method + - Return + - Description + * - ``packed`` + - Bits + - Returns a ``Bits`` signal that contains all fields assembled at their assigned positions + * - ``unpack(bits)`` + - Unit + - Assigns each field of the bundle from the corresponding bits of the provided ``Bits`` signal + * - ``getPackedWidth`` + - Int + - Returns the total bit-width needed to hold all packed fields (index of the highest bit + 1) + * - ``mappings`` + - Seq[(Range, Data)] + - Returns the ordered list of (bit range, data field) pairs for all fields + +PackedWordBundle +---------------- + +``PackedWordBundle`` is an extension of ``PackedBundle`` that organises packing around fixed-size words. +Each field can be assigned to a specific word index using the ``.inWord(index)`` helper. +Fields that span more than one word automatically wrap into subsequent words. + +The word width is supplied as a ``BitCount`` at construction time. + +.. code-block:: scala + + import spinal.lib._ + + val wordPacked = new PackedWordBundle(8 bits) { + val aNumber = UInt(8 bits).inWord(0) // Bits 7 downto 0 + val bNumber = UInt(8 bits).pack(0 to 7).inWord(1) // Bits 15 downto 8 + val large = Bits(18 bits).inWord(2) // Bits 33 downto 16 + val flag = Bool() // Bit 34 + } + +When ``.inWord(index)`` is combined with a prior ``.pack(range)`` annotation, the bit range is interpreted relative to the start of the specified word. + +Using PackedBundle with streams +-------------------------------- + +``PackedBundle`` integrates with the ``StreamUnpacker`` and ``StreamPacker`` utilities, which allow packing and unpacking structured data across a stream of fixed-width words: + +.. code-block:: scala + + import spinal.lib._ + + case class MyFrame() extends PackedBundle { + val addr = UInt(10 bits).packFrom(0) // Bits 9 downto 0 + val data = Bits(16 bits).packFrom(10) // Bits 25 downto 10 + val last = Bool().packFrom(26) // Bit 26 + } + + val inputStream = Stream(Bits(8 bits)) + val frame = MyFrame() + + // Unpack a multi-word stream into the frame fields + val unpacker = StreamUnpacker(inputStream, frame) + when(unpacker.io.allDone) { + // All fields have been filled + } + + // Pack the frame fields back into a stream + val outputStream = Stream(Bits(8 bits)) + val packer = StreamPacker(outputStream, frame) + packer.io.start := startCondition diff --git a/source/SpinalHDL/Libraries/stream.rst b/source/SpinalHDL/Libraries/stream.rst index f856ffae6c7..67b622b0c80 100644 --- a/source/SpinalHDL/Libraries/stream.rst +++ b/source/SpinalHDL/Libraries/stream.rst @@ -575,6 +575,121 @@ This util take its input stream and routes it to ``outputCount`` stream in a seq outputCount = 3 ) +StreamPipe +^^^^^^^^^^ + +``StreamPipe`` provides predefined stream pipeline stage styles that can be used to cut long combinatorial paths. +You can apply them by calling the corresponding method on a ``Stream`` or by passing a ``StreamPipe`` value to functions that accept one. + +.. list-table:: + :header-rows: 1 + :widths: 2 5 + + * - Value + - Description + * - ``StreamPipe.NONE`` + - No registering; connects the stream with a combinatorial stage (``combStage``) + * - ``StreamPipe.M2S`` + - Cuts the ``valid`` and ``payload`` paths with a register stage (``m2sPipe``) + * - ``StreamPipe.S2M`` + - Cuts the ``ready`` path with a register stage (``s2mPipe``) + * - ``StreamPipe.FULL`` + - Cuts ``valid``, ``ready``, and ``payload`` paths with register stages (``s2mPipe`` + ``m2sPipe``) + * - ``StreamPipe.HALF`` + - Cuts all paths using a single register stage that halves bandwidth (``halfPipe``) + +.. code-block:: scala + + val source = Stream(Bits(8 bits)) + val sink = Stream(Bits(8 bits)) + + // Apply a full pipeline stage + sink << source.pipelined(StreamPipe.FULL) + + // Or pass it as a parameter to utilities that accept StreamPipe + val pipeType: StreamPipe = StreamPipe.M2S + sink << pipeType(source) + +StreamAccessibleFifo +^^^^^^^^^^^^^^^^^^^^ + +``StreamAccessibleFifo`` is a FIFO where all stored elements are accessible at any time through the ``io.states`` ports in addition to the normal ``push``/``pop`` stream interface. +This is useful when logic needs to inspect the entire contents of the queue without consuming elements. + +.. list-table:: + :header-rows: 1 + :widths: 2 2 5 + + * - Signal + - Type + - Description + * - ``io.push`` + - slave Stream[T] + - Input stream; pushes data into the FIFO + * - ``io.pop`` + - master Stream[T] + - Output stream; pops data from the FIFO + * - ``io.states`` + - Vec(master Flow[T], length) + - Read-only view of each slot in the FIFO + +.. code-block:: scala + + val input = Stream(Bits(8 bits)) + val output = Stream(Bits(8 bits)) + + // Instantiate a StreamAccessibleFifo with 4 slots + val fifo = StreamAccessibleFifo(input, output, length = 4) + + // Access the first slot's data without consuming it + when(fifo.io.states(0).valid) { + val firstElement = fifo.io.states(0).payload + } + +StreamShiftChain +^^^^^^^^^^^^^^^^ + +``StreamShiftChain`` is a shift-register chain of ``length`` registered stream stages. +Each stage is a ``m2sPipe`` of the previous one. +The ``io.states`` ports expose all intermediate stages as ``Flow``\s so that any stage can be inspected. +An optional ``io.clear`` input can flush all stages simultaneously. + +.. list-table:: + :header-rows: 1 + :widths: 2 2 5 + + * - Signal + - Type + - Description + * - ``io.push`` + - slave Stream[T] + - Input stream; enters the first stage of the chain + * - ``io.pop`` + - master Stream[T] + - Output stream; exits the last stage of the chain + * - ``io.states`` + - Vec(master Flow[T], length) + - Read-only view of each registered stage in the chain + * - ``io.clear`` + - in Bool (default False) + - When asserted, flushes all pipeline stages + +.. code-block:: scala + + val input = Stream(Bits(8 bits)) + val output = Stream(Bits(8 bits)) + + // Create a 3-stage shift chain + val chain = StreamShiftChain(input, output, length = 3) + + // Read the second stage value without consuming it + when(chain.io.states(1).valid) { + val stage1Data = chain.io.states(1).payload + } + + // Flush all stages + chain.io.clear := flushCondition + StreamTransactionExtender ^^^^^^^^^^^^^^^^^^^^^^^^^ From 70ddfb26d4e87d0e9fb4d7db025399ac8cb7e491 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:10:31 +0000 Subject: [PATCH 3/6] Scope docs to only facilities used outside SpinalHDL/SpinalHDL: remove StreamAccessibleFifo, StreamShiftChain, PackedBundle page, and undocumented BusSlaveFactory methods Co-authored-by: Readon <3614708+Readon@users.noreply.github.com> --- .../SpinalHDL/Libraries/bus_slave_factory.rst | 47 +------ source/SpinalHDL/Libraries/index.rst | 1 - source/SpinalHDL/Libraries/packed_bundle.rst | 128 ------------------ source/SpinalHDL/Libraries/stream.rst | 80 ----------- 4 files changed, 1 insertion(+), 255 deletions(-) delete mode 100644 source/SpinalHDL/Libraries/packed_bundle.rst diff --git a/source/SpinalHDL/Libraries/bus_slave_factory.rst b/source/SpinalHDL/Libraries/bus_slave_factory.rst index 6301caca6ed..eab3df53efa 100644 --- a/source/SpinalHDL/Libraries/bus_slave_factory.rst +++ b/source/SpinalHDL/Libraries/bus_slave_factory.rst @@ -94,50 +94,5 @@ Functionality - T - | Set bits of ``that`` when the corresponding write bit is ``1``. | Writing a ``1`` to a bit position sets that bit; writing ``0`` has no effect. - * - clearOnSet(that,address,bitOffset) - - T - - | Clear bits of ``that`` when the corresponding write bit is ``1``. - | Writing a ``1`` to a bit position clears that bit; writing ``0`` has no effect. - * - readAndSetOnSet(that,address,bitOffset) - - T - - Map ``that`` as readable at ``address`` and apply the ``setOnSet`` behaviour on write - * - readAndClearOnSet(that,address,bitOffset) - - T - - Map ``that`` as readable at ``address`` and apply the ``clearOnSet`` behaviour on write - * - createReadAndSetOnSet(dataType,address,bitOffset) - - T - - Create a register of ``dataType``, make it readable at ``address``, and apply the ``setOnSet`` behaviour on write - * - createReadAndClearOnSet(dataType,address,bitOffset) - - T - - Create a register of ``dataType``, make it readable at ``address``, and apply the ``clearOnSet`` behaviour on write - * - createReadMultiWord(that,address) - - T - - | Create a register initialised from ``that``, and map it as a multi-word read starting at ``address``. - | Extends across consecutive addresses when ``that`` is wider than one bus word. - * - createWriteMultiWord(that,address) - - T - - | Create a register initialised from ``that``, and map it as a multi-word write starting at ``address``. - | Extends across consecutive addresses when ``that`` is wider than one bus word. - * - createWriteAndReadMultiWord(that,address) - - T - - | Create a register initialised from ``that``, and map it as both readable and writable across multiple words starting at ``address``. - * - multiCycleRead(address,cycles) - - Unit - - | Insert a read latency of ``cycles`` bus clock cycles for accesses to ``address``. - | Useful when the read data requires more than one cycle to become available (e.g. synchronous RAM reads). - * - readSyncMemWordAligned(mem,addressOffset,bitOffset,memOffset) - - Mem[T] - - | Memory-map a synchronous-read ``Mem`` at ``addressOffset`` for word-aligned bus access. - | Each bus word corresponds to one memory word. Automatically inserts a 2-cycle read latency. - * - readSyncMemMultiWord(mem,addressOffset,memOffset) - - Mem[T] - - | Memory-map a synchronous-read ``Mem`` at ``addressOffset`` when each memory element spans multiple bus words. - | Automatically inserts a 2-cycle read latency. - * - writeMemWordAligned(mem,addressOffset,bitOffset,memOffset) - - Mem[T] - - Memory-map a ``Mem`` for word-aligned bus write access at ``addressOffset``. Supports byte-enable masks when available. - * - writeMemMultiWord(mem,addressOffset) - - Mem[T] - - | Memory-map a ``Mem`` at ``addressOffset`` for write access when each memory element spans multiple bus words. - | The memory element width must be a multiple of the bus data width. + diff --git a/source/SpinalHDL/Libraries/index.rst b/source/SpinalHDL/Libraries/index.rst index 8a14dfc6f38..6b2cb41f3ca 100644 --- a/source/SpinalHDL/Libraries/index.rst +++ b/source/SpinalHDL/Libraries/index.rst @@ -32,7 +32,6 @@ To use features introduced in followings chapter you need, in most of cases, to fiber binarySystem regIf - packed_bundle Bus/index Com/index IO/index diff --git a/source/SpinalHDL/Libraries/packed_bundle.rst b/source/SpinalHDL/Libraries/packed_bundle.rst deleted file mode 100644 index 5732fef95d6..00000000000 --- a/source/SpinalHDL/Libraries/packed_bundle.rst +++ /dev/null @@ -1,128 +0,0 @@ -.. _packed_bundle: - -PackedBundle -============ - -Introduction ------------- - -``PackedBundle`` is an extension of ``Bundle`` that allows fields to be placed at exact bit positions within a word. -This is useful when mapping hardware registers, protocol frames, or any other structure with a defined bit layout. - -Unlike a plain ``Bundle``, which places fields consecutively and always starts from bit 0, ``PackedBundle`` lets you annotate each field with its target bit range using implicit helper methods. -Fields that are not annotated are placed immediately after the previous field, just like in a plain ``Bundle``. - -Placement methods ------------------ - -Inside a ``PackedBundle`` class body, each ``Data`` field can be annotated with one of the following helpers: - -.. list-table:: - :header-rows: 1 - :widths: 3 5 - - * - Method - - Description - * - ``.pack(range)`` - - Place the field at the given bit range. If the field is wider than the range, extra bits are lost. - * - ``.pack(range, endianness)`` - - Place the field at the given range, aligning to ``LITTLE`` (ascending) or ``BIG`` (descending) endianness. - * - ``.packFrom(pos)`` - - Place the field with its LSB at bit position ``pos``. Uses the full width of the field. - * - ``.packTo(pos)`` - - Place the field with its MSB at bit position ``pos``. Uses the full width of the field. - -Fields with no placement annotation are placed sequentially starting at the next free bit position after the last annotated or sequential field. - -Example -------- - -.. code-block:: scala - - import spinal.lib._ - - val regWord = new PackedBundle { - val init = Bool().packFrom(0) // Bit 0 - val stop = Bool() // Bit 1 (next available) - val result = Bits(16 bits).packTo(31) // Bits 31 downto 16 - } - - // Obtain the packed Bits representation - val packed: Bits = regWord.packed - - // Unpack from a Bits value - regWord.unpack(someReadData) - -Operations ----------- - -.. list-table:: - :header-rows: 1 - :widths: 2 2 5 - - * - Method - - Return - - Description - * - ``packed`` - - Bits - - Returns a ``Bits`` signal that contains all fields assembled at their assigned positions - * - ``unpack(bits)`` - - Unit - - Assigns each field of the bundle from the corresponding bits of the provided ``Bits`` signal - * - ``getPackedWidth`` - - Int - - Returns the total bit-width needed to hold all packed fields (index of the highest bit + 1) - * - ``mappings`` - - Seq[(Range, Data)] - - Returns the ordered list of (bit range, data field) pairs for all fields - -PackedWordBundle ----------------- - -``PackedWordBundle`` is an extension of ``PackedBundle`` that organises packing around fixed-size words. -Each field can be assigned to a specific word index using the ``.inWord(index)`` helper. -Fields that span more than one word automatically wrap into subsequent words. - -The word width is supplied as a ``BitCount`` at construction time. - -.. code-block:: scala - - import spinal.lib._ - - val wordPacked = new PackedWordBundle(8 bits) { - val aNumber = UInt(8 bits).inWord(0) // Bits 7 downto 0 - val bNumber = UInt(8 bits).pack(0 to 7).inWord(1) // Bits 15 downto 8 - val large = Bits(18 bits).inWord(2) // Bits 33 downto 16 - val flag = Bool() // Bit 34 - } - -When ``.inWord(index)`` is combined with a prior ``.pack(range)`` annotation, the bit range is interpreted relative to the start of the specified word. - -Using PackedBundle with streams --------------------------------- - -``PackedBundle`` integrates with the ``StreamUnpacker`` and ``StreamPacker`` utilities, which allow packing and unpacking structured data across a stream of fixed-width words: - -.. code-block:: scala - - import spinal.lib._ - - case class MyFrame() extends PackedBundle { - val addr = UInt(10 bits).packFrom(0) // Bits 9 downto 0 - val data = Bits(16 bits).packFrom(10) // Bits 25 downto 10 - val last = Bool().packFrom(26) // Bit 26 - } - - val inputStream = Stream(Bits(8 bits)) - val frame = MyFrame() - - // Unpack a multi-word stream into the frame fields - val unpacker = StreamUnpacker(inputStream, frame) - when(unpacker.io.allDone) { - // All fields have been filled - } - - // Pack the frame fields back into a stream - val outputStream = Stream(Bits(8 bits)) - val packer = StreamPacker(outputStream, frame) - packer.io.start := startCondition diff --git a/source/SpinalHDL/Libraries/stream.rst b/source/SpinalHDL/Libraries/stream.rst index 67b622b0c80..66645c8b328 100644 --- a/source/SpinalHDL/Libraries/stream.rst +++ b/source/SpinalHDL/Libraries/stream.rst @@ -610,86 +610,6 @@ You can apply them by calling the corresponding method on a ``Stream`` or by pas val pipeType: StreamPipe = StreamPipe.M2S sink << pipeType(source) -StreamAccessibleFifo -^^^^^^^^^^^^^^^^^^^^ - -``StreamAccessibleFifo`` is a FIFO where all stored elements are accessible at any time through the ``io.states`` ports in addition to the normal ``push``/``pop`` stream interface. -This is useful when logic needs to inspect the entire contents of the queue without consuming elements. - -.. list-table:: - :header-rows: 1 - :widths: 2 2 5 - - * - Signal - - Type - - Description - * - ``io.push`` - - slave Stream[T] - - Input stream; pushes data into the FIFO - * - ``io.pop`` - - master Stream[T] - - Output stream; pops data from the FIFO - * - ``io.states`` - - Vec(master Flow[T], length) - - Read-only view of each slot in the FIFO - -.. code-block:: scala - - val input = Stream(Bits(8 bits)) - val output = Stream(Bits(8 bits)) - - // Instantiate a StreamAccessibleFifo with 4 slots - val fifo = StreamAccessibleFifo(input, output, length = 4) - - // Access the first slot's data without consuming it - when(fifo.io.states(0).valid) { - val firstElement = fifo.io.states(0).payload - } - -StreamShiftChain -^^^^^^^^^^^^^^^^ - -``StreamShiftChain`` is a shift-register chain of ``length`` registered stream stages. -Each stage is a ``m2sPipe`` of the previous one. -The ``io.states`` ports expose all intermediate stages as ``Flow``\s so that any stage can be inspected. -An optional ``io.clear`` input can flush all stages simultaneously. - -.. list-table:: - :header-rows: 1 - :widths: 2 2 5 - - * - Signal - - Type - - Description - * - ``io.push`` - - slave Stream[T] - - Input stream; enters the first stage of the chain - * - ``io.pop`` - - master Stream[T] - - Output stream; exits the last stage of the chain - * - ``io.states`` - - Vec(master Flow[T], length) - - Read-only view of each registered stage in the chain - * - ``io.clear`` - - in Bool (default False) - - When asserted, flushes all pipeline stages - -.. code-block:: scala - - val input = Stream(Bits(8 bits)) - val output = Stream(Bits(8 bits)) - - // Create a 3-stage shift chain - val chain = StreamShiftChain(input, output, length = 3) - - // Read the second stage value without consuming it - when(chain.io.states(1).valid) { - val stage1Data = chain.io.states(1).payload - } - - // Flush all stages - chain.io.clear := flushCondition - StreamTransactionExtender ^^^^^^^^^^^^^^^^^^^^^^^^^ From ceceab1a8c46bc1ed480341c82979b692c5eff7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:16:04 +0000 Subject: [PATCH 4/6] Fix StreamPipe description: it's a trait with named singleton instances, not an enum or class Co-authored-by: Readon <3614708+Readon@users.noreply.github.com> --- source/SpinalHDL/Libraries/stream.rst | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/source/SpinalHDL/Libraries/stream.rst b/source/SpinalHDL/Libraries/stream.rst index 66645c8b328..06aac159893 100644 --- a/source/SpinalHDL/Libraries/stream.rst +++ b/source/SpinalHDL/Libraries/stream.rst @@ -578,37 +578,38 @@ This util take its input stream and routes it to ``outputCount`` stream in a seq StreamPipe ^^^^^^^^^^ -``StreamPipe`` provides predefined stream pipeline stage styles that can be used to cut long combinatorial paths. -You can apply them by calling the corresponding method on a ``Stream`` or by passing a ``StreamPipe`` value to functions that accept one. +``StreamPipe`` is a trait that defines a single ``apply(stream)`` method returning a pipelined version of the stream. +``object StreamPipe`` provides named singleton instances that act as pipeline-style selectors. +The primary usage is to pass a ``StreamPipe`` value to ``stream.pipelined()`` or to any utility function that accepts one. .. list-table:: :header-rows: 1 :widths: 2 5 - * - Value + * - Instance - Description * - ``StreamPipe.NONE`` - - No registering; connects the stream with a combinatorial stage (``combStage``) + - No registering; equivalent to ``stream.combStage()`` * - ``StreamPipe.M2S`` - - Cuts the ``valid`` and ``payload`` paths with a register stage (``m2sPipe``) + - Cuts the ``valid`` and ``payload`` paths through a register (``m2sPipe``) * - ``StreamPipe.S2M`` - - Cuts the ``ready`` path with a register stage (``s2mPipe``) + - Cuts the ``ready`` path through a register (``s2mPipe``) * - ``StreamPipe.FULL`` - - Cuts ``valid``, ``ready``, and ``payload`` paths with register stages (``s2mPipe`` + ``m2sPipe``) + - Cuts ``valid``, ``ready``, and ``payload`` paths through registers (``s2mPipe`` + ``m2sPipe``) * - ``StreamPipe.HALF`` - - Cuts all paths using a single register stage that halves bandwidth (``halfPipe``) + - Cuts all paths using one register stage that halves bandwidth (``halfPipe``) .. code-block:: scala val source = Stream(Bits(8 bits)) val sink = Stream(Bits(8 bits)) - // Apply a full pipeline stage + // Pass as a pipeline-style selector sink << source.pipelined(StreamPipe.FULL) - // Or pass it as a parameter to utilities that accept StreamPipe - val pipeType: StreamPipe = StreamPipe.M2S - sink << pipeType(source) + // Or call the instance directly — each StreamPipe instance is callable + val pipe: StreamPipe = StreamPipe.M2S + sink << pipe(source) StreamTransactionExtender ^^^^^^^^^^^^^^^^^^^^^^^^^ From 626fd5260bf8e9c3e82dedb93787d4389cef8c47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:19:31 +0000 Subject: [PATCH 5/6] Reframe StreamPipe section as pipelined() method doc, showing both call styles Co-authored-by: Readon <3614708+Readon@users.noreply.github.com> --- source/SpinalHDL/Libraries/stream.rst | 51 +++++++++++++++++++++------ 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/source/SpinalHDL/Libraries/stream.rst b/source/SpinalHDL/Libraries/stream.rst index 06aac159893..056cda0c244 100644 --- a/source/SpinalHDL/Libraries/stream.rst +++ b/source/SpinalHDL/Libraries/stream.rst @@ -575,18 +575,19 @@ This util take its input stream and routes it to ``outputCount`` stream in a seq outputCount = 3 ) -StreamPipe -^^^^^^^^^^ +pipelined +^^^^^^^^^ -``StreamPipe`` is a trait that defines a single ``apply(stream)`` method returning a pipelined version of the stream. -``object StreamPipe`` provides named singleton instances that act as pipeline-style selectors. -The primary usage is to pass a ``StreamPipe`` value to ``stream.pipelined()`` or to any utility function that accepts one. +``stream.pipelined(...)`` returns a registered version of ``stream``, cutting combinatorial paths to help timing closure. +It has two call styles: + +**Using a** ``StreamPipe`` **constant** (``StreamPipe`` is a trait; ``object StreamPipe`` provides the named instances): .. list-table:: :header-rows: 1 :widths: 2 5 - * - Instance + * - ``StreamPipe`` value - Description * - ``StreamPipe.NONE`` - No registering; equivalent to ``stream.combStage()`` @@ -599,17 +600,47 @@ The primary usage is to pass a ``StreamPipe`` value to ``stream.pipelined()`` or * - ``StreamPipe.HALF`` - Cuts all paths using one register stage that halves bandwidth (``halfPipe``) +**Using boolean flags** ``pipelined(m2s, s2m, halfRate)``\: + +.. list-table:: + :header-rows: 1 + :widths: 1 1 1 4 + + * - ``m2s`` + - ``s2m`` + - ``halfRate`` + - Equivalent ``StreamPipe`` + * - ``false`` + - ``false`` + - ``false`` + - ``StreamPipe.NONE`` + * - ``true`` + - ``false`` + - ``false`` + - ``StreamPipe.M2S`` + * - ``false`` + - ``true`` + - ``false`` + - ``StreamPipe.S2M`` + * - ``true`` + - ``true`` + - ``false`` + - ``StreamPipe.FULL`` + * - ``false`` + - ``false`` + - ``true`` + - ``StreamPipe.HALF`` + .. code-block:: scala val source = Stream(Bits(8 bits)) val sink = Stream(Bits(8 bits)) - // Pass as a pipeline-style selector + // Using a StreamPipe constant sink << source.pipelined(StreamPipe.FULL) - // Or call the instance directly — each StreamPipe instance is callable - val pipe: StreamPipe = StreamPipe.M2S - sink << pipe(source) + // Using boolean flags (equivalent to StreamPipe.FULL) + sink << source.pipelined(m2s = true, s2m = true) StreamTransactionExtender ^^^^^^^^^^^^^^^^^^^^^^^^^ From 96e48e797ee8815eafc393fd527d1c206934946d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:23:51 +0000 Subject: [PATCH 6/6] Move pipelined into stream.rst Functions table, remove standalone section Co-authored-by: Readon <3614708+Readon@users.noreply.github.com> --- source/SpinalHDL/Libraries/stream.rst | 75 ++++----------------------- 1 file changed, 9 insertions(+), 66 deletions(-) diff --git a/source/SpinalHDL/Libraries/stream.rst b/source/SpinalHDL/Libraries/stream.rst index 056cda0c244..f58289acafd 100644 --- a/source/SpinalHDL/Libraries/stream.rst +++ b/source/SpinalHDL/Libraries/stream.rst @@ -181,6 +181,15 @@ Functions | Modify the payload of the `x` stream, while preserving the `valid` and `ready` signals - Stream[T2] - 0 + * - | x.pipelined(pipe: StreamPipe) + | x.pipelined(m2s, s2m, halfRate) + - | Return a registered version of x cutting combinatorial paths. + | ``StreamPipe`` constants: ``NONE`` (no reg), ``M2S`` (cut valid/payload), + | ``S2M`` (cut ready), ``FULL`` (cut all), ``HALF`` (cut all, half bandwidth). + | Boolean flags: ``m2s`` cuts valid/payload; ``s2m`` cuts ready; + | ``halfRate`` cuts all paths at half bandwidth (exclusive with the others). + - Stream[T] + - varies The following code will create this logic : @@ -575,72 +584,6 @@ This util take its input stream and routes it to ``outputCount`` stream in a seq outputCount = 3 ) -pipelined -^^^^^^^^^ - -``stream.pipelined(...)`` returns a registered version of ``stream``, cutting combinatorial paths to help timing closure. -It has two call styles: - -**Using a** ``StreamPipe`` **constant** (``StreamPipe`` is a trait; ``object StreamPipe`` provides the named instances): - -.. list-table:: - :header-rows: 1 - :widths: 2 5 - - * - ``StreamPipe`` value - - Description - * - ``StreamPipe.NONE`` - - No registering; equivalent to ``stream.combStage()`` - * - ``StreamPipe.M2S`` - - Cuts the ``valid`` and ``payload`` paths through a register (``m2sPipe``) - * - ``StreamPipe.S2M`` - - Cuts the ``ready`` path through a register (``s2mPipe``) - * - ``StreamPipe.FULL`` - - Cuts ``valid``, ``ready``, and ``payload`` paths through registers (``s2mPipe`` + ``m2sPipe``) - * - ``StreamPipe.HALF`` - - Cuts all paths using one register stage that halves bandwidth (``halfPipe``) - -**Using boolean flags** ``pipelined(m2s, s2m, halfRate)``\: - -.. list-table:: - :header-rows: 1 - :widths: 1 1 1 4 - - * - ``m2s`` - - ``s2m`` - - ``halfRate`` - - Equivalent ``StreamPipe`` - * - ``false`` - - ``false`` - - ``false`` - - ``StreamPipe.NONE`` - * - ``true`` - - ``false`` - - ``false`` - - ``StreamPipe.M2S`` - * - ``false`` - - ``true`` - - ``false`` - - ``StreamPipe.S2M`` - * - ``true`` - - ``true`` - - ``false`` - - ``StreamPipe.FULL`` - * - ``false`` - - ``false`` - - ``true`` - - ``StreamPipe.HALF`` - -.. code-block:: scala - - val source = Stream(Bits(8 bits)) - val sink = Stream(Bits(8 bits)) - - // Using a StreamPipe constant - sink << source.pipelined(StreamPipe.FULL) - - // Using boolean flags (equivalent to StreamPipe.FULL) - sink << source.pipelined(m2s = true, s2m = true) StreamTransactionExtender ^^^^^^^^^^^^^^^^^^^^^^^^^