1
0
mirror of https://github.com/mist-devel/mist-board.git synced 2026-02-07 00:17:07 +00:00
Files
mist-devel.mist-board/cores/bbc/rtl/mc6845.v
2015-10-02 09:56:46 +02:00

628 lines
18 KiB
Verilog

`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,
CLKEN_ADR,
nRESET,
ENABLE,
R_nW,
RS,
DI,
DO,
VSYNC,
HSYNC,
DE,
CURSOR,
LPSTB,
MA,
RA,
ODDFIELD,
INTERLACE);
input CLOCK;
input CLKEN;
input CLKEN_ADR;
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_ADR === 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