SPI Slave Clock Signal – Issues with FPGA PMOD Pin

clockdigital-logicfpgaspiverilog

I have implemented a dummy SPI slave device within an FPGA (Basys 3). The master device is in an MCU.

I'm trying to connect the clock signal generated by the master (MCU) to the slave clock pin (a PMOD pin in the FPGA). However, it seems that Vivado doesn't allow to provide clock signal as an input, and it stops in the implementation step. Unless I comment out this PMOD assignment, in which case it can finish the impl. step.

The error I'm getting is:

[Place 30-574] Poor placement for routing between an IO pin and BUFG. If this sub optimal condition is acceptable for this design, you may use the CLOCK_DEDICATED_ROUTE constraint in the .xdc file to demote this message to a WARNING. However, the use of this override is highly discouraged. These examples can be used directly in the .xdc file to override this clock rule.
    < set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets JA_IBUF[3]] >

    JA_IBUF[3]_inst (IBUF.O) is locked to IOB_X1Y97
     and JA_IBUF_BUFG[3]_inst (BUFG.I) is provisionally placed by clockplacer on BUFGCTRL_X0Y31

and it's caused because I'm using JA[3] (Pmod pin) as an input clock signal for the SPI slave device.

I know there is a number of ways of fixing this, like generating a fast clock signal within the FPGA and pass the generated MCU clock signal through an edge detector, but do I really need to do this? (I have tried this but breaks my design)

I have read that there should be some pins that are valid for routing clock signals, however I haven't found which ones are in Basys 3.

Is there any way to provide a clock signal as an input to an FPGA?

I have tried to do what the error message says, that is, adding:

set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets JA_IBUF[3]]

to the constraints file. This works, but the error message 'highly discourages' doing it. Is there any 'good' way of doing this?

Best Answer

Depending on the speed of your SPI clock, a typical solution to this is to use oversampling and filtering of the incoming SPI clock signal using a higher speed clock already available in the FPGA (e.g. fed in from dedicated clock pin).

The idea is that rather than using the SPI clock as a clock for your SPI shift register logic, you instead use a high speed clock (e.g. 50MHz) to drive everything. The SPI clock signal is fed first through a multi-flop synchroniser (e.g. 2 DFFs driven by the sample clock), then goes into an edge detector to see when it transitions from low-to-high or high-to-low. The edge detector output is then used as a clock enable for the shift register. You can also add a filter as part of the edge detection - e.g. only detect changes after three consecutive samples:

// Synchroniser (do the same for MOSI and SS)
reg [1:0] sclk_sync;
always @ (posedge clock50) begin
   sclk_sync <= {sclk_sync[0], sclk};
end
// Optional Filter - avoids glitches caused by noise/etc.
reg [2:0] sclk_dly;
reg sclk_filt;
always @ (posede clock50 or posedge reset) begin
   if (reset) begin
       sclk_dly <= 3'b0;
       sclk_filt <= 1'b0;
   end else begin
       sclk_dly <= {sclk_dly[1:0],sclk_sync[1]};
       if (&sclk_dly) begin
           sclk_filt <= 1'b1; //Set high if sclk high for 3 clock cycles
       end else if (~|sclk_dly) begin
           sclk_filt <= 1'b0; //Set low if sclk low for 3 clock cycles
       end
   end
end
// Edge detect
reg sclk_old;
wire sclk_rise;
wire sclk_fall;
assign sclk_rise = (!sclk_old & sclk_filt); // Rising edge if was low and is now high
assign sclk_fall = (sclk_old & !sclk_filt); // Falling edge if was high and now low
always @ (posedge clock50 or posedge reset) begin
    if (reset) begin
        sclk_old <= 1'b0;
    end else begin
        sclk_old <= sclk_filt;
    end
end

// MOSI/MISO logic
always @ (posedge clock50 or posedge reset) begin
   if (reset) begin
       miso <= 1'b1;
   end else begin
       // Clock out MISO on falling edge?
       if (sclk_fall) begin
           miso <= shiftReg[end];
       end
       // Clock in MOSI on rising edge?
       if (sclk_rise) begin
           shiftReg <= {shiftReg[end:1], mosi_sync[1]};
       end
   end
end

By doing this your SPI clock is no longer treated as a clock by the FPGA tools - it can instead be treated as a data line. In addition it means the rest of the logic the SPI slave connects to (e.g. memories, registers, status flags, etc.) can run on the existing high speed clock without the need for any clock domain crossing. It also means you can have a state machine that can act on both rising and falling edge of the SPI clock (e.g. read value of MOSI on rising edge, update MISO on falling edge) without needing multi-edge sensitivity (which most FPGAs can't do).