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 @@ + + + + + 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