我在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
代码应该像这样工作:
当我运行这个版本时,它会在整个地方掉落一些东西。我从主服务器发送到服务器的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而不用担心这个问题吗?
谢谢!
答案 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上的同步;创建一个启用信号,以便在它们已知稳定时对其进行采样。