mirror of
https://github.com/steve-m/hsdaoh-fpga.git
synced 2025-12-09 23:34:45 +01:00
302 lines
10 KiB
Verilog
302 lines
10 KiB
Verilog
// Implementation of HDMI Spec v1.4a
|
|
// By Sameer Puri https://github.com/sameer
|
|
// source: https://github.com/hdl-util/hdmi/
|
|
// Dual-licensed under Apache License 2.0 and MIT License.
|
|
|
|
// converted to Verilog and stripped down for hsdaoh,
|
|
// changed VIDEO_ID_CODE 16 to minimal timings the MS2130 accepts
|
|
|
|
module hdmi (
|
|
clk_pixel_x5,
|
|
clk_pixel,
|
|
reset,
|
|
rgb,
|
|
tmds,
|
|
tmds_clock,
|
|
cx,
|
|
cy,
|
|
frame_width,
|
|
frame_height,
|
|
screen_width,
|
|
screen_height
|
|
);
|
|
// Defaults to 640x480 which should be supported by almost if not all HDMI sinks.
|
|
// See README.md or CEA-861-D for enumeration of video id codes.
|
|
// Pixel repetition, interlaced scans and other special output modes are not implemented (yet).
|
|
parameter [6:0] VIDEO_ID_CODE = 1;
|
|
|
|
// 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
|
|
// reconstruction. This is probably what you want if you treat pixels
|
|
// as "squares". If you generate a properly bandlimited signal or obtain
|
|
// one from elsewhere (e.g. a camera), this can be turned off.
|
|
//
|
|
// This flag also tends to cause receivers to treat RGB values as full
|
|
// range (0-255).
|
|
parameter [0:0] IT_CONTENT = 1'b1;
|
|
|
|
// Defaults to minimum bit lengths required to represent positions.
|
|
// Modify these parameters if you have alternate desired bit lengths.
|
|
parameter [12:0] BIT_WIDTH = (VIDEO_ID_CODE < 4 ? 10 : (VIDEO_ID_CODE == 4 ? 11 : 12));
|
|
parameter [12:0] BIT_HEIGHT = (VIDEO_ID_CODE == 16 ? 11 : 10);
|
|
|
|
// 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.
|
|
// Enable this flag if the output should be a DVI signal. You might want to do this to reduce resource usage or if you're only outputting video.
|
|
parameter [0:0] DVI_OUTPUT = 1'b0;
|
|
|
|
// **All parameters below matter ONLY IF you plan on sending auxiliary data (DVI_OUTPUT == 1'b0)**
|
|
|
|
// Starting screen coordinate when module comes out of reset.
|
|
//
|
|
// Setting these to something other than (0, 0) is useful when positioning
|
|
// an external video signal within a larger overall frame (e.g.
|
|
// letterboxing an input video signal). This allows you to synchronize the
|
|
// negative edge of reset directly to the start of the external signal
|
|
// instead of to some number of clock cycles before.
|
|
//
|
|
// You probably don't need to change these parameters if you are
|
|
// generating a signal from scratch instead of processing an
|
|
// external signal.
|
|
parameter [12:0] START_X = 0;
|
|
parameter [12:0] START_Y = 0;
|
|
|
|
input wire clk_pixel_x5;
|
|
input wire clk_pixel;
|
|
// synchronous reset back to 0,0
|
|
input wire reset;
|
|
input wire [23:0] rgb;
|
|
|
|
// These outputs go to your HDMI port
|
|
output wire [2:0] tmds;
|
|
output wire tmds_clock;
|
|
|
|
// All outputs below this line stay inside the FPGA
|
|
// 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 reg [BIT_WIDTH - 1:0] cx = START_X;
|
|
output reg [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 wire [BIT_WIDTH - 1:0] frame_width;
|
|
output wire [BIT_HEIGHT - 1:0] frame_height;
|
|
output wire [BIT_WIDTH - 1:0] screen_width;
|
|
output wire [BIT_HEIGHT - 1:0] screen_height;
|
|
|
|
localparam signed [31:0] NUM_CHANNELS = 3;
|
|
reg hsync;
|
|
reg vsync;
|
|
|
|
wire [BIT_WIDTH - 1:0] hsync_pulse_start, hsync_pulse_size;
|
|
wire [BIT_HEIGHT - 1:0] vsync_pulse_start, vsync_pulse_size;
|
|
wire invert;
|
|
|
|
// See CEA-861-D for more specifics formats described below.
|
|
generate
|
|
case (VIDEO_ID_CODE)
|
|
16, 34: begin : genblk1
|
|
// Those are the patched, minimal timings for video mode 16 that
|
|
// work with the MS2130. The inactive video period has been reduced
|
|
// significantly. See below (mode 99) for the original timings for
|
|
// 1080p
|
|
assign frame_width = 1982;
|
|
assign frame_height = 1084;
|
|
assign screen_width = 1920;
|
|
assign screen_height = 1080;
|
|
assign hsync_pulse_start = 6;
|
|
assign hsync_pulse_size = 4;
|
|
assign vsync_pulse_start = 0;
|
|
assign vsync_pulse_size = 1;
|
|
assign invert = 0;
|
|
end
|
|
99: begin : genblk1
|
|
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
|
|
endcase
|
|
endgenerate
|
|
always @(*) begin
|
|
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) - 1))
|
|
vsync <= invert ^ (cx >= (screen_width + hsync_pulse_start));
|
|
else if (cy == (((screen_height + vsync_pulse_start) + vsync_pulse_size) - 1))
|
|
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)));
|
|
end
|
|
|
|
// 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 @(posedge clk_pixel)
|
|
begin
|
|
if (reset) begin
|
|
cx <= START_X;
|
|
cy <= START_Y;
|
|
end
|
|
else begin
|
|
cx <= (cx == (frame_width - 1'b1) ? 0 : cx + 1'b1);
|
|
cy <= (cx == (frame_width - 1'b1) ? (cy == (frame_height - 1'b1) ? 0 : cy + 1'b1) : cy);
|
|
end
|
|
end
|
|
|
|
// See Section 5.2
|
|
reg video_data_period = 0;
|
|
always @(posedge clk_pixel)
|
|
begin
|
|
if (reset)
|
|
video_data_period <= 0;
|
|
else
|
|
video_data_period <= (cx < screen_width) && (cy < screen_height);
|
|
end
|
|
|
|
reg [2:0] mode = 3'd1;
|
|
reg [23:0] video_data = 24'd0;
|
|
reg [5:0] control_data = 6'd0;
|
|
reg [11:0] data_island_data = 12'd0;
|
|
|
|
function automatic [4:0] sv2v_cast_5;
|
|
input reg [4:0] inp;
|
|
sv2v_cast_5 = inp;
|
|
endfunction
|
|
|
|
generate
|
|
if (!DVI_OUTPUT) begin : true_hdmi_output
|
|
reg video_guard = 1;
|
|
reg video_preamble = 0;
|
|
always @(posedge clk_pixel)
|
|
begin
|
|
if (reset) begin
|
|
video_guard <= 1;
|
|
video_preamble <= 0;
|
|
end
|
|
else begin
|
|
video_guard <= ((cx >= (frame_width - 2)) && (cx < frame_width)) && ((cy == (frame_height - 1)) || (cy < (screen_height - 1)));
|
|
video_preamble <= ((cx >= (frame_width - 10)) && (cx < (frame_width - 2))) && ((cy == (frame_height - 1)) || (cy < (screen_height - 1)));
|
|
end
|
|
end
|
|
|
|
reg [4:0] num_packets_alongside = 1; // patched for hsdaoh: set to minimum required number
|
|
|
|
wire data_island_period_instantaneous;
|
|
assign data_island_period_instantaneous = ((num_packets_alongside > 0) && (cx >= (screen_width + 14))) && (cx < ((screen_width + 14) + (num_packets_alongside * 32)));
|
|
wire packet_enable;
|
|
assign packet_enable = data_island_period_instantaneous && (sv2v_cast_5((cx + screen_width) + 18) == 5'd0);
|
|
|
|
reg data_island_guard = 0;
|
|
reg data_island_preamble = 0;
|
|
reg data_island_period = 0;
|
|
always @(posedge clk_pixel)
|
|
begin
|
|
if (reset) begin
|
|
data_island_guard <= 0;
|
|
data_island_preamble <= 0;
|
|
data_island_period <= 0;
|
|
end
|
|
else begin
|
|
data_island_guard <= num_packets_alongside > 0 && (
|
|
(cx >= screen_width + 12 && cx < screen_width + 14) /* leading guard */ ||
|
|
(cx >= screen_width + 14 + num_packets_alongside * 32 && cx < screen_width + 14 + num_packets_alongside * 32 + 2) /* trailing guard */
|
|
);
|
|
data_island_preamble <= num_packets_alongside > 0 && cx >= screen_width + 4 && cx < screen_width + 12;
|
|
data_island_period <= data_island_period_instantaneous;
|
|
end
|
|
end
|
|
|
|
// See Section 5.2.3.4
|
|
wire [23:0] header;
|
|
wire [223:0] sub;
|
|
wire video_field_end;
|
|
assign video_field_end = (cx == (screen_width - 1'b1)) && (cy == (screen_height - 1'b1));
|
|
wire [4:0] packet_pixel_counter;
|
|
packet_picker #(
|
|
.VIDEO_ID_CODE(VIDEO_ID_CODE)
|
|
) packet_picker(
|
|
.clk_pixel(clk_pixel),
|
|
.reset(reset),
|
|
.video_field_end(video_field_end),
|
|
.packet_enable(packet_enable),
|
|
.packet_pixel_counter(packet_pixel_counter),
|
|
.header(header),
|
|
.sub(sub)
|
|
);
|
|
wire [8:0] packet_data;
|
|
packet_assembler packet_assembler(
|
|
.clk_pixel(clk_pixel),
|
|
.reset(reset),
|
|
.data_island_period(data_island_period),
|
|
.header(header),
|
|
.sub(sub),
|
|
.packet_data(packet_data),
|
|
.counter(packet_pixel_counter)
|
|
);
|
|
always @(posedge clk_pixel)
|
|
if (reset) begin
|
|
mode <= 3'd2;
|
|
video_data <= 24'd0;
|
|
control_data = 6'd0;
|
|
data_island_data <= 12'd0;
|
|
end
|
|
else begin
|
|
mode <= (data_island_guard ? 3'd4 : (data_island_period ? 3'd3 : (video_guard ? 3'd2 : (video_data_period ? 3'd1 : 3'd0))));
|
|
video_data <= rgb;
|
|
control_data <= {1'b0, data_island_preamble, 1'b0, video_preamble || data_island_preamble, vsync, hsync};
|
|
data_island_data[11:4] <= packet_data[8:1];
|
|
data_island_data[3] <= cx != 0;
|
|
data_island_data[2] <= packet_data[0];
|
|
data_island_data[1:0] <= {vsync, hsync};
|
|
end
|
|
end
|
|
else begin : genblk2 // DVI_OUTPUT = 1
|
|
always @(posedge clk_pixel)
|
|
if (reset) begin
|
|
mode <= 3'd0;
|
|
video_data <= 24'd0;
|
|
control_data <= 6'd0;
|
|
end
|
|
else begin
|
|
mode <= (video_data_period ? 3'd1 : 3'd0);
|
|
video_data <= rgb;
|
|
control_data <= {4'b0000, vsync, hsync}; // ctrl3, ctrl2, ctrl1, ctrl0, vsync, hsync
|
|
end
|
|
end
|
|
endgenerate
|
|
|
|
// All logic below relates to the production and output of the 10-bit TMDS code.
|
|
wire [29:0] tmds_internal;
|
|
genvar i;
|
|
generate
|
|
// TMDS code production.
|
|
for (i = 0; i < NUM_CHANNELS; i = i + 1) begin : tmds_gen
|
|
tmds_channel #(.CN(i)) tmds_channel(
|
|
.clk_pixel(clk_pixel),
|
|
.reset(reset),
|
|
.video_data(video_data[(i * 8) + 7:i * 8]),
|
|
.data_island_data(data_island_data[(i * 4) + 3:i * 4]),
|
|
.control_data(control_data[(i * 2) + 1:i * 2]),
|
|
.mode(mode),
|
|
.tmds(tmds_internal[i * 10+:10])
|
|
);
|
|
end
|
|
endgenerate
|
|
serializer #(
|
|
.NUM_CHANNELS(NUM_CHANNELS)
|
|
) serializer(
|
|
.clk_pixel(clk_pixel),
|
|
.clk_pixel_x5(clk_pixel_x5),
|
|
.reset(reset),
|
|
.tmds_internal(tmds_internal),
|
|
.tmds(tmds),
|
|
.tmds_clock(tmds_clock)
|
|
);
|
|
endmodule
|