- Cleaner re-usable Makefiles for ULX3S small projects

- Created project for SDRAM framebuffer (not connected to SDRAM yet)
This commit is contained in:
Bruno Levy
2021-01-13 11:05:23 +01:00
parent 469f9f3449
commit 9f5a987b59
19 changed files with 9991 additions and 40 deletions

View File

@@ -0,0 +1,61 @@
// A PLL that generates a 250MHz clock from the 25 MHz clock.
// Generates also a 125 MHz clock (that can be used with the
// DDR-based shifter, that shifts two bits per clock, but I'm
// not using it for now).
//
// Inspirations from:
// https://github.com/lawrie/ulx3s_examples/blob/master/hdmi/
// 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

View File

@@ -0,0 +1,12 @@
SDRAM framebuffer Test
======================
_WIP_
My experiments to learn how to talk to an SDRAM chip and display a
framebuffer stored on it to HDMI.
More notes and information:
- [SDRAM(https://github.com/BrunoLevy/learn-fpga/blob/master/FemtoRV/TUTORIALS/SDRAM.md).
- [HDMI](https://github.com/BrunoLevy/learn-fpga/blob/master/FemtoRV/TUTORIALS/HDMI.md).

View File

@@ -0,0 +1,91 @@
// WIP: framebuffer in SDRAM with HDMI output.
module SDRAM_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 *********************************************************/
reg [7:0] red, green, blue;
reg [31:0] counter;
always @(posedge pixclk) begin
counter <= counter + 1;
end
always @(posedge pixclk) begin
red <= CounterX[7:0] + counter[25:18];
green <= CounterX[9:2];
blue <= CounterY[7:0] + counter[25:18];;
end
/******** RGB TMDS encoding ***************************************************/
// Generate 10-bits TMDS red,green,blue signals. Blue embeds HSync/VSync in its
// control part.
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));
/******** 125 MHz clock *******************************************************/
// This one needs some FPGA-specific specialized blocks (a PLL).
wire half_clk_TMDS; // The 125 MHz clock used if using DDR mode.
HDMI_clock hdmi_clock(.clk(pixclk), .half_hdmi_clk(half_clk_TMDS));
/******** Shifter *************************************************************/
// Serialize the three 10-bits TMDS red,green,blue signals.
// Another version of the shifter, that shifts and sends two bits per clock,
// using the ODDRX1F block of the ULX3S.
// Counter now counts modulo 5 instead of modulo 10
reg [4:0] TMDS_mod5=1;
wire TMDS_shift_load = TMDS_mod5[4];
always @(posedge half_clk_TMDS) TMDS_mod5 <= {TMDS_mod5[3:0],TMDS_mod5[4]};
// Shifter now shifts two bits at each clock
reg [9:0] TMDS_shift_red=0, TMDS_shift_green=0, TMDS_shift_blue=0;
always @(posedge half_clk_TMDS) begin
TMDS_shift_red <= TMDS_shift_load ? TMDS_red : TMDS_shift_red [9:2];
TMDS_shift_green <= TMDS_shift_load ? TMDS_green : TMDS_shift_green[9:2];
TMDS_shift_blue <= TMDS_shift_load ? TMDS_blue : TMDS_shift_blue [9:2];
end
// DDR serializers: they send D0 at the rising edge and D1 at the falling edge.
ODDRX1F ddr_red (.D0(TMDS_shift_red[0]), .D1(TMDS_shift_red[1]), .Q(TMDS_rgb_p[2]), .SCLK(half_clk_TMDS), .RST(1'b0));
ODDRX1F ddr_green(.D0(TMDS_shift_green[0]), .D1(TMDS_shift_green[1]), .Q(TMDS_rgb_p[1]), .SCLK(half_clk_TMDS), .RST(1'b0));
ODDRX1F ddr_blue (.D0(TMDS_shift_blue[0]), .D1(TMDS_shift_blue[1]), .Q(TMDS_rgb_p[0]), .SCLK(half_clk_TMDS), .RST(1'b0));
// The pixel clock, still the same as before.
assign TMDS_clock_p = pixclk;
// Note (again): gpdi_dn[3:0] is generated automatically by LVCMOS33D mode in ulx3s.lpf
endmodule

View File

@@ -0,0 +1,44 @@
// Taken from: https://www.fpga4fun.com/HDMI.html
// (c) fpga4fun.com & KNJN LLC 2013
module TMDS_encoder(
input clk, // Pixel clock (25 MHz for 640x480)
input [7:0] VD, // video data (one of 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 // The generated 10-bits signal (scrambled to minimize transitions, and 0/1-balanced)
);
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);
// [Bruno Levy Jan 2021]
// Compact writing: wire [8:0] q_m = {~XNOR, q_m[6:0] ^ VD[7:1] ^ {7{XNOR}}, VD[0]};
// ... generates combinatorial loop warning, so I'd rather expand it (less compact,
// less elegant, but I did not like this combinatorial loop warning).
wire [8:0] q_m;
assign q_m[0] = VD[0];
assign q_m[1] = q_m[0] ^ VD[1] ^ XNOR;
assign q_m[2] = q_m[1] ^ VD[2] ^ XNOR;
assign q_m[3] = q_m[2] ^ VD[3] ^ XNOR;
assign q_m[4] = q_m[3] ^ VD[4] ^ XNOR;
assign q_m[5] = q_m[4] ^ VD[5] ^ XNOR;
assign q_m[6] = q_m[5] ^ VD[6] ^ XNOR;
assign q_m[7] = q_m[6] ^ VD[7] ^ XNOR;
assign q_m[8] = ~XNOR;
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) begin
TMDS <= VDE ? TMDS_data : TMDS_code;
balance_acc <= VDE ? balance_acc_new : 4'h0;
end
endmodule

View File

@@ -0,0 +1,14 @@
PROJECTNAME=SDRAM_HDMI_test
VERILOGS="$PROJECTNAME.v HDMI_clock.v TMDS_encoder.v"
if [ $1 == "clean" ]; then
rm -f *.bit *.json *.config *.svf *~
exit
fi
yosys -p "synth_ecp5 -abc9 -top $PROJECTNAME -json $PROJECTNAME.json" $VERILOGS || exit
nextpnr-ecp5 --json $PROJECTNAME.json --lpf ulx3s.lpf --textcfg $PROJECTNAME.config --85k --freq 25 --package CABGA381 || exit
ecppack --compress --svf-rowsize 100000 --svf $PROJECTNAME.svf $PROJECTNAME.config $PROJECTNAME.bit || exit
ujprog $PROJECTNAME.bit || exit
# To flash permanently, use instead:
# Use ujprog -j FLASH $PROJECTNAME.bit

View 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;

View File

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@
// - 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(
module HDMI_test_DDR(
input pclk,
output [3:0] gpdi_dp
// Note: gpdi_dn[3:0] is generated automatically by LVCMOS33D mode in ulx3s.lpf

View File

@@ -0,0 +1,6 @@
HDMI Test
=========
My experiments to learn how to display stuff on HDMI.
More information [here](https://github.com/BrunoLevy/learn-fpga/blob/master/FemtoRV/TUTORIALS/HDMI.md).

View File

@@ -1,7 +1,15 @@
yosys -p "synth_ecp5 -abc9 -top HDMI_test -json HDMI_test.json" HDMI_test_DDR.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
PROJECTNAME=HDMI_test
#PROJECTNAME=HDMI_test_DDR # This one uses DDR primitives for higher freq, ready for higher res (to be tested)
VERILOGS="$PROJECTNAME.v HDMI_clock.v TMDS_encoder.v"
if [ $1 == "clean" ]; then
rm -f *.bit *.json *.config *.svf *~
exit
fi
yosys -p "synth_ecp5 -abc9 -top $PROJECTNAME -json $PROJECTNAME.json" $VERILOGS || exit
nextpnr-ecp5 --json $PROJECTNAME.json --lpf ulx3s.lpf --textcfg $PROJECTNAME.config --85k --freq 25 --package CABGA381 || exit
ecppack --compress --svf-rowsize 100000 --svf $PROJECTNAME.svf $PROJECTNAME.config $PROJECTNAME.bit || exit
ujprog $PROJECTNAME.bit || exit
# To flash permanently, use instead:
# Use ujprog -j FLASH $PROJECTNAME.bit