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