aboutsummaryrefslogtreecommitdiff
path: root/design/vga.v
blob: e69907c6182ac3923263175ddcaec147d82a3bdd (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
`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