Apple-II_MiSTer/vga_controller.vhd

315 lines
9.8 KiB
VHDL

-------------------------------------------------------------------------------
--
-- A VGA line-doubler for an Apple ][
--
-- Stephen A. Edwards, sedwards@cs.columbia.edu
--
--
-- FIXME: This is all wrong
--
-- The Apple ][ uses a 14.31818 MHz master clock. It outputs a new
-- horizontal line every 65 * 14 + 2 = 912 14M cycles. The extra two
-- are from the "extended cycle" used to keep the 3.579545 MHz
-- colorburst signal in sync. Of these, 40 * 14 = 560 are active video.
--
-- In graphics mode, the Apple effectively generates 140 four-bit pixels
-- output serially (i.e., with 3.579545 MHz pixel clock). In text mode,
-- it generates 280 one-bit pixels (i.e., with a 7.15909 MHz pixel clock).
--
-- We capture 140 four-bit nibbles for each line and interpret them in
-- one of the two modes. In graphics mode, each is displayed as a
-- single pixel of one of 16 colors. In text mode, each is displayed
-- as two black or white pixels.
--
-- The VGA display is nominally 640 X 480, but we use a 14.31818 MHz
-- dot clock. To stay in sync with the Apple, we generate a new line
-- every 912 / 2 = 456 14M cycles= 31.8 us, a 31.4 kHz horizontal
-- refresh rate. Of these, 280 will be active video.
--
-- One set of suggested VGA timings:
--
-- ______________________ ________
-- ________| VIDEO |________| VIDEO
-- |-C-|----------D-----------|-E-|
-- __ ______________________________ ___________
-- |_| |_|
-- |B|
-- |---------------A----------------|
--
-- A = 31.77 us Scanline time
-- B = 3.77 us Horizontal sync time
-- C = 1.89 us Back porch
-- D = 25.17 us Active video
-- E = 0.94 us Front porch
--
-- We use A = 456 / 14.31818 MHz = 31.84 us
-- B = 54 / 14.31818 MHz = 3.77 us
-- C = 106 / 14.31818 MHz = 7.40 us
-- D = 280 / 14.31818 MHz = 19.56 us
-- E = 16 / 14.31818 MHz = 1.12 us
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity vga_controller is
port (
CLK_28M : in std_logic; -- 14.31818 MHz master clock
VIDEO : in std_logic; -- from the Apple video generator
COLOR_LINE : in std_logic;
SCREEN_MODE: in std_logic_vector(1 downto 0); -- 00: Color, 01: B&W, 10: Green, 11: Amber
HBL : in std_logic;
VBL : in std_logic;
LD194 : in std_logic;
VGA_CLK : out std_logic;
VGA_HS : out std_logic; -- Active low
VGA_VS : out std_logic; -- Active low
VGA_DE : out std_logic;
VGA_R : out unsigned(7 downto 0);
VGA_G : out unsigned(7 downto 0);
VGA_B : out unsigned(7 downto 0)
);
end vga_controller;
architecture rtl of vga_controller is
-- Double-ported RAM (one read port, one write port)
-- that holds two lines of 560 pixels
type line_memory_t is array (0 to 2047) of std_logic;
signal line_memory : line_memory_t;
-- RGB values from Linards Ticmanis,
-- http://newsgroups.derkeiler.com/Archive/Comp/comp.sys.apple2/2005-09/msg00534.html
type basis_color is array(0 to 3) of unsigned(7 downto 0);
constant basis_r : basis_color := ( X"88", X"38", X"07", X"38" );
constant basis_g : basis_color := ( X"22", X"24", X"67", X"52" );
constant basis_b : basis_color := ( X"2C", X"A0", X"2C", X"07" );
signal ram_write_addr : unsigned(10 downto 0);
signal ram_we : std_logic;
signal ram_read_addr : unsigned(10 downto 0);
signal ram_data_out : std_logic;
signal shift_reg : unsigned(5 downto 0); -- Last six pixels
signal last_hbl : std_logic;
signal hcount : unsigned(10 downto 0);
signal hcount2 : unsigned(10 downto 0);
signal vcount : unsigned(5 downto 0);
signal even_line : std_logic;
signal hactive, hactive_early2, hactive_early1 : std_logic;
constant VGA_SCANLINE : integer := 456*2; -- Must be 456*2 (set by the Apple)
constant VGA_HSYNC : integer := 54 * 2;
constant VGA_BACK_PORCH : integer := 66 * 2;
constant VGA_ACTIVE : integer := 282 * 2;
constant VGA_FRONT_PORCH : integer := 54 * 2;
-- VGA_HSYNC + VGA_BACK_PORCH + VGA_ACTIVE + VGA_FRONT_PORCH = VGA_SCANLINE
constant VBL_TO_VSYNC : integer := 33;
constant VGA_VSYNC_LINES : integer := 3;
signal VGA_VS_I, VGA_HS_I : std_logic;
signal video_active : std_logic;
signal vbl_delayed, vbl_delayed2 : std_logic;
signal hbl_delayed : std_logic;
signal color_line_delayed_1, color_line_delayed_2 : std_logic;
begin
delay_hbl : process (CLK_28M)
begin
if rising_edge(CLK_28M) then
if LD194 = '0' then
hbl_delayed <= HBL;
end if;
end if;
end process;
hcount_vcount_control : process (CLK_28M)
begin
if rising_edge(CLK_28M) then
if last_hbl = '1' and hbl_delayed = '0' then -- Falling edge
color_line_delayed_2 <= color_line_delayed_1;
color_line_delayed_1 <= COLOR_LINE;
hcount <= (others => '0');
vbl_delayed2 <= vbl_delayed;
vbl_delayed <= VBL;
if vbl_delayed = '1' then
even_line <= '0';
vcount <= vcount + 1;
else
vcount <= (others => '0');
even_line <= not even_line;
end if;
else
hcount <= hcount + 1;
end if;
last_hbl <= hbl_delayed;
end if;
end process hcount_vcount_control;
hsync_gen : process (CLK_28M)
begin
if rising_edge(CLK_28M) then
if hcount = VGA_ACTIVE + VGA_FRONT_PORCH or
hcount = VGA_SCANLINE + VGA_ACTIVE + VGA_FRONT_PORCH then
VGA_HS_I <= '0';
elsif hcount = VGA_ACTIVE + VGA_FRONT_PORCH + VGA_HSYNC or
hcount = VGA_SCANLINE + VGA_ACTIVE + VGA_FRONT_PORCH + VGA_HSYNC then
VGA_HS_I <= '1';
end if;
hactive <= hactive_early1;
hactive_early1 <= hactive_early2;
if hcount = VGA_SCANLINE - 1 or
hcount = VGA_SCANLINE + VGA_SCANLINE - 1 then
hactive_early2 <= '1';
elsif hcount = VGA_ACTIVE or
hcount = VGA_ACTIVE + VGA_SCANLINE then
hactive_early2 <= '0';
end if;
end if;
end process hsync_gen;
VGA_HS <= VGA_HS_I;
vsync_gen : process (CLK_28M)
begin
if rising_edge(CLK_28M) then
if vcount = VBL_TO_VSYNC then
VGA_VS_I <= '0';
elsif vcount = VBL_TO_VSYNC + VGA_VSYNC_LINES then
VGA_VS_I <= '1';
end if;
end if;
end process vsync_gen;
VGA_VS <= VGA_VS_I;
hcount2 <= hcount - VGA_SCANLINE;
ram_read_addr <=
even_line & hcount(9 downto 0) when hcount < VGA_SCANLINE else
even_line & hcount2(9 downto 0);
shifter: process (CLK_28M)
begin
if rising_edge(CLK_28M) then
shift_reg <= ram_data_out & shift_reg(5 downto 1);
end if;
end process;
ram_write_addr <= (not even_line) & hcount(10 downto 1);
ram_we <= '1' when hcount(0) = '1' else '0';
video_active <= hactive and not vbl_delayed2;
pixel_generator: process (CLK_28M)
variable r, g, b : unsigned(7 downto 0);
begin
if rising_edge(CLK_28M) then
r := X"00";
g := X"00";
b := X"00";
-- alternate background for monochrome modes
case SCREEN_MODE is
when "00" =>
r := X"00"; g := X"00"; b := X"00"; -- color mode background
when "01" =>
r := X"00"; g := X"00"; b := X"00"; -- B&W mode background
when "10" =>
r := X"00"; g := X"0F"; b := X"01"; -- green mode background color
when "11" =>
r := X"20"; g := X"08"; b := X"01"; -- amber mode background color
end case;
if video_active = '1' then
if color_line_delayed_2 = '0' then -- Monochrome mode
if shift_reg(2) = '1' then
-- handle green/amber color modes
case SCREEN_MODE is
when "00" =>
r := X"FF"; g := X"FF"; b := X"FF"; -- white (color mode)
when "01" =>
r := X"FF"; g := X"FF"; b := X"FF"; -- white (B&W mode)
when "10" =>
r := X"00"; g := X"C0"; b := X"01"; -- green
when "11" =>
r := X"FF"; g := X"80"; b := X"01"; -- amber
end case;
end if;
elsif shift_reg(0) = shift_reg(4) and shift_reg(5) = shift_reg(1) then
-- Tint of adjacent pixels is consistent : display the color
if shift_reg(1) = '1' then
r := r + basis_r(to_integer(hcount + 1));
g := g + basis_g(to_integer(hcount + 1));
b := b + basis_b(to_integer(hcount + 1));
end if;
if shift_reg(2) = '1' then
r := r + basis_r(to_integer(hcount + 2));
g := g + basis_g(to_integer(hcount + 2));
b := b + basis_b(to_integer(hcount + 2));
end if;
if shift_reg(3) = '1' then
r := r + basis_r(to_integer(hcount + 3));
g := g + basis_g(to_integer(hcount + 3));
b := b + basis_b(to_integer(hcount + 3));
end if;
if shift_reg(4) = '1' then
r := r + basis_r(to_integer(hcount));
g := g + basis_g(to_integer(hcount));
b := b + basis_b(to_integer(hcount));
end if;
else
-- Tint is changing: display only black, gray, or white
case shift_reg(3 downto 2) is
when "11" => r := X"FF"; g := X"FF"; b := X"FF";
when "01" | "10" => r := X"80"; g := X"80"; b := X"80";
when others => r := X"00"; g := X"00"; b := X"00";
end case;
end if;
end if;
VGA_R <= r;
VGA_G <= g;
VGA_B <= b;
end if;
end process pixel_generator;
-- The two-port RAM that stores the line data
line_storage : process (CLK_28M)
begin
if rising_edge(CLK_28M) then
if ram_we = '1' then
line_memory(to_integer(ram_write_addr)) <= VIDEO;
end if;
ram_data_out <= line_memory(to_integer(ram_read_addr));
end if;
end process line_storage;
VGA_CLK <= CLK_28M;
VGA_DE <= video_active;
end rtl;