aboutsummaryrefslogtreecommitdiffstats
path: root/vidproc.vhd
diff options
context:
space:
mode:
Diffstat (limited to 'vidproc.vhd')
-rw-r--r--vidproc.vhd279
1 files changed, 279 insertions, 0 deletions
diff --git a/vidproc.vhd b/vidproc.vhd
new file mode 100644
index 0000000..6d5f91f
--- /dev/null
+++ b/vidproc.vhd
@@ -0,0 +1,279 @@
+-- BBC Micro for Altera DE1
+--
+-- Copyright (c) 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.
+--
+-- BBC Micro "VIDPROC" Video ULA
+--
+-- Synchronous implementation for FPGA
+--
+-- (C) 2011 Mike Stirling
+--
+library IEEE;
+use IEEE.STD_LOGIC_1164.ALL;
+use IEEE.NUMERIC_STD.ALL;
+
+entity vidproc is
+port (
+ CLOCK : in std_logic;
+ -- Clock enable qualifies display cycles (interleaved with CPU cycles)
+ CLKEN : in std_logic;
+ nRESET : in std_logic;
+
+ -- Clock enable output to CRTC
+ CLKEN_CRTC : out std_logic;
+
+ -- Bus interface
+ ENABLE : in std_logic;
+ A0 : in std_logic;
+ -- CPU data bus (for register writes)
+ DI_CPU : in std_logic_vector(7 downto 0);
+ -- Display RAM data bus (for display data fetch)
+ DI_RAM : in std_logic_vector(7 downto 0);
+
+ -- Control interface
+ nINVERT : in std_logic;
+ DISEN : in std_logic;
+ CURSOR : in std_logic;
+
+ -- Video in (teletext mode)
+ R_IN : in std_logic;
+ G_IN : in std_logic;
+ B_IN : in std_logic;
+
+ -- Video out
+ R : out std_logic;
+ G : out std_logic;
+ B : out std_logic
+ );
+end entity;
+
+architecture rtl of vidproc is
+-- Write-only registers
+signal r0_cursor0 : std_logic;
+signal r0_cursor1 : std_logic;
+signal r0_cursor2 : std_logic;
+signal r0_crtc_2mhz : std_logic;
+signal r0_pixel_rate : std_logic_vector(1 downto 0);
+signal r0_teletext : std_logic;
+signal r0_flash : std_logic;
+
+type palette_t is array(0 to 15) of std_logic_vector(3 downto 0);
+signal palette : palette_t;
+
+-- Pixel shift register
+signal shiftreg : std_logic_vector(7 downto 0);
+-- Delayed display enable
+signal delayed_disen : std_logic;
+
+-- Internal clock enable generation
+signal clken_pixel : std_logic;
+signal clken_fetch : std_logic;
+signal clken_counter : unsigned(3 downto 0);
+
+-- Cursor generation - can span up to 32 pixels
+-- Segments 0 and 1 are 8 pixels wide
+-- Segment 2 is 16 pixels wide
+signal cursor_invert : std_logic;
+signal cursor_active : std_logic;
+signal cursor_counter : unsigned(1 downto 0);
+
+begin
+ -- Synchronous register access, enabled on every clock
+ process(CLOCK,nRESET)
+ begin
+ if nRESET = '0' then
+ r0_cursor0 <= '0';
+ r0_cursor1 <= '0';
+ r0_cursor2 <= '0';
+ r0_crtc_2mhz <= '0';
+ r0_pixel_rate <= "00";
+ r0_teletext <= '0';
+ r0_flash <= '0';
+
+ for colour in 0 to 15 loop
+ palette(colour) <= (others => '0');
+ end loop;
+ elsif rising_edge(CLOCK) then
+ if ENABLE = '1' then
+ if A0 = '0' then
+ -- Access control register
+ r0_cursor0 <= DI_CPU(7);
+ r0_cursor1 <= DI_CPU(6);
+ r0_cursor2 <= DI_CPU(5);
+ r0_crtc_2mhz <= DI_CPU(4);
+ r0_pixel_rate <= DI_CPU(3 downto 2);
+ r0_teletext <= DI_CPU(1);
+ r0_flash <= DI_CPU(0);
+ else
+ -- Access palette register
+ palette(to_integer(unsigned(DI_CPU(7 downto 4)))) <= DI_CPU(3 downto 0);
+ end if;
+ end if;
+ end if;
+ end process;
+
+ -- Clock enable generation.
+ -- Pixel clock can be divided by 1,2,4 or 8 depending on the value
+ -- programmed at r0_pixel_rate
+ -- 00 = /8, 01 = /4, 10 = /2, 11 = /1
+ clken_pixel <=
+ CLKEN when r0_pixel_rate = "11" else
+ (CLKEN and not clken_counter(0)) when r0_pixel_rate = "10" else
+ (CLKEN and not (clken_counter(0) or clken_counter(1))) when r0_pixel_rate = "01" else
+ (CLKEN and not (clken_counter(0) or clken_counter(1) or clken_counter(2)));
+ -- The CRT controller is always enabled in the 15th cycle, so that the result
+ -- is ready for latching into the shift register in cycle 0. If 2 MHz mode is
+ -- selected then the CRTC is also enabled in the 7th cycle
+ CLKEN_CRTC <= CLKEN and
+ clken_counter(0) and clken_counter(1) and clken_counter(2) and
+ (clken_counter(3) or r0_crtc_2mhz);
+ -- The result is fetched from the CRTC in cycle 0 and also cycle 8 if 2 MHz
+ -- mode is selected. This is used for reloading the shift register as well as
+ -- counting cursor pixels
+ clken_fetch <= CLKEN and not (clken_counter(0) or clken_counter(1) or clken_counter(2) or
+ (clken_counter(3) and not r0_crtc_2mhz));
+
+ process(CLOCK,nRESET)
+ begin
+ if nRESET = '0' then
+ clken_counter <= (others => '0');
+ elsif rising_edge(CLOCK) and CLKEN = '1' then
+ -- Increment internal cycle counter during each video clock
+ clken_counter <= clken_counter + 1;
+ end if;
+ end process;
+
+ -- Fetch control
+ process(CLOCK,nRESET)
+ begin
+ if nRESET = '0' then
+ shiftreg <= (others => '0');
+ elsif rising_edge(CLOCK) and clken_pixel = '1' then
+ if clken_fetch = '1' then
+ -- Fetch next byte from RAM into shift register. This always occurs in
+ -- cycle 0, and also in cycle 8 if the CRTC is clocked at double rate.
+ shiftreg <= DI_RAM;
+ else
+ -- Clock shift register and input '1' at LSB
+ shiftreg <= shiftreg(6 downto 0) & "1";
+ end if;
+ end if;
+ end process;
+
+ -- Cursor generation
+ cursor_invert <= cursor_active and
+ ((r0_cursor0 and not (cursor_counter(0) or cursor_counter(1))) or
+ (r0_cursor1 and cursor_counter(0) and not cursor_counter(1)) or
+ (r0_cursor2 and cursor_counter(1)));
+ process(CLOCK,nRESET)
+ begin
+ if nRESET = '0' then
+ cursor_active <= '0';
+ cursor_counter <= (others => '0');
+ elsif rising_edge(CLOCK) and clken_fetch = '1' then
+ if CURSOR = '1' or cursor_active = '1' then
+ -- Latch cursor
+ cursor_active <= '1';
+
+ -- Reset on counter wrap
+ if cursor_counter = "11" then
+ cursor_active <= '0';
+ end if;
+
+ -- Increment counter
+ if cursor_active = '0' then
+ -- Reset
+ cursor_counter <= (others => '0');
+ else
+ -- Increment
+ cursor_counter <= cursor_counter + 1;
+ end if;
+ end if;
+ end if;
+ end process;
+
+ -- Pixel generation
+ -- The new shift register contents are loaded during
+ -- cycle 0 (and 8) but will not be read here until the next cycle.
+ -- By running this process on every single video tick instead of at
+ -- the pixel rate we ensure that the resulting delay is minimal and
+ -- constant (running this at the pixel rate would cause
+ -- the display to move slightly depending on which mode was selected).
+ process(CLOCK,nRESET)
+ variable palette_a : std_logic_vector(3 downto 0);
+ variable dot_val : std_logic_vector(3 downto 0);
+ variable red_val : std_logic;
+ variable green_val : std_logic;
+ variable blue_val : std_logic;
+ begin
+ if nRESET = '0' then
+ R <= '0';
+ G <= '0';
+ B <= '0';
+ delayed_disen <= '0';
+ elsif rising_edge(CLOCK) and CLKEN = '1' then
+ -- Look up dot value in the palette. Bits are as follows:
+ -- bit 3 - FLASH
+ -- bit 2 - Not BLUE
+ -- bit 1 - Not GREEN
+ -- bit 0 - Not RED
+ palette_a := shiftreg(7) & shiftreg(5) & shiftreg(3) & shiftreg(1);
+ dot_val := palette(to_integer(unsigned(palette_a)));
+
+ -- Apply flash inversion if required
+ red_val := (dot_val(3) and r0_flash) xor not dot_val(0);
+ green_val := (dot_val(3) and r0_flash) xor not dot_val(1);
+ blue_val := (dot_val(3) and r0_flash) xor not dot_val(2);
+
+ -- To output
+ -- FIXME: INVERT option
+ if r0_teletext = '0' then
+ -- Cursor can extend outside the bounds of the screen, so
+ -- it is not affected by DISEN
+ R <= (red_val and delayed_disen) xor cursor_invert;
+ G <= (green_val and delayed_disen) xor cursor_invert;
+ B <= (blue_val and delayed_disen) xor cursor_invert;
+ else
+ R <= R_IN xor cursor_invert;
+ G <= G_IN xor cursor_invert;
+ B <= B_IN xor cursor_invert;
+ end if;
+
+ -- Display enable signal delayed by one clock
+ delayed_disen <= DISEN;
+ end if;
+ end process;
+end architecture;
+