// // scandoubler.v // // Copyright (c) 2015 Till Harbaum // // 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 . // TODO: Delay vsync one line module scandoubler ( // system interface input clk_sys, input bypass, input ce_divider, output pixel_ena, // scanlines (00-none 01-25% 10-50% 11-75%) input [1:0] scanlines, // shifter video interface 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 reg hs_out, output reg vs_out, output reg [5:0] r_out, output reg [5:0] g_out, output reg [5:0] b_out ); parameter HCNT_WIDTH = 9; parameter COLOR_DEPTH = 6; // pixel clock divider reg [1:0] i_div; reg ce_x1, ce_x2; always @(posedge clk_sys) begin reg last_hs_in; last_hs_in <= hs_in; if(last_hs_in & !hs_in) begin i_div <= 2'b00; end else begin i_div <= i_div + 2'd1; end end always @(*) begin if (!ce_divider) begin ce_x1 = (i_div == 2'b01); ce_x2 = i_div[0]; end else begin ce_x1 = i_div[0]; ce_x2 = 1'b1; end end assign pixel_ena = bypass ? ce_x1 : ce_x2; // --------------------- 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; always @(*) begin if (COLOR_DEPTH == 6) begin b = sd_out[5:0]; g = sd_out[11:6]; r = sd_out[17:12]; end else if (COLOR_DEPTH == 2) begin b = {3{sd_out[1:0]}}; g = {3{sd_out[3:2]}}; r = {3{sd_out[5:4]}}; end else if (COLOR_DEPTH == 1) begin b = {6{sd_out[0]}}; g = {6{sd_out[1]}}; r = {6{sd_out[2]}}; end else begin b = { sd_out[COLOR_DEPTH-1:0], sd_out[COLOR_DEPTH-1 -:(6-COLOR_DEPTH)] }; g = { sd_out[COLOR_DEPTH*2-1:COLOR_DEPTH], sd_out[COLOR_DEPTH*2-1 -:(6-COLOR_DEPTH)] }; r = { sd_out[COLOR_DEPTH*3-1:COLOR_DEPTH*2], sd_out[COLOR_DEPTH*3-1 -:(6-COLOR_DEPTH)] }; end end always @(posedge clk_sys) begin if(bypass) begin r_out <= r; g_out <= g; b_out <= b; hs_out <= hs_sd; vs_out <= vs_sd; end else if(ce_x2) begin hs_out <= hs_sd; vs_out <= vs_sd; // reset scanlines at every new screen if(vs_out != vs_in) scanline <= 0; // toggle scanlines at begin of every hsync if(hs_out && !hs_sd) scanline <= !scanline; // if no scanlines or not a scanline if(!scanline || !scanlines) begin r_out <= r; g_out <= g; b_out <= b; end else begin case(scanlines) 1: begin // reduce 25% = 1/2 + 1/4 r_out <= {1'b0, r[5:1]} + {2'b00, r[5:2] }; g_out <= {1'b0, g[5:1]} + {2'b00, g[5:2] }; b_out <= {1'b0, b[5:1]} + {2'b00, b[5:2] }; end 2: begin // reduce 50% = 1/2 r_out <= {1'b0, r[5:1]}; g_out <= {1'b0, g[5:1]}; b_out <= {1'b0, b[5:1]}; end 3: begin // reduce 75% = 1/4 r_out <= {2'b00, r[5:2]}; g_out <= {2'b00, g[5:2]}; b_out <= {2'b00, b[5:2]}; end endcase end end end // scan doubler output register wire [COLOR_DEPTH*3-1:0] sd_out = bypass ? sd_bypass_out : sd_buffer_out; // ================================================================== // ======================== the line buffers ======================== // ================================================================== // 2 lines of 2**HCNT_WIDTH pixels 3*COLOR_DEPTH bit RGB (* ramstyle = "no_rw_check" *) reg [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] hs_max; reg [HCNT_WIDTH-1:0] hs_rise; reg [HCNT_WIDTH-1:0] hcnt; always @(posedge clk_sys) begin reg hsD, vsD; if(ce_x1) begin hsD <= hs_in; // falling edge of hsync indicates start of line if(hsD && !hs_in) begin hs_max <= hcnt; hcnt <= 0; end else begin hcnt <= hcnt + 1'd1; end // save position of rising edge if(!hsD && hs_in) hs_rise <= hcnt; vsD <= vs_in; if(vsD != vs_in) line_toggle <= 0; // begin of incoming hsync if(hsD && !hs_in) line_toggle <= !line_toggle; sd_buffer[{line_toggle, hcnt}] <= {r_in, g_in, b_in}; end end // ================================================================== // ==================== output timing generation ==================== // ================================================================== reg [COLOR_DEPTH*3-1:0] sd_buffer_out, sd_bypass_out; reg [HCNT_WIDTH-1:0] sd_hcnt; reg hs_sd, vs_sd; // timing generation runs 32 MHz (twice the input signal analysis speed) always @(posedge clk_sys) begin reg hsD; if(ce_x2) begin hsD <= hs_in; // output counter synchronous to input and at twice the rate sd_hcnt <= sd_hcnt + 1'd1; if(hsD && !hs_in) sd_hcnt <= hs_max; if(sd_hcnt == hs_max) sd_hcnt <= 0; // replicate horizontal sync at twice the speed if(sd_hcnt == hs_max) hs_sd <= 0; if(sd_hcnt == hs_rise) hs_sd <= 1; // read data from line sd_buffer sd_buffer_out <= sd_buffer[{~line_toggle, sd_hcnt}]; vs_sd <= vs_in; end if(bypass) begin sd_bypass_out <= {r_in, g_in, b_in}; hs_sd <= hs_in; vs_sd <= vs_in; end end endmodule