//============================================================================
// Sinclair ZX Spectrum ULA
//
// This module contains video support.
//
// Note: There is no reset signal in this VGA design since all relevant
//       counters will reset themselves within one display frame as the
//       pixel clock keeps ticking.
//
//  Copyright (C) 2014-2016  Goran Devic
//
//  This program 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 2 of the License, or (at your option)
//  any later version.
//
//  This program 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, write to the Free Software Foundation, Inc.,
//  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//============================================================================
module video
(
    input wire clk_pix,         // Input VGA pixel clock of 25.175 MHz

    output wire [3:0] VGA_R,    // Output VGA R component
    output wire [3:0] VGA_G,    // Output VGA G component
    output wire [3:0] VGA_B,    // Output VGA B component
    output reg VGA_HS,          // Output VGA horizontal sync
    output reg VGA_VS,          // Output VGA vertical sync
    output wire vs_nintr,       // Vertical retrace interrupt

    output wire [12:0] vram_address,// Address request to the video RAM
    input wire [7:0] vram_data, // Data read from the video RAM

    input wire [2:0] border     // Border color index value
);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// VGA 640x480 Sync pulses generator
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
reg [9:0] vga_hc;               // Horizontal counter
reg [9:0] vga_vc;               // Vertical counter
reg [4:0] frame;                // Frame counter, used for the flash attribute

always @(posedge clk_pix)
begin
    vga_hc <= vga_hc + 10'b1;   // With each pixel clock, advance the horizontal counter
    //---------------------------------------------------------------
    // Horizontal sync and line end timings
    //---------------------------------------------------------------
    case (vga_hc)
        96:     VGA_HS <= 1;
        800: begin
                VGA_HS <= 0;
                vga_hc <= 0;
                vga_vc <= vga_vc + 10'b1;
             end
    endcase
    //---------------------------------------------------------------
    // Vertical sync and display end timings
    //---------------------------------------------------------------
    case (vga_vc)
        2:      VGA_VS <= 1;
        525: begin
                VGA_VS <= 0;
                vga_vc <= 0;
                frame  <= frame + 5'b1;
             end
    endcase
end

// Generate interrupt at around the time of the vertical retrace start
assign vs_nintr = (vga_vc=='0 && vga_hc[9:7]=='0)? '0 : '1;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// VGA active display area 640x480
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
wire disp_enable;
assign disp_enable = vga_hc>=144 && vga_hc<784 && vga_vc>=35 && vga_vc<515;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Fetch screen data from RAM based on the current video counters
// Spectrum resolution of 256x192 is line-doubled to 512x384 sub-frame
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
wire screen_en;
assign screen_en = vga_hc>=208 && vga_hc<720 && vga_vc>=83 && vga_vc<467;

reg [7:0] bits_prefetch;        // Line bitmap data prefetch register
reg [7:0] attr_prefetch;        // Attribute data prefetch register

// At the first clock of each new character, prefetch values are latched into these:
reg [7:0] bits;                 // Current line bitmap data register
reg [7:0] attr;                 // Current attribute data register

wire [4:0] pix_x;               // Column 0-31
wire [7:0] pix_y;               // Active display pixel Y coordinate
// We use 16 clocks for 1 byte of display; also prefetch 1 byte (+16)
wire [9:0] xd = vga_hc-10'd192; // =vga_hc-208+16
assign pix_x = xd[8:4];         // Effectively divide by 16
wire [9:0] yd = vga_vc-10'd83;  // Lines are (also) doubled vertically
assign pix_y = yd[8:1];         // Effectively divide by 2

always @(posedge clk_pix)
begin
    case (vga_hc[3:0])
                // Format the address into the bitmap which is a swizzle of coordinate parts
        10:     vram_address <= {pix_y[7:6], pix_y[2:0], pix_y[5:3], pix_x};
        12:     begin
                    bits_prefetch <= vram_data;
                    // Format the address into the attribute map
                    vram_address <= {3'b110, pix_y[7:3], pix_x};
                end
        14:     attr_prefetch <= vram_data;
        // Last tick before a new character: load working bitmap and attribute registers
        15:     begin
                    attr <= attr_prefetch;
                    bits <= bits_prefetch;
                end
    endcase
end

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Pixel data generator
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
wire [2:0] ink;                 // INK color (index into the palette)
wire [2:0] paper;               // PAPER color
wire bright;                    // BRIGHT attribute bit
wire flash;                     // FLASH attribute bit
wire pixbit;                    // Current pixel to render
wire inverted;                  // Are the pixel's attributes inverted?

// Output a pixel bit based on the VGA horizontal counter. This could have been
// a shift register but a mux works as well since we are writing out each pixel
// twice (required by this VGA clock rate)
always @(*) // always_comb
begin
    case (vga_hc[3:1])
        0:      pixbit = bits[7];
        1:      pixbit = bits[6];
        2:      pixbit = bits[5];
        3:      pixbit = bits[4];
        4:      pixbit = bits[3];
        5:      pixbit = bits[2];
        6:      pixbit = bits[1];
        7:      pixbit = bits[0];
    endcase
end

assign flash  = attr[7];
assign bright = attr[6];
assign inverted = flash & frame[4];
assign ink    = inverted? attr[5:3] : attr[2:0];
assign paper  = inverted? attr[2:0] : attr[5:3];

// The final color index depends on where we are (active display area, border) and
// whether we are rendering INK or PAPER color, including the brightness bit
assign cindex = screen_en? pixbit? {bright,ink} : {bright,paper} : {1'b0,border[2:0]};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Color lookup table
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
wire [3:0] cindex;
wire [11:0] pix_rgb;

always @(*) // always_comb
begin
    case (cindex[3:0])
        // Normal color
        0:   pix_rgb = 12'h000; // BLACK
        1:   pix_rgb = 12'h00D; // BLUE
        2:   pix_rgb = 12'hD00; // RED
        3:   pix_rgb = 12'hD0D; // MAGENTA
        4:   pix_rgb = 12'h0D0; // GREEN
        5:   pix_rgb = 12'h0DD; // CYAN
        6:   pix_rgb = 12'hDD0; // YELLOW
        7:   pix_rgb = 12'hDDD; // WHITE
        // "Bright" bit is set
        8:   pix_rgb = 12'h000; // BLACK remains black
        9:   pix_rgb = 12'h00F;
        10:  pix_rgb = 12'hF00;
        11:  pix_rgb = 12'hF0F;
        12:  pix_rgb = 12'h0F0;
        13:  pix_rgb = 12'h0FF;
        14:  pix_rgb = 12'hFF0;
        15:  pix_rgb = 12'hFFF;
    endcase
end

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// VGA RGB output drivers
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
assign VGA_R[3:0] = disp_enable? pix_rgb[11:8] : '0;
assign VGA_G[3:0] = disp_enable? pix_rgb[7:4]  : '0;
assign VGA_B[3:0] = disp_enable? pix_rgb[3:0]  : '0;

endmodule
