-------------------------------------------------------------------------------
--
-- Text-mode VGA controller for the Digilent Spartan 3 starter board
--
-- Uses an OPB interface, e.g., for use with the Microblaze soft core
--
-- Stephen A. Edwards
-- sedwards@cs.columbia.edu
--
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

entity opb_s3board_vga is
  
  generic (
    C_OPB_AWIDTH : integer                   := 32;
    C_OPB_DWIDTH : integer                   := 32;
    C_BASEADDR   : std_logic_vector(31 downto 0) := X"FEFF1000";
    C_HIGHADDR   : std_logic_vector(31 downto 0) := X"FEFF1FFF");

  port (
    OPB_Clk        : in std_logic;
    OPB_Rst        : in std_logic;

    -- OPB signals
    OPB_ABus       : in std_logic_vector (31 downto 0);
    OPB_BE         : in std_logic_vector (3 downto 0);
    OPB_DBus       : in std_logic_vector (31 downto 0);
    OPB_RNW        : in std_logic;
    OPB_select     : in std_logic;
    OPB_seqAddr    : in std_logic;

    VGA_DBus       : out std_logic_vector (31 downto 0);
    VGA_errAck     : out std_logic;
    VGA_retry      : out std_logic;
    VGA_toutSup    : out std_logic;
    VGA_xferAck    : out std_logic;

    Pixel_Clock_2x : in std_logic;

    -- Video signals
    VIDOUT_CLK     : out std_logic;
    VIDOUT_RED     : out std_logic;
    VIDOUT_GREEN   : out std_logic;
    VIDOUT_BLUE    : out std_logic;
    VIDOUT_HSYNC : out std_logic;
    VIDOUT_VSYNC : out std_logic);

end opb_s3board_vga;

architecture Behavioral of opb_s3board_vga is
  
  constant BASEADDR : std_logic_vector(31 downto 0) := X"FEFF1000";

  -- Video parameters
  
  constant HTOTAL : integer := 800;
  constant HSYNC : integer := 96;
  constant HBACK_PORCH : integer := 48;
  constant HACTIVE : integer := 640;
  constant HFRONT_PORCH : integer := 16;
  
  constant VTOTAL : integer := 525;
  constant VSYNC : integer := 2;
  constant VBACK_PORCH : integer := 33;
  constant VACTIVE : integer := 480;
  constant VFRONT_PORCH : integer := 10;

  -- 512 X 8 dual-ported Xilinx block RAM
  component RAMB4_S8_S8
    port (
      DOA   : out std_logic_vector (7 downto 0);
      ADDRA : in std_logic_vector (8 downto 0);
      CLKA  : in std_logic;
      DIA   : in std_logic_vector (7 downto 0);
      ENA   : in std_logic;
      RSTA  : in std_logic;
      WEA   : in std_logic;
      DOB   : out std_logic_vector (7 downto 0);
      ADDRB : in std_logic_vector (8 downto 0);
      CLKB  : in std_logic;
      DIB   : in std_logic_vector (7 downto 0);
      ENB   : in std_logic;
      RSTB  : in std_logic;
      WEB   : in std_logic);
  end component;

  -- Signals latched from the OPB
  signal ABus : std_logic_vector (31 downto 0);
  signal DBus : std_logic_vector (31 downto 0);
  signal RNW  : std_logic;
  signal select_delayed : std_logic;

  -- Latched output signals for the OPB
  signal DBus_out : std_logic_vector (31 downto 0);

  -- Signals for the OPB-mapped RAM
  signal ChipSelect : std_logic;            -- Address decode
  signal MemCycle1, MemCycle2 : std_logic;  -- State bits
  signal RamPageAddress : std_logic_vector(2 downto 0); 
  signal RamSelect : std_logic_vector (7 downto 0);
  signal RST, WE : std_logic_vector (7 downto 0);
  signal DOUT0, DOUT1, DOUT2, DOUT3 : std_logic_vector(7 downto 0);
  signal DOUT4, DOUT5, DOUT6, DOUT7 : std_logic_vector(7 downto 0);
  signal ReadData : std_logic_vector(7 downto 0);

  -- Signals for the video controller
  signal Pixel_Clock : std_logic;       -- 25 MHz clock divided from 50 MHz
  signal LoadNShift : std_logic;        -- Shift register control
  signal FontData : std_logic_vector(7 downto 0);   -- Input to shift register
  signal ShiftData : std_logic_vector(7 downto 0);  -- Shift register data
  signal VideoData : std_logic;         -- Serial out ANDed with blanking

  signal Hcount : std_logic_vector(9 downto 0);  -- Horizontal position (0-800)
  signal Vcount : std_logic_vector(9 downto 0);  -- Vertical position (0-524)
  signal HBLANK_N, VBLANK_N : std_logic;   -- Blanking signals
 
  signal FontLoad, LoadChar : std_logic;  -- Font/Character RAM read triggers
  signal FontAddr : std_logic_vector(10 downto 0);
  signal CharRamPage : std_logic_vector(2 downto 0);
  signal CharRamSelect_N : std_logic_vector(4 downto 0);
  signal FontRamPage : std_logic_vector(1 downto 0);
  signal FontRamSelect_N : std_logic_vector(2 downto 0);
  signal CharAddr : std_logic_vector(11 downto 0);
  signal CharColumn : std_logic_vector(9 downto 0);
  signal CharRow : std_logic_vector(9 downto 0);
  signal Column : std_logic_vector(6 downto 0); -- 0-79
  signal Row : std_logic_vector(4 downto 0);   -- 0-29
  signal EndOfLine, EndOfField : std_logic;

  signal DOUTB0, DOUTB1, DOUTB2, DOUTB3 : std_logic_vector(7 downto 0);
  signal DOUTB4, DOUTB5, DOUTB6, DOUTB7 : std_logic_vector(7 downto 0);

  attribute INIT_00 : string;
  attribute INIT_01 : string;
  attribute INIT_02 : string;
  attribute INIT_03 : string;
  attribute INIT_04 : string;
  attribute INIT_05 : string;
  attribute INIT_06 : string;
  attribute INIT_07 : string;
  attribute INIT_08 : string;
  attribute INIT_09 : string;
  attribute INIT_0a : string;
  attribute INIT_0b : string;
  attribute INIT_0c : string;
  attribute INIT_0d : string;
  attribute INIT_0e : string;
  attribute INIT_0f : string;

  attribute INIT_00 of RAMB4_S8_S8_0 : label is
   "21646c726f57206f6c6c6548";

  -- Standard 8x16 font taken from the Linux console font file "lat0-16.psfu"
  
  attribute INIT_00 of RAMB4_S8_S8_5 : label is
   "000000001818001818183c3c3c18000000000000000000000000000000000000";
  attribute INIT_01 of RAMB4_S8_S8_5 : label is
   "000000006c6cfe6c6c6cfe6c6c00000000000000000000000000002466666600";
  attribute INIT_02 of RAMB4_S8_S8_5 : label is
   "0000000086c66030180cc6c200000000000010107cd616167cd0d0d67c101000";
  attribute INIT_03 of RAMB4_S8_S8_5 : label is
   "000000000000000000000030181818000000000076ccccccdc76386c6c380000";
  attribute INIT_04 of RAMB4_S8_S8_5 : label is
   "0000000030180c0c0c0c0c0c18300000000000000c18303030303030180c0000";
  attribute INIT_05 of RAMB4_S8_S8_5 : label is
   "00000000000018187e18180000000000000000000000663cff3c660000000000";
  attribute INIT_06 of RAMB4_S8_S8_5 : label is
   "0000000000000000fe0000000000000000000030181818000000000000000000";
  attribute INIT_07 of RAMB4_S8_S8_5 : label is
   "0000000000c06030180c06000000000000000000181800000000000000000000";
  attribute INIT_08 of RAMB4_S8_S8_5 : label is
   "000000007e1818181818187838180000000000007cc6e6e6d6d6cecec67c0000";
  attribute INIT_09 of RAMB4_S8_S8_5 : label is
   "000000007cc60606063c0606c67c000000000000fec6c06030180c06c67c0000";
  attribute INIT_0a of RAMB4_S8_S8_5 : label is
   "000000007cc6060606fcc0c0c0fe0000000000001e0c0c0cfecc6c3c1c0c0000";
  attribute INIT_0b of RAMB4_S8_S8_5 : label is
   "0000000030303030180c0606c6fe0000000000007cc6c6c6c6fcc0c060380000";
  attribute INIT_0c of RAMB4_S8_S8_5 : label is
   "00000000780c0606067ec6c6c67c0000000000007cc6c6c6c67cc6c6c67c0000";
  attribute INIT_0d of RAMB4_S8_S8_5 : label is
   "0000000030181800000018180000000000000000001818000000181800000000";
  attribute INIT_0e of RAMB4_S8_S8_5 : label is
   "00000000000000fe0000fe000000000000000000060c18306030180c06000000";
  attribute INIT_0f of RAMB4_S8_S8_5 : label is
   "000000001818001818180cc6c67c0000000000006030180c060c183060000000";
  attribute INIT_00 of RAMB4_S8_S8_6 : label is
   "00000000c6c6c6c6fec6c66c38100000000000007cc0dcdededec6c6c67c0000";
  attribute INIT_01 of RAMB4_S8_S8_6 : label is
   "000000003c66c2c0c0c0c0c2663c000000000000fc666666667c666666fc0000";
  attribute INIT_02 of RAMB4_S8_S8_6 : label is
   "00000000fe6662606878686266fe000000000000f86c6666666666666cf80000";
  attribute INIT_03 of RAMB4_S8_S8_6 : label is
   "000000003a66c6c6dec0c0c2663c000000000000f06060606878686266fe0000";
  attribute INIT_04 of RAMB4_S8_S8_6 : label is
   "000000003c18181818181818183c000000000000c6c6c6c6c6fec6c6c6c60000";
  attribute INIT_05 of RAMB4_S8_S8_6 : label is
   "00000000e666666c78786c6666e600000000000078cccccc0c0c0c0c0c1e0000";
  attribute INIT_06 of RAMB4_S8_S8_6 : label is
   "00000000c6c6c6c6c6d6fefeeec6000000000000fe6662606060606060f00000";
  attribute INIT_07 of RAMB4_S8_S8_6 : label is
   "000000007cc6c6c6c6c6c6c6c67c000000000000c6c6c6c6cedefef6e6c60000";
  attribute INIT_08 of RAMB4_S8_S8_6 : label is
   "00000e0c7cded6c6c6c6c6c6c67c000000000000f0606060607c666666fc0000";
  attribute INIT_09 of RAMB4_S8_S8_6 : label is
   "000000007cc6c6060c3860c6c67c000000000000e66666666c7c666666fc0000";
  attribute INIT_0a of RAMB4_S8_S8_6 : label is
   "000000007cc6c6c6c6c6c6c6c6c60000000000003c1818181818185a7e7e0000";
  attribute INIT_0b of RAMB4_S8_S8_6 : label is
   "000000006ceefed6d6d6c6c6c6c600000000000010386cc6c6c6c6c6c6c60000";
  attribute INIT_0c of RAMB4_S8_S8_6 : label is
   "000000003c181818183c66666666000000000000c6c66c7c38387c6cc6c60000";
  attribute INIT_0d of RAMB4_S8_S8_6 : label is
   "000000003c30303030303030303c000000000000fec6c26030180c86c6fe0000";
  attribute INIT_0e of RAMB4_S8_S8_6 : label is
   "000000003c0c0c0c0c0c0c0c0c3c00000000000000060c183060c00000000000";
  attribute INIT_0f of RAMB4_S8_S8_6 : label is
   "00ff0000000000000000000000000000000000000000000000000000c66c3810";
  attribute INIT_00 of RAMB4_S8_S8_7 : label is
   "0000000076cccccc7c0c78000000000000000000000000000000001830303000";
  attribute INIT_01 of RAMB4_S8_S8_7 : label is
   "000000007cc6c0c0c0c67c0000000000000000007c666666666c786060e00000";
  attribute INIT_02 of RAMB4_S8_S8_7 : label is
   "000000007cc6c0c0fec67c00000000000000000076cccccccc6c3c0c0c1c0000";
  attribute INIT_03 of RAMB4_S8_S8_7 : label is
   "0078cc0c7ccccccccccc76000000000000000000f060606060f060646c380000";
  attribute INIT_04 of RAMB4_S8_S8_7 : label is
   "000000003c181818181838001818000000000000e666666666766c6060e00000";
  attribute INIT_05 of RAMB4_S8_S8_7 : label is
   "00000000e6666c78786c666060e00000003c66660606060606060e0006060000";
  attribute INIT_06 of RAMB4_S8_S8_7 : label is
   "00000000c6d6d6d6d6feec000000000000000000183430303030303030700000";
  attribute INIT_07 of RAMB4_S8_S8_7 : label is
   "000000007cc6c6c6c6c67c000000000000000000666666666666dc0000000000";
  attribute INIT_08 of RAMB4_S8_S8_7 : label is
   "001e0c0c7ccccccccccc76000000000000f060607c6666666666dc0000000000";
  attribute INIT_09 of RAMB4_S8_S8_7 : label is
   "000000007cc60c3860c67c000000000000000000f06060606676dc0000000000";
  attribute INIT_0a of RAMB4_S8_S8_7 : label is
   "0000000076cccccccccccc0000000000000000001c3630303030fc3030100000";
  attribute INIT_0b of RAMB4_S8_S8_7 : label is
   "000000006cfed6d6d6c6c6000000000000000000183c66666666660000000000";
  attribute INIT_0c of RAMB4_S8_S8_7 : label is
   "00f80c067ec6c6c6c6c6c6000000000000000000c66c3838386cc60000000000";
  attribute INIT_0d of RAMB4_S8_S8_7 : label is
   "000000000e18181818701818180e000000000000fec6603018ccfe0000000000";
  attribute INIT_0e of RAMB4_S8_S8_7 : label is
   "0000000070181818180e18181870000000000000181818181818181818180000";
  attribute INIT_0f of RAMB4_S8_S8_7 : label is
   "000000003c1818183c66666666006600000000000000000000000000dc760000";

begin  -- Behavioral

  -----------------------------------------------------------------------------
  --
  -- Instances of the block RAMs
  -- Each is 512 bytes, so 4K total
  --
  -----------------------------------------------------------------------------

  -- First 2.5K is used for characters (5 block RAMs)
  -- Remaining 1.5K holds the font     (3 block RAMs)
  --
 
  -- Port A is used for communication with the OPB
  -- Port B is for video
 
  RAMB4_S8_S8_0 : RAMB4_S8_S8
    port map (
      DOA   => DOUT0,
      ADDRA => ABus(8 downto 0),
      CLKA  => OPB_Clk,
      DIA   => DBus(7 downto 0),
      ENA   => '1',
      RSTA  => RST(0),
      WEA   => WE(0),
      DOB   => DOUTB0,
      ADDRB => CharAddr(8 downto 0),
      CLKB  => Pixel_Clock,
      DIB   => X"00",
      ENB   => '1',
      RSTB  => CharRamSelect_N(0),
      WEB   => '0');

  RAMB4_S8_S8_1 : RAMB4_S8_S8
    port map (
      DOA   => DOUT1,
      ADDRA => ABus(8 downto 0),
      CLKA  => OPB_Clk,
      DIA   => DBus(7 downto 0),
      ENA   => '1',
      RSTA  => RST(1),
      WEA   => WE(1),
      DOB   => DOUTB1,
      ADDRB => CharAddr(8 downto 0),
      CLKB  => Pixel_Clock,
      DIB   => X"00",
      ENB   => '1',
      RSTB  => CharRamSelect_N(1),
      WEB   => '0');

    RAMB4_S8_S8_2 : RAMB4_S8_S8
    port map (
      DOA   => DOUT2,
      ADDRA => ABus(8 downto 0),
      CLKA  => OPB_Clk,
      DIA   => DBus(7 downto 0),
      ENA   => '1',
      RSTA  => RST(2),
      WEA   => WE(2),
      DOB   => DOUTB2,
      ADDRB => CharAddr(8 downto 0),
      CLKB  => Pixel_Clock,
      DIB   => X"00",
      ENB   => '1',
      RSTB  => CharRamSelect_N(2),
      WEB   => '0');

    RAMB4_S8_S8_3 : RAMB4_S8_S8
    port map (
      DOA   => DOUT3,
      ADDRA => ABus(8 downto 0),
      CLKA  => OPB_Clk,
      DIA   => DBus(7 downto 0),
      ENA   => '1',
      RSTA  => RST(3),
      WEA   => WE(3),
      DOB   => DOUTB3,
      ADDRB => CharAddr(8 downto 0),
      CLKB  => Pixel_Clock,
      DIB   => X"00",
      ENB   => '1',
      RSTB  => CharRamSelect_N(3),
      WEB   => '0');

    RAMB4_S8_S8_4 : RAMB4_S8_S8
    port map (
      DOA   => DOUT4,
      ADDRA => ABus(8 downto 0),
      CLKA  => OPB_Clk,
      DIA   => DBus(7 downto 0),
      ENA   => '1',
      RSTA  => RST(4),
      WEA   => WE(4),
      DOB   => DOUTB4,
      ADDRB => CharAddr(8 downto 0),
      CLKB  => Pixel_Clock,
      DIB   => X"00",
      ENB   => '1',
      RSTB  => CharRamSelect_N(4),
      WEB   => '0');

    RAMB4_S8_S8_5 : RAMB4_S8_S8
    port map (
      DOA   => DOUT5,
      ADDRA => ABus(8 downto 0),
      CLKA  => OPB_Clk,
      DIA   => DBus(7 downto 0),
      ENA   => '1',
      RSTA  => RST(5),
      WEA   => WE(5),
      DOB   => DOUTB5,
      ADDRB => FontAddr(8 downto 0),
      CLKB  => Pixel_Clock,
      DIB   => X"00",
      ENB   => '1',
      RSTB  => FontRamSelect_N(0),
      WEB   => '0');

    RAMB4_S8_S8_6 : RAMB4_S8_S8
    port map (
      DOA   => DOUT6,
      ADDRA => ABus(8 downto 0),
      CLKA  => OPB_Clk,
      DIA   => DBus(7 downto 0),
      ENA   => '1',
      RSTA  => RST(6),
      WEA   => WE(6),
      DOB   => DOUTB6,
      ADDRB => FontAddr(8 downto 0),
      CLKB  => Pixel_Clock,
      DIB   => X"00",
      ENB   => '1',
      RSTB  => FontRamSelect_N(1),
      WEB   => '0');

    RAMB4_S8_S8_7 : RAMB4_S8_S8
    port map (
      DOA   => DOUT7,
      ADDRA => ABus(8 downto 0),
      CLKA  => OPB_Clk,
      DIA   => DBus(7 downto 0),
      ENA   => '1',
      RSTA  => RST(7),
      WEA   => WE(7),
      DOB   => DOUTB7,
      ADDRB => FontAddr(8 downto 0),
      CLKB  => Pixel_Clock,
      DIB   => X"00",
      ENB   => '1',
      RSTB  => FontRamSelect_N(2),
      WEB   => '0');

  -----------------------------------------------------------------------------
  --  
  -- OPB-RAM controller
  --
  -----------------------------------------------------------------------------
  
  -- Unused OPB control signals
  VGA_errAck <= '0';
  VGA_retry <= '0';
  VGA_toutSup <= '0';

  -- Latch the relevant OPB signals from the OPB, since they arrive late
  LatchOPB: process (OPB_Clk, OPB_Rst)
  begin
    if OPB_Rst = '1' then
      Abus <= ( others => '0' );
      DBus <= ( others => '0' );
      RNW <= '1';
      select_delayed <= '0';
    elsif OPB_Clk'event and OPB_Clk = '1' then
      ABus <= OPB_ABus;
      DBus <= OPB_DBus;
      RNW <= OPB_RNW;
      select_delayed <= OPB_Select;
    end if;
  end process LatchOPB;

  -- Address bits 31 downto 12 is our chip select
  -- 11 downto 9 is the RAM page select
  -- 8 downto 0 is the RAM byte select
  
  ChipSelect <=
    '1' when select_delayed = '1' and
       (ABus(31 downto 12) = BASEADDR(31 downto 12)) and
       MemCycle1 = '0' and MemCycle2 = '0' else
    '0';

  RamPageAddress <= ABus(11 downto 9);
  
  RamSelect <=
    "00000001" when RamPageAddress = "000" else
    "00000010" when RamPageAddress = "001" else
    "00000100" when RamPageAddress = "010" else
    "00001000" when RamPageAddress = "011" else
    "00010000" when RamPageAddress = "100" else
    "00100000" when RamPageAddress = "101" else
    "01000000" when RamPageAddress = "110" else
    "10000000" when RamPageAddress = "111" else
    "00000000";

  MemCycleFSM : process(OPB_Clk, OPB_Rst)
  begin
    if OPB_Rst = '1' then
      MemCycle1 <= '0';
      MemCycle2 <= '0';
    elsif OPB_Clk'event and OPB_Clk = '1' then
      MemCycle2 <= MemCycle1;
      MemCycle1 <= ChipSelect;
    end if;
  end process MemCycleFSM;
  
  VGA_xferAck <= MemCycle2;

  WE <=
  RamSelect when ChipSelect = '1' and RNW = '0' and OPB_Rst = '0' else
    "00000000";
 
  RST <=
   not RamSelect when ChipSelect = '1' and RNW = '1' and OPB_Rst = '0' else
    "11111111"; 

  ReadData <= DOUT0 or DOUT1 or DOUT2 or DOUT3 or
              DOUT4 or DOUT5 or DOUT6 or DOUT7 when MemCycle1 = '1'
              else "00000000";

  -- DBus(31 downto 24) is the byte for addresses ending in 0

  GenDOut: process (OPB_Clk, OPB_Rst)
  begin
    if OPB_Rst = '1' then
      DBus_out <= ( others => '0');
    elsif OPB_Clk'event and OPB_Clk = '1' then
      DBus_out <= ReadData & ReadData & ReadData & ReadData;
    end if;
  end process GenDOut;

  VGA_DBus <= DBus_out;

  ----------------------------------------------------------------------------
  --
  -- Video controller
  --
  -----------------------------------------------------------------------------

  -- Pixel clock divider
  
  Pixel_clk_divider : process (Pixel_Clock_2x, OPB_Rst)
  begin
    if OPB_Rst = '1' then
      Pixel_Clock <= '0';
    elsif Pixel_Clock_2x'event and Pixel_Clock_2x = '1' then
      Pixel_Clock <= not Pixel_Clock;
    end if;
  end process Pixel_clk_divider;
  
  -- Horizontal and vertical counters

  HCounter : process (Pixel_Clock, OPB_Rst)
  begin    
    if OPB_Rst = '1' then
      Hcount <= (others => '0');
    elsif Pixel_Clock'event and Pixel_Clock = '1' then
      if EndOfLine = '1' then
        Hcount <= (others => '0');
      else
        Hcount <= Hcount + 1;
      end if;      
    end if;
  end process HCounter;

  EndOfLine <= '1' when Hcount = HTOTAL - 1 else '0';
 
  VCounter: process (Pixel_Clock, OPB_Rst)
  begin
    if OPB_Rst = '1' then
      Vcount <= (others => '0');
    elsif Pixel_Clock'event and Pixel_Clock = '1' then
      if EndOfLine = '1' then
        if EndOfField = '1' then
          Vcount <= (others => '0');
        else
          Vcount <= Vcount + 1;
        end if;
      end if;
    end if;
  end process VCounter;

  EndOfField <= '1' when Vcount = VTOTAL - 1 else '0';

  -- State machines to generate HSYNC, VSYNC, HBLANK, and VBLANK

  HSyncGen : process (Pixel_Clock, OPB_Rst)
  begin    
    if OPB_Rst = '1' then
      VIDOUT_HSYNC <= '0';
    elsif Pixel_Clock'event and Pixel_Clock = '1' then
      if EndOfLine = '1' then
        VIDOUT_HSYNC <= '1';
      elsif Hcount = HSYNC - 1 then
        VIDOUT_HSYNC <= '0';
      end if;      
    end if;
  end process HSyncGen;

  HBlankGen : process (Pixel_Clock, OPB_Rst)
  begin    
    if OPB_Rst = '1' then
      HBLANK_N <= '0';
    elsif Pixel_Clock'event and Pixel_Clock = '1' then
      if Hcount = HSYNC + HBACK_PORCH - 1 then
        HBLANK_N <= '1';
      elsif Hcount = HSYNC + HBACK_PORCH + HACTIVE - 1 then
        HBLANK_N <= '0';
      end if;      
    end if;
  end process HBlankGen;
  
  VSyncGen : process (Pixel_Clock, OPB_Rst)
  begin    
    if OPB_Rst = '1' then
      VIDOUT_VSYNC <= '1';
    elsif Pixel_Clock'event and Pixel_Clock = '1' then
      if EndOfLine ='1' then
        if EndOfField = '1' then
          VIDOUT_VSYNC <= '1';
        elsif VCount = VSYNC - 1 then
          VIDOUT_VSYNC <= '0';
        end if;
      end if;      
    end if;
  end process VSyncGen;

  VBlankGen : process (Pixel_Clock, OPB_Rst)
  begin    
    if OPB_Rst = '1' then
      VBLANK_N <= '0';
    elsif Pixel_Clock'event and Pixel_Clock = '1' then
      if EndOfLine ='1' then
        if Vcount = VSYNC + VBACK_PORCH - 1 then
          VBLANK_N <= '1';
        elsif VCount = VSYNC + VBACK_PORCH + VACTIVE - 1 then
          VBLANK_N <= '0';
        end if;
      end if;      
    end if;
  end process VBlankGen;
  
  -- RAM read triggers and shift register control

  LoadChar   <= '1' when Hcount(2 downto 0) = X"5" else '0';
  FontLoad   <= '1' when Hcount(2 downto 0) = X"6" else '0'; 
  LoadNShift <= '1' when Hcount(2 downto 0) = X"7" else '0';

  -- Correction of 4 needed to calculate the character address before the
  -- character is displayed
  CharColumn <= Hcount - HSYNC - HBACK_PORCH + 4;
  Column <= CharColumn(9 downto 3);  -- /8
  CharRow <= Vcount - VSYNC - VBACK_PORCH;
  Row <= CharRow(8 downto 4);  -- / 16

  -- Column + Row * 80
  CharAddr <= Column +
              ("0" & Row(4 downto 0) & "000000") +
              ("000" & Row(4 downto 0) & "0000");

  CharRamPage <= CharAddr(11 downto 9);
  CharRamSelect_N <=
    "11110" when CharRamPage = "000" else
    "11101" when CharRamPage = "001" else
    "11011" when CharRamPage = "010" else
    "10111" when CharRamPage = "011" else
    "01111" when CharRamPage = "100" else
    "11111";

  -- Most significant bit of character ignored
  FontAddr(10 downto 4) <=
    (DOUTB0(6 downto 0) or DOUTB1(6 downto 0) or DOUTB2(6 downto 0) or
     DOUTB3(6 downto 0) or DOUTB4(6 downto 0));
  FontAddr(3 downto 0) <= CharRow(3 downto 0);

  -- Unusual addressing: font only holds 96 of 128 possible characters
  -- First 32 characters appear twice
  FontRamPage <= FontAddr(10 downto 9);
  FontRamSelect_N <=
    "110" when FontRamPage = "00" else
    "110" when FontRamPage = "01" else
    "101" when FontRamPage = "10" else
    "011" when FontRamPage = "11" else
    "111";

  FontData <= DOUTB5 or DOUTB6 or DOUTB7;
  
  -- Shift register

  ShiftRegister: process (Pixel_Clock, OPB_Rst)
  begin
    if OPB_Rst = '1' then
      ShiftData <= X"AB";
    elsif Pixel_Clock'event and Pixel_Clock = '1' then
      if LoadNShift = '1' then
        ShiftData <= FontData;
      else
        ShiftData <= ShiftData(6 downto 0) & ShiftData(7);
      end if;
    end if;
  end process ShiftRegister;

  VideoData <= ShiftData(7);

  -- Video signals going to the "video DAC"

  VideoOut: process (Pixel_Clock, OPB_Rst)
  begin
    if OPB_Rst = '1' then
      VIDOUT_RED   <= '0';
      VIDOUT_BLUE  <= '0';
      VIDOUT_GREEN <= '0';
    elsif Pixel_Clock'event and Pixel_Clock = '1' then
      if VideoData = '1' and HBLANK_N = '1' and VBLANK_N = '1' then
        VIDOUT_RED   <= '1';
        VIDOUT_GREEN <= '1';
        VIDOUT_BLUE  <= '1';
      else
        VIDOUT_RED   <= '0';
        VIDOUT_GREEN <= '0';
        VIDOUT_BLUE  <= '0';
      end if;
    end if;
  end process VideoOut;

  VIDOUT_CLK <= Pixel_Clock;

end Behavioral;
