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
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} {
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 rst resetn
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.SDO_DEFAULT 1
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_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
}
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 execution/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 NUM_OF_SDI = 1,
parameter [0:0] SDO_DEFAULT = 1'b0,
parameter ECHO_SCLK = 0,
parameter [1:0] SDI_DELAY = 2'b00) (
input clk,
@ -67,6 +68,7 @@ module spi_engine_execution #(
output reg sync_valid,
output [7:0] sync,
input echo_sclk,
output reg sclk,
output reg sdo,
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 [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 [7:0] transfer_counter = counter[(BIT_COUNTER_WIDTH+8):(BIT_COUNTER_WIDTH+1)];
wire ntx_rx = counter[0];
@ -127,8 +128,6 @@ wire end_of_word;
reg [7:0] sdi_counter = 8'b0;
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;
@ -159,13 +158,13 @@ wire trigger_rx;
wire sleep_counter_compare;
wire cs_sleep_counter_compare;
wire cs_sleep_counter_compare2;
wire io_ready1;
wire io_ready2;
wire trigger_rx_s;
wire last_sdi_bit;
wire end_of_sdi_latch;
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 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
if (idle == 1'b1) begin
@ -263,11 +261,11 @@ always @(posedge clk) begin
end else begin
case (inst_d1)
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;
end
CMD_CHIPSELECT: begin
if (cs_sleep_counter_compare2)
if (cs_sleep_counter_compare)
idle <= 1'b1;
end
CMD_MISC: begin
@ -318,15 +316,6 @@ always @(posedge clk) begin
sdo_data_ready <= 1'b0;
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) &&
(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) &&
@ -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
// 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]);
genvar i;
// NOTE: SPI configuration (CPOL/PHA) is only hardware configurable at this point
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
reg [DATA_WIDTH-1:0] data_sdi_shift;
@ -425,18 +546,35 @@ generate
assign sdi_data[i*DATA_WIDTH+:DATA_WIDTH] = data_sdi_shift;
end
endgenerate
assign last_sdi_bit = (sdi_counter == word_length-1);
always @(posedge clk) begin
if (resetn == 1'b0) begin
sdi_counter <= 8'b0;
end else begin
if (trigger_rx_s == 1'b1) begin
sdi_counter <= last_sdi_bit ? 8'b0 : sdi_counter + 1'b1;
assign last_sdi_bit = (sdi_counter == word_length-1);
always @(posedge clk) begin
if (resetn == 1'b0) begin
sdi_counter <= 8'b0;
end else begin
if (trigger_rx_s == 1'b1) begin
sdi_counter <= last_sdi_bit ? 8'b0 : sdi_counter + 1'b1;
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
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]
## 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
## Remove the automatically generated GUI page
@ -176,6 +191,15 @@ set_property -dict [list \
"tooltip" "\[SDO_DEFAULT\] Define the default voltage level on MOSI"
] [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
ipx::create_xgui_files $cc