New ULX3S HDMI example.
This commit is contained in:
54
Basic/ULX3S_hdmi/HDMI_clock.v
Normal file
54
Basic/ULX3S_hdmi/HDMI_clock.v
Normal file
@@ -0,0 +1,54 @@
|
||||
// Inspirations from https://github.com/sylefeb/Silice/blob/master/projects/common/hdmi_clock.v
|
||||
|
||||
module HDMI_clock (
|
||||
input clk, // 25 MHz
|
||||
output out_clk, // 25 MHz
|
||||
output hdmi_clk, // 250 MHz
|
||||
output half_hdmi_clk // 125 MHz
|
||||
);
|
||||
|
||||
wire clkfb;
|
||||
wire locked;
|
||||
|
||||
(* ICP_CURRENT="12" *) (* LPF_RESISTOR="8" *) (* MFG_ENABLE_FILTEROPAMP="1" *) (* MFG_GMCREF_SEL="2" *)
|
||||
EHXPLLL #(
|
||||
.PLLRST_ENA("DISABLED"),
|
||||
.INTFB_WAKE("DISABLED"),
|
||||
.STDBY_ENABLE("DISABLED"),
|
||||
.DPHASE_SOURCE("DISABLED"),
|
||||
.CLKOP_FPHASE(0),
|
||||
.CLKOP_CPHASE(0),
|
||||
.OUTDIVIDER_MUXA("DIVA"),
|
||||
.CLKOP_ENABLE("ENABLED"),
|
||||
.CLKOP_DIV(2),
|
||||
.CLKOS_ENABLE("ENABLED"),
|
||||
.CLKOS_DIV(4),
|
||||
.CLKOS_CPHASE(0),
|
||||
.CLKOS_FPHASE(0),
|
||||
.CLKOS2_ENABLE("ENABLED"),
|
||||
.CLKOS2_DIV(20),
|
||||
.CLKOS2_CPHASE(0),
|
||||
.CLKOS2_FPHASE(0),
|
||||
.CLKFB_DIV(10),
|
||||
.CLKI_DIV(1),
|
||||
.FEEDBK_PATH("INT_OP")
|
||||
) pll_i (
|
||||
.CLKI(clk),
|
||||
.CLKFB(clkfb),
|
||||
.CLKINTFB(clkfb),
|
||||
.CLKOP(hdmi_clk), // 250
|
||||
.CLKOS(half_hdmi_clk), // 125
|
||||
.CLKOS2(out_clk), // 25
|
||||
.RST(1'b0),
|
||||
.STDBY(1'b0),
|
||||
.PHASESEL0(1'b0),
|
||||
.PHASESEL1(1'b0),
|
||||
.PHASEDIR(1'b0),
|
||||
.PHASESTEP(1'b0),
|
||||
.PLLWAKESYNC(1'b0),
|
||||
.ENCLKOP(1'b0),
|
||||
.LOCK(locked)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
98
Basic/ULX3S_hdmi/HDMI_test.v
Normal file
98
Basic/ULX3S_hdmi/HDMI_test.v
Normal file
@@ -0,0 +1,98 @@
|
||||
// Started from: https://www.fpga4fun.com/HDMI.html (c) fpga4fun.com & KNJN LLC 2013
|
||||
// Adapted to ECP5 / ULX3S
|
||||
// Then introduced some ideas from Lawrie's code here: https://github.com/lawrie/ulx3s_examples/blob/master/hdmi/
|
||||
// See also https://github.com/sylefeb/Silice/tree/master/projects/hdmi_test (also based on Lawrie's code).
|
||||
// I'm not using Lawrie's "fake differential" but instead I'm using LVCMOS33D mode for the HDMI pins in ulx3S.lpf,
|
||||
// that automatically generates the negative signal from the positive one.
|
||||
// See: https://www.gitmemory.com/issue/YosysHQ/nextpnr/544/751511265
|
||||
// See also LATTICE ECP5 and ECP5-5G sysI/O Usage Guide / Technical note
|
||||
// In Lawrie's "fake differential", there is the ODRX1F trick that makes it possible to operate at half the
|
||||
// frequency for the bit clock, may be interesting/necessary to use for res higher than 640x480.
|
||||
// I've seen also some ECP5 primitives: OLVDS (A->Z,ZN) and OBCO (I->OT,OC)
|
||||
// that I tried to use with the standard LVCMOS33 mode, without success.
|
||||
|
||||
module HDMI_test(
|
||||
input pclk,
|
||||
output [3:0] gpdi_dp
|
||||
// Note: gpdi_dn[3:0] is generated automatically by LVCMOS33D mode in ulx3s.lpf
|
||||
);
|
||||
|
||||
HDMI_gen hdmi_gen(
|
||||
.pixclk(pclk),
|
||||
.TMDS_rgb_p(gpdi_dp[2:0]),
|
||||
.TMDS_clock_p(gpdi_dp[3])
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
/******************************************************************************/
|
||||
module HDMI_gen(
|
||||
input pixclk, // 25MHz
|
||||
output [2:0] TMDS_rgb_p, TMDS_rgb_n, // HDMI pins: RGB
|
||||
output TMDS_clock_p, TMDS_clock_n // HDMI pins: clock
|
||||
);
|
||||
|
||||
/******** Video generation ****************************************************/
|
||||
reg [9:0] CounterX, CounterY;
|
||||
reg hSync, vSync, DrawArea;
|
||||
always @(posedge pixclk) DrawArea <= (CounterX<640) && (CounterY<480);
|
||||
|
||||
always @(posedge pixclk) CounterX <= (CounterX==799) ? 0 : CounterX+1;
|
||||
always @(posedge pixclk) if(CounterX==799) CounterY <= (CounterY==524) ? 0 : CounterY+1;
|
||||
|
||||
always @(posedge pixclk) hSync <= (CounterX>=656) && (CounterX<752);
|
||||
always @(posedge pixclk) vSync <= (CounterY>=490) && (CounterY<492);
|
||||
|
||||
/******** Draw something ******************************************************/
|
||||
wire [7:0] W = {8{CounterX[7:0]==CounterY[7:0]}};
|
||||
wire [7:0] A = {8{CounterX[7:5]==3'h2 && CounterY[7:5]==3'h2}};
|
||||
reg [7:0] red, green, blue;
|
||||
always @(posedge pixclk) red <= ({CounterX[5:0] & {6{CounterY[4:3]==~CounterX[4:3]}}, 2'b00} | W) & ~A;
|
||||
always @(posedge pixclk) green <= (CounterX[7:0] & {8{CounterY[6]}} | W) & ~A;
|
||||
always @(posedge pixclk) blue <= CounterY[7:0] | W | A;
|
||||
|
||||
/******** RGB TMDS encoding ***************************************************/
|
||||
wire [9:0] TMDS_red, TMDS_green, TMDS_blue;
|
||||
TMDS_encoder encode_R(.clk(pixclk), .VD(red ), .CD(2'b00) , .VDE(DrawArea), .TMDS(TMDS_red));
|
||||
TMDS_encoder encode_G(.clk(pixclk), .VD(green), .CD(2'b00) , .VDE(DrawArea), .TMDS(TMDS_green));
|
||||
TMDS_encoder encode_B(.clk(pixclk), .VD(blue ), .CD({vSync,hSync}), .VDE(DrawArea), .TMDS(TMDS_blue));
|
||||
|
||||
/******** 250 MHz clock *******************************************************/
|
||||
// This one needs some FPGA-specific specialized blocks
|
||||
wire clk_TMDS;
|
||||
HDMI_clock hdmi_clock(.clk(pixclk), .hdmi_clk(clk_TMDS));
|
||||
|
||||
/******** Shifter *************************************************************/
|
||||
// Q: can we use the 25 MHz output clock of HDMI_clock instead of recomputing mod10 ?
|
||||
// Q: can we use Olof's trick (decrement, test sign bit) ?
|
||||
reg [3:0] TMDS_mod10=0; // modulus 10 counter
|
||||
reg [9:0] TMDS_shift_red=0, TMDS_shift_green=0, TMDS_shift_blue=0;
|
||||
reg TMDS_shift_load=0;
|
||||
always @(posedge clk_TMDS) TMDS_shift_load <= (TMDS_mod10==4'd9);
|
||||
|
||||
always @(posedge clk_TMDS) begin
|
||||
TMDS_shift_red <= TMDS_shift_load ? TMDS_red : TMDS_shift_red [9:1];
|
||||
TMDS_shift_green <= TMDS_shift_load ? TMDS_green : TMDS_shift_green[9:1];
|
||||
TMDS_shift_blue <= TMDS_shift_load ? TMDS_blue : TMDS_shift_blue [9:1];
|
||||
TMDS_mod10 <= (TMDS_mod10==4'd9) ? 4'd0 : TMDS_mod10+4'd1;
|
||||
end
|
||||
|
||||
/******** Output to HDMI *****************************************************/
|
||||
|
||||
assign TMDS_rgb_p[2] = TMDS_shift_red[0];
|
||||
assign TMDS_rgb_p[1] = TMDS_shift_green[0];
|
||||
assign TMDS_rgb_p[0] = TMDS_shift_blue[0];
|
||||
assign TMDS_clock_p = pixclk;
|
||||
|
||||
// Note: what's below would not work, _p and _n sides
|
||||
// require exact synchronization that could not be
|
||||
// guaranteed if written like that.
|
||||
// In fact, the negative side is not wired in the HDMI_test
|
||||
// module. I'm generating it at the level of the
|
||||
// output pins using LVCMOS33D pin type in ulx3s.lpf
|
||||
assign TMDS_rgb_n[2] = !TMDS_shift_red[0];
|
||||
assign TMDS_rgb_n[1] = !TMDS_shift_green[0];
|
||||
assign TMDS_rgb_n[0] = !TMDS_shift_blue[0];
|
||||
assign TMDS_clock_n = !pixclk;
|
||||
|
||||
endmodule
|
||||
27
Basic/ULX3S_hdmi/TMDS_encoder.v
Normal file
27
Basic/ULX3S_hdmi/TMDS_encoder.v
Normal file
@@ -0,0 +1,27 @@
|
||||
// (c) fpga4fun.com & KNJN LLC 2013
|
||||
|
||||
module TMDS_encoder(
|
||||
input clk, // Pixel clock (25 MHz for 640x480)
|
||||
input [7:0] VD, // video data (red, green or blue)
|
||||
input [1:0] CD, // control data
|
||||
input VDE, // video data enable, to choose between CD (when VDE=0) and VD (when VDE=1)
|
||||
output reg [9:0] TMDS = 0
|
||||
);
|
||||
|
||||
wire [3:0] Nb1s = VD[0] + VD[1] + VD[2] + VD[3] + VD[4] + VD[5] + VD[6] + VD[7];
|
||||
wire XNOR = (Nb1s>4'd4) || (Nb1s==4'd4 && VD[0]==1'b0);
|
||||
wire [8:0] q_m = {~XNOR, q_m[6:0] ^ VD[7:1] ^ {7{XNOR}}, VD[0]};
|
||||
|
||||
reg [3:0] balance_acc = 0;
|
||||
wire [3:0] balance = q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7] - 4'd4;
|
||||
wire balance_sign_eq = (balance[3] == balance_acc[3]);
|
||||
wire invert_q_m = (balance==0 || balance_acc==0) ? ~q_m[8] : balance_sign_eq;
|
||||
wire [3:0] balance_acc_inc = balance - ({q_m[8] ^ ~balance_sign_eq} & ~(balance==0 || balance_acc==0));
|
||||
wire [3:0] balance_acc_new = invert_q_m ? balance_acc-balance_acc_inc : balance_acc+balance_acc_inc;
|
||||
wire [9:0] TMDS_data = {invert_q_m, q_m[8], q_m[7:0] ^ {8{invert_q_m}}};
|
||||
wire [9:0] TMDS_code = CD[1] ? (CD[0] ? 10'b1010101011 : 10'b0101010100) : (CD[0] ? 10'b0010101011 : 10'b1101010100);
|
||||
|
||||
always @(posedge clk) TMDS <= VDE ? TMDS_data : TMDS_code;
|
||||
always @(posedge clk) balance_acc <= VDE ? balance_acc_new : 4'h0;
|
||||
endmodule
|
||||
|
||||
7
Basic/ULX3S_hdmi/makeit.sh
Executable file
7
Basic/ULX3S_hdmi/makeit.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
yosys -p "synth_ecp5 -abc9 -top HDMI_test -json HDMI_test.json" HDMI_test.v HDMI_clock.v TMDS_encoder.v
|
||||
nextpnr-ecp5 --json HDMI_test.json --lpf ulx3s.lpf --textcfg HDMI_test_out.config --85k --freq 25 --package CABGA381
|
||||
ecppack --compress --svf-rowsize 100000 --svf HDMI_test.svf HDMI_test_out.config HDMI_test.bit
|
||||
ujprog HDMI_test.bit
|
||||
|
||||
|
||||
|
||||
30
Basic/ULX3S_hdmi/ulx3s.lpf
Normal file
30
Basic/ULX3S_hdmi/ulx3s.lpf
Normal file
@@ -0,0 +1,30 @@
|
||||
# See https://github.com/emard/ulx3s/blob/master/doc/constraints/ulx3s_v20.lpf
|
||||
|
||||
## Clock
|
||||
|
||||
LOCATE COMP "pclk" SITE "G2";
|
||||
IOBUF PORT "pclk" PULLMODE=NONE IO_TYPE=LVCMOS33;
|
||||
FREQUENCY PORT "pclk" 25 MHZ;
|
||||
|
||||
## HDMI
|
||||
# Note: if using IO_TYPE=LVCMOS33D, it automatically
|
||||
# generates the negative (_dn[]) pins from the
|
||||
# positive ones.
|
||||
LOCATE COMP "gpdi_dp[0]" SITE "A16"; # Blue +
|
||||
LOCATE COMP "gpdi_dn[0]" SITE "B16"; # Blue -
|
||||
LOCATE COMP "gpdi_dp[1]" SITE "A14"; # Green +
|
||||
LOCATE COMP "gpdi_dn[1]" SITE "C14"; # Green -
|
||||
LOCATE COMP "gpdi_dp[2]" SITE "A12"; # Red +
|
||||
LOCATE COMP "gpdi_dn[2]" SITE "A13"; # Red -
|
||||
LOCATE COMP "gpdi_dp[3]" SITE "A17"; # Clock +
|
||||
LOCATE COMP "gpdi_dn[3]" SITE "B18"; # Clock -
|
||||
IOBUF PORT "gpdi_dp[0]" IO_TYPE=LVCMOS33D DRIVE=4;
|
||||
IOBUF PORT "gpdi_dn[0]" IO_TYPE=LVCMOS33D DRIVE=4;
|
||||
IOBUF PORT "gpdi_dp[1]" IO_TYPE=LVCMOS33D DRIVE=4;
|
||||
IOBUF PORT "gpdi_dn[1]" IO_TYPE=LVCMOS33D DRIVE=4;
|
||||
IOBUF PORT "gpdi_dp[2]" IO_TYPE=LVCMOS33D DRIVE=4;
|
||||
IOBUF PORT "gpdi_dn[2]" IO_TYPE=LVCMOS33D DRIVE=4;
|
||||
IOBUF PORT "gpdi_dp[3]" IO_TYPE=LVCMOS33D DRIVE=4;
|
||||
IOBUF PORT "gpdi_dn[3]" IO_TYPE=LVCMOS33D DRIVE=4;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user