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

Quad SPI flash read

Verilog Bus Protocols

A quad SPI master reads four bytes from a flash model with the Quad Output Fast Read command 0x6B.

Open in IDE
design.v
// Quad SPI read master (mode 0): sends the Quad Output Fast Read command 0x6B and a 24-bit address
// on IO0, waits 8 dummy clocks, then reads NBYTES bytes four bits per clock on IO[3:0].
//   CLK_DIV  system clocks per half SCLK period
module qspi_read #(
  parameter CLK_DIV = 2,
  parameter NBYTES  = 4
) (
  input            clk,
  input            rst,
  input            start,
  input     [23:0] addr,
  output reg       busy,
  output reg       byte_valid,          // one-cycle pulse per received byte
  output reg [7:0] rdata,
  output reg       sclk,
  output reg       cs_n,
  output     [3:0] io_o,
  output     [3:0] io_oe,                // 1: the master drives that IO line
  input      [3:0] io_i
);
  reg [15:0] div;
  reg [7:0]  rises;                      // rising SCLK edges so far
  reg [31:0] out_sh;                     // command and address, MSB first
  reg        out_en;
  reg [3:0]  hi_nib;
  reg        have_hi;

  assign io_o  = {3'b000, out_sh[31]};
  assign io_oe = {3'b000, out_en};

  always @(posedge clk) begin
    if (rst) begin
      busy       <= 1'b0;
      cs_n       <= 1'b1;
      sclk       <= 1'b0;
      out_en     <= 1'b0;
      byte_valid <= 1'b0;
    end
    else begin
      byte_valid <= 1'b0;
      if (!busy) begin
        if (start) begin
          busy    <= 1'b1;
          cs_n    <= 1'b0;
          div     <= 16'd0;
          rises   <= 8'd0;
          out_sh  <= {8'h6B, addr};      // the first bit is on IO0 before the first edge
          out_en  <= 1'b1;
          have_hi <= 1'b0;
        end
      end
      else if (div == CLK_DIV - 1) begin
        div <= 16'd0;
        if (rises == 40 + 2 * NBYTES && !sclk) begin
          busy <= 1'b0;                  // all nibbles read: end the transaction
          cs_n <= 1'b1;
        end
        else if (!sclk) begin            // rising edge: the flash samples IO0, we sample data nibbles
          sclk  <= 1'b1;
          rises <= rises + 8'd1;
          if (rises >= 40) begin
            if (!have_hi) begin
              hi_nib  <= io_i;           // the high nibble comes first
              have_hi <= 1'b1;
            end
            else begin
              rdata      <= {hi_nib, io_i};
              byte_valid <= 1'b1;
              have_hi    <= 1'b0;
            end
          end
        end
        else begin                       // falling edge: put the next command or address bit on IO0
          sclk <= 1'b0;
          if (rises < 32)
            out_sh <= {out_sh[30:0], 1'b0};
          if (rises == 32)
            out_en <= 1'b0;              // command and address sent: release IO0
        end
      end
      else begin
        div <= div + 16'd1;
      end
    end
  end
endmodule
testbench.v
// Testbench: a quad SPI master reads 4 bytes from a flash model with the Quad Output Fast Read command
module tb;
  reg         clk   = 0;
  reg         rst   = 1;
  reg         start = 0;
  reg  [23:0] addr  = 24'h000120;
  wire        busy;
  wire        byte_valid;
  wire [7:0]  rdata;
  wire        sclk;
  wire        cs_n;
  wire [3:0]  m_o;
  wire [3:0]  m_oe;
  wire [3:0]  io;                        // the four shared IO lines

  qspi_read #(
    .CLK_DIV (2),
    .NBYTES  (4)
  ) master (
    .clk        (clk),
    .rst        (rst),
    .start      (start),
    .addr       (addr),
    .busy       (busy),
    .byte_valid (byte_valid),
    .rdata      (rdata),
    .sclk       (sclk),
    .cs_n       (cs_n),
    .io_o       (m_o),
    .io_oe      (m_oe),
    .io_i       (io)
  );

  // ---- A behavioral flash model: byte at address a holds a[7:0] + 8'h40 ----
  reg  [7:0]  f_rises = 0;
  reg  [31:0] f_in;                      // command and address as received
  reg  [3:0]  f_o;
  reg         f_oe = 0;
  reg  [23:0] f_addr;

  genvar k;
  generate
    for (k = 0; k < 4; k = k + 1) begin : line
      assign io[k] = m_oe[k] ? m_o[k] : (f_oe ? f_o[k] : 1'bz);
    end
  endgenerate

  always @(negedge cs_n) begin
    f_rises = 0;
    f_oe    = 0;
  end

  always @(posedge sclk) begin
    if (!cs_n) begin
      f_rises = f_rises + 1;
      if (f_rises <= 32)
        f_in = {f_in[30:0], io[0]};      // command and address arrive on IO0
      if (f_rises == 32) begin
        f_addr = f_in[23:0];
        $display("%0t  flash: command 0x%h, address 0x%h", $time, f_in[31:24], f_in[23:0]);
      end
    end
  end

  always @(negedge sclk) begin
    if (!cs_n && f_rises >= 40) begin    // after 8 dummy clocks: drive a nibble per clock
      f_oe = 1;
      if (((f_rises - 40) % 2) == 0)
        f_o = (f_addr[7:0] + 8'h40) >> 4;              // high nibble first
      else begin
        f_o    = (f_addr[7:0] + 8'h40) & 8'h0F;
        f_addr = f_addr + 1;
      end
    end
  end

  always @(posedge cs_n)
    f_oe = 0;

  always #5 clk = ~clk;

  always @(posedge clk)
    if (byte_valid)
      $display("%0t  master read 0x%h", $time, rdata);

  initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, tb);
    #22 rst = 0;
    @(posedge clk);
    start <= 1;
    @(posedge clk);
    start <= 0;
    @(negedge busy);
    #40 $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.