Интерфейс SPI на Xilinx FPGA, тактовые домены и временные ограничения

Я подключаю плату 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 немного замедляет передачу — но это вполне приемлемо для этой системы.

Ответы (2)

Обычный подход состоит в том, чтобы совместить MOSI, CS и SCLK с доменом внутренних часов фабрики FPGA (работающих с гораздо более высокой скоростью, чем шина SPI) и выполнять всю фактическую работу там.

Пересечение домена часов с выходом параллельного регистра имеет, по крайней мере, неотъемлемую возможность недопустимого состояния, тогда как выполнение этого с последовательной шиной на самом деле не так. Это связано с тем, что ваш фильтр метастабильности может регистрировать разные уровни для разных битов, если несколько битов изменяют состояние в пределах окна установки или удержания. Кроме того, перенос последовательного потока в область основных часов позволяет вам легко реализовать такие вещи, как фильтры сбоев, которые могут быть полезными.

Это тоже хороший совет. Мой ответ касается более прямого исправления существующего кода.
Спасибо за это. У меня внутренние часы всего 64МГц, и увеличивать их особо не хочется. Я бы предположил, что хочу, чтобы он был как минимум в 8 или 10 раз выше, чем скорость SPI? Действительно, вывод сдвигателя будет иметь некоторые недопустимые значения, если он будет прочитан в неподходящий момент, но там есть сигнал, чтобы остановить это. Я мог бы также добавить еще один регистр, чтобы предотвратить это. Но «ваш фильтр метастабильности может регистрировать разные уровни в разных битах, если несколько битов изменяют состояние в окне настройки или удержания» - это я не совсем понимаю. Не могли бы вы объяснить, пожалуйста? большое спасибо.
Я мог понизить скорость SPI примерно до 4 МГц и попробовать CLK/MOSI с тактовой частотой 64 МГц. Скорость передачи данных практически не пострадала, а увеличение простоты весьма привлекательно. Спасибо.
Хорошо, я вижу идею. Фактически, таким образом я могу полностью избежать синхронизации с SPI CLk — используя два сигнала в регистрах для обнаружения фронта. Спасибо! отличная идея.
@dmb Всегда помните, что вы можете надежно синхронизировать только один бит через границу часов. По той же причине код Грея используется для указателей в асинхронных FIFO. В вашем случае вы синхронизируете бит управления с принимающим тактовым доменом, а затем используете его в качестве разрешения для захвата других сигналов в новом домене.
чтобы уточнить (и убедиться, что я понимаю): если мы запускаем параллельную шину через регистры синхронизации, мы не можем гарантировать, что все они будут синхронизироваться в одном и том же цикле синхронизирующих часов, верно? Однако, если они квалифицируются каким-либо другим (синхронным) сигналом, который возникает, скажем, через 3 такта (синхронизирующих часов) после их изменения, к тому времени все они должны быть синхронизированы. (Возможно, это не очень хорошая практика, но полезно понимать, что происходит и почему.) Всем большое спасибо. Отличная помощь и совет.

Нет, у вас совершенно неправильное представление о передаче многобитной шины через границу тактового домена.

Здесь проблема не в метастабильности, а скорее в выборке битов на шине в то время, когда известно, что они не изменяются, так что вы всегда получаете самосогласованное значение.

Таким образом, правильно синхронизировать и задерживать r_RPI_word_doneсигнал, прежде чем выполнять на нем обнаружение фронта, но НЕ правильно помещать сами данные через несколько регистров.

Ваши внутренние часы в несколько раз быстрее, чем часы SPI (правильно?), поэтому к моменту o_fifo_writeпоявления импульса вы ЗНАЕТЕ, что биты данных стабильны и могут безопасно сэмплироваться. Вам не нужны регистры r_data_sync_1и r_data_sync_2, и вы должны напрямую

assign o_data = r_RPI_shift_in;

На самом деле задержка данных очень контрпродуктивна, потому что она практически гарантирует , что вы производите выборку данных в то время, когда они изменяются, что приводит к захвату некоторых битов из одного слова и некоторых битов из следующего слова.

Спасибо. На самом деле, потому что каждый байт разделен долгим неактивным временем (передача байта занимает около 0,5 мкс, до следующего около 24 мкс), но действительно нет смысла в регистрах для данных, как вы утверждаете.