diff --git a/cores/bbc/fpga/mist/bbc_mist_top.qsf b/cores/bbc/fpga/mist/bbc_mist_top.qsf index 44ccddd..3c2aaec 100644 --- a/cores/bbc/fpga/mist/bbc_mist_top.qsf +++ b/cores/bbc/fpga/mist/bbc_mist_top.qsf @@ -165,13 +165,13 @@ set_location_assignment PLL_1 -to CLOCKS|altpll_component|auto_generated|pll1 set_global_assignment -name VERILOG_FILE ../rtl/sigma_delta_dac.v set_global_assignment -name VERILOG_FILE ../rtl/audio.v set_global_assignment -name VERILOG_FILE ../../rtl/adc.v -set_global_assignment -name VERILOG_FILE ../../rtl/mc6845.v set_global_assignment -name VERILOG_FILE ../../rtl/ps2_intf.v set_global_assignment -name VERILOG_FILE ../../rtl/vidproc.v set_global_assignment -name VERILOG_FILE ../../rtl/keyboard.v set_global_assignment -name VERILOG_FILE ../../rtl/clocks.v set_global_assignment -name VERILOG_FILE ../../rtl/address_decode.v set_global_assignment -name VERILOG_FILE ../../rtl/bbc.v +set_global_assignment -name VHDL_FILE ../../rtl/mc6845.vhd set_global_assignment -name VHDL_FILE ../../rtl/via6522.vhd set_global_assignment -name VHDL_FILE ../../rtl/saa5050/saa5050_rom_dual_port.vhd set_global_assignment -name VHDL_FILE ../../rtl/saa5050/saa5050.vhd diff --git a/cores/bbc/rtl/bbc.v b/cores/bbc/rtl/bbc.v index 47b4f6f..67b8583 100644 --- a/cores/bbc/rtl/bbc.v +++ b/cores/bbc/rtl/bbc.v @@ -127,8 +127,6 @@ wire crtc_cursor; reg crtc_lpstb; wire [13:0] crtc_ma; wire [4:0] crtc_ra; -wire crtc_interlace; -wire crtc_odd_field; // Decoded display address after address translation for hardware // scrolling @@ -426,9 +424,7 @@ mc6845 CRTC ( .CURSOR(crtc_cursor), .LPSTB(crtc_lpstb), .MA(crtc_ma), - .RA(crtc_ra), - .ODDFIELD(crtc_odd_field), - .INTERLACE(crtc_interlace) + .RA(crtc_ra) ); // no sound in the simulator. diff --git a/cores/bbc/rtl/mc6845.v b/cores/bbc/rtl/mc6845.v deleted file mode 100644 index b8cdd74..0000000 --- a/cores/bbc/rtl/mc6845.v +++ /dev/null @@ -1,625 +0,0 @@ -`timescale 1 ns / 1 ns // timescale for following modules - - -// 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 -// - -module mc6845 ( - CLOCK, - CLKEN, - nRESET, - ENABLE, - R_nW, - RS, - DI, - DO, - VSYNC, - HSYNC, - DE, - CURSOR, - LPSTB, - MA, - RA, - ODDFIELD, - INTERLACE); - - -input CLOCK; -input CLKEN; -input nRESET; -input ENABLE; -input R_nW; -input RS; -input [7:0] DI; -output [7:0] DO; -output VSYNC; -output HSYNC; -output DE; -output CURSOR; -input LPSTB; -output [13:0] MA; -output [4:0] RA; -output ODDFIELD; -output INTERLACE; - -reg [7:0] DO; -wire VSYNC; -wire HSYNC; -wire DE; -wire CURSOR; - -// Memory interface -reg [13:0] MA; -reg [4:0] RA; -wire ODDFIELD; -wire INTERLACE; -reg [4:0] addr_reg; -// Currently addressed register - -// These are write-only -reg [7:0] r00_h_total; -// Horizontal total, chars -reg [7:0] r01_h_displayed; -// Horizontal active, chars -reg [7:0] r02_h_sync_pos; -// Horizontal sync position, chars -reg [3:0] r03_v_sync_width; -// Vertical sync width, scan lines (0=16 lines) -reg [3:0] r03_h_sync_width; -// Horizontal sync width, chars (0=no sync) -reg [6:0] r04_v_total; -// Vertical total, character rows -reg [4:0] r05_v_total_adj; -// Vertical offset, scan lines -reg [6:0] r06_v_displayed; -// Vertical active, character rows -reg [6:0] r07_v_sync_pos; -// Vertical sync position, character rows -reg [1:0] r08_interlace; -reg [4:0] r09_max_scan_line_addr; -reg [1:0] r10_cursor_mode; -reg [4:0] r10_cursor_start; -// Cursor start, scan lines -reg [4:0] r11_cursor_end; -// Cursor end, scan lines -reg [5:0] r12_start_addr_h; -reg [7:0] r13_start_addr_l; - -// These are read/write -reg [5:0] r14_cursor_h; -reg [7:0] r15_cursor_l; - -// These are read-only -reg [5:0] r16_light_pen_h; -reg [7:0] r17_light_pen_l; - -// Timing generation -// Horizontal counter counts position on line -reg [7:0] h_counter; - -// HSYNC counter counts duration of sync pulse -reg [3:0] h_sync_counter; - -// Row counter counts current character row -reg [6:0] row_counter; - -// Line counter counts current line within each character row -reg [4:0] line_counter; - -// VSYNC counter counts duration of sync pulse -reg [3:0] v_sync_counter; - -// Field counter counts number of complete fields for cursor flash -reg [5:0] field_counter; - -// Internal signals -wire h_sync_start; -wire h_half_way; -reg h_display; -reg hs; -reg v_display; -reg vs; -reg odd_field; -reg [13:0] ma_i; -wire [13:0] ma_row_start; -// Start address of current character row -reg cursor_i; -reg lpstb_i; -reg [13:0] process_2_ma_row_start; -reg [4:0] process_2_max_scan_line; -wire [4:0] slv_line; -reg process_6_cursor_line; - -// Internal cursor enable signal delayed by 1 clock to line up -// with address outputs - -assign ODDFIELD = odd_field; -assign INTERLACE = r08_interlace[0]; -assign HSYNC = hs; -// External HSYNC driven directly from internal signal -assign VSYNC = vs; -// External VSYNC driven directly from internal signal -assign DE = h_display & v_display; - -// Cursor output generated combinatorially from the internal signal in -// accordance with the currently selected cursor mode -assign CURSOR = r10_cursor_mode === 2'b 00 ? cursor_i : - r10_cursor_mode === 2'b 01 ? 1'b 0 : - r10_cursor_mode === 2'b 10 ? cursor_i & field_counter[4] : - cursor_i & field_counter[5]; - -// Synchronous register access. Enabled on every clock. - -always @(posedge CLOCK) begin - - if (nRESET === 1'b 0) begin - - // Reset registers to defaults - addr_reg <= 'd0; - r00_h_total <= 'd0; - r01_h_displayed <= 'd0; - r02_h_sync_pos <= 'd0; - r03_v_sync_width <= {4{1'b 0}}; - r03_h_sync_width <= {4{1'b 0}}; - r04_v_total <= 'd0; - r05_v_total_adj <= 'd0; - r06_v_displayed <= 'd0; - r07_v_sync_pos <= 'd0; - r08_interlace <= {2{1'b 0}}; - r09_max_scan_line_addr <= 'd0; - r10_cursor_mode <= {2{1'b 0}}; - r10_cursor_start <= 'd0; - r11_cursor_end <= 'd0; - r12_start_addr_h <= 'd0; - r13_start_addr_l <= 'd0; - r14_cursor_h <= 'd0; - r15_cursor_l <= 'd0; - DO <= 'd0; - end - else begin - if (ENABLE === 1'b 1) begin - if (R_nW === 1'b 1) begin - - // Read - case (addr_reg) - 5'b 01110: begin - DO <= {2'b 00, r14_cursor_h}; - end - - 5'b 01111: begin - DO <= r15_cursor_l; - end - - 5'b 10000: begin - DO <= {2'b 00, r16_light_pen_h}; - end - - 5'b 10001: begin - DO <= r17_light_pen_l; - end - - default: begin - DO <= 'd0; - end - - endcase - end - else begin - if (RS === 1'b 0) begin - addr_reg <= DI[4:0]; - // Write - end - else begin - case (addr_reg) - 5'b 00000: begin - r00_h_total <= DI; - end - - 5'b 00001: begin - r01_h_displayed <= DI; - end - - 5'b 00010: begin - r02_h_sync_pos <= DI; - end - - 5'b 00011: begin - r03_v_sync_width <= DI[7:4]; - r03_h_sync_width <= DI[3:0]; - end - - 5'b 00100: begin - r04_v_total <= DI[6:0]; - end - - 5'b 00101: begin - r05_v_total_adj <= DI[4:0]; - end - - 5'b 00110: begin - r06_v_displayed <= DI[6:0]; - end - - 5'b 00111: begin - r07_v_sync_pos <= DI[6:0]; - end - - 5'b 01000: begin - r08_interlace <= DI[1:0]; - end - - 5'b 01001: begin - r09_max_scan_line_addr <= DI[4:0]; - end - - 5'b 01010: begin - r10_cursor_mode <= DI[6:5]; - r10_cursor_start <= DI[4:0]; - end - - 5'b 01011: begin - r11_cursor_end <= DI[4:0]; - end - - 5'b 01100: begin - r12_start_addr_h <= DI[5:0]; - end - - 5'b 01101: begin - r13_start_addr_l <= DI[7:0]; - end - - 5'b 01110: begin - r14_cursor_h <= DI[5:0]; - end - - 5'b 01111: begin - r15_cursor_l <= DI[7:0]; - end - - default: - ; - - endcase - end - end - end - end -end - -// registers - -always @(posedge CLOCK) begin - - if (nRESET === 1'b 0) begin - - // H - h_counter <= 'd0; - - // V - line_counter <= 'd0; - row_counter <= 'd0; - odd_field <= 1'b 0; - - // Fields (cursor flash) - field_counter <= 'd0; - - // Addressing - process_2_ma_row_start = 'd0; - ma_i <= 'd0; - end - else if (CLKEN === 1'b 1 ) begin - - // Horizontal counter increments on each clock, wrapping at - // h_total - if (h_counter === r00_h_total) begin - - // h_total reached - h_counter <= 'd0; - - // In interlace sync + video mode mask off the LSb of the - // max scan line address - if (r08_interlace === 2'b 11) begin - process_2_max_scan_line = {r09_max_scan_line_addr[4:1], 1'b 0}; - end - else begin - process_2_max_scan_line = r09_max_scan_line_addr; - end - - // Scan line counter increments, wrapping at max_scan_line_addr - if (line_counter === process_2_max_scan_line) begin - - // Next character row - // FIXME: No support for v_total_adj yet - line_counter <= 'd0; - - if (row_counter === r04_v_total) begin - - // 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'b 1) begin - odd_field <= ~odd_field; - end - else begin - odd_field <= 1'b 0; - end - - // Address is loaded from start address register at the top of - // each field and the row counter is reset - process_2_ma_row_start = {r12_start_addr_h, r13_start_addr_l}; - row_counter <= 'd0; - - // Increment field counter - field_counter <= field_counter + 1; - - // On all other character rows within the field the row start address is - // increased by h_displayed and the row counter is incremented - end - else begin - process_2_ma_row_start = process_2_ma_row_start + r01_h_displayed; - row_counter <= row_counter + 1; - end - - // Next scan line. Count in twos in interlaced sync+video mode - end - else begin - if (r08_interlace === 2'b 11) begin - line_counter <= line_counter + 2; - line_counter[0] <= 1'b 0; - // Force to even - end - else begin - line_counter <= line_counter + 1; - end - end - - // Memory address preset to row start at the beginning of each - // scan line - ma_i <= process_2_ma_row_start; - - // Increment horizontal counter - end - else begin - h_counter <= h_counter + 1; - - // Increment memory address - ma_i <= ma_i + 1; - end - end -end - -// Signals to mark hsync and half way points for generating -// vsync in even and odd fields - -// Horizontal, vertical and address counters - -assign h_sync_start = h_counter === r02_h_sync_pos; -assign h_half_way = h_counter === {1'b 0, r02_h_sync_pos[7:1]}; - -// Video timing and sync counters - -always @(posedge CLOCK) begin - - if (nRESET === 1'b 0) begin - - // H - h_display <= 1'b 0; - hs <= 1'b 0; - h_sync_counter <= {4{1'b 0}}; - - // V - v_display <= 1'b 0; - vs <= 1'b 0; - v_sync_counter <= {4{1'b 0}}; - end - else if (CLKEN === 1'b 1 ) begin - - // Horizontal active video - if (h_counter === 0) begin - - // Start of active video - h_display <= 1'b 1; - end - - if (h_counter === r01_h_displayed) begin - - // End of active video - h_display <= 1'b 0; - end - - // Horizontal sync - if (h_sync_start === 1'b 1 | hs === 1'b 1) begin - - // In horizontal sync - hs <= 1'b 1; - h_sync_counter <= h_sync_counter + 1; - end - else begin - h_sync_counter <= {4{1'b 0}}; - end - - if (h_sync_counter === r03_h_sync_width) begin - - // Terminate hsync after h_sync_width (0 means no hsync so this - // can immediately override the setting above) - hs <= 1'b 0; - end - - // Vertical active video - if (row_counter === 0) begin - - // Start of active video - v_display <= 1'b 1; - end - - if (row_counter === r06_v_displayed) begin - - // End of active video - v_display <= 1'b 0; - end - - // Vertical sync occurs either at the same time as the horizontal sync (even fields) - // or half a line later (odd fields) - if (odd_field === 1'b 0 & h_sync_start === 1'b 1 | - odd_field === 1'b 1 & h_sync_start === 1'b 1) begin - if (row_counter === r07_v_sync_pos & line_counter === 0 | - vs === 1'b 1) begin - - // In vertical sync - vs <= 1'b 1; - v_sync_counter <= v_sync_counter + 1; - end - else begin - v_sync_counter <= {4{1'b 0}}; - end - - if (v_sync_counter === r03_v_sync_width & vs === 1'b 1) begin - - // 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 <= 1'b 0; - end - end - end -end - -// Address generation - -assign slv_line = line_counter; - -always @(posedge CLOCK) begin - - if (nRESET === 1'b 0) begin - RA <= 'd0; - MA <= 'd0; - end - else if (CLKEN === 1'b 1 ) begin - - // Character row address is just the scan line counter delayed by - // one clock to line up with the syncs. - if (r08_interlace === 2'b 11) begin - - // 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:1], (slv_line[0] | odd_field)}; - end - else begin - RA <= slv_line; - end - - // Internal memory address delayed by one cycle as well - MA <= ma_i; - end -end - -// Cursor control -always @(posedge CLOCK) begin - - if (nRESET === 1'b 0) begin - cursor_i <= 1'b 0; - process_6_cursor_line = 1'b 0; - end - else if (CLKEN === 1'b 1 ) begin - if (h_display === 1'b 1 & v_display === 1'b 1 & - ma_i === {r14_cursor_h, r15_cursor_l}) begin - if (line_counter === 0) begin - - // Suppress wrap around if last line is > max scan line - process_6_cursor_line = 1'b 0; - end - - if (line_counter === r10_cursor_start) begin - - // First cursor scanline - process_6_cursor_line = 1'b 1; - end - - // Cursor output is asserted within the current cursor character - // on the selected lines only - cursor_i <= process_6_cursor_line; - if (line_counter === r11_cursor_end) begin - - // Last cursor scanline - process_6_cursor_line = 1'b 0; - end - - // Cursor is off in all character positions apart from the - // selected one - end - else begin - cursor_i <= 1'b 0; - end - end -end - -// Light pen capture -// Host-accessible registers -always @(posedge CLOCK) begin - - if (nRESET === 1'b 0) begin - lpstb_i <= 1'b 0; - r16_light_pen_h <= 'd0; - r17_light_pen_l <= 'd0; - end - else if (CLKEN === 1'b 1 ) begin - - // Register light-pen strobe input - lpstb_i <= LPSTB; - - if (LPSTB === 1'b 1 & lpstb_i === 1'b 0) begin - - // Capture address on rising edge - r16_light_pen_h <= ma_i[13:8]; - r17_light_pen_l <= ma_i[7:0]; - - end - end -end - - - - -endmodule // module mc6845 - diff --git a/cores/bbc/rtl/mc6845.vhd b/cores/bbc/rtl/mc6845.vhd new file mode 100644 index 0000000..8bf8fe9 --- /dev/null +++ b/cores/bbc/rtl/mc6845.vhd @@ -0,0 +1,579 @@ +-- 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 +-- +-- Corrected cursor flash rate +-- Fixed incorrect positioning of cursor when over left most character +-- Fixed timing of VSYNC +-- Fixed interlaced timing (add an extra line) +-- Implemented r05_v_total_adj +-- Implemented delay parts of r08_interlace (see Hitacht HD6845SP datasheet) +-- +-- (C) 2015 David Banks +-- +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; + + VGA : 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(7 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(4 downto 0); + +-- Internal signals +signal h_sync_start : std_logic; +signal v_sync_start : std_logic; +signal h_display : std_logic; +signal h_display_early : std_logic; +signal hs : std_logic; +signal v_display : std_logic; +signal v_display_early : 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; +signal de0 : std_logic; +signal de1 : std_logic; +signal de2 : std_logic; +signal cursor0 : std_logic; +signal cursor1 : std_logic; +signal cursor2 : std_logic; + + +begin + HSYNC <= hs; -- External HSYNC driven directly from internal signal + VSYNC <= vs; -- External VSYNC driven directly from internal signal + + de0 <= h_display and v_display; + + -- In Mode 7 DE Delay is set to 01, but in our implementation no delay is needed + -- TODO: Fix SAA5050 + DE <= de0 when r08_interlace(5 downto 4) = "00" else + de0 when r08_interlace(5 downto 4) = "01" else -- not accurate, should be de1 + de2 when r08_interlace(5 downto 4) = "10" else + '0'; + + -- Cursor output generated combinatorially from the internal signal in + -- accordance with the currently selected cursor mode + cursor0 <= cursor_i when r10_cursor_mode = "00" else + '0' when r10_cursor_mode = "01" else + (cursor_i and field_counter(3)) when r10_cursor_mode = "10" else + (cursor_i and field_counter(4)); + + -- In Mode 7 Cursor Delay is set to 10, but in our implementation one one cycle is needed + -- TODO: Fix SAA5050 + CURSOR <= cursor0 when r08_interlace(7 downto 6) = "00" else + cursor1 when r08_interlace(7 downto 6) = "01" else + cursor1 when r08_interlace(7 downto 6) = "10" else -- not accurate, should be cursor2 + '0'; + + -- 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 "01100" => + DO <= "00" & std_logic_vector(r12_start_addr_h); + when "01101" => + DO <= std_logic_vector(r13_start_addr_l); + 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(7 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); + variable adj_scan_line : unsigned(4 downto 0); + variable in_adj : std_logic; + variable need_adj : std_logic; + 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'); + + in_adj := '0'; + + elsif rising_edge(CLOCK) then + if 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'); + + -- Compute + if r05_v_total_adj /= 0 or odd_field = '1' then + need_adj := '1'; + else + need_adj := '0'; + end if; + + -- Compute the max scan line for this row + if in_adj = '0' then + -- This is a normal row, so use r09_max_scan_line_addr + if VGA = '1' then + -- So Mode 7 value of 18 becomes 19 (giving 20 rows per character) + max_scan_line := r09_max_scan_line_addr + 1; + else + max_scan_line := r09_max_scan_line_addr; + end if; + else + -- This is the "adjust" row, so use r05_v_total_adj + if VGA = '1' then + -- So Mode7 value of 2 becomes 4 (giving 31 * 20 + 4 = 624 lines) + max_scan_line := r05_v_total_adj + 1; + else + max_scan_line := r05_v_total_adj - 1; + end if; + -- If interlaced, the odd field contains an additional scan line + if odd_field = '1' then + if r08_interlace(1 downto 0) = "11" then + max_scan_line := max_scan_line + 2; + else + max_scan_line := max_scan_line + 1; + end if; + end if; + end if; + + -- In interlace sync + video mode mask off the LSb of the + -- max scan line address + if r08_interlace(1 downto 0) = "11" and VGA = '0' then + max_scan_line(0) := '0'; + end if; + + if line_counter = max_scan_line and ((need_adj = '0' and row_counter = r04_v_total) or in_adj = '1') then + + line_counter <= (others => '0'); + + -- 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' and VGA = '0' 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; + + -- Reset the in extra time flag + in_adj := '0'; + + elsif in_adj = '0' and line_counter = max_scan_line then + -- Scan line counter increments, wrapping at max_scan_line_addr + -- Next character row + line_counter <= (others => '0'); + -- 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; + -- Test if we are entering the adjust phase, and set + -- in_adj accordingly + if row_counter = r04_v_total and need_adj = '1' then + in_adj := '1'; + end if; + else + -- Next scan line. Count in twos in interlaced sync+video mode + if r08_interlace(1 downto 0) = "11" and VGA = '0' 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 if; + end process; + + -- Signals to mark hsync and and vsync in even and odd fields + process(h_counter, r00_h_total, r02_h_sync_pos, odd_field) + begin + h_sync_start <= '0'; + v_sync_start <= '0'; + + if h_counter = r02_h_sync_pos then + h_sync_start <= '1'; + end if; + + -- dmb: measurements on a real beeb confirm this is the actual + -- 6845 behaviour. i.e. in non-interlaced mode the start of vsync + -- coinscides with the start of the active display, and in intelaced + -- mode the vsync of the odd field is delayed by half a scan line + if (odd_field = '0' and h_counter = 0) or (odd_field = '1' and h_counter = "0" & r00_h_total(7 downto 1)) then + v_sync_start <= '1'; + end if; + end process; + + h_display_early <= '1' when h_counter < r01_h_displayed else '0'; + v_display_early <= '1' when row_counter < r06_v_displayed else '0'; + + -- 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) then + if CLKEN = '1' then + -- Horizontal active video + h_display <= h_display_early; + + -- 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 + v_display <= v_display_early; + + -- Vertical sync occurs either at the same time as the horizontal sync (even fields) + -- or half a line later (odd fields) + if (v_sync_start = '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 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) then + if 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(1 downto 0) = "11" and VGA = '0' then + RA <= slv_line(4 downto 1) & (slv_line(0) or odd_field); + else + RA <= slv_line(4 downto 1) & (slv_line(0) xor VGA); + end if; + -- Internal memory address delayed by one cycle as well + MA <= std_logic_vector(ma_i); + end if; + 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) then + if CLKEN = '1' then + if h_display_early = '1' and v_display_early = '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 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) then + if 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 if; + end process; + + -- Delayed CURSOR and DE (selected by R08) + process(CLOCK,nRESET) + begin + if rising_edge(CLOCK) then + if CLKEN = '1' then + de1 <= de0; + de2 <= de1; + cursor1 <= cursor0; + cursor2 <= cursor1; + end if; + end if; + end process; + +end architecture; diff --git a/cores/bbc/rtl/vidproc.v b/cores/bbc/rtl/vidproc.v index 34c368c..e3fad56 100644 --- a/cores/bbc/rtl/vidproc.v +++ b/cores/bbc/rtl/vidproc.v @@ -153,7 +153,7 @@ assign CLKEN_CRTC = CLKEN & clken_counter[0] & clken_counter[1] & clken_counter[ // mode is selected. This is used for reloading the shift register as well as // counting cursor pixels assign mhz4_clken = CLKEN & ~clken_counter[2] & clken_counter[1] & clken_counter[0]; -assign clken_fetch = mhz4_clken & (clken_counter[3] | r0_crtc_2mhz); +assign clken_fetch = mhz4_clken & (~clken_counter[3] | r0_crtc_2mhz); wire [7:0] shiftreg_nxt = clken_fetch ? DI_RAM : clken_pixel ? {shiftreg[6:0], 1'b 1} : shiftreg; @@ -188,9 +188,6 @@ assign cursor_invert = cursor_active & (r0_cursor0 & ~(cursor_counter[0] | curso (r0_cursor1 & cursor_counter[0] & ~cursor_counter[1]) | (r0_cursor2 & cursor_counter[1])); -// delayed cursor for teletext -always @(posedge CLOCK) if (mhz4_clken) cursor_invert_delayed <= cursor_invert; - always @(posedge CLOCK) begin : process_4 if (~nRESET) begin @@ -247,13 +244,11 @@ wire green_val = dot_val[3] & r0_flash ^ ~dot_val[1]; wire blue_val = dot_val[3] & r0_flash ^ ~dot_val[2]; // Display enable signal delayed by one clock -always @(posedge CLOCK) begin - if (mhz4_clken) begin - delayed_disen1 <= DISEN; - delayed_disen2 <= delayed_disen1; - end -end -wire delayed_disen = r0_crtc_2mhz ? delayed_disen1 : delayed_disen2; +reg delayed_disen; +always @(posedge CLOCK) if (mhz4_clken) delayed_disen <= DISEN; + +// delayed cursor for teletext +always @(posedge CLOCK) if (CLKEN) cursor_invert_delayed <= cursor_invert; always @(posedge CLOCK) begin