Я подключаю плату Raspberry Pi к плате разработки со Spartan 6. Я хочу сделать это с помощью SPI. Из-за того, как спроектирована плата разработчика, мне нужно подключить SPI CLK и DATA к стандартным контактам ввода-вывода.
Я знаю о необходимости перекрестной синхронизации доменов с двойной буферизацией для защиты от метастабильности. Очевидно, что RPi и SPI CLK находятся в отдельном домене от внутренней структуры FPGA. Я не вижу особых проблем: только один 8-битный регистр и сигнал, сообщающий о готовности байта, необходимо синхронизировать с внутренними часами фабрики. Я не пытаюсь получить высокие скорости передачи данных. Байт будет записываться каждые 25 мкс (это потому, что RPi медленно читает GPIO, но для этого проекта это не проблема). Я думаю, что SPI будет работать на частоте 15 МГц, и даже может уменьшить ее, если это необходимо.
Это мой верилог. Он прекрасно моделирует и проводит стендовые испытания.
module my_spi_in (
// RPI clock domain
input i_RPI_spi_data,
input i_RPI_spi_clk,
input i_RPI_reset,
// internal 64MHz domain
input i_sys_clk,
output [7:0] o_data,
output o_fifo_write
);
// registers in RPI clock domain
reg [7:0] r_RPI_shift_in = 8'b0;
reg [2:0] r_RPI_ctr = 3'b0;
reg r_RPI_word_done = 1'b0;
// synchronisation registers
reg [7:0]r_data_sync_1 = 8'b0;
reg [7:0]r_data_sync_2 = 8'b0;
reg [2:0] r_word_done_sync = 3'b0;
// RPI clock domain : input shift register logic
always @ (posedge i_RPI_spi_clk, posedge i_RPI_reset) begin
if (i_RPI_reset == 1'b1) begin
r_RPI_shift_in <= 8'b0;
r_RPI_ctr <= 3'b0;
end else begin
r_RPI_ctr <= r_RPI_ctr + 1'b1;
r_RPI_shift_in <= {i_RPI_spi_data, r_RPI_shift_in[7:1]};
end
end
// RPI clock domain : word done
always @ (negedge i_RPI_spi_clk) begin
if (~i_RPI_reset && r_RPI_ctr == 3'b000) r_RPI_word_done <= 1'b1;
else r_RPI_word_done <= 1'b0;
end
// sync registers
always @ (posedge i_sys_clk) begin
r_data_sync_1 <= r_RPI_shift_in;
r_data_sync_2 <= r_data_sync_1;
r_word_done_sync[0] <= r_RPI_word_done;
r_word_done_sync[1] <= r_word_done_sync[0];
r_word_done_sync[2] <= r_word_done_sync[1];
end
assign o_data = r_data_sync_2;
assign o_fifo_write = r_word_done_sync[1] && ~r_word_done_sync[2];
endmodule
В моем файле .ucf у меня есть только следующее, чтобы сообщить ISE, что это не «настоящие» часы (без этого они не будут построены):
NET "i_RPI_spi_clk" CLOCK_DEDICATED_ROUTE = FALSE;
NET "i_RPI_reset" CLOCK_DEDICATED_ROUTE = FALSE;
Мой вопрос: это лучший подход? Нужно ли мне делать что-то еще? (В идеале было бы неплохо также установить некоторые временные ограничения для часов и данных SPI, чтобы инструменты знали о скорости интерфейса SPI.)
Заранее спасибо за совет.
РЕДАКТИРОВАТЬ: я должен уточнить, что RPi передает только один байт перед проверкой контакта GPIO. Это оказывается медленным (занимает около 25 мкс), поэтому на шине SPI никогда не бывает двух байтов подряд. Активность SPI примерно 0,5 мкс (один байт на частоте 15 МГц), затем ничего не происходит примерно 24 мкс, пока RPi не прочитает GPIO. Это, очевидно, намного медленнее, чем способен SPI — время чтения RPi немного замедляет передачу — но это вполне приемлемо для этой системы.
Обычный подход состоит в том, чтобы совместить MOSI, CS и SCLK с доменом внутренних часов фабрики FPGA (работающих с гораздо более высокой скоростью, чем шина SPI) и выполнять всю фактическую работу там.
Пересечение домена часов с выходом параллельного регистра имеет, по крайней мере, неотъемлемую возможность недопустимого состояния, тогда как выполнение этого с последовательной шиной на самом деле не так. Это связано с тем, что ваш фильтр метастабильности может регистрировать разные уровни для разных битов, если несколько битов изменяют состояние в пределах окна установки или удержания. Кроме того, перенос последовательного потока в область основных часов позволяет вам легко реализовать такие вещи, как фильтры сбоев, которые могут быть полезными.
Нет, у вас совершенно неправильное представление о передаче многобитной шины через границу тактового домена.
Здесь проблема не в метастабильности, а скорее в выборке битов на шине в то время, когда известно, что они не изменяются, так что вы всегда получаете самосогласованное значение.
Таким образом, правильно синхронизировать и задерживать r_RPI_word_done
сигнал, прежде чем выполнять на нем обнаружение фронта, но НЕ правильно помещать сами данные через несколько регистров.
Ваши внутренние часы в несколько раз быстрее, чем часы SPI (правильно?), поэтому к моменту o_fifo_write
появления импульса вы ЗНАЕТЕ, что биты данных стабильны и могут безопасно сэмплироваться. Вам не нужны регистры r_data_sync_1
и r_data_sync_2
, и вы должны напрямую
assign o_data = r_RPI_shift_in;
На самом деле задержка данных очень контрпродуктивна, потому что она практически гарантирует , что вы производите выборку данных в то время, когда они изменяются, что приводит к захвату некоторых битов из одного слова и некоторых битов из следующего слова.
Дэйв Твид
danmcb
danmcb
danmcb
CapnJJ
danmcb