diff --git a/common/div3.v b/common/div3.v
new file mode 100644
index 0000000..5828156
--- /dev/null
+++ b/common/div3.v
@@ -0,0 +1,29 @@
+module clk_div3(clk,reset, clk_out);
+
+input clk;
+input reset;
+output clk_out;
+
+reg [1:0] pos_count, neg_count;
+wire [1:0] r_nxt;
+
+always @(posedge clk) begin
+ if (reset)
+ pos_count <= 0;
+ else if (pos_count == 2)
+ pos_count <= 0;
+ else
+ pos_count <= pos_count + 1;
+end
+
+always @(negedge clk) begin
+ if (reset)
+ neg_count <=0;
+ else if (neg_count == 2)
+ neg_count <= 0;
+ else
+ neg_count<= neg_count +1;
+end
+
+assign clk_out = ~((pos_count == 2) | (neg_count == 2));
+endmodule
diff --git a/common/hsdaoh/hsdaoh_core.v b/common/hsdaoh/hsdaoh_core.v
index f8aaa9b..31c8d22 100644
--- a/common/hsdaoh/hsdaoh_core.v
+++ b/common/hsdaoh/hsdaoh_core.v
@@ -14,8 +14,8 @@ module hsdaoh_core
input wire clk_pixel,
input wire fifo_empty,
input wire fifo_aempty,
- output reg fifo_read_en,
- input wire [15:0] data_in
+ output wire fifo_read_en,
+ input wire [23:0] data_in
);
parameter USE_CRC = 1;
@@ -48,6 +48,13 @@ module hsdaoh_core
wire [11:0] screen_width;
wire [10:0] screen_height;
+ reg read_en;
+ reg [15:0] tenbit_data;
+ reg [2:0] pack_state;
+
+ // For 10 bit mode, disable FIFO every four 20 bit words so we can pack them to 16 bit
+ assign fifo_read_en = read_en && (pack_state != 1);
+
always @(posedge clk_pixel) begin
if (cy < screen_height) begin
@@ -68,9 +75,29 @@ always @(posedge clk_pixel) begin
hdmi_data <= {last_line_crc, 8'h00};
end else if (cx < screen_width) begin
- if (fifo_read_en && !fifo_empty) begin
+ if (read_en && !fifo_empty) begin
// regular output of FIFO data
- hdmi_data <= {data_in[15:0], 8'h00};
+ hdmi_data <= (pack_state == 2) ? {tenbit_data[15:0], 8'h00} : {data_in[11:4], data_in[23:16], 8'h00};
+
+ // packing of 24 bit to 16 bit
+ case (pack_state)
+ 0:
+ begin
+ tenbit_data[7:0] <= {data_in[15:12], data_in[3:0]};
+ pack_state <= 1;
+ end
+ 1:
+ begin
+ tenbit_data[15:8] <= {data_in[15:12], data_in[3:0]};
+ pack_state <= 2;
+ end
+ 2:
+ begin
+ pack_state <= 0;
+ end
+ default:
+ pack_state <= 0;
+ endcase
// increment line payload counter
line_word_cnt <= line_word_cnt + 1'b1;
@@ -79,7 +106,7 @@ always @(posedge clk_pixel) begin
hdmi_data <= {idle_counter[15:8], idle_counter[7:0], 8'h00};
// increment idle counter
- idle_counter <= idle_counter + 1'b1;
+ // idle_counter <= idle_counter + 1'b1;
end
end else
line_word_cnt <= 16'h0000;
@@ -87,22 +114,22 @@ always @(posedge clk_pixel) begin
// Enable reading before beginning of next line
if ((cx == frame_width-1) && (cy != screen_height-1)) begin
if (!fifo_empty)
- fifo_read_en = 1'b1;
+ read_en = 1'b1;
end
// switch read off at end of line before sending the word counter
// -2 because the last word is reserved (line_word_cnt and metadata)
if (cx == screen_width-2-USE_CRC)
- fifo_read_en = 1'b0;
+ read_en = 1'b0;
end
// switch read off during blanking
if (cy > screen_height)
- fifo_read_en = 1'b0;
+ read_en = 1'b0;
// switch read off when FIFO has only one word remaining
if (fifo_aempty)
- fifo_read_en = 1'b0;
+ read_en = 1'b0;
// increment the frame counter at the end of the frame
if ((cx == frame_width-1) && (cy == frame_height-1)) begin
@@ -111,7 +138,15 @@ always @(posedge clk_pixel) begin
// start FIFO readout
if (!fifo_empty)
- fifo_read_en = 1'b1;
+ read_en = 1'b1;
+ end
+
+ // in order to align the packed data, stop the FIFO readout if we cannot complete a packing cycle
+ // at the end of the frame. This ensures that each frame starts with pack_state 0
+ // FIXME: only done at beginning of last line, should be removed
+ if ((cy == screen_height-1) && read_en) begin
+ if (pack_state == 2)
+ read_en = 1'b0;
end
if (cx == 0) begin
@@ -129,6 +164,9 @@ always @(posedge clk_pixel) begin
10 : status_nibble <= frame_cnt[11:8];
11 : status_nibble <= frame_cnt[15:12];
14 : status_nibble <= { 3'b000, USE_CRC };
+ 92 : status_nibble <= 4'b0010; // set format ID = FPGA_12BIT_DUAL
+ 93 : status_nibble <= 4'b0001;
+ 94 : status_nibble <= 4'b0001;
default : status_nibble <= 4'h0;
endcase
end
diff --git a/hsdaoh_nano9k_dualadc.gprj b/hsdaoh_nano9k_dualadc.gprj
new file mode 100644
index 0000000..0e0598e
--- /dev/null
+++ b/hsdaoh_nano9k_dualadc.gprj
@@ -0,0 +1,27 @@
+
+
+
+ FPGA
+ 5
+ gw1nr9c-004
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hsdaoh_nano9k_dualadc/hsdaoh_nano9k_dualadc.cst b/hsdaoh_nano9k_dualadc/hsdaoh_nano9k_dualadc.cst
new file mode 100644
index 0000000..da09205
--- /dev/null
+++ b/hsdaoh_nano9k_dualadc/hsdaoh_nano9k_dualadc.cst
@@ -0,0 +1,65 @@
+IO_LOC "sys_resetn" 4;
+IO_PORT "sys_resetn" PULL_MODE=UP;
+IO_LOC "sys_clk" 52;
+IO_PORT "sys_clk" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "tmds_d_p[0]" 71,70;
+IO_PORT "tmds_d_p[0]" PULL_MODE=NONE DRIVE=8;
+IO_LOC "tmds_d_p[1]" 73,72;
+IO_PORT "tmds_d_p[1]" PULL_MODE=NONE DRIVE=8;
+IO_LOC "tmds_d_p[2]" 75,74;
+IO_PORT "tmds_d_p[2]" PULL_MODE=NONE DRIVE=8;
+IO_LOC "tmds_clk_p" 69,68;
+IO_PORT "tmds_clk_p" PULL_MODE=NONE DRIVE=8;
+
+IO_LOC "adc0_data[0]" 41;
+IO_PORT "adc0_data[0]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[1]" 35;
+IO_PORT "adc0_data[1]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[2]" 40;
+IO_PORT "adc0_data[2]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[3]" 34;
+IO_PORT "adc0_data[3]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[4]" 33;
+IO_PORT "adc0_data[4]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[5]" 30;
+IO_PORT "adc0_data[5]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[6]" 29;
+IO_PORT "adc0_data[6]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[7]" 28;
+IO_PORT "adc0_data[7]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[8]" 27;
+IO_PORT "adc0_data[8]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[9]" 26;
+IO_PORT "adc0_data[9]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[10]" 25;
+IO_PORT "adc0_data[10]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc0_data[11]" 38;
+IO_PORT "adc0_data[11]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+
+IO_LOC "adc1_data[0]" 32;
+IO_PORT "adc1_data[0]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[1]" 31;
+IO_PORT "adc1_data[1]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[2]" 49;
+IO_PORT "adc1_data[2]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[3]" 48;
+IO_PORT "adc1_data[3]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[4]" 76;
+IO_PORT "adc1_data[4]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[5]" 57;
+IO_PORT "adc1_data[5]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[6]" 56;
+IO_PORT "adc1_data[6]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[7]" 55;
+IO_PORT "adc1_data[7]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[8]" 54;
+IO_PORT "adc1_data[8]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[9]" 53;
+IO_PORT "adc1_data[9]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[10]" 51;
+IO_PORT "adc1_data[10]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+IO_LOC "adc1_data[11]" 42;
+IO_PORT "adc1_data[11]" IO_TYPE=LVCMOS33 PULL_MODE=UP;
+
+IO_LOC "adc_clkout" 36;
+IO_PORT "adc_clkout" IO_TYPE=LVCMOS33 PULL_MODE=UP;
\ No newline at end of file
diff --git a/hsdaoh_nano9k_dualadc/hsdaoh_nano9k_dualadc.sdc b/hsdaoh_nano9k_dualadc/hsdaoh_nano9k_dualadc.sdc
new file mode 100644
index 0000000..b4e6de1
--- /dev/null
+++ b/hsdaoh_nano9k_dualadc/hsdaoh_nano9k_dualadc.sdc
@@ -0,0 +1 @@
+create_clock -name sys_clk -period 37.04 [get_ports {sys_clk}] -add
diff --git a/hsdaoh_nano9k_dualadc/top.v b/hsdaoh_nano9k_dualadc/top.v
new file mode 100644
index 0000000..37b3d40
--- /dev/null
+++ b/hsdaoh_nano9k_dualadc/top.v
@@ -0,0 +1,166 @@
+// hsdaoh - High Speed Data Acquisition over HDMI
+// Dual 12 bit ADC top design for Tang Nano 9K
+// Copyright (C) 2024-25 by Steve Markgraf
+// License: MIT
+
+module top (
+ sys_clk,
+ sys_resetn,
+ tmds_clk_n,
+ tmds_clk_p,
+ tmds_d_n,
+ tmds_d_p,
+ adc0_data,
+ adc1_data,
+ adc_clkout
+);
+ input sys_clk;
+ input sys_resetn;
+ output wire tmds_clk_n;
+ output wire tmds_clk_p;
+ output wire [2:0] tmds_d_n;
+ output wire [2:0] tmds_d_p;
+
+ input wire [11:0] adc0_data;
+ input wire [11:0] adc1_data;
+ output wire adc_clkout;
+
+ wire [2:0] tmds;
+ wire clk_pixel;
+ wire clk_pixel_x5;
+ wire hdmi_pll_lock;
+
+ wire data_pll_out;
+ wire clk_data;
+ wire data_pll_lock;
+
+ assign adc_clkout = clk_data;
+
+ localparam HDMI_PLL_IDIV = 3;
+ localparam HDMI_PLL_FBDIV = 46;
+ localparam HDMI_PLL_ODIV = 2;
+
+ // PLL for HDMI clock
+ rPLL #(
+ .FCLKIN (27),
+ .IDIV_SEL (HDMI_PLL_IDIV),
+ .FBDIV_SEL (HDMI_PLL_FBDIV),
+ .ODIV_SEL (HDMI_PLL_ODIV),
+ .DEVICE ("GW1NR-9C")
+ ) hdmi_pll (
+ .CLKIN(sys_clk),
+ .CLKFB(1'b0),
+ .RESET(1'b0),
+ .RESET_P(1'b0),
+ .FBDSEL(6'b0),
+ .IDSEL(6'b0),
+ .ODSEL(6'b0),
+ .DUTYDA(4'b0),
+ .PSDA(4'b0),
+ .FDLY(4'b0),
+ .CLKOUT(clk_pixel_x5),
+ .LOCK(hdmi_pll_lock),
+ .CLKOUTP(),
+ .CLKOUTD(),
+ .CLKOUTD3()
+ );
+
+ CLKDIV #(
+ .DIV_MODE(5),
+ .GSREN("false")
+ ) div_5 (
+ .CLKOUT(clk_pixel),
+ .HCLKIN(clk_pixel_x5),
+ .RESETN(hdmi_pll_lock),
+ .CALIB(1'b0)
+ );
+
+ // 120 MHz
+ localparam DATA_PLL_IDIV = 8;
+ localparam DATA_PLL_FBDIV = 39;
+ localparam DATA_PLL_ODIV = 8;
+
+ // 120 MHz / 3 = 40 MHz
+ clk_div3 clkdiv (.clk(data_pll_out),
+ .reset (!hdmi_pll_lock),
+ .clk_out(clk_data));
+
+ // PLL for data clock
+ rPLL #(
+ .FCLKIN (27),
+ .IDIV_SEL (DATA_PLL_IDIV),
+ .FBDIV_SEL (DATA_PLL_FBDIV),
+ .ODIV_SEL (DATA_PLL_ODIV),
+ .DEVICE ("GW1NR-9C")
+ ) data_pll (
+ .CLKIN (sys_clk),
+ .CLKFB (1'b0),
+ .RESET (rst),
+ .RESET_P (1'b0),
+ .FBDSEL (6'b0),
+ .IDSEL (6'b0),
+ .ODSEL (6'b0),
+ .DUTYDA (4'b0),
+ .PSDA (4'b0),
+ .FDLY (4'b0),
+ .CLKOUT (data_pll_out),
+ .LOCK (data_pll_lock),
+ .CLKOUTP (),
+ .CLKOUTD (),
+ .CLKOUTD3 ()
+ );
+
+ reg [23:0] fifo_in;
+
+ wire write_enable;
+
+ wire [23:0] fifo_out;
+ wire fifo_empty;
+ wire fifo_aempty;
+ wire Full_o;
+
+ wire FifoHalfFull;
+ wire FifoFull;
+
+ wire fifo_rd_en_i;
+
+ async_fifo #(
+ .DSIZE(24),
+ .ASIZE($clog2(8192)), // 3 + (1982 * 4) = 7931 => at least 8K entries to buffer 4 lines during VSYNC
+ .FALLTHROUGH("FALSE")
+ ) fifo (
+ .wclk(clk_data),
+ .wrst_n(hdmi_pll_lock),
+ .winc(write_enable),
+ .wdata(fifo_in),
+ .wfull(FifoFull),
+ .awfull(FifoHalfFull), //fixme
+ .rclk(clk_pixel),
+ .rrst_n(hdmi_pll_lock),
+ .rinc(fifo_rd_en_i),
+ .rdata(fifo_out),
+ .rempty(fifo_empty),
+ .arempty(fifo_aempty)
+ );
+
+ hsdaoh_core hsdaoh (
+ .rstn(hdmi_pll_lock),
+ .tmds_clk_n(tmds_clk_n),
+ .tmds_clk_p(tmds_clk_p),
+ .tmds_d_n(tmds_d_n),
+ .tmds_d_p(tmds_d_p),
+ .clk_pixel_x5(clk_pixel_x5),
+ .clk_pixel(clk_pixel),
+ .fifo_empty(fifo_empty),
+ .fifo_aempty(fifo_aempty),
+ .fifo_read_en(fifo_rd_en_i),
+ .data_in(fifo_out)
+ );
+
+ assign write_enable = 1'b1;
+
+ always @(posedge clk_data) begin
+ fifo_in <= {adc0_data[11:0], adc1_data[11:0]};
+ end
+
+endmodule