Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Как в одном клок-домейне сказать Квартусу, что де инструкция может выполняться несколько тактов?
Форум разработчиков электроники ELECTRONIX.ru > Программируемая логика ПЛИС (FPGA,CPLD, PLD) > Работаем с ПЛИС, области применения, выбор
iiv
Всем привет,

есть что-то типа примитивного софт-процессора - CmdAddr - номер выполняемого оператора, Cmd - код оператора, и с десяток различных инструкций.

Если оставить все инструкции и собрать проект, то клок можно выставить только в 50МГц (есть долгие инструкции, в качестве примера, я написал инструкцию деления). Если закомментировать несколько "долгих инструкций", то проект можно собрать с клоком в 200МГц.

Реально я понимаю, что, если я для самого себя условлюсь, что после некоторых инструкций я поставлю 1-2-3 такта пустые инструкции, а длительность выполнения этих инструкций будет не один клок, а 2-3-4, то все будет работать, но вот как это объяснить квартусу?

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

Может есть какой-то простой способ как сказать, что де

инструкция Номер 1 и 2 может работать два такта,
инструкция Номер 3 может работать 3 такта,
инструкия Номер 4 - может работать 4 такта

и пусть квартус не парится и не доводит тайминг этих долгих инструкций до высокой частоты?

Научите, пожалуйста, меня, кто знает, как это сделать!

Спасибо

ИИВ

Код
reg Clk; // клок
reg [31:0] In1Reg;
reg [31:0] In2Reg;
reg [31:0] OutReg;
reg [8:0] CmdAddr;
reg [17:0] CmdA[0:511]; // список команд-операций как-то где-то инициализируемый
reg [17:0] Cmd;

always @(posedge Clk)
begin
   Cmd<=CmdA[CmdAddr+1];
   if(Cmd[17]) CmdAddr<=Cmd[8:0];
   else
   begin
     CmdAddr<=CmdAddr+1;
     case(Cmd[16:14])
       3'b000: OutReg<=In1Reg+In2Reg; // было бы классно выполнять ее 2 такта
       3'b001: OutReg<=In1Reg-In2Reg; // было бы классно выполнять ее 2 такта
       3'b010: OutReg<=In1Reg*In2Reg; // было бы классно выполнять ее 3 такта
       3'b011: OutReg<=In1Reg/In2Reg; // было бы классно выполнять ее 4 такта
       3'b100: OutReg<=In1Reg&In2Reg; // должна выполниться за такт
       3'b101: OutReg<=In1Reg^In2Reg; // должна выполниться за такт
       3'b110: OutReg<=In1Reg|In2Reg; // должна выполниться за такт
       3'b111: // пустой оператор
     endcase
   end
end
Tiro
Цитата(iiv @ Aug 2 2013, 22:23) *
Реально я понимаю, что, если я для самого себя условлюсь, что после некоторых инструкций я поставлю 1-2-3 такта пустые инструкции, а длительность выполнения этих инструкций будет не один клок, а 2-3-4, то все будет работать, но вот как это объяснить квартусу?

Вот тут посмотрите, des00 объясняет про мультициклы. des00 про мультициклы
Джеймс
Цитата(iiv @ Aug 2 2013, 23:23) *
Реально я понимаю, что, если я для самого себя условлюсь, что после некоторых инструкций я поставлю 1-2-3 такта пустые инструкции, а длительность выполнения этих инструкций будет не один клок, а 2-3-4, то все будет работать, но вот как это объяснить квартусу?


В Вашей записи через Case - никак. И это не вопрос constrain-ов.
Как вариант - вместо "умножения звездочкой" и прочих + - / поставить оптимизированные IP-ядра, которые будут работать за несколько тактов и у которых будет сигнал ready на выходе.
iosifk
Цитата(Джеймс @ Aug 3 2013, 10:13) *
В Вашей записи через Case - никак. И это не вопрос constrain-ов.
Как вариант - вместо "умножения звездочкой" и прочих + - / поставить оптимизированные IP-ядра, которые будут работать за несколько тактов и у которых будет сигнал ready на выходе.


либо в дешифраторе инструкций предусмотреть включение таймера на соотв. кол-во тактов и этим таймером приостанавливать счетчик адреса.
yes
ну все равно мультиплексор параллельный, то есть сделайте

always @(posedge Clk)
...
tmp1<=In1Reg*In2Reg;
tmp2<=In1Reg+In2Reg;

always @*
case
3'b000: OutReg<=tmp1
....

и законстрейните мультицайклы с end в tmp1, tmp2 соответственно

Fat Robot
1. Нужно немного переделать модуль:
- сделать промежуточные результаты tmp_add, tmp_sub, tmp_mul, tmp_div, etc комбинаторным присвоением
- указать оптимизатору, чтоб он не выбрасывал эти промежуточные результаты. Можно использовать директиву // synopsys keep (altera поддерживала, кажется). Можно сделать отдельные модули, которые будут вычислять эти промежуточные результаты, и запретить синтезатору их разгруппировку (я бы сделал так)
- в мультиплексоре в регистр записывать эти промежуточные результаты.
- в sdc что-то в таком духе (пути только правильно укажите):

set path_stp 3 # например
set path_hld [expr ${path_stp} - 1]

set_multicycle_path ${path_stp} -setup -end -through [get_net [list {tmp_xx\[*\]}]] -to [get_cells [list {OutReg_reg\[*\]}]]
set_multicycle_path ${path_hld} -hold -end -through [get_net [list {tmp_xx\[*\]}]] -to [get_cells [list {OutReg_reg\[*\]}]]

или
-through [get_pins [list ... если делать отдельные модули для результатов

2. формально у Вас In1Reg, In2Reg и управляющий вход мультиплексора меняются каждый такт. Те несколько тактов, которые вы закладываете на вчисление результата, эти цепи не должны меняться. Вам это придется как-то учитывать за пределами области видимости RTL (например, как Вы верно отметили, в микрокоде).

3. CmdA[CmdAddr+1] - это, конечно, "сильно" в плане временных ограничений, учитывая, что CmdAddr вычисляется на предыдущем такте. Сделайте CmdAddr_plus_one, обновляемый вместе с CmdAddr, и используйте его для выборки след. команды.
Fat Robot
ах, да..

Лучше всего о Synopsys Design Constraints рассказывет, как это ни странно, первоисточник: Synopsys Timing Constraints and Optimization User Guide из SOLD.

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

Сейчас немного уперся в то, что не сильно понимаю, почему у меня не работает команда с мультициклом.

Так как мне нужна фактически стейт-машина с микрокодом, а не процессор, поле деятельности как это все модифицировать большое, поэтому я все переписал так, что есть некоторые регистры (я их обозвал GPIO0, GPIO1, ..., GPIO9), которые я как-то инициализирую на каждый такт, и есть несколько результатов операций, которые каждый или почти каждый такт выполняются.

Код
module main(...)


my_cpu my_cpu_module(...);

endmodule


module my_cpu(...)
...
reg signed      [REG_LEN-1:0] GPIO0;
reg signed      [REG_LEN-1:0] GPIO1;
reg signed      [REG_LEN-1:0] GPIO2;
reg signed      [REG_LEN-1:0] GPIO3;
reg signed      [REG_LEN-1:0] GPIO4;
reg signed      [REG_LEN-1:0] GPIO5;
reg signed      [REG_LEN-1:0] GPIO6;
reg signed      [REG_LEN-1:0] GPIO7;
reg signed      [REG_LEN-1:0] GPIO8;
reg signed      [REG_LEN-1:0] GPIO9;

// 14 registers
reg signed      [REG_LEN-1:0] RegRes0;
reg signed      [REG_LEN-1:0] RegRes1;
reg signed      [REG_LEN-1:0] RegRes2;
reg signed      [REG_LEN-1:0] RegRes3;
reg signed      [REG_LEN-1:0] RegRes4;
reg signed      [REG_LEN-1:0] RegRes5;
reg signed      [REG_LEN-1:0] RegRes6;
reg signed      [REG_LEN-1:0] RegRes7;
reg signed      [REG_LEN-1:0] RegRes8;
reg signed      [REG_LEN-1:0] RegRes9;
reg signed      [REG_LEN-1:0] RegRes10;
reg signed      [REG_LEN-1:0] RegRes11;
reg signed      [REG_LEN-1:0] RegRes12;

my_div my_dive_module(.clock(Clk), .denom(GPIO6), .numer(GPIO9), .quotient(RegRes9), .remain(RegRes10));  // должно вычисляться за 8 тактов
my_mult my_mult_module(.clock(Clk), .dataa(GPIO5), .datab(GPIO8), .result(RegRes8));  // должно вычисляться за два такта
my_sub  my_sub_module(.clock(Clk), .dataa(GPIO1), .datab(GPIO7), .result(RegRes7));  // должно вычисляться за один такт
my_add  my_add_module(.clock(Clk), .dataa(GPIO1), .datab(GPIO7), .result(RegRes6)); // должно вычисляться за один такт
my_compare my_compare_module(.clock(Clk), .dataa(GPIO0), .datab(GPIO2),
   .aeb(CmpFlag[0]), .agb(CmpFlag[1]), .alb(CmpFlag[2])); // должно вычисляться за один такт


always @(posedge Clk)
begin
   RegRes0 <=GPIO0&GPIO2;
   RegRes1 <=GPIO0|GPIO2;
   RegRes2 <=GPIO0^GPIO2;
   RegRes3 <=GPIO3>>1;
   RegRes4 <=GPIO3<<1;
   RegRes5 <=~GPIO4;
// ... и еще всякие операции по перетаскиванию значений из одного регистра в другой на основе микрокода
end
endmodule


При написании констрейна он не признает его, и я все никак не могу понять почему, констрейн написал вроде бы как все здесь советовали, но, провидимому, так до сих пор что-то до конца не понял.

Код
set_multicycle_path 8 -setup -end -from [get_registers [list \
  {my_cpu_module|GPIO0\[*\] my_cpu_module|GPIO1\[*\] my_cpu_module|GPIO2\[*\] \
   my_cpu_module|GPIO3\[*\] my_cpu_module|GPIO4\[*\] my_cpu_module|GPIO5\[*\] \
   my_cpu_module|GPIO6\[*\] my_cpu_module|GPIO7\[*\] my_cpu_module|GPIO8\[*\] \
   my_cpu_module|GPIO9\[*\] }]] \
  -to [get_registers [list {my_cpu_module|RegRes9\[*\] my_cpu_module|RegRes10\[*\]}]]

set_multicycle_path 7 -hold -end -from [get_registers [list \
  {my_cpu_module|GPIO0\[*\] my_cpu_module|GPIO1\[*\] my_cpu_module|GPIO2\[*\] \
   my_cpu_module|GPIO3\[*\] my_cpu_module|GPIO4\[*\] my_cpu_module|GPIO5\[*\] \
   my_cpu_module|GPIO6\[*\] my_cpu_module|GPIO7\[*\] my_cpu_module|GPIO8\[*\] \
   my_cpu_module|GPIO9\[*\] }]] \
  -to [get_registers [list {my_cpu_module|RegRes9\[*\] my_cpu_module|RegRes10\[*\]}]]


Квартус ругается, что де

Warning: Ignored set_multicycle_path at DE0_PWM.SDC(141): Argument <from> is an empty collection
Info: set_multicycle_path 8 -setup -end -from [get_registers [list \
{my_cpu_module|GPIO0[*] my_cpu_module|GPIO1[*] my_cpu_module|GPIO2[*] \
my_cpu_module|GPIO3[*] my_cpu_module|GPIO4[*] my_cpu_module|GPIO5[*] \
my_cpu_module|GPIO6[*] my_cpu_module|GPIO7[*] my_cpu_module|GPIO8[*] \
my_cpu_module|GPIO9[*] }]] \
-to [get_registers [list {my_cpu_module|RegRes9[*] my_cpu_module|RegRes10[*]}]]



вдруг кто-то сможет меня на путь истинный наставить, помогите, пожалуйста, советом!

Спасибо!

ИИВ
Fat Robot
По сути сообщение о том, что квартус не может найти начальные узлы для описания ограничения.
Сразу оговорюсь: я не великий специалист в квартусе, но вот что я вижу:
1. Директивы get_registers в оригинальном sdc нет. Пути тоже смешно обозначены вертикальной чертой. Подозреваю что это что-то самобытное от альтеры (но в сравнении с ucf от x. это все равно прекрасно)
2. Синтезаторы, с которыми мне приходится иметь дело, поступают так: если на этапе elaboration они видят ff, то они добавляют к имени _reg и помещают объект в структуру проекта. Возможно, квартус делает похожие изменения. Попробуйте после синтеза отыскать ваши регистры, набрав в командной строке вариации на тему get_registers [list {my_cpu_module|GPIO0\[*\]}] и get_registers [list {my_cpu_module|GPIO0_reg\[*\]}], или сохранить post-synthesys netlist и посмотреть, как регистры стали называться после синтеза.

Цитата(iiv @ Aug 6 2013, 23:46) *
Warning: Ignored set_multicycle_path at DE0_PWM.SDC(141): Argument <from> is an empty collection
Info: set_multicycle_path 8 -setup -end -from [get_registers [list \
{my_cpu_module|GPIO0[*] my_cpu_module|GPIO1[*] my_cpu_module|GPIO2[*] \
my_cpu_module|GPIO3[*] my_cpu_module|GPIO4[*] my_cpu_module|GPIO5[*] \
my_cpu_module|GPIO6[*] my_cpu_module|GPIO7[*] my_cpu_module|GPIO8[*] \
my_cpu_module|GPIO9[*] }]] \
-to [get_registers [list {my_cpu_module|RegRes9[*] my_cpu_module|RegRes10[*]}]]


Я еще не очень понимаю, как можно в регистр передать прямо из выходного порта модуля.

reg signed [REG_LEN-1:0] RegRes9;

.quotient(RegRes9)

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

yes
советую констрейны (то есть то, что патерн со * что-то нашел) исполнять в TimeQuest-е - эта такая следа, пиктограмма секундомер (по-моему). она (gui) достаточно не превычная в оформлении, но вполне удобная. ну и вообще, если хитрая времянка для альтеры, то гуи таймквеста нужно освоить
iiv
Уважаемые Fat Robot и Yes,

очень Вам благодарен, что помогаете и не оставляете меня наедине с моей незадачкой!

Цитата(Fat Robot @ Aug 7 2013, 14:59) *
reg signed [REG_LEN-1:0] RegRes9;

.quotient(RegRes9)


прошляпил, так как переписывал с обычного синтаксиса (RegRes9<=GPIO6/GPIO9;) в мегафункцию, спасибо, что увидели и подсказали!!!

С констрейнами, похоже я что-то глобально не понимаю. Пытался в таймквесте явно задать, получается, что в "-from" у меня возникает очень длинный список, но все равно не работает.

Понятно, что команда типа

set_multicycle_path 9 -setup -end -from [get_registers *] -to [get_registers *]
set_multicycle_path 8 -hold -end -from [get_registers *] -to [get_registers *]

слаки изгоняет, но это не то, что мне надо sm.gif

То есть я, на сколько смог поразобравшись, понимаю, что во "-from" надо написать что-то типа:

[get_registers [list {my_cpu:my_cpu_module|GPIO6[*] my_cpu:my_cpu_module|GPIO6[*]}]]

а вот для "-to" - что-то связанное с моими выходными проводами (wire).

Причем даже "-from" у меня все еще не до конца правилен, так как если взять то, что у меня во "-from" и в "-to" поставить все возможные регистры -to [get_registers *] - то слаки не исчезают sad.gif

Гуру Квартуса, пожалуйста, помогите!

Спасибо!

ИИВ
Fat Robot
-from {port | pin | clock | instance}
Selects all paths that start from the specified start points. The start points can be input ports of your design, clock pins of flip-flops, clock objects, instances, or a combination of these. Specify a Tcl list.

-through {port |pin | instance | net}
Selects all paths that traverse through the specified points. The points to traverse can be ports of your design, hierarchical pins, pins on a sequential/mapped combinational cells, or sequential/mapped combinational instances or nets. Specify a Tcl list of start points.

-to {port | pin | clock | instance}
Selects all paths that end at the specified end points. The end points can be output ports of your design, clock pins of flip-flops, clock objects, instances, or a combination of these. Specify a Tcl list of end points.

Наверное не стоит пытаться красиво сделать все сразу.
Разберитесь сначала с одной операцией, одной парой операндов и одиним результатом. Например, деление:
из GPIO6 и GPIO9
через my_dive_module/quotient (я подразумеваю, что делитель у Вас чисто комбинаторный)
в RegRes9 или что там будет правильное

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

Подробнее здесь:
http://acms.ucsd.edu/_files/tcoug.pdf

Успехов
iiv
Цитата(Fat Robot @ Aug 7 2013, 17:36) *
-from {port | pin | clock | instance}
...
-through {port |pin | instance | net}
...
-to {port | pin | clock | instance}
...

да, правильно, я это в доках уже много раз видел, и вроде синтаксис тикла тоже минимально мне знаком - я даже когда-то на нем оболочки рисовал, но, тут упорно не понимаю, почему написав туда имя регистра, квартус на синтаксис не ругается, но зависимость с этого регистра не мультициклит.
Timmy
Цитата(iiv @ Aug 6 2013, 23:46) *
set_multicycle_path 7 -hold -end -from [get_registers [list \
{my_cpu_module|GPIO0\[*\] my_cpu_module|GPIO1\[*\] my_cpu_module|GPIO2\[*\] \
my_cpu_module|GPIO3\[*\] my_cpu_module|GPIO4\[*\] my_cpu_module|GPIO5\[*\] \
my_cpu_module|GPIO6\[*\] my_cpu_module|GPIO7\[*\] my_cpu_module|GPIO8\[*\] \
my_cpu_module|GPIO9\[*\] }]] \
-to [get_registers [list {my_cpu_module|RegRes9\[*\] my_cpu_module|RegRes10\[*\]}]][/code]

Здесь ошибка в синтаксисе TCL. Во-первых, строка из фигурных скобочек передаётся в команду list, которая снова заворачивает эту строку в фигурные скобочки, создавая список в списке, и передаёт дальше в get_registers, которая резонно возвращает пустую коллекцию, так как ожидает увидеть список имён, не завёрнутый в скобочки. Во-вторых, внутри фигурных скобочек никакие подстановки не производятся, поэтому ставить обратные слэши перед квадратными скобками в этом случае не нужно. Тут надо либо убрать [list] и бэкслэши, либо убрать фигурные скобки. В среде Timequest можно посмотреть список всех активированных констрейнов с их параметрами, а также, если в TCL-консоли ввести команду puts c какой-нибудь строкой, можно посмотреть, во что эта строка на самом деле превращается. И лучше изучить TCL поближе, там есть много всего интересного.
Кроме того, полезно включить в имена всех мультицикловых сигналов общие суффиксы, уникальные для каждого варианта задержки(на 2,3,4 цикла и т.д), которые и использовать в шаблоне при задании констрейнов.
iiv
Уважаемые друзья,

огромное Вам спасибо за помощь!!!

Действительно было несколько багов - как заметил Timmy - баг в тикле, и баг в том, что мегафункцию я собрал с конвейером, но комментарий Fat Robotа
Цитата
т.е. внутри блока ваш путь будет пролегать от выхода регистра через определенное облако комбинаторной логики к входу следующего регистра.

помог мне над этим задуматься.

Исправив эти мои ошибки мультицикл у меня успешно заработал.

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

Спасибо

ИИВ
Fat Robot
Еще раз:

-through {port |pin | instance | net}
Selects all paths that traverse through the specified points. The points to traverse can be ports of your design, hierarchical pins, pins on a sequential/mapped combinational cells, or sequential/mapped combinational instances or nets. Specify a Tcl list of start points.

нет там указания регистров, а есть комбинаторные цепи.

Вы бы хоть почитали что-то для начала. Хотя бы тот документ, на который я давал ссылку. Без базового понимания задачи интерактивное взаимодействие с вселенским разумом (он же "уважаемые друзья") заведет Вас в тупик. И уже заводит.

Такие дела.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.