From 1ccf7399b8438119df6ccb7a500b015dfab77670 Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Thu, 23 May 2024 21:42:52 +0200 Subject: [PATCH] import hsdaohSDR FPGA design --- common/hsdaoh/hsdaoh_core.v | 78 ++- common/uart_i2c_bridge/i2c_master.v | 567 ++++++++++++++++++++++ common/uart_i2c_bridge/uart_i2c_bridge.v | 233 +++++++++ common/uart_i2c_bridge/uart_rx.v | 146 ++++++ common/uart_i2c_bridge/uart_tx.v | 136 ++++++ hsdaoh_nano20k_sdr.gprj | 29 ++ hsdaoh_nano20k_sdr/hsdaoh_nano20k_sdr.cst | 70 +++ hsdaoh_nano20k_sdr/hsdaoh_nano20k_sdr.sdc | 2 + hsdaoh_nano20k_sdr/top.v | 205 ++++++++ 9 files changed, 1456 insertions(+), 10 deletions(-) create mode 100644 common/uart_i2c_bridge/i2c_master.v create mode 100644 common/uart_i2c_bridge/uart_i2c_bridge.v create mode 100644 common/uart_i2c_bridge/uart_rx.v create mode 100644 common/uart_i2c_bridge/uart_tx.v create mode 100644 hsdaoh_nano20k_sdr.gprj create mode 100644 hsdaoh_nano20k_sdr/hsdaoh_nano20k_sdr.cst create mode 100644 hsdaoh_nano20k_sdr/hsdaoh_nano20k_sdr.sdc create mode 100644 hsdaoh_nano20k_sdr/top.v diff --git a/common/hsdaoh/hsdaoh_core.v b/common/hsdaoh/hsdaoh_core.v index 8bafc62..e1e33f8 100644 --- a/common/hsdaoh/hsdaoh_core.v +++ b/common/hsdaoh/hsdaoh_core.v @@ -14,8 +14,9 @@ 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 [19:0] data_in, + input wire tenbit_enable_in ); localparam [31:0] MAGIC = 32'hda7acab1; @@ -25,6 +26,11 @@ module hsdaoh_core reg [15:0] idle_counter = 16'h0000; reg [15:0] line_word_cnt = 16'h00; reg [3:0] status_nibble = 4'h0; + reg read_en; + reg [2:0] pack_state; + reg [3:0] pack_sequence_start; + reg [15:0] tenbit_data; + reg tenbit_enabled = 1'b1; wire [11:0] cx; wire [10:0] cy; @@ -33,6 +39,9 @@ module hsdaoh_core wire [11:0] screen_width; wire [10:0] screen_height; + // 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 != 3) || !tenbit_enabled); + always @(posedge clk_pixel) begin if ((cx == screen_width-1) && (cy < screen_height)) begin @@ -40,9 +49,43 @@ always @(posedge clk_pixel) begin hdmi_data <= {status_nibble[3:0], line_word_cnt[11:0], 8'h00}; end else if ((cx < screen_width) && (cy < screen_height)) begin - if (fifo_read_en && !fifo_empty) begin - // regular output of FIFO data - hdmi_data <= {data_in[15:0], 8'h00}; + if (read_en && !fifo_empty) begin + + // regular output of FIFO data + hdmi_data <= (tenbit_enabled && (pack_state == 4)) ? {tenbit_data[15:0], 8'h00} : {data_in[19:12], data_in[9:2], 8'h00}; + + if (tenbit_enabled) begin + // for 10 bit IQ samples, we need to pack them + case (pack_state) + 0: + begin + tenbit_data[3:0] <= {data_in[1:0], data_in[11:10]}; + pack_state <= 1; + end + 1: + begin + tenbit_data[7:4] <= {data_in[1:0], data_in[11:10]}; + pack_state <= 2; + end + 2: + begin + tenbit_data[11:8] <= {data_in[1:0], data_in[11:10]}; + pack_state <= 3; + end + 3: + begin + tenbit_data[15:12] <= {data_in[1:0], data_in[11:10]}; + pack_state <= 4; + end + 4: + begin + pack_state <= 0; + end + default: + pack_state <= 0; + endcase + end + // increment line payload counter line_word_cnt <= line_word_cnt + 1'b1; @@ -59,29 +102,42 @@ always @(posedge clk_pixel) begin // Enable reading before beginning of next line if ((cx == frame_width-1) && (cy < screen_height-1) && !fifo_empty) - fifo_read_en = 1'b1; + read_en = 1'b1; // switch read off during blanking if (cy > screen_height) - fifo_read_en = 1'b0; + read_en = 1'b0; // 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) && (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; + + // 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 (tenbit_enabled && (cy == screen_height-1) && read_en) begin + if (pack_state == 4) + read_en = 1'b0; + end // increment the frame counter at the end of the frame if ((cx == frame_width-1) && (cy == frame_height-1)) begin frame_cnt <= frame_cnt + 1'b1; line_word_cnt <= 16'h0000; + // latch the current state of the pack sequence for the beginning of the next frame, + // which we transfer in the metadata so the host knows how to unpack the data + pack_sequence_start <= pack_state; + pack_state <= 0; + tenbit_enabled <= tenbit_enable_in; // start FIFO readout if (!fifo_empty) - fifo_read_en = 1'b1; + read_en = 1'b1; end if (cx == 0) begin @@ -98,6 +154,8 @@ always @(posedge clk_pixel) begin 9 : status_nibble <= frame_cnt[7:4]; 10 : status_nibble <= frame_cnt[11:8]; 11 : status_nibble <= frame_cnt[15:12]; + 12 : status_nibble <= pack_sequence_start; + 13 : status_nibble <= {3'b000, tenbit_enabled}; default : status_nibble <= 4'h0; endcase end diff --git a/common/uart_i2c_bridge/i2c_master.v b/common/uart_i2c_bridge/i2c_master.v new file mode 100644 index 0000000..8570416 --- /dev/null +++ b/common/uart_i2c_bridge/i2c_master.v @@ -0,0 +1,567 @@ +// Tiny but mighty I2C master +// Copyright (c) 2021-2024 Artin Isagholian +// https://github.com/0xArt/Tiny_But_Mighty_I2C_Master_Verilog +// License: GPL V3.0 +// +// converted to Verilog and extended with the 'got_acknowledge' output for hsdaoh + +module i2c_master ( + clock, + reset_n, + enable, + read_write, + mosi_data, + register_address, + device_address, + divider, + miso_data, + busy, + got_acknowledge, + external_serial_data, + external_serial_clock +); + +localparam S_IDLE = 0; +localparam S_START = 1; +localparam S_WRITE_ADDR_W = 2; +localparam S_CHECK_ACK = 3; +localparam S_WRITE_REG_ADDR = 4; +localparam S_RESTART = 5; +localparam S_WRITE_ADDR_R = 6; +localparam S_READ_REG = 7; +localparam S_SEND_NACK = 8; +localparam S_SEND_STOP = 9; +localparam S_WRITE_REG_DATA = 10; +localparam S_WRITE_REG_ADDR_MSB = 11; +localparam S_WRITE_REG_DATA_MSB = 12; +localparam S_READ_REG_MSB = 13; +localparam S_SEND_ACK = 14; + + parameter DATA_WIDTH = 8; + parameter REGISTER_WIDTH = 8; + parameter ADDRESS_WIDTH = 7; + input wire clock; + input wire reset_n; + input wire enable; + input wire read_write; + input wire [DATA_WIDTH - 1:0] mosi_data; + input wire [REGISTER_WIDTH - 1:0] register_address; + input wire [ADDRESS_WIDTH - 1:0] device_address; + input wire [15:0] divider; + output reg [DATA_WIDTH - 1:0] miso_data; + output reg busy; + output reg got_acknowledge; + inout external_serial_data; + inout external_serial_clock; + reg [3:0] state; + reg [3:0] _state; + reg [3:0] post_state; + reg [3:0] _post_state; + reg serial_clock; + reg _serial_clock; + reg [ADDRESS_WIDTH:0] saved_device_address; + reg [ADDRESS_WIDTH:0] _saved_device_address; + reg [REGISTER_WIDTH - 1:0] saved_register_address; + reg [REGISTER_WIDTH - 1:0] _saved_register_address; + reg [DATA_WIDTH - 1:0] saved_mosi_data; + reg [DATA_WIDTH - 1:0] _saved_mosi_data; + reg [1:0] process_counter; + reg [1:0] _process_counter; + reg [7:0] bit_counter; + reg [7:0] _bit_counter; + reg serial_data; + reg _serial_data; + reg post_serial_data; + reg _post_serial_data; + reg last_acknowledge; + reg _last_acknowledge; + reg _saved_read_write; + reg saved_read_write; + reg [15:0] divider_counter; + reg [15:0] _divider_counter; + reg divider_tick; + reg [DATA_WIDTH - 1:0] _miso_data; + reg _busy; + reg _got_acknowledge; + reg serial_data_output_enable; + reg serial_clock_output_enable; + assign external_serial_clock = (serial_clock_output_enable ? serial_clock : 1'bz); + assign external_serial_data = (serial_data_output_enable ? serial_data : 1'bz); + always @(*) begin + _state = state; + _post_state = post_state; + _process_counter = process_counter; + _bit_counter = bit_counter; + _last_acknowledge = last_acknowledge; + _miso_data = miso_data; + _saved_read_write = saved_read_write; + _busy = busy; + _got_acknowledge = got_acknowledge; + _divider_counter = divider_counter; + _saved_register_address = saved_register_address; + _saved_device_address = saved_device_address; + _saved_mosi_data = saved_mosi_data; + _serial_data = serial_data; + _serial_clock = serial_clock; + _post_serial_data = post_serial_data; + if (divider_counter == divider) begin + _divider_counter = 0; + divider_tick = 1; + end + else begin + _divider_counter = divider_counter + 1; + divider_tick = 0; + end + if ((((state != S_IDLE) && (state != S_CHECK_ACK)) && (state != S_READ_REG)) && (state != S_READ_REG_MSB)) + serial_data_output_enable = 1; + else + serial_data_output_enable = 0; + if (((state != S_IDLE) && (process_counter != 1)) && (process_counter != 2)) + serial_clock_output_enable = 1; + else + serial_clock_output_enable = 0; + case (state) + S_IDLE: begin + _process_counter = 0; + _bit_counter = 0; + _last_acknowledge = 0; + _busy = 0; + _saved_read_write = read_write; + _saved_register_address = register_address; + _saved_device_address = {device_address, 1'b0}; + _saved_mosi_data = mosi_data; + _serial_data = 1; + _serial_clock = 1; + if (enable) begin + _got_acknowledge = 0; + _state = S_START; + _post_state = S_WRITE_ADDR_W; + _busy = 1; + end + end + S_START: + if (divider_tick) + case (process_counter) + 0: _process_counter = 1; + 1: begin + _serial_data = 0; + _process_counter = 2; + end + 2: begin + _bit_counter = 8; + _process_counter = 3; + end + 3: begin + _serial_clock = 0; + _process_counter = 0; + _state = post_state; + _serial_data = saved_device_address[ADDRESS_WIDTH]; + end + endcase + S_WRITE_ADDR_W: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) + _process_counter = 2; + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _post_serial_data = saved_register_address[REGISTER_WIDTH - 1]; + if (REGISTER_WIDTH == 16) + _post_state = S_WRITE_REG_ADDR_MSB; + else + _post_state = S_WRITE_REG_ADDR; + _state = S_CHECK_ACK; + _bit_counter = 8; + end + else + _serial_data = saved_device_address[bit_counter - 1]; + _process_counter = 0; + end + endcase + S_CHECK_ACK: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _serial_clock = 0; + if (external_serial_data == 0) + _last_acknowledge = 1; + _process_counter = 3; + end + 3: begin + if (last_acknowledge == 1) begin + _got_acknowledge = 1; + _last_acknowledge = 0; + _serial_data = post_serial_data; + _state = post_state; + end + else begin + _got_acknowledge = 0; + _state = S_SEND_STOP; + end + _process_counter = 0; + end + endcase + S_WRITE_REG_ADDR_MSB: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _post_state = S_WRITE_REG_ADDR; + _post_serial_data = saved_register_address[7]; + _bit_counter = 8; + _serial_data = 0; + _state = S_CHECK_ACK; + end + else + _serial_data = saved_register_address[bit_counter + 7]; + _process_counter = 0; + end + endcase + S_WRITE_REG_ADDR: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + if (read_write == 0) begin + if (DATA_WIDTH == 16) begin + _post_state = S_WRITE_REG_DATA_MSB; + _post_serial_data = saved_mosi_data[15]; + end + else begin + _post_state = S_WRITE_REG_DATA; + _post_serial_data = saved_mosi_data[7]; + end + end + else begin + _post_state = S_RESTART; + _post_serial_data = 1; + end + _bit_counter = 8; + _serial_data = 0; + _state = S_CHECK_ACK; + end + else + _serial_data = saved_register_address[bit_counter - 1]; + _process_counter = 0; + end + endcase + S_WRITE_REG_DATA_MSB: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _state = S_CHECK_ACK; + _post_state = S_WRITE_REG_DATA; + _post_serial_data = saved_mosi_data[7]; + _bit_counter = 8; + _serial_data = 0; + end + else + _serial_data = saved_mosi_data[bit_counter + 7]; + _process_counter = 0; + end + endcase + S_WRITE_REG_DATA: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _state = S_CHECK_ACK; + _post_state = S_SEND_STOP; + _post_serial_data = 0; + _bit_counter = 8; + _serial_data = 0; + end + else + _serial_data = saved_mosi_data[bit_counter - 1]; + _process_counter = 0; + end + endcase + S_RESTART: + if (divider_tick) + case (process_counter) + 0: begin + _process_counter = 1; + end + 1: begin + _process_counter = 2; + _serial_clock = 1; + end + 2: begin + _process_counter = 3; + end + 3: begin + _state = S_START; + _post_state = S_WRITE_ADDR_R; + _saved_device_address[0] = 1; + _process_counter = 0; + end + endcase + S_WRITE_ADDR_R: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + if (DATA_WIDTH == 16) begin + _post_state = S_READ_REG_MSB; + _post_serial_data = 0; + end + else begin + _post_state = S_READ_REG; + _post_serial_data = 0; + end + _state = S_CHECK_ACK; + _bit_counter = 8; + end + else + _serial_data = saved_device_address[bit_counter - 1]; + _process_counter = 0; + end + endcase + S_READ_REG_MSB: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _serial_clock = 0; + _miso_data[bit_counter + 7] = external_serial_data; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _post_state = S_READ_REG; + _state = S_SEND_ACK; + _bit_counter = 8; + _serial_data = 0; + end + _process_counter = 0; + end + endcase + S_READ_REG: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _serial_clock = 0; + _miso_data[bit_counter - 1] = external_serial_data; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _state = S_SEND_NACK; + _serial_data = 0; + end + _process_counter = 0; + end + endcase + S_SEND_NACK: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _serial_data = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _process_counter = 3; + _serial_clock = 0; + end + 3: begin + _state = S_SEND_STOP; + _process_counter = 0; + _serial_data = 0; + end + endcase + S_SEND_ACK: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + _serial_data = 0; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _process_counter = 3; + _serial_clock = 0; + end + 3: begin + _state = post_state; + _process_counter = 0; + end + endcase + S_SEND_STOP: + if (divider_tick) + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + 2: begin + _process_counter = 3; + _serial_data = 1; + end + 3: _state = S_IDLE; + endcase + endcase + end + always @(posedge clock) + if (!reset_n) begin + state <= S_IDLE; + post_state <= S_IDLE; + process_counter <= 0; + bit_counter <= 0; + last_acknowledge <= 0; + miso_data <= 0; + saved_read_write <= 0; + divider_counter <= 0; + saved_device_address <= 0; + saved_register_address <= 0; + saved_mosi_data <= 0; + serial_clock <= 0; + serial_data <= 0; + saved_mosi_data <= 0; + post_serial_data <= 0; + busy <= 0; + got_acknowledge <= 0; + end + else begin + state <= _state; + post_state <= _post_state; + process_counter <= _process_counter; + bit_counter <= _bit_counter; + last_acknowledge <= _last_acknowledge; + miso_data <= _miso_data; + saved_read_write <= _saved_read_write; + divider_counter <= _divider_counter; + saved_device_address <= _saved_device_address; + saved_register_address <= _saved_register_address; + saved_mosi_data <= _saved_mosi_data; + serial_clock <= _serial_clock; + serial_data <= _serial_data; + post_serial_data <= _post_serial_data; + busy <= _busy; + got_acknowledge <= _got_acknowledge; + end +endmodule diff --git a/common/uart_i2c_bridge/uart_i2c_bridge.v b/common/uart_i2c_bridge/uart_i2c_bridge.v new file mode 100644 index 0000000..7009975 --- /dev/null +++ b/common/uart_i2c_bridge/uart_i2c_bridge.v @@ -0,0 +1,233 @@ +// UART to I2C and control bridge for hsdaohSDR +// Copyright (C) 2024 by Steve Markgraf +// License: MIT + +module uart_i2c_bridge + + #( + parameter CLK_FRE = 27 + )( + input wire clk, + input wire rst_n, + input wire uart_rx, + output wire uart_tx, + inout wire i2c_sda, + inout wire i2c_scl, + output reg[31:0] settings = 32'h0003cf76 // default PLL settings: 90 MHz + ); + +//parameter CLK_FRE = 27;//Mhz +parameter UART_FRE = 921600; +reg[7:0] tx_data; +reg[7:0] tx_str; +reg tx_data_valid; +wire tx_data_ready; +reg[7:0] tx_cnt; +wire[7:0] rx_data; +wire rx_data_valid; +wire rx_data_ready; +reg[31:0] wait_cnt; +reg[3:0] state; +reg[3:0] rx_byte_cnt; +reg[23:0] set_tmp; + +localparam IDLE = 0; +localparam READ_SLAVE_ADDR = 1; +localparam READ_VALUE_TO_WRITE = 2; +localparam READ_REG_ADDR = 3; +localparam WAIT_I2C_BUSY = 4; +localparam SEND_ACKNOWLEDGE = 5; +localparam END_SENDING_ACKNOWLEDGE = 6; +localparam SEND_READ_RESULT = 7; +localparam RECEIVE_SETTINGS = 8; + +// commands +localparam CMD_I2C_INIT = "i"; +localparam CMD_I2C_READ = "r"; +localparam CMD_I2C_WRITE = "w"; +localparam CMD_CONFIG = "c"; + +assign rx_data_ready = 1'b1; + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + begin + wait_cnt <= 32'd0; + tx_data <= 8'd0; + state <= IDLE; + tx_cnt <= 8'd0; + tx_data_valid <= 1'b0; + i2c_enable <= 1'b0; + settings <= 32'h00000000; + rx_byte_cnt <= 0; + end + else + + case(state) + IDLE: + begin + tx_data_valid <= 1'b0; + if (rx_data_valid == 1'b1) + begin + // receive command + case (rx_data) + CMD_I2C_READ: + begin + i2c_read_write <= 1'b1; + state <= READ_SLAVE_ADDR; + end + CMD_I2C_WRITE: + begin + i2c_read_write <= 1'b0; + state <= READ_SLAVE_ADDR; + end + CMD_I2C_INIT: + state <= IDLE; + CMD_CONFIG: + state <= RECEIVE_SETTINGS; + endcase + // default: + end + end + + RECEIVE_SETTINGS: + if (rx_data_valid == 1'b1) begin + if (rx_byte_cnt < 3) + rx_byte_cnt <= rx_byte_cnt + 1'b1; + + case (rx_byte_cnt) + 0: + set_tmp[23:16] <= rx_data[7:0]; + 1: + set_tmp[15:8] <= rx_data[7:0]; + 2: + set_tmp[7:0] <= rx_data[7:0]; + 3: + begin + settings[31:0] <= {set_tmp[23:0], rx_data[7:0]}; + rx_byte_cnt <= 0; + state <= IDLE; + end + + default: + rx_byte_cnt <= 0; + endcase + end + READ_SLAVE_ADDR: + if(rx_data_valid == 1'b1) begin + device_address <= rx_data[6:0]; + state <= i2c_read_write ? READ_REG_ADDR : READ_VALUE_TO_WRITE; + end + + READ_VALUE_TO_WRITE: + // only for I2C TX: read value to be written + if(rx_data_valid == 1'b1) begin + mosi_data <= rx_data; + state <= READ_REG_ADDR; + end + READ_REG_ADDR: + if(rx_data_valid == 1'b1) begin + reg_addr <= rx_data; + + if (i2c_busy == 0) + i2c_enable <= 1'b1; + else + state <= IDLE; + + state <= WAIT_I2C_BUSY; + end + + WAIT_I2C_BUSY: + if (i2c_busy == 1) begin + i2c_enable <= 1'b0; + state <= SEND_ACKNOWLEDGE; + + end + + SEND_ACKNOWLEDGE: + begin + if ((i2c_busy == 0) && tx_data_ready) begin + tx_data <= {7'b0000000, i2c_acknowledge}; + tx_data_valid <= 1'b1; + state <= END_SENDING_ACKNOWLEDGE; + end + end + + END_SENDING_ACKNOWLEDGE: + begin + tx_data_valid <= 1'b0; + state <= i2c_read_write ? SEND_READ_RESULT : IDLE; + end + + SEND_READ_RESULT: + begin + if ((i2c_busy == 0) && tx_data_ready) begin + tx_data <= miso_data; + tx_data_valid <= 1'b1; + state <= IDLE; + end + end + + default: + state <= IDLE; + endcase +end + + +reg i2c_enable; +reg i2c_read_write; +reg[7:0] mosi_data; +reg[7:0] reg_addr; +wire[7:0] miso_data; +reg[6:0] device_address; +wire i2c_busy; +wire i2c_acknowledge; + +i2c_master #(.DATA_WIDTH(8),.REGISTER_WIDTH(8),.ADDRESS_WIDTH(7)) + i2c_master_inst( + .clock (clk), + .reset_n (rst_n), + .enable (i2c_enable), + .read_write (i2c_read_write), + .mosi_data (mosi_data), + .register_address (reg_addr), + .device_address (device_address), + .divider (16), // ~400 kHz + + .miso_data (miso_data), + .busy (i2c_busy), + .got_acknowledge (i2c_acknowledge), + + .external_serial_data (i2c_sda), + .external_serial_clock (i2c_scl) + ); + +uart_rx# +( + .CLK_FRE(CLK_FRE), + .BAUD_RATE(UART_FRE) +) uart_rx_inst +( + .clk (clk ), + .rst_n (rst_n ), + .rx_data (rx_data ), + .rx_data_valid (rx_data_valid ), + .rx_data_ready (rx_data_ready ), + .rx_pin (uart_rx ) +); + +uart_tx# +( + .CLK_FRE(CLK_FRE), + .BAUD_RATE(UART_FRE) +) uart_tx_inst +( + .clk (clk ), + .rst_n (rst_n ), + .tx_data (tx_data ), + .tx_data_valid (tx_data_valid ), + .tx_data_ready (tx_data_ready ), + .tx_pin (uart_tx ) +); +endmodule diff --git a/common/uart_i2c_bridge/uart_rx.v b/common/uart_i2c_bridge/uart_rx.v new file mode 100644 index 0000000..ead97cb --- /dev/null +++ b/common/uart_i2c_bridge/uart_rx.v @@ -0,0 +1,146 @@ +// UART RX implementation +// source: https://github.com/sipeed/TangNano-20K-example/blob/main/uart/src/uart_rx.v + +module uart_rx +#( + parameter CLK_FRE = 50, //clock frequency(Mhz) + parameter BAUD_RATE = 115200 //serial baud rate +) +( + input clk, //clock input + input rst_n, //asynchronous reset input, low active + output reg[7:0] rx_data, //received serial data + output reg rx_data_valid, //received serial data is valid + input rx_data_ready, //data receiver module ready + input rx_pin //serial data input +); +//calculates the clock cycle for baud rate +localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE; +//state machine code +localparam S_IDLE = 1; +localparam S_START = 2; //start bit +localparam S_REC_BYTE = 3; //data bits +localparam S_STOP = 4; //stop bit +localparam S_DATA = 5; + +reg[2:0] state; +reg[2:0] next_state; +reg rx_d0; //delay 1 clock for rx_pin +reg rx_d1; //delay 1 clock for rx_d0 +wire rx_negedge; //negedge of rx_pin +reg[7:0] rx_bits; //temporary storage of received data +reg[15:0] cycle_cnt; //baud counter +reg[2:0] bit_cnt; //bit counter + +assign rx_negedge = rx_d1 && ~rx_d0; + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + begin + rx_d0 <= 1'b0; + rx_d1 <= 1'b0; + end + else + begin + rx_d0 <= rx_pin; + rx_d1 <= rx_d0; + end +end + + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + state <= S_IDLE; + else + state <= next_state; +end + +always@(*) +begin + case(state) + S_IDLE: + if(rx_negedge) + next_state <= S_START; + else + next_state <= S_IDLE; + S_START: + if(cycle_cnt == CYCLE - 1)//one data cycle + next_state <= S_REC_BYTE; + else + next_state <= S_START; + S_REC_BYTE: + if(cycle_cnt == CYCLE - 1 && bit_cnt == 3'd7) //receive 8bit data + next_state <= S_STOP; + else + next_state <= S_REC_BYTE; + S_STOP: + if(cycle_cnt == CYCLE/2 - 1)//half bit cycle,to avoid missing the next byte receiver + next_state <= S_DATA; + else + next_state <= S_STOP; + S_DATA: + if(rx_data_ready) //data receive complete + next_state <= S_IDLE; + else + next_state <= S_DATA; + default: + next_state <= S_IDLE; + endcase +end + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + rx_data_valid <= 1'b0; + else if(state == S_STOP && next_state != state) + rx_data_valid <= 1'b1; + else if(state == S_DATA && rx_data_ready) + rx_data_valid <= 1'b0; +end + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + rx_data <= 8'd0; + else if(state == S_STOP && next_state != state) + rx_data <= rx_bits;//latch received data +end + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + begin + bit_cnt <= 3'd0; + end + else if(state == S_REC_BYTE) + if(cycle_cnt == CYCLE - 1) + bit_cnt <= bit_cnt + 3'd1; + else + bit_cnt <= bit_cnt; + else + bit_cnt <= 3'd0; +end + + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + cycle_cnt <= 16'd0; + else if((state == S_REC_BYTE && cycle_cnt == CYCLE - 1) || next_state != state) + cycle_cnt <= 16'd0; + else + cycle_cnt <= cycle_cnt + 16'd1; +end +//receive serial data bit data +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + rx_bits <= 8'd0; + else if(state == S_REC_BYTE && cycle_cnt == CYCLE/2 - 1) + rx_bits[bit_cnt] <= rx_pin; + else + rx_bits <= rx_bits; +end +endmodule diff --git a/common/uart_i2c_bridge/uart_tx.v b/common/uart_i2c_bridge/uart_tx.v new file mode 100644 index 0000000..2ac0775 --- /dev/null +++ b/common/uart_i2c_bridge/uart_tx.v @@ -0,0 +1,136 @@ +// UART RX implementation +// source: https://github.com/sipeed/TangNano-20K-example/blob/main/uart/src/uart_tx.v + +module uart_tx +#( + parameter CLK_FRE = 50, //clock frequency(Mhz) + parameter BAUD_RATE = 115200 //serial baud rate +) +( + input clk, //clock input + input rst_n, //asynchronous reset input, low active + input[7:0] tx_data, //data to send + input tx_data_valid, //data to be sent is valid + output reg tx_data_ready, //send ready + output tx_pin //serial data output +); +//calculates the clock cycle for baud rate +localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE; +//state machine code +localparam S_IDLE = 1; +localparam S_START = 2;//start bit +localparam S_SEND_BYTE = 3;//data bits +localparam S_STOP = 4;//stop bit +reg[2:0] state; +reg[2:0] next_state; +reg[15:0] cycle_cnt; //baud counter +reg[2:0] bit_cnt;//bit counter +reg[7:0] tx_data_latch; //latch data to send +reg tx_reg; //serial data output +assign tx_pin = tx_reg; +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + state <= S_IDLE; + else + state <= next_state; +end + +always@(*) +begin + case(state) + S_IDLE: + if(tx_data_valid == 1'b1) + next_state <= S_START; + else + next_state <= S_IDLE; + S_START: + if(cycle_cnt == CYCLE - 1) + next_state <= S_SEND_BYTE; + else + next_state <= S_START; + S_SEND_BYTE: + if(cycle_cnt == CYCLE - 1 && bit_cnt == 3'd7) + next_state <= S_STOP; + else + next_state <= S_SEND_BYTE; + S_STOP: + if(cycle_cnt == CYCLE - 1) + next_state <= S_IDLE; + else + next_state <= S_STOP; + default: + next_state <= S_IDLE; + endcase +end +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + begin + tx_data_ready <= 1'b0; + end + else if(state == S_IDLE) + if(tx_data_valid == 1'b1) + tx_data_ready <= 1'b0; + else + tx_data_ready <= 1'b1; + else if(state == S_STOP && cycle_cnt == CYCLE - 1) + tx_data_ready <= 1'b1; +end + + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + begin + tx_data_latch <= 8'd0; + end + else if(state == S_IDLE && tx_data_valid == 1'b1) + tx_data_latch <= tx_data; + +end + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + begin + bit_cnt <= 3'd0; + end + else if(state == S_SEND_BYTE) + if(cycle_cnt == CYCLE - 1) + bit_cnt <= bit_cnt + 3'd1; + else + bit_cnt <= bit_cnt; + else + bit_cnt <= 3'd0; +end + + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + cycle_cnt <= 16'd0; + else if((state == S_SEND_BYTE && cycle_cnt == CYCLE - 1) || next_state != state) + cycle_cnt <= 16'd0; + else + cycle_cnt <= cycle_cnt + 16'd1; +end + +always@(posedge clk or negedge rst_n) +begin + if(rst_n == 1'b0) + tx_reg <= 1'b1; + else + case(state) + S_IDLE,S_STOP: + tx_reg <= 1'b1; + S_START: + tx_reg <= 1'b0; + S_SEND_BYTE: + tx_reg <= tx_data_latch[bit_cnt]; + default: + tx_reg <= 1'b1; + endcase +end + +endmodule diff --git a/hsdaoh_nano20k_sdr.gprj b/hsdaoh_nano20k_sdr.gprj new file mode 100644 index 0000000..c7f953e --- /dev/null +++ b/hsdaoh_nano20k_sdr.gprj @@ -0,0 +1,29 @@ + + + + + 5 + gw2ar18c-000 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hsdaoh_nano20k_sdr/hsdaoh_nano20k_sdr.cst b/hsdaoh_nano20k_sdr/hsdaoh_nano20k_sdr.cst new file mode 100644 index 0000000..dcd8c91 --- /dev/null +++ b/hsdaoh_nano20k_sdr/hsdaoh_nano20k_sdr.cst @@ -0,0 +1,70 @@ +IO_LOC "sys_clk" 4; +IO_PORT "sys_clk" PULL_MODE=UP; +IO_LOC "uart_tx" 69; +IO_PORT "uart_tx" PULL_MODE=UP IO_TYPE=LVCMOS33; +IO_LOC "uart_rx" 70; +IO_PORT "uart_rx" PULL_MODE=UP IO_TYPE=LVCMOS33; +IO_LOC "i2c_scl" 15; +IO_PORT "i2c_scl" PULL_MODE=UP IO_TYPE=LVCMOS33; +IO_LOC "i2c_sda" 85; +IO_PORT "i2c_sda" PULL_MODE=UP IO_TYPE=LVCMOS33; +IO_LOC "lcd_bl" 49; +IO_PORT "lcd_bl" IO_TYPE=LVCMOS33; +IO_LOC "pa_en" 51; +IO_PORT "pa_en" IO_TYPE=LVCMOS33; +IO_LOC "bl616_boot" 75; +IO_PORT "bl616_boot" IO_TYPE=LVCMOS33 PULL_MODE=UP; + +IO_LOC "tmds_d_p[0]" 35,36; +IO_PORT "tmds_d_p[0]" PULL_MODE=NONE DRIVE=8; +IO_LOC "tmds_d_p[1]" 37,38; +IO_PORT "tmds_d_p[1]" PULL_MODE=NONE DRIVE=8; +IO_LOC "tmds_d_p[2]" 39,40; +IO_PORT "tmds_d_p[2]" PULL_MODE=NONE DRIVE=8; +IO_LOC "tmds_clk_p" 33,34; +IO_PORT "tmds_clk_p" PULL_MODE=NONE DRIVE=8; + +IO_LOC "adc0_data[0]" 73; +IO_PORT "adc0_data[0]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc0_data[1]" 74; +IO_PORT "adc0_data[1]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc0_data[2]" 54; +IO_PORT "adc0_data[2]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc0_data[3]" 27; +IO_PORT "adc0_data[3]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc0_data[4]" 28; +IO_PORT "adc0_data[4]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc0_data[5]" 25; +IO_PORT "adc0_data[5]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc0_data[6]" 26; +IO_PORT "adc0_data[6]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc0_data[7]" 29; +IO_PORT "adc0_data[7]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc0_data[8]" 30; +IO_PORT "adc0_data[8]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc0_data[9]" 31; +IO_PORT "adc0_data[9]" IO_TYPE=LVCMOS33 PULL_MODE=UP; + +IO_LOC "adc1_data[0]" 76; +IO_PORT "adc1_data[0]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc1_data[1]" 80; +IO_PORT "adc1_data[1]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc1_data[2]" 42; +IO_PORT "adc1_data[2]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc1_data[3]" 41; +IO_PORT "adc1_data[3]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc1_data[4]" 56; +IO_PORT "adc1_data[4]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc1_data[5]" 48; +IO_PORT "adc1_data[5]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc1_data[6]" 55; +IO_PORT "adc1_data[6]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc1_data[7]" 86; +IO_PORT "adc1_data[7]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc1_data[8]" 72; +IO_PORT "adc1_data[8]" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "adc1_data[9]" 71; +IO_PORT "adc1_data[9]" IO_TYPE=LVCMOS33 PULL_MODE=UP; + +IO_LOC "adc_clkout" 77; +IO_PORT "adc_clkout" IO_TYPE=LVCMOS33 PULL_MODE=UP; diff --git a/hsdaoh_nano20k_sdr/hsdaoh_nano20k_sdr.sdc b/hsdaoh_nano20k_sdr/hsdaoh_nano20k_sdr.sdc new file mode 100644 index 0000000..a3e5c12 --- /dev/null +++ b/hsdaoh_nano20k_sdr/hsdaoh_nano20k_sdr.sdc @@ -0,0 +1,2 @@ +create_clock -name sys_clk -period 37.04 [get_ports {sys_clk}] -add +create_clock -name adc_clkout -period 11.11 [get_ports {adc_clkout}] -add \ No newline at end of file diff --git a/hsdaoh_nano20k_sdr/top.v b/hsdaoh_nano20k_sdr/top.v new file mode 100644 index 0000000..8b22387 --- /dev/null +++ b/hsdaoh_nano20k_sdr/top.v @@ -0,0 +1,205 @@ +// hsdaoh - High Speed Data Acquisition over HDMI +// hsdaohSDR top design for Tang Nano 20K +// Copyright (C) 2024 by Steve Markgraf +// License: MIT + +module top ( + sys_clk, + tmds_clk_n, + tmds_clk_p, + tmds_d_n, + tmds_d_p, + adc0_data, + adc1_data, + adc_clkout, + uart_rx, + uart_tx, + i2c_scl, + i2c_sda, + pa_en, + lcd_bl, + bl616_boot +); + input sys_clk; + 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 [9:0] adc0_data; + input wire [9:0] adc1_data; + output wire adc_clkout; + + input wire uart_rx; + output wire uart_tx; + + inout wire i2c_scl; + inout wire i2c_sda; + + output wire pa_en; + output wire lcd_bl; + output wire bl616_boot; + + wire [2:0] tmds; + wire clk_pixel; + wire clk_pixel_x5; + wire hdmi_pll_lock; + + wire clk_data; + wire data_pll_lock; + + assign pa_en = 1'b0; + assign lcd_bl = 1'b0; + assign bl616_boot = 1'b1; + + assign adc_clkout = clk_data; + + // 477 MHz, maximum that works with the nano 20K + // 477/5 = 95.4 MHz + localparam HDMI_PLL_IDIV = 2; + localparam HDMI_PLL_FBDIV = 52; + 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 ("GW2AR-18C") + ) 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) + ); + + // 91.8 MHz clock for data (ignored, using dynamic settings) + localparam DATA_PLL_IDIV = 6; + localparam DATA_PLL_FBDIV = 18; + localparam DATA_PLL_ODIV = 16; + + wire data_pll_reset; + wire tenbit_enable; + + assign data_pll_reset = settings[18]; + assign tenbit_enable = settings[19]; + + // PLL for data clock + rPLL #( + .FCLKIN (27), + .IDIV_SEL (DATA_PLL_IDIV), + .FBDIV_SEL (DATA_PLL_FBDIV), + .ODIV_SEL (DATA_PLL_ODIV), + .DYN_IDIV_SEL("true"), + .DYN_FBDIV_SEL("true"), + .DYN_ODIV_SEL("true"), + .DEVICE ("GW2AR-18C") + ) data_pll ( + .CLKIN(sys_clk), + .CLKFB(1'b0), + .RESET(data_pll_reset), + .RESET_P(1'b0), + .FBDSEL(settings[5:0]), + .IDSEL(settings[11:6]), + .ODSEL(settings[17:12]), + .DUTYDA(4'b0), + .PSDA(4'b0), + .FDLY(4'b0), + .CLKOUT(clk_data), + .LOCK(data_pll_lock), + .CLKOUTP(), + .CLKOUTD(), + .CLKOUTD3() + ); + + reg [19:0] counter = 16'h0000; + + reg [19:0] fifo_in; + + wire write_enable; + + wire [19:0] fifo_out; + wire fifo_empty; + wire fifo_aempty; + + wire fifo_full; + wire fifo_afull; + + wire fifo_read_en; + async_fifo #( + .DSIZE(20), + .ASIZE($clog2(16384)), + .FALLTHROUGH("FALSE") + ) fifo ( + .wclk(clk_data), + .wrst_n(hdmi_pll_lock), + .winc(write_enable), + .wdata(fifo_in), + .wfull(fifo_full), + .awfull(fifo_afull), + .rclk(clk_pixel), + .rrst_n(hdmi_pll_lock), + .rinc(fifo_read_en), + .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_read_en), + .data_in(fifo_out), + .tenbit_enable_in(tenbit_enable) + ); + + wire [31:0] settings; + + uart_i2c_bridge #( + ) uart_i2c_bridge ( + .clk(sys_clk), + .rst_n(1'b1),//pll_lock), + .uart_rx(uart_rx), + .uart_tx(uart_tx), + .i2c_sda(i2c_sda), + .i2c_scl(i2c_scl), + .settings(settings) + ); + + assign write_enable = 1'b1; + + always @(posedge clk_data) begin + fifo_in <= {adc0_data[9:0], adc1_data[9:0]}; + counter <= counter + 1'b1; + end + +endmodule