Refactors sprite pipeline design for clarity and modularity

Replaces CalcPipeline with HorizontalSpritePipeline for improved
readability and maintainability. Introduces a structured pipeline
architecture with enhanced visibility checks, ROM access efficiency,
and modular design principles.

Provides better handling of non-visible pixels and adheres to
AXI-like handshake standards.
This commit is contained in:
2025-04-20 03:02:45 +00:00
parent bbe0ff9b9e
commit daa1abf477
2 changed files with 301 additions and 207 deletions

View File

@@ -1,207 +0,0 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
entity CalcPipeline is
generic (
--@ Width of the sprite index (Base address) register
G_Index_Width : integer := 5;
--@ Width of the sprite offset (Line address) register
G_Offset_Width : integer := 8;
--@ Width of the X position (Row) register
G_X_Width : integer := 10;
--@ Width of the pixel data from rom
G_Rom_Width : integer := 8;
--@ Width of the pixel data output
G_Pixel_Width : integer := 8
);
port (
--@ Clock; (**Rising edge** triggered)
I_CLK : in std_logic;
--@ Clock Enable; (**Synchronous**, **Active high**)
I_CE : in std_logic;
--@ Reset; (**Synchronous**, **Active high**)
I_RST : in std_logic;
--@ @virtualbus Start-OP @dir In Start calculation bus
--@ AXI like valid; (**Synchronous**, **Active high**)
I_OP_Valid : in std_logic;
--@ AXI like ready; (**Synchronous**, **Active high**)
O_OP_Ready : out std_logic;
--@ Index address
I_OP_Index : in std_logic_vector(G_Index_Width - 1 downto 0);
--@ Offset address
I_OP_Offset : in std_logic_vector(G_Offset_Width - 1 downto 0);
--@ X position of the request
I_OP_X_Request : in std_logic_vector(G_X_Width - 1 downto 0);
--@ X position of the sprite
I_OP_X_Sprite : in std_logic_vector(G_X_Width - 1 downto 0);
--@ @end
--@ @virtualbus Rom-Address @dir Out Request rom data bus
--@ AXI like valid; (**Synchronous**, **Active high**)
O_Rom_Valid : out std_logic;
--@ AXI like ready; (**Synchronous**, **Active high**)
I_Rom_Ready : in std_logic;
--@ Rom address
O_Rom_Address : out std_logic_vector(G_Index_Width + G_Offset_Width - 1 downto 0);
--@ @end
--@ @virtualbus Rom-Data @dir In Rom data bus
--@ AXI like valid; (**Synchronous**, **Active high**)
I_Rom_Valid : in std_logic;
--@ AXI like ready; (**Synchronous**, **Active high**)
O_Rom_Ready : out std_logic;
--@ Rom data
I_Rom_Data : in std_logic_vector(G_Rom_Width - 1 downto 0);
--@ @end
--@ @virtualbus Pixel-Data @dir Out Pixel data output bus
--@ AXI like valid; (**Synchronous**, **Active high**)
O_Pixel_Valid : out std_logic;
--@ AXI like ready; (**Synchronous**, **Active high**)
I_Pixel_Ready : in std_logic;
--@ Pixel data
O_Pixel_Data : out std_logic_vector(G_Pixel_Width - 1 downto 0)
--@ @end
);
end entity CalcPipeline;
architecture RTL of CalcPipeline is
signal R_Index : std_logic_vector(G_Index_Width - 1 downto 0) := (others => '0');
signal R_Offset : std_logic_vector(G_Offset_Width - 1 downto 0) := (others => '0');
signal R_X_Request : std_logic_vector(G_X_Width - 1 downto 0) := (others => '0');
signal R_X_Sprite : std_logic_vector(G_X_Width - 1 downto 0) := (others => '0');
signal C_Address : std_logic_vector(G_Index_Width + G_Offset_Width - 1 downto 0) := (others => '0');
signal R_Address : std_logic_vector(G_Index_Width + G_Offset_Width - 1 downto 0) := (others => '0');
signal I_I_Calculating_Ready : std_logic := '0';
signal O_I_Calculating_Valid : std_logic := '0';
signal I_O_Calculating_Ready : std_logic := '0';
signal O_O_Calculating_Valid : std_logic := '0';
signal C_X_Visible : std_logic := '0';
signal R_X_Visible : std_logic := '0';
signal I_RomRequest_Valid : std_logic := '0';
signal O_RomRequest_Ready : std_logic := '0';
begin
INST_I_Calculating : entity work.PipelineStage
generic map(
G_PipelineStages => 1,
G_D0_Width => G_Index_Width,
G_D1_Width => G_Offset_Width,
G_D2_Width => G_X_Width,
G_D3_Width => G_X_Width
)
port map(
I_CLK => I_CLK,
I_CE => I_CE,
I_RST => I_RST,
I_Valid => I_OP_Valid,
O_Ready => O_OP_Ready,
I_Data_0 => I_OP_Index,
I_Data_1 => I_OP_Offset,
I_Data_2 => I_OP_X_Request,
I_Data_3 => I_OP_X_Sprite,
O_Valid => O_I_Calculating_Valid,
I_Ready => I_I_Calculating_Ready,
O_Data_0 => R_Index,
O_Data_1 => R_Offset,
O_Data_2 => R_X_Request,
O_Data_3 => R_X_Sprite
);
process (R_Offset, R_Index, R_X_Request, R_X_Sprite)
variable V_Offset : unsigned(G_Offset_Width - 1 downto 0);
variable V_X_Request : unsigned(G_X_Width - 1 downto 0);
variable V_X_Sprite : unsigned(G_X_Width - 1 downto 0);
variable V_RelativeOffset : unsigned(G_X_Width - 1 downto 0);
variable V_CalculateOffset : unsigned(G_Offset_Width - 1 downto 0);
begin
V_Offset := unsigned(R_Offset);
V_X_Request := unsigned(R_X_Request);
V_X_Sprite := unsigned(R_X_Sprite);
V_RelativeOffset := V_X_Request - V_X_Sprite;
V_CalculateOffset := V_Offset + V_RelativeOffset(G_Offset_Width - 1 downto 0);
if V_CalculateOffset > 15 then
C_X_Visible <= '0';
else
C_X_Visible <= '1';
end if;
C_Address <= R_Index & std_logic_vector(V_CalculateOffset);
end process;
INST_O_Calculating : entity work.PipelineStage
generic map(
G_PipelineStages => 1,
G_D0_Width => G_Index_Width + G_Offset_Width,
G_D1_Width => 1
)
port map(
I_CLK => I_CLK,
I_CE => I_CE,
I_RST => I_RST,
I_Valid => O_I_Calculating_Valid,
O_Ready => I_I_Calculating_Ready,
I_Data_0 => C_Address,
I_Data_1(0) => C_X_Visible,
O_Valid => O_O_Calculating_Valid,
I_Ready => I_O_Calculating_Ready,
O_Data_0 => R_Address,
O_Data_1(0) => R_X_Visible
);
process (R_X_Visible, O_O_Calculating_Valid, O_RomRequest_Ready)
begin
if R_X_Visible = '0' then
I_RomRequest_Valid <= '0';
I_O_Calculating_Ready <= '1';
else
I_RomRequest_Valid <= O_O_Calculating_Valid;
I_O_Calculating_Ready <= O_RomRequest_Ready;
end if;
end process;
INST_RomRequest : entity work.PipelineStage
generic map(
G_PipelineStages => 1,
G_D0_Width => G_Index_Width + G_Offset_Width
)
port map(
I_CLK => I_CLK,
I_CE => I_CE,
I_RST => I_RST,
I_Valid => I_RomRequest_Valid,
O_Ready => O_RomRequest_Ready,
I_Data_0 => R_Address,
O_Valid => O_Rom_Valid,
I_Ready => I_Rom_Ready,
O_Data_0 => O_Rom_Address
);
I_ForwardPixelData : entity work.PipelineStage
generic map(
G_PipelineStages => 1,
G_D0_Width => G_Pixel_Width
)
port map(
I_CLK => I_CLK,
I_CE => I_CE,
I_RST => I_RST,
I_Valid => I_Rom_Valid,
O_Ready => O_Rom_Ready,
I_Data_0 => I_Rom_Data,
O_Valid => O_Pixel_Valid,
I_Ready => I_Pixel_Ready,
O_Data_0 => O_Pixel_Data
);
end architecture;

View File

@@ -0,0 +1,301 @@
----------------------------------------------------------------------------------
--@ - Name: **HorizontalSpritePipeline**
--@ - Version: 0.0.1
--@ - Author: _0xMax42 ([Blog](0xMax42.io))_
--@ - License: [MIT](LICENSE)
--@
--@ ## Description
--@ This pipeline calculates whether a sprite is visible at a given horizontal
--@ scanline position (X coordinate), and if so, outputs the corresponding pixel
--@ address and color data.
--@
--@ The operation is fully pipelined and includes the following steps:
--@ - Latching of sprite index, offset, and X positions
--@ - Calculation of the horizontal offset between request and sprite position
--@ - Visibility check against the maximum sprite width
--@ - ROM address generation for the sprite pixel
--@ - Forwarding of pixel data to the output pipeline
--@
--@ Non-visible pixels (outside the horizontal sprite bounds) are filtered out
--@ before any ROM access occurs. The pipeline uses fully synchronous logic
--@ and AXI-like handshake signals across all bus interfaces.
--@
--@ ## History
--@ - 0.0.1 (2025-04-20) Initial version
----------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
entity HorizontalSpritePipeline is
generic (
--@ Width of the sprite index (Base address) register
G_Index_Width : integer := 5;
--@ Width of the sprite offset (Line address) register
G_Offset_Width : integer := 8;
--@ Width of the X position (Row) register
G_X_Width : integer := 10;
--@ Width of the pixel data from rom
G_Rom_Width : integer := 8;
--@ Width of the pixel data output
G_Pixel_Width : integer := 8;
--@ Horizontal width of a sprite in pixels
G_SpriteMaxWidth : integer := 16
);
port (
--@ Clock; (**Rising edge** triggered)
I_CLK : in std_logic;
--@ Clock Enable; (**Synchronous**, **Active high**)
I_CE : in std_logic;
--@ Reset; (**Synchronous**, **Active high**)
I_RST : in std_logic;
--@ @virtualbus Operation @dir In Operation input bus
--@ AXI like valid; (**Synchronous**, **Active high**)
I_OP_Valid : in std_logic;
--@ AXI like ready; (**Synchronous**, **Active high**)
O_OP_Ready : out std_logic;
--@ Index address
I_OP_Index : in std_logic_vector(G_Index_Width - 1 downto 0);
--@ Offset address
I_OP_Offset : in std_logic_vector(G_Offset_Width - 1 downto 0);
--@ X position of the request
I_OP_X_Request : in std_logic_vector(G_X_Width - 1 downto 0);
--@ X position of the sprite
I_OP_X_Sprite : in std_logic_vector(G_X_Width - 1 downto 0);
--@ @end
--@ @virtualbus Rom-Address @dir Out Request rom data bus
--@ AXI like valid; (**Synchronous**, **Active high**)
O_Rom_Valid : out std_logic;
--@ AXI like ready; (**Synchronous**, **Active high**)
I_Rom_Ready : in std_logic;
--@ Rom address
O_Rom_Address : out std_logic_vector(G_Index_Width + G_Offset_Width - 1 downto 0);
--@ @end
--@ @virtualbus Rom-Data @dir In Rom data bus
--@ AXI like valid; (**Synchronous**, **Active high**)
I_Rom_Valid : in std_logic;
--@ AXI like ready; (**Synchronous**, **Active high**)
O_Rom_Ready : out std_logic;
--@ Rom data
I_Rom_Data : in std_logic_vector(G_Rom_Width - 1 downto 0);
--@ @end
--@ @virtualbus Pixel-Data @dir Out Pixel data output bus
--@ AXI like valid; (**Synchronous**, **Active high**)
O_Pixel_Valid : out std_logic;
--@ AXI like ready; (**Synchronous**, **Active high**)
I_Pixel_Ready : in std_logic;
--@ Pixel data
O_Pixel_Data : out std_logic_vector(G_Pixel_Width - 1 downto 0)
--@ @end
);
end entity HorizontalSpritePipeline;
architecture RTL of HorizontalSpritePipeline is
--@ Register enable for the horizontal sprite pipeline
signal O_HSpritePipelineCtrl_Enable : std_logic := '0';
--@ Ready in signal for the **output** of the horizontal sprite pipeline
signal I_HSpritePipeline_Ready : std_logic := '0';
--@ Valid out signal for the **output** of the horizontal sprite pipeline
signal O_HSpritePipeline_Valid : std_logic := '0';
--@ Register for the `I_OP_Index` signal (Pipeline stage 1/2)
signal R_Index : std_logic_vector(G_Index_Width - 1 downto 0) := (others => '0');
--@ Register for the `I_OP_Offset` signal (Pipeline stage 1/2)
signal R_Offset : std_logic_vector(G_Offset_Width - 1 downto 0) := (others => '0');
--@ Register for the `I_OP_X_Request` signal (Pipeline stage 1)
signal R_X_Request : std_logic_vector(G_X_Width - 1 downto 0) := (others => '0');
--@ Register for the `I_OP_X_Sprite` signal (Pipeline stage 1)
signal R_X_Sprite : std_logic_vector(G_X_Width - 1 downto 0) := (others => '0');
--@ Calculated sprite X offset (Between pipeline stage 1 and 2)
signal C_Sprite_X_Offset : std_logic_vector(G_X_Width - 1 downto 0) := (others => '0');
--@ Register for the calculated sprite X offset (Pipeline stage 2)
signal R_Sprite_X_Offset : std_logic_vector(G_X_Width - 1 downto 0) := (others => '0');
--@ Calculated address for the ROM (Between pipeline stage 2 and 3)
signal C_Address : std_logic_vector(G_Index_Width + G_Offset_Width - 1 downto 0) := (others => '0');
--@ Register for the calculated address for the ROM (Pipeline stage 3)
signal R_Address : std_logic_vector(G_Index_Width + G_Offset_Width - 1 downto 0) := (others => '0');
--@ Calculated visibility signal (Between pipeline stage 2 and 3)
signal C_X_Visible : std_logic := '0';
--@ Register for the calculated visibility signal (Pipeline stage 3)
signal R_X_Visible : std_logic := '0';
--@ Valid signal for the ROM request (From pipeline stage 3 to 4; after filtering)
signal I_RomRequest_Valid : std_logic := '0';
--@ Ready signal for the ROM request (From pipeline stage 3 to 4; after filtering)
signal O_RomRequest_Ready : std_logic := '0';
begin
INST_HSpritePipelineCtrl : entity work.PipelineController
generic map(
G_PipelineStages => 3
)
port map(
I_CLK => I_CLK,
I_CE => I_CE,
O_Enable => O_HSpritePipelineCtrl_Enable,
I_Valid => I_OP_Valid,
O_Ready => O_OP_Ready,
O_Valid => O_HSpritePipeline_Valid,
I_Ready => I_HSpritePipeline_Ready
);
INST_HSpritePipeline_Index : entity work.PipelineRegister
generic map(
G_PipelineStages => 2,
G_Width => G_Index_Width,
G_RegisterBalancing => "forward"
)
port map(
I_CLK => I_CLK,
I_Enable => O_HSpritePipelineCtrl_Enable,
I_Data => I_OP_Index,
O_Data => R_Index
);
INST_HSpritePipeline_Offset : entity work.PipelineRegister
generic map(
G_PipelineStages => 2,
G_Width => G_Offset_Width,
G_RegisterBalancing => "forward"
)
port map(
I_CLK => I_CLK,
I_Enable => O_HSpritePipelineCtrl_Enable,
I_Data => I_OP_Offset,
O_Data => R_Offset
);
INST_HSpritePipeline_Request : entity work.PipelineRegister
generic map(
G_PipelineStages => 1,
G_Width => G_X_Width,
G_RegisterBalancing => "forward"
)
port map(
I_CLK => I_CLK,
I_Enable => O_HSpritePipelineCtrl_Enable,
I_Data => I_OP_X_Request,
O_Data => R_X_Request
);
INST_HSpritePipeline_X_Sprite : entity work.PipelineRegister
generic map(
G_PipelineStages => 1,
G_Width => G_X_Width,
G_RegisterBalancing => "forward"
)
port map(
I_CLK => I_CLK,
I_Enable => O_HSpritePipelineCtrl_Enable,
I_Data => I_OP_X_Sprite,
O_Data => R_X_Sprite
);
C_Sprite_X_Offset <= std_logic_vector(
unsigned(R_X_Request) - unsigned(R_X_Sprite)
);
INST_HSpritePipeline_X_Offset : entity work.PipelineRegister
generic map(
G_PipelineStages => 1,
G_Width => G_X_Width,
G_RegisterBalancing => "yes"
)
port map(
I_CLK => I_CLK,
I_Enable => O_HSpritePipelineCtrl_Enable,
I_Data => C_Sprite_X_Offset,
O_Data => R_Sprite_X_Offset
);
C_Address <= R_Index & std_logic_vector(
unsigned(R_Offset) + unsigned(R_Sprite_X_Offset(G_Offset_Width - 1 downto 0))
);
C_X_Visible <= '1' when unsigned(R_Sprite_X_Offset) < G_SpriteMaxWidth else
'0';
INST_HSpritePipeline_Address : entity work.PipelineRegister
generic map(
G_PipelineStages => 1,
G_Width => G_Index_Width + G_Offset_Width,
G_RegisterBalancing => "backward"
)
port map(
I_CLK => I_CLK,
I_Enable => O_HSpritePipelineCtrl_Enable,
I_Data => C_Address,
O_Data => R_Address
);
INST_HSpritePipeline_X_Visible : entity work.PipelineRegister
generic map(
G_PipelineStages => 1,
G_Width => 1,
G_RegisterBalancing => "backward"
)
port map(
I_CLK => I_CLK,
I_Enable => O_HSpritePipelineCtrl_Enable,
I_Data(0) => C_X_Visible,
O_Data(0) => R_X_Visible
);
INST_DropUnvisibleRequests : entity work.PipelineFilter
generic map(
G_MaskWidth => 1,
G_Mask => "1",
G_MaskMode => "not_equal"
)
port map(
I_Match(0) => R_X_Visible,
I_Valid => O_HSpritePipeline_Valid,
O_Ready => I_HSpritePipeline_Ready,
O_Valid => I_RomRequest_Valid,
I_Ready => O_RomRequest_Ready
);
INST_RomRequestBuffer : entity work.PipelineStage
generic map(
G_PipelineStages => 1,
G_D0_Width => G_Index_Width + G_Offset_Width
)
port map(
I_CLK => I_CLK,
I_CE => I_CE,
I_RST => I_RST,
I_Valid => I_RomRequest_Valid,
O_Ready => O_RomRequest_Ready,
I_Data_0 => R_Address,
O_Valid => O_Rom_Valid,
I_Ready => I_Rom_Ready,
O_Data_0 => O_Rom_Address
);
INST_PixelDataBuffer : entity work.PipelineStage
generic map(
G_PipelineStages => 1,
G_D0_Width => G_Pixel_Width
)
port map(
I_CLK => I_CLK,
I_CE => I_CE,
I_RST => I_RST,
I_Valid => I_Rom_Valid,
O_Ready => O_Rom_Ready,
I_Data_0 => I_Rom_Data,
O_Valid => O_Pixel_Valid,
I_Ready => I_Pixel_Ready,
O_Data_0 => O_Pixel_Data
);
end architecture;