Files
Arcade-Tecmo_MiSTer/rtl/gpu/scroll_layer.vhd
2020-06-15 18:49:22 +10:00

291 lines
9.0 KiB
VHDL

-- __ __ __ __ __ __
-- /\ "-.\ \ /\ \/\ \ /\ \ /\ \
-- \ \ \-. \ \ \ \_\ \ \ \ \____ \ \ \____
-- \ \_\\"\_\ \ \_____\ \ \_____\ \ \_____\
-- \/_/ \/_/ \/_____/ \/_____/ \/_____/
-- ______ ______ __ ______ ______ ______
-- /\ __ \ /\ == \ /\ \ /\ ___\ /\ ___\ /\__ _\
-- \ \ \/\ \ \ \ __< _\_\ \ \ \ __\ \ \ \____ \/_/\ \/
-- \ \_____\ \ \_____\ /\_____\ \ \_____\ \ \_____\ \ \_\
-- \/_____/ \/_____/ \/_____/ \/_____/ \/_____/ \/_/
--
-- https://joshbassett.info
-- https://twitter.com/nullobject
-- https://github.com/nullobject
--
-- Copyright (c) 2020 Josh Bassett
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.common.all;
use work.math.all;
use work.types.all;
-- The scroll module handles the scrolling foreground and background layers in
-- the graphics pipeline.
--
-- It consists of a 32x16 grid of 16x16 tiles. Each 16x16 tile is made up of
-- four separate 8x8 tiles, stored in a left-to-right, top-to-bottom order.
--
-- Each tile in the tilemap is represented by two bytes in the scroll RAM,
-- a high byte and a low byte, which contains the tile colour and code.
--
-- Because a scrolling layer is twice the width of the screen, it can never be
-- entirely visible on the screen at once. The horizontal and vertical scroll
-- positions are used to set the position of the visible area.
entity scroll_layer is
generic (
ROM_ADDR_WIDTH : natural;
ROM_DATA_WIDTH : natural
);
port (
-- reset
reset : in std_logic;
-- clock signals
clk : in std_logic;
cen : in std_logic;
-- configuration
config : in tile_config_t;
-- control signals
busy : buffer std_logic;
flip : in std_logic;
-- scroll RAM
ram_cs : in std_logic;
ram_we : in std_logic;
ram_addr : in unsigned(SCROLL_RAM_CPU_ADDR_WIDTH-1 downto 0);
ram_din : in byte_t;
ram_dout : out byte_t;
-- tile ROM
rom_addr : out unsigned(ROM_ADDR_WIDTH-1 downto 0);
rom_data : in std_logic_vector(ROM_DATA_WIDTH-1 downto 0);
-- video signals
video : in video_t;
-- scroll position
scroll_pos : in pos_t;
-- graphics data
data : out byte_t
);
end scroll_layer;
architecture arch of scroll_layer is
signal ram_addr_b : unsigned(SCROLL_RAM_GPU_ADDR_WIDTH-1 downto 0) := (others => '0');
signal ram_dout_b : std_logic_vector(SCROLL_RAM_GPU_DATA_WIDTH-1 downto 0);
signal ram_cs_rising : std_logic;
-- tile signals
signal tile : tile_t;
signal color : color_t;
signal pixel : pixel_t;
signal tile_row : row_t;
-- line buffer signals
signal line_buffer_swap : std_logic;
signal line_buffer_addr_a : unsigned(LINE_BUFFER_ADDR_WIDTH-1 downto 0);
signal line_buffer_din_a : std_logic_vector(LINE_BUFFER_DATA_WIDTH-1 downto 0);
signal line_buffer_we_a : std_logic;
signal line_buffer_addr_b : unsigned(LINE_BUFFER_ADDR_WIDTH-1 downto 0);
signal line_buffer_dout_b : std_logic_vector(LINE_BUFFER_DATA_WIDTH-1 downto 0);
-- flipped vertical position
signal flip_y : unsigned(7 downto 0);
-- destination position
signal dest_pos : pos_t;
-- aliases to extract the components of the horizontal and vertical position
alias col : unsigned(4 downto 0) is dest_pos.x(8 downto 4);
alias row : unsigned(3 downto 0) is dest_pos.y(7 downto 4);
alias offset_x : unsigned(3 downto 0) is dest_pos.x(3 downto 0);
alias offset_y : unsigned(3 downto 0) is dest_pos.y(3 downto 0);
begin
-- The scroll RAM (1kB) contains the code and colour of each tile in the
-- tilemap.
scroll_ram : entity work.true_dual_port_ram
generic map (
ADDR_WIDTH_A => SCROLL_RAM_CPU_ADDR_WIDTH,
ADDR_WIDTH_B => SCROLL_RAM_GPU_ADDR_WIDTH,
DATA_WIDTH_B => SCROLL_RAM_GPU_DATA_WIDTH
)
port map (
-- CPU interface
clk_a => clk,
cs_a => ram_cs,
we_a => ram_we and not busy,
addr_a => ram_addr,
din_a => ram_din,
dout_a => ram_dout,
-- GPU interface
clk_b => clk,
addr_b => ram_addr_b,
dout_b => ram_dout_b
);
-- A line buffer is used to cache pixel data for the next scanline.
--
-- It is not present in the original arcade hardware, but it hugely
-- simplifies screen flipping because you only have to reverse the line
-- buffer, instead of having to decode tiles in reverse.
line_buffer : entity work.line_buffer
generic map (
ADDR_WIDTH => LINE_BUFFER_ADDR_WIDTH,
DATA_WIDTH => LINE_BUFFER_DATA_WIDTH
)
port map (
clk => clk,
swap => line_buffer_swap,
-- port A (write)
addr_a => line_buffer_addr_a,
din_a => line_buffer_din_a,
we_a => line_buffer_we_a,
-- port B (read)
addr_b => line_buffer_addr_b,
dout_b => line_buffer_dout_b
);
-- detect rising edges of the RAM_CS signal
ram_cs_edge_detector : entity work.edge_detector
generic map (RISING => true)
port map (
clk => clk,
data => video.enable,
q => ram_cs_rising
);
-- update position counter
update_pos_counter : process (clk)
begin
if rising_edge(clk) then
if cen = '1' then
if video.hsync = '1' then
dest_pos.x <= scroll_pos.x-4;
else
dest_pos.x <= dest_pos.x+1;
end if;
end if;
end if;
end process;
-- Load tile data from the scroll RAM.
--
-- While the current tile is being rendered, we need to fetch data for the
-- next tile ahead, so that it is loaded in time to render it on the screen.
--
-- The 16-bit tile data words aren't stored contiguously in RAM, instead they
-- are split into high and low bytes. The high bytes are stored in the
-- upper-half of the RAM, while the low bytes are stored in the lower-half.
--
-- We latch the tile code well before the end of the row, to allow the GPU
-- enough time to fetch pixel data from the tile ROM.
tile_data_pipeline : process (clk)
begin
if rising_edge(clk) then
if cen = '1' then
case to_integer(offset_x) is
when 7 =>
-- latch row data
tile_row <= rom_data;
when 8 =>
-- load next tile
ram_addr_b <= row & (col+1);
when 9 =>
-- latch tile
tile <= decode_tile(config, ram_dout_b);
when 15 =>
-- latch row data
tile_row <= rom_data;
-- latch tile color
color <= tile.color;
when others => null;
end case;
end if;
end if;
end process;
-- latch graphics data from the line buffer
latch_gfx_data : process (clk)
begin
if rising_edge(clk) then
if cen = '1' then
data <= line_buffer_dout_b;
end if;
end if;
end process;
-- The busy signal is asserted when the CPU tries to access the VRAM. It is
-- deasserted after the tile data has been latched.
latch_busy : process (clk, reset, offset_x)
begin
if reset = '1' or to_integer(offset_x) = 9 then
busy <= '0';
elsif rising_edge(clk) then
if cen = '1' then
if ram_cs_rising = '1' then
busy <= '1';
end if;
end if;
end if;
end process;
-- set flipped vertical position to be one scanline ahead/behind
flip_y <= (not video.pos.y(7 downto 0))-1 when flip = '1' else
video.pos.y(7 downto 0)+1;
-- set destination position
dest_pos.y <= resize(scroll_pos.y+flip_y, dest_pos.y'length);
-- set tile ROM address
rom_addr <= tile.code & offset_y(3) & (not offset_x(3)) & offset_y(2 downto 0);
-- select pixel from the tile row data
pixel <= select_pixel(tile_row, offset_x(2 downto 0));
-- swap line buffer on alternating rows
line_buffer_swap <= video.pos.y(0);
-- write line buffer
line_buffer_addr_a <= not video.pos.x(7 downto 0) when flip = '1' else video.pos.x(7 downto 0);
line_buffer_din_a <= color & pixel;
line_buffer_we_a <= not video.hblank;
-- read line buffer two pixels ahead
line_buffer_addr_b <= video.pos.x(7 downto 0)+2;
end architecture arch;