315 lines
9.8 KiB
VHDL
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;
|