Jump to content

    

Clock domain crossing при непостоянном внешнем тактировании

Приветствую!

Возникла задача сделать на ПЛИС приёмник данных по интерфейсу, временная диаграмма работы которого схематически выглядит так:

interface.thumb.JPG.320c6a7e4c2f8d0fbe4dd31e4521c980.JPG

RDY - выход (для ПЛИС), показывает внешнему устройству, что можно начинать передачу данных. Начавшуюся передачу тормозить уже нельзя, к слову, но объём каждой посылки фиксирован и тут всё норм. С этим сигналом сложностей нет - он синхронизирован с логикой в ПЛИС. 

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

Din0...Din3 - линии передачи данных. Их может быть 1 или 4. Тут всё просто.

 

Как видно, интерфейс DDR - с передачей данных по обоим фронтам тактового сигнала. Это не представляется проблемой, т.к. получить data_rise и data_fall несложно, но это увеличивает число параллельных сигналов, подлежащих передаче между доменами тактирования в ПЛИС. Впрочем, Xilinx-овский примитив IDDR применить не удалось - он конвееризирован и при любой конфигурации требует несколько дополнительных тактов (которых у меня нет) для вывода последних полученных бит.

После обдумывания проблемы, я вспомнил, что это мне напоминает - частый на форумах вопрос о реализации SPI slave - там тоже клок внешний и присутствует только при передаче данных. Но в том случае большинство рекомендаций сводится к стробированию входных сигналов внутренним тактовым через стандартную схему с регистрами. Однако, даже там получается шина как минимум из двух сигналов SPI_CLK и SPI_MOSI, а шину так синхронизировать, насколько я знаю, не рекомендуют.

А главный вопрос - скорость передачи. В случае SPI - внешняя тактовая МГц до 10 при клоке ПЛИС 50-100 МГц. А у меня интерефейс работает с частотой 100-500 МГц. Понятно, что после десериализации частота следования данных упадёт как минимум в 8 раз (если распараллеливать в 8 бит по фронту + 8 бит по спаду), но синхронизировать шинные сигналы через асинхронные регистры нельзя, нужно FIFO. А вот как раз FIFO у Xilinx работает только при постоянно присутствующем (free running) тактировании с обеих сторон.

Засада... 

Пока что попробовал сделать приём данных в принципе, только по внешнему клоку. Сыро, но работает. Но чтобы всё это интегрировать в полную подсистему, с AXI-stream инфраструктурой, нужно корректно передать данные во внутренний тактовый домен - иначе хитрая Вивада пошлёт лесом, да и работать всё будет глючно. Пока что правильного рещения не нашёл.

Share this post


Link to post
Share on other sites

Вам нужно всего 2 регистра сдвига: один будет работать по прямому входящему клоку, второй по инверсному. Плюс пара триггеров для логики RDY и счётчик полученных байт - защёлкивать входные десериализированные данные в следующий блок для дальнейшей обработки. Схема проще некуда.

НО! Это только начало :biggrin: Какой чип, какой тип интерфейса и т.д.? Там с констрейнами/плейсментом будет геморроя дофигища. Я на дев борде Artix'а с горем по-полам распихал 3 последовательных триггера на 400MHz...

Share this post


Link to post
Share on other sites

Думаю, можно оставить в стороне все частоты входного тактирования выше 100 МГц - бОльшая скорость в моей задаче не требуется, а меньше не позволяет микросхема-передатчик. 

Собственно, на такой частоте у меня сейчас работает "сырой" тест приёмника данных на ZedBoard-e (Zynq-7020) - выводит полученные данные на светодиоды :)  Регистр причём даже один - загружается по обоим фронтам, хотя это, возможно, и не совсем корректно. Проблема в том, что вся эта схема работает в тактовом домене входного сигнала (тот самый CLK, который то есть, то нет). И чтобы корректно передать их в основной тактовый домен ПЛИС,нужно какое-то хитрое FIFO, которое умеет работать с непостоянным клоком с одной стороны. IP core FIFO от Xilinx не умеет - даже в даташите написано.

 

Share this post


Link to post
Share on other sites

100-500 МГц - довольно большой диапазон. Предложенный выше вариант (два сдвиговых регистра + счётчик) точно заработает на 100 МГц. А вот на 500 - уже не точно.

Так что, первым дело, стоит определиться с частотой интерфейса и от этого отталкиваться.

2 minutes ago, Lionet said:

Думаю, можно оставить в стороне все частоты входного тактирования выше 100 МГц - бОльшая скорость в моей задаче не требуется, а меньше не позволяет микросхема-передатчик. 

Собственно, на такой частоте у меня сейчас работает "сырой" тест приёмника данных на ZedBoard-e (Zynq-7020) - выводит полученные данные на светодиоды :)  Регистр причём даже один - загружается по обоим фронтам, хотя это, возможно, и не совсем корректно. Проблема в том, что вся эта схема работает в тактовом домене входного сигнала (тот самый CLK, который то есть, то нет). И чтобы корректно передать их в основной тактовый домен ПЛИС,нужно какое-то хитрое FIFO, которое умеет работать с непостоянным клоком с одной стороны. IP core FIFO от Xilinx не умеет - даже в даташите написано.

 

Можно без фифо. Перекладываете строб RDY на внутреннюю тактовую, заводите по нему счётчик и, допустим, на 10й такт (те когда ВСЕ пришедшие биты ТОЧНО легли в сдвиговые регистры) перекладываете эти регистры на внутренний клок.

Share this post


Link to post
Share on other sites
3 minutes ago, nice_vladi said:

Можно без фифо. Перекладываете строю RDY на внутреннюю тактовую, заводите по нему счётчик и, допустим, на 10й такт (те когда ВСЕ пришедшие биты ТОЧНО легли в сдвиговые регистры) перекладываете эти регистры на внутренни клок.

Как вариант. Но всё равно остаётся ощущение, что что-то делаю не так. 

К слову, тот же счётчик входных бит надо бы периодически обнулять - при сбросе или перед приёмом очередного байта (пусть он и сам по переполнению обнуляется, но так дополнительная защита от сбоев). А для этого сигнал от внутреннего клока ПЛИС надо затащить в домен внешнего CLK. а его опять нет до передачи данных. Снова засада...

Share this post


Link to post
Share on other sites
2 часа назад, Lionet сказал:

К слову, тот же счётчик входных бит надо бы периодически обнулять - при сбросе или перед приёмом очередного байта (пусть он и сам по переполнению обнуляется, но так дополнительная защита от сбоев). А для этого сигнал от внутреннего клока ПЛИС надо затащить в домен внешнего CLK. а его опять нет до передачи данных. Снова засада...

Судя по картинке сигнал RDY может быть признаком 1-го бита.. И он может обнулять счетчик принятых битов... А дальше нужен не счетчик, а сдвиговый регистр, который и отсчитает принятые биты. В этот регистр задвигайте 1, и когда она появится в последней позиции - это и будет означать конец приема.. И там, если после приема есть пауза, то бит сдвигового регистра будет стоять все время паузы... Но можно сделать регистр длиннее на 1 бит и отлавливать лишние сбойные клоки...

Share this post


Link to post
Share on other sites
16 minutes ago, iosifk said:

Судя по картинке сигнал RDY может быть признаком 1-го бита.. И он может обнулять счетчик принятых битов... 

Тут есть нюанс: RDY - это сигнал, формируемый во внутреннем тактовом домене ПЛИС (например, когда процессор проинициализировал DMA для приёма данных). Соответственно, он находится не в тактовом домене входного сигнала CLK (в котором работает счетчик входных бит). И чтобы его туда пробросить, нужно, чтобы этот CLK работал. А он появляется, когда уже пошла передача, соответственно что-то обнулять поздно.

Т.е. логически я идею, конечно, понял и сам пытался её реализовать. Но всё оказалось не так просто

Share this post


Link to post
Share on other sites

Приветствую!
 

1 hour ago, Lionet said:

Т.е. логически я идею, конечно, понял и сам пытался её реализовать. Но всё оказалось не так просто

Тогда уж выкладывает все как есть на входе и что хотите получать на выходе - а так только угадывать приходится. 

Самый простой вариант cdc тут это elastic-buffer - пишете последним  фронтом в память (distributed) увеличивая счетчик записи. Сам счетчик передаете на сторону чтения  через обычную цепочку cdc (тут только clk чтения нужно),  а там уж логика чтения как у обычного FIFO.  

Удачи! Rob.

Share this post


Link to post
Share on other sites
On 10/31/2019 at 8:33 PM, Lionet said:

RDY - выход (для ПЛИС), показывает внешнему устройству, что можно начинать передачу данных. Начавшуюся передачу тормозить уже нельзя, к слову, но объём каждой посылки фиксирован и тут всё норм. С этим сигналом сложностей нет - он синхронизирован с логикой в ПЛИС.  

 

On 11/1/2019 at 2:10 AM, Lionet said:

Тут есть нюанс: RDY - это сигнал, формируемый во внутреннем тактовом домене ПЛИС (например, когда процессор проинициализировал DMA для приёма данных). Соответственно, он находится не в тактовом домене входного сигнала CLK (в котором работает счетчик входных бит). И чтобы его туда пробросить, нужно, чтобы этот CLK работал. А он появляется, когда уже пошла передача, соответственно что-то обнулять поздно. 

в чем проблема использовать RDY асинхронно? Аналогично тому, как делаются синхронизаторы сброса. Управляете уровнем, RDY держите всю посылку ну или сформируйте из него короткий импульс сброса. RDY появляется гарантированно до начала посылки, ну а дальше тривиально, собираете ваши слова и кладете их асинхронно в регистры или в память на SLICEM или в квазиасинхронную RAMB

ЗЫ. В свое время, от Xilinx гуру (что, то мне подсказывает что от Ken Chapmen) была статья, с примерами асинхронно/синхронных переходов, еще под спартан 2. Работало же) Но сходу не нагуглилось

Share this post


Link to post
Share on other sites
В 31.10.2019 в 22:10, Lionet сказал:

Тут есть нюанс: RDY - это сигнал, формируемый во внутреннем тактовом домене ПЛИС (например, когда процессор проинициализировал DMA для приёма данных). Соответственно, он находится не в тактовом домене входного сигнала CLK (в котором работает счетчик входных бит). И чтобы его туда пробросить, нужно, чтобы этот CLK работал. А он появляется, когда уже пошла передача, соответственно что-то обнулять поздно.

Я предлагаю сделать все на стороне внутреннего клока. Для этого в FIFO делаем теги, т.к. еще один дополнительный бит, кроме бита данных. И туда записываем сигнал RDY . И уже на внутренней стороне по наличию этого сигнала определяем начало посылки, делаем счетчики и пр. Ну а данные пишем "как есть" и на другой стороне их преобразовываем. Получится наверное два FIFO, один для положительного фронта, другой - для отрицательного. 

Теги можно сделать и большей разрядности и писать туда служебную информацию. У меня есть ранняя статья о том, как я делал МАС. И там это более подробно расписано...

Share this post


Link to post
Share on other sites
2 hours ago, iosifk said:

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

на частоте как у ТС не получится так записать весь поток, а потом разгрести

Share this post


Link to post
Share on other sites

Приветствую!

2 hours ago, des00 said:

на частоте как у ТС не получится так записать весь поток, а потом разгрести

Так и есть - да и не нужно это. Тут  классический elastic buffer. Разве что клок не постоянный. Отсюда вытекает что все надо сделать на последнем фронте.

Spoiler

`timescale 1ns/1ps
`default_nettype none

module data_din #(parameter
  DIN_WH  = 4,
  DIN_N   = 4,
  ADDR_WH = 4
) (
  input  wire                          clk        ,
  input  wire                          rst        ,
  input  wire                          ready      ,
  // device pins
  output logic                         dev_rdy    ,
  input  wire                          dev_clk    ,
  input  wire  [ DIN_WH-1:0]           dev_din    ,
  // write to fifo
  output wire                          ram_wr_clk ,
  output logic [ADDR_WH-1:0]           ram_wr_addr,
  output logic                         ram_wr_ena ,
  output logic [DIN_N*2-1:0][DIN_WH:0] ram_wr_data,
  output logic                         ram_wr_done
);

logic [DIN_N:1] cnt_f;
logic           rdy_f;

assign dev_rdy = ready && ~rdy_f;

always @(negedge dev_clk or negedge ready) begin
  if (~ready) begin
    rdy_f <= '0;
    cnt_f <= '0;
  end
  else begin
    rdy_f <= 1'b1; // ~cnt_f[DIN_N-1]
    cnt_f <= (cnt_f<<1) | ~rdy_f;
  end
end

//
logic [ADDR_WH-1:0]             addr_wr ;
logic [  DIN_N-1:0][DIN_WH-1:0] din_r   ;
logic [  DIN_N-1:0][DIN_WH-1:0] din_f   ;
logic [  DIN_N-1:0][DIN_WH-1:0] din_f_nx;

assign din_f_nx = (din_r<<DIN_WH) | dev_din;
always @(posedge dev_clk) begin
  din_r <= din_f_nx;
end

always @(negedge dev_clk) begin
  din_f <= (din_f<<DIN_WH) | dev_din;
end

//
assign ram_wr_clk  = ~dev_clk;
assign ram_wr_ena  = cnt_f[DIN_N-1];
assign ram_wr_done = cnt_f[DIN_N];

gray_count #(.WH(ADDR_WH), .ASYNC_RST(1)) i_addr_wr (.clk(ram_wr_clk), .rst(rst), .ena(ram_wr_ena), .dou(ram_wr_addr));

always_comb begin
  for (int ii=0; ii<DIN_N; ++ii) begin
    ram_wr_data[ii*2+0] = din_r[ii];
    ram_wr_data[ii*2+1] = din_f_nx[ii];
  end
end

endmodule
`default_nettype wire

 

Основной гемор будет с констрейнами.  Так как есть 3 критический пути  от dev_din к  регистрам din_r, din_f и ram_wr_data. Ну и  возможно  задержка снятия  dev_rdy после первого отрицательного клока. Но это уже на целевой FPGA надо смотреть. Если нужно будет выжать последний 5 MНz можно разные трюки применить кои  не всегда по феншую синхронного дизайна делаются.  :unknw:

Успехов! Rob.

Share this post


Link to post
Share on other sites
1 hour ago, RobFPGA said:

 Тут  классический elastic buffer. Разве что клок не постоянный.

асинхронно-синхронный переход как он есть))) @SM был бы, он бы лютой асинхры из азиков привел))))

ЗЫ. а вы точно код тот привели? ~rdy_f всегда в нуле, стробы записи не поднимутся никогда)

Share this post


Link to post
Share on other sites

Приветствую!

17 minutes ago, des00 said:

ЗЫ. а вы точно код тот привели? ~rdy_f всегда в нуле, стробы записи не поднимутся никогда)

Это  вы про это ? 

  rdy_f <= 1'b1; // ~cnt_f[DIN_N-1]
  cnt_f <= (cnt_f<<1) | ~rdy_f;

Предполагается что управление ready поднимается на все время передачи, rdy_f  установится в 1 первом  false edge, а по cnt_f  при этом побежит 1. Я набросал это по быстрому и на симе не проверял -  может и накосячил где.  Если нужно принимать несколько посылок то можно сразу закольцевать ( закоменнтированй кусок).

Удачи! Rob.

Share this post


Link to post
Share on other sites
3 minutes ago, RobFPGA said:

Предполагается что управление ready поднимается на все время передачи, rdy_f  установится в 1 первом  false edge, а по cnt_f  при этом побежит 1. Я набросал это по быстрому и на симе не проверял -  может и накосячил где.      

логику я понял, все по класике, но вот единица не побежит) главное чтобы ТС понял что и как править)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now