1
0
mirror of https://github.com/mist-devel/mist-board.git synced 2026-02-05 07:34:41 +00:00

BBC: update CRTC from BeebFpga

This commit is contained in:
Gyorgy Szombathelyi
2020-05-19 18:35:31 +02:00
parent c550481866
commit 7fb259bf19
5 changed files with 587 additions and 642 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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

579
cores/bbc/rtl/mc6845.vhd Normal file
View File

@@ -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;

View File

@@ -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