1
0
mirror of https://github.com/Gehstock/Mist_FPGA.git synced 2026-01-31 13:51:56 +00:00
Files
Gehstock.Mist_FPGA/common/mist/scandoubler.v
2023-06-02 21:35:38 +02:00

275 lines
7.7 KiB
Verilog

//
// scandoubler.v
//
// Copyright (c) 2015 Till Harbaum <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// TODO: Delay vsync one line
// AMR - generates and output a pixel clock with a reliable phase relationship with
// with the scandoubled hsync pulse. Allows the incoming data to be sampled more
// sparsely, reducing block RAM usage. ce_x1/x2 are replaced with a ce_divider
// which is the largest value the counter will reach before resetting - so 3'111 to
// divide clk_sys by 8, 3'011 to divide by 4, 3'101 to divide by six.
// Also now has a bypass mode, in which the incoming data will be scaled to the output
// width but otherwise unmodified. Simplifies the rest of the video chain.
module scandoubler
(
// system interface
input clk_sys,
input bypass,
// Pixelclock
input [2:0] ce_divider, // 0 - clk_sys/4, 1 - clk_sys/2, 2 - clk_sys/3, 3 - clk_sys/4, etc.
output pixel_ena,
// scanlines (00-none 01-25% 10-50% 11-75%)
input [1:0] scanlines,
// shifter video interface
input hb_in,
input vb_in,
input hs_in,
input vs_in,
input [COLOR_DEPTH-1:0] r_in,
input [COLOR_DEPTH-1:0] g_in,
input [COLOR_DEPTH-1:0] b_in,
// output interface
output hb_out,
output vb_out,
output hs_out,
output vs_out,
output [5:0] r_out,
output [5:0] g_out,
output [5:0] b_out
);
parameter HCNT_WIDTH = 9; // Resolution of scandoubler buffer
parameter COLOR_DEPTH = 6; // Bits per colour to be stored in the buffer
parameter HSCNT_WIDTH = 12; // Resolution of hsync counters
// --------------------- create output signals -----------------
// latch everything once more to make it glitch free and apply scanline effect
reg scanline;
reg [5:0] r;
reg [5:0] g;
reg [5:0] b;
wire [COLOR_DEPTH*3-1:0] sd_mux = bypass ? {r_in, g_in, b_in} : sd_out[COLOR_DEPTH*3-1:0];
always @(*) begin
if (COLOR_DEPTH == 6) begin
b = sd_mux[5:0];
g = sd_mux[11:6];
r = sd_mux[17:12];
end else if (COLOR_DEPTH == 2) begin
b = {3{sd_mux[1:0]}};
g = {3{sd_mux[3:2]}};
r = {3{sd_mux[5:4]}};
end else if (COLOR_DEPTH == 1) begin
b = {6{sd_mux[0]}};
g = {6{sd_mux[1]}};
r = {6{sd_mux[2]}};
end else begin
b = { sd_mux[COLOR_DEPTH-1:0], sd_mux[COLOR_DEPTH-1 -:(6-COLOR_DEPTH)] };
g = { sd_mux[COLOR_DEPTH*2-1:COLOR_DEPTH], sd_mux[COLOR_DEPTH*2-1 -:(6-COLOR_DEPTH)] };
r = { sd_mux[COLOR_DEPTH*3-1:COLOR_DEPTH*2], sd_mux[COLOR_DEPTH*3-1 -:(6-COLOR_DEPTH)] };
end
end
reg [12:0] r_mul;
reg [12:0] g_mul;
reg [12:0] b_mul;
wire scanline_bypass = (!scanline) | (!(|scanlines)) | bypass;
// More subtle variant of the scanlines effect.
// 0 00 -> 1000000 0x40 - bypass / inert mode
// 1 01 -> 0111010 0x3a - 25%
// 2 10 -> 0101110 0x2e - 50%
// 3 11 -> 0011010 0x1a - 75%
wire [6:0] scanline_coeff = scanline_bypass ?
7'b1000000 : {~(&scanlines),scanlines[0],1'b1,~scanlines[0],2'b10};
always @(posedge clk_sys) begin
if(ce_x2) begin
hs_o <= hs_sd;
vs_o <= vs_in;
// reset scanlines at every new screen
if(vs_o != vs_in) scanline <= 0;
// toggle scanlines at begin of every hsync
if(hs_o && !hs_sd) scanline <= !scanline;
r_mul<=r*scanline_coeff;
g_mul<=g*scanline_coeff;
b_mul<=b*scanline_coeff;
end
end
wire [5:0] r_o = r_mul[11:6];
wire [5:0] g_o = g_mul[11:6];
wire [5:0] b_o = b_mul[11:6];
wire hb_o = hb_sd;
wire vb_o = vb_sd;
reg hs_o;
reg vs_o;
// Output multiplexing
wire blank_out = hb_out | vb_out;
assign r_out = blank_out ? {COLOR_DEPTH{1'b0}} : bypass ? r : r_o;
assign g_out = blank_out ? {COLOR_DEPTH{1'b0}} : bypass ? g : g_o;
assign b_out = blank_out ? {COLOR_DEPTH{1'b0}} : bypass ? b : b_o;
assign hb_out = bypass ? hb_in : hb_o;
assign vb_out = bypass ? vb_in : vb_o;
assign hs_out = bypass ? hs_in : hs_o;
assign vs_out = bypass ? vs_in : vs_o;
assign pixel_ena = bypass ? ce_x1 : ce_x2;
// scan doubler output register
reg [3+COLOR_DEPTH*3-1:0] sd_out;
// ==================================================================
// ======================== the line buffers ========================
// ==================================================================
// 2 lines of 2**HCNT_WIDTH pixels 3*COLOR_DEPTH bit RGB
(* ramstyle = "no_rw_check" *) reg [3+COLOR_DEPTH*3-1:0] sd_buffer[2*2**HCNT_WIDTH];
// use alternating sd_buffers when storing/reading data
reg line_toggle;
// total hsync time (in 16MHz cycles), hs_total reaches 1024
reg [HCNT_WIDTH-1:0] hcnt;
reg [HSCNT_WIDTH:0] hs_max;
reg [HSCNT_WIDTH:0] hs_rise;
reg [HSCNT_WIDTH:0] synccnt;
// Input pixel clock, aligned with input sync:
wire[2:0] ce_divider_adj = |ce_divider ? ce_divider : 3'd3; // 0 = clk/4 for compatiblity
reg [2:0] ce_divider_in;
reg [2:0] ce_divider_out;
reg [2:0] i_div;
wire ce_x1 = (i_div == ce_divider_in);
always @(posedge clk_sys) begin
reg hsD, vsD;
reg vbD;
// Pixel logic on x1 clkena
if(ce_x1) begin
hcnt <= hcnt + 1'd1;
vbD <= vb_in;
sd_buffer[{line_toggle, hcnt}] <= {vbD & ~vb_in, ~vbD & vb_in, hb_in, r_in, g_in, b_in};
end
// Generate pixel clock
i_div <= i_div + 1'd1;
if (i_div==ce_divider_adj) i_div <= 3'b000;
synccnt <= synccnt + 1'd1;
hsD <= hs_in;
if(hsD && !hs_in) begin
// At hsync latch the ce_divider counter limit for the input clock
// and pass the previous input clock limit to the output stage.
// This should give correct output if the pixel clock changes mid-screen.
ce_divider_out <= ce_divider_in;
ce_divider_in <= ce_divider_adj;
hs_max <= {1'b0,synccnt[HSCNT_WIDTH:1]};
hcnt <= 0;
synccnt <= 0;
i_div <= 3'b000;
end
// save position of rising edge
if(!hsD && hs_in) hs_rise <= {1'b0,synccnt[HSCNT_WIDTH:1]};
// begin of incoming hsync
if(hsD && !hs_in) line_toggle <= !line_toggle;
vsD <= vs_in;
if(vsD != vs_in) line_toggle <= 0;
end
// ==================================================================
// ==================== output timing generation ====================
// ==================================================================
reg [HSCNT_WIDTH:0] sd_synccnt;
reg [HCNT_WIDTH-1:0] sd_hcnt;
reg vb_sd = 0;
wire vb_on = sd_out[COLOR_DEPTH*3+1];
wire vb_off = sd_out[COLOR_DEPTH*3+2];
reg hb_sd = 0;
reg hs_sd = 0;
// Output pixel clock, aligned with output sync:
reg [2:0] sd_i_div;
wire ce_x2 = (sd_i_div == ce_divider_out) | (sd_i_div == {1'b0,ce_divider_out[2:1]});
// timing generation runs 32 MHz (twice the input signal analysis speed)
always @(posedge clk_sys) begin
reg hsD;
// Output logic on x2 clkena
if(ce_x2) begin
// output counter synchronous to input and at twice the rate
sd_hcnt <= sd_hcnt + 1'd1;
// read data from line sd_buffer
sd_out <= sd_buffer[{~line_toggle, sd_hcnt}];
if (vb_on) vb_sd <= 1;
if (vb_off) vb_sd <= 0;
hb_sd <= sd_out[COLOR_DEPTH*3];
end
// Framing logic on sysclk
sd_synccnt <= sd_synccnt + 1'd1;
hsD <= hs_in;
if(sd_synccnt == hs_max || (hsD && !hs_in)) begin
sd_synccnt <= 0;
sd_hcnt <= 0;
end
sd_i_div <= sd_i_div + 1'd1;
if (sd_i_div==ce_divider_adj) sd_i_div <= 3'b000;
// replicate horizontal sync at twice the speed
if(sd_synccnt == 0) begin
hs_sd <= 0;
sd_i_div <= 3'b000;
end
if(sd_synccnt == hs_rise) hs_sd <= 1;
end
endmodule