From 27b39499095ccb7e7e73a78776946b60fc96fb05 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 Feb 2023 17:13:06 +0100 Subject: PPU background sprite component done (but unverified) --- basys3/basys3.srcs/ppu_consts.vhd | 4 +- basys3/basys3.srcs/ppu_sprite_bg.vhd | 150 ++++++++++++++++++++++++++++ basys3/basys3.srcs/ppu_sprite_bg_tb.vhd | 86 ++++++++++++++++ basys3/basys3.srcs/ppu_sprite_transform.vhd | 24 +++++ basys3/basys3.xpr | 16 ++- 5 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 basys3/basys3.srcs/ppu_sprite_bg.vhd create mode 100644 basys3/basys3.srcs/ppu_sprite_bg_tb.vhd create mode 100644 basys3/basys3.srcs/ppu_sprite_transform.vhd diff --git a/basys3/basys3.srcs/ppu_consts.vhd b/basys3/basys3.srcs/ppu_consts.vhd index f4d428e..89b7a7a 100644 --- a/basys3/basys3.srcs/ppu_consts.vhd +++ b/basys3/basys3.srcs/ppu_consts.vhd @@ -21,12 +21,14 @@ package ppu_consts is constant PPU_POS_V_WIDTH : natural := 8; -- amount of bits for vertical screen offset constant PPU_SPRITE_WIDTH : natural := 16; -- sprite width (pixels) constant PPU_SPRITE_HEIGHT : natural := 16; -- sprite height (pixels) + constant PPU_SPRITE_POS_H_WIDTH: natural := 4; -- bits needed to identify horizontal pixel within sprite + constant PPU_SPRITE_POS_V_WIDTH: natural := 4; -- bits needed to identify vertical pixel within sprite constant PPU_SCREEN_WIDTH : natural := 320; -- absolute screen width (pixels) constant PPU_SCREEN_HEIGHT : natural := 240; -- absolute screen height (pixels) constant PPU_BG_CANVAS_TILES_H : natural := 40; -- amount of tiles (horizontally) on background canvas constant PPU_BG_CANVAS_TILES_V : natural := 30; -- amount of tiles (vertically) on background canvas constant PPU_TILE_INDEX_WIDTH : natural := 10; -- amount of bits needed to index a tile from TMM memory constant PPU_PIXELS_PER_TILE_WORD : natural := 5; -- amount of pixels defined in one word in TMM memory - constant PPU_TILE_SIZE : natural := 48; + constant PPU_SPRITE_PIXELS_PER_WORD : natural := 52; -- amount of words needed for a single sprite end package ppu_consts; diff --git a/basys3/basys3.srcs/ppu_sprite_bg.vhd b/basys3/basys3.srcs/ppu_sprite_bg.vhd new file mode 100644 index 0000000..a92c401 --- /dev/null +++ b/basys3/basys3.srcs/ppu_sprite_bg.vhd @@ -0,0 +1,150 @@ +library ieee; +library work; + +use ieee.std_logic_1164.all; +use ieee.std_logic_unsigned.all; +use ieee.numeric_std.all; +use work.ppu_consts.all; + +entity ppu_sprite_bg is port( + -- inputs + CLK : in std_logic; -- pipeline clock + RESET : in std_logic; -- reset clock counter + OE : in std_logic; -- output enable (of CIDX) + X : in std_logic_vector(PPU_POS_H_WIDTH-1 downto 0); -- current screen pixel x + Y : in std_logic_vector(PPU_POS_V_WIDTH-1 downto 0); -- current screen pixel y + + -- aux inputs + BG_SHIFT_X : in std_logic_vector(PPU_POS_H_WIDTH-1 downto 0); + BG_SHIFT_Y : in std_logic_vector(PPU_POS_V_WIDTH-1 downto 0); + + -- used memory blocks + BAM_ADDR : out std_logic_vector(PPU_BAM_ADDR_WIDTH-1 downto 0); + BAM_DATA : in std_logic_vector(PPU_BAM_DATA_WIDTH-1 downto 0); + TMM_ADDR : out std_logic_vector(PPU_TMM_ADDR_WIDTH-1 downto 0); + TMM_DATA : in std_logic_vector(PPU_TMM_DATA_WIDTH-1 downto 0); + + -- outputs + CIDX : out std_logic_vector(PPU_PALETTE_CIDX_WIDTH-1 downto 0)); -- output color +end ppu_sprite_bg; + +architecture Behavioral of ppu_sprite_bg is + component ppu_sprite_transform port( + XI : in std_logic_vector(PPU_SPRITE_POS_H_WIDTH-1 downto 0); -- pixel position relative to tile + YI : in std_logic_vector(PPU_SPRITE_POS_V_WIDTH-1 downto 0); -- pixel position relative to tile + FLIP_H, FLIP_V : in std_logic; -- flip sprite + XO : out std_logic_vector(PPU_SPRITE_POS_H_WIDTH-1 downto 0); -- new pixel position relative to tile + YO : out std_logic_vector(PPU_SPRITE_POS_V_WIDTH-1 downto 0)); -- new pixel position relative to tile + end component; + + signal O_BAM_ADDR : std_logic_vector(PPU_BAM_ADDR_WIDTH-1 downto 0) := (others => '0'); + signal I_BAM_DATA : std_logic_vector(PPU_BAM_DATA_WIDTH-1 downto 0); + signal O_TMM_ADDR : std_logic_vector(PPU_TMM_ADDR_WIDTH-1 downto 0) := (others => '0'); + signal I_TMM_DATA : std_logic_vector(PPU_TMM_DATA_WIDTH-1 downto 0); + + signal FLIP_H, FLIP_V : std_logic := '0'; + signal TMM_IDX : std_logic_vector(PPU_TILE_INDEX_WIDTH-1 downto 0) := (others => '0'); + + alias BAM_DATA_COL_IDX is I_BAM_DATA(12 downto 10); + signal TMM_DATA_PAL_IDX : std_logic_vector(PPU_PALETTE_COLOR_WIDTH-1 downto 0); + signal O_CIDX : std_logic_vector(PPU_PALETTE_CIDX_WIDTH-1 downto 0) := (others => '0'); + + type states is (PL_BAM_ADDR, PL_BAM_DATA, PL_TMM_ADDR, PL_TMM_DATA); + signal state, next_state : states := PL_BAM_ADDR; + + -- docs/architecture.md#background-attribute-memory + alias BAM_DATA_FLIP_H is I_BAM_DATA(14); + alias BAM_DATA_FLIP_V is I_BAM_DATA(13); + alias BAM_DATA_TILE_IDX is I_BAM_DATA(9 downto 0); + + signal PIXEL_ABS_X, PIXEL_ABS_Y : integer := 0; -- absolute pixel position (relative to BG canvas instead of viewport) + signal TILE_IDX_X, TILE_IDX_Y : integer := 0; -- background canvas tile grid xy + signal TILE_PIXEL_IDX_X, TILE_PIXEL_IDX_Y : integer := 0; -- xy position of pixel within tile + signal TILE_PIXEL_IDX : integer := 0; -- index of pixel within tile + signal TRANS_TILE_PIDX_X, TRANS_TILE_PIDX_Y : integer := 0; -- transformed xy position of pixel within tile + signal TILEMAP_WORD_OFFSET : integer := 0; + signal TRANSFORM_XI, TRANSFORM_XO : std_logic_vector(PPU_SPRITE_POS_H_WIDTH-1 downto 0); + signal TRANSFORM_YI, TRANSFORM_YO : std_logic_vector(PPU_SPRITE_POS_V_WIDTH-1 downto 0); + signal PIXEL_BIT_OFFSET : integer := 0; +begin + -- CIDX tri-state driver + CIDX <= O_CIDX when OE = '1' else (others => 'Z'); + + -- internal line separators + FLIP_H <= BAM_DATA_FLIP_H; + FLIP_V <= BAM_DATA_FLIP_V; + O_CIDX <= BAM_DATA_COL_IDX & TMM_DATA_PAL_IDX; + + -- BAM ADDR + PIXEL_ABS_X <= to_integer(unsigned(X)) + to_integer(unsigned(BG_SHIFT_X)); + PIXEL_ABS_Y <= to_integer(unsigned(Y)) + to_integer(unsigned(BG_SHIFT_Y)); + TILE_IDX_X <= PIXEL_ABS_X / 16; + TILE_IDX_Y <= PIXEL_ABS_Y / 16; + TILE_PIXEL_IDX_X <= PIXEL_ABS_X - TILE_IDX_X * 16; + TILE_PIXEL_IDX_Y <= PIXEL_ABS_Y - TILE_IDX_Y * 16; + O_BAM_ADDR <= std_logic_vector(to_unsigned((TILE_IDX_Y * integer(PPU_BG_CANVAS_TILES_H)) + TILE_IDX_X, PPU_BAM_ADDR_WIDTH)); + + -- BAM DATA + FAM ADDR + TRANSFORM_XI <= std_logic_vector(to_unsigned(TILE_PIXEL_IDX_X, PPU_SPRITE_POS_H_WIDTH)); + TRANSFORM_YI <= std_logic_vector(to_unsigned(TILE_PIXEL_IDX_Y, PPU_SPRITE_POS_V_WIDTH)); + transform: component ppu_sprite_transform port map( + XI => TRANSFORM_XI, + YI => TRANSFORM_YI, + FLIP_H => FLIP_H, + FLIP_V => FLIP_V, + XO => TRANSFORM_XO, + YO => TRANSFORM_YO); + TRANS_TILE_PIDX_X <= to_integer(unsigned(TRANSFORM_XO)); + TRANS_TILE_PIDX_Y <= to_integer(unsigned(TRANSFORM_YO)); + + TILE_PIXEL_IDX <= integer(PPU_SPRITE_WIDTH) * TRANS_TILE_PIDX_Y + TRANS_TILE_PIDX_X; + TILEMAP_WORD_OFFSET <= TILE_PIXEL_IDX / PPU_PIXELS_PER_TILE_WORD; + PIXEL_BIT_OFFSET <= TILE_PIXEL_IDX mod PPU_PIXELS_PER_TILE_WORD; + + O_TMM_ADDR <= std_logic_vector(to_unsigned(PPU_SPRITE_PIXELS_PER_WORD * to_integer(unsigned(BAM_DATA_TILE_IDX)) + TILEMAP_WORD_OFFSET, PPU_TMM_ADDR_WIDTH)); + + -- TMM DATA + with PIXEL_BIT_OFFSET select + TMM_DATA_PAL_IDX <= TMM_DATA(2 downto 0) when 0, + TMM_DATA(5 downto 3) when 1, + TMM_DATA(8 downto 6) when 2, + TMM_DATA(11 downto 9) when 3, + TMM_DATA(14 downto 12) when 4, + (others => '0') when others; + + -- state machine (pipeline stage counter) + fsm: process(CLK, RESET) + begin + if RESET = '1' then + state <= PL_BAM_ADDR; + elsif rising_edge(CLK) then + state <= next_state; + end if; + end process; + + -- sync read/write + process(state) + begin + next_state <= state; + case state is + when PL_BAM_ADDR => + next_state <= PL_BAM_DATA; + BAM_ADDR <= O_BAM_ADDR; + + when PL_BAM_DATA => + next_state <= PL_TMM_ADDR; + I_BAM_DATA <= BAM_DATA; + + when PL_TMM_ADDR => + next_state <= PL_TMM_DATA; + TMM_ADDR <= O_TMM_ADDR; + + when PL_TMM_DATA => + next_state <= PL_BAM_ADDR; + I_TMM_DATA <= TMM_DATA; + + + end case; + end process; + +end Behavioral; diff --git a/basys3/basys3.srcs/ppu_sprite_bg_tb.vhd b/basys3/basys3.srcs/ppu_sprite_bg_tb.vhd new file mode 100644 index 0000000..2dee2fe --- /dev/null +++ b/basys3/basys3.srcs/ppu_sprite_bg_tb.vhd @@ -0,0 +1,86 @@ +library ieee; +library work; +library unisim; + +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; +use unisim.vcomponents.all; +use work.ppu_consts.all; + +entity ppu_sprite_bg_tb is +end ppu_sprite_bg_tb; + +architecture Behavioral of ppu_sprite_bg_tb is + component ppu_sprite_bg port( + -- inputs + CLK : in std_logic; -- pipeline clock + RESET : in std_logic; -- reset clock counter + OE : in std_logic; -- output enable (of CIDX) + X : in std_logic_vector(PPU_POS_H_WIDTH-1 downto 0); -- current screen pixel x + Y : in std_logic_vector(PPU_POS_V_WIDTH-1 downto 0); -- current screen pixel y + + -- aux inputs + BG_SHIFT_X : in std_logic_vector(PPU_POS_H_WIDTH-1 downto 0); + BG_SHIFT_Y : in std_logic_vector(PPU_POS_V_WIDTH-1 downto 0); + + -- used memory blocks + BAM_ADDR : out std_logic_vector(PPU_BAM_ADDR_WIDTH-1 downto 0); + BAM_DATA : in std_logic_vector(PPU_BAM_DATA_WIDTH-1 downto 0); + TMM_ADDR : out std_logic_vector(PPU_TMM_ADDR_WIDTH-1 downto 0); + TMM_DATA : in std_logic_vector(PPU_TMM_DATA_WIDTH-1 downto 0); + + -- outputs + CIDX : out std_logic_vector(PPU_PALETTE_CIDX_WIDTH-1 downto 0)); -- output color + end component; + signal CLK : std_logic := '0'; -- pipeline clock + signal RESET : std_logic := '0'; -- reset clock counter + signal OE : std_logic := '0'; -- output enable (of CIDX) + signal X : std_logic_vector(PPU_POS_H_WIDTH-1 downto 0) := (others => '0'); -- current screen pixel x + signal Y : std_logic_vector(PPU_POS_V_WIDTH-1 downto 0) := (others => '0'); -- current screen pixel y + signal BG_SHIFT_X : std_logic_vector(PPU_POS_H_WIDTH-1 downto 0) := (others => '0'); + signal BG_SHIFT_Y : std_logic_vector(PPU_POS_V_WIDTH-1 downto 0) := (others => '0'); + signal BAM_ADDR : std_logic_vector(PPU_BAM_ADDR_WIDTH-1 downto 0); + signal BAM_DATA : std_logic_vector(PPU_BAM_DATA_WIDTH-1 downto 0) := (others => '0'); + signal TMM_ADDR : std_logic_vector(PPU_TMM_ADDR_WIDTH-1 downto 0); + signal TMM_DATA : std_logic_vector(PPU_TMM_DATA_WIDTH-1 downto 0) := (others => '0'); + signal CIDX : std_logic_vector(PPU_PALETTE_CIDX_WIDTH-1 downto 0); -- output color +begin + uut : ppu_sprite_bg port map( + CLK => CLK, + RESET => RESET, + OE => OE, + X => X, + Y => Y, + BG_SHIFT_X => BG_SHIFT_X, + BG_SHIFT_Y => BG_SHIFT_Y, + BAM_ADDR => BAM_ADDR, + BAM_DATA => BAM_DATA, + TMM_ADDR => TMM_ADDR, + TMM_DATA => TMM_DATA, + CIDX => CIDX); + + BAM_DATA <= std_logic_vector(to_unsigned(16#4814#, PPU_BAM_DATA_WIDTH)); -- hex((1 << 14) | (0 << 13) | (2 << 10) | (20 << 0)) + TMM_DATA <= std_logic_vector(to_unsigned(16#ffff#, PPU_TMM_DATA_WIDTH)); + X <= std_logic_vector(to_unsigned(25, PPU_POS_H_WIDTH)); + Y <= std_logic_vector(to_unsigned(60, PPU_POS_V_WIDTH)); + BG_SHIFT_X <= std_logic_vector(to_unsigned(3, PPU_POS_H_WIDTH)); + BG_SHIFT_Y <= std_logic_vector(to_unsigned(3, PPU_POS_V_WIDTH)); + + tb : process + begin + for i in 0 to 32 loop + if i > 10 then + OE <= '1'; + end if; + if i > 20 then + RESET <= '1'; + end if; + + wait for 5 ns; + CLK <= '1'; + wait for 5 ns; + CLK <= '0'; + end loop; + wait; -- stop for simulator + end process; +end Behavioral; diff --git a/basys3/basys3.srcs/ppu_sprite_transform.vhd b/basys3/basys3.srcs/ppu_sprite_transform.vhd new file mode 100644 index 0000000..d1b1a23 --- /dev/null +++ b/basys3/basys3.srcs/ppu_sprite_transform.vhd @@ -0,0 +1,24 @@ +library ieee; +library work; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; +use work.ppu_consts.all; + +entity ppu_sprite_transform is port( -- flip sprites + XI : in std_logic_vector(PPU_SPRITE_POS_H_WIDTH-1 downto 0); -- pixel position relative to tile + YI : in std_logic_vector(PPU_SPRITE_POS_V_WIDTH-1 downto 0); -- pixel position relative to tile + FLIP_H, FLIP_V : in std_logic; -- flip sprite + XO : out std_logic_vector(PPU_SPRITE_POS_H_WIDTH-1 downto 0); -- new pixel position relative to tile + YO : out std_logic_vector(PPU_SPRITE_POS_V_WIDTH-1 downto 0)); -- new pixel position relative to tile +end ppu_sprite_transform; + +architecture Behavioral of ppu_sprite_transform is + signal FLIPPED_X : std_logic_vector(PPU_SPRITE_POS_H_WIDTH-1 downto 0); + signal FLIPPED_Y : std_logic_vector(PPU_SPRITE_POS_V_WIDTH-1 downto 0); +begin + FLIPPED_X <= std_logic_vector(to_unsigned(PPU_SPRITE_WIDTH-1 - to_integer(unsigned(XI)), PPU_SPRITE_POS_H_WIDTH)); + FLIPPED_Y <= std_logic_vector(to_unsigned(PPU_SPRITE_HEIGHT-1 - to_integer(unsigned(YI)), PPU_SPRITE_POS_V_WIDTH)); + + XO <= FLIPPED_X when FLIP_H = '1' else XI; + YO <= FLIPPED_Y when FLIP_V = '1' else YI; +end Behavioral; diff --git a/basys3/basys3.xpr b/basys3/basys3.xpr index 8dcc698..556c73e 100644 --- a/basys3/basys3.xpr +++ b/basys3/basys3.xpr @@ -61,7 +61,7 @@