Pārlūkot izejas kodu

cores/hub75: Add support for DDR PHY

Most panels don't support this natively, but with minimal external
hardware, this can increase the number of data lines shifted out at
once.

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Sylvain Munaut 5 gadi atpakaļ
vecāks
revīzija
68d6faf21d
3 mainītis faili ar 296 papildinājumiem un 24 dzēšanām
  1. 1 0
      cores/hub75/core.mk
  2. 239 0
      cores/hub75/rtl/hub75_phy_ddr.v
  3. 56 24
      cores/hub75/rtl/hub75_top.v

+ 1 - 0
cores/hub75/core.mk

@@ -12,6 +12,7 @@ RTL_SRCS_hub75 := $(addprefix rtl/, \
 	hub75_gamma.v \
 	hub75_linebuffer.v \
 	hub75_phy.v \
+	hub75_phy_ddr.v \
 	hub75_scan.v \
 	hub75_shift.v \
 	hub75_top.v \

+ 239 - 0
cores/hub75/rtl/hub75_phy_ddr.v

@@ -0,0 +1,239 @@
+/*
+ * hub75_phy_ddr.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * Copyright (C) 2019  Piotr Esden-Tempski <piotr@esden.net>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module hub75_phy_ddr #(
+	parameter integer N_BANKS  = 2,
+	parameter integer N_ROWS   = 32,
+	parameter integer N_CHANS  = 3,
+	parameter integer PHY_AIR  = 0,		// PHY Address Inc/Reset
+	parameter integer PHY_DDR  = 1,		// PHY DDR Phase
+
+	// Auto-set
+	parameter integer SDW         = N_BANKS * N_CHANS,
+	parameter integer ESDW        = SDW / 2,
+	parameter integer LOG_N_ROWS  = $clog2(N_ROWS)
+)(
+	// Hub75 interface pads
+	output wire hub75_addr_inc,
+	output wire hub75_addr_rst,
+	output wire [LOG_N_ROWS-1:0] hub75_addr,
+	output wire [ESDW-1:0] hub75_data,
+	output wire hub75_clk,
+	output wire hub75_le,
+	output wire hub75_blank,
+
+	// PHY interface signals
+	input wire phy_addr_inc,
+	input wire phy_addr_rst,
+	input wire [LOG_N_ROWS-1:0] phy_addr,
+	input wire [SDW-1:0] phy_data,
+	input wire phy_clk,
+	input wire phy_le,
+	input wire phy_blank,
+
+	// Clock / Reset
+	input  wire clk,
+	input  wire clk_2x,
+	input  wire rst
+);
+	// Signals
+	// -------
+
+	// Sync
+	reg sync_toggle;
+	reg sync_done;
+	reg [1:0] sync_cap;
+	reg [1:0] sync;			// [0] in phase with clk, [1] is clk_n
+
+	// Cross-clock
+	reg  cc_addr_inc;
+	reg  cc_addr_rst;
+	reg  [LOG_N_ROWS-1:0] cc_addr;
+	reg  [(N_BANKS*N_CHANS)-1:0] cc_data;
+	reg  cc_clk;
+	reg  cc_le;
+	reg  cc_blank;
+
+	// Data Mux
+	wire [ESDW-1:0] mux_data;
+
+	// External Shift clock
+	reg clk_sig;
+
+
+	// Capture signals in 2x domain
+	// ----------------------------
+
+	// Sync signals
+	always @(posedge clk or posedge rst)
+		if (rst)
+			sync_toggle <= 1'b0;
+		else
+			sync_toggle <= ~sync_toggle;
+
+	always @(posedge clk_2x or posedge rst)
+	begin
+		if (rst) begin
+			sync_done <= 1'b0;
+			sync_cap  <= 2'b00;
+			sync      <= 2'b00;
+		end else begin
+			sync_done <= sync_done | (sync_cap[0] ^ sync_cap[1]);
+			sync_cap  <= { sync_cap[0], sync_toggle };
+			sync[0]   <= sync_done ? ~sync[0] : (sync_cap[0] ^ sync_cap[1]);
+			sync[1]   <= sync[0];
+		end
+	end
+
+	// Capture
+	always @(posedge clk_2x or posedge rst)
+	begin
+		if (rst) begin
+			cc_addr_inc <= 1'b0;
+			cc_addr_rst <= 1'b0;
+			cc_addr     <= 0;
+			cc_data     <= 0;
+			cc_clk      <= 1'b0;
+			cc_le       <= 1'b0;
+			cc_blank    <= 1'b0;
+		end else if (sync[0]) begin
+			cc_addr_inc <= phy_addr_inc ^ PHY_AIR[1];
+			cc_addr_rst <= phy_addr_rst ^ PHY_AIR[2];
+			cc_addr     <= phy_addr;
+			cc_data     <= phy_data;
+			cc_clk      <= phy_clk;
+			cc_le       <= phy_le;
+			cc_blank    <= phy_blank;
+		end
+	end
+
+
+	// IOB
+	// ---
+
+	// Address
+	generate
+		if (PHY_AIR == 0) begin
+			SB_IO #(
+				.PIN_TYPE(6'b010100),
+				.PULLUP(1'b0),
+				.NEG_TRIGGER(1'b0),
+				.IO_STANDARD("SB_LVCMOS")
+			) iob_addr_I[LOG_N_ROWS-1:0] (
+				.PACKAGE_PIN(hub75_addr),
+				.CLOCK_ENABLE(1'b1),
+				.OUTPUT_CLK(clk_2x),
+				.D_OUT_0(cc_addr)
+			);
+		end else begin
+			SB_IO #(
+				.PIN_TYPE(6'b010100),
+				.PULLUP(1'b0),
+				.NEG_TRIGGER(1'b0),
+				.IO_STANDARD("SB_LVCMOS")
+			) iob_addr_inc_I (
+				.PACKAGE_PIN(hub75_addr_inc),
+				.CLOCK_ENABLE(1'b1),
+				.OUTPUT_CLK(clk_2x),
+				.D_OUT_0(cc_addr_inc)
+			);
+
+			SB_IO #(
+				.PIN_TYPE(6'b010100),
+				.PULLUP(1'b0),
+				.NEG_TRIGGER(1'b0),
+				.IO_STANDARD("SB_LVCMOS")
+			) iob_addr_rst_I (
+				.PACKAGE_PIN(hub75_addr_rst),
+				.CLOCK_ENABLE(1'b1),
+				.OUTPUT_CLK(clk_2x),
+				.D_OUT_0(cc_addr_rst)
+			);
+		end
+	endgenerate
+
+	// Data lines
+	for (i=0; i<ESDW; i=i+N_CHANS)
+		assign mux_data[i+:N_CHANS] = cc_clk ? (sync[0] ? cc_data[2*i+:N_CHANS] : cc_data[2*i+N_CHANS+:N_CHANS]) : 0;
+
+	SB_IO #(
+		.PIN_TYPE(6'b010100),
+		.PULLUP(1'b0),
+		.NEG_TRIGGER(1'b0),
+		.IO_STANDARD("SB_LVCMOS")
+	) iob_data_I[ESDW-1:0] (
+		.PACKAGE_PIN(hub75_data),
+		.CLOCK_ENABLE(1'b1),
+		.OUTPUT_CLK(clk_2x),
+		.D_OUT_0(mux_data)
+	);
+
+	// Clock DDR register
+	always @(posedge clk_2x)
+		clk_sig <= cc_clk ? sync[0] : 1'b1;
+
+	SB_IO #(
+		.PIN_TYPE(6'b010000),
+		.PULLUP(1'b0),
+		.NEG_TRIGGER(1'b0),
+		.IO_STANDARD("SB_LVCMOS")
+	) iob_clk_I (
+		.PACKAGE_PIN(hub75_clk),
+		.CLOCK_ENABLE(1'b1),
+		.OUTPUT_CLK(clk_2x),
+		.D_OUT_0(clk_sig | (PHY_DDR == 2)),
+		.D_OUT_1(clk_sig)
+	);
+
+	// Latch
+	SB_IO #(
+		.PIN_TYPE(6'b010100),
+		.PULLUP(1'b0),
+		.NEG_TRIGGER(1'b0),
+		.IO_STANDARD("SB_LVCMOS")
+	) iob_le_I (
+		.PACKAGE_PIN(hub75_le),
+		.CLOCK_ENABLE(1'b1),
+		.OUTPUT_CLK(clk_2x),
+		.D_OUT_0(cc_le)
+	);
+
+	// Blanking
+	SB_IO #(
+		.PIN_TYPE(6'b010100),
+		.PULLUP(1'b0),
+		.NEG_TRIGGER(1'b0),
+		.IO_STANDARD("SB_LVCMOS")
+	) iob_blank_I (
+		.PACKAGE_PIN(hub75_blank),
+		.CLOCK_ENABLE(1'b1),
+		.OUTPUT_CLK(clk_2x),
+		.D_OUT_0(cc_blank)
+	);
+
+endmodule

+ 56 - 24
cores/hub75/rtl/hub75_top.v

@@ -32,12 +32,14 @@ module hub75_top #(
 	parameter integer N_CHANS  = 3,		// # of data channel
 	parameter integer N_PLANES = 8,		// # bitplanes
 	parameter integer BITDEPTH = 24,	// # bits per color
+	parameter integer PHY_DDR  = 0,		// PHY DDR data output
 	parameter integer PHY_AIR  = 0,		// PHY Address Inc/Reset
 
 	parameter SCAN_MODE = "ZIGZAG",		// 'LINEAR' or 'ZIGZAG'
 
 	// Auto-set
 	parameter integer SDW         = N_BANKS * N_CHANS,
+	parameter integer ESDW        = SDW / (PHY_DDR ? 2 : 1),
 	parameter integer LOG_N_BANKS = $clog2(N_BANKS),
 	parameter integer LOG_N_ROWS  = $clog2(N_ROWS),
 	parameter integer LOG_N_COLS  = $clog2(N_COLS)
@@ -46,7 +48,7 @@ module hub75_top #(
 	output wire hub75_addr_inc,
 	output wire hub75_addr_rst,
 	output wire [LOG_N_ROWS-1:0] hub75_addr,
-	output wire [SDW-1:0] hub75_data,
+	output wire [ESDW-1:0] hub75_data,
 	output wire hub75_clk,
 	output wire hub75_le,
 	output wire hub75_blank,
@@ -78,6 +80,7 @@ module hub75_top #(
 
 	// Clock / Reset
 	input  wire clk,
+	input  wire clk_2x,
 	input  wire rst
 );
 
@@ -260,28 +263,57 @@ module hub75_top #(
 	);
 
 	// Physical layer control
-	hub75_phy #(
-		.N_BANKS(N_BANKS),
-		.N_ROWS(N_ROWS),
-		.N_CHANS(N_CHANS),
-		.PHY_AIR(PHY_AIR)
-	) phy_I (
-		.hub75_addr_inc(hub75_addr_inc),// -> pad
-		.hub75_addr_rst(hub75_addr_rst),// -> pad
-		.hub75_addr(hub75_addr),		// -> pad
-		.hub75_data(hub75_data),		// -> pad
-		.hub75_clk(hub75_clk),			// -> pad
-		.hub75_le(hub75_le),			// -> pad
-		.hub75_blank(hub75_blank),		// -> pad
-		.phy_addr_inc(phy_addr_inc),	// <- hub75_bcm
-		.phy_addr_rst(phy_addr_rst),	// <- hub75_bcm
-		.phy_addr(phy_addr),			// <- hub75_bcm
-		.phy_data(phy_data),			// <- hub75_shift
-		.phy_clk(phy_clk),				// <- hub75_shift
-		.phy_le(phy_le),				// <- hub75_bcm
-		.phy_blank(phy_blank),			// <- hub75_blanking
-		.clk(clk),						// <- top
-		.rst(rst)						// <- top
-	);
+	generate
+		if (PHY_DDR == 0)
+			hub75_phy #(
+				.N_BANKS(N_BANKS),
+				.N_ROWS(N_ROWS),
+				.N_CHANS(N_CHANS),
+				.PHY_AIR(PHY_AIR)
+			) phy_I (
+				.hub75_addr_inc(hub75_addr_inc),// -> pad
+				.hub75_addr_rst(hub75_addr_rst),// -> pad
+				.hub75_addr(hub75_addr),		// -> pad
+				.hub75_data(hub75_data),		// -> pad
+				.hub75_clk(hub75_clk),			// -> pad
+				.hub75_le(hub75_le),			// -> pad
+				.hub75_blank(hub75_blank),		// -> pad
+				.phy_addr_inc(phy_addr_inc),	// <- hub75_bcm
+				.phy_addr_rst(phy_addr_rst),	// <- hub75_bcm
+				.phy_addr(phy_addr),			// <- hub75_bcm
+				.phy_data(phy_data),			// <- hub75_shift
+				.phy_clk(phy_clk),				// <- hub75_shift
+				.phy_le(phy_le),				// <- hub75_bcm
+				.phy_blank(phy_blank),			// <- hub75_blanking
+				.clk(clk),						// <- top
+				.rst(rst)						// <- top
+			);
+		else
+			hub75_phy_ddr #(
+				.N_BANKS(N_BANKS),
+				.N_ROWS(N_ROWS),
+				.N_CHANS(N_CHANS),
+				.PHY_DDR(PHY_DDR),
+				.PHY_AIR(PHY_AIR)
+			) phy_I (
+				.hub75_addr_inc(hub75_addr_inc),// -> pad
+				.hub75_addr_rst(hub75_addr_rst),// -> pad
+				.hub75_addr(hub75_addr),		// -> pad
+				.hub75_data(hub75_data),		// -> pad
+				.hub75_clk(hub75_clk),			// -> pad
+				.hub75_le(hub75_le),			// -> pad
+				.hub75_blank(hub75_blank),		// -> pad
+				.phy_addr_inc(phy_addr_inc),	// <- hub75_bcm
+				.phy_addr_rst(phy_addr_rst),	// <- hub75_bcm
+				.phy_addr(phy_addr),			// <- hub75_bcm
+				.phy_data(phy_data),			// <- hub75_shift
+				.phy_clk(phy_clk),				// <- hub75_shift
+				.phy_le(phy_le),				// <- hub75_bcm
+				.phy_blank(phy_blank),			// <- hub75_blanking
+				.clk(clk),						// <- top
+				.clk_2x(clk_2x),				// <- top
+				.rst(rst)						// <- top
+			);
+	endgenerate
 
 endmodule // hub75_top