Basic Verilog & SPI Question


#1

Hi All,

I’ve been playing with implementing a very basic SPI slave based on the fpga4fun example. I am trying to extend it ever so slightly so that it echoes back the byte it receives. I’m fighting with an issue I don’t fully understand - the “bytes received” counter works well, but my certainly amateur attempt to just echo the last byte received doesn’t seem to work.

In an attempt to simplify things, I just hardcoded the “echo” value in Verilog to 0x55 (to at least demonstrate to myself that I can send two bytes in a row!). I’m listening with a Teensy 4.0 that just prints what it hears on the SPI bus to serial twice a second. What I see running right now (e.g. with the exact code below) is an incrementing count of bytes received (up to 0xFF), and then 0 - so the total output looks like

0x01 0x00
0x02 0x00
...
0xFE 0x00
0xFF 0x00

The top module is just the basic blink demo, with this SPI module instantiated and then the pins adjusted in pins.pcf to point to the SPI bus on the Teensy, plus a slave select pin. For the time being, I’m not using the RST input - I had been using it to try to debug things, but decided simpler was better. I’ve been banging my head against a wall for a while, and figure this is my extreme inexperience with Verilog and FPGA thinking that I can’t get past. Any help would be great!

Here’s my SPI module code:

module SPI_slave(
    input clk,
    input RST,
    input SCK,
    input MOSI,
    output MISO,
    output SSEL);

  reg [2:0] SCK_sync;
  reg [2:0] SSEL_sync;
  reg [1:0] MOSI_sync;
  reg [1:0] RESET_sync;


  //Synchronize the signals to the FPGA internal clock
  //Also set up some triggers for rising and falling edges
  always @ ( posedge clk ) begin
    SCK_sync <= {SCK_sync[1:0], SCK};
    SSEL_sync <= {SSEL_sync[1:0], SSEL};
    MOSI_sync <= {MOSI_sync[0], MOSI};
    RESET_sync <= {RESET_sync[0], RST};
  end

  wire SCK_re = (SCK_sync[2:1] == 2'b01);
  wire SCK_fe = (SCK_sync[2:1] == 2'b10);

  wire SSEL_re = (SSEL_sync[2:1] == 2'b01);
  wire SSEL_fe = (SSEL_sync[2:1] == 2'b10);
  wire SSEL_on = ~SSEL_sync[1]; //Slave select is active low

  wire MOSI_data = MOSI_sync[1];

  //Variables and registers to store incoming and outgoing data
  reg [2:0] bit_count;
  reg byte_received;
  reg [7:0] incoming_data;

  reg [7:0] outgoing_data;
  reg [7:0] byte_count;


//Incoming DATA
always @ ( posedge clk ) begin
  if(~SSEL_on) begin
    //Slave select not asserted. Set bit count to 0 and await bits.
    bit_count <= 3'b000;
  end else begin
    if (SCK_re) begin
      bit_count <= bit_count + 3'b001;
      incoming_data <= {incoming_data[6:0], MOSI_data};
    end
  end

  byte_received <= SSEL_on && SCK_re && (bit_count == 3'b111);
end

//Outgoing DATA
always @ ( posedge clk ) begin
  if(SSEL_on) begin
    if(SSEL_fe) begin
      byte_count <= byte_count + 1;
      outgoing_data <= byte_count;
    end else begin
      if (SCK_fe) begin
        if (byte_received) begin
          outgoing_data <= 8'h55;
        end else begin
          outgoing_data <= outgoing_data << 1;
        end
      end
    end
  end
end

//MISO should always equal the MSB of outgoing data. It'll be updated on every
//SCK falling edge.
assign MISO = outgoing_data[7];

endmodule

#2

Hi stevetronics,

I only quickly looked at your code, but one thing popped out: should SSEL be an input? Your code currently declares it to be an output.


#3
 //Synchronize the signals to the FPGA internal clock
  //Also set up some triggers for rising and falling edges
  always @ ( posedge clk ) begin
    SCK_sync <= {SCK_sync[1:0], SCK};
    SSEL_sync <= {SSEL_sync[1:0], SSEL};
    MOSI_sync <= {MOSI_sync[0], MOSI};
    RESET_sync <= {RESET_sync[0], RST};
  end

  wire SCK_re = (SCK_sync[2:1] == 2'b01);
  wire SCK_fe = (SCK_sync[2:1] == 2'b10);

The first part of this is OK, you are crossing clock domains so you need to synchronise to the clock in the fpga domain. But the 2nd part to generate the rising and falling edge signals, you are using the buffers that may still be metastable. I would expect some unpredictable problems here.

Better to have another set of registers that the synchronised signals are fed into, and use those for the RE, FE signals.

Also, you didn’t post a trace, so I assume you are not simulating this. I would definitely get that setup to help you see what’s going on.