mikrocontroller.net

Forum: FPGA, VHDL & Co. Speicherfehler im BlockRAM


Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

hatte schon einmal jemand Speicherfehler im BlockRAM eines Spartan?

Ich instanziiere in meinem Spartan 6 über CoreGen einen Speicher, der in 
der Sekunde ungefähr 2*10^6 Mal ausgelesen wird. Ich stelle nun fest, 
dass von Zeit zu Zeit, spätestens jedoch nach 15 min., ein 
Speicherfehler auftritt. Erst wollte ich das gar nicht glauben, aber 
dann habe ich ein Paritätsbit eingefügt und gesehen, dass tatsächlich 
die Parität in diesen Fällen nicht stimmt. Es kippt übrigens immer ein 
beliebiges Bit um.

Ist es üblich, dass diese Fehler auftreten? 15 min. sind immerhin fast 2 
Millarden Zugriffe. Oder stimmt mein Design einfach nicht?

Grüße
Steffen

Autor: bastler (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Steffen,

und das Timinig deines Designs ist okay ?
Mache mal einen detailierten timing check.

und auch die spannungsversorgung ?

Bei Blockram vielleicht ein Problem durch Dual-Ported zugriff ?

Deine HDL Quellcode und das ucf file waeren bei der Nachstellung des 
Problems hilfreich.

da der sram im blockram nicht viel anders ist der sram fuer die 
konfiguration
(Verbindungen und Logik). Wenn es da ein Problem im sram gibt, wuerde es 
sich auf den gesamten FPGA auswirken.

LG

ein bastler

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Steffen Hausinger schrieb:
> Ist es üblich, dass diese Fehler auftreten?
Nein. Und 2 MHz ist ja nun fast schon beschaulich...

> Oder stimmt mein Design einfach nicht?
Der Verdacht liegt nahe...
Wieviele Takte hat dein Design?

Autor: Steffen Hausinger (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Bastler!

bastler schrieb:
> und das Timinig deines Designs ist okay ?
> Mache mal einen detailierten timing check.

Wie mach ich den? Ich habe es eben mit "Analyze Timing / Floorplan 
Design" in ISE versucht. Bin ich da richtig und wenn ja, was kann ich 
dort tun? Mein Design steuert den Speicher aber eh nur mit 50 MHz an, 
laut Datenblatt sind bis zu 200 MHz möglich.


bastler schrieb:
> und auch die spannungsversorgung ?

Siehe Anhang. Ich musste leider direkt auf der 1,2V und 3,3V Verteilung 
messen, das heißt vor allen Abblockkondensatoren (100µF, 4,7µF und 
470nF)


bastler schrieb:
> Bei Blockram vielleicht ein Problem durch Dual-Ported zugriff ?

Möglich, aber ich greife über den einen Port ausschließlich lesend zu. 
Chipscope zeigt mir zudem, dass das WE-Signal nie aktiviert wird.


bastler schrieb:
> Deine HDL Quellcode und das ucf file waeren bei der Nachstellung des
> Problems hilfreich.

Gerne, ebenfalls siehe Anhang. In der Datei befindet sich die Entity, in 
der ich den Speicher anspreche. Der relevante heißt "WrkPage_RAM". Ich 
hoffe, die Kommentare erklären Aufbau und Zugriff.



Hallo Lothar!

Lothar Miller schrieb:
> Der Verdacht liegt nahe...
> Wieviele Takte hat dein Design?

Wenn Du hier die Taktfrequenz meinst, wovon ich jetzt einmal ausgehe: 
sie beträgt 50 MHz.



Viele Grüße
Steffen

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mir ist eben noch was aufgefallen: ich hatte hier neulich nach der 
Möglichkeit gefragt, auf rising_edge und falling_edge zu synchronisieren 
(siehe Beitrag "Synchronisieren auf rising- und falling-edge"). Nachdem das nicht 
so recht funktionierte, habe ich per DCM den Takt verdoppelt.

Die Folge: Das Signal XBus_Source_IM kommt nun mit der doppelten 
Frequenz (100 MHz) an als der Rest des Speichers (50 MHz). Das wird wohl 
der Fehler sein?

Autor: bastler (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Steffen

Steffen Hausinger schrieb:
> Wie mach ich den? Ich habe es eben mit "Analyze Timing / Floorplan
> Design" in ISE versucht. Bin ich da richtig und wenn ja, was kann ich
> dort tun? Mein Design steuert den Speicher aber eh nur mit 50 MHz an,
> laut Datenblatt sind bis zu 200 MHz möglich.

hm ich bewege mich beim ise eigentlich nur per makefile mit den 
commandline tools, die grafische oberflaeche ist halt nichts fuer einen 
vernuenftigen automatisierten und reproduzierbaren flow

trace -a -e -tsi -u -v "design".ncd "timing_constraints".pcf

im design directory sollte schon mal helfen

LG

ein bastler

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Steffen Hausinger schrieb:
> Die Folge: Das Signal XBus_Source_IM kommt nun mit der doppelten
> Frequenz (100 MHz) an als der Rest des Speichers (50 MHz). Das wird wohl
> der Fehler sein?

Nein, eben ausprobiert: Der Speicherfehler tritt weiterhin auf :-(



bastler schrieb:
> trace -a -e -tsi -u -v "design".ncd "timing_constraints".pcf
>
> im design directory sollte schon mal helfen

Werde ich ausprobieren!

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
bastler schrieb:
> trace -a -e -tsi -u -v "design".ncd "timing_constraints".pcf


Ich komme mit der Command Line leider nicht zurecht. Folgendes konnte 
ich aber über das Menü ausführen:

Command Line: trce -intstyle ise -v 3 -s 2 -n 3 -fastpaths -xml Top_preroute.twx Top_map.ncd -o Top_preroute.twr Top.pcf -ucf Top.ucf
Loading device for application Rf_Device from file '6slx45.nph' in environment
C:\Programme\Xilinx\ISE_DS\ISE\.
   "Top" is an NCD, version 3.2, device xa6slx45, package fgg484, speed -2

Analysis completed Thu Feb 24 21:03:39 2011
--------------------------------------------------------------------------------

Generating Report ...

Number of warnings: 0
Total time: 26 secs 

Process "Generate Post-Map Static Timing" completed successfully


Der Meldung nach ist mein Timing also in Ordnung?

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
bastler schrieb:
> Mein Design steuert den Speicher aber eh nur mit 50 MHz an,
> laut Datenblatt sind bis zu 200 MHz möglich.
Die Frage ist eher, wieviel vorher deine Adresse stabil ist...

Steffen Hausinger schrieb:
> habe ich per DCM den Takt verdoppelt
> Das wird wohl der Fehler sein?
Hast du ein (mindestens das eine) Timing-Constraint auf den 
ursprünglichen 50MHz Takt gesetzt?

Sieh dir in der statischen Timinganalyse mal den kritischen Pfad an...

BTW: sieh dir im Schematic mal an, was daraus gemacht wird
    if XBus_Source_IM = WP then   ...   
    elsif XBus_Source_IM = MP then    ... 
    elsif XBus_Source_IM = ProcRAM_RD then  ....           
    elsif XBus_Source_IM = ExtRAM_RD then   ...        
    elsif XBus_Source_IM = ProcRAM_WR then  ...        
    elsif XBus_Source_IM = ExtRAM_WR then ....
    end if; 
Nicht, dass der Synthesizer hier tatsächliche eine "Logikkette" macht...

Autor: s6 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

schau dir mal das Errata zum Spartan6 an. Es gibt Probleme mit Blkrams. 
Schau mal ob dein Betriebsmodus zutrifft.

http://www.xilinx.com/support/documentation/spartan-6.htm

Vg

Autor: Georg A. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
War da nicht mal was, dass selbst das reine Auslesen zerstörend sein 
kann, wenn die Adressen nicht ausreichend Setup/Hold einhalten?

Aja, S6 Block RAM Resources User Guide, S11:

"The setup time of the block RAM address and write enable pins must not 
be violated.Violating the address setup time (even if write enable is 
Low) will corrupt the data contents of the block RAM."

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Bezüglich der möglichen Setup-Verletzung der Adresse: sie wird vorher 
einsynchronisiert. Auch die übrigen Signale des Speicherinterfaces sind 
synchronisiert. Ich gehe davon aus, da die Signale nun intern sind, hat 
ISE sich ums Timing gekümmert. (Unabhängig von diesem Fall bin ich 
ohnehin davon ausgegangen, dass das anders nicht sauber funktionieren 
würde!)


Lothar Miller schrieb:
> Sieh dir in der statischen Timinganalyse mal den kritischen Pfad an...

Ok! Das wird ein wenig dauern, da ich mit dem Tool noch nie gearbeitet 
habe. Ich nehme an, er zeigt mir den kritischen Pfad von selbst an?


s6 schrieb:
> schau dir mal das Errata zum Spartan6 an.

Ich habe nachgeschaut: EN117, EN147 und EN148 treffen nicht zu. Ich habe 
zwar tatsächlich den READ_FIRST Modus eingestellt, verwende aber einen 
gemeinsamen Takt für Port A und B.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Steffen Hausinger schrieb:
> Ich gehe davon aus, da die Signale nun intern sind, hat
> ISE sich ums Timing gekümmert.
???
Freiwillig tut die ISE gar nichts. wenn du willst, dass sich da 
irgendwer um irgendwas kümmert, dann mußt du Constraints angeben. Sonst 
hat die Toolchain alle Freiheiten...  :-o

Das war es, warum Lothar Miller schrieb:
>>> Hast du ein ... Timing-Constraint auf den ... 50MHz Takt gesetzt?

Autor: T. M. (xgcfx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lothar Miller schrieb:
> Freiwillig tut die ISE gar nichts. wenn du willst, dass sich da
> irgendwer um irgendwas kümmert, dann mußt du Constraints angeben. Sonst
> hat die Toolchain alle Freiheiten...  :-o

Naja. Also Timing constraints (ausser Clock Frequenz, das ist klar) auf 
rein interne Signale habe ich auch noch nie erstellt. Ich weiß auch gar 
nicht, ob man Set/Hold Zeiten überhaupt für solche Signale einstellen 
kann. IMHO ist das nämlich etwas, was man dem ISE überlassen können 
sollte. Ansonsten müsste man ja für jedes Register solche Constraints 
angeben, wenn ISE die nicht selber kennen würde. Für Signale von/nach 
außen gibt man sie ja deshalb an, weil das Tool eben keine Informationen 
zu den zeitlichen Begebenheiten außerhalb des Chips hat, der Entwickler 
aber schon.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Torsten M. schrieb:
> ausser Clock Frequenz, das ist klar
Diesen Period Constraint hatte ich gemeint. Beim Verdoppeln dieser 
Frequenz mit dem Taktmanager wird automatisch ein Constraint auf die 
"neue" Frequenz erzeugt.

Du wirst dein Design mal bis auf den Fehler abspecken müssen...  :-/


BTW:
Entweder die (genormte) obere Lib oder
die (etablierten alten herstellerabhängigen) unteren beiden Libs:
use IEEE.NUMERIC_STD.all;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
Aber niemals beide zusammen...

Autor: Duke Scarring (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Steffen Hausinger schrieb:
> dann habe ich ein Paritätsbit eingefügt und gesehen, dass tatsächlich
> die Parität in diesen Fällen nicht stimmt. Es kippt übrigens immer ein
> beliebiges Bit um.
Kippt das Bit auch wieder zurück, oder ist der Speicherinhalt dann 
komplett falsch?

> Ist es üblich, dass diese Fehler auftreten?
Nein.

> 15 min. sind immerhin fast 2
> Millarden Zugriffe. Oder stimmt mein Design einfach nicht?
Wenn das Design flasch wäre, dürfte es eigentlich keine 100 ms laufen.
Ich würde mich auch nochmal auf die Stromversorgung und die Taktquelle 
konzentrieren. Vielleicht kannst Du mit gezielten externen Störungen 
einer mögliche Fehlerursache auf die Spur kommen.

Duke

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich habe mich dieses Wochenende in meinem Kämmerlein eingeschlossen und 
diverse Umbauten ausprobiert. Vorweg, keine hat endgültige Abhilfe 
gebracht :-( Dennoch ist vielleicht folgendes interessant:

Meine Schaltung hat einen Eingangstakt CLK_in von 48 MHz. Diesen habe 
ich bisher auf einen DCM gegeben, der daraus eine Ausgangsfrequenz 
CLK_50MHz von wieder 48 MHz erzeugt (ist historisch gewachsen). Lasse 
ich diesen DCM nun weg und nehme die 48 MHz direkt aus dem Eingangstakt 
CLK_in, dauert es bis zum ersten Speicherfehler statt 20 min. nun 60 
min.

Ist das ein Indiz für ein Timing-Problem?



Lothar Miller schrieb:
>>>> Hast du ein ... Timing-Constraint auf den ... 50MHz Takt gesetzt?

Ja, ist gesetzt.


Duke Scarring schrieb:
> Kippt das Bit auch wieder zurück, oder ist der Speicherinhalt dann
> komplett falsch?

Der Speicherinhalt bleibt fortan gleich. Das gekippte Bit ist also 
tatsächlich gespeichert.


Lothar Miller schrieb:
> Entweder die (genormte) obere Lib oder
> die (etablierten alten herstellerabhängigen) unteren beiden Libs

Danke für den Hinweis!

Autor: D. I. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Steffen Hausinger schrieb:
> Ist das ein Indiz für ein Timing-Problem?

Ziemlich sicher.

Um Hardwarefehler weitestgehend auszuschließen, könntest du ja nen 
kleinen March-Algorithmus schreiben und damit den Speicher befüllen und 
wieder auslesen. Also wirklich nur eine minimal Beschaltung und gucken 
ob du immer das ausliest was du rein geschrieben hast.

Autor: Georg A. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>  Lasse ich diesen DCM nun weg und nehme die 48 MHz direkt aus dem
> Eingangstakt CLK_in, dauert es bis zum ersten Speicherfehler statt
> 20 min. nun 60 min.

Damit hast du wohl etwas weniger Jitter auf dem Takt. Wenn das einen 
Unterschied macht, ist irgendwas mit der Taktverteilung oder der 
Synchronisierung der RAM-Addressen Daten ziemlich daneben. Bist du 
wirklich 1000%ig sicher, dass der Takt, der die Adressen generiert, auch 
der für die Blockrams ist? Notfalls per FPGA-Editor nachschauen...

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
D. I. schrieb:
> Um Hardwarefehler weitestgehend auszuschließen, könntest du ja nen
> kleinen March-Algorithmus schreiben und damit den Speicher befüllen und
> wieder auslesen.

Ich bin gerade dabei, es von der anderen Seite zu probieren: Schaltung 
schlanker machen, bis der Fehler nicht mehr auftritt. Wegnehmen ist hier 
einfacher als neu designen ;-)



Georg A. schrieb:
>>  Lasse ich diesen DCM nun weg und nehme die 48 MHz direkt aus dem
>> Eingangstakt CLK_in, dauert es bis zum ersten Speicherfehler statt
>> 20 min. nun 60 min.
>
> Damit hast du wohl etwas weniger Jitter auf dem Takt.

Also da habe ich bisher das genaue Gegenteil vermutet! Durch den DCM 
habe ich doch gerade einen stabileren Takt? Weil er doch durch die DLL 
entkoppelt wird.


Georg A. schrieb:
> Bist du
> wirklich 1000%ig sicher, dass der Takt, der die Adressen generiert, auch
> der für die Blockrams ist?

Seit dem Rausschmiss des DCM habe ich ja nur noch den einen ;-) Und 
trotzdem treten die Fehler weiter auf :-(

Autor: D. I. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Steffen Hausinger schrieb:
> D. I. schrieb:
>> Um Hardwarefehler weitestgehend auszuschließen, könntest du ja nen
>> kleinen March-Algorithmus schreiben und damit den Speicher befüllen und
>> wieder auslesen.
>
> Ich bin gerade dabei, es von der anderen Seite zu probieren: Schaltung
> schlanker machen, bis der Fehler nicht mehr auftritt. Wegnehmen ist hier
> einfacher als neu designen ;-)

Das ist ne kleine Mini-FSM in diesem Fall, das sollte mit 100 Zeilen 
maximal erledigt sein. Erst neulich habe ich ein Paper zu Speichertests 
gelesen. Da stehen dann ein paar Algorithmen wie man bestimme Fehler 
erkennen kann, mal gucken ob ichs wieder finde, das ist dann aber schon 
fortgeschrittener.

Autor: Duke Scarring (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
D. I. schrieb:
> Paper zu Speichertests
> gelesen. Da stehen dann ein paar Algorithmen wie man bestimme Fehler
> erkennen kann, mal gucken ob ichs wieder finde,

Das würde mich auch mal interessieren.

Duke

Autor: D. I. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert

Autor: Duke Scarring (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke. Das ist ja doch etwas umfangreich. Mal sehen ob ich die nächsten 
Tage Zeit finde, mir das zu Gemüte zu führen.

Duke

Autor: berndl (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Steffen Hausinger schrieb:
> Ist das ein Indiz für ein Timing-Problem?
>
>
> Duke Scarring schrieb:
>> Kippt das Bit auch wieder zurück, oder ist der Speicherinhalt dann
>> komplett falsch?
>
> Der Speicherinhalt bleibt fortan gleich. Das gekippte Bit ist also
> tatsächlich gespeichert.

dann ist die Wahrscheinlichkeit fuer ein Timing-Problem seeeehr hoch. 
Ueberlege mal wie eine 6T SRAM-Zelle aussieht und denke dir die 
Verschaltung mit Word- und Bitlines.
Wenn deine Wordline ein Timing-Problem hat (==READ Adresse), dann 
zerschiesst es dir deinen Speicher, der ueberschreibt sich beim lesen 
selber...

Autor: berndl (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
und uebrigens:
Poste doch mal die Zeile aus deinem .ucf File, wo du die Clock auf eine 
bestimmte Frequenz vorgibst.
Und, da du ja wohl bisher noch nie mit static-timing-analysis gearbeitet 
hast, auch mal den ersten Pfad dieser Clock der im timing-report 
ausgegeben wird.
Mir scheint, dein Place+Route schert sich nicht um deine 
Clockvorgaben/-wuensche.

Autor: berndl (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Duke Scarring schrieb:
> Danke. Das ist ja doch etwas umfangreich. Mal sehen ob ich die nächsten
> Tage Zeit finde, mir das zu Gemüte zu führen.
>
> Duke

wenn's kuerzer sein soll, dann schau dir mal die ~30 Folien an:
http://www.ece.uc.edu/~wjone/Memory.pdf
Ist als Einstieg kurz und knackig...

Autor: Steffen Hausinger (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
berndl schrieb:
> Poste doch mal die Zeile aus deinem .ucf File, wo du die Clock auf eine
> bestimmte Frequenz vorgibst.

Gerne, siehe hier:
NET "CLK_in" TNM_NET = "CLK_in";
TIMESPEC TS_CLK_in = PERIOD "CLK_in" 48 MHz HIGH 50% INPUT_JITTER 2.1 ps;

Der Quarz-Oszillator (SG-310SCF) hat eine Frequenztoleranz von +-100 
ppm. Macht bei f = 48 MHz bzw. T = 20.833 ns einen Jitter von max. 
+-2,083 ps.


berndl schrieb:
> Und, da du ja wohl bisher noch nie mit static-timing-analysis gearbeitet
> hast, auch mal den ersten Pfad dieser Clock der im timing-report
> ausgegeben wird.

Du meinst vermutlich das hier (der ganze Ausschnitt ist im Anhang):
================================================================================ 
 Timing constraint: TS_CLK_in = PERIOD TIMEGRP "CLK_in" 48 MHz HIGH 50% INPUT_JITTER 0.0021 ns; 
  7377055 paths analyzed, 15710 endpoints analyzed, 0 failing endpoints 
  0 timing errors detected. (0 setup errors, 0 hold errors, 0 component switching limit errors) 
  Minimum period is  19.557ns. 
 -------------------------------------------------------------------------------- 


Für mich sieht es so aus, als wenn P&R meine Constraints schon 
ordnungsgemäß umgesetzt hätte. Stimmt das?

Autor: Duke Scarring (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Steffen Hausinger schrieb:
> Für mich sieht es so aus, als wenn P&R meine Constraints schon
> ordnungsgemäß umgesetzt hätte. Stimmt das?
 All constraints were met. 

 ...
  
 Timing summary: 
 --------------- 
  
 Timing errors: 0  Score: 0  (Setup/Max: 0, Hold: 0) 
Sieht eigentlich gut aus.

Duke

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [vhdl]VHDL-Code[/vhdl]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.