From 3975fdfe4275347dab666e43dbfdaebe80c58ff8 Mon Sep 17 00:00:00 2001 From: Mike Stirling Date: Tue, 12 Jul 2011 20:52:34 +0100 Subject: Initial commit. Some modules imported from experimental 6502 platform. Project added for Quartus 9.1 --- mc6845.vhd | 419 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 mc6845.vhd (limited to 'mc6845.vhd') 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; + + -- cgit v1.2.3