2

我用 Verilog 写了一个 SPI 从机。那里有一些实现,但我仍在学习 Verilog 和一般的数字逻辑,所以我决定尝试自己编写它。

我的实施有效。但是我必须进行更改才能使其正常工作,并且进行更改(我认为)使我的实现与摩托罗拉 SPI 规范不一致。我想知道:我认为这很奇怪是对的,还是我只是不明白某些东西是如何工作的?

SPI 信号通过称为 SCK、SS、MOSI 和 MISO 的四根线输入。那里并不奇怪。FPGA 以 12MHz 运行,SPI 总线以 1MHz 运行。该策略是在每个 FPGA 时钟频率上采样总线 SPI 总线,并跟踪 SCK 和 SS 的当前值和最后值,以便我可以检测边缘。我还将每个信号通过缓冲区寄存器。所以逻辑总是在实际事件之后运行两个 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 上,从机对 MOSI 进行采样,主机在 MISO 上获取数据[511]。
  3. 在 SCK 的下降沿,从机将从 MOSI 采样的位移入数据,导致新的位移入 MISO 输出位置。

当我运行这个版本时,它到处乱扔垃圾。我从主机发送到从机的 512 位消息中有一半最终损坏了。

我查看了一个开源的 SPI 从机实现,并注意到它没有使用 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

进行此更改后,它运行良好。完全防弹。

但我在想,嗯?

我知道这没关系。MISO 在 SCK 定位后立即获得新值,并且在下一次定位时,当主采样时它仍然存在。但是在 negedge 上更改 MISO 有什么问题呢?即使由于缓冲,我在 SPI 总线后面运行了两个 FPGA 周期,但我仍然有 12 个 FPGA 时钟用于每一个 SCK 滴答,这意味着我在 SCK negedge 和下一个 posedge 之间有六个 FPGA 周期。我应该能够及时了解 MISO 的内容,对吧?

在实践中,每个人是否都只是(在 SPI 模式 0 中)在 SCK posege 之后立即更新 MISO 而不必担心 negedge?

谢谢!

4

1 回答 1

2

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

无论如何,您要做的第一件事就是获取主设备的数据表并找出它的用途。理想情况下,您还应该获得 SCLK、MOSI 和 SS 的示波器,并找出实际的主时序是什么。找出主设备何时更改 MOSI,以及 MOSI 设置并保持到 SCLK 边沿(正或负)是什么。

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

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

于 2013-08-13T13:20:41.327 回答