From 0bb7b5bed0d4f815baa15732d181273eb63297e3 Mon Sep 17 00:00:00 2001 From: Brian Koropoff Date: Sun, 6 Feb 2022 11:08:41 -0800 Subject: [PATCH] Refactor video attributes - Abstract video attributes and video ID code lookup into a package - All video attributes are now static values rather than signals within the hdmi block. The width/height output signals are removed since they are available by accessing hdmi localparams or through the hdmi_attr package. - Allow using custom video attributes if desired --- src/Manifest.py | 1 + src/hdmi.sv | 177 +++++++++++------------------------------- src/hdmi_attr.sv | 134 ++++++++++++++++++++++++++++++++ test/top_tb/top_tb.sv | 10 +-- top/top.sv | 15 ++-- 5 files changed, 191 insertions(+), 146 deletions(-) create mode 100644 src/hdmi_attr.sv diff --git a/src/Manifest.py b/src/Manifest.py index a6a7af3..74ca109 100644 --- a/src/Manifest.py +++ b/src/Manifest.py @@ -1,5 +1,6 @@ files = [ "hdmi.sv", + "hdmi_attr.sv", "tmds_channel.sv", "packet_assembler.sv", "packet_picker.sv", diff --git a/src/hdmi.sv b/src/hdmi.sv index 373c33c..4fc8015 100644 --- a/src/hdmi.sv +++ b/src/hdmi.sv @@ -1,6 +1,8 @@ // Implementation of HDMI Spec v1.4a // By Sameer Puri https://github.com/sameer +import hdmi_attr::*; + module hdmi #( // Defaults to 640x480 which should be supported by almost if not all HDMI sinks. @@ -8,6 +10,12 @@ module hdmi // Pixel repetition, interlaced scans and other special output modes are not implemented (yet). parameter int VIDEO_ID_CODE = 1, + // Basic video attributes. + // + // Derived from VIDEO_ID_CODE by default. Only override if you know what + // you're doing. + parameter video_attr_t VIDEO_ATTR = video_attr_for_id(VIDEO_ID_CODE), + // The IT content bit indicates that image samples are generated in an ad-hoc // manner (e.g. directly from values in a framebuffer, as by a PC video // card) and therefore aren't suitable for filtering or analog @@ -21,8 +29,8 @@ module hdmi // Defaults to minimum bit lengths required to represent positions. // Modify these parameters if you have alternate desired bit lengths. - parameter int BIT_WIDTH = VIDEO_ID_CODE < 4 ? 10 : VIDEO_ID_CODE == 4 ? 11 : 12, - parameter int BIT_HEIGHT = VIDEO_ID_CODE == 16 ? 11: 10, + parameter int BIT_WIDTH = $clog2(VIDEO_ATTR.frame_width), + parameter int BIT_HEIGHT = $clog2(VIDEO_ATTR.frame_height), // A true HDMI signal sends auxiliary data (i.e. audio, preambles) which prevents it from being parsed by DVI signal sinks. // HDMI signal sinks are fortunately backwards-compatible with DVI signals. @@ -79,136 +87,41 @@ module hdmi // They are used (by you) to pick the color each pixel should have // i.e. always_ff @(posedge pixel_clk) rgb <= {8'd0, 8'(cx), 8'(cy)}; output logic [BIT_WIDTH-1:0] cx = START_X, - output logic [BIT_HEIGHT-1:0] cy = START_Y, - - // The screen is at the upper left corner of the frame. - // 0,0 = 0,0 in video - // the frame includes extra space for sending auxiliary data - output logic [BIT_WIDTH-1:0] frame_width, - output logic [BIT_HEIGHT-1:0] frame_height, - output logic [BIT_WIDTH-1:0] screen_width, - output logic [BIT_HEIGHT-1:0] screen_height + output logic [BIT_HEIGHT-1:0] cy = START_Y ); -localparam int NUM_CHANNELS = 3; -logic hsync; -logic vsync; +localparam int FRAME_WIDTH = VIDEO_ATTR.frame_width; +localparam int FRAME_HEIGHT = VIDEO_ATTR.frame_height; +localparam int SCREEN_WIDTH = VIDEO_ATTR.screen_width; +localparam int SCREEN_HEIGHT = VIDEO_ATTR.screen_height; +localparam int HSYNC_PULSE_START = VIDEO_ATTR.hsync_pulse_start; +localparam int HSYNC_PULSE_SIZE = VIDEO_ATTR.hsync_pulse_size; +localparam int VSYNC_PULSE_START = VIDEO_ATTR.vsync_pulse_start; +localparam int VSYNC_PULSE_SIZE = VIDEO_ATTR.vsync_pulse_size; +localparam logic INVERT = VIDEO_ATTR.invert; +localparam real BASE_PIXEL_CLOCK = VIDEO_ATTR.base_pixel_clock; +localparam real VIDEO_RATE = BASE_PIXEL_CLOCK * ( + (VIDEO_REFRESH_RATE == 59.94 || VIDEO_REFRESH_RATE == 29.97) ? + 1000.0/1001.0 : + 1 +); // https://groups.google.com/forum/#!topic/sci.engr.advanced-tv/DQcGk5R_zsM -logic [BIT_WIDTH-1:0] hsync_pulse_start, hsync_pulse_size; -logic [BIT_HEIGHT-1:0] vsync_pulse_start, vsync_pulse_size; -logic invert; +localparam int NUM_CHANNELS = 3; -// See CEA-861-D for more specifics formats described below. -generate - case (VIDEO_ID_CODE) - 1: - begin - assign frame_width = 800; - assign frame_height = 525; - assign screen_width = 640; - assign screen_height = 480; - assign hsync_pulse_start = 16; - assign hsync_pulse_size = 96; - assign vsync_pulse_start = 10; - assign vsync_pulse_size = 2; - assign invert = 1; - end - 2, 3: - begin - assign frame_width = 858; - assign frame_height = 525; - assign screen_width = 720; - assign screen_height = 480; - assign hsync_pulse_start = 16; - assign hsync_pulse_size = 62; - assign vsync_pulse_start = 9; - assign vsync_pulse_size = 6; - assign invert = 1; - end - 4: - begin - assign frame_width = 1650; - assign frame_height = 750; - assign screen_width = 1280; - assign screen_height = 720; - assign hsync_pulse_start = 110; - assign hsync_pulse_size = 40; - assign vsync_pulse_start = 5; - assign vsync_pulse_size = 5; - assign invert = 0; - end - 16, 34: - begin - assign frame_width = 2200; - assign frame_height = 1125; - assign screen_width = 1920; - assign screen_height = 1080; - assign hsync_pulse_start = 88; - assign hsync_pulse_size = 44; - assign vsync_pulse_start = 4; - assign vsync_pulse_size = 5; - assign invert = 0; - end - 17, 18: - begin - assign frame_width = 864; - assign frame_height = 625; - assign screen_width = 720; - assign screen_height = 576; - assign hsync_pulse_start = 12; - assign hsync_pulse_size = 64; - assign vsync_pulse_start = 5; - assign vsync_pulse_size = 5; - assign invert = 1; - end - 19: - begin - assign frame_width = 1980; - assign frame_height = 750; - assign screen_width = 1280; - assign screen_height = 720; - assign hsync_pulse_start = 440; - assign hsync_pulse_size = 40; - assign vsync_pulse_start = 5; - assign vsync_pulse_size = 5; - assign invert = 0; - end - 95, 105, 97, 107: - begin - assign frame_width = 4400; - assign frame_height = 2250; - assign screen_width = 3840; - assign screen_height = 2160; - assign hsync_pulse_start = 176; - assign hsync_pulse_size = 88; - assign vsync_pulse_start = 8; - assign vsync_pulse_size = 10; - assign invert = 0; - end - endcase -endgenerate +logic hsync, vsync; always_comb begin - hsync <= invert ^ (cx >= screen_width + hsync_pulse_start && cx < screen_width + hsync_pulse_start + hsync_pulse_size); + hsync <= INVERT ^ (cx >= SCREEN_WIDTH + HSYNC_PULSE_START && cx < SCREEN_WIDTH + HSYNC_PULSE_START + HSYNC_PULSE_SIZE); // vsync pulses should begin and end at the start of hsync, so special // handling is required for the lines on which vsync starts and ends - if (cy == screen_height + vsync_pulse_start) - vsync <= invert ^ (cx >= screen_width + hsync_pulse_start); - else if (cy == screen_height + vsync_pulse_start + vsync_pulse_size) - vsync <= invert ^ (cx < screen_width + hsync_pulse_start); + if (cy == SCREEN_HEIGHT + VSYNC_PULSE_START) + vsync <= INVERT ^ (cx >= SCREEN_WIDTH + HSYNC_PULSE_START); + else if (cy == SCREEN_HEIGHT + VSYNC_PULSE_START + VSYNC_PULSE_SIZE) + vsync <= INVERT ^ (cx < SCREEN_WIDTH + HSYNC_PULSE_START); else - vsync <= invert ^ (cy >= screen_height + vsync_pulse_start && cy < screen_height + vsync_pulse_start + vsync_pulse_size); + vsync <= INVERT ^ (cy >= SCREEN_HEIGHT + VSYNC_PULSE_START && cy < SCREEN_HEIGHT + VSYNC_PULSE_START + VSYNC_PULSE_SIZE); end -localparam real VIDEO_RATE = (VIDEO_ID_CODE == 1 ? 25.2E6 - : VIDEO_ID_CODE == 2 || VIDEO_ID_CODE == 3 ? 27.027E6 - : VIDEO_ID_CODE == 4 ? 74.25E6 - : VIDEO_ID_CODE == 16 ? 148.5E6 - : VIDEO_ID_CODE == 17 || VIDEO_ID_CODE == 18 ? 27E6 - : VIDEO_ID_CODE == 19 ? 74.25E6 - : VIDEO_ID_CODE == 34 ? 74.25E6 - : VIDEO_ID_CODE == 95 || VIDEO_ID_CODE == 105 || VIDEO_ID_CODE == 97 || VIDEO_ID_CODE == 107 ? 594E6 - : 0) * (VIDEO_REFRESH_RATE == 59.94 || VIDEO_REFRESH_RATE == 29.97 ? 1000.0/1001.0 : 1); // https://groups.google.com/forum/#!topic/sci.engr.advanced-tv/DQcGk5R_zsM // Wrap-around pixel position counters indicating the pixel to be generated by the user in THIS clock and sent out in the NEXT clock. always_ff @(posedge clk_pixel) @@ -220,8 +133,8 @@ begin end else begin - cx <= cx == frame_width-1'b1 ? BIT_WIDTH'(0) : cx + 1'b1; - cy <= cx == frame_width-1'b1 ? cy == frame_height-1'b1 ? BIT_HEIGHT'(0) : cy + 1'b1 : cy; + cx <= cx == FRAME_WIDTH-1'b1 ? BIT_WIDTH'(0) : cx + 1'b1; + cy <= cx == FRAME_WIDTH-1'b1 ? cy == FRAME_HEIGHT-1'b1 ? BIT_HEIGHT'(0) : cy + 1'b1 : cy; end end @@ -232,7 +145,7 @@ begin if (reset) video_data_period <= 0; else - video_data_period <= cx < screen_width && cy < screen_height; + video_data_period <= cx < SCREEN_WIDTH && cy < SCREEN_HEIGHT; end logic [2:0] mode = 3'd1; @@ -254,8 +167,8 @@ generate end else begin - video_guard <= cx >= frame_width - 2 && cx < frame_width && (cy == frame_height - 1 || cy < screen_height); - video_preamble <= cx >= frame_width - 10 && cx < frame_width - 2 && (cy == frame_height - 1 || cy < screen_height); + video_guard <= cx >= FRAME_WIDTH - 2 && cx < FRAME_WIDTH && (cy == FRAME_HEIGHT - 1 || cy < SCREEN_HEIGHT); + video_preamble <= cx >= FRAME_WIDTH - 10 && cx < FRAME_WIDTH - 2 && (cy == FRAME_HEIGHT - 1 || cy < SCREEN_HEIGHT); end end @@ -264,7 +177,7 @@ generate logic [4:0] num_packets_alongside; always_comb begin - max_num_packets_alongside = ((frame_width - screen_width) /* VD period */ - 2 /* V guard */ - 8 /* V preamble */ - 12 /* 12px control period */ - 2 /* DI guard */ - 2 /* DI start guard */ - 8 /* DI premable */) / 32; + max_num_packets_alongside = ((FRAME_WIDTH - SCREEN_WIDTH) /* VD period */ - 2 /* V guard */ - 8 /* V preamble */ - 12 /* 12px control period */ - 2 /* DI guard */ - 2 /* DI start guard */ - 8 /* DI premable */) / 32; if (max_num_packets_alongside > 18) num_packets_alongside = 5'd18; else @@ -272,9 +185,9 @@ generate end logic data_island_period_instantaneous; - assign data_island_period_instantaneous = num_packets_alongside > 0 && cx >= screen_width + 10 && cx < screen_width + 10 + num_packets_alongside * 32; + assign data_island_period_instantaneous = num_packets_alongside > 0 && cx >= SCREEN_WIDTH + 10 && cx < SCREEN_WIDTH + 10 + num_packets_alongside * 32; logic packet_enable; - assign packet_enable = data_island_period_instantaneous && 5'(cx + screen_width + 22) == 5'd0; + assign packet_enable = data_island_period_instantaneous && 5'(cx + SCREEN_WIDTH + 22) == 5'd0; logic data_island_guard = 0; logic data_island_preamble = 0; @@ -289,8 +202,8 @@ generate end else begin - data_island_guard <= num_packets_alongside > 0 && ((cx >= screen_width + 8 && cx < screen_width + 10) || (cx >= screen_width + 10 + num_packets_alongside * 32 && cx < screen_width + 10 + num_packets_alongside * 32 + 2)); - data_island_preamble <= num_packets_alongside > 0 && cx >= screen_width && cx < screen_width + 8; + data_island_guard <= num_packets_alongside > 0 && ((cx >= SCREEN_WIDTH + 8 && cx < SCREEN_WIDTH + 10) || (cx >= SCREEN_WIDTH + 10 + num_packets_alongside * 32 && cx < SCREEN_WIDTH + 10 + num_packets_alongside * 32 + 2)); + data_island_preamble <= num_packets_alongside > 0 && cx >= SCREEN_WIDTH && cx < SCREEN_WIDTH + 8; data_island_period <= data_island_period_instantaneous; end end @@ -299,7 +212,7 @@ generate logic [23:0] header; logic [55:0] sub [3:0]; logic video_field_end; - assign video_field_end = cx == screen_width - 1'b1 && cy == screen_height - 1'b1; + assign video_field_end = cx == SCREEN_WIDTH - 1'b1 && cy == SCREEN_HEIGHT - 1'b1; logic [4:0] packet_pixel_counter; packet_picker #( .VIDEO_ID_CODE(VIDEO_ID_CODE), diff --git a/src/hdmi_attr.sv b/src/hdmi_attr.sv new file mode 100644 index 0000000..1652dd4 --- /dev/null +++ b/src/hdmi_attr.sv @@ -0,0 +1,134 @@ +// Package for representing/manipulating HDMI stream attributes +package hdmi_attr; + +// Basic video attributes +typedef struct { + // Width/height of HDMI frame, including blanking periods + int frame_width; + int frame_height; + // Width/height of screen (active video) + int screen_width; + int screen_height; + // Start of hsync pulse in pixel clock cycles after start of horizontal + // blanking + int hsync_pulse_start; + // Size of hsync pulse in pixel clock cycles + int hsync_pulse_size; + // Start of vsync pulse in scanlines after start of vertical blanking. The + // pulse is always aligned with the start of hsync on the indicated + // scanline (as required by CEA-861-D), so the actual start is + // (frame_width * vsync_pulse_start + hsync_pulse_start) pixel clock + // cycles after the last active pixel. + int vsync_pulse_start; + // Size of vsync pulse in scanlines + int vsync_pulse_size; + // Sync pulses are negative-going if true + logic invert; + // Base pixel clock rate in Hz. Can be modified when using 59.94/29.97 + // vertical refresh rate. + real base_pixel_clock; +} video_attr_t; + +// Get video attributes for supported CEA-861-D video ID code +function video_attr_t video_attr_for_id(int code); + case (code) + 1: return '{ + frame_width: 800, + frame_height: 525, + screen_width: 640, + screen_height: 480, + hsync_pulse_start: 16, + hsync_pulse_size: 96, + vsync_pulse_start: 10, + vsync_pulse_size: 2, + invert: 1, + base_pixel_clock: 25.2E6 + }; + 2, 3: return '{ + frame_width: 858, + frame_height: 525, + screen_width: 720, + screen_height: 480, + hsync_pulse_start: 16, + hsync_pulse_size: 62, + vsync_pulse_start: 9, + vsync_pulse_size: 6, + invert: 1, + base_pixel_clock: 27.027E6 + }; + 4: return '{ + frame_width: 1650, + frame_height: 750, + screen_width: 1280, + screen_height: 720, + hsync_pulse_start: 110, + hsync_pulse_size: 40, + vsync_pulse_start: 5, + vsync_pulse_size: 5, + invert: 0, + base_pixel_clock: 74.25E6 + }; + 16: return '{ + frame_width: 2200, + frame_height: 1125, + screen_width: 1920, + screen_height: 1080, + hsync_pulse_start: 88, + hsync_pulse_size: 44, + vsync_pulse_start: 4, + vsync_pulse_size: 5, + invert: 0, + base_pixel_clock: 148.5E6 + }; + 17, 18: return '{ + frame_width: 864, + frame_height: 625, + screen_width: 720, + screen_height: 576, + hsync_pulse_start: 12, + hsync_pulse_size: 64, + vsync_pulse_start: 5, + vsync_pulse_size: 5, + invert: 1, + base_pixel_clock: 27E6 + }; + 19: return '{ + frame_width: 1980, + frame_height: 750, + screen_width: 1280, + screen_height: 720, + hsync_pulse_start: 440, + hsync_pulse_size: 40, + vsync_pulse_start: 5, + vsync_pulse_size: 5, + invert: 0, + base_pixel_clock: 74.25E6 + }; + 34: return '{ + frame_width: 2200, + frame_height: 1125, + screen_width: 1920, + screen_height: 1080, + hsync_pulse_start: 88, + hsync_pulse_size: 44, + vsync_pulse_start: 4, + vsync_pulse_size: 5, + invert: 0, + base_pixel_clock: 74.25E6 + }; + 95, 105, 97, 107: return '{ + frame_width: 4400, + frame_height: 2250, + screen_width: 3840, + screen_height: 2160, + hsync_pulse_start: 176, + hsync_pulse_size: 88, + vsync_pulse_start: 8, + vsync_pulse_size: 10, + invert: 0, + base_pixel_clock: 595E6 + }; + endcase +endfunction + +endpackage diff --git a/test/top_tb/top_tb.sv b/test/top_tb/top_tb.sv index f4738b0..56c1702 100644 --- a/test/top_tb/top_tb.sv +++ b/test/top_tb/top_tb.sv @@ -108,15 +108,15 @@ logic [15:0] previous_sample [1:0]; integer k; always @(posedge top.clk_pixel) begin - cx <= cx == top.frame_width - 1 ? 0 : cx + 1; - cy <= cx == top.frame_width-1'b1 ? cy == top.frame_height-1'b1 ? 0 : cy + 1'b1 : cy; - if (top.hdmi.true_hdmi_output.num_packets_alongside > 0 && (cx >= top.screen_width + 8 && cx < top.screen_width + 10) || (cx >= top.screen_width + 10 + top.hdmi.true_hdmi_output.num_packets_alongside * 32 && cx < top.screen_width + 10 + top.hdmi.true_hdmi_output.num_packets_alongside * 32 + 2)) + cx <= cx == top.hdmi.FRAME_WIDTH - 1 ? 0 : cx + 1; + cy <= cx == top.hdmi.FRAME_WIDTH-1'b1 ? cy == top.hdmi.FRAME_HEIGHT-1'b1 ? 0 : cy + 1'b1 : cy; + if (top.hdmi.true_hdmi_output.num_packets_alongside > 0 && (cx >= top.hdmi.SCREEN_WIDTH + 8 && cx < top.hdmi.SCREEN_WIDTH + 10) || (cx >= top.hdmi.SCREEN_WIDTH + 10 + top.hdmi.true_hdmi_output.num_packets_alongside * 32 && cx < top.hdmi.SCREEN_WIDTH + 10 + top.hdmi.true_hdmi_output.num_packets_alongside * 32 + 2)) begin assert(tmds_values[2] == 10'b0100110011) else $fatal("Channel 2 DI GB incorrect: %b", tmds_values[2]); assert(tmds_values[1] == 10'b0100110011) else $fatal("Channel 1 DI GB incorrect"); assert(tmds_values[0] == 10'b1010001110 || tmds_values[0] == 10'b1001110001 || tmds_values[0] == 10'b0101100011 || tmds_values[0] == 10'b1011000011) else $fatal("Channel 0 DI GB incorrect"); end - else if (top.hdmi.true_hdmi_output.num_packets_alongside > 0 && cx >= top.screen_width + 10 && cx < top.screen_width + 10 + top.hdmi.true_hdmi_output.num_packets_alongside * 32) + else if (top.hdmi.true_hdmi_output.num_packets_alongside > 0 && cx >= top.hdmi.SCREEN_WIDTH + 10 && cx < top.hdmi.SCREEN_WIDTH + 10 + top.hdmi.true_hdmi_output.num_packets_alongside * 32) begin data_counter <= data_counter + 1'd1; if (data_counter == 0) @@ -126,7 +126,7 @@ begin sub[1][63:1] <= 63'dX; sub[0][63:1] <= 63'dX; header[31:1] <= 31'dX; - if (cx != top.screen_width + 10 || !first_packet) // Packet complete + if (cx != top.hdmi.SCREEN_WIDTH + 10 || !first_packet) // Packet complete begin first_packet <= 0; case(header[7:0]) diff --git a/top/top.sv b/top/top.sv index 20ba9fd..bdbb766 100644 --- a/top/top.sv +++ b/top/top.sv @@ -14,10 +14,7 @@ always @(posedge clk_audio) audio_sample_word <= '{audio_sample_word[1] + 16'd1, audio_sample_word[0] - 16'd1}; logic [23:0] rgb = 24'd0; -logic [9:0] cx, cy, screen_start_x, screen_start_y, frame_width, frame_height, screen_width, screen_height; -// Border test (left = red, top = green, right = blue, bottom = blue, fill = black) -always @(posedge clk_pixel) - rgb <= {cx == 0 ? ~8'd0 : 8'd0, cy == 0 ? ~8'd0 : 8'd0, cx == screen_width - 1'd1 || cy == screen_width - 1'd1 ? ~8'd0 : 8'd0}; +logic [9:0] cx, cy; // 640x480 @ 59.94Hz hdmi #(.VIDEO_ID_CODE(1), .VIDEO_REFRESH_RATE(59.94), .AUDIO_RATE(48000), .AUDIO_BIT_WIDTH(16)) hdmi( @@ -30,11 +27,11 @@ hdmi #(.VIDEO_ID_CODE(1), .VIDEO_REFRESH_RATE(59.94), .AUDIO_RATE(48000), .AUDIO .tmds(tmds), .tmds_clock(tmds_clock), .cx(cx), - .cy(cy), - .frame_width(frame_width), - .frame_height(frame_height), - .screen_width(screen_width), - .screen_height(screen_height) + .cy(cy) ); +// +// Border test (left = red, top = green, right = blue, bottom = blue, fill = black) +always @(posedge clk_pixel) + rgb <= {cx == 0 ? ~8'd0 : 8'd0, cy == 0 ? ~8'd0 : 8'd0, cx == hdmi.SCREEN_WIDTH - 1'd1 || cy == hdmi.SCREEN_HEIGHT - 1'd1 ? ~8'd0 : 8'd0}; endmodule