当我遵循规范时,SPI奴隶不起作用,当我不遵守时,SPI奴隶不起作用吗?

时间:2013-08-13 01:58:42

标签: verilog fpga spi

我在Verilog中写了一个SPI奴隶。有一些实现,但我仍然在学习Verilog和数字逻辑,因此我决定自己编写它。

我的实施有效。但我不得不做出改变才能让它发挥作用,并且做出改变(我认为)使我的实现与摩托罗拉SPI规范不一致。我想知道:我认为这很奇怪,或者我只是不明白它是如何起作用的?

SPI信号通过称为SCK,SS,MOSI和MISO的四条线路进入。这并不奇怪。 FPGA以12MHz运行,SPI总线以1MHz运行。策略是在每个FPGA时钟上对总线SPI总线进行采样,并跟踪SCK和SS的当前值和最后值,以便检测边沿。我还通过缓冲区reg传递每个信号。因此逻辑总是在实际事件后面运行两个FPGA时钟:在时钟1,我将信号锁存在缓冲器中;在时钟2上,我将其复制到我用于逻辑的reg中,在时钟3上,我对它采取行动。

我正在使用SPI模式0.根据SPI规范,在模式0中,从器件应采样 SCK的边缘上的MOSI线,然后传输关于SCK的问题。

所以我就这样写了:

reg [511:0] data; // I exchange 512-bit messages with the master.

reg [2:0] SCK_buffer = 0;
always @(posedge clock) begin // clock is my FPGA clock
    SCK_buffer <= {SCK_buffer[1:0], SCK};
end 
wire SCK_posedge = SCK_buffer[2:1] == 2'b01;
wire SCK_negedge = SCK_buffer[2:1] == 2'b10;

reg [2:0] SS_buffer = 0;
always @(posedge clock) begin
    SS_buffer <= {SS_buffer[1:0], SS};
end
wire SS_posedge = SS_buffer[2:1] == 2'b01;
wire SS_negedge = SS_buffer[2:1] == 2'b10;
wire SS_active = ~SS_buffer[1];

reg [1:0] MOSI_buffer = 0;
always @(posedge clock) begin
    MOSI_buffer = {MOSI_buffer[0], MOSI};
end
wire MOSI_in = MOSI_buffer[1];     

assign MISO = data[511];

always @(posedge clock) begin 
    if (SS_active) begin
        if (SCK_posedge) begin
            // Clock goes high: capture one bit from MOSI.
            MOSI_capture <= MOSI_in;
        end
        else if (SCK_negedge) begin
            // Shift the captured MOSI bit into the LSB of data and output 
            // the MSB from data.  Note: MISO is a wire that outputs data[511].
            data <= {data[510:0], MOSI_capture};
        end
    end
end

代码应该像这样工作:

  1. 当SS变为活动(低)时,数据[511]已经通过线路在MISO上输出。 (这里没有三态,因为我是公共汽车上唯一的东西。)
  2. 在SCK的posedge上,从器件采样MOSI,主器件在MISO上获取数据[511]。
  3. 在SCK的negedge上,从器件将从MOSI采样的位移位到数据中,从而使新位移入MISO输出位置。
  4. 当我运行这个版本时,它会在整个地方掉落一些东西。我从主服务器发送到服务器的512位消息中有一半已损坏。

    我查看了一个开源SPI slave实现,发现它没有使用SCK的negedge。所以我把我的代码更改为:

    always @(posedge clock) begin 
        if (SS_active) begin
            if (SCK_posedge) begin
                // Skip that "capture" business and just shift MOSI right into the
                // data reg.
                data <= {data[510:0], MOSI_in};
            end
        end
    end
    

    完成此更改后,完美。完全防弹。

    但我在想,是吗?

    我知道这没关系。在SCK产生之后,MISO立即得到它的新值,并且当主人对它进行采样时,它仍然存在于下一个posedge中。但是,改变MISO在negedge上出了什么问题呢?尽管由于缓冲而我在SPI总线后面运行了两个FPGA周期,但每SCK的一个时钟周期仍有12个FPGA时钟,这意味着我在SCK negedge和下一个posedge之间有6个FPGA周期。我应该能够及时在MISO上找到一点,对吗?

    在实践中,每个人(在SPI模式0下)都会在SCK出现之后立即更新MISO而不用担心这个问题吗?

    谢谢!

1 个答案:

答案 0 :(得分:2)

我认为你可能会对你的主人正在做什么感到困惑。你说 MOSI在SCK的构成之后立即得到它的新值,当主人对它进行采样时,它仍然存在于下一个posedge中。大概你的意思是当 slave 对它进行采样时,但一般的想法是读者在其他时钟边缘采样到生成数据的时钟边缘,以最大化设置和保持。我认为,在你的情况下,主机应该在SCK的下降沿产生数据,而不是上升沿。

在任何情况下,您要做的第一件事就是获取主设备的数据表并找出它的意图。理想情况下,您还应该在SCLK,MOSI和SS上获得一个范围,并找出实际的主时序。找出主机何时改变MOSI,以及MOSI设置并保持SCLK边缘(pos或neg)是什么。

您的代码实际上并未在SCK上升沿采样MOSI,而第二个版本不会在下降沿采样。它在靠近边缘的地方采样。问题是你在SCK和MOSI(和SS)上有单独的同步器。通常,这是一个失败的方法,但它可能会或可能不会在您的情况下,取决于具体的时间。这样想吧。如果你是对的,并且“MOSI在SCK结束后立即得到它的新值”,那么MOSI会有很长的设置和短暂的保持,你的采样将失败,因为当你看到高价值时在SCK(实际发生后最多84ns),MOSI已经改变并且无效。

正确的方法是在由SCK提供时钟的F / F上采样MOSI(可能还有SS),并使用小型数字PLL锁定SCK,以便知道何时读取采样器输出。如果您了解时间安排,并且您已经进行了大量设置并保留了您正在采样的所有内容,那么您可以像现在一样对SCK进行采样。在这种情况下,您不需要MOSI和SS上的同步;创建一个启用信号,以便在它们已知稳定时对其进行采样。