Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Javier Valenzuela <javier.valenzuela@ni.com> Original-commit: 7015f5ed2d495f3908773b7c7d74864d0cc3871a
289 lines
8.6 KiB
Verilog
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
|