module display ( input clk, // 18.78 MHz input width, input reverse, input noblink, input testpattern, input reveal, output reg [10:0] a, input [7:0] d, output [10:0] ga, input [5:0] gd, output [12:0] bga, input [5:0] bgd, output reg [5:0] rgb, output vsync, output hsync, input [7:0] fg_ctl, input [1:0] fg_data, output fg_ack, output fg_rst ); // We use the standard VGA 640x480 monitor timings mode, htime = 31.77 // us (31.47 kHz), vtime = 16.68 ms (60 Hz) The standard VGA uses a // pixel clock of 25.175 MHz, we use 18.783 MHz, which is 25*3/4 for // 480 horizontal pixels; the error is about 0.6%, which is far, far // less than the margin of error in real systems. // // This gives us the following timings: // // Horizontal: // 480 pixels (80 char) graphics // 12 pixels ( 2 char) back porch/border // 72 pixels (12 char) sync // 30 pixels ( 5 char) front porch // ---------- // 594 pixels (31.68 us) // // Vertical: // 480 lines graphics (24 rows x 10 pixels x 2 scans/pixel) // 10 lines back porch/border // 2 lines sync // 33 lines front porch // --------- // 525 lines // // In this implementation we start timing with the display area in both // cases, *except* that we prefetch by one character, and therefore start // 1 character shy of the actual start of display. // // This gives us 4 clock cycles (@ 25 MHz = 160 ns) to get the character, // and an additional 4 clock cycles to get the bit representation of // that character. // // VGA monitors used the sync polarity to determine the mode, // especially the desired aspect ratio. Therefore, we want to use -hsync // and -vsync, - meaning active low, + meaning active high. parameter x_blank = 480+6; parameter x_sync = x_blank+(12-6); parameter x_front = x_sync+72; parameter x_max = 594; parameter y_blank = 480; parameter y_sync = y_blank+10; parameter y_front = y_sync+2; parameter y_max = 525; parameter hsync_minus = 1'b1; // -hsync parameter vsync_minus = 1'b1; // -vsync wire v_width; // Synchronized width reg hsync_q; // Horizontal sync active reg vsync_q; // Vertical sync active reg [9:0] x; // Horizontal pixel count reg [9:0] y; // Vertical pixel count reg [6:0] xchr; // Character column (0..99) reg [2:0] xpxl; // Pixel column in character (0..5) reg [4:0] ychr; // Character row (0..26) reg [3:0] ypxl; // Pixel row in character (0..9) wire [3:0] ypxlp = ypxl + 4'd1; // d:o but 1..10 reg [4:0] ylu; // Previous character line wire xvideo; // Non-blanked in the x direction wire yvideo; // Non-blanked in the y direction wire [10:0] a80; // Memory address assuming 80 columns wire [10:0] a80u; // Memory address assuming 80 columns (-1 line) wire [10:0] a40; // Memory address assuming 40 columns wire [10:0] a40u; // Memory address assuming 40 columns (-1 line) reg [4:0] scan_counter; // Counter of total scans (for flashing et al) reg [5:0] pixrow; // One character worth of pixels reg prefetch; // True for the prefetch character position // Fine Graphics control wire [7:0] fgctl_q; // Latched version of fg_ctl reg [1:0] fgpixel; // One fg pixel from FIFO // Block graphics data reg [5:0] block_rgb; // Current block graphics pixel reg [6:0] block_x; // Block graphics pixel X reg [2:0] block_xpxl; // Physical pixel inside block // We leverage ychr and ypxl for the vertical // For the current text line reg [2:0] curfg; // Foreground RGB reg [2:0] curbg; // Background RGB reg inverse; // Inverse video reg isgraph; // Graphic mode? reg isgsep; // Separated graphics? reg isgrel; // Hold graphics? reg isdble; // Double height? reg isflsh; // Flashing? reg ishide; // Hidden reg [7:0] thischar; // Character code currently processing // These refer to the same as above, but for the previous text line reg [2:0] oldfg; // Foreground RGB reg [2:0] oldbg; // Background RGB reg wasgraph; // Graphic mode? reg wasgsep; // Separated graphics? reg wasgrel; // Hold graphics? reg wasdble; // Double height? reg wasflsh; // Flashing? reg washide; // Hidden reg [7:0] thatchar; // Character code currently processing // The one we're currently displaying wire [2:0] fg = wasdble ? oldfg : curfg; wire [2:0] bg = wasdble ? oldbg : curbg; wire [7:0] somechar = wasdble ? thatchar : thischar; wire gsep = wasdble ? wasgsep : isgsep; wire do_flsh = wasdble ? wasflsh : isflsh; wire do_hide = wasdble ? washide : ishide; // Synchronize width input synchronize width_sync (.reset(1'b0), .clk(clk), .enable (1'b1), .d(width), .q(v_width)); // Synchronize fg_ctl input, update during vsync only synchronize #(.width(8)) fg_ctl_sync (.reset(1'b0), .clk(clk), .enable (~yvideo), .d(fg_ctl), .q(fgctl_q)); // Should we advance the character pixel? // We need to always advance at full speed during prefetch, or // we would have to start the prefetch earlier in 40-character mode. wire advance = v_width | x[0] | prefetch; // Address mapping for 40 and 80 characters assign a80[3:0] = xchr[3:0]; wire [3:0] x80middle = { 1'b0, xchr[6:4] }; wire [3:0] ymiddle = { ychr[4:3] , ychr[4:3] }; assign a80[7:4] = x80middle+ymiddle; assign a80[10:8] = ychr[2:0]; assign a40[2:0] = xchr[2:0]; wire [3:0] x40middle = { 1'b0, xchr[5:3] }; assign a40[6:3] = x40middle + ymiddle; assign a40[10:7] = { 1'b1, ychr[2:0] }; // Address mapping for 40 and 80 characters minus one line assign a80u[3:0] = xchr[3:0]; wire [3:0] yumiddle = { ylu[4:3] , ylu[4:3] }; assign a80u[7:4] = x80middle+yumiddle; assign a80u[10:8] = ylu[2:0]; assign a40u[2:0] = xchr[2:0]; assign a40u[6:3] = x40middle+yumiddle; assign a40u[10:7] = { 1'b1, ylu[2:0] }; // Final address mapping // Note: We read the current char between pixels 0 and 1, // and the char above between pixels 2 and 3; hence the use of xpxl[1]. always @(*) case ( { v_width, xpxl[1] } ) 2'b00: a = a40; 2'b01: a = a40u; 2'b10: a = a80; 2'b11: a = a80u; endcase // case( { v_width, xchr[1] } ) // Block graphics address mapping assign bga[3:0] = block_x[3:0]; wire [3:0] bgxmiddle = { 1'b0, block_x[6:4] }; wire [3:0] bgymiddle = { ypxlp[3:2], ypxlp[3:2] }; assign bga[7:4] = bgxmiddle + bgymiddle; assign bga[12:8] = ychr; // Character generator address mapping assign ga[10:4] = somechar[6:0]; assign ga[3:0] = wasdble ? { 1'b1, ypxl[3:1] } : isdble ? { 1'b0, ypxl[3:1] } : ypxl[3:0]; // Video enable signal assign xvideo = ( x < x_blank ); assign yvideo = ( y < y_blank ); assign hsync = hsync_q ^ hsync_minus; assign vsync = vsync_q ^ vsync_minus; assign fg_rst = vsync; // Flashing wire flash_on = scan_counter[4]; // Inverse video. The normal is a flashing inverse cursor, but // if "noblink" is asserted the cursor is steady inverted. If // "reverse" is asserted with invert everything *on top of that*... wire invert = (inverse & (flash_on|noblink))^reverse; // // Fine graphics // assign fg_ack = xvideo & yvideo & ~x[0]; wire [3:0] fg_argb; always @(posedge clk) if (~x[0]) fgpixel <= fg_data; fgcolrom fgcolrom ( .address ( { fgctl_q[6:0], fgpixel } ), .clock ( clk ), .q ( fg_argb ) ); // Synchronous logic always @(posedge clk) begin if ( ~xvideo | ~yvideo | prefetch ) rgb <= 6'b000000; // Blank else if ( testpattern ) // Pixel test pattern for LCD monitor calibration rgb <= {6{x[0] ^ y[0]}}; else if ( ~fgctl_q[7] & pixrow[5] & ~(do_flsh & ~flash_on) & ~(do_hide & ~reveal) ) rgb <= {fg[2],fg[2],fg[1],fg[1],fg[0],fg[0]} ^ {6{invert}}; else if (fgctl_q[7] | fg_argb[3]) rgb <= {fg_argb[2],fg_argb[2],fg_argb[1],fg_argb[1],fg_argb[0],fg_argb[0]} ^ {6{invert}}; else if (bg == 3'b000) rgb <= block_rgb ^ {6{invert}}; else rgb <= {bg[2],bg[2],bg[1],bg[1],bg[0],bg[0]} ^ {6{invert}}; // Sync pulses vsync_q <= ( y >= y_sync && y < y_front ); hsync_q <= ( x >= x_sync && x < x_front ); // Rotating shift register; may be overridden by the below // The rotation is so that if we're in GHOL mode we already // have the previous value if ( advance ) pixrow <= { pixrow[4:0], pixrow[5] }; // This code is run 6 times per character; regardless of v_width if ( advance ) begin case ( xpxl ) 3'b001: begin // Load and process character thischar <= d; end 3'b011: begin // Load and process previous-line character thatchar <= (ychr == 0) ? 8'h00 : d; end 3'b101: begin // Load a new pixel row? if ( xvideo ) begin // Attribute engine for current row if ( thischar[6:5] == 2'b00 ) begin // Control character casex ( thischar[4:0] ) 5'bx0xxx: begin curfg <= thischar[2:0]; isgraph <= thischar[4]; end 5'b0110x: isdble <= thischar[0]; 5'b11000: ishide <= 1'b1; 5'b1111x: isgrel <= thischar[0]; 5'b0100x: isflsh <= ~thischar[0]; 5'b11001: isgsep <= 1'b0; 5'b11010: isgsep <= 1'b1; 5'b11100: // BLBG begin curbg <= 3'b000; curfg <= curbg; end 5'b11101: // NWBG begin curbg <= curfg; curfg <= 3'b000; end endcase // casex( thischar[4:0] ) end // if ( thischar[6:5] == 2'b00 ) // Attribute engine for previous row if ( thatchar[6:5] == 2'b00 ) begin // Control character casex ( thatchar[4:0] ) 5'bx0xxx: begin oldfg <= thatchar[2:0]; wasgraph <= thatchar[4]; end 5'b0110x: wasdble <= thatchar[0]; 5'b11000: washide <= 1'b1; 5'b1111x: wasgrel <= thatchar[0]; 5'b0100x: wasflsh <= ~thatchar[0]; 5'b11001: wasgsep <= 1'b0; 5'b11010: wasgsep <= 1'b1; 5'b11100: // BLBG begin oldbg <= 3'b000; oldfg <= oldbg; end 5'b11101: // NWBG begin oldbg <= oldfg; oldfg <= 3'b000; end endcase // casex( thatchar[4:0] ) end // if ( thatchar[6:5] == 2'b00 ) // Character generation if ( wasdble ? (wasgraph & thatchar[5]) : (isgraph & thischar[5]) ) begin // Generate graphical character case ( ga[3:0] ) 4'h0, 4'h1: begin pixrow[5] <= somechar[0]; pixrow[4] <= somechar[0]; pixrow[3] <= somechar[0] & ~gsep; pixrow[2] <= somechar[1]; pixrow[1] <= somechar[1]; pixrow[0] <= somechar[1] & ~gsep; end 4'h2: begin pixrow[5] <= somechar[0] & ~gsep; pixrow[4] <= somechar[0] & ~gsep; pixrow[3] <= somechar[0] & ~gsep; pixrow[2] <= somechar[1] & ~gsep; pixrow[1] <= somechar[1] & ~gsep; pixrow[0] <= somechar[1] & ~gsep; end 4'h3, 4'h4, 4'h5: begin pixrow[5] <= somechar[2]; pixrow[4] <= somechar[2]; pixrow[3] <= somechar[2] & ~gsep; pixrow[2] <= somechar[3]; pixrow[1] <= somechar[3]; pixrow[0] <= somechar[3] & ~gsep; end 4'h6: begin pixrow[5] <= somechar[2] & ~gsep; pixrow[4] <= somechar[2] & ~gsep; pixrow[3] <= somechar[2] & ~gsep; pixrow[2] <= somechar[3] & ~gsep; pixrow[1] <= somechar[3] & ~gsep; pixrow[0] <= somechar[3] & ~gsep; end 4'h7, 4'h8: begin pixrow[5] <= somechar[4]; pixrow[4] <= somechar[4]; pixrow[3] <= somechar[4] & ~gsep; pixrow[2] <= somechar[6]; pixrow[1] <= somechar[6]; pixrow[0] <= somechar[6] & ~gsep; end 4'h9: begin pixrow[5] <= somechar[4] & ~gsep; pixrow[4] <= somechar[4] & ~gsep; pixrow[3] <= somechar[4] & ~gsep; pixrow[2] <= somechar[6] & ~gsep; pixrow[1] <= somechar[6] & ~gsep; pixrow[0] <= somechar[6] & ~gsep; end default: pixrow <= 5'bxxxxxx; endcase // case( ga[3:0] ) end // if ( wasdble ? (wasgraph & thatchar[5]) : (isgraph & thischar[5]) ) else if ( (somechar[6:5] != 2'b00) | (wasdble ? wasgrel : isgrel) ) pixrow <= gd; // Input from character ROM // Flash/inverse inverse <= somechar[7]; // This is no longer a prefetch character... prefetch <= 1'b0; end // if ( xvideo ) end // case: 3'b101 endcase // case( xpxl ) end // if ( advance ) // Block graphics - the block graphics pixels exactly match // 80-column characters if ( block_xpxl == 3'd5 ) block_rgb <= bgd; // Counters if ( x == x_max-1 ) begin x <= 10'd0; xchr <= 7'd0; xpxl <= 3'd0; block_x <= 7'd0; block_xpxl <= 3'd0; pixrow <= 6'b0; // Read-ahead spot is blank block_rgb <= 6'b0; prefetch <= 1'b1; // Prefetch character inverse <= 1'b0; // Not inverse video curfg <= 3'b111; // Default fg is white curbg <= 3'b000; // Default bg is black isgraph <= 1'b0; // Not graphic mode isflsh <= 1'b0; // Not flashing isgsep <= 1'b0; // Not separated isdble <= 1'b0; // Not double isgrel <= 1'b1; // Release graphics ishide <= 1'b0; // Not hidden oldfg <= 3'b111; // Default fg is white oldbg <= 3'b000; // Default bg is black wasgraph <= 1'b0; // Not graphic mode wasflsh <= 1'b0; // Not flashing wasgsep <= 1'b0; // Not separated wasdble <= 1'b0; // Not double wasgrel <= 1'b1; // Release graphics washide <= 1'b0; // Not hidden ylu <= ychr; // Previous character row if ( y == y_max-1 ) begin y <= 10'd0; ychr <= 6'd0; ypxl <= 5'd0; scan_counter <= scan_counter + 1; end else begin if ( y[0] ) // Double scanning begin if ( ypxl == 4'd9 ) begin ypxl <= 4'd0; ychr <= ychr + 1; end else ypxl <= ypxl + 1; end y <= y + 1; end // else: !if( y == y_max-1 ) end // if ( x == x_max-1 ) else begin if ( advance ) begin if ( xpxl == 3'd5 ) begin xpxl <= 0; xchr <= xchr + 1; end else xpxl <= xpxl + 1; end if ( block_xpxl == 3'd5 ) begin block_xpxl <= 0; block_x <= block_x + 1; end else block_xpxl <= block_xpxl + 1; x <= x + 1; end // else: !if( x == x_max-1 ) end // always @ (posedge clk) endmodule // display