aboutsummaryrefslogtreecommitdiff
path: root/design/vga.v
blob: 5e42866972feb9fa06181cf6bb117631a5adcf9e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
/*
 * | *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