Browse Source

video: initial simple video generator; add support for HDMI TERC4

A simple video generator which should be able to display some color
bars.

Fix bugs in the TMDS encoder, and add support for the TERC4 symbols
used by HDMI but not DVI.
H. Peter Anvin 3 years ago
parent
commit
510e702728

+ 9 - 9
fpga/ip/hdmitx.v

@@ -80,14 +80,14 @@ module hdmitx (
 		ALTLVDS_TX_component.center_align_msb = "UNUSED",
 		ALTLVDS_TX_component.common_rx_tx_pll = "ON",
 		ALTLVDS_TX_component.coreclock_divide_by = 2,
-		ALTLVDS_TX_component.data_rate = "480.0 Mbps",
+		ALTLVDS_TX_component.data_rate = "336.0 Mbps",
 		ALTLVDS_TX_component.deserialization_factor = 10,
 		ALTLVDS_TX_component.differential_drive = 0,
 		ALTLVDS_TX_component.enable_clock_pin_mode = "UNUSED",
 		ALTLVDS_TX_component.implement_in_les = "ON",
 		ALTLVDS_TX_component.inclock_boost = 0,
 		ALTLVDS_TX_component.inclock_data_alignment = "EDGE_ALIGNED",
-		ALTLVDS_TX_component.inclock_period = 20833,
+		ALTLVDS_TX_component.inclock_period = 14881,
 		ALTLVDS_TX_component.inclock_phase_shift = 0,
 		ALTLVDS_TX_component.intended_device_family = "Cyclone IV E",
 		ALTLVDS_TX_component.lpm_hint = "CBX_MODULE_PREFIX=hdmitx",
@@ -100,7 +100,7 @@ module hdmitx (
 		ALTLVDS_TX_component.outclock_multiply_by = 2,
 		ALTLVDS_TX_component.outclock_phase_shift = 0,
 		ALTLVDS_TX_component.outclock_resource = "AUTO",
-		ALTLVDS_TX_component.output_data_rate = 480,
+		ALTLVDS_TX_component.output_data_rate = 336,
 		ALTLVDS_TX_component.pll_compensation_mode = "AUTO",
 		ALTLVDS_TX_component.pll_self_reset_on_loss_lock = "ON",
 		ALTLVDS_TX_component.preemphasis_setting = 0,
@@ -121,15 +121,15 @@ endmodule
 // Retrieval info: PRIVATE: CNX_CLOCK_CHOICES STRING "tx_coreclock"
 // Retrieval info: PRIVATE: CNX_CLOCK_MODE NUMERIC "0"
 // Retrieval info: PRIVATE: CNX_COMMON_PLL NUMERIC "1"
-// Retrieval info: PRIVATE: CNX_DATA_RATE STRING "480.0"
+// Retrieval info: PRIVATE: CNX_DATA_RATE STRING "336.0"
 // Retrieval info: PRIVATE: CNX_DESER_FACTOR NUMERIC "10"
 // Retrieval info: PRIVATE: CNX_EXT_PLL STRING "OFF"
 // Retrieval info: PRIVATE: CNX_LE_SERDES STRING "ON"
 // Retrieval info: PRIVATE: CNX_NUM_CHANNEL NUMERIC "3"
 // Retrieval info: PRIVATE: CNX_OUTCLOCK_DIVIDE_BY NUMERIC "10"
 // Retrieval info: PRIVATE: CNX_PLL_ARESET NUMERIC "1"
-// Retrieval info: PRIVATE: CNX_PLL_FREQ STRING "48.00"
-// Retrieval info: PRIVATE: CNX_PLL_PERIOD STRING "20.833"
+// Retrieval info: PRIVATE: CNX_PLL_FREQ STRING "67.20"
+// Retrieval info: PRIVATE: CNX_PLL_PERIOD STRING "14.881"
 // Retrieval info: PRIVATE: CNX_REG_INOUT NUMERIC "1"
 // Retrieval info: PRIVATE: CNX_TX_CORECLOCK STRING "ON"
 // Retrieval info: PRIVATE: CNX_TX_LOCKED STRING "ON"
@@ -145,14 +145,14 @@ endmodule
 // Retrieval info: CONSTANT: COMMON_RX_TX_PLL STRING "ON"
 // Retrieval info: CONSTANT: CORECLOCK_DIVIDE_BY NUMERIC "2"
 // Retrieval info: CONSTANT: clk_src_is_pll STRING "off"
-// Retrieval info: CONSTANT: DATA_RATE STRING "480.0 Mbps"
+// Retrieval info: CONSTANT: DATA_RATE STRING "336.0 Mbps"
 // Retrieval info: CONSTANT: DESERIALIZATION_FACTOR NUMERIC "10"
 // Retrieval info: CONSTANT: DIFFERENTIAL_DRIVE NUMERIC "0"
 // Retrieval info: CONSTANT: ENABLE_CLOCK_PIN_MODE STRING "UNUSED"
 // Retrieval info: CONSTANT: IMPLEMENT_IN_LES STRING "ON"
 // Retrieval info: CONSTANT: INCLOCK_BOOST NUMERIC "0"
 // Retrieval info: CONSTANT: INCLOCK_DATA_ALIGNMENT STRING "EDGE_ALIGNED"
-// Retrieval info: CONSTANT: INCLOCK_PERIOD NUMERIC "20833"
+// Retrieval info: CONSTANT: INCLOCK_PERIOD NUMERIC "14881"
 // Retrieval info: CONSTANT: INCLOCK_PHASE_SHIFT NUMERIC "0"
 // Retrieval info: CONSTANT: INTENDED_DEVICE_FAMILY STRING "Cyclone IV E"
 // Retrieval info: CONSTANT: LPM_HINT STRING "UNUSED"
@@ -165,7 +165,7 @@ endmodule
 // Retrieval info: CONSTANT: OUTCLOCK_MULTIPLY_BY NUMERIC "2"
 // Retrieval info: CONSTANT: OUTCLOCK_PHASE_SHIFT NUMERIC "0"
 // Retrieval info: CONSTANT: OUTCLOCK_RESOURCE STRING "AUTO"
-// Retrieval info: CONSTANT: OUTPUT_DATA_RATE NUMERIC "480"
+// Retrieval info: CONSTANT: OUTPUT_DATA_RATE NUMERIC "336"
 // Retrieval info: CONSTANT: PLL_COMPENSATION_MODE STRING "AUTO"
 // Retrieval info: CONSTANT: PLL_SELF_RESET_ON_LOSS_LOCK STRING "ON"
 // Retrieval info: CONSTANT: PREEMPHASIS_SETTING NUMERIC "0"

+ 8 - 8
fpga/ip/pll.v

@@ -143,9 +143,9 @@ module pll (
 		altpll_component.clk1_duty_cycle = 50,
 		altpll_component.clk1_multiply_by = 7,
 		altpll_component.clk1_phase_shift = "0",
-		altpll_component.clk2_divide_by = 4,
+		altpll_component.clk2_divide_by = 5,
 		altpll_component.clk2_duty_cycle = 50,
-		altpll_component.clk2_multiply_by = 1,
+		altpll_component.clk2_multiply_by = 7,
 		altpll_component.clk2_phase_shift = "0",
 		altpll_component.clk3_divide_by = 5,
 		altpll_component.clk3_duty_cycle = 50,
@@ -232,7 +232,7 @@ endmodule
 // Retrieval info: PRIVATE: DEVICE_SPEED_GRADE STRING "Any"
 // Retrieval info: PRIVATE: DIV_FACTOR0 NUMERIC "2"
 // Retrieval info: PRIVATE: DIV_FACTOR1 NUMERIC "4"
-// Retrieval info: PRIVATE: DIV_FACTOR2 NUMERIC "4"
+// Retrieval info: PRIVATE: DIV_FACTOR2 NUMERIC "10"
 // Retrieval info: PRIVATE: DIV_FACTOR3 NUMERIC "5"
 // Retrieval info: PRIVATE: DIV_FACTOR4 NUMERIC "2"
 // Retrieval info: PRIVATE: DUTY_CYCLE0 STRING "50.00000000"
@@ -242,7 +242,7 @@ endmodule
 // Retrieval info: PRIVATE: DUTY_CYCLE4 STRING "50.00000000"
 // Retrieval info: PRIVATE: EFF_OUTPUT_FREQ_VALUE0 STRING "168.000000"
 // Retrieval info: PRIVATE: EFF_OUTPUT_FREQ_VALUE1 STRING "84.000000"
-// Retrieval info: PRIVATE: EFF_OUTPUT_FREQ_VALUE2 STRING "12.000000"
+// Retrieval info: PRIVATE: EFF_OUTPUT_FREQ_VALUE2 STRING "67.199997"
 // Retrieval info: PRIVATE: EFF_OUTPUT_FREQ_VALUE3 STRING "134.399994"
 // Retrieval info: PRIVATE: EFF_OUTPUT_FREQ_VALUE4 STRING "168.000000"
 // Retrieval info: PRIVATE: EXPLICIT_SWITCHOVER_COUNTER STRING "0"
@@ -279,13 +279,13 @@ endmodule
 // Retrieval info: PRIVATE: MIRROR_CLK4 STRING "0"
 // Retrieval info: PRIVATE: MULT_FACTOR0 NUMERIC "7"
 // Retrieval info: PRIVATE: MULT_FACTOR1 NUMERIC "7"
-// Retrieval info: PRIVATE: MULT_FACTOR2 NUMERIC "1"
+// Retrieval info: PRIVATE: MULT_FACTOR2 NUMERIC "14"
 // Retrieval info: PRIVATE: MULT_FACTOR3 NUMERIC "14"
 // Retrieval info: PRIVATE: MULT_FACTOR4 NUMERIC "7"
 // Retrieval info: PRIVATE: NORMAL_MODE_RADIO STRING "0"
 // Retrieval info: PRIVATE: OUTPUT_FREQ0 STRING "168.00000000"
 // Retrieval info: PRIVATE: OUTPUT_FREQ1 STRING "84.00000000"
-// Retrieval info: PRIVATE: OUTPUT_FREQ2 STRING "48.00000000"
+// Retrieval info: PRIVATE: OUTPUT_FREQ2 STRING "60.00000000"
 // Retrieval info: PRIVATE: OUTPUT_FREQ3 STRING "133.00000000"
 // Retrieval info: PRIVATE: OUTPUT_FREQ4 STRING "168.00000000"
 // Retrieval info: PRIVATE: OUTPUT_FREQ_MODE0 STRING "0"
@@ -362,9 +362,9 @@ endmodule
 // Retrieval info: CONSTANT: CLK1_DUTY_CYCLE NUMERIC "50"
 // Retrieval info: CONSTANT: CLK1_MULTIPLY_BY NUMERIC "7"
 // Retrieval info: CONSTANT: CLK1_PHASE_SHIFT STRING "0"
-// Retrieval info: CONSTANT: CLK2_DIVIDE_BY NUMERIC "4"
+// Retrieval info: CONSTANT: CLK2_DIVIDE_BY NUMERIC "5"
 // Retrieval info: CONSTANT: CLK2_DUTY_CYCLE NUMERIC "50"
-// Retrieval info: CONSTANT: CLK2_MULTIPLY_BY NUMERIC "1"
+// Retrieval info: CONSTANT: CLK2_MULTIPLY_BY NUMERIC "7"
 // Retrieval info: CONSTANT: CLK2_PHASE_SHIFT STRING "0"
 // Retrieval info: CONSTANT: CLK3_DIVIDE_BY NUMERIC "5"
 // Retrieval info: CONSTANT: CLK3_DUTY_CYCLE NUMERIC "50"

+ 2 - 2
fpga/max80.qsf

@@ -222,7 +222,6 @@ set_global_assignment -name POWER_USE_DEVICE_CHARACTERISTICS MAXIMUM
 set_global_assignment -name POWER_USE_TA_VALUE 35
 
 
-set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top
 set_global_assignment -name VERILOG_FILE ip/statusram.v
 set_global_assignment -name VERILOG_INCLUDE_FILE iodevs.vh
 set_global_assignment -name SYSTEMVERILOG_FILE tty.sv
@@ -253,4 +252,5 @@ set_global_assignment -name SYSTEMVERILOG_FILE max80.sv
 set_global_assignment -name SOURCE_FILE max80.pins
 set_global_assignment -name SOURCE_TCL_SCRIPT_FILE scripts/pins.tcl
 set_global_assignment -name VERILOG_FILE ip/fifo.v
-set_global_assignment -name VERILOG_FILE ip/ddufifo.v
+set_global_assignment -name VERILOG_FILE ip/ddufifo.v
+set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top

+ 11 - 55
fpga/max80.sv

@@ -180,61 +180,17 @@ module max80 (
      else
        vid_rst_n <= rst_n;
 
-   // HDMI - generate random data to give Quartus something to do
-   reg [23:0] dummydata = 30'hc8_fb87;
-
-   always @(posedge vid_clk)
-     dummydata <= { dummydata[22:0], dummydata[23] };
-
-   wire [7:0] hdmi_data[3];
-   wire [9:0] hdmi_tmds[3];
-   wire [29:0] hdmi_to_tx;
-
-   assign hdmi_data[0] = dummydata[7:0];
-   assign hdmi_data[1] = dummydata[15:8];
-   assign hdmi_data[2] = dummydata[23:16];
-
-   generate
-      genvar   i;
-      for (i = 0; i < 3; i = i + 1)
-	begin : hdmitmds
-	   tmdsenc enc (
-		    .rst_n ( vid_rst_n ),
-		    .clk ( vid_clk ),
-		    .den ( 1'b1 ),
-		    .d ( hdmi_data[i] ),
-		    .c ( 2'b00 ),
-		    .q ( hdmi_tmds[i] )
-		    );
-	end
-   endgenerate
-
-   assign hdmi_scl = 1'bz;
-   assign hdmi_sda = 1'bz;
-   assign hdmi_hpd = 1'bz;
-
-   //
-   // The ALTLVDS_TX megafunctions is MSB-first and in time-major order.
-   // However, TMDS is LSB-first, and we have three TMDS words that
-   // concatenate in word(channel)-major order.
-   //
-   transpose #(.words(3), .bits(10), .reverse_b(1),
-	       .reg_d(0), .reg_q(0)) hdmitranspose
-     (
-      .clk ( vid_clk ),
-      .d ( { hdmi_tmds[2], hdmi_tmds[1], hdmi_tmds[0] } ),
-      .q ( hdmi_to_tx )
-      );
-
-   hdmitx hdmitx (
-		  .pll_areset ( ~pll_locked[0] ),
-		  .tx_in ( hdmi_to_tx ),
-		  .tx_inclock ( vid_clk ),
-		  .tx_coreclock ( vid_hdmiclk ), // Pixel clock in HDMI domain
-		  .tx_locked ( pll_locked[1] ),
-		  .tx_out ( hdmi_d ),
-		  .tx_outclock ( hdmi_clk )
-		  );
+   // HDMI video interface
+   video video (
+		.rst_n      ( vid_rst_n ),
+		.vid_clk    ( vid_clk ),
+		.pll_locked ( pll_locked ),
+
+		.hdmi_d   ( hdmi_d ),
+		.hdmi_clk ( hdmi_clk ),
+		.hdmi_scl ( hdmi_scl ),
+		.hdmi_hpd ( hdmi_hpd )
+		);
 
    //
    // Internal CPU bus

BIN
fpga/output_files/max80.jbc


BIN
fpga/output_files/max80.jic


BIN
fpga/output_files/max80.pof


BIN
fpga/output_files/max80.sof


+ 79 - 110
fpga/tmdsenc.sv

@@ -2,100 +2,72 @@
 // Encodes a word in TMDS 8/10 format
 //
 
-`undef USE_TMDSROM
-
-`ifdef USE_TMDSROM
-module tmdsenc
-  (
-   input	rst_n,
-   input	clk,
-   input	den, // It is a data word, not a control word
-   input [7:0]	d, // Data word
-   input [1:0]	c, // Control word
-   output [9:0] q
-   );
-
-   reg signed [3:0]	     disparity; // Running disparity/2
-   reg [9:0]		     qreg;
-   assign q = qreg;
-
-   wire [15:0]		     romq;
-
-   tmdsrom tmdsrom (
-		    .clk ( clk ),
-		    .d ( d ),
-		    .q ( romq )
-		    );
-
-   // Delay two cycles to match tmdsrom
-   reg [1:0]		     denreg;
-   reg [1:0]		     creg[2];
-
-   wire invert =
-	(disparity > 4'sd0 & romq[10]) |
-	(disparity < 4'sd0 & romq[11]);
-
-   wire cp =  creg[1][0];
-   wire cn = ~creg[1][0];
-   wire cx = ~creg[1][0] ^ creg[1][1]; // XNOR of C1 and C0
-
-   always @(negedge rst_n or posedge clk)
-     if (~rst_n)
-       begin
-	  disparity <= 4'sd0;
-	  denreg    <= 2'b00;
-	  creg[0]   <= 2'b00;
-	  creg[1]   <= 2'b00;
-       end
-     else
-       begin
-	  denreg <= { denreg[0], den };
-	  creg[0] <= c;
-	  creg[1] <= creg[0];
-
-	  if (denreg[1])
-	    begin
-	       qreg <= romq[9:0] ^ (invert ? 10'h2ff : 10'h000);
-	       disparity <= invert ?
-			    disparity - romq[15:12] :
-			    disparity + romq[15:12];
-	    end
-	  else
-	    begin
-	       qreg <= { cx, {4{cn, cp}}, cp };
-	       disparity <= 4'd0;
-	    end // else: !if(den)
-       end // else: !if(~rst_n)
-endmodule // tmdsenc
-
-`else // not USE_TMDSROM
-
 module tmdsenc
   (
    input	rst_n,
    input	clk,
-   input	den, // It is a data word, not a control word
-   input [7:0]	d, // Data word
-   input [1:0]	c, // Control word
+   input	den,	// It is a data word, not a control word
+   input [7:0]	d,	// Data word
+   input	tercen, // Control data is TERC4 encoded
+   input [3:0]	c,	// Control or TERC4 word
    output [9:0] q
    );
 
-   reg signed [3:0] disparity; // Running disparity/2
-   reg [9:0]	    qreg = 10'b11010101000; // Symbol C0
+   // Bit 4 is TERC4 enable
+   function logic [9:0] csym(input tercen, input [4:0] sym);
+      casez ({tercen, sym})
+	// Plain TMDS control symbols
+	5'b0_??00: csym = 10'b11010_10100;
+	5'b0_??01: csym = 10'b00101_01011;
+	5'b0_??10: csym = 10'b01010_10100;
+	5'b0_??11: csym = 10'b10101_01011;
+
+	// TERC4 control symbols
+	5'b1_0000: csym = 10'b10100_11100;
+	5'b1_0001: csym = 10'b1001100011;
+	5'b1_0010: csym = 10'b1011100100;
+	5'b1_0011: csym = 10'b1011100010;
+	5'b1_0100: csym = 10'b0101110001;
+	5'b1_0101: csym = 10'b0100011110;
+	5'b1_0110: csym = 10'b0110001110;
+	5'b1_0111: csym = 10'b0100111100;
+	5'b1_1000: csym = 10'b1011001100;
+	5'b1_1001: csym = 10'b0100111001;
+	5'b1_1010: csym = 10'b0110011100;
+	5'b1_1011: csym = 10'b1011000110;
+	5'b1_1100: csym = 10'b1010001110;
+	5'b1_1101: csym = 10'b1001110001;
+	5'b1_1110: csym = 10'b0101100011;
+	5'b1_1111: csym = 10'b1011000011;
+      endcase // casez (sym)
+   endfunction // csym
+
+   function logic [9:0] bitrev10(input [9:0] in);
+      for (int i = 0; i < 10; i++)
+	bitrev10[i] = in[9-i];
+   endfunction // bitrev10
+
+   reg signed [4:0] disparity; // Running disparity/2
+   reg [9:0]	    qreg;
    assign q = qreg;
 
    reg [7:0]	    dreg;
    reg		    denreg;
-   reg [1:0]	    creg;
+   reg [3:0]	    creg;
+   reg		    tercenreg;
 
-   wire signed [2:0] delta =
-	((dreg[7] + dreg[6]) + (dreg[5] + dreg[4])) +
-	((dreg[3] + dreg[2]) + (dreg[1] + dreg[0])) - 3'sd4;
+   wire signed [3:0] ddisp =
+	dreg[7] + dreg[6] + dreg[5] + dreg[4] +
+	dreg[3] + dreg[2] + dreg[1] + dreg[0] - 'sd4;
 
    reg [8:0]	     dx;		// X(N)OR stage output
-   always @(*)
+   wire signed [3:0] xdisp =		// Does not include dx[8]!
+	dx[7] + dx[6] + dx[5] + dx[4] +
+	dx[3] + dx[2] + dx[1] + dx[0] - 'sd4;
+
+   always_comb
      begin
-	dx[8] = (delta > 3'sd0) | (~&delta & ~dreg[0]);
+	dx[8] = $signed(ddisp | { 3'd0, ~dreg[0] }) <= 4'sd0;
 	dx[0] = dreg[0];
 	dx[1] = dx[0] ^ dreg[1] ^ ~dx[8];
 	dx[2] = dx[1] ^ dreg[2] ^ ~dx[8];
@@ -106,50 +78,47 @@ module tmdsenc
 	dx[7] = dx[6] ^ dreg[7] ^ ~dx[8];
      end // always @ (*)
 
-   wire cp =  creg[0];
-   wire cn = ~creg[0];
-   wire cx = ~creg[0] ^ creg[1];	// XNOR of c[1] and c[0]
+   reg [9:0] dq;		// Disparity stage output
+   reg	     dispsign;		// Disparity counter up or down
+
+   always_comb
+     begin
+	dq[9]   = ((disparity == 5'sd0) | (xdisp == 4'sd0))
+	  ? ~dx[8] : ( disparity[4] == xdisp[3] );
+	dq[8]   = dx[8];
+	dq[7:0] = dx[7:0] ^ {{8{dq[9]}}};
+     end
+
+   wire signed [3:0] qdisp =
+	dq[9] + dq[8] + dq[7] + dq[6] + dq[5] +
+	dq[4] + dq[3] + dq[2] + dq[1] + dq[0] - 'sd5;
 
    always @(negedge rst_n or posedge clk)
      if (~rst_n)
        begin
-	  disparity <= 4'sd0;
-	  qreg      <= 10'b11010101000;
-	  dreg      <= 8'hxx;
+	  dreg      <= 'b0;
+	  disparity <= 'sd0;
+	  qreg      <= csym(1'b0, 4'b0000);
 	  denreg    <= 1'b0;
 	  creg      <= 2'b00;
+	  tercenreg <= 1'b0;
        end
      else
        begin
-	  denreg <= den;
-	  creg   <= c;
-	  dreg   <= d;
+	  denreg    <= den;
+	  tercenreg <= tercen;
+	  creg      <= c;
+	  dreg      <= d;
 
 	  if (denreg)
 	    begin
-	       if ( (disparity == 4'sd0) | (delta == 3'sd0) )
-		 begin
-		    qreg <= { ~dx[8], dx[8], dx[7:0] ^ ~{8{dx[8]}} };
-	            disparity <= dx[8] ?
-			    disparity + delta :
-			    disparity - delta;
-	         end
-	       else if ( disparity[3] ^ ~delta[2] )
-		 begin
-		    qreg <= { 1'b1, dx[8], ~dx[7:0] };
-		    disparity <= disparity - (delta - dx[8]);
-		 end
-	       else
-		 begin
-		    qreg <= { 1'b0, dx[8], dx[7:0] };
-		    disparity <= disparity + (delta - ~dx[8]);
-		 end
-	    end // if (den)
+	       qreg <= dq;
+	       disparity <= disparity + qdisp;
+	    end
 	  else
 	    begin
-	       qreg <= { cx, {4{cn, cp}}, cp };
-	       disparity <= 4'sd0;
-	    end // else: !if(den)
+	       qreg <= csym(tercenreg, creg);
+	       disparity <= 'sd0;
+	    end
        end // else: !if(~rst_n)
 endmodule // tmdsenc
-`endif

+ 24 - 31
fpga/transpose.sv

@@ -33,13 +33,13 @@ endmodule // condreg
 // this may be useful to parameterize other modules.
 //
 module transpose
-  #(parameter words,
-    parameter bits,
-    parameter reverse_w = 0,
-    parameter reverse_b = 0,
-    parameter reg_d     = 0,
-    parameter reg_q     = 0,
-    parameter transpose = 1)
+  #(parameter integer words,
+    parameter integer bits,
+    parameter [0:0] reverse_w = 1'b0,
+    parameter [0:0] reverse_b = 1'b0,
+    parameter [0:0] reg_d     = 1'b0,
+    parameter [0:0] reg_q     = 1'b0,
+    parameter [0:0] transpose = 1'b1)
    (
     input		    clk,
     input [words*bits-1:0]  d,
@@ -61,8 +61,8 @@ module transpose
 	  for (b = 0; b < bits; b = b + 1)
 	    begin
 	       integer ww, bb, ii, oo;
-	       ww = reverse_w ? words-w-1 : w;
-	       bb = reverse_b ? bits -b-1 : b;
+	       ww = reverse_w ? (words-1)-w : w;
+	       bb = reverse_b ? (bits -1)-b : b;
 	       ii = ww*bits+bb;
 	       oo = transpose ? b*words+w  : w*bits+b;
 	       out[oo] = in[ii];
@@ -75,34 +75,27 @@ endmodule // parameter
 //
 // If the parameter "reverse" is 0, then just pass through
 // the input; this may be useful to parameterize other modules.
+// This is just a special case of the transpose module.
 //
 module reverse
-  #(parameter bits,
-    parameter reg_d   = 0,
-    parameter reg_q   = 0,
-    parameter reverse = 1)
+  #(parameter integer bits,
+    parameter [0:0] reg_d   = 1'b0,
+    parameter [0:0] reg_q   = 1'b0,
+    parameter [0:0] reverse = 1'b1)
    (
     input	      clk,
     input [bits-1:0]  d,
     output [bits-1:0] q
     );
 
-   wire [bits-1:0]   in;
-   reg [bits-1:0]    out;
-
-   condreg #(.bits(bits), .register(reg_d))
-   dreg (.clk (clk), .d (d), .q(in));
-   condreg #(.bits(bits), .register(reg_q))
-   qreg (.clk (clk), .d (out), .q(q));
-
-   always @(*)
-     begin
-	integer i;
-	for (i = 0; i < bits; i = i + 1)
-	  begin
-	     integer ii;
-	     ii = reverse ? bits-i-1 : i;
-	     out[i] = in[ii];
-	  end
-     end
+   transpose #(
+	       .words     ( 1 ),
+	       .bits      ( bits ),
+	       .reverse_w ( 1'b0 ),
+	       .reverse_b ( reverse ),
+	       .reg_d     ( reg_d ),
+	       .reg_q     ( reg_q ),
+	       .transpose ( 1'b0 )
+	       )
+   rev ( .clk (clk), .d (d), .q (q) );
 endmodule // parameter

+ 132 - 0
fpga/video.sv

@@ -0,0 +1,132 @@
+module video (
+	      input	   rst_n,
+	      input	   vid_clk,
+	      input [1:0]  pll_locked,
+
+	      output [2:0] hdmi_d,
+	      output	   hdmi_clk,
+	      inout	   hdmi_scl,
+	      inout	   hdmi_sda,
+	      inout	   hdmi_hpd
+	      );
+
+   assign hdmi_scl = 1'bz;
+   assign hdmi_sda = 1'bz;
+   assign hdmi_hpd = 1'bz;
+
+   // 1024x768x60 with a 67.2 MHz pixel clock
+   // Htiming: 1024 128 112 140  = 1404
+   // Vtiming:  768   3   4  23  =  798
+   reg [10:0]	    x;
+   reg [ 9:0]	    y;
+
+   reg [7:0]	    r;
+   reg [7:0]	    g;
+   reg [7:0]	    b;
+   reg		    hblank;
+   reg		    hsync;
+   reg		    vblank;
+   reg		    vsync;
+
+   wire [7:0]	    pixbar = { x[6:0], 1'b0 } ^ {8{y[9]}};
+
+   always @(posedge vid_clk or negedge rst_n)
+     if (~rst_n)
+       begin
+	  x      <= 11'b0;
+	  y      <= 10'b0;
+	  r      <= 8'b0;
+	  g      <= 8'b0;
+	  b      <= 8'b0;
+
+	  hblank <= 1'b0;
+	  hsync  <= 1'b0;
+	  vblank <= 1'b0;
+	  vsync  <= 1'b0;
+       end
+     else
+       begin
+	  r <= pixbar & {8{x[9]}};
+	  g <= pixbar & {8{x[8]}};
+	  b <= pixbar & {8{x[7]}};
+
+	  x <= x + 1'b1;
+	  if (x >= 11'd1403)
+	    begin
+	       x <= 11'd0;
+	       y <= y + 1'b1;
+	       if (y >= 10'd797)
+		 y <= 10'd0;
+	    end
+
+	  hblank <= x[10];
+	  vblank <= &y[9:8];
+
+	  hsync  <= (x >= 11'd1152 && x < 11'd1264);
+	  vsync  <= (y >= 10'd771 && y < 10'd775);
+       end // else: !if(~rst_n)
+
+   wire [7:0] hdmi_data[0:2];
+
+   assign hdmi_data[2] = r;
+   assign hdmi_data[1] = g;
+   assign hdmi_data[0] = b;
+
+   // hdmi_ctl[4] enables TERC4 encoding
+   wire [4:0] hdmi_ctl[0:2];
+
+   assign hdmi_ctl[0][0]   = hsync;
+   assign hdmi_ctl[0][1]   = vsync;
+   assign hdmi_ctl[0][4:2] = 3'b0_00;
+   assign hdmi_ctl[1]      = 5'b0_0000;
+   assign hdmi_ctl[2]      = 5'b0_0000;
+
+   wire [9:0] hdmi_tmds_data[0:2]; // TMDS encoded data per channel
+
+   generate
+      genvar   i;
+      for (i = 0; i < 3; i = i + 1)
+	begin : hdmitmds
+	   tmdsenc enc (
+		    .rst_n ( rst_n ),
+		    .clk ( vid_clk ),
+		    .den ( ~hblank & ~vblank ),
+		    .d ( hdmi_data[i] ),
+		    .c ( hdmi_ctl[i][3:0] ),
+		    .tercen( hdmi_ctl[i][4] ),
+		    .q ( hdmi_tmds_data[i] )
+		    );
+	end
+   endgenerate
+
+   assign hdmi_scl = 1'bz;
+   assign hdmi_sda = 1'bz;
+   assign hdmi_hpd = 1'bz;
+
+   //
+   // The ALTLVDS_TX megafunctions is MSB-first and in time-major order.
+   // However, TMDS is LSB-first, and we have three TMDS words that
+   // concatenate in word(channel)-major order.
+   //
+   wire [29:0] hdmi_to_tx;	// TMDS data in the order hdmitx expects
+
+   transpose #(.words(3), .bits(10), .reverse_b(1),
+	       .reg_d(0), .reg_q(0)) hdmitranspose
+     (
+      .clk ( vid_clk ),
+      .d ( { hdmi_tmds_data[2], hdmi_tmds_data[1], hdmi_tmds_data[0] } ),
+      .q ( hdmi_to_tx )
+      );
+
+   wire        vid_hdmiclk;
+
+   hdmitx hdmitx (
+		  .pll_areset ( ~pll_locked[0] ),
+		  .tx_in ( hdmi_to_tx ),
+		  .tx_inclock ( vid_clk ),
+		  .tx_coreclock ( vid_hdmiclk ), // Pixel clock in HDMI domain
+		  .tx_locked ( pll_locked[1] ),
+		  .tx_out ( hdmi_d ),
+		  .tx_outclock ( hdmi_clk )
+		  );
+endmodule // video

+ 3 - 3
fw/boot.mif

@@ -6601,10 +6601,10 @@ CONTENT BEGIN
 19C2 : 2064656C;
 19C3 : 203A6E6F;
 19C4 : 20766F4E;
-19C5 : 32203720;
+19C5 : 32203620;
 19C6 : 20313230;
-19C7 : 333A3030;
-19C8 : 36313A37;
+19C7 : 323A3032;
+19C8 : 36313A39;
 19C9 : 5452000A;
 19CA : 49203A43;
 19CB : 53204332;