I've been playing around with some CPLDs and thought I'd try to build a simple color VGA circuit for my 6502 system.
The code fits within either a Xilinx XL9572XL (68/72 Macrocells used) or an Altera MAX7064S (58/64 Macrocells used)
It's not as full featured as the CRTC because there doesn't seem to be enough space in the device to fit the registers required, so no hardware scrolling with this one but I plan to move up to a 128 macrocell cpld to achieve that later.
The current circuit is pictured here without the CPLD included in the diagram:
Memory is divided into Even (Character data) and Odd (Color data) addresses from the MPU side
And the VHDL
Code:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity vga6502 is
port
(
clk : in std_logic;
sl : out std_logic;
latch : out std_logic;
reset : in std_logic;
ma : out std_logic_vector(11 downto 0);
ra : out std_logic_vector(3 downto 0);
e : out std_logic;
hsync : out std_logic;
vsync : out std_logic
);
end entity;
architecture behavioral of vga6502 is
signal CLK8: std_logic := '0';
signal CLKDIV: integer range 0 to 3;
signal SRDIV: std_logic_vector(2 downto 0);
constant H_TOT: integer range 0 to 100 := 100;
constant H_FP: integer := 2;
constant H_BP: integer := 6;
constant H_SW: integer := 12;
signal H_CNTR: integer range 0 to 100;
signal H_END: std_logic := '0';
signal H_BLANK: std_logic := '0';
constant V_TOT: integer := 449;
constant V_FP: integer := 12;
constant V_SW: integer := 2;
constant V_BP: integer := 35;
signal V_CNTR: integer range 0 to 525;
signal V_BLANK: std_logic := '0';
signal CH_CNTR: integer range 0 to 4095;
signal RW_REG: integer range 0 to 4095;
signal CH_CLK: std_logic := '0';
signal SL_CNTR: integer range 0 to 15;
begin
e <= (V_BLANK OR H_BLANK);
---
--- Clock Divider
--- Divides 25.175 Mhz Pixel clock by 8, i.e to load one character every 8 clocks
---
charclk:
process(clk, reset)
begin
if reset = '1' then
CLKDIV <= 0;
elsif (rising_edge(clk)) then
if (CLKDIV = 3) then
CLK8 <= NOT(CLK8);
CLKDIV <= 0;
else
CLKDIV <= CLKDIV + 1;
end if;
end if;
end process;
--
-- Horizontal
--
H_cnt:
process(clk8, reset)
begin
if (reset = '1') then
H_CNTR <= 0;
elsif rising_edge(clk8) then
if (H_CNTR = (H_TOT - 1)) then
H_END <= '1';
H_CNTR <= 0;
else
H_END <= '0';
H_CNTR <= H_CNTR + 1;
end if;
end if;
end process;
H_display:
process(clk8)
begin
if rising_edge(clk8) then
if (H_CNTR >= (H_SW + H_BP)) AND (H_CNTR < (H_TOT - H_FP)) then
H_BLANK <= '0';
else
H_BLANK <= '1';
end if;
end if;
end process;
H_sync:
process(clk)
begin
if rising_edge(clk) then
if (H_CNTR < H_SW) then
HSYNC <= '0';
else
HSYNC <= '1';
end if;
end if;
end process;
---
--- Vertical
---
V_cnt:
process (H_END, reset)
begin
if (reset = '1') then
V_CNTR <= 0;
elsif falling_edge(H_END) then
if (V_CNTR < V_TOT) then
V_CNTR <= V_CNTR + 1;
else
V_CNTR <= 0;
end if;
end if;
end process;
V_display:
process(H_END)
begin
if falling_edge(H_END) then
if (V_CNTR >= (V_SW + V_BP)) AND (V_CNTR < (V_TOT - V_FP)) then
V_BLANK <= '0';
else
V_BLANK <= '1';
end if;
end if;
end process;
V_sync:
process(H_END)
begin
if falling_edge(H_END) then
if (V_CNTR < V_SW) then
VSYNC <= '1';
else
VSYNC <= '0';
end if;
end if;
end process;
---
--- Scanline Counter
--- Outputs RA row address to Char Rom
---
SL_cnt:
process(H_END, reset)
begin
if (reset = '1') then
SL_CNTR <= 0;
elsif falling_edge(H_END) then
if (SL_CNTR = 15) or (V_BLANK = '1') then
SL_CNTR <= 0;
else
SL_CNTR <= SL_CNTR + 1;
end if;
end if;
end process;
RA <= std_logic_vector(to_unsigned(SL_CNTR, RA'length));
---
--- Char Row Clock, increment row count every 16 scanlines
---
CHCLK:
process(SL_CNTR)
begin
if (SL_CNTR = 15) then
CH_CLK <= '1';
else
CH_CLK <= '0';
end if;
end process;
RW_cnt:
process(reset,CH_CLK,V_BLANK)
begin
if (reset = '1') OR (V_BLANK = '1') then
RW_REG <= 0;
elsif falling_edge(CH_CLK) then
RW_REG <= RW_REG + 80;
end if;
end process;
---
--- Char Counter
--- Generates linear memory addresses
---
C_cnt:
process(reset,CLK8)
begin
if (reset = '1') then
CH_CNTR <= 0;
elsif rising_edge(CLK8) then
if (H_CNTR >= (H_SW + H_BP)) AND (H_CNTR < (H_TOT - H_FP)) then
CH_CNTR <= CH_CNTR + 1;
else
CH_CNTR <= RW_REG;
end if;
end if;
end process;
MA <= std_logic_vector(to_unsigned(CH_CNTR, MA'length));
---
--- Shift Register control
--- load the shift register every 8 clocks
---
SR_ctrl:
process(reset,clk)
begin
if reset = '1' then
SRDIV <= (others => '0');
elsif rising_edge(clk) then
if (H_CNTR >= (H_SW + H_BP)) AND (H_CNTR <= (H_TOT - H_FP)) then
SRDIV <= SRDIV + '1';
else
SRDIV <= (others => '0');
end if;
end if;
end process;
--- Load shift register
SL <= '0' when (SRDIV (2 downto 0) = "000" AND (CLK = '1')) else '1';
--- Load color latch
LATCH <= '0' when (SRDIV (2 downto 0) = "111" AND (CLK = '1')) else '1';
end behavioral;
As a VHDL noob there's probably a heap of things I can improve on, please let me know if you've got any tips!
Interestingly the ROM is so slow (150ns) that to get it working I have to load the shift register just before the rom is pointed to a new address
As a matter of fact if you look at the timing from the RTL Simulation you can see that the shift register is loaded at the same time the MA output changes, presumably the SRAM and ROM delays are just long enough for this to work...
SL = Shift register Load
E = Video output enable
I could load the shift register sooner than that by a couple of clocks but that'd push the first X pixels off the left hand side of the screen. since the horizontal count is in characters rather than pixels I can only really load shift register right as the address is changing (and this is why I need to latch the color data at around the same time). how was this done with CRTC based systems?
This results in an 80x30 display with 16 colors foreground/background that works almost perfectly. At the moment I don't have the second SRAM to store the color data so I've been testing by connecting MA0-7 to the inputs of the color latch.
The only problem I've had is that I can't get the color to sync up with the characters perfectly
Basically the color data seems to change just after the character does, resulting in the first pixel of the character being the wrong color. tuning the latch output to load sooner/later just pushes it over one side or the other. never matching up perfectly with the character data
I'm going to see what happens when I actually source the color data from RAM, as well as replacing the HC logic with AC logic to tighten things up a bit.
I probably need to add a second latch after the color mux too?
Any pointers would be appreciated!