Files
b210-k7-fpga/top/x400/cpld/spi_slave.v
T
7e59e9516d fpga: x400: cpld: Add support for X410 motherboard CPLD
Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com>
Co-authored-by: Javier Valenzuela <javier.valenzuela@ni.com>


Original-commit: 7015f5ed2d495f3908773b7c7d74864d0cc3871a
2021-06-10 11:56:58 -05:00

289 lines
8.6 KiB
Verilog

//
// Copyright 2021 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: spi_slave
//
// Description:
//
// SPI slave for configuration CPOL = CPHA = 0.
// Transfers 8 bit = 1 byte MSB first. Parallel data has to be
// provided and consumed immediately when flags are asserted.
//
// Limitation: clk frequency <= 2*sclk frequency
//
// Data request from sclk domain is triggered towards the clk domain ahead of
// time. This is due to the clock domain crossing using the synchronizer and
// processing pipeline stages.
//
// The worst case propagation delay of the used synchronizer is:
//
// 4 'clk' clock cycles:
// 1 clock cycle of signal propagation to synchronizer
// (data_request_sclk assertion)
// 1 clock cycle to capture data with instability in first stage
// 1 clock cycle to stabilize first stage
// 1 clock cycle to capture data in second stage
// (data_request_clk available in 'clk' domain)
//
// Once synchronized in 'clk' domain, there is one additional clock cycle to
// derive data_out_valid and data_in_required. To ensure that transmit data
// is registered a 'clk' cycle ahead of the actual transmission we need 2
// more 'clk' clock cycles. This ensures that transmit_word has changed and
// is stable for at least one 'clk' cycle before 'sclk' asserts again. Any
// additional time required externally to respond to the control port
// requests should be considered in this crossing as well. This is a total of
// 7 clock cycles (+ctrlport response margin) @ clk domain. The minimum
// required time in sclk domain to issue the request is calculated based on
// the clock frequencies.
//
// Parameters:
//
// CLK_FREQUENCY : Frequency of "clk"
// SPI_FREQUENCY : Frequency of "sclk"
//
`default_nettype none
module spi_slave #(
parameter CLK_FREQUENCY = 50000000,
parameter SPI_FREQUENCY = 10000000
) (
//---------------------------------------------------------------
// SPI Interface
//---------------------------------------------------------------
input wire sclk,
input wire cs_n,
input wire mosi,
output wire miso,
//---------------------------------------------------------------
// Parallel Interface
//---------------------------------------------------------------
input wire clk,
input wire rst,
output reg data_in_required,
input wire data_in_valid,
input wire [7:0] data_in,
output reg data_out_valid,
output reg [7:0] data_out,
output wire active
);
wire [0:0] data_request_clk;
wire [0:0] reception_complete_clk;
//---------------------------------------------------------------
// SPI Receiver @ sclk
//---------------------------------------------------------------
reg [7:0] receiver_reg;
reg [2:0] current_bit_index;
reg reception_complete_sclk = 1'b0;
reg [7:0] received_word;
always @(posedge sclk or posedge cs_n) begin
// Reset logic on positive cs_n edge = slave idle
if (cs_n) begin
receiver_reg <= 8'b0;
end
// Rising edge of sclk
else begin
// Capture bits into shift register MSBs first
receiver_reg <= {receiver_reg[6:0], mosi};
end
end
// Reset with cs_n might occur too early during clk sync.
// Reset half way through the reception.
always @(posedge sclk) begin
// Complete word was received
if (current_bit_index == 7) begin
reception_complete_sclk <= 1'b1;
received_word <= {receiver_reg[6:0], mosi};
// Reset after half transaction
end else if (current_bit_index == 3) begin
reception_complete_sclk <= 1'b0;
end
end
//---------------------------------------------------------------
// Handover of data sclk -> clk
//---------------------------------------------------------------
synchronizer #(
.WIDTH (1),
.STAGES (2),
.INITIAL_VAL (1'b0),
.FALSE_PATH_TO_IN (1)
) data_sync_inst (
.clk (clk),
.rst (1'b0),
.in (reception_complete_sclk),
.out (reception_complete_clk)
);
//---------------------------------------------------------------
// Parallel interface data output @ clk
//---------------------------------------------------------------
reg reception_complete_clk_delayed = 1'b0;
// Propagate toggling signal without reset to ensure stability on reset
always @(posedge clk) begin
// Capture last state of reception
reception_complete_clk_delayed <= reception_complete_clk;
end
// Derive data and control signal
always @(posedge clk) begin
if (rst) begin
data_out_valid <= 1'b0;
data_out <= 8'b0;
end
else begin
// Default assignment
data_out_valid <= 1'b0;
// Provide data to output on rising_edge
if (reception_complete_clk & ~reception_complete_clk_delayed) begin
// Data can simply be captured as the reception complete signal
// indicates stable values in received_word.
data_out <= received_word;
data_out_valid <= 1'b1;
end
end
end
//---------------------------------------------------------------
// SPI Transmitter @ sclk
//---------------------------------------------------------------
// Data request calculation:
// SCLK_CYCLES_DURING_DATA_REQ = 8 clk period / sclk period
// Clock periods are expressed by reciprocal of frequencies.
// Term "+CLK_FREQUENCY-1" is used to round up the result in integer logic.
localparam SCLK_CYCLES_DURING_DATA_REQ = (8*SPI_FREQUENCY + CLK_FREQUENCY-1)/CLK_FREQUENCY;
// subtract from 8 bits per transfer to get target index
localparam DATA_REQ_BIT_INDEX = 8 - SCLK_CYCLES_DURING_DATA_REQ;
reg [7:0] transmit_bits;
reg [7:0] transmit_word;
reg data_request_sclk = 1'b0;
always @(negedge sclk or posedge cs_n) begin
// Reset logic on positive cs_n edge = slave idle
if (cs_n) begin
current_bit_index <= 3'b0;
data_request_sclk <= 1'b0;
transmit_bits <= 8'b0;
end
// Falling edge of sclk
else begin
// Fill or move shift register for byte transmissions
if (current_bit_index == 7) begin
transmit_bits <= transmit_word;
end else begin
transmit_bits <= {transmit_bits[6:0], 1'b0};
end
// Update bit index
current_bit_index <= current_bit_index + 1'b1;
// Trigger request for new word at start of calculated index
if (current_bit_index == DATA_REQ_BIT_INDEX-1) begin
data_request_sclk <= 1'b1;
// Reset after half the reception in case cs_n is not changed in between
// two transactions.
end else if (current_bit_index == (DATA_REQ_BIT_INDEX+4-1)%8) begin
data_request_sclk <= 1'b0;
end
end
end
// Drive miso output with data when cs_n low
assign miso = cs_n ? 1'bz : transmit_bits[7];
//---------------------------------------------------------------
// Handover of Data Request sclk -> clk
//---------------------------------------------------------------
synchronizer #(
.WIDTH (1),
.STAGES (2),
.INITIAL_VAL (1'b0),
.FALSE_PATH_TO_IN (1)
) request_sync_inst (
.clk (clk),
.rst (rst),
.in (data_request_sclk),
.out (data_request_clk)
);
//---------------------------------------------------------------
// Parallel Interface Data Input Control
//---------------------------------------------------------------
reg data_request_clk_delayed;
always @(posedge clk) begin
if (rst) begin
data_request_clk_delayed <= 1'b0;
data_in_required <= 1'b0;
transmit_word <= 8'b0;
end
else begin
// Default assignment
data_in_required <= 1'b0;
// Capture last state of data request
data_request_clk_delayed <= data_request_clk;
// Request data from input
if (~data_request_clk_delayed & data_request_clk) begin
data_in_required <= 1'b1;
end
// Capture new data if valid data available, 0 otherwise.
if (data_in_required) begin
if (data_in_valid) begin
transmit_word <= data_in;
end else begin
transmit_word <= 8'b0;
end
end
end
end
//---------------------------------------------------------------
// Chip Select
//---------------------------------------------------------------
// Driven as active signal in parallel clock domain
wire cs_n_clk;
assign active = ~cs_n_clk;
synchronizer #(
.WIDTH (1),
.STAGES (2),
.INITIAL_VAL (1'b1),
.FALSE_PATH_TO_IN (1)
) active_sync_inst (
.clk (clk),
.rst (rst),
.in (cs_n),
.out (cs_n_clk)
);
endmodule
`default_nettype wire