/* * | *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