Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 1 | //---------------------------------------------------------------------------- |
| 2 | // Module: simple_spi_master |
| 3 | // |
| 4 | //---------------------------------------------------------------------------- |
| 5 | // Copyright (C) 2019 efabless, inc. |
| 6 | // |
| 7 | // This source file may be used and distributed without |
| 8 | // restriction provided that this copyright statement is not |
| 9 | // removed from the file and that any derivative work contains |
| 10 | // the original copyright notice and the associated disclaimer. |
| 11 | // |
| 12 | // This source file is free software; you can redistribute it |
| 13 | // and/or modify it under the terms of the GNU Lesser General |
| 14 | // Public License as published by the Free Software Foundation; |
| 15 | // either version 2.1 of the License, or (at your option) any |
| 16 | // later version. |
| 17 | // |
| 18 | // This source is distributed in the hope that it will be |
| 19 | // useful, but WITHOUT ANY WARRANTY; without even the implied |
| 20 | // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
| 21 | // PURPOSE. See the GNU Lesser General Public License for more |
| 22 | // details. |
| 23 | // |
| 24 | //-------------------------------------------------------------------- |
| 25 | // |
| 26 | // resetn: active low async reset |
| 27 | // clk: master clock (before prescaler) |
| 28 | // stream: |
| 29 | // 0 = apply/release CSB separately for each byte |
| 30 | // 1 = apply CSB until stream bit is cleared |
| 31 | // mlb: |
| 32 | // 0 = msb 1st |
| 33 | // 1 = lsb 1st |
| 34 | // invsck: |
| 35 | // 0 = normal SCK |
| 36 | // 1 = inverted SCK |
| 37 | // invcsb: |
| 38 | // 0 = normal CSB (active low) |
| 39 | // 1 = inverted CSB (active high) |
| 40 | // mode: |
| 41 | // 0 = read and change data on opposite SCK edges |
| 42 | // 1 = read and change data on the same SCK edge |
| 43 | // enable: |
| 44 | // 0 = disable the SPI master |
| 45 | // 1 = enable the SPI master |
| 46 | // irqena: |
| 47 | // 0 = disable interrupt |
| 48 | // 1 = enable interrupt |
Tim Edwards | 8115320 | 2020-10-09 19:57:04 -0400 | [diff] [blame] | 49 | // hkconn: |
| 50 | // 0 = housekeeping SPI disconnected |
| 51 | // 1 = housekeeping SPI connected (when SPI master enabled) |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 52 | // prescaler: count (in master clock cycles) of 1/2 SCK cycle. |
| 53 | // |
| 54 | // reg_dat_we: |
| 55 | // 1 = data write enable |
| 56 | // reg_dat_re: |
| 57 | // 1 = data read enable |
| 58 | // reg_cfg_*: Signaling for read/write of configuration register |
| 59 | // reg_dat_*: Signaling for read/write of data register |
| 60 | // |
| 61 | // err_out: Indicates attempt to read/write before data ready |
| 62 | // (failure to wait for reg_dat_wait to clear) |
| 63 | // |
| 64 | // Between "mode" and "invsck", all four standard SPI modes are supported |
| 65 | // |
| 66 | //-------------------------------------------------------------------- |
| 67 | |
| 68 | module simple_spi_master_wb #( |
| 69 | parameter BASE_ADR = 32'h2100_0000, |
| 70 | parameter CONFIG = 8'h00, |
| 71 | parameter DATA = 8'h04 |
| 72 | ) ( |
| 73 | input wb_clk_i, |
| 74 | input wb_rst_i, |
| 75 | input [31:0] wb_adr_i, |
| 76 | input [31:0] wb_dat_i, |
| 77 | input [3:0] wb_sel_i, |
| 78 | input wb_we_i, |
| 79 | input wb_cyc_i, |
| 80 | input wb_stb_i, |
| 81 | output wb_ack_o, |
| 82 | output [31:0] wb_dat_o, |
| 83 | |
Tim Edwards | 8115320 | 2020-10-09 19:57:04 -0400 | [diff] [blame] | 84 | output hk_connect, // Connect to housekeeping SPI |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 85 | input sdi, // SPI input |
| 86 | output csb, // SPI chip select |
| 87 | output sck, // SPI clock |
| 88 | output sdo, // SPI output |
Tim Edwards | 44bab47 | 2020-10-04 22:09:54 -0400 | [diff] [blame] | 89 | output sdoenb, // SPI output enable |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 90 | output irq // interrupt output |
| 91 | ); |
| 92 | |
| 93 | wire [31:0] simple_spi_master_reg_cfg_do; |
| 94 | wire [31:0] simple_spi_master_reg_dat_do; |
| 95 | |
| 96 | wire resetn = ~wb_rst_i; |
| 97 | wire valid = wb_stb_i && wb_cyc_i; |
| 98 | wire simple_spi_master_reg_cfg_sel = valid && (wb_adr_i == (BASE_ADR | CONFIG)); |
| 99 | wire simple_spi_master_reg_dat_sel = valid && (wb_adr_i == (BASE_ADR | DATA)); |
| 100 | |
| 101 | wire [1:0] reg_cfg_we = (simple_spi_master_reg_cfg_sel) ? |
| 102 | (wb_sel_i[1:0] & {2{wb_we_i}}): 2'b00; |
| 103 | wire reg_dat_we = (simple_spi_master_reg_dat_sel) ? (wb_sel_i[0] & wb_we_i): 1'b0; |
| 104 | |
| 105 | wire [31:0] mem_wdata = wb_dat_i; |
| 106 | wire reg_dat_re = simple_spi_master_reg_dat_sel && !wb_sel_i && ~wb_we_i; |
| 107 | |
| 108 | assign wb_dat_o = (simple_spi_master_reg_cfg_sel) ? simple_spi_master_reg_cfg_do : |
| 109 | simple_spi_master_reg_dat_do; |
| 110 | assign wb_ack_o = (simple_spi_master_reg_cfg_sel || simple_spi_master_reg_dat_sel) |
| 111 | && (!reg_dat_wait); |
| 112 | |
| 113 | simple_spi_master spi_master ( |
| 114 | .resetn(resetn), |
| 115 | .clk(wb_clk_i), |
| 116 | .reg_cfg_we(reg_cfg_we), |
| 117 | .reg_cfg_di(mem_wdata), |
| 118 | .reg_cfg_do(simple_spi_master_reg_cfg_do), |
| 119 | .reg_dat_we(reg_dat_we), |
| 120 | .reg_dat_re(reg_dat_re), |
| 121 | .reg_dat_di(mem_wdata), |
| 122 | .reg_dat_do(simple_spi_master_reg_dat_do), |
| 123 | .reg_dat_wait(reg_dat_wait), |
| 124 | |
Tim Edwards | 8115320 | 2020-10-09 19:57:04 -0400 | [diff] [blame] | 125 | .hk_connect(hk_connect), // Attach to housekeeping SPI slave |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 126 | .sdi(sdi), // SPI input |
| 127 | .csb(csb), // SPI chip select |
| 128 | .sck(sck), // SPI clock |
| 129 | .sdo(sdo), // SPI output |
| 130 | .irq_out(irq) // interrupt |
| 131 | ); |
| 132 | endmodule |
| 133 | |
| 134 | module simple_spi_master ( |
| 135 | input resetn, |
| 136 | input clk, // master clock (assume 100MHz) |
| 137 | |
| 138 | input [1:0] reg_cfg_we, |
| 139 | input [31:0] reg_cfg_di, |
| 140 | output [31:0] reg_cfg_do, |
| 141 | |
| 142 | input reg_dat_we, |
| 143 | input reg_dat_re, |
| 144 | input [31:0] reg_dat_di, |
| 145 | output [31:0] reg_dat_do, |
| 146 | output reg_dat_wait, |
| 147 | output irq_out, |
| 148 | output err_out, |
| 149 | |
Tim Edwards | 8115320 | 2020-10-09 19:57:04 -0400 | [diff] [blame] | 150 | output hk_connect, // Connect to housekeeping SPI |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 151 | input sdi, // SPI input |
| 152 | output csb, // SPI chip select |
| 153 | output sck, // SPI clock |
| 154 | output sdo // SPI output |
| 155 | ); |
| 156 | |
| 157 | parameter IDLE = 2'b00; |
| 158 | parameter SENDL = 2'b01; |
| 159 | parameter SENDH = 2'b10; |
| 160 | parameter FINISH = 2'b11; |
| 161 | |
| 162 | reg done; |
| 163 | reg isdo, hsck, icsb; |
| 164 | reg [1:0] state; |
| 165 | reg isck; |
| 166 | reg err_out; |
| 167 | |
| 168 | reg [7:0] treg, rreg, d_latched; |
| 169 | reg [2:0] nbit; |
| 170 | |
| 171 | reg [7:0] prescaler; |
| 172 | reg [7:0] count; |
| 173 | reg invsck; |
| 174 | reg invcsb; |
| 175 | reg mlb; |
| 176 | reg irqena; |
| 177 | reg stream; |
| 178 | reg mode; |
| 179 | reg enable; |
Tim Edwards | 8115320 | 2020-10-09 19:57:04 -0400 | [diff] [blame] | 180 | reg hkconn; |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 181 | |
| 182 | wire csb; |
| 183 | wire irq_out; |
| 184 | wire sck; |
| 185 | wire sdo; |
Tim Edwards | 44bab47 | 2020-10-04 22:09:54 -0400 | [diff] [blame] | 186 | wire sdoenb; |
Tim Edwards | 8115320 | 2020-10-09 19:57:04 -0400 | [diff] [blame] | 187 | wire hk_connect; |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 188 | |
| 189 | // Define behavior for inverted SCK and inverted CSB |
Tim Edwards | 44bab47 | 2020-10-04 22:09:54 -0400 | [diff] [blame] | 190 | assign csb = (enable == 1'b0) ? 1'bz : (invcsb) ? ~icsb : icsb; |
| 191 | assign sck = (enable == 1'b0) ? 1'bz : (invsck) ? ~isck : isck; |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 192 | |
| 193 | // No bidirectional 3-pin mode defined, so SDO is enabled whenever CSB is low. |
Tim Edwards | 44bab47 | 2020-10-04 22:09:54 -0400 | [diff] [blame] | 194 | assign sdoenb = icsb; |
| 195 | // assign sdo = (enable == 1'b0) ? 1'bz : icsb ? 1'bz : isdo; |
Tim Edwards | ca2f318 | 2020-10-06 10:05:11 -0400 | [diff] [blame] | 196 | assign sdo = (enable == 1'b0) ? 1'bz : isdo; |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 197 | |
| 198 | assign irq_out = irqena & done; |
Tim Edwards | 8115320 | 2020-10-09 19:57:04 -0400 | [diff] [blame] | 199 | assign hk_connect = (enable == 1'b1) ? hkconn : 1'b0; |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 200 | |
| 201 | // Read configuration and data registers |
Tim Edwards | 8115320 | 2020-10-09 19:57:04 -0400 | [diff] [blame] | 202 | assign reg_cfg_do = {16'd0, hkconn, irqena, enable, stream, mode, |
| 203 | invsck, invcsb, mlb, prescaler}; |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 204 | assign reg_dat_wait = ~done; |
| 205 | assign reg_dat_do = done ? rreg : ~0; |
| 206 | |
| 207 | // Write configuration register |
| 208 | always @(posedge clk or negedge resetn) begin |
| 209 | if (resetn == 1'b0) begin |
| 210 | prescaler <= 8'd2; |
| 211 | invcsb <= 1'b0; |
| 212 | invsck <= 1'b0; |
| 213 | mlb <= 1'b0; |
| 214 | enable <= 1'b0; |
| 215 | irqena <= 1'b0; |
| 216 | stream <= 1'b0; |
| 217 | mode <= 1'b0; |
Tim Edwards | 8115320 | 2020-10-09 19:57:04 -0400 | [diff] [blame] | 218 | hkconn <= 1'b0; |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 219 | end else begin |
| 220 | if (reg_cfg_we[0]) prescaler <= reg_cfg_di[7:0]; |
| 221 | if (reg_cfg_we[1]) begin |
| 222 | mlb <= reg_cfg_di[8]; |
| 223 | invcsb <= reg_cfg_di[9]; |
| 224 | invsck <= reg_cfg_di[10]; |
| 225 | mode <= reg_cfg_di[11]; |
| 226 | stream <= reg_cfg_di[12]; |
| 227 | enable <= reg_cfg_di[13]; |
| 228 | irqena <= reg_cfg_di[14]; |
Tim Edwards | 8115320 | 2020-10-09 19:57:04 -0400 | [diff] [blame] | 229 | hkconn <= reg_cfg_di[15]; |
Tim Edwards | 04ba17f | 2020-10-02 22:27:50 -0400 | [diff] [blame] | 230 | end //reg_cfg_we[1] |
| 231 | end //resetn |
| 232 | end //always |
| 233 | |
| 234 | // Watch for read and write enables on clk, not hsck, so as not to |
| 235 | // miss them. |
| 236 | |
| 237 | reg w_latched, r_latched; |
| 238 | |
| 239 | always @(posedge clk or negedge resetn) begin |
| 240 | if (resetn == 1'b0) begin |
| 241 | err_out <= 1'b0; |
| 242 | w_latched <= 1'b0; |
| 243 | r_latched <= 1'b0; |
| 244 | d_latched <= 8'd0; |
| 245 | end else begin |
| 246 | // Clear latches on SEND, otherwise latch when seen |
| 247 | if (state == SENDL || state == SENDH) begin |
| 248 | if (reg_dat_we == 1'b0) begin |
| 249 | w_latched <= 1'b0; |
| 250 | end |
| 251 | end else begin |
| 252 | if (reg_dat_we == 1'b1) begin |
| 253 | if (done == 1'b0 && w_latched == 1'b1) begin |
| 254 | err_out <= 1'b1; |
| 255 | end else begin |
| 256 | w_latched <= 1'b1; |
| 257 | d_latched <= reg_dat_di[7:0]; |
| 258 | err_out <= 1'b0; |
| 259 | end |
| 260 | end |
| 261 | end |
| 262 | |
| 263 | if (reg_dat_re == 1'b1) begin |
| 264 | if (r_latched == 1'b1) begin |
| 265 | r_latched <= 1'b0; |
| 266 | end else begin |
| 267 | err_out <= 1'b1; // byte not available |
| 268 | end |
| 269 | end else if (state == FINISH) begin |
| 270 | r_latched <= 1'b1; |
| 271 | end if (state == SENDL || state == SENDH) begin |
| 272 | if (r_latched == 1'b1) begin |
| 273 | err_out <= 1'b1; // last byte was never read |
| 274 | end else begin |
| 275 | r_latched <= 1'b0; |
| 276 | end |
| 277 | end |
| 278 | end |
| 279 | end |
| 280 | |
| 281 | // State transition. |
| 282 | |
| 283 | always @(posedge hsck or negedge resetn) begin |
| 284 | if (resetn == 1'b0) begin |
| 285 | state <= IDLE; |
| 286 | nbit <= 3'd0; |
| 287 | icsb <= 1'b1; |
| 288 | done <= 1'b1; |
| 289 | end else begin |
| 290 | if (state == IDLE) begin |
| 291 | if (w_latched == 1'b1) begin |
| 292 | state <= SENDL; |
| 293 | nbit <= 3'd0; |
| 294 | icsb <= 1'b0; |
| 295 | done <= 1'b0; |
| 296 | end else begin |
| 297 | icsb <= ~stream; |
| 298 | end |
| 299 | end else if (state == SENDL) begin |
| 300 | state <= SENDH; |
| 301 | end else if (state == SENDH) begin |
| 302 | nbit <= nbit + 1; |
| 303 | if (nbit == 3'd7) begin |
| 304 | state <= FINISH; |
| 305 | end else begin |
| 306 | state <= SENDL; |
| 307 | end |
| 308 | end else if (state == FINISH) begin |
| 309 | icsb <= ~stream; |
| 310 | done <= 1'b1; |
| 311 | state <= IDLE; |
| 312 | end |
| 313 | end |
| 314 | end |
| 315 | |
| 316 | // Set up internal clock. The enable bit gates the internal clock |
| 317 | // to shut down the master SPI when disabled. |
| 318 | |
| 319 | always @(posedge clk or negedge resetn) begin |
| 320 | if (resetn == 1'b0) begin |
| 321 | count <= 8'd0; |
| 322 | hsck <= 1'b0; |
| 323 | end else begin |
| 324 | if (enable == 1'b0) begin |
| 325 | count <= 8'd0; |
| 326 | end else begin |
| 327 | count <= count + 1; |
| 328 | if (count == prescaler) begin |
| 329 | hsck <= ~hsck; |
| 330 | count <= 8'd0; |
| 331 | end // count |
| 332 | end // enable |
| 333 | end // resetn |
| 334 | end // always |
| 335 | |
| 336 | // sck is half the rate of hsck |
| 337 | |
| 338 | always @(posedge hsck or negedge resetn) begin |
| 339 | if (resetn == 1'b0) begin |
| 340 | isck <= 1'b0; |
| 341 | end else begin |
| 342 | if (state == IDLE || state == FINISH) |
| 343 | isck <= 1'b0; |
| 344 | else |
| 345 | isck <= ~isck; |
| 346 | end // resetn |
| 347 | end // always |
| 348 | |
| 349 | // Main procedure: read, write, shift data |
| 350 | |
| 351 | always @(posedge hsck or negedge resetn) begin |
| 352 | if (resetn == 1'b0) begin |
| 353 | rreg <= 8'hff; |
| 354 | treg <= 8'hff; |
| 355 | isdo <= 1'b0; |
| 356 | end else begin |
| 357 | if (isck == 1'b0 && (state == SENDL || state == SENDH)) begin |
| 358 | if (mlb == 1'b1) begin |
| 359 | // LSB first, sdi@msb -> right shift |
| 360 | rreg <= {sdi, rreg[7:1]}; |
| 361 | end else begin |
| 362 | // MSB first, sdi@lsb -> left shift |
| 363 | rreg <= {rreg[6:0], sdi}; |
| 364 | end |
| 365 | end // read on ~isck |
| 366 | |
| 367 | if (w_latched == 1'b1) begin |
| 368 | if (mlb == 1'b1) begin |
| 369 | treg <= {1'b1, d_latched[7:1]}; |
| 370 | isdo <= d_latched[0]; |
| 371 | end else begin |
| 372 | treg <= {d_latched[6:0], 1'b1}; |
| 373 | isdo <= d_latched[7]; |
| 374 | end // mlb |
| 375 | end else if ((mode ^ isck) == 1'b1) begin |
| 376 | if (mlb == 1'b1) begin |
| 377 | // LSB first, shift right |
| 378 | treg <= {1'b1, treg[7:1]}; |
| 379 | isdo <= treg[0]; |
| 380 | end else begin |
| 381 | // MSB first shift LEFT |
| 382 | treg <= {treg[6:0], 1'b1}; |
| 383 | isdo <= treg[7]; |
| 384 | end // mlb |
| 385 | end // write on mode ^ isck |
| 386 | end // resetn |
| 387 | end // always |
| 388 | |
| 389 | endmodule |