spi_engine_execution: Add echoed SCLK support

There are boards (e.g. AD4630-24) which take the SCLK and echo back to
the FPGA through a level shifter - doing this removes the effect of
round-trip timing delays from the level shifter. This is commonly done
whenever isolators are used since they are very slow.

By setting the ECHO_SCLK parameter to 1, the IP will use the incoming
echoed SCLK clock to latch the SDI line(s). The sdi_data_valid is still
synchronous to the SPI clock, and it's generated after the last valid
SDI latch.

The designer's responsibility is to time the SDI shift registers in order to
respect the design requirements.
main
Istvan Csomortani 2020-07-22 13:45:27 +01:00 committed by István Csomortáni
parent 6f4053f3b0
commit ab10bd136e
3 changed files with 196 additions and 26 deletions

View File

@ -1,4 +1,4 @@
proc spi_engine_create {{name "hier_spi_engine"} {data_width 32} {async_spi_clk 1} {num_cs 1} {num_sdi 1} {sdi_delay 2}} { proc spi_engine_create {{name "spi_engine"} {data_width 32} {async_spi_clk 1} {num_cs 1} {num_sdi 1} {sdi_delay 0} {echo_sclk 0}} {
create_bd_cell -type hier $name create_bd_cell -type hier $name
current_bd_instance /$name current_bd_instance /$name
@ -6,6 +6,9 @@ proc spi_engine_create {{name "hier_spi_engine"} {data_width 32} {async_spi_clk
if {$async_spi_clk == 1} { if {$async_spi_clk == 1} {
create_bd_pin -dir I -type clk spi_clk create_bd_pin -dir I -type clk spi_clk
} }
if {$echo_sclk == 1} {
create_bd_pin -dir I -type clk echo_sclk
}
create_bd_pin -dir I -type clk clk create_bd_pin -dir I -type clk clk
create_bd_pin -dir I -type rst resetn create_bd_pin -dir I -type rst resetn
create_bd_pin -dir I trigger create_bd_pin -dir I trigger
@ -19,6 +22,7 @@ proc spi_engine_create {{name "hier_spi_engine"} {data_width 32} {async_spi_clk
ad_ip_parameter execution CONFIG.NUM_OF_SDI $num_sdi ad_ip_parameter execution CONFIG.NUM_OF_SDI $num_sdi
ad_ip_parameter execution CONFIG.SDO_DEFAULT 1 ad_ip_parameter execution CONFIG.SDO_DEFAULT 1
ad_ip_parameter execution CONFIG.SDI_DELAY $sdi_delay ad_ip_parameter execution CONFIG.SDI_DELAY $sdi_delay
ad_ip_parameter execution CONFIG.ECHO_SCLK $echo_sclk
ad_ip_instance axi_spi_engine axi_regmap ad_ip_instance axi_spi_engine axi_regmap
ad_ip_parameter axi_regmap CONFIG.DATA_WIDTH $data_width ad_ip_parameter axi_regmap CONFIG.DATA_WIDTH $data_width
@ -60,6 +64,10 @@ proc spi_engine_create {{name "hier_spi_engine"} {data_width 32} {async_spi_clk
ad_connect clk interconnect/clk ad_connect clk interconnect/clk
} }
if {$echo_sclk == 1} {
ad_connect echo_sclk execution/echo_sclk
}
ad_connect axi_regmap/spi_resetn offload/spi_resetn ad_connect axi_regmap/spi_resetn offload/spi_resetn
ad_connect axi_regmap/spi_resetn execution/resetn ad_connect axi_regmap/spi_resetn execution/resetn
ad_connect axi_regmap/spi_resetn interconnect/resetn ad_connect axi_regmap/spi_resetn interconnect/resetn

View File

@ -43,6 +43,7 @@ module spi_engine_execution #(
parameter DATA_WIDTH = 8, // Valid data widths values are 8/16/24/32 parameter DATA_WIDTH = 8, // Valid data widths values are 8/16/24/32
parameter NUM_OF_SDI = 1, parameter NUM_OF_SDI = 1,
parameter [0:0] SDO_DEFAULT = 1'b0, parameter [0:0] SDO_DEFAULT = 1'b0,
parameter ECHO_SCLK = 0,
parameter [1:0] SDI_DELAY = 2'b00) ( parameter [1:0] SDI_DELAY = 2'b00) (
input clk, input clk,
@ -67,6 +68,7 @@ module spi_engine_execution #(
output reg sync_valid, output reg sync_valid,
output [7:0] sync, output [7:0] sync,
input echo_sclk,
output reg sclk, output reg sclk,
output reg sdo, output reg sdo,
output reg sdo_t, output reg sdo_t,
@ -107,7 +109,6 @@ reg [(BIT_COUNTER_WIDTH+8):0] counter = 'h00;
wire [7:0] sleep_counter = counter[(BIT_COUNTER_WIDTH+8):(BIT_COUNTER_WIDTH+1)]; wire [7:0] sleep_counter = counter[(BIT_COUNTER_WIDTH+8):(BIT_COUNTER_WIDTH+1)];
wire [1:0] cs_sleep_counter = counter[(BIT_COUNTER_WIDTH+2):(BIT_COUNTER_WIDTH+1)]; wire [1:0] cs_sleep_counter = counter[(BIT_COUNTER_WIDTH+2):(BIT_COUNTER_WIDTH+1)];
wire [2:0] cs_sleep_counter2 = counter[(BIT_COUNTER_WIDTH+3):(BIT_COUNTER_WIDTH+1)];
wire [(BIT_COUNTER_WIDTH-1):0] bit_counter = counter[BIT_COUNTER_WIDTH:1]; wire [(BIT_COUNTER_WIDTH-1):0] bit_counter = counter[BIT_COUNTER_WIDTH:1];
wire [7:0] transfer_counter = counter[(BIT_COUNTER_WIDTH+8):(BIT_COUNTER_WIDTH+1)]; wire [7:0] transfer_counter = counter[(BIT_COUNTER_WIDTH+8):(BIT_COUNTER_WIDTH+1)];
wire ntx_rx = counter[0]; wire ntx_rx = counter[0];
@ -127,8 +128,6 @@ wire end_of_word;
reg [7:0] sdi_counter = 8'b0; reg [7:0] sdi_counter = 8'b0;
assign first_bit = ((bit_counter == 'h0) || (bit_counter == word_length)); assign first_bit = ((bit_counter == 'h0) || (bit_counter == word_length));
assign last_bit = bit_counter == word_length - 1;
assign end_of_word = last_bit == 1'b1 && ntx_rx == 1'b1 && clk_div_last == 1'b1;
reg [15:0] cmd_d1; reg [15:0] cmd_d1;
@ -159,13 +158,13 @@ wire trigger_rx;
wire sleep_counter_compare; wire sleep_counter_compare;
wire cs_sleep_counter_compare; wire cs_sleep_counter_compare;
wire cs_sleep_counter_compare2;
wire io_ready1; wire io_ready1;
wire io_ready2; wire io_ready2;
wire trigger_rx_s; wire trigger_rx_s;
wire last_sdi_bit; wire last_sdi_bit;
wire end_of_sdi_latch;
assign cmd_ready = idle; assign cmd_ready = idle;
@ -240,7 +239,6 @@ assign trigger_rx = trigger == 1'b1 && ntx_rx == 1'b1;
assign sleep_counter_compare = sleep_counter == cmd_d1[7:0] && clk_div_last == 1'b1; assign sleep_counter_compare = sleep_counter == cmd_d1[7:0] && clk_div_last == 1'b1;
assign cs_sleep_counter_compare = cs_sleep_counter == cmd_d1[9:8] && clk_div_last == 1'b1; assign cs_sleep_counter_compare = cs_sleep_counter == cmd_d1[9:8] && clk_div_last == 1'b1;
assign cs_sleep_counter_compare2 = cs_sleep_counter2 == {cmd_d1[9:8],1'b1} && clk_div_last == 1'b1;
always @(posedge clk) begin always @(posedge clk) begin
if (idle == 1'b1) begin if (idle == 1'b1) begin
@ -263,11 +261,11 @@ always @(posedge clk) begin
end else begin end else begin
case (inst_d1) case (inst_d1)
CMD_TRANSFER: begin CMD_TRANSFER: begin
if (transfer_active == 1'b0 && wait_for_io == 1'b0) if (transfer_active == 1'b0 && wait_for_io == 1'b0 && end_of_sdi_latch == 1'b1)
idle <= 1'b1; idle <= 1'b1;
end end
CMD_CHIPSELECT: begin CMD_CHIPSELECT: begin
if (cs_sleep_counter_compare2) if (cs_sleep_counter_compare)
idle <= 1'b1; idle <= 1'b1;
end end
CMD_MISC: begin CMD_MISC: begin
@ -318,15 +316,6 @@ always @(posedge clk) begin
sdo_data_ready <= 1'b0; sdo_data_ready <= 1'b0;
end end
always @(posedge clk) begin
if (resetn == 1'b0)
sdi_data_valid <= 1'b0;
else if (sdi_enabled == 1'b1 && last_sdi_bit == 1'b1 && trigger_rx_s == 1'b1)
sdi_data_valid <= 1'b1;
else if (sdi_data_ready == 1'b1)
sdi_data_valid <= 1'b0;
end
assign io_ready1 = (sdi_data_valid == 1'b0 || sdi_data_ready == 1'b1) && assign io_ready1 = (sdi_data_valid == 1'b0 || sdi_data_ready == 1'b1) &&
(sdo_enabled == 1'b0 || last_transfer == 1'b1 || sdo_data_valid == 1'b1); (sdo_enabled == 1'b0 || last_transfer == 1'b1 || sdo_data_valid == 1'b1);
assign io_ready2 = (sdi_enabled == 1'b0 || sdi_data_ready == 1'b1) && assign io_ready2 = (sdi_enabled == 1'b0 || sdi_data_ready == 1'b1) &&
@ -404,10 +393,142 @@ assign trigger_rx_s = trigger_rx_d[SDI_DELAY+1];
// Load the serial data into SDI shift register(s), then link it to the output // Load the serial data into SDI shift register(s), then link it to the output
// register of the module // register of the module
// NOTE: ECHO_SCLK mode can be used when the SCLK line is looped back to the FPGA
// through an other level shifter, in order to remove the round-trip timing delays
// introduced by the level shifters. This can improve the timing significantly
// on higher SCLK rates. Devices like ad4630 have an echod SCLK, which can be
// used to latch the MISO lines, improving the overall timing margin of the
// interface.
wire cs_active_s = (inst_d1 == CMD_CHIPSELECT) & ~(&cmd_d1[NUM_OF_CS-1:0]); wire cs_active_s = (inst_d1 == CMD_CHIPSELECT) & ~(&cmd_d1[NUM_OF_CS-1:0]);
genvar i; genvar i;
// NOTE: SPI configuration (CPOL/PHA) is only hardware configurable at this point
generate generate
if (ECHO_SCLK == 1) begin : g_echo_sclk_miso_latch
reg [7:0] sdi_counter_d = 8'b0;
reg [7:0] sdi_transfer_counter = 8'b0;
reg [7:0] num_of_transfers = 8'b0;
reg [(NUM_OF_SDI * DATA_WIDTH)-1:0] sdi_data_latch = {(NUM_OF_SDI * DATA_WIDTH){1'b0}};
if ((DEFAULT_SPI_CFG[1:0] == 2'b01) || (DEFAULT_SPI_CFG[1:0] == 2'b10)) begin : g_echo_miso_nshift_reg
// MISO shift register runs on negative echo_sclk
for (i=0; i<NUM_OF_SDI; i=i+1) begin: g_sdi_shift_reg
reg [DATA_WIDTH-1:0] data_sdi_shift;
always @(negedge echo_sclk or posedge cs_active_s) begin
if (cs_active_s) begin
data_sdi_shift <= 0;
end else begin
data_sdi_shift <= {data_sdi_shift, sdi[i]};
end
end
// intended LATCH
always @(negedge echo_sclk) begin
if (last_sdi_bit)
sdi_data_latch[i*DATA_WIDTH+:DATA_WIDTH] <= {data_sdi_shift, sdi[i]};
end
end
always @(posedge echo_sclk or posedge cs_active_s) begin
if (cs_active_s == 1'b1) begin
sdi_counter <= 8'b0;
sdi_counter_d <= 8'b0;
end else begin
sdi_counter <= (sdi_counter == word_length-1) ? 8'b0 : sdi_counter + 1'b1;
sdi_counter_d <= sdi_counter;
end
end
end else begin : g_echo_miso_pshift_reg
// MISO shift register runs on positive echo_sclk
for (i=0; i<NUM_OF_SDI; i=i+1) begin: g_sdi_shift_reg
reg [DATA_WIDTH-1:0] data_sdi_shift;
always @(posedge echo_sclk or posedge cs_active_s) begin
if (cs_active_s) begin
data_sdi_shift <= 0;
end else begin
data_sdi_shift <= {data_sdi_shift, sdi[i]};
end
end
// intended LATCH
always @(posedge echo_sclk) begin
if (last_sdi_bit)
sdi_data_latch[i*DATA_WIDTH+:DATA_WIDTH] <= data_sdi_shift;
end
end
always @(posedge echo_sclk or posedge cs_active_s) begin
if (cs_active_s == 1'b1) begin
sdi_counter <= 8'b0;
sdi_counter_d <= 8'b0;
end else begin
sdi_counter <= (sdi_counter == word_length-1) ? 8'b0 : sdi_counter + 1'b1;
sdi_counter_d <= sdi_counter;
end
end
end
assign sdi_data = sdi_data_latch;
assign last_sdi_bit = (sdi_counter == 0) && (sdi_counter_d == word_length-1);
// sdi_data_valid is synchronous to SPI clock, so synchronize the
// last_sdi_bit to SPI clock
reg [3:0] last_sdi_bit_m = 4'b0;
always @(posedge clk) begin
if (cs_active_s) begin
last_sdi_bit_m <= 4'b0;
end else begin
last_sdi_bit_m <= {last_sdi_bit_m, last_sdi_bit};
end
end
always @(posedge clk) begin
if (cs_active_s) begin
sdi_data_valid <= 1'b0;
end else if (sdi_enabled == 1'b1 &&
last_sdi_bit_m[3] == 1'b0 &&
last_sdi_bit_m[2] == 1'b1) begin
sdi_data_valid <= 1'b1;
end else if (sdi_data_ready == 1'b1 && sdi_data_valid == 1'b1) begin
sdi_data_valid <= 1'b0;
end
end
always @(posedge clk) begin
if (cs_active_s) begin
num_of_transfers <= 8'b0;
end else begin
if (cmd_d1[15:12] == 4'b0) begin
num_of_transfers <= cmd_d1[7:0] + 1'b1; // cmd_d1 contains the NUM_OF_TRANSFERS - 1
end
end
end
always @(posedge clk) begin
if (cs_active_s) begin
sdi_transfer_counter <= 0;
end else if (last_sdi_bit_m[2] == 1'b0 &&
last_sdi_bit_m[1] == 1'b1) begin
sdi_transfer_counter <= sdi_transfer_counter + 1'b1;
end
end
assign end_of_sdi_latch = last_sdi_bit_m[2] & (sdi_transfer_counter == num_of_transfers);
end /* g_echo_sclk_miso_latch */
else
begin : g_sclk_miso_latch
assign end_of_sdi_latch = 1'b1;
for (i=0; i<NUM_OF_SDI; i=i+1) begin: g_sdi_shift_reg for (i=0; i<NUM_OF_SDI; i=i+1) begin: g_sdi_shift_reg
reg [DATA_WIDTH-1:0] data_sdi_shift; reg [DATA_WIDTH-1:0] data_sdi_shift;
@ -425,18 +546,35 @@ generate
assign sdi_data[i*DATA_WIDTH+:DATA_WIDTH] = data_sdi_shift; assign sdi_data[i*DATA_WIDTH+:DATA_WIDTH] = data_sdi_shift;
end end
endgenerate
assign last_sdi_bit = (sdi_counter == word_length-1); assign last_sdi_bit = (sdi_counter == word_length-1);
always @(posedge clk) begin always @(posedge clk) begin
if (resetn == 1'b0) begin if (resetn == 1'b0) begin
sdi_counter <= 8'b0; sdi_counter <= 8'b0;
end else begin end else begin
if (trigger_rx_s == 1'b1) begin if (trigger_rx_s == 1'b1) begin
sdi_counter <= last_sdi_bit ? 8'b0 : sdi_counter + 1'b1; sdi_counter <= last_sdi_bit ? 8'b0 : sdi_counter + 1'b1;
end
end end
end end
end
always @(posedge clk) begin
if (resetn == 1'b0)
sdi_data_valid <= 1'b0;
else if (sdi_enabled == 1'b1 && last_sdi_bit == 1'b1 && trigger_rx_s == 1'b1)
sdi_data_valid <= 1'b1;
else if (sdi_data_ready == 1'b1)
sdi_data_valid <= 1'b0;
end
end /* g_sclk_miso_latch */
endgenerate
// end_of_word will signal the end of a transaction, pushing the command
// stream execution to the next command. end_of_word in normal mode can be
// generated using the global bit_counter
assign last_bit = bit_counter == word_length - 1;
assign end_of_word = last_bit == 1'b1 && ntx_rx == 1'b1 && clk_div_last == 1'b1;
always @(posedge clk) begin always @(posedge clk) begin
if (transfer_active == 1'b1) begin if (transfer_active == 1'b1) begin

View File

@ -107,6 +107,21 @@ set_property -dict [list \
] \ ] \
[ipx::get_hdl_parameters SDI_DELAY -of_objects $cc] [ipx::get_hdl_parameters SDI_DELAY -of_objects $cc]
## ECHO SCLK
set_property -dict [list \
"value_format" "bool" \
"value" "false" \
] \
[ipx::get_user_parameters ECHO_SCLK -of_objects $cc]
set_property -dict [list \
"value_format" "bool" \
"value" "false" \
] \
[ipx::get_hdl_parameters ECHO_SCLK -of_objects $cc]
## echo_sclk should be active only when ECHO_SCLK is set
adi_set_ports_dependency echo_sclk ECHO_SCLK 0
## Customize IP Layout ## Customize IP Layout
## Remove the automatically generated GUI page ## Remove the automatically generated GUI page
@ -176,6 +191,15 @@ set_property -dict [list \
"tooltip" "\[SDO_DEFAULT\] Define the default voltage level on MOSI" "tooltip" "\[SDO_DEFAULT\] Define the default voltage level on MOSI"
] [ipgui::get_guiparamspec -name "SDO_DEFAULT" -component $cc] ] [ipgui::get_guiparamspec -name "SDO_DEFAULT" -component $cc]
set custom_clocking_group [ipgui::add_group -name "Custom clocking options" -component $cc \
-parent $page0 -display_name "Custom clocking options" ]
ipgui::add_param -name "ECHO_SCLK" -component $cc -parent $custom_clocking_group
set_property -dict [list \
"display_name" "Echoed SCLK" \
"tooltip" "\[ECHO_SCLK\] Activate echo SCLK option (hardware support required)"
] [ipgui::get_guiparamspec -name "ECHO_SCLK" -component $cc]
## Create and save the XGUI file ## Create and save the XGUI file
ipx::create_xgui_files $cc ipx::create_xgui_files $cc