// http://chasingtrons.com/main/2012/6/14/television-fpga-verilog.html // https://en.wikipedia.org/wiki/Resistor_ladder#R.E2.80.932R_resistor_ladder_network_.28digital_to_analog_conversion.29 entity Second { in clk: bit, out value: bit } impl Second { def mut index: uint{..50000000}; on clk.posedge { if index == 25000000 - 1 { index <= 0; value <= !value; } else { index <= index + 1; } } } entity Ntsc { in clk: bit, // 0 ( black )..5 (bright white) in pixel_data: bit[3], // single clock tick indicating pixel_y will incrememt on next clock ( for debugging ) out h_sync_out: bit, // single clock tick indicating pixel_y will reset to 0 or 1 on next clock, depending on the field ( for debugging ) out v_sync_out: bit, // which line out pixel_y: bit[10], out pixel_x: bit[10], out pixel_is_visible: bit, out ntsc_out: bit[3], } impl Ntsc { const BASE_PIXEL_X = 184; const RESOLUTION_HORIZONTAL = 560; const BASE_PIXEL_Y = 89; const RESOLUTION_VERTICAL = 400; // TODO: Make a better interface for inputting luminance def luminance: bit[3]; // 0..524 def mut line_count_reg: bit[10]; def line_count_reg_next: bit[10]; // One of EQ, VERTICAL_BLANK, or SCANLINE. def mut line_type_reg: bit[2]; def line_type_reg_next: bit[2]; def mut horizontal_count_reg: bit[12]; def horizontal_count_reg_next: bit[12]; const LINE_TYPE_EQ = 0b00; const LINE_TYPE_VERTICAL_BLANK = 0b01; const LINE_TYPE_SCANLINE = 0b10; // 1.5 uS @ 50 MHz const WIDTH_FRONT_PORCH = 75; // 4.7 uS @ 50 MHz const WIDTH_SYNC_TIP = 235; // 4.7 uS @ 50 MHz const WIDTH_BACK_PORCH = 235; // 52.6 uS @ 50 MHz const WIDTH_VIDEO = 2630; // 63.5 uS @ 50 MHz const WIDTH_WHOLE_LINE = 3175; // 31.75 uS @ 50 MHz const WIDTH_HALF_LINE = 1588; // 2.35 uS @ 50 MHz const WIDTH_EQ_PULSE = 117; // 27.05 uS @ 50 MHz const WIDTH_V_SYNC_PULSE = 1353; // 0 V const SIGNAL_LEVEL_SYNC = 0b000; // 0.3 V ... const SIGNAL_LEVEL_BLANK = 0b001; const SIGNAL_LEVEL_BLACK = 0b010; const SIGNAL_LEVEL_DARK_GREY = 0b011; const SIGNAL_LEVEL_GREY = 0b100; const SIGNAL_LEVEL_LIGHT_GREY = 0b101; const SIGNAL_LEVEL_WHITE = 0b110; // 1 V const SIGNAL_LEVEL_BRIGHT_WHITE = 0b111; const HALF_LINE_EVEN_FIELD = 18; const HALF_LINE_ODD_FIELD = 527; // ____ _ _ _ _ ____ ____ _ ____ _ _ ____ _ ____ // [__ \_/ |\ | | [__ | | __ |\ | |__| | [__ // ___] | | \| |___ ___] | |__] | \| | | |___ ___] // def at_half_line_width: bit = horizontal_count_reg >= WIDTH_HALF_LINE; // signals that the current line has // reached a half scanline's 31.75 def at_full_line_width: bit = ( horizontal_count_reg >= WIDTH_WHOLE_LINE ); // signals that the current line has // reached a normal scanline's 63.5us def is_a_half_line: bit = ( line_count_reg == HALF_LINE_EVEN_FIELD ) | ( line_count_reg == HALF_LINE_ODD_FIELD ); // signals current line should be treaded as a half def is_a_whole_line: bit = !is_a_half_line; // signals current line should be treaded as a whole def h_sync: bit = (is_a_half_line & at_half_line_width) | (is_a_whole_line & at_full_line_width); def v_sync: bit = h_sync & (line_count_reg >= 526); def h_sync_out: bit = h_sync; def v_sync_out: bit = v_sync; def pixel_is_visible: bit = (horizontal_count_reg[12:2] >= BASE_PIXEL_X) & (horizontal_count_reg[12:2] < BASE_PIXEL_X + RESOLUTION_HORIZONTAL) & (line_count_reg >= BASE_PIXEL_Y) & (line_count_reg < BASE_PIXEL_Y + RESOLUTION_VERTICAL); // Set pixel values. pixel_x = if pixel_is_visible { horizontal_count_reg[12:2] - BASE_PIXEL_X } else { 0 }; pixel_y = if pixel_is_visible { line_count_reg - BASE_PIXEL_Y } else { 0 }; // Main register transfer. // On each positive clock edge, transfer our new register values. on clk.posedge { // all registers that are needed for decision // keeping are buffered so they hold their // current value until the next clock cycle horizontal_count_reg <= horizontal_count_reg_next; line_count_reg <= line_count_reg_next; line_type_reg <= line_type_reg_next; } // Compute the next line value. if line_count_reg <= 5 || (line_count_reg >= 12 && line_count_reg <= 18) { // is this an equalizing pulse line? line_type_reg_next = LINE_TYPE_EQ; } else if line_count_reg >= 6 && line_count_reg <= 11 { // is this a vertical blanking line? line_type_reg_next = LINE_TYPE_VERTICAL_BLANK; } else { // must be a normal scanline line_type_reg_next = LINE_TYPE_SCANLINE; }; // ____ _ ____ _ _ ____ _ ___ _ _ _ _ _ _ ____ // [__ | | __ |\ | |__| | | | |\/| | |\ | | __ // ___] | |__] | \| | | |___ | | | | | | \| |__] // // reached the end of the current line? if h_sync { // yes, reset counter to 0 horizontal_count_reg_next = 0; } else { // nope, advance horizontal_count_reg_next = horizontal_count_reg + 1; }; // generate the proper signals depending on line type if line_type_reg == LINE_TYPE_EQ { if horizontal_count_reg < WIDTH_EQ_PULSE || (horizontal_count_reg > WIDTH_HALF_LINE && horizontal_count_reg < WIDTH_HALF_LINE + WIDTH_EQ_PULSE) { ntsc_out = SIGNAL_LEVEL_SYNC; } else { ntsc_out = SIGNAL_LEVEL_BLANK; } } else if line_type_reg == LINE_TYPE_VERTICAL_BLANK { if horizontal_count_reg < WIDTH_V_SYNC_PULSE || (horizontal_count_reg > WIDTH_HALF_LINE && horizontal_count_reg < WIDTH_HALF_LINE + WIDTH_V_SYNC_PULSE) { ntsc_out = SIGNAL_LEVEL_SYNC; } else { ntsc_out = SIGNAL_LEVEL_BLANK; } } else if line_type_reg == LINE_TYPE_SCANLINE { if horizontal_count_reg > WIDTH_FRONT_PORCH && horizontal_count_reg < WIDTH_FRONT_PORCH + WIDTH_SYNC_TIP { ntsc_out = SIGNAL_LEVEL_SYNC; } else if horizontal_count_reg > WIDTH_WHOLE_LINE - WIDTH_VIDEO { ntsc_out = luminance; } else { ntsc_out = SIGNAL_LEVEL_BLANK; } }; if v_sync == 1 && h_sync == 1 && line_count_reg == 526 { line_count_reg_next = 0; // SHOULD BE 1 } else if v_sync == 1 && h_sync == 1 && line_count_reg == 527 { line_count_reg_next = 0; } else if v_sync == 0 && h_sync == 1 { line_count_reg_next = line_count_reg + 2; } else { line_count_reg_next = line_count_reg; }; // a lookup table to determing next line number // match {v_sync, h_sync, line_count_reg} { // // v_sync & line number 526, go to line 1 // {0b1, 0b1, 526} => line_count_reg_next = 1, // // v_sync & line number 527, go to line 0 // {0b1, 0b1, 527} => line_count_reg_next = 0, // // hsync, but not vsync, jump a line // {0b0, 0b1, _} => line_count_reg_next = line_count_reg + 2, // // do nothing // _ => line_count_reg_next = line_count_reg, // } // luminance if pixel_is_visible { match pixel_data { 0 => luminance = SIGNAL_LEVEL_BLANK, 1 => luminance = SIGNAL_LEVEL_DARK_GREY, 2 => luminance = SIGNAL_LEVEL_GREY, 3 => luminance = SIGNAL_LEVEL_LIGHT_GREY, 4 => luminance = SIGNAL_LEVEL_WHITE, 5 => luminance = SIGNAL_LEVEL_BRIGHT_WHITE, _ => luminance = SIGNAL_LEVEL_BLANK, } } else { luminance = SIGNAL_LEVEL_BLANK; }; } // For the moment, we use a Verilog literal for adding the // initialized RAM block. Ideally, this would be wrapped into // a language feature. ` module Screen ( input clk, input [14:0] raddr0, output reg [1:0] rdata0, ); reg [1:0] memory [0:32767]; initial $readmemh("zelda.hex", memory); always @(posedge clk) rdata0 <= memory[raddr0]; endmodule ` entity Main { in clk: bit, out LED1: bit, out LED2: bit, out LED3: bit, out LED4: bit, out LED5: bit, in PMOD1: bit, out PMOD2: bit, out PMOD3: bit, out PMOD4: bit, } impl Main { def pos: bit[15] = ((pixel_y << 7) + ((pixel_x >> 1) - 128)); def rdata0: bit[2]; def screen = Screen { clk: PMOD1, raddr0: pos, rdata0: rdata0, }; def pixel: bit[3]; match rdata0 { 0 => { pixel = 1; } 1 => { pixel = 2; } 2 => { pixel = 3; } 3 => { pixel = 4; } }; def pixel_x: bit[10]; def pixel_y: bit[10]; def ntsc = Ntsc { clk: PMOD1, pixel_data: pixel, h_sync_out: _, v_sync_out: _, pixel_y: pixel_y, pixel_x: pixel_x, pixel_is_visible: _, ntsc_out: {PMOD4, PMOD3, PMOD2}, }; def s = Second { clk: PMOD1, value: LED5, }; }