aboutsummaryrefslogtreecommitdiffstats
path: root/saa5050.vhd
diff options
context:
space:
mode:
authorMike Stirling <opensource@mikestirling.co.uk>2011-08-07 19:58:44 +0100
committerMike Stirling <opensource@mikestirling.co.uk>2011-08-07 19:58:44 +0100
commitb9547b5b46ccd0546c3fc312b57dd4fd07f08a64 (patch)
treee16bac979a37aa7222b9cc71ce8baaeca3606b16 /saa5050.vhd
parenteaf58b095e333967182edee679ebd0989f6ca9ed (diff)
downloadfpga-bbc-b9547b5b46ccd0546c3fc312b57dd4fd07f08a64.tar.gz
fpga-bbc-b9547b5b46ccd0546c3fc312b57dd4fd07f08a64.tar.bz2
fpga-bbc-b9547b5b46ccd0546c3fc312b57dd4fd07f08a64.zip
Fixed cursor generation in VIDPROC. Added SAA5050 for MODE 7 support. Added tools for generating SAA5050 character ROM.
Diffstat (limited to 'saa5050.vhd')
-rw-r--r--saa5050.vhd378
1 files changed, 378 insertions, 0 deletions
diff --git a/saa5050.vhd b/saa5050.vhd
new file mode 100644
index 0000000..734890c
--- /dev/null
+++ b/saa5050.vhd
@@ -0,0 +1,378 @@
+-- SAA5050 teletext generator
+--
+-- Synchronous implementation for FPGA. Certain TV-specific functions are
+-- not implemented. e.g.
+--
+-- No /SI pin - 'TEXT' mode is permanently enabled
+-- No remote control features (/DATA, DLIM)
+-- No large character support
+-- No support for box overlay (BLAN, PO, DE)
+-- No character rounding, although this may be added
+--
+-- FIXME: Hold graphics not supported - this needs to be added
+--
+-- (C) 2011 Mike Stirling
+--
+library IEEE;
+use IEEE.STD_LOGIC_1164.ALL;
+use IEEE.NUMERIC_STD.ALL;
+
+entity saa5050 is
+port (
+ CLOCK : in std_logic;
+ -- 6 MHz dot clock enable
+ CLKEN : in std_logic;
+ -- Async reset
+ nRESET : in std_logic;
+
+ -- Character data input (in the bus clock domain)
+ DI_CLOCK : in std_logic;
+ DI_CLKEN : in std_logic;
+ DI : in std_logic_vector(6 downto 0);
+
+ -- Timing inputs
+ -- General line reset (not used)
+ GLR : in std_logic; -- /HSYNC
+ -- Data entry window - high during VSYNC.
+ -- Resets ROM row counter and drives 'flash' signal
+ DEW : in std_logic; -- VSYNC
+ -- Character rounding select - high during even field
+ CRS : in std_logic; -- FIELD
+ -- Load output shift register enable - high during active video
+ LOSE : in std_logic; -- DE
+
+ -- Video out
+ R : out std_logic;
+ G : out std_logic;
+ B : out std_logic;
+ Y : out std_logic
+ );
+end entity;
+
+architecture rtl of saa5050 is
+
+component saa5050_rom IS
+ PORT
+ (
+ address : IN STD_LOGIC_VECTOR (11 DOWNTO 0);
+ clock : IN STD_LOGIC ;
+ q : OUT STD_LOGIC_VECTOR (7 DOWNTO 0)
+ );
+end component;
+
+-- Data input registered in the bus clock domain
+signal di_r : std_logic_vector(6 downto 0);
+-- Data input registered in the pixel clock domain
+signal code : std_logic_vector(6 downto 0);
+signal line_addr : unsigned(3 downto 0);
+signal rom_address : std_logic_vector(11 downto 0);
+signal rom_data : std_logic_vector(7 downto 0);
+
+-- Latched timing signals for detection of falling edges
+signal dew_r : std_logic;
+signal lose_r : std_logic;
+-- Delayed display enable derived from LOSE by delaying for one character
+signal disp_enable : std_logic;
+signal disp_enable_r : std_logic;
+
+-- Row and column addressing is handled externally. We just need to
+-- keep track of which of the 10 lines we are on within the character...
+signal line_counter : unsigned(3 downto 0);
+-- ... and which of the 6 pixels we are on within each line
+signal pixel_counter : unsigned(2 downto 0);
+-- We also need to count frames to implement the flash feature.
+-- The datasheet says this is 0.75 Hz with a 3:1 on/off ratio, so it
+-- is probably a /64 counter, which gives us 0.78 Hz
+signal flash_counter : unsigned(5 downto 0);
+-- Output shift register
+signal shift_reg : std_logic_vector(5 downto 0);
+
+-- Flash mask
+signal flash : std_logic;
+
+-- Current display state
+-- Foreground colour (B2, G1, R0)
+signal fg : std_logic_vector(2 downto 0);
+-- Background colour (B2, G1, R0)
+signal bg : std_logic_vector(2 downto 0);
+signal conceal : std_logic;
+signal gfx : std_logic;
+signal gfx_sep : std_logic;
+signal gfx_hold : std_logic;
+signal is_flash : std_logic;
+signal double_high : std_logic;
+-- Set in first row of double height
+signal double_high1 : std_logic;
+-- Set in second row of double height
+signal double_high2 : std_logic;
+
+begin
+ char_rom: saa5050_rom port map (
+ rom_address,
+ CLOCK,
+ rom_data
+ );
+
+ -- Generate flash signal for 3:1 ratio
+ flash <= flash_counter(5) and flash_counter(4);
+
+ -- Sync data input
+ process(DI_CLOCK,nRESET)
+ begin
+ if nRESET = '0' then
+ di_r <= (others => '0');
+ elsif rising_edge(DI_CLOCK) and DI_CLKEN = '1' then
+ di_r <= DI;
+ end if;
+ end process;
+
+ -- Register data into pixel clock domain
+ process(CLOCK,nRESET)
+ begin
+ if nRESET = '0' then
+ code <= (others => '0');
+ elsif rising_edge(CLOCK) and CLKEN = '1' then
+ code <= di_r;
+ end if;
+ end process;
+
+ -- Generate character rom address in pixel clock domain
+ -- This is done combinatorially since all the inputs are already
+ -- registered and the address is re-registered by the ROM
+ line_addr <= line_counter when double_high = '0' else
+ ("0" & line_counter(3 downto 1)) when double_high2 = '0' else
+ ("0" & line_counter(3 downto 1)) + 5;
+ rom_address <= (others => '0') when (double_high = '0' and double_high2 = '1') else
+ gfx & code & std_logic_vector(line_addr);
+
+-- process(CLOCK,nRESET)
+-- variable line_addr : unsigned(3 downto 0);
+-- variable c : std_logic_vector(6 downto 0);
+-- begin
+-- -- Derive line address taking double height into account
+-- if double_high = '1' then
+-- line_addr := "0" & line_counter(3 downto 1);
+-- if double_high2 = '1' then
+-- line_addr := line_addr + 5;
+-- end if;
+-- else
+-- line_addr := line_counter;
+-- end if;
+--
+-- -- If this is the second row of a double height pair and double_high
+-- -- is not asserted then we fetch a blank instead (0)
+-- if double_high = '0' and double_high2 = '1' then
+-- c := (others => '0');
+-- else
+-- c := di_r;
+-- end if;
+--
+-- if nRESET = '0' then
+-- rom_address <= (others => '0');
+-- elsif rising_edge(CLOCK) and CLKEN = '1' then
+-- rom_address <= gfx & c & std_logic_vector(line_addr);
+-- end if;
+-- end process;
+
+ -- Character row and pixel counters
+ process(CLOCK,nRESET)
+ begin
+ if nRESET = '0' then
+ dew_r <= '0';
+ lose_r <= '0';
+ disp_enable <= '0';
+ disp_enable_r <= '0';
+ double_high1 <= '0';
+ double_high2 <= '0';
+ line_counter <= (others => '0');
+ pixel_counter <= (others => '0');
+ flash_counter <= (others => '0');
+ elsif rising_edge(CLOCK) and CLKEN = '1' then
+ -- Register syncs for edge detection
+ dew_r <= DEW;
+ lose_r <= LOSE;
+ disp_enable_r <= disp_enable;
+
+ -- When first entering double-height mode start on top row
+ if double_high = '1' and double_high1 = '0' and double_high2 = '0' then
+ double_high1 <= '1';
+ end if;
+
+ -- Rising edge of LOSE is the start of the active line
+ if LOSE = '1' and lose_r = '0' then
+ -- Reset pixel counter - small offset to make the output
+ -- line up with the cursor from the video ULA
+ pixel_counter <= "010";
+ else
+ -- Count pixels between 0 and 5
+ if pixel_counter = 5 then
+ -- Start of next character and delayed display enable
+ pixel_counter <= (others => '0');
+ disp_enable <= lose_r;
+ else
+ pixel_counter <= pixel_counter + 1;
+ end if;
+ end if;
+
+ if DEW = '1' then
+ -- Reset line counter and double height state during VSYNC
+ line_counter <= (others => '0');
+ double_high1 <= '0';
+ double_high2 <= '0';
+ else
+ -- Count frames on end of VSYNC (falling edge of DEW)
+ if DEW = '0' and dew_r = '1' then
+ flash_counter <= flash_counter + 1;
+ end if;
+
+ -- Count lines on end of active video (falling edge of disp_enable)
+ if disp_enable = '0' and disp_enable_r = '1' then
+ if line_counter = 9 then
+ line_counter <= (others => '0');
+
+ -- Keep track of which row we are on for double-height
+ -- The double_high flag can be cleared before the end of a row, but if
+ -- double height characters are used anywhere on a row then the double_high1
+ -- flag will be set and remain set until the next row. This is used
+ -- to determine that the bottom half of the characters should be shown if
+ -- double_high is set once again on the row below.
+ double_high1 <= '0';
+ double_high2 <= double_high1;
+ else
+ line_counter <= line_counter + 1;
+ end if;
+ end if;
+ end if;
+ end if;
+ end process;
+
+ -- Shift register
+ process(CLOCK,nRESET)
+ begin
+ if nRESET = '0' then
+ shift_reg <= (others => '0');
+ elsif rising_edge(CLOCK) and CLKEN = '1' then
+ if disp_enable = '1' and pixel_counter = 0 then
+ -- Load the shift register with the ROM bit pattern
+ -- at the start of each character while disp_enable is asserted.
+ shift_reg <= rom_data(5 downto 0);
+
+ -- If bit 7 of the ROM data is set then this is a graphics
+ -- character and separated/hold graphics modes apply.
+ -- We don't just assume this to be the case if gfx=1 because
+ -- these modes don't apply to caps even in graphics mode
+ if rom_data(7) = '1' then
+ -- Apply a mask for separated graphics mode
+ if gfx_sep = '1' then
+ shift_reg(5) <= '0';
+ shift_reg(2) <= '0';
+ if line_counter = 2 or line_counter = 6 or line_counter = 9 then
+ shift_reg <= (others => '0');
+ end if;
+ end if;
+ end if;
+ else
+ -- Pump the shift register
+ shift_reg <= shift_reg(4 downto 0) & "0";
+ end if;
+ end if;
+ end process;
+
+ -- Control character handling
+ process(CLOCK,nRESET)
+ begin
+ if nRESET = '0' then
+ fg <= (others => '1');
+ bg <= (others => '0');
+ conceal <= '0';
+ gfx <= '0';
+ gfx_sep <= '0';
+ gfx_hold <= '0';
+ is_flash <= '0';
+ double_high <= '0';
+ elsif rising_edge(CLOCK) and CLKEN = '1' then
+ if disp_enable = '0' then
+ -- Reset to start of line defaults
+ fg <= (others => '1');
+ bg <= (others => '0');
+ conceal <= '0';
+ gfx <= '0';
+ gfx_sep <= '0';
+ gfx_hold <= '0';
+ is_flash <= '0';
+ double_high <= '0';
+ elsif pixel_counter = 0 then
+ -- Latch new control codes at the start of each character
+ if code(6 downto 5) = "00" then
+ if code(3) = '0' then
+ -- Colour and graphics setting clears conceal mode
+ conceal <= '0';
+
+ -- Select graphics or alpha mode
+ gfx <= code(4);
+
+ -- 0 would be black but is not allowed so has no effect,
+ -- otherwise set the colour
+ if code(2 downto 0) /= "000" then
+ fg <= code(2 downto 0);
+ end if;
+ else
+ case code(4 downto 0) is
+ -- FLASH
+ when "01000" => is_flash <= '1';
+ -- STEADY
+ when "01001" => is_flash <= '0';
+ -- NORMAL HEIGHT
+ when "01100" => double_high <= '0';
+ -- DOUBLE HEIGHT
+ when "01101" => double_high <= '1';
+ -- CONCEAL
+ when "11000" => conceal <= '1';
+ -- CONTIGUOUS GFX
+ when "11001" => gfx_sep <= '0';
+ -- SEPARATED GFX
+ when "11010" => gfx_sep <= '1';
+ -- BLACK BACKGROUND
+ when "11100" => bg <= (others => '0');
+ -- NEW BACKGROUND
+ when "11101" => bg <= fg;
+ -- HOLD GFX
+ when "11110" => gfx_hold <= '1';
+ -- RELEASE GFX
+ when "11111" => gfx_hold <= '0';
+
+ when others => null;
+ end case;
+ end if;
+ end if;
+ end if;
+ end if;
+ end process;
+
+ -- Output
+ process(CLOCK,nRESET)
+ variable pixel : std_logic;
+ begin
+ pixel := shift_reg(5) and not ((flash and is_flash) or conceal);
+
+ if nRESET = '0' then
+ R <= '0';
+ G <= '0';
+ B <= '0';
+ elsif rising_edge(CLOCK) and CLKEN = '1' then
+ -- Generate mono output
+ Y <= pixel;
+
+ -- Generate colour output
+ if pixel = '1' then
+ R <= fg(0);
+ G <= fg(1);
+ B <= fg(2);
+ else
+ R <= bg(0);
+ G <= bg(1);
+ B <= bg(2);
+ end if;
+ end if;
+ end process;
+end architecture;