File: simple_spi_core.v

package info (click to toggle)
uhd 3.15.0.0-4
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 202,252 kB
  • sloc: cpp: 182,756; ansic: 94,109; vhdl: 53,420; python: 45,849; xml: 12,956; tcl: 7,046; makefile: 2,248; sh: 1,741; pascal: 230; javascript: 120; csh: 94; asm: 20; perl: 11
file content (229 lines) | stat: -rw-r--r-- 7,212 bytes parent folder | download | duplicates (4)
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
//
// Copyright 2012 Ettus Research LLC
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//


// Simple SPI core, the simplest, yet complete spi core I can think of

// Settings register controlled.
// 2 settings regs, control and data
// 1 32-bit readback and status signal

// Settings reg map:
//
// BASE+0 divider setting
// bits [15:0] spi clock divider
//
// BASE+1 configuration input
// bits [23:0] slave select, bit0 = slave0 enabled
// bits [29:24] num bits (1 through 32)
// bit [30] data input edge = in data bit latched on rising edge of clock
// bit [31] data output edge = out data bit latched on rising edge of clock
//
// BASE+2 input data
// Writing this register begins a spi transaction.
// Bits are latched out from bit 0.
// Therefore, load this register in reverse.
//
// Readback
// Bits are latched into bit 0.
// Therefore, data will be in-order.

module simple_spi_core
    #(
        //settings register base address
        parameter BASE = 0,

        //width of serial enables (up to 24 is possible)
        parameter WIDTH = 8,

        //idle state of the spi clock
        parameter CLK_IDLE = 0,

        //idle state of the serial enables
        parameter SEN_IDLE = 24'hffffff
    )
    (
        //clock and synchronous reset
        input clock, input reset,

        //32-bit settings bus inputs
        input set_stb, input [7:0] set_addr, input [31:0] set_data,

        //32-bit data readback
        output [31:0] readback,
        output reg readback_stb,

        //read is high when spi core can begin another transaction
        output ready,

        //spi interface, slave selects, clock, data in, data out
        output [WIDTH-1:0] sen,
        output sclk,
        output reg mosi,
        input miso,

        //optional debug output
        output [31:0] debug
    );

    wire [15:0] sclk_divider;
    setting_reg #(.my_addr(BASE+0),.width(16)) divider_sr(
        .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data),
        .out(sclk_divider),.changed());

    wire [23:0] slave_select;
    wire [5:0] num_bits;
    wire datain_edge, dataout_edge;
    setting_reg #(.my_addr(BASE+1),.width(32)) ctrl_sr(
        .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data),
        .out({dataout_edge, datain_edge, num_bits, slave_select}),.changed());

    wire [31:0] mosi_data;
    wire trigger_spi;
    setting_reg #(.my_addr(BASE+2),.width(32)) data_sr(
        .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data),
        .out(mosi_data),.changed(trigger_spi));

    localparam WAIT_TRIG = 0;
    localparam PRE_IDLE = 1;
    localparam CLK_REG = 2;
    localparam CLK_INV = 3;
    localparam POST_IDLE = 4;
    localparam IDLE_SEN = 5;

    reg [2:0] state;

    reg ready_reg;
    assign ready = ready_reg && ~trigger_spi;

    //serial clock either idles or is in one of two clock states
    reg sclk_reg;
    assign sclk = sclk_reg;

    //serial enables either idle or enabled based on state
    // IJB. One pipeline stage to break critical path from register in I/O pads.
    wire sen_is_idle = (state == WAIT_TRIG) || (state == IDLE_SEN);
    wire [23:0] sen24 = (sen_is_idle)? SEN_IDLE : (SEN_IDLE ^ slave_select);
    reg [WIDTH-1:0] sen_reg = SEN_IDLE;
    always @(posedge clock) begin
      if (reset) begin
        sen_reg <= SEN_IDLE;
      end else begin
        sen_reg <= sen24[WIDTH-1:0];
      end
    end
    assign sen = sen_reg;

    //data output shift register
   // IJB. One pipeline stage to break critical path from register in I/O pads.
    reg [31:0] dataout_reg;
    wire [31:0] dataout_next = {dataout_reg[30:0], 1'b0};
   
   always @(posedge clock)
     mosi <= dataout_reg[31];

    //data input shift register
   // IJB. Two pipeline stages to break critical path from register in I/O pads.
   reg 		miso_pipe, miso_pipe2;
   always @(posedge clock) begin
      miso_pipe2 <= miso;
      miso_pipe <= miso_pipe2;
   end
   
    reg [31:0] datain_reg;
    wire [31:0] datain_next = {datain_reg[30:0], miso_pipe};
    assign readback = datain_reg;

    //counter for spi clock
    reg [15:0] sclk_counter;
    wire sclk_counter_done = (sclk_counter == sclk_divider);
    wire [15:0] sclk_counter_next = (sclk_counter_done)? 0 : sclk_counter + 1;

    //counter for latching bits miso/mosi
    reg [6:0] bit_counter;
    wire [6:0] bit_counter_next = bit_counter + 1;
    wire bit_counter_done = (bit_counter_next == num_bits);

    always @(posedge clock) begin
        if (reset) begin
            state <= WAIT_TRIG;
            sclk_reg <= CLK_IDLE;
            ready_reg <= 0;
            readback_stb <= 1'b0;
        end
        else begin
            case (state)

            WAIT_TRIG: begin
                if (trigger_spi) state <= PRE_IDLE;
                readback_stb <= 1'b0;
                ready_reg <= ~trigger_spi;
                dataout_reg <= mosi_data;
                sclk_counter <= 0;
                bit_counter <= 0;
                sclk_reg <= CLK_IDLE;
            end

            PRE_IDLE: begin
                if (sclk_counter_done) state <= CLK_REG;
                sclk_counter <= sclk_counter_next;
                sclk_reg <= CLK_IDLE;
            end

            CLK_REG: begin
                if (sclk_counter_done) begin
                    state <= CLK_INV;
                    if (datain_edge  != CLK_IDLE)                     datain_reg  <= datain_next;
                    if (dataout_edge != CLK_IDLE && bit_counter != 0) dataout_reg <= dataout_next;
                    sclk_reg <= ~CLK_IDLE; //transition to rising when CLK_IDLE == 0
                end
                sclk_counter <= sclk_counter_next;
            end

            CLK_INV: begin
                if (sclk_counter_done) begin
                    state <= (bit_counter_done)? POST_IDLE : CLK_REG;
                    bit_counter <= bit_counter_next;
                    if (datain_edge  == CLK_IDLE)                      datain_reg  <= datain_next;
                    if (dataout_edge == CLK_IDLE && ~bit_counter_done) dataout_reg <= dataout_next;
                    sclk_reg <= CLK_IDLE; //transition to falling when CLK_IDLE == 0
                end
                sclk_counter <= sclk_counter_next;
            end

            POST_IDLE: begin
                if (sclk_counter_done) state <= IDLE_SEN;
                sclk_counter <= sclk_counter_next;
                sclk_reg <= CLK_IDLE;
            end

            IDLE_SEN: begin
                if (sclk_counter_done) begin
                  ready_reg <= 1'b1;
                  readback_stb <= 1'b1;
                  state <= WAIT_TRIG;
                end
                sclk_counter <= sclk_counter_next;
                sclk_reg <= CLK_IDLE;
            end

            default: state <= WAIT_TRIG;

            endcase //state
        end
    end

    assign debug = {
        trigger_spi, state, //4
        sclk, mosi, miso, ready, //4
        //sen[7:0], //8
        1'b0, bit_counter[6:0], //8
        sclk_counter_done, bit_counter_done, //2
        sclk_counter[5:0] //6
    };

endmodule //simple_spi_core