gateware

A collection of little open source FPGA hobby projects
git clone http://frotz.net/git/gateware.git
Log | Files | Refs | README

display.sv (7427B)


      1 // Copyright 2020, Brian Swetland <swetland@frotz.net>
      2 // Licensed under the Apache License, Version 2.0.
      3 
      4 `default_nettype none
      5 
      6 module display #(
      7 	// bits per pixel for rgb output
      8 	parameter BPP = 2,
      9 
     10 	// HEXMODE=1 skip even cells, display odd cells in hex, 2-wide
     11 	parameter HEXMODE = 0,
     12 
     13 	// WIDE=0 selects 80x30, 2.5KB VRAM
     14 	// WIDE=1 selects 40x30, 1.5KB VRAM
     15 	parameter WIDE = 1,
     16 
     17 	// MINIFONT=0 selects half-ascii (0 through 127) 4KB PROM
     18 	// MINIFONT=1 selects quarter-ascii (uppercase only) 2KB PROM
     19 	// (0..31 map to 32..63, 96..127 map to 64..95)
     20 	parameter MINIFONT = 0,
     21 
     22 	// RGB=1 enables fg/bg color storage in video ram
     23 	//       (doubles the size of video ram)
     24 	parameter RGB = 0,
     25 
     26 	// horizontal timing (all values -1)
     27 	parameter HZNT_FRONT = 15,
     28 	parameter HZNT_SYNC = 95,
     29 	parameter HZNT_BACK = 47,
     30 	parameter HZNT_ACTIVE = 639,
     31 
     32 	// vertical timing (all values -1)
     33 	parameter VERT_FRONT = 11,
     34 	parameter VERT_SYNC = 1,
     35 	parameter VERT_BACK = 29,
     36 	parameter VERT_ACTIVE = 479
     37 	)(
     38 	input wire clk,
     39 
     40 	output wire [BPP-1:0]red,
     41 	output wire [BPP-1:0]grn,
     42 	output wire [BPP-1:0]blu,
     43 	output wire hsync,
     44 	output wire vsync,
     45 	output wire active,
     46 	output wire frame,
     47 
     48 	input wire wclk,
     49 	input wire [11:0]waddr,
     50 	input wire [15:0]wdata,
     51 	input wire we
     52 );
     53 
     54 localparam PWIDTH = WIDE ? 16 : 8;
     55 localparam PCOUNT = WIDE ? 15 : 7;
     56 localparam VRAMSZ = WIDE ? 1536 : 2560;
     57 localparam VRAMAW = WIDE ? 11 : 12;
     58 localparam VRAMDW = RGB ? 16 : 8;
     59 
     60 localparam PROMSZ = MINIFONT ? 1024 : 2048;
     61 localparam PROMAW = MINIFONT ? 10 : 11;
     62 
     63 wire hs;
     64 wire vs;
     65 wire start_frame;
     66 wire start_line;
     67 wire pxl_accept;
     68 wire [9:0]pxl_x;
     69 wire [9:0]pxl_y;
     70 
     71 assign hsync = hs;
     72 assign vsync = vs;
     73 assign active = pxl_accept;
     74 assign frame = start_frame;
     75 
     76 display_timing #(
     77 	.HZNT_FRONT(HZNT_FRONT),
     78 	.HZNT_SYNC(HZNT_SYNC),
     79 	.HZNT_BACK(HZNT_BACK),
     80 	.HZNT_ACTIVE(HZNT_ACTIVE),
     81 	.VERT_FRONT(VERT_FRONT),
     82 	.VERT_SYNC(VERT_SYNC),
     83 	.VERT_BACK(VERT_BACK),
     84 	.VERT_ACTIVE(VERT_ACTIVE)
     85 	) timing (
     86 	.clk(clk),
     87 	.hsync(hs),
     88 	.vsync(vs),
     89 	.start_frame(start_frame),
     90 	.start_line(start_line),
     91 	.pxl_accept(pxl_accept),
     92 	.pxl_x(pxl_x),
     93 	.pxl_y(pxl_y)
     94 );
     95 
     96 // VIDEO RAM
     97 //
     98 reg [VRAMDW-1:0] video_ram[0:VRAMSZ-1];
     99 
    100 `ifdef HEX_PATHS
    101 initial $readmemh("hdl/display/vram-40x30.hex", video_ram);
    102 `else
    103 initial $readmemh("vram-40x30.hex", video_ram);
    104 `endif
    105 
    106 wire re;
    107 wire [11:0] raddr;
    108 reg [VRAMDW-1:0] vdata;
    109 
    110 always_ff @(posedge wclk) begin
    111 	if (we)
    112 		video_ram[waddr[VRAMAW-1:0]] <= wdata[VRAMDW-1:0];
    113 end
    114 
    115 always_ff @(posedge clk) begin
    116 	if (re)
    117 		vdata <= video_ram[raddr[VRAMAW-1:0]];
    118 end
    119 
    120 // PATTERN ROM
    121 //
    122 reg [7:0] pattern_rom [0:PROMSZ-1];
    123 
    124 generate
    125 `ifdef HEX_PATHS
    126 if (MINIFONT) initial $readmemh("hdl/display/fontdata-8x16x64.hex", pattern_rom);
    127 else initial $readmemh("hdl/display/fontdata-8x16x128.hex", pattern_rom);
    128 `else
    129 if (MINIFONT) initial $readmemh("fontdata-8x16x64.hex", pattern_rom);
    130 else initial $readmemh("fontdata-8x16x128.hex", pattern_rom);
    131 `endif
    132 endgenerate
    133 
    134 wire [PROMAW-1:0] prom_addr;
    135 
    136 wire [7:0]glyph;
    137 
    138 generate if(HEXMODE) begin
    139 hex2dec cvt(
    140 	.din(vram_raddr[0] ? vdata[3:0] : vdata[7:4]),
    141 	.dout(glyph)
    142 );
    143 end else begin
    144 assign glyph = vdata[7:0];
    145 end endgenerate
    146 
    147 // generate pattern rom address based on character id
    148 // from vram and the low bits of the display line
    149 generate
    150 if (MINIFONT) assign prom_addr = { glyph[6], glyph[4:0], pxl_y[3:0] };
    151 else assign prom_addr = { glyph[6:0], pxl_y[3:0] };
    152 endgenerate
    153 
    154 reg [7:0]prom_data;
    155 
    156 always_ff @(posedge clk) begin
    157 	prom_data <= pattern_rom[prom_addr];
    158 end
    159 
    160 reg [PWIDTH-1:0]pattern;
    161 reg [PWIDTH-1:0]pattern_next;
    162 reg [3:0]pattern_count;
    163 reg [3:0]pattern_count_next;
    164 reg [3:0]pattern_count_sub1;
    165 reg pattern_count_done;
    166 
    167 // pattern downcounter underflow (_done) used to trigger next character
    168 assign { pattern_count_done, pattern_count_sub1 } = { 1'b0, pattern_count } - 5'd1;
    169 
    170 reg load_character_addr = 1'b0;
    171 reg load_character_addr_next;
    172 
    173 reg [11:0]vram_raddr;
    174 reg [11:0]vram_raddr_next;
    175 
    176 wire [11:0]vram_raddr_add1 = vram_raddr + 12'd1;
    177 
    178 generate if (HEXMODE) begin
    179 assign raddr = { vram_raddr[11:1], 1'b0 };
    180 end else begin
    181 assign raddr = vram_raddr;
    182 end endgenerate
    183 
    184 assign re = load_character_addr;
    185 
    186 // Map pattern rom data to pattern 1:1 or 1:2 depending on WIDE
    187 wire [PWIDTH-1:0]pattern_expand;
    188 generate
    189 	if (WIDE) assign pattern_expand = {
    190 		prom_data[7], prom_data[7], prom_data[6], prom_data[6],
    191 		prom_data[5], prom_data[5], prom_data[4], prom_data[4],
    192 		prom_data[3], prom_data[3], prom_data[2], prom_data[2],
    193 		prom_data[1], prom_data[1], prom_data[0], prom_data[0] };
    194 	else assign pattern_expand = prom_data;
    195 endgenerate
    196 
    197 reg preload1 = 1'b0;
    198 reg preload1_next;
    199 reg preload2 = 1'b0;
    200 reg preload2_next;
    201 reg preload3 = 1'b0;
    202 reg preload3_next;
    203 
    204 // start of row in vram is high bits of y position x40 or x80
    205 wire [11:0]vram_rowbase;
    206 generate
    207 if (WIDE) assign vram_rowbase = { 3'b0, pxl_y[9:4], 3'b0 } + { pxl_y[9:4], 5'b0 };
    208 else assign vram_rowbase = { 2'b0, pxl_y[9:4], 4'b0 } + { pxl_y[9:4], 6'b0 };
    209 endgenerate
    210 
    211 always_comb begin
    212 	vram_raddr_next = vram_raddr;
    213 	load_character_addr_next = 1'b0;
    214 	pattern_next = pattern;
    215 	pattern_count_next = pattern_count;
    216 	preload1_next = 1'b0;
    217 	preload2_next = preload1;
    218 	preload3_next = preload2;
    219 
    220 	if (pxl_accept) begin
    221 		if (pattern_count_done) begin
    222 			pattern_next = pattern_expand;
    223 			pattern_count_next = PCOUNT;
    224 			load_character_addr_next = 1'b1;
    225 			vram_raddr_next = vram_raddr_add1;
    226 		end else begin
    227 			pattern_next = { pattern[PWIDTH-2:0], 1'b0 };
    228 			pattern_count_next = pattern_count_sub1;
    229 		end
    230 	end else begin
    231 		if (start_line) begin
    232 			vram_raddr_next = vram_rowbase;
    233 			load_character_addr_next = 1'b1;
    234 			preload1_next = 1'b1;
    235 		end
    236 		if (preload3) begin
    237 			pattern_next = pattern_expand;
    238 			pattern_count_next = PCOUNT;
    239 			vram_raddr_next = vram_raddr_add1;
    240 			load_character_addr_next = 1'b1;
    241 		end
    242 	end
    243 end
    244 
    245 always_ff @(posedge clk) begin
    246 	pattern <= pattern_next;
    247 	pattern_count <= pattern_count_next;
    248 	preload1 <= preload1_next;
    249 	preload2 <= preload2_next;
    250 	preload3 <= preload3_next;
    251 	load_character_addr <= load_character_addr_next;
    252 	vram_raddr <= vram_raddr_next;
    253 end
    254 
    255 // in RGB mode, we extract fg and bg 1bpp rgb colors from
    256 // the upper 8 bits of video ram and use those instead of
    257 // the hardcoded white and blue
    258 generate
    259 if (RGB) begin
    260 reg [2:0]fg;
    261 reg [2:0]fg_next;
    262 reg [2:0]bg;
    263 reg [2:0]bg_next;
    264 
    265 always_comb begin
    266 	fg_next = fg;
    267 	bg_next = bg;
    268 	if ((pxl_accept & pattern_count_done) | preload3) begin
    269 		fg_next = vdata[14:12];
    270 		bg_next = vdata[10:8];
    271 	end
    272 end
    273 
    274 always_ff @(posedge clk) begin
    275 	fg <= fg_next;
    276 	bg <= bg_next;
    277 end
    278 assign red = pattern[PWIDTH-1] ? {BPP{fg[2]}} : {BPP{bg[2]}};
    279 assign grn = pattern[PWIDTH-1] ? {BPP{fg[1]}} : {BPP{bg[1]}};
    280 assign blu = pattern[PWIDTH-1] ? {BPP{fg[0]}} : {BPP{bg[0]}};
    281 end else begin
    282 assign red = active ? { BPP {pattern[PWIDTH-1]} } : {BPP{1'b0}};
    283 assign grn = active ? { BPP {pattern[PWIDTH-1]} } : {BPP{1'b0}};
    284 assign blu = active ? { BPP {1'b1} } : {BPP{1'b0}};
    285 end
    286 endgenerate
    287 
    288 endmodule
    289 
    290 
    291 
    292 module hex2dec(
    293 	input wire [3:0]din,
    294 	output reg [7:0]dout
    295 );
    296 always_comb begin
    297 	case (din)
    298 	4'h0: dout = 8'h30;
    299 	4'h1: dout = 8'h31;
    300 	4'h2: dout = 8'h32;
    301 	4'h3: dout = 8'h33;
    302 	4'h4: dout = 8'h34;
    303 	4'h5: dout = 8'h35;
    304 	4'h6: dout = 8'h36;
    305 	4'h7: dout = 8'h37;
    306 	4'h8: dout = 8'h38;
    307 	4'h9: dout = 8'h39;
    308 	4'hA: dout = 8'h41;
    309 	4'hB: dout = 8'h42;
    310 	4'hC: dout = 8'h43;
    311 	4'hD: dout = 8'h44;
    312 	4'hE: dout = 8'h45;
    313 	4'hF: dout = 8'h46;
    314 	endcase
    315 end
    316 endmodule
    317