TimeQuest для чайников. Приложение 2 (Хак для System Synchronus Ouput на cycloneIII)
- ПЛИС |
- TimeQuest |
- cyclone |
- constrains |
- altera
Решал недавно одну непростую задачу, танцевал с бубном достаточно долго. Итак имеем 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
Если таймквест пишет фигню, то это не значит что он не прав
- блог пользователя des00
- 17120 просмотров
Новые записи в блогах
- Устранение дребезга контактов на основе вертикальных счетчиков
- Диагностика Imprecise Bus Faults в микроконтроллерах Cortex-M3/M4/M4F
- Self-powered камера
- Фоновый модулятор: беспроводная связь из ничего (перевод)
- Texas Instruments Analog Applications Journal SLYT612 "Снижение искажений в аналоговых КМОП ключах" (перевод)
- USB MSD. Часть 6. Команды SCSI (перевод)
- USB MSD. Часть 3. USB класс накопителей данных (перевод)
- Texas Instruments Application Report SBAA042 "Кодовые схемы, используемые в аналогово-цифровых преобразователях" (перевод)
- 10 принципов правильного интерфейса
- Релиз SDK на русский микропроцессор КРОЛИК
Комментарии
Отправить комментарий