TimeQuest для чайников. Приложение 2 (Хак для System Synchronus Ouput на cycloneIII)

Решал недавно одну непростую задачу, танцевал с бубном достаточно долго. Итак имеем 3 асинхронных потока данных на частоте 180 МГц и нужно было выдать их на внешний DAC включенный в режиме System Synchronus Ouput.

Что сложного скажет кто-то, берем еще один клок на PLL, немного двигаем его по фазе и вуаля. Но в том-то и дело что PLL у меня не было. А не было ее потому, что два клока шли с других PLL, а еще один клок шел с обычного порта. ПЛИС cycloneIII, как известно не поддерживает подачу на PLL абы какого сигнала, поэтому пришлось выкручиваться без нее.

Положение усугублялось еще и тем, что tsu/th у цапа были не очень подходящие для клока в 180МГц (период 5.56нс), а именно 2.0/1.5нс. Также у  ПЛИС cycloneIII практически отсутствует возможность играть задержкой на выходных пинах, точнее эта задержка либо есть, либо нет. Промежуточных значений не дано. Кроме того заполнение ПЛИС на тот момент составляло 96%.

Вот код показывающий как было сделано изначально

module time_test (input clk1, clk2, clk3,
                  input [7 : 0] data1, data2, data3,
                  input [1 : 0] sel,
                  output logic [7 : 0] odat,
                  output               oclk
                  ) ;

  logic [7 : 0] data1_reg [0 : 1], data2_reg [0 : 1] , data3_reg[0 : 1];

  always_ff @(posedge clk1) begin
    {data1_reg[1], data1_reg[0]} <= {data1_reg[0], data1};
  end

  always_ff @(posedge clk2) begin
    {data2_reg[1], data2_reg[0]} <= {data2_reg[0], data2};
  end

  always_ff @(posedge clk3) begin
    {data3_reg[1], data3_reg[0]} <= {data3_reg[0], data3};
  end

  logic [7 : 0] mux_data, mux_data_reg;
  logic         mux_clk ;

  always_comb begin
    unique case(sel)
      2'b01   : {mux_clk, mux_data} = {clk2, data2_reg[1]};
      2'b10   : {mux_clk, mux_data} = {clk3, data3_reg[1]};
      default : {mux_clk, mux_data} = {clk1, data1_reg[1]};
    endcase
  end

  always_ff @(posedge mux_clk) begin
    mux_data_reg <= mux_data;
  end

  //
  // output mapping
  always_ff @(posedge mux_clk) begin
    odat <= mux_data_reg;
  end

  assign oclk = mux_clk;
 
endmodule

sdc файл для этого кода следующий

set_time_format -unit ns -decimal_places 3
derive_clock_uncertainty

# base clocks
create_clock -period 180MHz -name {clk1} [get_ports {clk1}]
create_clock -period 180MHz -name {clk2} [get_ports {clk2}]
create_clock -period 180MHz -name {clk3} [get_ports {clk3}]

set_false_path -from [get_ports {data1[*] data2[*] data3[*]}] -to [all_clocks]
set_false_path -from [get_ports {sel[*]}]

create_generated_clock      -name oclka -source [get_ports clk1] -invert [get_ports oclk]
create_generated_clock -add -name oclkb -source [get_ports clk2] -invert [get_ports oclk]
create_generated_clock -add -name oclkc -source [get_ports clk3] -invert [get_ports oclk]

set_clock_groups -exclusive -group {clk1 oclka}
set_clock_groups -exclusive -group {clk2 oclkb}
set_clock_groups -exclusive -group {clk3 oclkc}

set DATA_delay_max [expr 7.446*0.007]
set DATA_delay_min [expr 5.178*0.007]
set CLK_delay_max  [expr 13.86*0.007]
set CLK_delay_min  [expr 13.86*0.007]
set Tsu 2.0
set Th  1.5

set usedTsu [expr $DATA_delay_max + $Tsu - $CLK_delay_min]
set usedTh  [expr $DATA_delay_min - $Th  - $CLK_delay_max]

set_output_delay            -clock [get_clocks oclka] -max $usedTsu [get_ports odat[*]]
set_output_delay            -clock [get_clocks oclka] -min $usedTh  [get_ports odat[*]]

set_output_delay -add_delay -clock [get_clocks oclkb] -max $usedTsu [get_ports odat[*]]
set_output_delay -add_delay -clock [get_clocks oclkb] -min $usedTh  [get_ports odat[*]]

set_output_delay -add_delay -clock [get_clocks oclkc] -max $usedTsu [get_ports odat[*]]
set_output_delay -add_delay -clock [get_clocks oclkc] -min $usedTh  [get_ports odat[*]]

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

В общем танцевал я долго, и клок задерживал на lcell ах и играл с регистрами ввода/вывода, а все равно. То по одному, то по другому клоку все валилось( напоминаю что клоков у меня было 3 и заполненность на 96%).

Во время очередного битья головой о стол, мне пришла идея хакнуть это дело. Нужно было сделать задержки по клоку и по данным абсолютно одинаковыми и используя инверсный клок точно сфазироваться фронтом на середину данных, а то что на плате проводник клока давал лишние 0.15нс только играло на руку. Сделать это можно было за счет использования ddio триггеров в ячейках ввода вывода. ddio это два триггера, работающие на разных фронтах клока и мультиплексор стоящий за ними, работающий по клоку. Подаем для данных на оба триггера одно и тоже, а для клока 1'b0/1'b1, получаем нужную нам фазировку клока относительно данных и одинаковую задержку В итоге получилась такая схема.

module time_test (input clk1, clk2, clk3,
                  input [7 : 0] data1, data2, data3,
                  input [1 : 0] sel,
                  output logic [7 : 0] odat,
                  output               oclk
                  ) ;

  logic [7 : 0] data1_reg [0 : 1], data2_reg [0 : 1] , data3_reg[0 : 1]  ;

  always_ff @(posedge clk1) begin
    {data1_reg[1], data1_reg[0]} <= {data1_reg[0], data1};
  end

  always_ff @(posedge clk2) begin
    {data2_reg[1], data2_reg[0]} <= {data2_reg[0], data2};
  end

  always_ff @(posedge clk3) begin
    {data3_reg[1], data3_reg[0]} <= {data3_reg[0], data3};
  end

  logic [7 : 0] mux_data, mux_data_reg;
  logic         mux_clk ;

  always_comb begin
    unique case(sel)
      2'b01   : {mux_clk, mux_data} = {clk2, data2_reg[1]};
      2'b10   : {mux_clk, mux_data} = {clk3, data3_reg[1]};
      default : {mux_clk, mux_data} = {clk1, data1_reg[1]};
    endcase
  end

  always_ff @(posedge mux_clk) begin
    mux_data_reg <= mux_data;
  end

  //
  // output mapping
 
  logic [7 : 0] data2hi, data2lo ;
  logic         clk2hi,  clk2lo  ;
 
  assign data2hi = mux_data_reg;
  assign data2lo = mux_data_reg;

  generate
    genvar i;
    for (i = 0; i < 8; i++) begin : data_gen
      ddio_out
      ddio_out
      (
        .aclr     ( 1'b0        ) ,
        .datain_h ( data2hi [i] ) ,
        .datain_l ( data2lo [i] ) ,
        .outclock ( mux_clk     ) ,
        .dataout  ( odat    [i] )
      );
    end
  endgenerate 
 
  assign clk2hi = 1'b0;  // inverted
  assign clk2lo = 1'b1;  

  ddio_out
  ddio_out
  (
    .aclr     ( 1'b0    ),
    .datain_h ( clk2hi  ),
    .datain_l ( clk2lo  ),
    .outclock ( mux_clk ),
    .dataout  ( oclk    )
  );

endmodule

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

Вот второй

Причем как видите основной нужный нам констрейн не выполняется.

Но ларчик просто открывался, видя что данные меняются по обоим фронтам клока, TimeQuest находит 2 ре времянки для tsu/th. Он то не знает что нас интересует конкретная ситуация с восходящего фронта до восходящего фронта. А квартус в свою очередь стремиться при разводке выполнить условия по обоим фронтам LaunchClock, именно поэтому констрейны, по нужным нам фронтам не выполняются.  Чтобы ему это объяснить используем наш старый знакомый set_false_path.

set_time_format -unit ns -decimal_places 3
derive_clock_uncertainty

# base clocks
create_clock -period 180MHz -name {clk1} [get_ports {clk1}]
create_clock -period 180MHz -name {clk2} [get_ports {clk2}]
create_clock -period 180MHz -name {clk3} [get_ports {clk3}]

set_false_path -from [get_ports {data1[*] data2[*] data3[*]}] -to [all_clocks]
set_false_path -from [get_ports {sel[*]}]

create_generated_clock      -name oclka -source [get_ports clk1] -invert [get_ports oclk]
create_generated_clock -add -name oclkb -source [get_ports clk2] -invert [get_ports oclk]
create_generated_clock -add -name oclkc -source [get_ports clk3] -invert [get_ports oclk]

set_clock_groups -exclusive -group {clk1 oclka}
set_clock_groups -exclusive -group {clk2 oclkb}
set_clock_groups -exclusive -group {clk3 oclkc}

set DATA_delay_max [expr 7.446*0.007]
set DATA_delay_min [expr 5.178*0.007]
set CLK_delay_max  [expr 13.86*0.007]
set CLK_delay_min  [expr 13.86*0.007]
set Tsu 2.0
set Th  1.5

set usedTsu [expr $DATA_delay_max + $Tsu - $CLK_delay_min]
set usedTh  [expr $DATA_delay_min - $Th  - $CLK_delay_max]

set_output_delay            -clock [get_clocks oclka] -max $usedTsu [get_ports odat[*]]
set_output_delay            -clock [get_clocks oclka] -min $usedTh  [get_ports odat[*]]

set_false_path -setup -fall_from [get_clocks clk1] -rise_to [get_clocks oclka]
set_false_path -hold -fall_from [get_clocks clk1] -rise_to [get_clocks oclka]



set_output_delay -add_delay -clock [get_clocks oclkb] -max $usedTsu [get_ports odat[*]]
set_output_delay -add_delay -clock [get_clocks oclkb] -min $usedTh  [get_ports odat[*]]

set_false_path -setup -fall_from [get_clocks clk2] -rise_to [get_clocks oclkb]
set_false_path -hold -fall_from [get_clocks clk2] -rise_to [get_clocks oclkb]


set_output_delay -add_delay -clock [get_clocks oclkc] -max $usedTsu [get_ports odat[*]]
set_output_delay -add_delay -clock [get_clocks oclkc] -min $usedTh  [get_ports odat[*]]

set_false_path -setup -fall_from [get_clocks clk3] -rise_to [get_clocks oclkc]
set_false_path -hold -fall_from [get_clocks clk3] -rise_to [get_clocks oclkc]

Рассмотрим незнакомые нам строки

set_false_path -setup -fall_from [get_clocks clk3] -rise_to [get_clocks oclkc]
set_false_path -hold -fall_from [get_clocks clk3] -rise_to [get_clocks oclkc]

Я описал что анализировать пути по tsu/th от спадающего фронта clk3 до восходящего фронта oclkc не нужно. И так для всех трех клоков. 

И вуаля, все стало нормальным.

Отсюда третье правило TimeQuest

Если таймквест пишет фигню, то это не значит что он не прав

Комментарии

Отправить комментарий

Содержание этого поля является приватным и не предназначено к показу.
  • Syntax highlight code surrounded by the {syntaxhighlighter SPEC}...{/syntaxhighlighter} tags, where SPEC is a Syntaxhighlighter options string or "class="OPTIONS" title="the title".
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Доступны HTML теги: <a> <p> <span> <s> <strike> <div> <h1> <h2> <h3> <h4> <h5> <h6> <img> <map> <area> <hr> <br> <br /> <ul> <ol> <li> <dl> <dt> <dd> <table> <caption> <tbody> <tr> <td> <em> <b> <u> <i> <strong> <del> <ins> <sub> <sup> <quote> <blockquote> <pre> <address> <code> <cite> <embed> <object> <param> <strike>
  • Использовать как разделитель страниц.

Подробнее о форматировании