aboutsummaryrefslogtreecommitdiffstats
path: root/mc6845.vhd
diff options
context:
space:
mode:
authorMike Stirling <opensource@mikestirling.co.uk>2011-07-12 20:52:34 +0100
committerMike Stirling <opensource@mikestirling.co.uk>2011-07-12 20:52:34 +0100
commit3975fdfe4275347dab666e43dbfdaebe80c58ff8 (patch)
tree5558fbfb78e6196545fd2fe47465a714d2461e73 /mc6845.vhd
downloadfpga-bbc-3975fdfe4275347dab666e43dbfdaebe80c58ff8.tar.gz
fpga-bbc-3975fdfe4275347dab666e43dbfdaebe80c58ff8.tar.bz2
fpga-bbc-3975fdfe4275347dab666e43dbfdaebe80c58ff8.zip
Initial commit. Some modules imported from experimental 6502 platform. Project added for Quartus 9.1
Diffstat (limited to 'mc6845.vhd')
-rw-r--r--mc6845.vhd419
1 files changed, 419 insertions, 0 deletions
diff --git a/mc6845.vhd b/mc6845.vhd
new file mode 100644
index 0000000..d2098c4
--- /dev/null
+++ b/mc6845.vhd
@@ -0,0 +1,419 @@
+-- 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);
+ 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');
+
+ -- Scan line counter increments, wrapping at max_scan_line_addr
+ if line_counter = r09_max_scan_line_addr 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 chracter 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
+ line_counter <= line_counter + 1;
+ 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)
+ begin
+ if nRESET = '0' then
+ RA <= (others => '0');
+ MA <= (others => '0');
+ elsif rising_edge(CLOCK) and CLKEN = '1' then
+ -- Character row address is just the scan line counter delayed by
+ -- one clock to line up with the syncs.
+ -- FIXME: Interlace sync + video mode needs special row addressing
+ RA <= std_logic_vector(line_counter);
+ -- 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 ma_i = r14_cursor_h & r15_cursor_l then
+ 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;
+
+