aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/x400/cpld/spi_slave.v
blob: ab2f16fea5a1d49639fac99b288c997e48a8f84e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
//
// 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