Ready-to-run snippets

Code Examples

Ready-to-run Verilog, SystemVerilog, and UVM examples. Click any to open it live in the IDE.

← All examples

SPI with two slaves

Verilog Bus Protocols

One SPI master and two slaves sharing SCLK, MOSI and a tri-state MISO, with a chip select each.

Open in IDE
design.v
// SPI master: one byte per transfer, MSB first, any of the four modes.
//   CLK_DIV  system clocks per half SCLK period
//   CPOL     SCLK level when idle
//   CPHA     0: sample on the leading edge, change on the trailing edge
//            1: change on the leading edge, sample on the trailing edge
// cs_req holds the chip select active across several bytes.
module spi_master #(
  parameter CLK_DIV = 4,
  parameter CPOL    = 0,
  parameter CPHA    = 0
) (
  input            clk,
  input            rst,
  input            start,              // begin a byte transfer (when not busy)
  input      [7:0] tx_byte,
  input            cs_req,             // 1: keep chip select active
  output reg       busy,
  output reg       done,               // one-cycle pulse: rx_byte is valid
  output reg [7:0] rx_byte,
  output reg       sclk,
  output reg       mosi,
  input            miso,
  output           cs_n
);
  reg [15:0] div;
  reg [4:0]  edges;                    // SCLK edges so far in this byte (16 per byte)
  reg [7:0]  tx_sh;
  reg [7:0]  rx_sh;

  assign cs_n = !cs_req;

  always @(posedge clk) begin
    if (rst) begin
      busy <= 1'b0;
      done <= 1'b0;
      sclk <= CPOL;
      mosi <= 1'b0;
    end
    else begin
      done <= 1'b0;
      if (!busy) begin
        sclk <= CPOL;
        if (start) begin
          busy  <= 1'b1;
          div   <= 16'd0;
          edges <= 5'd0;
          tx_sh <= tx_byte;
          if (CPHA == 0)
            mosi <= tx_byte[7];        // the first bit must be ready before the first edge
        end
      end
      else if (div == CLK_DIV - 1) begin
        div <= 16'd0;
        if (edges == 5'd16) begin      // all 16 edges done
          busy    <= 1'b0;
          done    <= 1'b1;
          rx_byte <= rx_sh;
        end
        else begin
          sclk  <= ~sclk;
          edges <= edges + 5'd1;
          if (edges[0] == 1'b0) begin  // leading edge
            if (CPHA == 0) begin
              rx_sh <= {rx_sh[6:0], miso};             // sample
            end
            else begin
              mosi  <= tx_sh[7];                       // change
              tx_sh <= {tx_sh[6:0], 1'b0};
            end
          end
          else begin                   // trailing edge
            if (CPHA == 0) begin
              mosi  <= tx_sh[6];                       // change: the next bit
              tx_sh <= {tx_sh[6:0], 1'b0};
            end
            else begin
              rx_sh <= {rx_sh[6:0], miso};             // sample
            end
          end
        end
      end
      else begin
        div <= div + 16'd1;
      end
    end
  end
endmodule

// SPI slave with four 8-bit registers, oversampling SCLK with its own clock.
// A transaction is two bytes with chip select held low:
//   byte 1 from the master: command {rw, 5'b0, addr[1:0]}, rw = 1 for a write
//   byte 1 to the master:   the slave's ID
//   byte 2: write data from the master, or the register value to the master for a read
// MISO is high impedance while the slave is not selected, so several slaves can share it.
module spi_slave #(
  parameter CPOL = 0,
  parameter CPHA = 0,
  parameter ID   = 8'hA5
) (
  input        clk,
  input        rst,
  input        sclk,
  input        mosi,
  output       miso,
  input        cs_n,
  output [7:0] reg0,
  output [7:0] reg1
);
  reg [7:0] regs [0:3];
  reg [2:0] sclk_s;                    // synchronizer plus previous value
  reg [1:0] cs_s;
  reg [1:0] mosi_s;
  reg [2:0] bitc;
  reg       second;                    // receiving the second byte
  reg [7:0] cmd;
  reg [7:0] rx_sh;
  reg [7:0] tx_sh;
  reg       miso_r;
  integer   i;

  assign reg0 = regs[0];
  assign reg1 = regs[1];
  assign miso = cs_s[1] ? 1'bz : miso_r;

  wire rise     = sclk_s[1] && !sclk_s[2];
  wire fall     = !sclk_s[1] && sclk_s[2];
  wire leading  = CPOL ? fall : rise;
  wire trailing = CPOL ? rise : fall;
  wire sample   = CPHA ? trailing : leading;
  wire change   = CPHA ? leading : trailing;
  wire selected = !cs_s[1];
  wire cs_fall  = cs_s[1] && !cs_n;    // chip select is about to become active

  always @(posedge clk) begin
    sclk_s <= {sclk_s[1:0], sclk};
    cs_s   <= {cs_s[0], cs_n};
    mosi_s <= {mosi_s[0], mosi};
  end

  always @(posedge clk) begin
    if (rst) begin
      for (i = 0; i < 4; i = i + 1)
        regs[i] <= 8'h00;
      miso_r <= 1'b0;
    end
    else if (cs_fall) begin
      bitc   <= 3'd0;
      second <= 1'b0;
      if (CPHA == 0) begin
        miso_r <= ID[7];               // the first bit is on MISO before the first edge
        tx_sh  <= {ID[6:0], 1'b0};
      end
      else begin
        tx_sh <= ID;
      end
    end
    else if (selected) begin
      if (sample) begin
        rx_sh <= {rx_sh[6:0], mosi_s[1]};
        bitc  <= bitc + 3'd1;
        if (bitc == 3'd7) begin        // a whole byte has arrived
          if (!second) begin
            cmd    <= {rx_sh[6:0], mosi_s[1]};
            second <= 1'b1;
            tx_sh  <= regs[{rx_sh[0], mosi_s[1]}];   // the reply for a read
          end
          else if (cmd[7]) begin
            regs[cmd[1:0]] <= {rx_sh[6:0], mosi_s[1]};
          end
        end
      end
      if (change) begin
        miso_r <= tx_sh[7];
        tx_sh  <= {tx_sh[6:0], 1'b0};
      end
    end
  end
endmodule
testbench.v
// Testbench: one SPI master, two SPI slaves sharing SCLK, MOSI and MISO, one chip select each
module tb;
  parameter CPOL = 0;
  parameter CPHA = 0;

  reg        clk     = 0;
  reg        rst     = 1;
  reg        start   = 0;
  reg  [7:0] tx_byte = 0;
  reg        cs_req  = 0;
  wire       busy;
  wire       done;
  wire [7:0] rx_byte;
  wire       sclk;
  wire       mosi;
  tri        miso;                       // shared: only the selected slave drives it
  wire       cs_n;
  reg  [7:0] r;
  reg        sel = 0;                    // which slave the next transaction is for
  wire       cs0_n = cs_n || sel != 0;
  wire       cs1_n = cs_n || sel != 1;

  spi_master #(
    .CLK_DIV (4),
    .CPOL    (CPOL),
    .CPHA    (CPHA)
  ) master (
    .clk     (clk),
    .rst     (rst),
    .start   (start),
    .tx_byte (tx_byte),
    .cs_req  (cs_req),
    .busy    (busy),
    .done    (done),
    .rx_byte (rx_byte),
    .sclk    (sclk),
    .mosi    (mosi),
    .miso    (miso),
    .cs_n    (cs_n)
  );

  spi_slave #(
    .CPOL (CPOL),
    .CPHA (CPHA),
    .ID   (8'hA5)
  ) slave0 (
    .clk  (clk),
    .rst  (rst),
    .sclk (sclk),
    .mosi (mosi),
    .miso (miso),
    .cs_n (cs0_n),
    .reg0 (),
    .reg1 ()
  );

  spi_slave #(
    .CPOL (CPOL),
    .CPHA (CPHA),
    .ID   (8'h5A)
  ) slave1 (
    .clk  (clk),
    .rst  (rst),
    .sclk (sclk),
    .mosi (mosi),
    .miso (miso),
    .cs_n (cs1_n),
    .reg0 (),
    .reg1 ()
  );

  always #5 clk = ~clk;

  // Send one byte and return the byte received at the same time
  task xfer(input [7:0] b, output [7:0] got);
    begin
      @(posedge clk);
      tx_byte <= b;
      start   <= 1;
      @(posedge clk);
      start   <= 0;
      @(posedge done);
      got = rx_byte;
    end
  endtask

  // One transaction: command byte and data byte with chip select held low
  task access(input rw, input [1:0] addr, input [7:0] wdata);
    reg [7:0] id;
    reg [7:0] rdata;
    begin
      cs_req <= 1;
      repeat (4) @(posedge clk);
      xfer({rw, 5'b0, addr}, id);
      xfer(rw ? wdata : 8'h00, rdata);
      repeat (2) @(posedge clk);
      cs_req <= 0;
      repeat (6) @(posedge clk);
      if (rw)
        $display("%0t  slave %0d: write reg%0d = 0x%h   (ID 0x%h)", $time, sel, addr, wdata, id);
      else
        $display("%0t  slave %0d: read  reg%0d = 0x%h   (ID 0x%h)", $time, sel, addr, rdata, id);
    end
  endtask

  initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, tb);
    #22 rst = 0;
    $display("SPI mode %0d (CPOL=%0d, CPHA=%0d)", CPOL * 2 + CPHA, CPOL, CPHA);
    sel = 0;
    access(1, 2'd1, 8'h11);
    sel = 1;
    access(1, 2'd1, 8'h22);
    sel = 0;
    access(0, 2'd1, 8'h00);
    sel = 1;
    access(0, 2'd1, 8'h00);
    #50 $finish;
  end
endmodule
Admin mode — edit and delete controls are visible on each row. Use the IDE to add new snippets via File → Save as Snippet.