aboutsummaryrefslogtreecommitdiffstats
path: root/hdl/spi_controller.v
blob: 5bc71f643bc132420a6cd36f8e8d346d3b360c81 (plain) (blame)
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
289
290
`timescale 1ns / 1ps

module spi_controller (
    input wire clk,
    input wire rst_n,
    
    input wire [7:0] cmd_opcode,
    input wire [31:0] cmd_address,
    input wire [15:0] cmd_length,
    input wire cmd_valid,
    output wire cmd_ready,
    
    input wire [7:0] data_in,
    input wire data_in_valid,
    output wire data_in_ready,
    
    output wire [7:0] data_out,
    output wire data_out_valid,
    input wire data_out_ready,
    
    output wire spi0_sck,
    output wire spi0_mosi,
    input wire spi0_miso,
    output wire spi0_cs,
    
    output wire spi1_sck,
    output wire spi1_mosi,
    input wire spi1_miso,
    output wire spi1_cs,
    
    output reg [3:0] flash_select,
    output reg [3:0] spi_state,
    output reg [3:0] status_reg
);

    localparam STATE_IDLE        = 4'h0;
    localparam STATE_CMD        = 4'h1;
    localparam STATE_ADDR       = 4'h2;
    localparam STATE_DUMMY      = 4'h3;
    localparam STATE_DATA_RX    = 4'h4;
    localparam STATE_DATA_TX    = 4'h5;
    localparam STATE_WAIT       = 4'h6;
    localparam STATE_ERROR      = 4'h7;
    
    localparam MODE_STANDARD    = 2'b00;
    localparam MODE_FAST       = 2'b01;
    localparam MODE_DUAL       = 2'b10;
    localparam MODE_QUAD       = 2'b11;
    
    localparam CMD_WREN         = 8'h06;
    localparam CMD_WRDI        = 8'h04;
    localparam CMD_RDSR        = 8'h05;
    localparam CMD_WRSR        = 8'h01;
    localparam CMD_READ        = 8'h03;
    localparam CMD_FAST_READ   = 8'h0B;
    localparam CMD_DREAD       = 8'h3B;
    localparam CMD_QREAD       = 8'h6B;
    localparam CMD_PP          = 8'h02;
    localparam CMD_SE          = 8'h20;
    localparam CMD_BE32        = 8'h52;
    localparam CMD_BE64        = 8'hD8;
    localparam CMD_CE          = 8'hC7;
    localparam CMD_RDID        = 8'h9F;
    localparam CMD_RDFR        = 8'h48;
    localparam CMD_WRFR        = 8'h42;
    
    reg [7:0] spi_mode;
    reg [2:0] spi_divider;
    reg [31:0] current_addr;
    reg [15:0] bytes_remaining;
    reg [7:0] command_reg;
    reg [3:0] state;
    reg [3:0] next_state;
    
    reg [7:0] tx_shift_reg;
    reg [7:0] rx_shift_reg;
    reg [3:0] bit_counter;
    reg tx_active;
    reg rx_active;
    
    reg cs0_active;
    reg cs1_active;
    reg sck_active;
    
    reg [23:0] delay_counter;
    
    reg [7:0] clk_div_counter;
    wire sck_posedge;
    wire sck_negedge;
    
    assign sck_posedge = (clk_div_counter == spi_divider);
    assign sck_negedge = (clk_div_counter == (spi_divider >> 1));
    
    assign spi0_sck = sck_active & cs0_active & ~spi0_cs;
    assign spi1_sck = sck_active & cs1_active & ~spi1_cs;
    assign spi0_cs = ~cs0_active;
    assign spi1_cs = ~cs1_active;
    
    assign spi0_mosi = tx_active ? tx_shift_reg[7] : 1'bz;
    assign spi1_mosi = tx_active ? tx_shift_reg[7] : 1'bz;
    
    assign cmd_ready = (state == STATE_IDLE);
    
    assign data_in_ready = (state == STATE_DATA_TX) && tx_active && sck_negedge;
    assign data_out_valid = (state == STATE_DATA_RX) && rx_active && sck_posedge;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= STATE_IDLE;
            spi_state <= STATE_IDLE;
            cs0_active <= 1'b0;
            cs1_active <= 1'b0;
            sck_active <= 1'b0;
            tx_active <= 1'b0;
            rx_active <= 1'b0;
            spi_mode <= MODE_STANDARD;
            spi_divider <= 3'd1;
            flash_select <= 2'b00;
            status_reg <= 4'b0000;
        end else begin
            state <= next_state;
            spi_state <= state;
            
            case (state)
                STATE_IDLE: begin
                    cs0_active <= 1'b0;
                    cs1_active <= 1'b0;
                    sck_active <= 1'b0;
                    tx_active <= 1'b0;
                    rx_active <= 1'b0;
                    
                    if (cmd_valid && cmd_ready) begin
                        if (cmd_address[24] == 1'b0) begin
                            cs0_active <= 1'b1;
                            flash_select <= 2'b01;
                        end else begin
                            cs1_active <= 1'b1;
                            flash_select <= 2'b10;
                        end
                        
                        command_reg <= cmd_opcode;
                        current_addr <= cmd_address;
                        bytes_remaining <= cmd_length;
                        next_state <= STATE_CMD;
                    end
                end
                
                STATE_CMD: begin
                    if (sck_negedge) begin
                        if (bit_counter == 4'd7) begin
                            bit_counter <= 4'd0;
                            if (command_reg == CMD_READ || 
                                command_reg == CMD_FAST_READ ||
                                command_reg == CMD_PP) begin
                                next_state <= STATE_ADDR;
                            end else if (command_reg == CMD_RDSR ||
                                       command_reg == CMD_RDID) begin
                                next_state <= STATE_DATA_RX;
                            end else begin
                                next_state <= STATE_WAIT;
                            end
                        end else begin
                            tx_shift_reg <= tx_shift_reg << 1;
                            bit_counter <= bit_counter + 1;
                        end
                    end
                end
                
                STATE_ADDR: begin
                    if (sck_negedge) begin
                        if (bit_counter == 4'd7) begin
                            bit_counter <= 4'd0;
                            if (addr_byte_counter == 3'd2) begin
                                if (command_reg == CMD_FAST_READ ||
                                    command_reg == CMD_DREAD ||
                                    command_reg == CMD_QREAD) begin
                                    next_state <= STATE_DUMMY;
                                end else if (command_reg == CMD_READ) begin
                                    next_state <= STATE_DATA_RX;
                                end else if (command_reg == CMD_PP) begin
                                    next_state <= STATE_DATA_TX;
                                end else begin
                                    next_state <= STATE_WAIT;
                                end
                            end else begin
                                addr_byte_counter <= addr_byte_counter + 1;
                            end
                        end else begin
                            tx_shift_reg <= tx_shift_reg << 1;
                            bit_counter <= bit_counter + 1;
                        end
                    end
                end
                
                STATE_DATA_RX: begin
                    rx_active <= 1'b1;
                    if (sck_posedge) begin
                        rx_shift_reg <= {rx_shift_reg[6:0], (cs0_active ? spi0_miso : spi1_miso)};
                        if (bit_counter == 4'd7) begin
                            bit_counter <= 4'd0;
                            data_out <= rx_shift_reg;
                            if (bytes_remaining > 0) begin
                                bytes_remaining <= bytes_remaining - 1;
                            end else begin
                                next_state <= STATE_IDLE;
                                rx_active <= 1'b0;
                            end
                        end else begin
                            bit_counter <= bit_counter + 1;
                        end
                    end
                end
                
                STATE_DATA_TX: begin
                    tx_active <= 1'b1;
                    if (sck_negedge) begin
                        if (bit_counter == 4'd7) begin
                            bit_counter <= 4'd0;
                            if (data_in_valid && data_in_ready) begin
                                tx_shift_reg <= data_in;
                                if (bytes_remaining > 0) begin
                                    bytes_remaining <= bytes_remaining - 1;
                                end else begin
                                    next_state <= STATE_WAIT;
                                    tx_active <= 1'b0;
                                end
                            end
                        end else begin
                            tx_shift_reg <= tx_shift_reg << 1;
                            bit_counter <= bit_counter + 1;
                        end
                    end
                end
                
                STATE_WAIT: begin
                    if (delay_counter == 24'hFFFFFF) begin
                        next_state <= STATE_IDLE;
                    end else begin
                        delay_counter <= delay_counter + 1;
                    end
                end
                
                STATE_ERROR: begin
                    cs0_active <= 1'b0;
                    cs1_active <= 1'b0;
                    sck_active <= 1'b0;
                    next_state <= STATE_IDLE;
                end
            endcase
        end
    end
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            clk_div_counter <= 8'd0;
            sck_active <= 1'b0;
        end else begin
            if (cs0_active || cs1_active) begin
                if (clk_div_counter >= spi_divider) begin
                    clk_div_counter <= 8'd0;
                    sck_active <= ~sck_active;
                end else begin
                    clk_div_counter <= clk_div_counter + 1;
                end
            end else begin
                clk_div_counter <= 8'd0;
                sck_active <= 1'b0;
            end
        end
    end
    
    reg [2:0] addr_byte_counter;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            addr_byte_counter <= 3'd0;
        end else if (state == STATE_ADDR) begin
        end else begin
            addr_byte_counter <= 3'd0;
        end
    end
    
    initial begin
        tx_shift_reg = 8'h00;
        rx_shift_reg = 8'h00;
        bit_counter = 4'd0;
        delay_counter = 24'h0;
    end

endmodule