---------------------------------------------------------------------------------- --@ - 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;