Skip to content

Commit

Permalink
[SLINK] add AXI-stream-compatible "tlast" signals (#815)
Browse files Browse the repository at this point in the history
  • Loading branch information
stnolting committed Feb 20, 2024
2 parents bf53181 + 8746f4b commit 20cc8f0
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 57 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ mimpid = 0x01040312 -> Version 01.04.03.12 -> v1.4.3.12

| Date | Version | Comment | Link |
|:----:|:-------:|:--------|:----:|
| 19.02.2024 | 1.9.5.5 | SLINK: add native hardware support for AXI-stream's "tlast" signal | [#815](https://github.com/stnolting/neorv32/pull/815) |
| 19.02.2024 | 1.9.5.4 | :warning: remove support of `Smcntrpmf` ISA extension (counter privilege mode filtering) | [#814](https://github.com/stnolting/neorv32/pull/814) |
| 17.02.2024 | 1.9.5.3 | :warning: reworked CPU's hardware performance monitor (HPMs) events | [#811](https://github.com/stnolting/neorv32/pull/811) |
| 16.02.2024 | 1.9.5.2 | :warning: **revert** support for page faults (keep that in mmu branch for now) | [#809](https://github.com/stnolting/neorv32/pull/809) |
Expand Down
2 changes: 2 additions & 0 deletions docs/datasheet/soc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,11 @@ Some interfaces (like the TWI and the 1-Wire bus) require tri-state drivers in t
5+^| **<<_stream_link_interface_slink>>**
| `slink_rx_dat_i` | 32 | in | `'L'` | RX data
| `slink_rx_val_i` | 1 | in | `'L'` | RX data valid
| `slink_rx_lst_i` | 1 | in | `'L'` | RX last element of stream
| `slink_rx_rdy_o` | 1 | out | - | RX ready to receive
| `slink_tx_dat_o` | 32 | out | - | TX data
| `slink_tx_val_o` | 1 | out | - | TX data valid
| `slink_tx_lst_o` | 1 | out | - | TX last element of stream
| `slink_tx_rdy_i` | 1 | in | `'L'` | TX allowed to send
5+^| **<<_execute_in_place_module_xip>>**
| `xip_csn_o` | 1 | out | - | chip select, low-active
Expand Down
52 changes: 34 additions & 18 deletions docs/datasheet/soc_slink.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
| Hardware source file(s): | neorv32_slink.vhd |
| Software driver file(s): | neorv32_slink.c |
| | neorv32_slink.h |
| Top entity port: | `slink_rx_dat_i` | RX data (32-bit)
| | `slink_rx_val_i` | RX data valid (1-bit)
| | `slink_rx_rdy_o` | RX ready to receive (1-bit)
| | `slink_tx_dat_o` | TX data (32-bit)
| | `slink_tx_val_o` | TX data valid (1-bit)
| | `slink_tx_rdy_i` | TX allowed to send (1-bit)
| Top entity port(s): | `slink_rx_dat_i` | RX link data (32-bit)
| | `slink_rx_val_i` | RX link data valid (1-bit)
| | `slink_rx_lst_i` | RX link last element of stream (1-bit)
| | `slink_rx_rdy_o` | RX link ready to receive (1-bit)
| | `slink_tx_dat_o` | TX link data (32-bit)
| | `slink_tx_val_o` | TX link data valid (1-bit)
| | `slink_tx_lst_o` | TX link last element of stream (1-bit)
| | `slink_tx_rdy_i` | TX link allowed to send (1-bit)
| Configuration generics: | `IO_SLINK_EN` | implement SLINK when _true_
| | `IO_SLINK_RX_FIFO` | RX FIFO depth (1..32k), has to be a power of two
| | `IO_SLINK_TX_FIFO` | TX FIFO depth (1..32k), has to be a power of two
Expand All @@ -25,7 +27,7 @@

The stream link interface provides independent RX and TX channels for sending for sending and receiving
stream data. Each channel features an internal FIFO with configurable depth to buffer stream data
(`IO_SLINK_RX_FIFO` for the RX FIFO, `IO_SLINK_TX_FIFO` for the TX FIFO). The interface provides higher
(`IO_SLINK_RX_FIFO` for the RX FIFO, `IO_SLINK_TX_FIFO` for the TX FIFO). The SLINK interface provides higher
bandwidth and less latency than the external bus interface making it ideally suited for coupling custom
stream processing units or streaming peripherals.

Expand All @@ -36,10 +38,11 @@ An example program for the SLINK module is available in `sw/example/demo_slink`.

**Interface & Protocol**

The SLINK interface consists of three signals `dat`, `val` and `rdy` per channel.
The SLINK interface consists of four signals per channel:

* `dat` contains the actual data word
* `val` marks the current transmission cycle as valid
* `lst` makes the current transmission cycle as the last element of a stream
* `rdy` indicates that the receiving part is ready to receive
.AXI4-Stream Compatibility
Expand All @@ -49,11 +52,20 @@ The interface names and the underlying protocol is compatible to the AXI4-Stream

**Theory of Operation**

The SLINK provides two interface registers: `CTRL` and `DATA`. The control register (`CTRL`) is used to configure
the module and to check its status. The data register (`DATA`) is sued to send data (by writing to it) or to read
received data (by reading from it). Note that accessing the data register will actually access the internal FIFOs.
The SLINK provides four interface registers. The control register (`CTRL`) is used to configure
the module and to check its status. Three individual data registers (`RX_DATA`, `TX_DATA`, `TX_DATA_LAST`)
are used to send and received the link's actual data stream.

The configured FIFO sizes can be retrieved by software via the `SLINK_CTRL_RX_FIFO_*` and `SLINK_CTRL_TX_FIFO_*` bits.
The `RX_DATA` register provides direct access to the RX link FIFO buffer. After reading data from this the register
the control register's `SLINK_CTRL_RX_LAST` can be checked to determine if the according data word has been marked
as "end of stream" via the `slink_rx_lst_i` signal (this signal is also buffered by the link's FIFO).

Writing to the `TX_DATA` or `TX_DATA_LAST` register will immediately write to the TX link FIFO buffer.
When writing to the `TX_DATA_LAST` the according data word will be marked as "end of stream" via the
`slink_tx_lst_o` signal (this signal is also buffered by the link's FIFO).

The configured FIFO sizes can be retrieved by software via the control register's `SLINK_CTRL_RX_FIFO_*` and
`SLINK_CTRL_TX_FIFO_*` bits.

The SLINK is globally activated by setting the control register's enable bit `SLINK_CTRL_EN`. Clearing this bit will
reset all internal logic and will also clear both FIFOs. The FIFOs can also be cleared manually at all time by
Expand All @@ -76,18 +88,20 @@ it has to be explicitly cleared again by writing zero to the according <<_mip>>
[options="header",grid="all"]
|=======================
| Address | Name [C] | Bit(s) | R/W | Function
.20+<| `0xffffec00` .20+<| `NEORV32_SLINK.CTRL` <| `0` `SLINK_CTRL_EN` ^| r/w <| SLINK global enable
<| `1` `SLINK_CTRL_RX_CLR` ^| -/w <| Clear RX FIFO (bit auto-clears)
<| `2` `SLINK_CTRL_TX_CLR` ^| -/w <| Clear TX FIFO (bit auto-clears)
<| `7:3` _reserved_ ^| r/- <| _reserved_, read as zero
.22+<| `0xffffec00` .22+<| `NEORV32_SLINK.CTRL` <| `0` `SLINK_CTRL_EN` ^| r/w <| SLINK global enable
<| `1` `SLINK_CTRL_RX_CLR` ^| -/w <| Clear RX FIFO when set (bit auto-clears)
<| `2` `SLINK_CTRL_TX_CLR` ^| -/w <| Clear TX FIFO when set (bit auto-clears)
<| `3` _reserved_ ^| r/- <| _reserved_, read as zero
<| `4` `SLINK_CTRL_RX_LAST` ^| r/- <| Last word read from `RX_DATA` is marked as "end of stream"
<| `7:5` _reserved_ ^| r/- <| _reserved_, read as zero
<| `8` `SLINK_CTRL_RX_EMPTY` ^| r/- <| RX FIFO empty
<| `9` `SLINK_CTRL_RX_HALF` ^| r/- <| RX FIFO at least half full
<| `10` `SLINK_CTRL_RX_FULL` ^| r/- <| RX FIFO full
<| `11` `SLINK_CTRL_TX_EMPTY` ^| r/- <| TX FIFO empty
<| `12` `SLINK_CTRL_TX_HALF` ^| r/- <| TX FIFO at least half full
<| `13` `SLINK_CTRL_TX_FULL` ^| r/- <| TX FIFO full
<| `15:14` _reserved_ ^| r/- <| _reserved_, read as zero
<| `16` `SLINK_CTRL_IRQ_RX_NEMPTY` ^| r/w <| IRQ if RX FIFO not empty
<| `16` `SLINK_CTRL_IRQ_RX_NEMPTY` ^| r/w <| IRQ if RX FIFO not empty
<| `17` `SLINK_CTRL_IRQ_RX_HALF` ^| r/w <| IRQ if RX FIFO at least half full
<| `18` `SLINK_CTRL_IRQ_RX_FULL` ^| r/w <| IRQ if RX FIFO full
<| `19` `SLINK_CTRL_IRQ_TX_EMPTY` ^| r/w <| IRQ if TX FIFO empty
Expand All @@ -96,5 +110,7 @@ it has to be explicitly cleared again by writing zero to the according <<_mip>>
<| `23:22` _reserved_ ^| r/- <| _reserved_, read as zero
<| `27:24` `SLINK_CTRL_RX_FIFO_MSB : SLINK_CTRL_RX_FIFO_LSB` ^| r/- <| log2(RX FIFO size)
<| `31:28` `SLINK_CTRL_TX_FIFO_MSB : SLINK_CTRL_TX_FIFO_LSB` ^| r/- <| log2(TX FIFO size)
| `0xffffec04` | `NEORV32_SLINK.DATA` | `31:0` | r/w | RX/TX data
| `0xffffec04` | `NEORV32_SLINK.RX_DATA` | `31:0` | r/- | Read word from RX link FIFO
| `0xffffec08` | `NEORV32_SLINK.TX_DATA` | `31:0` | -/w | Write word to TX link FIFO
| `0xffffec0c` | `NEORV32_SLINK.TX_DATA_LAST` | `31:0` | -/w | Write word to TX link FIFO and also set "end-of-stream" delimiter
|=======================
4 changes: 3 additions & 1 deletion rtl/core/neorv32_package.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ package neorv32_package is

-- Architecture Constants -----------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01090504"; -- hardware version
constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01090505"; -- hardware version
constant archid_c : natural := 19; -- official RISC-V architecture ID
constant XLEN : natural := 32; -- native data path width

Expand Down Expand Up @@ -856,9 +856,11 @@ package neorv32_package is
-- Stream Link Interface (available if IO_SLINK_EN = true) --
slink_rx_dat_i : in std_ulogic_vector(31 downto 0) := (others => 'L');
slink_rx_val_i : in std_ulogic := 'L';
slink_rx_lst_i : in std_ulogic := 'L';
slink_rx_rdy_o : out std_ulogic;
slink_tx_dat_o : out std_ulogic_vector(31 downto 0);
slink_tx_val_o : out std_ulogic;
slink_tx_lst_o : out std_ulogic;
slink_tx_rdy_i : in std_ulogic := 'L';
-- XIP (execute in-place via SPI) signals (available if XIP_EN = true) --
xip_csn_o : out std_ulogic;
Expand Down
64 changes: 46 additions & 18 deletions rtl/core/neorv32_slink.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,31 @@ entity neorv32_slink is
-- RX stream interface --
slink_rx_data_i : in std_ulogic_vector(31 downto 0); -- input data
slink_rx_valid_i : in std_ulogic; -- valid input
slink_rx_last_i : in std_ulogic; -- end of stream
slink_rx_ready_o : out std_ulogic; -- ready to receive
-- TX stream interface --
slink_tx_data_o : out std_ulogic_vector(31 downto 0); -- output data
slink_tx_valid_o : out std_ulogic; -- valid output
slink_tx_last_o : out std_ulogic; -- end of stream
slink_tx_ready_i : in std_ulogic -- ready to send
);
end neorv32_slink;

architecture neorv32_slink_rtl of neorv32_slink is

-- memory-mapped interface registers --
constant addr_ctrl_c : std_ulogic_vector(1 downto 0) := "00"; -- control register
constant addr_rx_c : std_ulogic_vector(1 downto 0) := "01"; -- RX data
constant addr_tx_c : std_ulogic_vector(1 downto 0) := "10"; -- TX data
constant addr_tx_last_c : std_ulogic_vector(1 downto 0) := "11"; -- TY data + last-delimiter

-- control register --
constant ctrl_en_c : natural := 0; -- r/w: Global module enable
constant ctrl_rx_clr_c : natural := 1; -- -/w: Clear RX FIFO, auto-clears
constant ctrl_tx_clr_c : natural := 2; -- -/w: Clear TX FIFO, auto-clears
--
constant ctrl_rx_last_c : natural := 4; -- r/-: RX end-of-stream (according to prev. read RX data)
--
constant ctrl_rx_empty_c : natural := 8; -- r/-: RX FIFO empty
constant ctrl_rx_half_c : natural := 9; -- r/-: RX FIFO at least half full
constant ctrl_rx_full_c : natural := 10; -- r/-: RX FIFO full
Expand All @@ -94,7 +104,7 @@ architecture neorv32_slink_rtl of neorv32_slink is
constant ctrl_tx_fifo_size2_c : natural := 30; -- r/-: log2(TX fifo size), bit 2
constant ctrl_tx_fifo_size3_c : natural := 31; -- r/-: log2(TX fifo size), bit 3 (msb)

-- control register (see bit definitions above) --
-- control register --
type ctrl_t is record
enable : std_ulogic;
rx_clr : std_ulogic;
Expand All @@ -108,13 +118,16 @@ architecture neorv32_slink_rtl of neorv32_slink is
end record;
signal ctrl : ctrl_t;

-- RX last indicator --
signal rx_last : std_ulogic;

-- FIFO interface --
type fifo_t is record
we : std_ulogic; -- write enable
re : std_ulogic; -- read enable
clear : std_ulogic; -- sync reset, high-active
wdata : std_ulogic_vector(31 downto 0); -- write data
rdata : std_ulogic_vector(31 downto 0); -- read data
wdata : std_ulogic_vector(32 downto 0); -- write data + last-flag
rdata : std_ulogic_vector(32 downto 0); -- read data + last-flag
avail : std_ulogic; -- data available?
free : std_ulogic; -- free entry available?
half : std_ulogic; -- half full
Expand Down Expand Up @@ -151,10 +164,9 @@ begin
ctrl.tx_clr <= '0'; -- auto-clear

if (bus_req_i.stb = '1') then

-- write access --
if (bus_req_i.rw = '1') then
if (bus_req_i.addr(2) = '0') then -- control register
if (bus_req_i.addr(3 downto 2) = addr_ctrl_c) then -- control register
ctrl.enable <= bus_req_i.data(ctrl_en_c);
ctrl.rx_clr <= bus_req_i.data(ctrl_rx_clr_c);
ctrl.tx_clr <= bus_req_i.data(ctrl_tx_clr_c);
Expand All @@ -166,12 +178,13 @@ begin
ctrl.irq_tx_nhalf <= bus_req_i.data(ctrl_irq_tx_nhalf_c);
ctrl.irq_tx_nfull <= bus_req_i.data(ctrl_irq_tx_nfull_c);
end if;

-- read access --
else
if (bus_req_i.addr(2) = '0') then -- control register
if (bus_req_i.addr(3 downto 2) = addr_ctrl_c) then -- control register
bus_rsp_o.data(ctrl_en_c) <= ctrl.enable;
--
bus_rsp_o.data(ctrl_rx_last_c) <= rx_last;
--
bus_rsp_o.data(ctrl_rx_empty_c) <= not rx_fifo.avail;
bus_rsp_o.data(ctrl_rx_half_c) <= rx_fifo.half;
bus_rsp_o.data(ctrl_rx_full_c) <= not rx_fifo.free;
Expand All @@ -188,11 +201,10 @@ begin
--
bus_rsp_o.data(ctrl_rx_fifo_size3_c downto ctrl_rx_fifo_size0_c) <= std_ulogic_vector(to_unsigned(index_size_f(SLINK_RX_FIFO), 4));
bus_rsp_o.data(ctrl_tx_fifo_size3_c downto ctrl_tx_fifo_size0_c) <= std_ulogic_vector(to_unsigned(index_size_f(SLINK_TX_FIFO), 4));
else -- RX/TX data register
else -- RX (TX) data register
bus_rsp_o.data <= rx_fifo.rdata(31 downto 0);
end if;
end if;

end if;
end if;
end process bus_access;
Expand All @@ -203,7 +215,7 @@ begin
rx_fifo_inst: entity neorv32.neorv32_fifo
generic map (
FIFO_DEPTH => SLINK_RX_FIFO,
FIFO_WIDTH => 32,
FIFO_WIDTH => 32+1, -- data + last-flag
FIFO_RSYNC => true, -- sync read
FIFO_SAFE => true -- safe access
)
Expand All @@ -224,19 +236,34 @@ begin
);

rx_fifo.clear <= (not ctrl.enable) or ctrl.rx_clr;
rx_fifo.re <= '1' when (bus_req_i.stb = '1') and (bus_req_i.rw = '0') and (bus_req_i.addr(2) = '1') else '0';
rx_fifo.re <= '1' when (bus_req_i.stb = '1') and (bus_req_i.rw = '0') and (bus_req_i.addr(3 downto 2) = addr_rx_c) else '0';

rx_fifo.we <= slink_rx_valid_i;
rx_fifo.wdata(31 downto 0) <= slink_rx_data_i;
rx_fifo.wdata(32) <= slink_rx_last_i;
slink_rx_ready_o <= rx_fifo.free;

rx_fifo.we <= slink_rx_valid_i;
rx_fifo.wdata <= slink_rx_data_i;
slink_rx_ready_o <= rx_fifo.free;
-- backup current RX last indicator for current access --
rx_last_flag: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
rx_last <= '0';
elsif rising_edge(clk_i) then
if (rx_fifo.clear = '1') then
rx_last <= '0';
elsif (rx_fifo.re = '1') then
rx_last <= rx_fifo.rdata(32);
end if;
end if;
end process rx_last_flag;


-- TX Data FIFO ---------------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
tx_fifo_inst: entity neorv32.neorv32_fifo
generic map (
FIFO_DEPTH => SLINK_TX_FIFO,
FIFO_WIDTH => 32,
FIFO_WIDTH => 32+1, -- data + last-flag
FIFO_RSYNC => true, -- sync read
FIFO_SAFE => true -- safe access
)
Expand All @@ -257,11 +284,12 @@ begin
);

tx_fifo.clear <= (not ctrl.enable) or ctrl.tx_clr;
tx_fifo.we <= '1' when (bus_req_i.stb = '1') and (bus_req_i.rw = '1') and (bus_req_i.addr(2) = '1') else '0';
tx_fifo.wdata <= bus_req_i.data;
tx_fifo.we <= '1' when (bus_req_i.stb = '1') and (bus_req_i.rw = '1') and (bus_req_i.addr(3) = '1') else '0';
tx_fifo.wdata <= bus_req_i.addr(2) & bus_req_i.data; -- last-flag is set implicitly via access address (TX/TX_LAST register)

tx_fifo.re <= slink_tx_ready_i;
slink_tx_data_o <= tx_fifo.rdata;
slink_tx_data_o <= tx_fifo.rdata(31 downto 0);
slink_tx_last_o <= tx_fifo.rdata(32) and tx_fifo.avail;
slink_tx_valid_o <= tx_fifo.avail;


Expand Down
5 changes: 5 additions & 0 deletions rtl/core/neorv32_top.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,11 @@ entity neorv32_top is
-- Stream Link Interface (available if IO_SLINK_EN = true) --
slink_rx_dat_i : in std_ulogic_vector(31 downto 0) := (others => 'L'); -- RX input data
slink_rx_val_i : in std_ulogic := 'L'; -- RX valid input
slink_rx_lst_i : in std_ulogic := 'L'; -- last element of stream
slink_rx_rdy_o : out std_ulogic; -- RX ready to receive
slink_tx_dat_o : out std_ulogic_vector(31 downto 0); -- TX output data
slink_tx_val_o : out std_ulogic; -- TX valid output
slink_tx_lst_o : out std_ulogic; -- last element of stream
slink_tx_rdy_i : in std_ulogic := 'L'; -- TX ready to send

-- XIP (execute in place via SPI) signals (available if XIP_EN = true) --
Expand Down Expand Up @@ -1479,10 +1481,12 @@ begin
-- RX stream interface --
slink_rx_data_i => slink_rx_dat_i,
slink_rx_valid_i => slink_rx_val_i,
slink_rx_last_i => slink_rx_lst_i,
slink_rx_ready_o => slink_rx_rdy_o,
-- TX stream interface --
slink_tx_data_o => slink_tx_dat_o,
slink_tx_valid_o => slink_tx_val_o,
slink_tx_last_o => slink_tx_lst_o,
slink_tx_ready_i => slink_tx_rdy_i
);
end generate;
Expand All @@ -1494,6 +1498,7 @@ begin
slink_rx_rdy_o <= '0';
slink_tx_dat_o <= (others => '0');
slink_tx_val_o <= '0';
slink_tx_lst_o <= '0';
end generate;


Expand Down
Loading

0 comments on commit 20cc8f0

Please sign in to comment.