Adds pipeline buffer and controller with testbench

Implements a pipeline buffer component supporting passthrough and register modes, controlled via a dedicated controller.
Adds AXI-like handshake signals for data flow management.
Includes a testbench to validate functionality with randomized delays.

Addresses robust data buffering and flow control.
This commit is contained in:
2025-04-19 20:39:13 +00:00
parent 31ce816816
commit 286ae5a12c
3 changed files with 294 additions and 0 deletions

52
src/PipelineBuffer.vhd Normal file
View File

@@ -0,0 +1,52 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
entity PipelineBuffer is
generic (
--@ Data width
G_Width : integer := 32
);
port (
--@ Clock signal; (**Rising edge** triggered)
I_CLK : in std_logic := '0';
--@ Enable input from **Pipeline Buffer Controller**
--@ [1]: If low, data is passed through, else data is registered
--@ [0]: Enable for register
I_Enable : in std_logic_vector(1 downto 0) := (others => '0');
--@ Data input
I_Data : in std_logic_vector(G_Width - 1 downto 0) := (others => '0');
--@ Data output
O_Data : out std_logic_vector(G_Width - 1 downto 0) := (others => '0')
);
end entity PipelineBuffer;
architecture RTL of PipelineBuffer is
signal C_MUX : std_logic := '0';
signal C_Enable : std_logic := '0';
signal R_Data : std_logic_vector(G_Width - 1 downto 0) := (others => '0');
begin
C_MUX <= I_Enable(1);
C_Enable <= I_Enable(0);
P_MUX : process (C_MUX, I_Data, R_Data)
begin
if C_MUX = '0' then -- Passthrough mode
O_Data <= I_Data;
else -- Register mode
O_Data <= R_Data;
end if;
end process;
P_Register : process (I_CLK)
begin
if rising_edge(I_CLK) then
if C_Enable = '1' then
R_Data <= I_Data;
end if;
end if;
end process;
end architecture;

View File

@@ -0,0 +1,73 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
entity PipelineBufferController is
generic (
--@ Reset active at this level
G_ResetActiveAt : std_logic := '1'
);
port (
--@ Clock signal; (**Rising edge** triggered)
I_CLK : in std_logic := '0';
--@ Reset; (**Synchronous**, **Active at `G_ResetActiveAt`**)
I_RST : in std_logic := '0';
--@ Chip enable; (**Synchronous**, **Active high**)
I_CE : in std_logic := '1';
--@ [1]: If low, data is passed through, else data is registered
--@ [0]: Enable for register
O_Enable : out std_logic_vector(1 downto 0) := (others => '0');
--@ @virtualbus AXI-Flags-In @dir In Input interface for AXI-like handshake
--@ AXI like valid; (**Synchronous**, **Active high**)
I_Valid : in std_logic := '0';
--@ AXI like ready; (**Synchronous**, **Active high**)
O_Ready : out std_logic := '0';
--@ @end
--@ @virtualbus AXI-Flags-Out @dir Out Output interface for AXI-like handshake
--@ AXI like valid; (**Synchronous**, **Active high**)
O_Valid : out std_logic := '0';
--@ AXI like ready; (**Synchronous**, **Active high**)
I_Ready : in std_logic := '0'
--@ @end
);
end entity PipelineBufferController;
architecture RTL of PipelineBufferController is
signal C_MUX : std_logic := '0';
signal C_Enable : std_logic := '0';
signal R_IsBuffered : std_logic := '0';
begin
--@ Set mux to buffered mode if data is available in the buffer.
C_MUX <= R_IsBuffered;
--@ Enable the buffer register if not buffered and chip enable is high.
C_Enable <= I_CE and not R_IsBuffered;
--@ Set the ready signal to high if not buffered.
O_Ready <= not R_IsBuffered;
--@ Set the valid signal to high if data is available in the buffer or if data is valid.
O_Valid <= R_IsBuffered or I_Valid;
process (I_CLK)
begin
if rising_edge(I_CLK) then
if I_RST = G_ResetActiveAt then
R_IsBuffered <= '0';
elsif I_CE = '1' then
if R_IsBuffered = '0' and I_Valid = '1' then
R_IsBuffered <= '1';
elsif I_Ready = '1' and (R_IsBuffered or I_Valid) = '1' then
R_IsBuffered <= '0';
end if;
end if;
end if;
end process;
O_Enable(1) <= C_MUX;
O_Enable(0) <= C_Enable;
end architecture;

169
tests/PipelineBuffer_tb.vhd Normal file
View File

@@ -0,0 +1,169 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use std.env.stop;
entity PipelineBuffer_tb is
end entity PipelineBuffer_tb;
architecture RTL of PipelineBuffer_tb is
-- Clock signal period
constant K_Period : time := 20 ns;
-- Zufallsverzögerungen
constant K_WriteDelay : natural := 40;
constant K_ReadDelay : natural := 60;
-- Konstanten
constant G_ResetActiveAt : std_logic := '1';
constant G_Width : integer := 32;
-- Zufalls-Seed
shared variable seed1 : integer := 42;
shared variable seed2 : integer := 1337;
-- Randomfunktion
impure function rand_int(min_val, max_val : integer) return integer is
variable r : real;
begin
uniform(seed1, seed2, r);
return integer(round(r * real(max_val - min_val + 1) + real(min_val) - 0.5));
end function;
-- Signale
signal I_CLK : std_logic := '0';
signal I_RST : std_logic := '1';
signal I_CE : std_logic := '1';
signal O_Enable : std_logic_vector(1 downto 0) := (others => '0');
signal I_Valid : std_logic := '0';
signal O_Ready : std_logic := '0';
signal O_Valid : std_logic := '0';
signal I_Ready : std_logic := '0';
signal I_Data : std_logic_vector(G_Width - 1 downto 0) := (others => 'U');
signal O_Data : std_logic_vector(G_Width - 1 downto 0) := (others => 'U');
begin
-- Clock
Clocking : process
begin
while true loop
I_CLK <= '0';
wait for (K_Period / 2);
I_CLK <= '1';
wait for (K_Period / 2);
end loop;
end process;
-- Reset
I_RST <= G_ResetActiveAt, not G_ResetActiveAt after 100 ns;
-- DUT: Controller
i_PipelineBufferController : entity work.PipelineBufferController
generic map(
G_ResetActiveAt => G_ResetActiveAt
)
port map(
I_CLK => I_CLK,
I_RST => I_RST,
I_CE => I_CE,
O_Enable => O_Enable,
I_Valid => I_Valid,
O_Ready => O_Ready,
O_Valid => O_Valid,
I_Ready => I_Ready
);
-- DUT: Register
i_PipelineBuffer : entity work.PipelineBuffer
generic map(
G_Width => G_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_Enable,
I_Data => I_Data,
O_Data => O_Data
);
-- Write Stimulus
Stim_Write : process
variable delay : integer := 0;
variable i : integer := 1;
variable pending : boolean := false;
begin
I_Valid <= '0';
I_Data <= (others => 'U');
wait until I_RST = '0';
while true loop
wait until rising_edge(I_CLK);
-- Neues Paket vorbereiten
if not pending and delay = 0 then
I_Data <= std_logic_vector(to_unsigned(i, G_Width));
I_Valid <= '1';
pending := true;
report "Sende Paket #" & integer'image(i) severity note;
end if;
-- Handshake erfolgt
if O_Ready = '1' and I_Valid = '1' then
I_Valid <= '0';
i := i + 1;
delay := rand_int(1, K_WriteDelay);
pending := false;
end if;
-- Verzögerung herunterzählen
if delay > 0 and not pending then
delay := delay - 1;
end if;
end loop;
end process;
-- Read Stimulus (robust)
Stim_Read : process
variable delay : integer := 0;
variable expected : integer := 1;
variable received : integer;
variable consume_now : boolean := false;
begin
I_Ready <= '0';
wait until I_RST = '0';
while true loop
wait until rising_edge(I_CLK);
-- Wenn O_Valid vorhanden und kein Delay mehr: jetzt lesen
if O_Valid = '1' and delay = 0 and not consume_now then
I_Ready <= '1';
consume_now := true; -- Warte auf nächste Gültigkeit
end if;
-- Sobald Handshake erfolgt (O_Valid & I_Ready), auswerten
if O_Valid = '1' and I_Ready = '1' and consume_now then
received := to_integer(unsigned(O_Data));
if received = expected then
report "Empfange Paket #" & integer'image(expected) severity note;
else
report "FEHLER bei Paket #" & integer'image(expected) &
": erwartet " & integer'image(expected) &
", empfangen " & integer'image(received) severity error;
end if;
expected := expected + 1;
delay := rand_int(1, K_ReadDelay);
consume_now := false;
I_Ready <= '0';
end if;
-- Wartezeit herunterzählen
if delay > 0 and not consume_now then
delay := delay - 1;
end if;
end loop;
end process;
end architecture RTL;