• 0
Sign in to follow this  
Tickstart

SPI - Arduino to Basys 3

Question

Arduino is the SPI Master and therefore provides the clock, SPICLK through a PMOD. How do I receive the clock in a good way on the FPGA?

Vivado does not approve of checking rising_edge(SPICLK) so I though I'd put a clock buffer or something in between (not that I know why or what they do but it sounds like a good idea). At some point Vivado told me to add "set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {SPICLK_IBUF}]" to the constraints file, but I still got warnings and it didn't recommend I proceed.

 

If I have the top level SPICLK connected to an IBUF_IBUFDISABLE with the disable line connected to the slave select (SS) line, I get this warning:

[DRC 23-20] Rule violation (CKLD-2) Clock Net has IO Driver, not a Clock Buf, and/or non-Clock loads - Clock net spi_buf is directly driven by an IO rather than a Clock Buffer or may be an IO driving a mix of Clock Buffer and non-Clock loads. This connectivity should be reviewed and corrected as appropriate. Driver(s): IBUF_IBUFDISABLE_inst/O
 

If I have the top level SPICLK connected to an IBUF_IBUFDISABLE and that into a BUFGCE, with the disable line connected to the slave select (SS) line and the inverse of SS into the CE, I get this warning:

[Place 30-574] Poor placement for routing between an IO pin and BUFG. This is normally an ERROR but the CLOCK_DEDICATED_ROUTE constraint is set to FALSE allowing your design to continue. The use of this override is highly discouraged as it may lead to very poor timing results. It is recommended that this error condition be corrected in the design.

    IBUF_IBUFDISABLE_inst (IBUF_IBUFDISABLE.O) is locked to IOB_X0Y25
     and BUFGCE_inst (BUFGCTRL.I0) is provisionally placed by clockplacer on BUFGCTRL_X0Y1

Roughly the same warning was issued with just the BUFGCE.

 

I know there are other ways of polling the input clock from the arduino and treating it as normal signal but I want to do it the "proper" way.

Share this post


Link to post
Share on other sites

33 answers to this question

Recommended Posts

  • 0

@Tickstart,

"Polling the input clock and treating it as a normal signal" is almost exactly what I would do.

There would be at least one difference, though, between that and what I would recommend: You need to take the inputs from the SPI port and apply a circuit to synchronize those inputs to your FPGA system clock.  To do that, register the inputs twice before using them.  So, something like:

reg	[1:0] clk_transfer;
always @(posedge i_clk)
	clk_transfer <= { clk_transfer[0], i_spi_sck };
   
wire	ck_sck; // Our version of the SCK signal, having gone through the clock transfer
assign	ck_sck = clk_transfer[1];

reg	ck_stb;	// True on any positive edge of the clock
always @(posedge i_clk)
	ck_stb <= (clk_tranfer[2:1] == 2'b01);

Something like the above code, that takes two clocks to synchronize the incoming SPI, should always take place whenever an input comes into your design that has not been synchronized to your own clock.  If your are curious as to why, look up metastability.  If you don't do this, you may find that the clock signal you use .. isn't reliable across your FPGA.  (For some logic, it'll act like it was true, for other logic it'll act like it was false, etc.)

With the above code, you can then gate your logic to only run anytime (ck_stb) is high (will only happen for one clock interval per i_spi_sck tick) and (i_spi_cs_n) is low. 

If you know the other lines are stable whenever the clock ticks, you might get away without synchronizing them as well.

But ... that's basically how you do it.

Dan

Share this post


Link to post
Share on other sites
  • 0
1 minute ago, D@n said:

@Tickstart,

"Polling the input clock and treating it as a normal signal" is almost exactly what I would do.

There would be at least one difference, though, between that and what I would recommend: You need to take the inputs from the SPI port and apply a circuit to synchronize those inputs to your FPGA system clock.  To do that, register the inputs twice before using them.  So, something like:


reg	[1:0] clk_transfer;
always @(posedge i_clk)
	clk_transfer <= { clk_transfer[0], i_spi_sck };
   
wire	ck_sck; // Our version of the SCK signal, having gone through the clock transfer
assign	ck_sck = clk_transfer[1];

reg	ck_stb;	// True on any positive edge of the clock
always @(posedge i_clk)
	ck_stb <= (clk_tranfer[2:1] == 2'b01);

Something like the above code, that takes two clocks to synchronize the incoming SPI, should always take place whenever an input comes into your design that has not been synchronized to your own clock.  If your are curious as to why, look up metastability.  If you don't do this, you may find that the clock signal you use .. isn't reliable across your FPGA.  (For some logic, it'll act like it was true, for other logic it'll act like it was false, etc.)

With the above code, you can then gate your logic to only run anytime (ck_stb) is high (will only happen for one clock interval per i_spi_sck tick) and (i_spi_cs_n) is low. 

If you know the other lines are stable whenever the clock ticks, you might get away without synchronizing them as well.

But ... that's basically how you do it.

Dan

So basically a debouncer for the incoming SPI master clock?

Share this post


Link to post
Share on other sites
  • 0

@Tickstart,

It's not a debouncer, but a synchronizer, even though it may have much in common with a debouncer.  Unlike a debouncer, you want the synchronization to take place as fast as possible.  Hence ... only two clocks.  Some folks suggest three, I've never heard anyone suggest more than three--it's all a matter of the reliability you wish to create.

Debouncers often have programmable wait intervals ... synchronizers have only the two clocks.

Dan

Share this post


Link to post
Share on other sites
  • 0
8 minutes ago, D@n said:

@Tickstart,

It's not a debouncer, but a synchronizer, even though it may have much in common with a debouncer.  Unlike a debouncer, you want the synchronization to take place as fast as possible.  Hence ... only two clocks.  Some folks suggest three, I've never heard anyone suggest more than three--it's all a matter of the reliability you wish to create.

Debouncers often have programmable wait intervals ... synchronizers have only the two clocks.

Dan

Do I need to synchronize on the falling edge of the incoming SCLK too (let's say I only care about rising edge)?

Share this post


Link to post
Share on other sites
  • 0

@Tickstart,

In the logic I presented above, the clock was synchronized before I placed the rising edge detection into clk_stb.  If you cared about the falling edge, you could create a ck_falling signal anytime the incoming clk_transfer pipe had 2'b10 in the upper two bits.  Do you *need* to if you don't care about the falling edge?  Not at all.

Oh, one more thing, only transition on your system clock.  You don't want to do any "always @(posedge ck_stb)" stuffs, neither do you want to do any "always @(posedge ck_clock)" or falling edge for that matter.  Keep everything on the same system clock, "always @(posedge i_clk), as I called it in my example.

Dan

Share this post


Link to post
Share on other sites
  • 0

Alright, please give me some input on my super simplistic SPI slave code for the FPGA. It doesn't work, but I don't know if that's due to the Arduino or the Basys 3:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity spi_receiver is
    Port ( clk, SPICLK, MOSI, SS : in STD_LOGIC;
           sw : in STD_LOGIC_VECTOR ( 0 downto 0 );
           MISO : out STD_LOGIC;
           led : out STD_LOGIC_VECTOR ( 15 downto 8 ));
end spi_receiver;

architecture Behavioral of spi_receiver is

component IO_sync is
    Port ( clk, io_in : in STD_LOGIC;
           io_out, io_tick : out STD_LOGIC);
end component;

signal spi_clk, spi_clk_tick, slave_select, mosi_sync : STD_LOGIC;
signal data_reg, led_data : STD_LOGIC_VECTOR ( 7 downto 0 ) := X"00";

begin

    SPICLK_SYNC: IO_sync port map(clk => clk, io_in => SPICLK, io_out => open, io_tick => spi_clk_tick);
    SS_SYNC: IO_sync port map(clk => clk, io_in => SS, io_out => slave_select);
    MISO_SYNC: IO_sync port map(clk => clk, io_in => MOSI, io_out => mosi_sync);

    spi_clk <= '0' when slave_select = '1' else spi_clk_tick;
    
    TRANSCIEVE: process(clk)
    begin
        if rising_edge(clk) and (not slave_select and spi_clk) = '1' then
            data_reg <= data_reg(6 downto 0) & mosi_sync;
        end if;
    end process;

    led_data <= data_reg when slave_select = '1' else led_data;

    led <= led_data;

    MISO <= sw(0); -- just whatever right now

end Behavioral;

The IO_sync is as such, I simulated it so it works as intended. After two cycles the io_in is output to io_out, and io_tick is true on the "rising edge" of it:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity IO_sync is
    Port ( clk, io_in : in STD_LOGIC;
           io_out, io_tick : out STD_LOGIC);
end IO_sync;

architecture Behavioral of IO_sync is

component D_flip_flop_rst is
    Port ( clk, d, rst : in STD_LOGIC;
           q : out STD_LOGIC);
end component;

component D_flipflop is
    Port ( clk, d : in STD_LOGIC;
           q : out STD_LOGIC);
end component;

component T_vippa is
    Port ( clk, t, rst : in STD_LOGIC;
           q : out STD_LOGIC);
end component;

signal intermediate : STD_LOGIC;

begin

    DFF0: D_flipflop port map(clk => clk, d => io_in, q => intermediate);
    DFF1: D_flip_flop_rst port map(clk => clk, d => intermediate, rst => io_in, q => io_out);
    TICK: T_vippa port map(clk => clk, t => intermediate, rst => io_in, q => io_tick);

end Behavioral;

EDIT* Aha, yep I found at least one problem. Since the SS is active low, my desynchronization module won't work properly. I need to invert it somehow. I will set about doing that.

EDIT#2: Still does not work after fixing the former error.

EDIT#3: The code for the synchro is garbage, disregard it. Read later replies for rectifications.

Edited by Tickstart

Share this post


Link to post
Share on other sites
  • 0

@Tickstart,

I've looked over your code several times and haven't seen anything.  That "if", though, on (the positive edge of the clock and other stuff) bothers me, but I'm not enough of a VHDL guru to say if it looks good or not.  Sorry.  Have you simulated your code?  That's what I'd be doing now if I got that far and things weren't working.

Next, if it works in simulation, can you tell what is failing and where within your code?  I'm trying to slowly put together a list of things that can be done when you get stuck to get you unstuck, but ... my examples are in Verilog (not VHDL) and they are far from complete.

Dan

Share this post


Link to post
Share on other sites
  • 0

It does look a little odd. It looks as though it will move bits into data_reg every cycle that "spi_clk" is high,

TRANSCIEVE: process(clk)
   begin
      if rising_edge(clk) 
         if slave_select = '0' then  
           -- slave is selected
           if spi_clk_tick = '1' and  spi_clk_tick_last = '0' then
              -- seen the rising edge on spi_clk, so grab the data
              data_reg <= data_reg(6 downto 0) & mosi_sync;
           end if;
         end if;
         -- remember the current spi_clk_tick signal so we can detect if it 
         -- changes from '0' to '1' in the next cycle
         spi_clk_tick_last <= spi_clk_tick;
      end if;
    end process;
Edited by hamster

Share this post


Link to post
Share on other sites
  • 0

Hi again, thanks for your replies. I had thoroughly messed up with the code for the synchronizer which I now have taken care of. Here's a simulation of the new and improved code to delay the SCL-signal from the Arduino two cycles to make it stable. I also made a signal that signifies the rising edge of the clock so I can run it on the fpga without considering the difference in speed between the two.

 

 

synchro_edit.PNG

Edited by Tickstart

Share this post


Link to post
Share on other sites
  • 0
17 hours ago, hamster said:

It does look a little odd. It looks as though it will move bits into data_reg every cycle that "spi_clk" is high,


TRANSCIEVE: process(clk)
   begin
      if rising_edge(clk) 
         if slave_select = '0' then  
           -- slave is selected
           if spi_clk_tick = '1' and  spi_clk_tick_last = '0' then
              -- seen the rising edge on spi_clk, so grab the data
              data_reg <= data_reg(6 downto 0) & mosi_sync;
           end if;
         end if;
         -- remember the current spi_clk_tick signal so we can detect if it 
         -- changes from '0' to '1' in the next cycle
         spi_clk_tick_last <= spi_clk_tick;
      end if;
    end process;

Thanks for your input. The spi_clk_tick is only one cycle long, FPGA clock-wise. At least that's the idea. Anyway, my previous code was all wrong so it behaved very weird.

Share this post


Link to post
Share on other sites
  • 0
53 minutes ago, D@n said:

@Tickstart,

Does this mean you now have things working?

Dan

Well as always, they're evolving.. The rising_edge-thing seems to be working, the Arduino is supposed to transmit a byte every second and the incrementer I've connected it to shows a multiple of 8 in hex every second on the 7-seg display. 00 -> 08 -> 10 -> 18 -> 20 etc. So it's progressing, but the "data_reg <= data_reg(6 downto 0) & mosi_sync;"-code does not want to do anything, don't know if it's because of the MOSI-line not actually responding or something else. The chase continues.. (:

Edited by Tickstart

Share this post


Link to post
Share on other sites
  • 0

@Tickstart,

Making progress?  Good ...

Ok, so ... if it works in simulation, but not in real life ... do you have the right pin identified in your XDC file?

Since you are coming from the arduino, are you properly toggling the right pin?

Just some things to think about.

Dan

Share this post


Link to post
Share on other sites
  • 0
8 minutes ago, D@n said:

@Tickstart,

Making progress?  Good ...

Ok, so ... if it works in simulation, but not in real life ... do you have the right pin identified in your XDC file?

Since you are coming from the arduino, are you properly toggling the right pin?

Just some things to think about.

Dan

The simulation works and so does the real life thing, at least that's what I think. It (the synchronizer that is, which is what I've spent most time on thus far trying to fix) seems to work with the SPI clock at least. But whether the Arduino actually does what I want it to do is a whole other question..

This is my Arduno code:

#include <avr/io.h>
#include <util/delay.h>

//Initialize SPI Master Device
void spi_init_master (void)
{
	PRR |= (1 << PRTWI) | (1 << PRTIM2) | (1 << PRTIM0) | (1 << PRTIM1) | (1 << PRUSART0) | (1 << PRADC); // power save
	PRR &= ~(1 << PRSPI);
	DDRB |= (1 << DDB5) | (1 << DDB3) | (1 << DDB2);		//Set SS, MOSI, SCK as Output
	SPCR |= (1 << SPE) | (1 << MSTR) | (1 << SPR0);			//Enable SPI, Set as Master, divide by 16
	SPSR;
	SPDR;
}

//Function to send and receive data
unsigned char spi_tranceiver (unsigned char data)
{
	PORTB &= ~(1 << DDB2);			   // SS low
	SPDR = data;                       //Load data into the buffer
	while(!(SPSR & (1 << SPIF)));      //Wait until transmission complete
	PORTB |= (1 << DDB2);			   // SS high
	return(SPDR);                      //Return received data
}

//Main
int main(void)
{
	spi_init_master();                  //Initialize SPI Master

	unsigned char data = 0;

	while(1)
	{
		spi_tranceiver(data++);
		_delay_ms(1000);
	}
}

Not that it necessarily tells you very much, I asked on AVRFreaks and they thought it was fine.

Edited by Tickstart

Share this post


Link to post
Share on other sites
  • 0

@Tickstart,

There's still a test you can do with the arduino ... send a whole bunch of zeros, like in a tight loop, and use a voltmeter to make sure you are getting the right value.  Repeat using all ones.  Then you know you are toggling the right wire.

Dan

Share this post


Link to post
Share on other sites
  • 0

@D@n,

Tell you what mate :D I had swapped the yellow cable on my breadboard for the orange one. Once I swapped them over it worked! At least some past of the communication. I send a byte over the MOSI-line and link each bit up to leds on the Basys 3. Each second I increment the data sent (0 - 255) and a pretty binary pattern shows up! =)

Edited by Tickstart

Share this post


Link to post
Share on other sites
  • 0
1 hour ago, D@n said:

LED[1] should be on E19, not 19.  If that's not it then can you post a screen shot of the error and a copy of your XDC file?

Dan

## LEDs
set_property PACKAGE_PIN U16 [get_ports {led[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[0]}]
#set_property PACKAGE_PIN E19 [get_ports {led[1]}]
#set_property IOSTANDARD LVCMOS33 [get_ports {led[1]}]
set_property PACKAGE_PIN U19 [get_ports {led[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[2]}]
ETC

I can't unprohibit the pin either.. I guess that led is disabled forever. Bullshit

Share this post


Link to post
Share on other sites
  • 0

@Tickstart,

Prohibiting the LED forever is not the solution.  I've used all 16 LED's on my Basys3 without a problem.  There's something else going on.

Can you provide a log with some context to the error?

Dan

Share this post


Link to post
Share on other sites
  • 0

Well ... perhaps.  I have come across problems that needed rebuilding the project from the source files.  In my case, that was always because I had some generated products from a prior version of Vivado lying around.  The error I was getting didn't make sense then either, and asking on the xilinx forum didn't help, but rebuilding the project did ... so, it may help.

Dan

Share this post


Link to post
Share on other sites
  • 0
35 minutes ago, D@n said:

Well ... perhaps.  I have come across problems that needed rebuilding the project from the source files.  In my case, that was always because I had some generated products from a prior version of Vivado lying around.  The error I was getting didn't make sense then either, and asking on the xilinx forum didn't help, but rebuilding the project did ... so, it may help.

Dan

Is there any way to "clean" a project or something? I created a new one and JUST tried using led[1], still wasn't allowed..

Edited by Tickstart

Share this post


Link to post
Share on other sites
  • 0

Ahh ... no, wait ... I've got it .... what chip did you tell Vivado you were building for?  My next guess is that you are building for the wrong chip.

Dan

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this