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