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

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

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Syntax highlight code surrounded by the {syntaxhighlighter SPEC}...{/syntaxhighlighter} tags, where SPEC is a Syntaxhighlighter options string or "class="OPTIONS" title="the title".
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <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>
  • Use to create page breaks.

More information about formatting options