01signal.com

Quartus: Register in I/O-Zellen unterbringen

Oft ziehe ich es vor, das I/O-Timing so zu handhaben, dass alle Register in den I/O-Zellen untergebracht werden. Zumindest dort, wo das Timing eine Rolle spielt.

Wie es scheint, ist das Ablegen von Registern in I/O-Zellen (I/O register packing) bei Quartus nicht die Standardeinstellung. Wie auch immer, hier ist das Rezept für alle, die es sich leicht machen wollen.

In einer früheren Version dieses Beitrags habe ich vorgeschlagen, die Timing-Prüfung für alle I/Os zu deaktivieren. Dadurch wird die Warnung wegen nicht eingeschränkter Pfade während der Implementierung unterdrückt, und insbesondere verhindert es, dass der Abschnitt „TimeQuest Timing Analyzer“ im Berichtsfenster von Quartus rot wird:

set_false_path -from [get_ports]
set_false_path -to [get_ports]

Wie sich herausstellt, ist das keine so gute Idee, insbesondere was Eingangsports betrifft. Weiter unten wird das näher ausgeführt.

Trotzdem muss man den Fitter davon überzeugen, die Register in den I/O-Block zu legen. In die QSF-Datei fügt man Folgendes ein:

set_instance_assignment -name FAST_OUTPUT_REGISTER ON -to *
set_instance_assignment -name FAST_INPUT_REGISTER ON -to *
set_instance_assignment -name FAST_OUTPUT_ENABLE_REGISTER ON -to *

Es ist zwar ziemlich pauschal, diese Zuweisungen auf ausnahmslos jedes Register anzuwenden, aber es erfüllt seinen Zweck. Der Fitter gibt Warnungen für die I/O-Elemente aus, bei denen er diese Constraints nicht durchsetzen kann. Das ist eigentlich eine gute Sache.

Um zu sehen, wie gut es geklappt hat, schaut man im Fitter-Bericht nach dem Abschnitt „Resource Section“ (möglicherweise im Berichtsfenster von Quartus zu finden) und sucht dort nach „Input Registers“ usw., je nachdem, was zutrifft.

Der Unterschied wird in Timing-Reports von Pfaden (paths) deutlich, die I/O-Zellen betreffen. Man vergleiche etwa diesen Pfad, bei dem ein I/O-Register beteiligt ist:

+----------------------------------------------------------------------------------+
; Data Arrival Path                                                                ;
+---------+---------+----+------+--------+-----------------------+-----------------+
; Total   ; Incr    ; RF ; Type ; Fanout ; Location              ; Element         ;
+---------+---------+----+------+--------+-----------------------+-----------------+
; 2.918   ; 2.918   ;    ;      ;        ;                       ; data path       ;
;   0.000 ;   0.000 ;    ;      ; 1      ; DDIOOUTCELL_X3_Y0_N32 ; rst             ;
;   0.465 ;   0.465 ; RR ; CELL ; 1      ; DDIOOUTCELL_X3_Y0_N32 ; rst|q           ;
;   0.465 ;   0.000 ; RR ; IC   ; 1      ; IOOBUF_X3_Y0_N30      ; RESETB~output|i ;
;   2.918 ;   2.453 ; RR ; CELL ; 1      ; IOOBUF_X3_Y0_N30      ; RESETB~output|o ;
;   2.918 ;   0.000 ; RR ; CELL ; 0      ; PIN_P3                ; RESETB          ;
+---------+---------+----+------+--------+-----------------------+-----------------+

Man beachte das DDIOOUTCELL-Element und die Null als Inkrement bei der Verdrahtung zwischen dem Register und dem IOOBUF.

Zum Vergleich hier ein Pfad, bei dem kein I/O-Register verwendet wurde (weil Logik dazwischenlag):

+--------------------------------------------------------------------------------+
; Data Arrival Path                                                              ;
+---------+---------+----+------+--------+-----------------+---------------------+
; Total   ; Incr    ; RF ; Type ; Fanout ; Location        ; Element             ;
+---------+---------+----+------+--------+-----------------+---------------------+
; 8.284   ; 8.284   ;    ;      ;        ;                 ; data path           ;
;   0.000 ;   0.000 ;    ;      ; 1      ; FF_X3_Y0_N17    ; Dir_flop_sig        ;
;   0.496 ;   0.496 ; RR ; CELL ; 8      ; FF_X3_Y0_N17    ; Dir_flop_sig|q      ;
;   2.153 ;   1.657 ; RR ; IC   ; 1      ; IOOBUF_X3_Y0_N9 ; DATA[7]~output|oe   ;
;   8.284 ;   6.131 ; RF ; CELL ; 1      ; IOOBUF_X3_Y0_N9 ; DATA[7]~output|o    ;
;   8.284 ;   0.000 ; FF ; CELL ; 1      ; PIN_T3          ; DATA[7]             ;
+---------+---------+----+------+--------+-----------------+---------------------+

Hier sieht man, wie ein normales Flip-Flop das Signal erzeugt, was zu einer Verdrahtungsverzögerung von 1,657 ns führt. Das Hauptproblem ist, dass diese Verdrahtungsverzögerung bei jedem Implementierungslauf anders ausfällt. Wenn also ein Signalintegritätsproblem auf der Platine besteht, könnte man dem FPGA die Schuld geben, denn verschiedene FPGA-Designversionen scheinen das Problem zu beheben oder wieder auftreten zu lassen.

Timing-Constraints

Sowohl die Eingangsports als auch die Ausgangsports sollten enge Timing-Constraints (Zeitvorgaben) erhalten, sodass sie nur durch vollständige Nutzung der I/O-Register erfüllt werden können. Falls etwas mit der gewünschten Unterbringung der Register schiefgeht, erzeugt das nicht nur einen Timing-Fehler, sondern ist auch notwendig, um die minimale Verzögerung vom Eingang zum Register zu erreichen, wie im Folgenden erklärt wird.

Die folgende Betrachtung gilt nur, wenn der Takt, der die Register speist, unmittelbar mit einem externen Takt zusammenhängt (also etwa über eine PLL, die den Takt mit beispielsweise einer ganzen Zahl multipliziert). Wenn der Takt der Register praktisch nichts mit dem externen Takt zu tun hat, wird die Sache deutlich komplizierter, wie in diesem Beitrag besprochen.

Um diesen Punkt zu verdeutlichen, betrachte man den folgenden Verilog-Code:

module top
  (
   input        clk,
   input        in,
   output reg   out
   );

   reg 		in_d, in_d2;
   wire  	pll_clk;

   always @(posedge pll_clk)
     begin
	in_d <= in;
	in_d2 <= in_d;
	out <= in_d2;
     end

  /* Here comes an instantiation of a phase-compensating PLL, which
     doesn't change the frequency */
endmodule

Man betrachte außerdem die folgende Zeitvorgabe in der SDC-Datei:

create_clock -name main_clk -period 10 -waveform { 0 5 } [get_ports {clk}]

derive_pll_clocks
derive_clock_uncertainty

set_input_delay -clock main_clk -max 8.5 [get_ports in*]
set_input_delay -clock main_clk -min 0 [get_ports in*]

Wie in diesem Beitrag erklärt, ist set_input_delay die maximale Verzögerung der Signalquelle – von der Taktflanke bis zum gültigen Logikzustand. Da die Taktperiode auf 10 ns gesetzt ist, lässt eine Verzögerungsvorgabe von 8,5 ns noch 1,5 ns bis zur nächsten Taktflanke bei 10 ns übrig. Anders gesagt: Die Setup-Zeit am FPGA-Pin ist so begrenzt, dass sie 1,5 ns nicht überschreiten darf.

Man beachte, dass set_max_delay für diesen Zweck ebenfalls verwendet werden kann (in manchen Fällen ist es der einzige Weg), wie in diesem Beitrag diskutiert.

Eine Kompilierung dieses Designs (zusammen mit der oben gezeigten QSF-Zuweisung FAST_INPUT_REGISTER ON) ergibt im Timing-Report den folgenden Ausschnitt:

+----------------------------------------------------------------------------------+
; Data Arrival Path                                                                ;
+---------+---------+----+------+--------+-------------------+---------------------+
; Total   ; Incr    ; RF ; Type ; Fanout ; Location          ; Element             ;
+---------+---------+----+------+--------+-------------------+---------------------+
; 0.000   ; 0.000   ;    ;      ;        ;                   ; launch edge time    ;
; 0.000   ; 0.000   ;    ;      ;        ;                   ; clock path          ;
;   0.000 ;   0.000 ; R  ;      ;        ;                   ; clock network delay ;
; 8.500   ; 8.500   ; F  ; iExt ; 1      ; PIN_F2            ; in                  ;
; 9.550   ; 1.050   ;    ;      ;        ;                   ; data path           ;
;   8.500 ;   0.000 ; FF ; IC   ; 1      ; IOIBUF_X0_Y22_N15 ; in~input|i          ;
;   9.308 ;   0.808 ; FF ; CELL ; 1      ; IOIBUF_X0_Y22_N15 ; in~input|o          ;
;   9.308 ;   0.000 ; FF ; IC   ; 1      ; FF_X0_Y22_N17     ; in_d|d              ;
;   9.550 ;   0.242 ; FF ; CELL ; 1      ; FF_X0_Y22_N17     ; in_d                ;
+---------+---------+----+------+--------+-------------------+---------------------+

Anders als beim Ausgangsregister taucht in der Liste kein Flip-Flop vom Typ „DDIOINCELL“ auf, sondern etwas, das wie ein normales Flip-Flop aussieht. Man beachte jedoch, dass die Verbindung zu diesem Flip-Flop eine Verzögerung von null aufweist (rot markiert). Das ist ein klares Zeichen dafür, dass Flip-Flop und Eingangspuffer zu einer Einheit verschmolzen sind.

Der Datenblatt-Report für diesen Eingang lautet:

+---------------------------------------------------------------------------------------------------+
; Setup Times                                                                                       ;
+-----------+------------+-------+-------+------------+---------------------------------------------+
; Data Port ; Clock Port ; Rise  ; Fall  ; Clock Edge ; Clock Reference                             ;
+-----------+------------+-------+-------+------------+---------------------------------------------+
; in        ; main_clk   ; 1.282 ; 1.461 ; Rise       ; altpll_component|auto_generated|pll1|clk[0] ;
+-----------+------------+-------+-------+------------+---------------------------------------------+

+-----------------------------------------------------------------------------------------------------+
; Hold Times                                                                                          ;
+-----------+------------+--------+--------+------------+---------------------------------------------+
; Data Port ; Clock Port ; Rise   ; Fall   ; Clock Edge ; Clock Reference                             ;
+-----------+------------+--------+--------+------------+---------------------------------------------+
; in        ; main_clk   ; -0.683 ; -0.862 ; Rise       ; altpll_component|auto_generated|pll1|clk[0] ;
+-----------+------------+--------+--------+------------+---------------------------------------------+

Wie gefordert ist die vom FPGA benötigte Setup-Zeit geringer als die durch das Constraint gesetzte Grenze von 1,5 ns.

Jetzt lockern wir die Eingangs-Setup-Verzögerung um 2 ns, lassen alles andere unverändert und starten die Kompilierung erneut:

set_input_delay -clock main_clk -max 6.5 [get_ports in*]
set_input_delay -clock main_clk -min 0 [get_ports in*]

Der Ausschnitt aus dem Timing-Report sieht nun so aus:

+----------------------------------------------------------------------------------+
; Data Arrival Path                                                                ;
+---------+---------+----+------+--------+-------------------+---------------------+
; Total   ; Incr    ; RF ; Type ; Fanout ; Location          ; Element             ;
+---------+---------+----+------+--------+-------------------+---------------------+
; 0.000   ; 0.000   ;    ;      ;        ;                   ; launch edge time    ;
; 0.000   ; 0.000   ;    ;      ;        ;                   ; clock path          ;
;   0.000 ;   0.000 ; R  ;      ;        ;                   ; clock network delay ;
; 6.500   ; 6.500   ; F  ; iExt ; 1      ; PIN_F2            ; in                  ;
; 8.612   ; 2.112   ;    ;      ;        ;                   ; data path           ;
;   6.500 ;   0.000 ; FF ; IC   ; 1      ; IOIBUF_X0_Y22_N15 ; in~input|i          ;
;   7.308 ;   0.808 ; FF ; CELL ; 1      ; IOIBUF_X0_Y22_N15 ; in~input|o          ;
;   8.370 ;   1.062 ; FF ; IC   ; 1      ; FF_X0_Y22_N17     ; in_d|d              ;
;   8.612 ;   0.242 ; FF ; CELL ; 1      ; FF_X0_Y22_N17     ; in_d                ;
+---------+---------+----+------+--------+-------------------+---------------------+

Wie bitte? Die Verdrahtungsverzögerung (Interconnect) ist plötzlich auf 1,062 ns gestiegen?! Man beachte, dass sich die Platzierung des Registers nicht geändert hat, es besteht also kein Zweifel, dass in_d ein I/O-Register ist. Wo kommt diese Verzögerung also her?

Um das zu beantworten, muss man sich das Design genauer ansehen. Nach einer vollständigen Kompilierung und der Auswahl von Tools > Netlist Viewers > Technology Map Viewer (Post-Fitting) erscheint das folgende Diagramm (unten teilweise zu sehen, zum Vergrößern anklicken):

Design diagram

Wenn man mit der rechten Maustaste auf in_d (das Register) klickt und dort Locate Node > Locate in Resource Property Editor wählt, erscheint Folgendes (zum Vergrößern anklicken):

Property editor view

Rechts neben dieser Zeichnung (oben nicht dargestellt) ist die Eigenschaft „Input Pin to Input Register Delay“ auf 2 gesetzt. Das ist der Grund für die Verzögerung. Bevor das Constraint gelockert wurde, stand sie auf 0. Die unmittelbare Lehre daraus lautet:

Wenn das Setup-Constraint nicht auf den technisch bestmöglichen Wert gesetzt wird, kann Quartus eine Verzögerung einfügen – und das geht zulasten dieser Reserve.

Aber warum, Quartus, warum?

Man fragt sich vielleicht, warum Quartus diese Verzögerung zwischen dem Eingangs-Pad und dem Register einfügt. War es nicht der ganze Sinn, so früh wie möglich abzutasten? Zur Beantwortung schauen wir uns den aktualisierten Datenblatt-Report an:

---------------------+
; Data Port ; Clock Port ; Rise  ; Fall  ; Clock Edge ; Clock Reference                             ;
+-----------+------------+-------+-------+------------+---------------------------------------------+
; in        ; main_clk   ; 2.205 ; 2.523 ; Rise       ; altpll_component|auto_generated|pll1|clk[0] ;
+-----------+------------+-------+-------+------------+---------------------------------------------+

+-----------------------------------------------------------------------------------------------------+
; Hold Times                                                                                          ;
+-----------+------------+--------+--------+------------+---------------------------------------------+
; Data Port ; Clock Port ; Rise   ; Fall   ; Clock Edge ; Clock Reference                             ;
+-----------+------------+--------+--------+------------+---------------------------------------------+
; in        ; main_clk   ; -1.570 ; -1.882 ; Rise       ; altpll_component|auto_generated|pll1|clk[0] ;
+-----------+------------+--------+--------+------------+---------------------------------------------+

Zur Erinnerung: Von der Verzögerungsvorgabe wurden 2 ns abgezogen. Die maximal zulässige Setup-Zeit ist damit von 1,5 ns auf 3,5 ns gestiegen. Man sieht leicht, dass diese Anforderung erfüllt wird, mit einem Slack von fast 1 ns.

Quartus dachte sich also ungefähr: „Die Setup-Anforderung kann ich mit einem Überschuss von 2 ns leicht erfüllen. Dann gebe ich mal 1 ns extra zur Setup-Zeit und 1 ns zur Hold-Zeit-Anforderung (die bei 0 ns liegt).“ Und tatsächlich verbesserte sich die Hold-Zeit durch die Hinzunahme dieser 1,062 ns von -0,683 ns auf -1,570 ns (und bitte hackt nicht auf mir herum, warum die Differenz nicht exakt stimmt).

Unterm Strich hat Quartus die Marge für Setup und Hold vergrößert und damit den Eingang robuster gegenüber Jitter (jitter) gemacht. Das ist zwar eine recht vernünftige Sache, aber oft weder gewünscht noch erwartet.

Fazit: Wenn man die absolut minimale Verzögerung vom Eingang zum Register erreichen will, startet man eine Kompilierung mit einer Verzögerungsvorgabe, die nicht erfüllt werden kann, und lockert die Vorgabe danach gerade so weit, dass der Fehler verschwindet. So wird sichergestellt, dass Quartus nicht versucht, das Timing durch Einfügen dieser Eingangsverzögerung zugunsten einer besseren Hold-Zeit zu „verbessern“.

DDR-Primitive verwenden

Die FPGAs von Intel besitzen Logik in oder nahe den I/O-Zellen, die dafür vorgesehen ist, Ausgänge zu erzeugen und Eingänge mit doppelter Taktfrequenz abzutasten. Dieses Thema ist in dem entsprechenden Benutzerhandbuch ug_altddio.pdf ausführlich beschrieben. Eine DDR-Primitive (primitive) zu instanziieren (oder die ALTDDIO_BIDIR-Megafunktion zu verwenden) ist ein verlockender Weg, die Werkzeuge dazu zu bringen, die Register in die I/O-Zellen zu legen. Es ist aber nicht unbedingt eine gute Idee.

Eine Instanziierung wie die folgende etwa:

altddio_bidir ioddr
 (
 .padio(pin),
 .aclr (1'b0),
 .datain_h(datain_h),
 .datain_l(datain_l),
 .inclock(clk),
 .oe(oe),
 .outclock(clk),
 .dataout_h(dataout_h),
 .dataout_l(dataout_l),
 .oe_out (),
 .aset (1'b0),
 .combout(),
 .dqsundelayedout(),
 .inclocken(1'b1),
 .outclocken(1'b1),
 .sclr(1'b0),
 .sset(1'b0));
 defparam
   ioddr.extend_oe_disable = "OFF",
   ioddr.implement_input_in_lcell = "OFF",
   ioddr.intended_device_family = "Cyclone IV E",
   ioddr.invert_output = "OFF",
   ioddr.lpm_hint = "UNUSED",
   ioddr.lpm_type = "altddio_bidir",
   ioddr.oe_reg = "REGISTERED",
   ioddr.power_up_high = "OFF",
   ioddr.width = 1;

Das führt tatsächlich zu Logik, die eine bidirektionale DDR-Schnittstelle implementiert – allerdings mit nur teilweisem Erfolg, was das Timing betrifft, zumindest bei Cyclone IV. Die Clock-to-Output-Zeit ist exakt dieselbe wie bei einem einfachen Ausgangsregister, das in die I/O-Zelle gelegt wurde, aber die Verzögerung auf dem Eingangspfad ist mit der obigen Instanziierung (instantiation) sogar schlechter. Bei anderen Intel-FPGA-Familien können die Ergebnisse anders ausfallen.

Man beachte: Um mit einer DDR-Primitive einfache SDR-Register nachzubilden, müssen die Ports datain_h und datain_l mit derselben Leitung verbunden werden, sodass die fallende Taktflanke nichts ändert. Ebenso sollte der Wert des Ports dataout_l ignoriert werden, da dieser mit der fallenden Flanke abgetastet wird. Außerdem ist der Output-Enable-Port (oe) ein SDR-Eingang – soweit ich das verstehe, ist es bei Intel-FPGAs nicht möglich, High-Z mit DDR-Rate ein- und auszuschalten. Zumindest nicht mit den mitgelieferten Logik-Primitiven (primitives).

Jetzt dazu, warum es bei den Ausgangsregistern gut funktioniert hat und beim Eingang nicht: Der Hinweis steht in den Timing-Reports oben. Selbst für ein einfaches I/O-Zellen-Register wird eine DDIOOUTCELL_Xn_Ym_Nk-Komponente als Register verwendet. Mit anderen Worten: Das DDR-Ausgangsregister wird selbst für Single-Rate-Ausgänge genutzt, allerdings nur mit einer Taktflanke. Beim Eingangspfad dagegen zeigen die Timing-Reports oben, dass ein Register in der Logikstruktur (logic fabric) verwendet wird, also FF_Xn_Ym_Nk. Und genau hier liegt der Haken: Die DDR-Eingangslogik wird ebenfalls in der Logikstruktur implementiert. Und um es noch schlimmer zu machen: Bei DDR werden kombinatorische Blöcke (combinatorial logic blocks) zwischen die I/O-Zelle und das Flip-Flop gequetscht. Ich verstehe ehrlich gesagt nicht, warum, denn jeder solcher kombinatorischer Block ist nur eine Durchschaltung von einem einzelnen Eingang auf einen einzelnen Ausgang.

Diese Beobachtungen werden sowohl durch die Timing-Reports als auch durch die Darstellungen im Post-Fit Technology Map Viewer von Quartus gestützt. Insbesondere diese nutzlosen kombinatorischen Blöcke sind in diesen Informationsquellen deutlich zu erkennen.

Das ganze Thema hängt höchstwahrscheinlich von der FPGA-Familie ab. Bei Cyclone IV ergibt es nur Sinn, DDR-Primitive für Ausgänge zu verwenden.

Noch wichtiger: Die Tatsache, dass bei einem benötigten Ausgangsregister ein DDR-Primitivausgang verwendet wird, ermöglicht es, einen Ausgangstakt zu erzeugen, der zu den anderen Ausgängen ausgerichtet ist. Dazu speist man ein DDR-Ausgangsprimitiv am datain_h-Port mit konstant „1“ und am datain_l-Port mit konstant „0“. Für die anderen Ausgänge verlangt man die Nutzung des Register-Packings im Ausgang. Das Umschalten der anderen Ausgänge ist dadurch auf die steigende Flanke des Takts ausgerichtet, der vom DDR-Ausgang kommt.

Nun, fast. Die Timing-Analyse eines Ausgangstakts ist anders, weil der Takt einen Multiplexer umschaltet, der auswählt, welches der beiden Ausgangsregister den Ausgang speist (für Details horizontal scrollen):

+------------------------------------------------------------------------------------------------------------------------------------+
; Data Arrival Path                                                                                                                  ;
+---------+---------+----+------+--------+-------------------------+-----------------------------------------------------------------+
; Total   ; Incr    ; RF ; Type ; Fanout ; Location                ; Element                                                         ;
+---------+---------+----+------+--------+-------------------------+-----------------------------------------------------------------+
; 0.000   ; 0.000   ;    ;      ;        ;                         ; launch edge time                                                ;
; 0.000   ; 0.000   ;    ;      ;        ;                         ; clock path                                                      ;
;   0.000 ;   0.000 ; R  ;      ;        ;                         ; clock network delay                                             ;
; 0.000   ; 0.000   ; R  ;      ; 1      ; PIN_B12                 ; osc_clock                                                       ;
; 5.610   ; 5.610   ;    ;      ;        ;                         ; data path                                                       ;
;   0.000 ;   0.000 ; RR ; IC   ; 1      ; IOIBUF_X19_Y29_N8       ; osc_clock~input|i                                               ;
;   0.667 ;   0.667 ; RR ; CELL ; 2      ; IOIBUF_X19_Y29_N8       ; osc_clock~input|o                                               ;
;   0.853 ;   0.186 ; RR ; IC   ; 1      ; CLKCTRL_G12             ; osc_clock~inputclkctrl|inclk[0]                                 ;
;   0.853 ;   0.000 ; RR ; CELL ; 165    ; CLKCTRL_G12             ; osc_clock~inputclkctrl|outclk                                   ;
;   1.971 ;   1.118 ; RR ; IC   ; 1      ; DDIOOUTCELL_X16_Y29_N11 ; sram_controller_ins|ddr_clk|auto_generated|ddio_outa[0]|muxsel  ;
;   3.137 ;   1.166 ; RR ; CELL ; 1      ; DDIOOUTCELL_X16_Y29_N11 ; sram_controller_ins|ddr_clk|auto_generated|ddio_outa[0]|dataout ;
;   3.137 ;   0.000 ; RR ; IC   ; 1      ; IOOBUF_X16_Y29_N9       ; sram_clk~output|i                                               ;
;   5.610 ;   2.473 ; RR ; CELL ; 1      ; IOOBUF_X16_Y29_N9       ; sram_clk~output|o                                               ;
;   5.610 ;   0.000 ; RR ; CELL ; 0      ; PIN_E10                 ; sram_clk                                                        ;
+---------+---------+----+------+--------+-------------------------+-----------------------------------------------------------------;

Man beachte, dass dies keine Register-zu-Pin-Analyse ist, sondern eine Takt-zu-Pin-Analyse. Ein set_output_delay-Constraint schließt diesen Pfad trotzdem ein. Ein set_max_delay-Constraint von Registern zu Ports wird diesen Pfad jedoch – falls verwendet – nicht einschließen, also muss man ihn separat behandeln. Mit anderen Worten: Wenn set_max_delay verwendet wird, muss es die folgende Form haben:

set_max_delay -from [get_clocks main_clk] -to [get_ports sram_clk] 3.8

Vergleichen wir das nun mit einem anderen Pin mit demselben Spannungsstandard usw., der aber von einem Register angesteuert wird:

+----------------------------------------------------------------------------------------------------------------------+
; Data Arrival Path                                                                                                    ;
+---------+---------+----+------+--------+-------------------------+---------------------------------------------------+
; Total   ; Incr    ; RF ; Type ; Fanout ; Location                ; Element                                           ;
+---------+---------+----+------+--------+-------------------------+---------------------------------------------------+
; 0.000   ; 0.000   ;    ;      ;        ;                         ; launch edge time                                  ;
; 2.507   ; 2.507   ;    ;      ;        ;                         ; clock path                                        ;
;   0.000 ;   0.000 ;    ;      ;        ;                         ; source latency                                    ;
;   0.000 ;   0.000 ;    ;      ; 1      ; PIN_B12                 ; osc_clock                                         ;
;   0.000 ;   0.000 ; RR ; IC   ; 1      ; IOIBUF_X19_Y29_N8       ; osc_clock~input|i                                 ;
;   0.667 ;   0.667 ; RR ; CELL ; 2      ; IOIBUF_X19_Y29_N8       ; osc_clock~input|o                                 ;
;   0.853 ;   0.186 ; RR ; IC   ; 1      ; CLKCTRL_G12             ; osc_clock~inputclkctrl|inclk[0]                   ;
;   0.853 ;   0.000 ; RR ; CELL ; 165    ; CLKCTRL_G12             ; osc_clock~inputclkctrl|outclk                     ;
;   1.970 ;   1.117 ; RR ; IC   ; 1      ; DDIOOUTCELL_X37_Y29_N11 ; sram_controller_ins|dq_wr_data[6]|clk             ;
;   2.507 ;   0.537 ; RR ; CELL ; 1      ; DDIOOUTCELL_X37_Y29_N11 ; sram_controller:sram_controller_ins|dq_wr_data[6] ;
; 5.645   ; 3.138   ;    ;      ;        ;                         ; data path                                         ;
;   2.717 ;   0.210 ;    ; uTco ; 1      ; DDIOOUTCELL_X37_Y29_N11 ; sram_controller:sram_controller_ins|dq_wr_data[6] ;
;   3.182 ;   0.465 ; RR ; CELL ; 1      ; DDIOOUTCELL_X37_Y29_N11 ; sram_controller_ins|dq_wr_data[6]|q               ;
;   3.182 ;   0.000 ; RR ; IC   ; 1      ; IOOBUF_X37_Y29_N9       ; sram_dq[6]~output|i                               ;
;   5.645 ;   2.463 ; RR ; CELL ; 1      ; IOOBUF_X37_Y29_N9       ; sram_dq[6]~output|o                               ;
;   5.645 ;   0.000 ; RR ; CELL ; 1      ; PIN_G14                 ; sram_dq[6]                                        ;
+---------+---------+----+------+--------+-------------------------+---------------------------------------------------;

Die gesamte Clock-to-Output-Zeit unterscheidet sich um höchstens 35 ps, obwohl der letztgenannte Pfad auf den ersten Blick völlig anders ist. Das ist kein Zufall. Das FPGA ist offensichtlich darauf ausgelegt, diese Ähnlichkeit zu erzeugen. Konkret stammt die obige Timing-Analyse aus dem Modell „slow 1200 mV“ bei einer Temperatur von 100 °C, aber diese geringe Differenz zeigt sich auch unter den anderen analysierten Bedingungen.

Diese Seite wurde maschinell aus dem Englischen übersetzt. Im Zweifelsfall siehe den Originaltext.
Copyright © 2021-2026. All rights reserved. (dcc38493)