New ULX3S HDMI example.

This commit is contained in:
Bruno Levy
2021-01-01 13:42:54 +01:00
parent 4ee857b958
commit 9eab504f29
14 changed files with 254 additions and 26 deletions

View 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

View 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

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

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

@@ -14,18 +14,21 @@ exit:
exitl1:
bnez a0, exitl1 # if exit code is non-0, loop.
exitl:
li a0, 100 # for milliwait
li t0, 1
sw t0, IO_LEDS(gp)
li a0, 100
call milliwait
li t0, 2
sw t0, IO_LEDS(gp)
li a0, 100
call milliwait
li t0, 4
sw t0, IO_LEDS(gp)
li a0, 100
call milliwait
li t0, 8
sw t0, IO_LEDS(gp)
li a0, 100
call milliwait
j exitl

View File

@@ -5,16 +5,16 @@
FIRMWARE_DIR=/home/blevy/Programming/learn-fpga/FemtoRV/FIRMWARE
# Configuration for larger boards (ULX3S, ECP5-EVN)
#ARCH=rv32im
#ABI=ilp32
#OPTIMIZE=-O3
ARCH=rv32im
ABI=ilp32
OPTIMIZE=-O3
# More minimalistic configurations (ICEStick, ICE40)
# note: ICE40up5K could support rv32im but this requires some work
# since yosys does not (yet) infer DSP blocks for ICE40).
ARCH=rv32i
ABI=ilp32
OPTIMIZE=-Os
#ARCH=rv32i
#ABI=ilp32
#OPTIMIZE=-Os
# Optimize for size, on the IceStick we only have 6K of RAM !
# Normally you do not need to modify anything beyond this line.

View File

@@ -16,8 +16,12 @@ TODO:
ALU:
in1,in2,aluInSel1(reg/pc),aluInSel2(reg/imm)
A,B,A_is_pc,B_is_imm
Pour augmenter FMAX:
on devrait peut-être latcher:
Les tests pour les branchements
Le calcul d'adresses
(et il faudrait aussi essayer d'utiliser pour ça la primitive ALU ECP5)
* register using combinatorial RAM.
* print_float, printf("%f")
* A 90-ish "megademo" on the OLED display !

View File

@@ -3,23 +3,23 @@
/************************* Devices **********************************************************************************/
`define NRV_IO_LEDS // Mapped IO, LEDs D1,D2,D3,D4 (D5 is used to display errors)
`define NRV_IO_UART // Mapped IO, virtual UART (USB)
//`define NRV_IO_SSD1351 // Mapped IO, 128x128x64K OLed screen
//`define NRV_IO_UART // Mapped IO, virtual UART (USB)
`define NRV_IO_SSD1351 // Mapped IO, 128x128x64K OLed screen
//`define NRV_IO_MAX7219 // Mapped IO, 8x8 led matrix
//`define NRV_IO_SPI_FLASH // Mapped IO, SPI flash
//`define NRV_IO_SPI_SDCARD // Mapped IO, SPI SDCARD
//`define NRV_IO_BUTTONS // Mapped IO, buttons
`define NRV_IO_SPI_SDCARD // Mapped IO, SPI SDCARD
`define NRV_IO_BUTTONS // Mapped IO, buttons
//`define NRV_MAPPED_SPI_FLASH // SPI flash mapped in address space. Use with MINIRV32 to run code from SPI flash.
/************************* Frequency ********************************************************************************/
`define NRV_FREQ 50 // Frequency in MHz. Recomm: 50 MHz (FOMU: 16MHz) Overclocking: 80-100 MHz (HX1K, ECP5)
`define NRV_FREQ 100 // Frequency in MHz. Recomm: 50 MHz (FOMU: 16MHz) Overclocking: 80-100 MHz (HX1K, ECP5)
/************************* RAM (in bytes, needs to be a multiple of 4)***********************************************/
//`define NRV_RAM 393216 // bigger config for ULX3S
//`define NRV_RAM 262144 // default for ULX3S
`define NRV_RAM 6144 // default for ICESTICK (cannot do more !)
`define NRV_RAM 262144 // default for ULX3S
//`define NRV_RAM 6144 // default for ICESTICK (cannot do more !)
//`define NRV_RAM 1024 // small ICESTICK config (to further save LUTs if need be)
/************************* Processor configuration ******************************************************************/
@@ -27,12 +27,12 @@
//`define NRV_MINIRV32 // Mini config, can execute code stored in SPI flash from 1Mb offset (mapped to address 0x800000)
`ifndef NRV_MINIRV32 // The options below are not supported by minifemtorv32
//`define NRV_CSR // Uncomment if using something below (counters,...)
//`define NRV_COUNTERS // Uncomment for instr and cycle counters (won't fit on the ICEStick)
//`define NRV_COUNTERS_64 // ... and uncomment this one as well if you want 64-bit counters
//`define NRV_RV32M // Uncomment for hardware mul and div support (RV32M instructions). Not supported on IceStick !
`define NRV_CSR // Uncomment if using something below (counters,...)
`define NRV_COUNTERS // Uncomment for instr and cycle counters (won't fit on the ICEStick)
`define NRV_COUNTERS_64 // ... and uncomment this one as well if you want 64-bit counters
`define NRV_RV32M // Uncomment for hardware mul and div support (RV32M instructions). Not supported on IceStick !
`define NRV_TWOSTAGE_SHIFTER // if not RV32M, comment-out if running out of LUTs (at the expense of slower shifts)
//`define NRV_LATCH_ALU // Uncomment to latch all ALU ops (reduces critical path)
`define NRV_LATCH_ALU // Uncomment to latch all ALU ops (reduces critical path)
`endif
/************************* Advanced processor configuration *********************************************************/

View File

@@ -7,7 +7,7 @@ simple and self-contained design that works well with the ULX3S. There
are several reasons, one of them is that we need to use some specialized
blocks, that may differ from one FPGA to another.
First thing I found is the [fpga4fun](https://www.fpga4fun.com/HDMI.html)
First thing I found is the [fpga4fun](https://www.fpga4fun.com/HDMI.html)
website. It has a nice explanation and example program (that will need
to be adapted though). We learn there that the HDMI connector has 19
pins, but that we need to generate signals on 8 of them, forming 4
@@ -28,7 +28,8 @@ signal, just like VGA.
Now we need to serialize the red, green and blue signals, but there are
some complications: we learn that the bits need to be scrambled in
some way, and that there will be 2 additional bits per signal
(maybe something like stop and parity bits in serial line). For that,
(if you want to know, this is for minimizing the transitions between
0 and 1, and for balancing the number of 0 and 1). For that,
there is a `TMDS_encoder` module. It takes as input the pixel clock,
the 8-bits channel data, two bits of control, and a VDE (Video Display
Enable) signal (that goes zero when we are offscreen). The two bits of
@@ -98,5 +99,8 @@ I think I got it !
References
----------
[Silice HDMI walkthrough](https://github.com/sylefeb/Silice/tree/master/projects/hdmi_test)
- [fpga4fun](https://www.fpga4fun.com/HDMI.html)
- [UlatraEmbedded's core dvi FB](https://github.com/ultraembedded/core_dvi_framebuffer/blob/master/src_v/dvi.v)
- [Silice HDMI walkthrough](https://github.com/sylefeb/Silice/tree/master/projects/hdmi_test)
- [HDMI with audio](https://github.com/hdl-util/hdmi/)
- [HDMI tutorial](https://purisa.me/blog/hdmi-on-fpga/)

View File

@@ -16,7 +16,8 @@ I2C bit-banging:
https://github.com/ultraembedded/core_dvi_framebuffer
(+ TMDS encoder with permissive license)
https://github.com/XarkLabs/Xosera
https://github.com/XarkLabs/Xosera (text mode VGA, HDMI ... through PMOD)