Я написал подчиненное устройство SPI в Verilog. Есть некоторые реализации, но я все еще изучаю Verilog и цифровую логику в целом, поэтому я решил попробовать написать это самостоятельно.
Моя реализация работает. Но мне пришлось внести изменения, чтобы заставить его работать, и внесение изменений (я думаю) ставит мою реализацию в противоречие со спецификацией Motorola SPI. Мне интересно: правильно ли я считаю, что это странно, или я просто не понимаю, как что-то работает?
Сигналы SPI поступают по четырем проводам, которые называются SCK, SS, MOSI и MISO. В этом нет ничего удивительного. FPGA работает на частоте 12 МГц, а шина SPI работает на частоте 1 МГц. Стратегия состоит в том, чтобы сэмплировать шину SPI для каждой позиции тактового сигнала FPGA и отслеживать текущее и последнее значение для SCK и SS, чтобы я мог обнаруживать фронты. Я также пропускаю каждый сигнал через буферную регистрацию. Таким образом, логика всегда работает на два тактовых сигнала FPGA позади фактических событий: на тактовом сигнале 1 я фиксирую сигнал в буфере; на часах 2 я копирую его в регистр, который использую для логики, а на часах 3 я действую в соответствии с ним.
Я использую режим SPI 0. В соответствии со спецификацией SPI в режиме 0 ведомое устройство должно выбрать строку MOSI на позиции SCK, а затем передать на negedge 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
Код должен работать так:
- Когда SS становится активным (низким), данные [511] уже выводятся на MISO через провод. (Здесь нет трех состояний, потому что я единственный, кто едет в автобусе.)
- При выдаче SCK ведомое устройство выбирает MOSI, а ведущее устройство получает данные [511] по MISO.
- При отсутствии SCK ведомое устройство сдвигает бит, выбранный из MOSI, в данные, заставляя новый бит перемещаться в позицию вывода MISO.
Когда я запускал эту версию, биты падали повсюду. Буквально половина 512-битных сообщений, которые я отправлял от ведущего к ведомому, оказалась поврежденной.
Я посмотрел на реализацию ведомого устройства SPI с открытым исходным кодом и заметил, что он не использует необходимость SCK. Поэтому я изменил свой код на этот:
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, и оно все еще присутствует в следующей постановке, когда мастер сэмплирует ее. Но что плохого в том, чтобы изменить MISO на negedge? Несмотря на то, что я запускаю два цикла FPGA позади шины SPI из-за буферизации, у меня все еще есть 12 тактов FPGA на каждый тик SCK, что означает, что у меня есть шесть циклов FPGA между SCK negedge и следующей позицией. Я должен вовремя разобраться в MISO, верно?
На практике все ли просто (в режиме SPI 0) обновляют MISO сразу после установки SCK и не беспокоятся о ней?
Спасибо!