summaryrefslogtreecommitdiffstats
path: root/quartus/mc6845.vhd
diff options
context:
space:
mode:
Diffstat (limited to 'quartus/mc6845.vhd')
-rw-r--r--quartus/mc6845.vhd481
1 files changed, 481 insertions, 0 deletions
diff --git a/quartus/mc6845.vhd b/quartus/mc6845.vhd
new file mode 100644
index 0000000..31208ea
--- /dev/null
+++ b/quartus/mc6845.vhd
@@ -0,0 +1,481 @@
+-- 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;
+
+