aboutsummaryrefslogtreecommitdiff
/*
 * | *WISHBONE DATASHEET*                                                      |
 * |---------------------------------------------------------------------------|
 * | *Description*                   | *Specification*                         |
 * |---------------------------------+-----------------------------------------|
 * | General description             | VGA signal generator (640x480@60Hz)     |
 * |---------------------------------+-----------------------------------------|
 * | Supported cycles                | SLAVE, pipelined READ/WRITE             |
 * |---------------------------------+-----------------------------------------|
 * | Data port, size                 | 16-bit                                  |
 * | Data port, granularity          | 16-bit                                  |
 * | Data port, maximum operand size | 16-bit                                  |
 * | Data transfer ordering          | Big endian and/or little endian         |
 * | Data transfer ordering          | Undefined                               |
 * | Address port, size              | 11-bit                                  |
 * |---------------------------------+-----------------------------------------|
 * | Clock frequency constraints     | NONE (determined by memory primitive,   |
 * |                                 |     about 100 MHz in case of iCE40HX8K) |
 * |---------------------------------+-----------------------------------------|
 * |                                 | *Signal name*    | *WISHBONE Equiv.*    |
 * |                                 |------------------+----------------------|
 * |                                 | ACK_O            | ACK_O                |
 * |                                 | ADR_I            | ADR_I()              |
 * | Supported signal list and cross | CLK_I            | CLK_I                |
 * |     reference to equivalent     | DAT_I            | DAT_I()              |
 * |     WISHBONE signals            | DAT_O            | DAT_O()              |
 * |                                 | STB_I            | STB_I                |
 * |                                 | WE_I             | WE_I                 |
 * |                                 | RST_I            | RST_I                |
 * |                                 | STALL_O          | STALL_O              |
 * |---------------------------------+-----------------------------------------|
 * |                                 | Circuit assumes the use of synchronous  |
 * | Special requirements            |     RAM with asynchronour read          |
 * |                                 |     inreffable by synthesis software.   |
 * |---------------------------------+-----------------------------------------|
 * |                                 | Module provides a simple text mode,     |
 * |                                 | that can be used to display 30 lines of |
 * |                                 | 80 characters each. Module can be used  |
 * |                                 | as follows:                             |
 * | Additional information          |     * ASCII values of characters to     |
 * |                                 |       display should be written to text |
 * |                                 |       mode memory.                      |
 * |                                 |     * A non-zero value should be        |
 * |                                 |       written to the "power-on"         |
 * |                                 |       register to start generating VGA  |
 * |                                 |       signal.                           |
 * |                                 |     * Zero should be written to the     |
 * |                                 |       "power-on" register to stop       |
 * |                                 |       generating VGA output.            |
 * |                                 |     Also see the memory map below.      |
 * |                                 |     The "power-on" register can be      |
 * |                                 |     read at any time to check if VGA    |
 * |                                 |     output is being generated. It reads |
 * |                                 |     a non-zero value if module is       |
 * |                                 |     operating and zero otherwise.       |
 * |                                 |     Each byte of the text mode memory   |
 * |                                 |     corresponds to one character on     |
 * |                                 |     video display. They are arranged by |
 * |                                 |     rows, from up to down. I.e. writing |
 * |                                 |     value 65 to the first 2 bytes of    |
 * |                                 |     text video memory shall result in   |
 * |                                 |     character "A" being printed in 2    |
 * |                                 |     leftmost fields of the topmost line |
 * |                                 |     of the display. Writing a byte      |
 * |                                 |     value outside ASCII range shall     |
 * |                                 |     result in a replacement character   |
 * |                                 |     being written. Text mode memory is  |
 * |                                 |     2560 bytes big. The last 160 bytes  |
 * |                                 |     are not used by video display and   |
 * |                                 |     don't serve any special purpose.    |
 * |                                 |     The font used for ASCII characters  |
 * |                                 |     is defined in an external file and  |
 * |                                 |     can be substituted.                 |
 */

/*
 * The memory map is as follows:
 *   h000 - h4FF - VGA text memory
 *   h500        - VGA power-on reg
 *
 * Accessing higher addresses than specified results in UNDEFINED behavior.
 */

`default_nettype none

module vga
  #(
    parameter FONT_FILE = "font.mem"
    )
   (
    output wire        ACK_O,
    input wire 	       CLK_I,
    input wire [10:0]  ADR_I,
    input wire [15:0]  DAT_I,
    output wire [15:0] DAT_O,
    input wire 	       RST_I,
    input wire 	       STB_I,
    input wire 	       WE_I,
    output wire        STALL_O,

    /* Non-wishbone */
    input wire 	       clock_25mhz,
    output wire        h_sync,
    output wire        v_sync,
    output wire [2:0]  red,
    output wire [2:0]  green,
    output wire [2:0]  blue
    );

   reg 		       powered_on;

   parameter LINES = V_ACTIVE_VIDEO / 16; /* 30 */
   parameter LINE_LENGTH = H_ACTIVE_VIDEO / 8; /* 80 */
   parameter CHARACTERS_ON_SCREEN = LINE_LENGTH * LINES; /* 2400 */

   /*
    * We want to store 2400 characters in memory. 2 chars go into one 16-bit
    * word, so wee need a 1200x16 array. One embedded RAM block in iCE40 FPGAs
    * is able to store 256x16 bits. This means, instead of choosing 1200 as
    * array size, we can choose 1280, which is a multiple of 256.
    */
   reg [15:0]  text_memory [1279 : 0];

   /* Enable writes and reads of text_memory using wishbone interface. */

   reg [15:0]  data_at_adr;
   reg 	       outputting_data;
   reg 	       ack;

   assign DAT_O = outputting_data ? data_at_adr : {16{powered_on}};
   assign ACK_O = ack;
   assign STALL_O = 1'b0;

   always @ (posedge CLK_I) begin
      ack <= STB_I && !RST_I;

      if (STB_I && WE_I) begin
	 if (ADR_I < 1280)
	   text_memory[ADR_I] <= DAT_I;
	 else
	   powered_on <= DAT_I != 16'b0;
      end

      outputting_data <= ADR_I < 1280;

      data_at_adr <= text_memory[ADR_I];
   end

   /* Non-wishbone part - generate 640x480 60Hz VGA output */

   reg 		       powered_on_latched;

   always @ (posedge clock_25mhz)
     powered_on_latched <= powered_on;

   parameter H_POLARITY = 1'b0;
   parameter H_FRONT_PORCH = 8;
   parameter H_SYNC = 96;
   parameter H_BACK_PORCH = 40;
   parameter H_LEFT_BORDER = 8;
   parameter H_ACTIVE_VIDEO = 640;
   parameter H_RIGHT_BORDER = 8;

   reg [9:0] 	       h_counter;

   parameter H_STAGE_RB_OR_FP = 0; /* right border of front porch */
   parameter H_STAGE_SYNC = 1;
   parameter H_STAGE_BP_OR_LB = 2; /* back porch or left border */
   parameter H_STAGE_ACTIVE_VIDEO = 3;

   reg [1:0] 	       h_stage;

   always @ (posedge clock_25mhz) begin
      if (powered_on_latched) begin
	 if ((h_stage == H_STAGE_RB_OR_FP &&
	      h_counter + 1 == H_RIGHT_BORDER + H_FRONT_PORCH) ||
	     (h_stage == H_STAGE_SYNC &&
	      h_counter + 1 == H_SYNC) ||
	     (h_stage == H_STAGE_BP_OR_LB &&
	      h_counter + 1 == H_BACK_PORCH + H_LEFT_BORDER) ||
	     (h_stage == H_STAGE_ACTIVE_VIDEO &&
	      h_counter + 1 == H_ACTIVE_VIDEO)) begin
	    h_stage <= h_stage + 1;
	    h_counter <= 0;
	 end else begin // if ((h_stage == H_STAGE_RB_OR_FP &&...
	    h_counter <= h_counter + 1;
	 end
      end else begin // if (powered_on_latched)
	 h_stage <= H_STAGE_RB_OR_FP;
	 h_counter <= H_RIGHT_BORDER - 1;
      end // else: !if(powered_on_latched)
   end // always @ (posedge clock_25mhz)

   wire end_of_line;
   assign end_of_line = h_stage == H_STAGE_RB_OR_FP &&
			h_counter + 1 == H_RIGHT_BORDER;

   parameter V_POLARITY = 1'b1;
   parameter V_FRONT_PORCH = 2;
   parameter V_SYNC = 2;
   parameter V_BACK_PORCH = 25;
   parameter V_TOP_BORDER = 8;
   parameter V_ACTIVE_VIDEO = 480;
   parameter V_BOTTOM_BORDER = 8;

   reg [8:0] v_counter;

   parameter V_STAGE_BB_OR_FP = 0; /* bottom border of front porch */
   parameter V_STAGE_SYNC = 1;
   parameter V_STAGE_BP_OR_TB = 2; /* back porch or top border */
   parameter V_STAGE_ACTIVE_VIDEO = 3;

   reg [1:0] 	       v_stage;

   always @ (posedge clock_25mhz) begin
      if (powered_on_latched) begin
	 if (end_of_line) begin
	    if ((v_stage == V_STAGE_BB_OR_FP &&
		 v_counter + 1 == V_BOTTOM_BORDER + V_FRONT_PORCH) ||
		(v_stage == V_STAGE_SYNC &&
		 v_counter + 1 == V_SYNC) ||
		(v_stage == V_STAGE_BP_OR_TB &&
		 v_counter + 1 == V_BACK_PORCH + V_TOP_BORDER) ||
		(v_stage == V_STAGE_ACTIVE_VIDEO &&
		 v_counter + 1 == V_ACTIVE_VIDEO)) begin
	       v_stage <= v_stage + 1;
	       v_counter <= 0;
	    end else begin // if ((v_stage == V_STAGE_BB_OR_FP &&...
	       v_counter <= v_counter + 1;
	    end
	 end // if (end_of_line)
      end else begin // if (powered_on_latched)
	 v_stage <= V_STAGE_BB_OR_FP;
	 v_counter <= V_BOTTOM_BORDER;
      end // else: !if(powered_on_latched)
   end // always @ (posedge clock_25mhz)

   reg [0:7] font [128 * 16 - 1 : 0];

   /* Should result in initialization of embedded RAM */
   initial begin
      $readmemb(FONT_FILE, font, 0, 128 * 16 - 1);
   end

   parameter FG_COLOR = {3'b010, 3'b111, 3'b101};
   parameter BG_COLOR = {3'b000, 3'b000, 3'b111};

   /* display this for non-ascii characters */
   wire [0:7]  replacement_char [0:16];
   assign replacement_char[0]  = 8'b00000000;
   assign replacement_char[1]  = 8'b00011000;
   assign replacement_char[2]  = 8'b00111100;
   assign replacement_char[3]  = 8'b01100110;
   assign replacement_char[4]  = 8'b01011010;
   assign replacement_char[5]  = 8'b01111010;
   assign replacement_char[6]  = 8'b01111010;
   assign replacement_char[7]  = 8'b01111010;
   assign replacement_char[8]  = 8'b01110110;
   assign replacement_char[9]  = 8'b01101110;
   assign replacement_char[10] = 8'b01101110;
   assign replacement_char[11] = 8'b01111110;
   assign replacement_char[12] = 8'b01101110;
   assign replacement_char[13] = 8'b00111100;
   assign replacement_char[14] = 8'b00011000;
   assign replacement_char[15] = 8'b00000000;

   wire [6:0]  char_x;
   wire [4:0]  char_y;
   wire [12:0] char_flat_idx;
   assign char_x = h_counter / 8;
   assign char_y = v_counter / 16;
   assign char_flat_idx = char_x + char_y * LINE_LENGTH;

   /*
    * hs[0], vs[0], fetched_memory_field, is_high_byte, char, char_pixel_x[0],
    * char_pixel_y and display_on[0] get loaded first, then, one tick later,
    * hs[1], vs[1], char_pixel_x[1], display_on[1], char_pixel_row,
    * replacement_pixel_row and is_ascii_char get loaded and finally, another
    * tick later, color gets loaded
    */
   reg [2:0]   hs;
   reg [2:0]   vs;
   reg [15:0]  fetched_memory_field;
   reg 	       is_high_byte;
   reg [2:0]   char_pixel_x [1:0];
   reg [3:0]   char_pixel_y;
   reg [1:0]   display_on;
   reg [0:7]   char_pixel_row;
   reg [0:7]   replacement_pixel_row;
   reg 	       is_ascii_char;
   reg [8:0]   color;

   wire [7:0]  char;
   wire [0:7]  pixel_row_to_use;
   wire        pixel_on;
   assign char = is_high_byte ? fetched_memory_field[15:8] :
		 fetched_memory_field[7:0];
   assign pixel_row_to_use = is_ascii_char ? char_pixel_row :
			     replacement_pixel_row;
   assign pixel_on = pixel_row_to_use[char_pixel_x[1]];

   /* Assign module's outputs */
   assign h_sync = hs[2];
   assign v_sync = vs[2];
   assign {red, green, blue} = color;

   always @ (posedge clock_25mhz) begin
      /* Stuff, that gets loaded first */
      if (h_stage == H_STAGE_SYNC)
	hs[0] <= H_POLARITY;
      else
	hs[0] <= ~H_POLARITY;

      if (v_stage == V_STAGE_SYNC)
	vs[0] <= V_POLARITY;
      else
	vs[0] <= ~V_POLARITY;

      fetched_memory_field <= text_memory[char_flat_idx[12:1]];
      is_high_byte <= char_flat_idx[0];

      char_pixel_x[0] <= h_counter % 8;
      char_pixel_y <= v_counter % 16;

      display_on[0] <= h_stage == H_STAGE_ACTIVE_VIDEO &&
		       v_stage == V_STAGE_ACTIVE_VIDEO;

      /* Stuff, that gets loaded one tick later */
      hs[1] <= hs[0];
      vs[1] <= vs[0];

      char_pixel_x[1] <= char_pixel_x[0];

      display_on[1] <= display_on[0];

      char_pixel_row <= font[char[6:0] * 16 + char_pixel_y];
      replacement_pixel_row <= replacement_char[char_pixel_y];

      is_ascii_char <= ~char[7];

      /* Stuff, that gets loaded another tick later */
      hs[2] <= hs[1];
      vs[2] <= vs[1];

      if (!display_on)
	color <= 9'b0;
      else if (pixel_on)
	color <= FG_COLOR;
      else
	color <= BG_COLOR;
   end // always @ (posedge clock_25mhz)

`ifdef SIMULATION
   /* avoid undefined values */
   initial begin
      powered_on <= 0;
      powered_on_latched <= 0;
      ack <= 0;

      hs <= {3{~H_POLARITY}};
      vs <= {3{~V_POLARITY}};
      fetched_memory_field <= 16'b0;
      is_high_byte <= 0;
      char_pixel_x[0] <= 0;
      char_pixel_x[1] <= 0;
      char_pixel_y <= 0;
      display_on <= 2'b0;
      char_pixel_row <= 8'b0;
      replacement_pixel_row <= 8'b0;
      is_ascii_char <= 0;
      color <= 9'b0;

      h_counter <= 0;
      h_stage <= H_STAGE_RB_OR_FP;

      v_counter <= 0;
      v_stage <= V_STAGE_BB_OR_FP;
   end
`endif
endmodule // vga