1
0
mirror of https://github.com/mist-devel/mist-board.git synced 2026-02-16 04:12:43 +00:00
Files
mist-devel.mist-board/cores/spectrum/video.vhd
2015-12-04 09:46:27 +01:00

413 lines
13 KiB
VHDL
Executable File

-- ZX Spectrum for Altera DE1
--
-- Copyright (c) 2009-2011 Mike Stirling
--
-- All rights reserved
--
-- Redistribution and use in source and synthezised forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- * Redistributions in synthesized form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- * Neither the name of the author nor the names of other contributors may
-- be used to endorse or promote products derived from this software without
-- specific prior written agreement from the author.
--
-- * License is granted for non-commercial use only. A fee may not be charged
-- for redistributions as source code or in synthesized/hardware form without
-- specific prior written agreement from the author.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity video is
port(
-- Master clock (28 MHz)
CLK : in std_logic;
-- Video domain clock enable (14 MHz)
CLKEN : in std_logic;
--
MEM_CYC : in std_logic;
-- Master reset
nRESET : in std_logic;
-- Mode
VGA : in std_logic;
-- Memory interface
VID_A : out std_logic_vector(12 downto 0);
VID_D_IN : in std_logic_vector(7 downto 0);
nVID_RD : out std_logic;
nWAIT : out std_logic;
-- IO interface
BORDER_IN : in std_logic_vector(2 downto 0);
-- Video outputs
R : out std_logic_vector(3 downto 0);
G : out std_logic_vector(3 downto 0);
B : out std_logic_vector(3 downto 0);
nVSYNC : out std_logic;
nHSYNC : out std_logic;
nCSYNC : out std_logic;
nHCSYNC : out std_logic;
SCANLINE : out std_logic;
-- Interrupt to CPU (asserted for 32 T-states, 64 ticks)
nIRQ : out std_logic
);
end video;
architecture video_arch of video is
signal pixels : std_logic_vector(9 downto 0);
signal attr : std_logic_vector(7 downto 0);
-- additional buffer used in non-VGA mode (TV) to store the pixels/attr a little
-- bit ahead of time to not interfere with cpu ram access
signal pixels_tv : std_logic_vector(7 downto 0);
signal attr_tv : std_logic_vector(7 downto 0);
-- Video logic runs at 14 MHz so hcounter has an additonal LSb which is
-- skipped if running in VGA scan-doubled mode. The value of this
-- extra bit is 1/2 for the purposes of timing calculations bit 1 is
-- assumed to have a value of 1.
signal hcounter : std_logic_vector(9 downto 0);
-- vcounter has an extra LSb as well except this is skipped if running
-- in PAL mode. By not skipping it in VGA mode we get the required
-- double-scanning of each line. This extra bit has a value 1/2 as well.
signal vcounter : std_logic_vector(9 downto 0);
signal flashcounter : std_logic_vector(4 downto 0);
signal vblanking : std_logic;
signal hblanking : std_logic;
signal hpicture : std_logic;
signal vpicture : std_logic;
signal picture : std_logic;
signal blanking : std_logic;
signal hsync : std_logic;
signal vsync : std_logic;
signal red : std_logic;
signal green : std_logic;
signal blue : std_logic;
signal bright : std_logic;
signal dot : std_logic;
begin
-- The first 256 pixels of each line are valid picture
picture <= hpicture and vpicture;
blanking <= hblanking or vblanking;
-- Output syncs
-- drive VSYNC to 1 in PAL mode for Minimig VGA cable
nVSYNC <= not vsync;
nHSYNC <= not hsync;
nCSYNC <= not (vsync xor hsync);
-- Combined HSYNC/CSYNC. Feeds HSYNC to VGA HSYNC in VGA mode,
-- or CSYNC to the same pin in PAL mode
nHCSYNC <= not (vsync xor hsync) when VGA = '0' else
not hsync;
SCANLINE <= vcounter(0);
-- Determine the pixel colour
dot <= pixels(9) xor (flashcounter(4) and attr(7)); -- Combine delayed pixel with FLASH attr and clock state
red <= attr(1) when picture = '1' and dot = '1' else
attr(4) when picture = '1' and dot = '0' else
BORDER_IN(1) when blanking = '0' else
'0';
green <= attr(2) when picture = '1' and dot = '1' else
attr(5) when picture = '1' and dot = '0' else
BORDER_IN(2) when blanking = '0' else
'0';
blue <= attr(0) when picture = '1' and dot = '1' else
attr(3) when picture = '1' and dot = '0' else
BORDER_IN(0) when blanking = '0' else
'0';
bright <= attr(6) when picture = '1' else
'0';
-- Re-register video output to DACs to clean up edges
process(nRESET,CLK)
begin
if nRESET = '0' then
-- Asynchronous clear
R <= (others => '0');
G <= (others => '0');
B <= (others => '0');
elsif rising_edge(CLK) then
-- Output video to DACs
R <= (3 => red, others => bright and red);
G <= (3 => green, others => bright and green);
B <= (3 => blue, others => bright and blue);
end if;
end process;
-- This is what the contention model is supposed to look like.
-- We may need to emulate this to ensure proper compatibility.
--
-- At vcounter = 0 and hcounter = 0 we are at
-- 14336*T since the falling edge of the vsync.
-- This is where we start contending RAM access.
-- The contention pattern repeats every 8 T states, with
-- CPU clock held during the first 6 of every 8 T states
-- (where one T state is two ticks of the horizontal counter).
-- Two screen bytes are fetched consecutively, display first
-- followed by attribute. The cycle looks like this:
-- hcounter[3..1] = 000 Fetch data 1 nWAIT = 0
-- 001 Fetch attr 1 0
-- 010 Fetch data 2 0
-- 011 Fetch attr 2 0
-- 100 1
-- 101 1
-- 110 0
-- 111 0
-- What we actually do is the following, interleaved with CPU RAM access
-- so that we don't need any contention:
-- hcounter[2..0] = 000 Fetch data (LOAD)
-- 001 Fetch data (STORE)
-- 010 Fetch attr (LOAD)
-- 011 Fetch attr (STORE)
-- 100 Idle
-- 101 Idle
-- 110 Idle
-- 111 Idle
-- The load/store pairs take place over two clock enables. In VGA mode
-- there is one picture/attribute pair fetch per CPU clock enable. In PAL
-- mode every other tick is ignored, so the picture/attribute fetches occur
-- on alternate CPU clocks. At no time must a CPU cycle be allowed to split
-- a LOAD/STORE pair, as the bus routing logic will disconnect the memory from
-- the CPU during this time.
-- RAM address is generated continuously from the counter values
-- Pixel fetch takes place when hcounter(2) = 0, attribute when = 1
VID_A(12 downto 0) <=
-- Picture
vcounter(8 downto 7) & vcounter(3 downto 1) & vcounter(6 downto 4) & hcounter(8 downto 4)
when (VGA = '1' and hcounter(2) = '0') or (VGA = '0' and hcounter(1) = '0') else
-- Attribute
"110" & vcounter(8 downto 7) & vcounter(6 downto 4) & hcounter(8 downto 4);
-- This timing model is completely uncontended. CPU runs all the time.
nWAIT <= '1';
-- First 192 lines are picture
vpicture <= not (vcounter(9) or (vcounter(8) and vcounter(7)));
process(nRESET,CLK,CLKEN,hcounter,vcounter)
begin
if nRESET = '0' then
-- Asynchronous master reset
hcounter <= (others => '0');
vcounter <= (others => '0');
flashcounter <= (others => '0');
vblanking <= '0';
hblanking <= '0';
hpicture <= '1';
hsync <= '0';
vsync <= '0';
nIRQ <= '1';
nVID_RD <= '1';
pixels <= (others => '0');
attr <= (others => '0');
elsif rising_edge(CLK) and CLKEN = '1' then
-- activate nVID_RD in advance of pixel and attribute read so data
-- is present in time. This is needed for the SDRAM which is operated at
-- much lower speed than the orignal SRAM in the DE1/DE2
if blanking='0' and
((hcounter(3 downto 1) = "111") or
(hcounter(3 downto 1) = "000") or
(hcounter(3 downto 1) = "001") or
(hcounter(3 downto 1) = "010")) then
nVID_RD <= '0';
else
nVID_RD <= '1';
end if;
-- Most functions are only performed when hcounter(0) is clear.
-- This is the 'half' bit inserted to allow for scan-doubled VGA output.
-- In VGA mode the counter will be stepped through the even values only,
-- so the rest of the logic remains the same.
if vpicture = '1' and hcounter(0) = '1' then
-- Pump pixel shift register - this is two pixels longer
-- than a byte to delay the pixels back into alignment with
-- the attribute byte, stored two ticks later
pixels(9 downto 1) <= pixels(8 downto 0);
-- in TV mode everything happens a little slower. Fetch data ahead of
-- time to have the same memory timing as VGA
if hcounter(9) = '0' and hcounter(3) = '0' then
if hcounter(2) = '0' then
if hcounter(1) = '0' then
pixels_tv <= VID_D_IN;
else
attr_tv <= VID_D_IN;
end if;
end if;
end if;
if hcounter(9) = '0' and hcounter(3) = '0' then
-- Handle the fetch cycle
-- 3210
-- 0000 PICTURE LOAD
-- 0010 PICTURE STORE
-- 0100 ATTR LOAD
-- 0110 ATTR STORE
if hcounter(1) = '1' then
-- STORE
if hcounter(2) = '0' then
-- PICTURE
if(VGA = '1') then
pixels(7 downto 0) <= VID_D_IN;
else
pixels(7 downto 0) <= pixels_tv;
end if;
else
-- ATTR
if(VGA = '1') then
attr <= VID_D_IN;
else
attr <= attr_tv;
end if;
end if;
end if;
end if;
-- Delay horizontal picture enable until the end of the first fetch cycle
-- This also allows for the re-registration of the outputs
if hcounter(9) = '0' and hcounter(2 downto 1) = "11" then
hpicture <= '1';
end if;
if hcounter(9) = '1' and hcounter(2 downto 1) = "11" then
hpicture <= '0';
end if;
end if;
-- Step the horizontal counter and check for wrap
if VGA = '1' then
-- Counter wraps after 894 in VGA mode
if hcounter = "1101111111" then
if MEM_CYC = '1' then
hcounter <= (others => '0');
-- Increment vertical counter by ones for VGA so that
-- lines are double-scanned
vcounter <= vcounter + '1';
end if;
else
-- Increment horizontal counter
-- Even values only for VGA mode
hcounter <= hcounter + "10";
end if;
hcounter(0) <= '1';
else
-- Counter wraps after 895 in PAL mode
if hcounter = "1101111111" then
if MEM_CYC = '1' then
hcounter <= (others => '0');
-- Increment vertical counter by even values for PAL
vcounter <= vcounter + "10";
vcounter(0) <= '0';
end if;
else
-- Increment horizontal counter
-- All values for PAL mode
hcounter <= hcounter + '1';
end if;
end if;
--------------------
-- HORIZONTAL
--------------------
-- Each line comprises the following:
-- 256 pixels of active image
-- 48 pixels right border
-- 24 pixels front porch
-- 32 pixels sync
-- 40 pixels back porch
-- 48 pixels left border
-- Generate timing signals during inactive region
-- (when hcounter(9) = 1)
case hcounter(9 downto 4) is
-- Blanking starts at 304
when "100110" => hblanking <= '1';
-- Sync starts at 328
when "101001" => hsync <= '1';
-- Sync ends at 360
when "101101" => hsync <= '0';
-- Blanking ends at 400
when "110010" => hblanking <= '0';
when others =>
null;
end case;
-- Clear interrupt after 32T
if hcounter(7) = '1' then
nIRQ <= '1';
end if;
----------------
-- VERTICAL
----------------
case vcounter(9 downto 3) is
when "0111110" =>
-- Start of blanking and vsync(line 248)
vblanking <= '1';
vsync <= '1';
-- Assert vsync interrupt
nIRQ <= '0';
when "0111111" =>
-- End of vsync after 4 lines (line 252)
vsync <= '0';
when "1000000" =>
-- End of blanking and start of top border (line 256)
-- Should be line 264 but this is simpler and doesn't really make
-- any difference
vblanking <= '0';
when others =>
null;
end case;
-- Wrap vertical counter at line 312-1,
-- Top counter value is 623 for VGA, 622 for PAL
if vcounter(9 downto 1) = "100110111" then
if ((VGA = '1' and vcounter(0) = '1' and hcounter = "1101111111") or
(VGA = '0' and hcounter = "1101111111")) then
-- Start of picture area
vcounter <= (others => '0');
-- Increment the flash counter once per frame
flashcounter <= flashcounter + '1';
end if;
end if;
end if;
end process;
end video_arch;