-- 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. -- -- MC6845 CRTC -- -- Synchronous implementation for FPGA -- -- (C) 2011 Mike Stirling -- library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity mc6845 is port ( CLOCK : in std_logic; CLKEN : in std_logic; nRESET : in std_logic; -- Bus interface ENABLE : in std_logic; R_nW : in std_logic; RS : in std_logic; DI : in std_logic_vector(7 downto 0); DO : out std_logic_vector(7 downto 0); -- Display interface VSYNC : out std_logic; HSYNC : out std_logic; DE : out std_logic; CURSOR : out std_logic; LPSTB : in std_logic; -- Memory interface MA : out std_logic_vector(13 downto 0); RA : out std_logic_vector(4 downto 0) ); end entity; architecture rtl of mc6845 is -- Host-accessible registers signal addr_reg : std_logic_vector(4 downto 0); -- Currently addressed register -- These are write-only signal r00_h_total : unsigned(7 downto 0); -- Horizontal total, chars signal r01_h_displayed : unsigned(7 downto 0); -- Horizontal active, chars signal r02_h_sync_pos : unsigned(7 downto 0); -- Horizontal sync position, chars signal r03_v_sync_width : unsigned(3 downto 0); -- Vertical sync width, scan lines (0=16 lines) signal r03_h_sync_width : unsigned(3 downto 0); -- Horizontal sync width, chars (0=no sync) signal r04_v_total : unsigned(6 downto 0); -- Vertical total, character rows signal r05_v_total_adj : unsigned(4 downto 0); -- Vertical offset, scan lines signal r06_v_displayed : unsigned(6 downto 0); -- Vertical active, character rows signal r07_v_sync_pos : unsigned(6 downto 0); -- Vertical sync position, character rows signal r08_interlace : std_logic_vector(1 downto 0); signal r09_max_scan_line_addr : unsigned(4 downto 0); signal r10_cursor_mode : std_logic_vector(1 downto 0); signal r10_cursor_start : unsigned(4 downto 0); -- Cursor start, scan lines signal r11_cursor_end : unsigned(4 downto 0); -- Cursor end, scan lines signal r12_start_addr_h : unsigned(5 downto 0); signal r13_start_addr_l : unsigned(7 downto 0); -- These are read/write signal r14_cursor_h : unsigned(5 downto 0); signal r15_cursor_l : unsigned(7 downto 0); -- These are read-only signal r16_light_pen_h : unsigned(5 downto 0); signal r17_light_pen_l : unsigned(7 downto 0); -- Timing generation -- Horizontal counter counts position on line signal h_counter : unsigned(7 downto 0); -- HSYNC counter counts duration of sync pulse signal h_sync_counter : unsigned(3 downto 0); -- Row counter counts current character row signal row_counter : unsigned(6 downto 0); -- Line counter counts current line within each character row signal line_counter : unsigned(4 downto 0); -- VSYNC counter counts duration of sync pulse signal v_sync_counter : unsigned(3 downto 0); -- Field counter counts number of complete fields for cursor flash signal field_counter : unsigned(5 downto 0); -- Internal signals signal h_sync_start : std_logic; signal h_half_way : std_logic; signal h_display : std_logic; signal hs : std_logic; signal v_display : std_logic; signal vs : std_logic; signal odd_field : std_logic; signal ma_i : unsigned(13 downto 0); signal ma_row_start : unsigned(13 downto 0); -- Start address of current character row signal cursor_i : std_logic; signal lpstb_i : std_logic; begin HSYNC <= hs; -- External HSYNC driven directly from internal signal VSYNC <= vs; -- External VSYNC driven directly from internal signal DE <= h_display and v_display; -- Cursor output generated combinatorially from the internal signal in -- accordance with the currently selected cursor mode CURSOR <= cursor_i when r10_cursor_mode = "00" else '0' when r10_cursor_mode = "01" else (cursor_i and field_counter(4)) when r10_cursor_mode = "10" else (cursor_i and field_counter(5)); -- Synchronous register access. Enabled on every clock. process(CLOCK,nRESET) begin if nRESET = '0' then -- Reset registers to defaults addr_reg <= (others => '0'); r00_h_total <= (others => '0'); r01_h_displayed <= (others => '0'); r02_h_sync_pos <= (others => '0'); r03_v_sync_width <= (others => '0'); r03_h_sync_width <= (others => '0'); r04_v_total <= (others => '0'); r05_v_total_adj <= (others => '0'); r06_v_displayed <= (others => '0'); r07_v_sync_pos <= (others => '0'); r08_interlace <= (others => '0'); r09_max_scan_line_addr <= (others => '0'); r10_cursor_mode <= (others => '0'); r10_cursor_start <= (others => '0'); r11_cursor_end <= (others => '0'); r12_start_addr_h <= (others => '0'); r13_start_addr_l <= (others => '0'); r14_cursor_h <= (others => '0'); r15_cursor_l <= (others => '0'); DO <= (others => '0'); elsif rising_edge(CLOCK) then if ENABLE = '1' then if R_nW = '1' then -- Read case addr_reg is when "01110" => DO <= "00" & std_logic_vector(r14_cursor_h); when "01111" => DO <= std_logic_vector(r15_cursor_l); when "10000" => DO <= "00" & std_logic_vector(r16_light_pen_h); when "10001" => DO <= std_logic_vector(r17_light_pen_l); when others => DO <= (others => '0'); end case; else -- Write if RS = '0' then addr_reg <= DI(4 downto 0); else case addr_reg is when "00000" => r00_h_total <= unsigned(DI); when "00001" => r01_h_displayed <= unsigned(DI); when "00010" => r02_h_sync_pos <= unsigned(DI); when "00011" => r03_v_sync_width <= unsigned(DI(7 downto 4)); r03_h_sync_width <= unsigned(DI(3 downto 0)); when "00100" => r04_v_total <= unsigned(DI(6 downto 0)); when "00101" => r05_v_total_adj <= unsigned(DI(4 downto 0)); when "00110" => r06_v_displayed <= unsigned(DI(6 downto 0)); when "00111" => r07_v_sync_pos <= unsigned(DI(6 downto 0)); when "01000" => r08_interlace <= DI(1 downto 0); when "01001" => r09_max_scan_line_addr <= unsigned(DI(4 downto 0)); when "01010" => r10_cursor_mode <= DI(6 downto 5); r10_cursor_start <= unsigned(DI(4 downto 0)); when "01011" => r11_cursor_end <= unsigned(DI(4 downto 0)); when "01100" => r12_start_addr_h <= unsigned(DI(5 downto 0)); when "01101" => r13_start_addr_l <= unsigned(DI(7 downto 0)); when "01110" => r14_cursor_h <= unsigned(DI(5 downto 0)); when "01111" => r15_cursor_l <= unsigned(DI(7 downto 0)); when others => null; end case; end if; end if; end if; end if; end process; -- registers -- Horizontal, vertical and address counters process(CLOCK,nRESET) variable ma_row_start : unsigned(13 downto 0); variable max_scan_line : unsigned(4 downto 0); begin if nRESET = '0' then -- H h_counter <= (others => '0'); -- V line_counter <= (others => '0'); row_counter <= (others => '0'); odd_field <= '0'; -- Fields (cursor flash) field_counter <= (others => '0'); -- Addressing ma_row_start := (others => '0'); ma_i <= (others => '0'); elsif rising_edge(CLOCK) and CLKEN='1' then -- Horizontal counter increments on each clock, wrapping at -- h_total if h_counter = r00_h_total then -- h_total reached h_counter <= (others => '0'); -- In interlace sync + video mode mask off the LSb of the -- max scan line address if r08_interlace = "11" then max_scan_line := r09_max_scan_line_addr(4 downto 1) & "0"; else max_scan_line := r09_max_scan_line_addr; end if; -- Scan line counter increments, wrapping at max_scan_line_addr if line_counter = max_scan_line then -- Next character row -- FIXME: No support for v_total_adj yet line_counter <= (others => '0'); if row_counter = r04_v_total then -- If in interlace mode we toggle to the opposite field. -- Save on some logic by doing this here rather than at the -- end of v_total_adj - it shouldn't make any difference to the -- output if r08_interlace(0) = '1' then odd_field <= not odd_field; else odd_field <= '0'; end if; -- Address is loaded from start address register at the top of -- each field and the row counter is reset ma_row_start := r12_start_addr_h & r13_start_addr_l; row_counter <= (others => '0'); -- Increment field counter field_counter <= field_counter + 1; else -- On all other character rows within the field the row start address is -- increased by h_displayed and the row counter is incremented ma_row_start := ma_row_start + r01_h_displayed; row_counter <= row_counter + 1; end if; else -- Next scan line. Count in twos in interlaced sync+video mode if r08_interlace = "11" then line_counter <= line_counter + 2; line_counter(0) <= '0'; -- Force to even else line_counter <= line_counter + 1; end if; end if; -- Memory address preset to row start at the beginning of each -- scan line ma_i <= ma_row_start; else -- Increment horizontal counter h_counter <= h_counter + 1; -- Increment memory address ma_i <= ma_i + 1; end if; end if; end process; -- Signals to mark hsync and half way points for generating -- vsync in even and odd fields process(h_counter) begin h_sync_start <= '0'; h_half_way <= '0'; if h_counter = r02_h_sync_pos then h_sync_start <= '1'; end if; if h_counter = "0" & r02_h_sync_pos(7 downto 1) then h_half_way <= '1'; end if; end process; -- Video timing and sync counters process(CLOCK,nRESET) begin if nRESET = '0' then -- H h_display <= '0'; hs <= '0'; h_sync_counter <= (others => '0'); -- V v_display <= '0'; vs <= '0'; v_sync_counter <= (others => '0'); elsif rising_edge(CLOCK) and CLKEN = '1' then -- Horizontal active video if h_counter = 0 then -- Start of active video h_display <= '1'; end if; if h_counter = r01_h_displayed then -- End of active video h_display <= '0'; end if; -- Horizontal sync if h_sync_start = '1' or hs = '1' then -- In horizontal sync hs <= '1'; h_sync_counter <= h_sync_counter + 1; else h_sync_counter <= (others => '0'); end if; if h_sync_counter = r03_h_sync_width then -- Terminate hsync after h_sync_width (0 means no hsync so this -- can immediately override the setting above) hs <= '0'; end if; -- Vertical active video if row_counter = 0 then -- Start of active video v_display <= '1'; end if; if row_counter = r06_v_displayed then -- End of active video v_display <= '0'; end if; -- Vertical sync occurs either at the same time as the horizontal sync (even fields) -- or half a line later (odd fields) if (odd_field = '0' and h_sync_start = '1') or (odd_field = '1' and h_half_way = '1') then if (row_counter = r07_v_sync_pos and line_counter = 0) or vs = '1' then -- In vertical sync vs <= '1'; v_sync_counter <= v_sync_counter + 1; else v_sync_counter <= (others => '0'); end if; if v_sync_counter = r03_v_sync_width and vs = '1' then -- Terminate vsync after v_sync_width (0 means 16 lines so this is -- masked by 'vs' to ensure a full turn of the counter in this case) vs <= '0'; end if; end if; end if; end process; -- Address generation process(CLOCK,nRESET) variable slv_line : std_logic_vector(4 downto 0); begin if nRESET = '0' then RA <= (others => '0'); MA <= (others => '0'); elsif rising_edge(CLOCK) and CLKEN = '1' then slv_line := std_logic_vector(line_counter); -- Character row address is just the scan line counter delayed by -- one clock to line up with the syncs. if r08_interlace = "11" then -- In interlace sync and video mode the LSb is determined by the -- field number. The line counter counts up in 2s in this case. RA <= slv_line(4 downto 1) & (slv_line(0) or odd_field); else RA <= slv_line; end if; -- Internal memory address delayed by one cycle as well MA <= std_logic_vector(ma_i); end if; end process; -- Cursor control process(CLOCK,nRESET) variable cursor_line : std_logic; begin -- Internal cursor enable signal delayed by 1 clock to line up -- with address outputs if nRESET = '0' then cursor_i <= '0'; cursor_line := '0'; elsif rising_edge(CLOCK) and CLKEN = '1' then if h_display = '1' and v_display = '1' and ma_i = r14_cursor_h & r15_cursor_l then if line_counter = 0 then -- Suppress wrap around if last line is > max scan line cursor_line := '0'; end if; if line_counter = r10_cursor_start then -- First cursor scanline cursor_line := '1'; end if; -- Cursor output is asserted within the current cursor character -- on the selected lines only cursor_i <= cursor_line; if line_counter = r11_cursor_end then -- Last cursor scanline cursor_line := '0'; end if; else -- Cursor is off in all character positions apart from the -- selected one cursor_i <= '0'; end if; end if; end process; -- Light pen capture process(CLOCK,nRESET) begin if nRESET = '0' then lpstb_i <= '0'; r16_light_pen_h <= (others => '0'); r17_light_pen_l <= (others => '0'); elsif rising_edge(CLOCK) and CLKEN = '1' then -- Register light-pen strobe input lpstb_i <= LPSTB; if LPSTB = '1' and lpstb_i = '0' then -- Capture address on rising edge r16_light_pen_h <= ma_i(13 downto 8); r17_light_pen_l <= ma_i(7 downto 0); end if; end if; end process; end architecture;