[Из песочницы] Генерация клока в ПЛИС на примитивах

Читая даташиты на ПЛИС, можно находить таблички об их рабочих частотах…

Хотя нет, история начинается еще с 2015 года, когда я познакомился с ПЛИС. В своих первых простеньких работах я формировал нужный мне клок из счетчика и запитывал от него всю логику (естественно при условии что клок мне нужен медленнее чем подавался на ПЛИС, например UART и SPI). Естественно за такое меня гоняли, но у меня была простая отмазка «но ведь работает же!», и действительно все работало. С тех пор у меня в голове закралась мысль «а откуда вообще можно взять тактирующий сигнал?».

Вариантов источников взять клок не много. Либо взять из некого ClockWizard основанный на PLL или MMCM, либо сформировать из счетчика, либо сразу с ножки так сказать single ended. А что, если взять тактовый сигнал сформированный примитивом ПЛИС?
В рамках этой статьи я решил рассмотреть три варианта: мультиплексор (MUXF7), таблица истинности (LUT1) и замкнуть ножки ПЛИС сами на себя.

В случае с мультиплексором выход подаем на управляющий сигнал, а входные сигналы притягиваем к 0 и 1.

image


В случае с LUT замыкаем выход на вход и задаем инвертирующую таблицу истинности. При подаче »1» выдавать ноль, а при »0» выдавать единицу.

image


В случае с GPIO там все просто, выходному сигналу присваиваем инверсию входного:
Цель эксперимента: сгенерировать частоту тремя способами и замерить ее.
Измерять частоту будем за счет счетчиков. Будет 4 счетчика: три на каждый вариант и один счетчик базовый, относительно которого все будет считаться. А смотреть эти счетчики будем через ChipScope.

А вот собственно весь код модуля:
module gen_clk(
    input clk_base,
    input s1, //gpio
    output s2 //gpio
    );

//счетчик на входных-выходных контактах
assign s2 = ~s1;
wire clk_gpio = s1;
reg [31:0] cnt_gpio = 0;
 (* MARK_DEBUG="true" *) reg [31:0] cnt_gpio_buf = 0;
always@(posedge clk_gpio)
begin 
    if(cnt_gpio[2:0]==3'd0) cnt_gpio_buf<=cnt_gpio; 
    cnt_gpio <= cnt_gpio + 1'b1;
end

//счетчик на мультиплексоре
wire clk_mux;
MUXF7 MUXF7_inst
(
    .O(clk_mux),
    .I0(1'b1),
    .I1(1'b0),
    .S(clk_mux)
);
reg [31:0] cnt_mux = 0;
 (* MARK_DEBUG="true" *) reg [31:0] cnt_mux_buf = 0;
always@(posedge clk_mux)
begin 
    if(cnt_mux[2:0]==3'd0) cnt_mux_buf<=cnt_mux; 
    cnt_mux <= cnt_mux + 1'b1;
end
//счетчик на одном луте
wire clk_lut;
LUT1#(
    .INIT(2'b01)
)
LUT1_inst(
    .O(clk_lut),
    .I0(clk_lut)
);
reg [31:0] cnt_lut = 0;
 (* MARK_DEBUG="true" *) reg [31:0] cnt_lut_buf = 0;
always@(posedge clk_lut)
begin 
    if(cnt_lut[2:0]==3'd0) cnt_lut_buf<=cnt_lut; 
    cnt_lut <= cnt_lut + 1'b1;
end
//базовый счетчик относительно которого будем считать    
 (* MARK_DEBUG="true" *) reg [31:0] cnt_base = 'd0;        
always@(posedge clk_base)
begin
    cnt_base <= cnt_base + 1'b1;
end    
   
endmodule


Вот схематик проекта. В круг обведены примитивы, а стрелками указаны сигнал которые будут внесены в ChipScope для анализа частоты:

image

Практическая часть

В моем распоряжении есть три платы:

  1. KC705 Evaluation Kit
    image

  2. ML507 Evaluation Kit
    image

  3. Китайская плата Spartan-6 XC6SLX16
    image

    Забегая вперед

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



И так теперь собственно результаты


Kintex-7:

Так как проект начинал делать под него, то и проект был написан не сразу целиком, а поэтапно. Сначала подключил один LUT добавил сигналы в отладку и стал смотреть.

Базовый счетчик тактируется на 200 МГц, поэтому посчитать частоту клока сгенерированного на луте не сложно, во сколько раз больше дельта счетчика лута дельты базового счетчика за одно и тоже время, во столько раз больше его частота. В данном случае: получается частота генерируемая лутом 381.55 МГц.

image


Теперь к проекту добавим мультиплексор, и по аналогии как с одним лутом посчитаем частоту для него, ну и для лута (должно ведь что-то поменяться).

image


Первое на что бросается взгляд это на то, как сильно дребезжит счетчик. Это сказывается огромная частота мультиплексора, но в целом видно, что счетчик идет по нарастающей, а это значит, что его тоже можно взять и посчитать. В итоге:

  • Частота на мультиплексоре: 5953.89 МГц
  • Частота на луте (изменилась): 379.98 МГц


Ну и на конец добавим к проекту замкнутую петлю из пары GPIO. На плате KC705 есть SMA разъемы J13 и J14. Вот их то я и замкнул проводником длиной примерно 10 см. В результате:

  • Частота на GPIO: 90.59 МГц
  • Частота на мультиплексоре: 12994.13 МГц
  • Частота на луте: 380.18 МГц


Заменим, эксперимента ради, проводник на более длинный, у меня имеется провод в два раза длиннее. В итоге частота упала до 85.29 МГц.

На данном этапе эксперимента можно отметить, что частота работы примитивов в ПЛИС не одинаковая. В случае, когда был только один лут то синтезатор выбрал самый быстрый лут и строил вокруг него схему, затем когда добавился мультиплексор синтезатор попытался найти ту супер позицию где и лут и мультиплексор работают максимально быстро, а это уже другие элемента и частоты уже медленнее. Когда добавились внешние пины то весь проект на кристалле в принципе передислоцировался к этим ножкам и проект стал синтезироваться на близ лежащих элементах, по какой-то причине в том месте частоты лута и мультиплексора заметно выросли, но не стоит забывать что на фоне всего этого к проекту подключён ChipScope глубиной 1024 и шиной данных от 64 до 128(от проекта к проекту меняется). Теперь перейдем к следующей плате.

Virtex-5:

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

image


На рисунке видны две метки Х и О. А так же их значения в столбцах, формат чисел беззнаковый десятичный. Стоит отметить, что базовый счетчик теперь считает на частоте 100 МГц. И так результат:

  • Частота на GPIO: 96.34 МГц
  • Частота на мультиплексоре: 614.41 МГц
  • Частота на луте: 5761.1 МГц


Видно что на этой плате лут оказался быстрее чем мультиплексор, а частота работы пинов оказалась выше чем у первой платы, возможно это потому что я соединил два пина не проводником 10 см, а джампером, в итоге линия связи стала короче, а частота выше.

А теперь последний вариант с китайской платой.

Spartan-6:

В ChipScope появилось два базовых счетчика, на самом деле это один и тот же счетчик просто не хотел перенастраивать ChipScope. В данном проекте базовый счетчик тактируется на частоте 50 МГц.

image


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

  • Частота на GPIO: 51.77 МГц
  • Частота на мультиплексоре: 3 490 504 МГц
  • Частота на луте: не получилось собрать


Результаты, в исполнении этой платы, оказались совсем уж не радостные, и не только потому что лут не получилось использовать в качестве клока, но и из-за неимоверно огромной частоты мультиплексора. Что касательно клока генерируемого на ножках, то был использован проводник порядка 25–30 см, на конце замкнутый проволочкой, наверняка там образовались паразитные емкости и индуктивности которые и оказали свое влияние на генерацию клока.

Заключение

В целом, получилось сгенерировать тактовые сигналы на различных примитивах, а так же получилось увидеть (на примере Kintex-7), что примитивы имеют разную задержку работы в зависимости от расположения. От себя хочу добавить, что не считаю проведённый эксперимент полностью корректным, например не рассчитывалась разрядность счетчиков, не учитывался перенос сигнала из разных клоковых доменов (хотя я сделал чтобы сигнал в буфере держался несколько тактов), сам ChipScope в идеале нужно убрать и найти другой способ анализировать генерируемую частоту.

Встретившиеся проблемы:
В ходе эксперимента синтезаторы Vivado и ISE ругались на комбинаторные петли, и трудности развода сигналов. Эти трудности решаются добавлением парой строк в констрейн:
  • set_property ALLOW_COMBINATORIAL_LOOPS TRUE [get_nets -of_objects [get_cells gen_clk_inst/LUT1_inst]]
  • NET «s1» CLOCK_DEDICATED_ROUTE = FALSE;

© Habrahabr.ru