Hallo zusammen,
ich muss einen Entwurf von uC auf CPLD umstellen, da im Protokoll einer
seriellen Übertragung im Nachhinein ein paar Randbedingungen
implementiert werden, wie meine Software-Statemachine so groß werden
lässt, dass sie sich auf einem vernünftigen Controller nicht mehr
implementieren lässt (zumindest nicht für die maximale spezifizierte
Taktfrequenz der Schnittstelle).
Es wird nach einer Startmarkierung ein Datenwort variabler Länge
übertragen, diese wird vorher auch nicht bekannt gegeben. Die
Übertragung beginnt mit dem LSB.
Würde ich nun ein normales Schieberegister einsetzen, hätte ich das MSB
"rechtsbündig", müsste aber mitzählen, wo mein LSB ist - doof.
In der uC-Realisierung habe ich es so gemacht, dass ich mit der
Startmarkierung ein Register "A" genullt und ein Register "B" auf 0x0001
gesetzt habe (beide 16bit, beim 16. Takt wird implizit angehalten, da
nur die ersten 12-14 bits interessieren)
Mit jedem Takt wurde dann regA |= regB ausgeführt, wenn die Datenleitung
log. 1 war, ansonsten nix. Danach wurde in jedem Fall regB um eins nach
links geschoben (mit 0en auffüllend). Da nach dem 16. Takt nur noch
Nullen in regB stehen, kann ab dann so viel ge-odert werden wie will...
Das könnte ich zwar auch in den CPLD giessen, aber irgendwie habe ich
das Gefühl, etwas zu übersehen...
Jemand 'ne Idee?
Das ist ein einfaches Schieberegister das du da beschreibst:
signal register : std_logic_vector(16 downto 0);
...
if rising_edge(CLK) then
register <= register(16 downto 0) & input;
Da du jetzt noch implizit zaehlen moechtest, setzt du das Register am
Anfang einfach auf 00000...001 und wartest, bis Bit 16 1 ist.
Da kann ich aber lange warten, da ein Datenwort wie geschrieben VARIABLE
Länge hat.In der Regel wird das initiale 1-Bit also nie ankommen. Bei
einem "einfachen" Schieberegister, wie Du es nennst, steht das zuletzt
reingeschobene Bit auf der 0-Position ("rechtsbündig"), das zuerst
reingeschobene Bit, bei mir das LSB, bei variabler Länge irgendwo.
Bei >=16 Takten wäre das mit Deinem Vorschlag die MSB-Position.
Verstehst' was ich meine?
Nach Deinem Vorschlag müsste ich parallel mitzählen, wo mein LSB gerade
ist und dann entsprechend für die Ausgabe zuweisen. DAS möchte ich
umgehen.
(Die Bits haben festgelegte Bedeutungen und steuern v.a. LEDs an)
Ah okay, woher weisst du die Laenge des Wortes? Dann wenn der Takt
aufhoert zu laufen?
Aber straeube dich nicht so gegen einen Zaehler, von 0 bis 16 zu zaehlen
kostet dich gerade einmal 4 Flipflops / LUTs (Zeit spielt im FPGA ja
keine Rolle), alleine das Schieberegister sind schon 16 Flipflops, da du
am Ende ja parallel auf das gesamte Wort zugreifen willst.
Richtig, aber ein "breites" Register brauche ich ja in jedem Fall, weil
ich, wie Du sagtest, das gesamte Wort brauche (zumindest die ersten
12-14 bits davon).
Das Ende des Datenstroms ist da erreicht, wo die Startbedingung erfüllt
wird (LOAD-Leitung wird nach min. 5 aufeinanderfolgenden 0-Zyklen wieder
1).
Wenn ich Deinen Vorschlag mit Zähler und Decoder (LUT) aufgreife komme
ich zu einer Lösung, die im Prinzip aus 12 D-FlipFlops besteht, die
D-Leitungen hängen zusammen an der seriellen Datenleitung, die Clocks
werden nacheinander durch den aktuellen Zählerstand bestimmt bedient.
Ich muss mir das glaube ich mal aufmalen, auf jeden Fall war das wohl
der entscheidende Tipp, ich hing noch zu sehr meiner ersten Realisierung
in C nach.
Dann habe ich ja auch wieder Ressourcen für die andere Richtung, da
reicht aber ein einfaches Schieberegister, da auch hier das erste
geschobene Bit das LSB ist, dann kommen 11 weitere und der Rest ist mir
egal.
signalregister:std_logic_vector(15downto0);-- 16 Bit breites Register
2
signalcnt:integerrange0to16;
3
...
4
if(start='1')then-- bei Start alles zurücksetzen
5
register<=(others=>'0');
6
cnt<=0;
7
elsifrising_edge(CLK)then
8
if(cnt<16)then-- nur die Bits von 0..15 beackern
9
register(cnt)<=input;-- beim LSB beginnen
10
cnt<=cnt+1;
11
endif;
12
endif;
Dann hast du als Abfallprodukt auch noch den Zähler mit der Anzahl der
übertragenen Bits.
EDIT:
> Das Ende des Datenstroms ist da, wo die Startbedingung erfüllt wird
Das ist ja mal ein krasses Protokoll (Ende wo Start) ;-)
> (LOAD-Leitung wird nach min. 5 aufeinanderfolgenden 0-Zyklen wieder 1).
Also gut: überarbeitet, Schieberegister+Flankenerkennung für die
LOAD-Leitung zugefügt.
1
signalausgaberegister:std_logic_vector(15downto0);-- 16 Bit breites Register
2
signalempfangsregister:std_logic_vector(15downto0);-- 16 Bit breites Register
Das sieht super aus. Bis auf dass theoretisch das letzte interessierende
Bit der Nutzdaten auch gleichzeitig mit dem Startbit auf der
LOAD-Leitung kommen kann.
Dann müsste ich wohl das
empfangsregister (cnt) <= input; -- beim LSB beginnen
cnt <= cnt+1;
ohne die elsif-clause vor die Prüfung der Startbedingung ziehen und noch
ein
if(cnt < 16)
drumlegen.
So habe ich zwar wieder die (in der anfangs von mir angedachten Lösung)
doppelten Register, aber dafür sollten die Ressourcen reichen. Der
andere Ansatz, den ich oben schon nach Jans Anregung weitergedacht habe,
wäre ja Flip-Flop-mäßig wesentlich sparsamer, in Logikblöcken gedacht
(pfui, schematic entry ;) ) bräuchte ich da ja nur die 12 Flipflops für
die Nutzdaten, vier für den 4-Bit-Zähler und ansonsten nur ein Rudel
Logik, die die 4 Bit ausdecodiert und den Clock auf die Flipflops
verteilt.
Zugeben, Dein VHDL-Ansatz sieht wesentlich eleganter aus ;)
Wie sieht's bei dem VHDL-Code eigentlich mit der zeitlichen Parallelität
aus - startsr wird geschoben, mit LOAD aufgefüllt und verglichen - aber
wann? Definiert nach dem Schieben?
Du merkst schon: VHDL war vor einigen Jahren mal kurz Thema im Studium,
seit dem habe ich mich mit nichtprogrammierbarer Elektronik und
Microcontrollern beschäftigt, aber definitiv nicht mit PLDs. Ich glaube,
das letzte PLD, das ich in einem Design verbaut habe, war ein PALCE16V8
um 1999 herum.
> bräuchte ich da ja nur die 12 Flipflops für die Nutzdaten,> vier für den 4-Bit-Zähler und ansonsten nur ein Rudel Logik...
Dann würde dir aber noch der Puffer fehlen, wenn eine Übertragung
beginnt wird sofort das Ausgaberegister=Empfangsregister überschrieben.
> Wie sieht's bei dem VHDL-Code eigentlich mit der zeitlichen Parallelität> aus - startsr wird geschoben, mit LOAD aufgefüllt und verglichen - aber> wann? Definiert nach dem Schieben?
In meinem Beispielcode wird der eingetaktete Wert erst beim nächsten
Takt auf die steigende Flanke geprüft. Das muß noch genau aufs Protokoll
angepasst werden. Das konnte ich aus der Beschreibung noch nicht so ganz
entnehmen.
Z.B. könnte man den einen Takt Latency so wegbekommen:
1
signalstartsr:std_logic_vector(4dowto0):="00000";
2
:
3
startsr<=startsr(3downto0)&LOAD;
4
if(startsr="00000"andLOAD='1')then-- 5 mal 0 und eine 1 ==> Start
5
ausgaberegister<=empfangsregister;
6
cnt<=0;
7
:
> Bis auf dass theoretisch das letzte interessierende> Bit der Nutzdaten auch gleichzeitig mit dem Startbit
Da sollte man mal eine genauere Beschreibung des gesamten Protokolls mit
ein paar Grenzfällen gesehen haben. Es gibt da offenbar einen Takt
(läuft der immer durch?), eine Datenleitung und ein Frame-Signal (LOAD).
Wie spielen die zueinander und miteinander?
Ok, noch mal drübergelesen (hätte ich mich mal eingeloggt, dann könnte
ich meine Postings vielleicht auch mal editieren...naja, morgen ;) )
Also: zuerst brauche ich bei cnt<16 auf jeden Fall mal das Datenbit bei
der (übrigens fallenden) Clock-Flanke.
Parallel dazu darf meinetwegen das startsr behandelt werden.
Die Prüfung, ob startsr = 000001 ist, darf aber erst erfolgen, wenn das
startsr seinen aktuellen Zustand hat - nicht parallel dazu, sonst
besteht doch wohl die Gefahr, dass die Prüfung von startsr während es
verändert wird falsch anschlägt, oder...?
Dann muss das Empfangsregister ins Ausgaberegister gerettet und parallel
dazu cnt auf 0 gesetzt werden.
Preisfrage: reicht es, wenn ich die
if (startsr="000001") then
ausgaberegister <= empfangsregister;
cnt <= 0;
end if;
in einen Prozess kapsele, oder muss das davor ( Empfangsregister
schieben, startsr schieben) auch ein Prozess werden?
Hirn wie Sieb...
Nebenbei: nein, den Puffer bräuchte ich nicht, weil ich 12 unabhängige
D-Flipflops nehmen würde und diese nacheinander clocken, die
Ausgangswerte sind dann zwar asynchron, aber das wäre egal.
Egal, die VHDL-Beschreibung finde ich eleganter.
Das Protokoll sieht aus wie folgt: ein Takt läuft permanent durch.
Data und Load ändern sich mit der steigenden Taktflanke, gesampelt
werden soll auf die Fallende.
Taucht nach min. 5 Taktzyklen Load=0 dort eine 1 auf, kennzeichnet dies
das letzte Bit des aktuellen Wortes auf der Datenleitung, bei der
nächsten(!) fallenden Flanke kann dann das LSB gesampelt werden.
Nach dieser ersten 1 auf Load folgen vier arbiträre Zustände, die nicht
weiter beachtet werden müssen, danach muss Load bis zum Startkriterium 0
sein. Das wird durch Dein startsr-Register ja bereits erfüllt, nur die
Nebenläufigkeit ist mir noch nicht ganz klar.
> oder muss das davor ( Empfangsregister> schieben, startsr schieben) auch ein Prozess werden?
1) Alles, was getaktet ist, muß in einen Prozess.
2) Ein Signal kann nur von 1 Prozess aus geändert werden.
> nein, den Puffer bräuchte ich nicht,
Dann lass ihn weg ;-)
Du kannst es also auch so schreiben:
1
processbegin-- das geht, startsr wird nur hier manipuliert
2
ifrising_edge(CLK)then
3
startsr<=startsr(4downto0)&LOAD;
4
endif;
5
endprocess;
6
7
processbegin
8
ifrising_edge(CLK)then
9
if(cnt<16)then
10
cnt<=cnt+1;-- das geht, cnt wird nur in diesem Prozess manipuliert
-- cnt <= cnt+1; -- das geht nicht, cnt wird in 2 Prozessen geändert
26
endif;
27
endprocess;
Und irgendwie mußt du nach aussen signalisieren, dass ein Wort komplett
empfangen wurde (hier RX_done).
> Egal, die VHDL-Beschreibung finde ich eleganter.
Und portabler. Die kannst du ohne Zahnschmerzen dann auch auf einer ganz
anderen Zielplattform umsetzen.
Danke,
so erscheint's mir dann auch wieder logisch.
Schade, dass man die Prozesse damals zwar erwähnt hat, aber dann doch
immer nur Zustandslogik in den Laborstunden zusammengestrickt hat. Wenn
man's dann nie einsetzen musste...
Den 2. Puffer brauche ich bei Deinem Ansatz aber schon, da habe ich mich
wohl missverständlich ausgedrückt - hier wird ja der Puffer "irgendwo"
genullt und dann mit dem LSB beginnend aufgefüllt. Das LSB steht also
fast den ganzen Zyklus an, das MSB nur für den Rest, der gegen Null
gehen kann, da per Definition nach dem 10., praktisch nach dem 12. Bit
ein neuer Start signalisiert werden darf. Folge wäre, dass die LEDs an
den Registerausgängen unterschiedlich hell wären (sprich: von volle
Helligkeit bis ganz aus)
Ohne 2. Register ginge es nur mit einzelnen D-Flipflops, wobei jedes
erst an "seiner" Bitposition getaktet würde.
Dann würden die Ausgänge zwar asynchron schalten, aber immer die gleiche
Zeitspanne ihren Pegel halten.
> Ohne 2. Register ginge es nur mit einzelnen D-Flipflops, wobei jedes> erst an "seiner" Bitposition getaktet würde.
Genau das wird mit meinem letzten Ansatz gemacht:
Das Empfangsregister selber wird nicht mehr "genullt". Es wird immer
"nur" das aktuell addressierte Bit überschrieben und am Ende der
Übertragung (also bei Erkennen der Startbedingung) das RX-done Flag
gesetzt.
Nee doch nicht ... die Prozesse haben doch keine Sensitivitätsangaben,
die werden doch nach wie vor nebenläufig ausgeführt - oder?!
Eigentlich brauche ich ja nur zwei definiert sequentiell auszuführende
Blöcke:
1. wenn cnt<16 ( empfangsregister(cnt)=LOAD; cnt++ )
startsr shiftleft(1) & LOAD
2. wenn startsr=000001 ( ausgabe=empfangsregister; cnt=0; )
(startsr=000001 war doch richtig, das LSB kommt ja erst beim Takt NACH
dem, in dem LOAD wieder 1 wird)
Wenn 1 und 2 wirklich GLEICHZEITIG bei der fallenden CLK-Flanke
ausgeführt werden wäre das ja absolut richtig, denn startsr ist ja im
MOMENT der Flanke noch x00000 und wird erst durch den Schiebevorgang zu
000001, der GLEICHZEITIG erfolgende Vergleich mit 000001 würde also
negativ ausfallen und Ausgaberegister ebenso wie cnt in Ruhe gelassen.
Wenn ich in normaler, verdrahteter Logik denke (fällt mir immer noch
irgendwie leichter...) hätte ich also ein Schieberegister mit
flankensensitivem Eingang, welches LOAD durchschiebt, da dran hängt ein
Komparator mit 000001 belegt und dessen Match-Ausgang ist am D-Eingang
eines flankensensitiven D-FFs angeschlossen, welches ebenfalls am CLK
hängt.
So eine Schaltung würde den Zustand des Komparators im Moment der
fallenden Flanke latchen, es sei denn, das Schieberegister und der
Komparator sind irre schnell und das FF etwas träge, dann kann es zu
falschen Werten kommen - und genau das ist meine Sorge bei dem
VHDL-Code.
Weisst Du, was ich meine?
> Nee doch nicht ... die Prozesse haben doch keine Sensitivitätsangaben,> die werden doch nach wie vor nebenläufig ausgeführt - oder?!
Das macht zum Glück bei der Synthese nichts aus. Da werden nach Ausgeben
einer freundlichen Warnung die Listen einfach automatisch erweitert.
Für die Simulation sollte allerdings der CLK in die Liste aufgenommen
werden.
BTW:
Der Fehler kommt daher, dass ich getaktete Prozesse gern so schreibe:
1
processbegin
2
waituntilrising_edge(CLK);
3
empfangsregister(cnt)<=input;
4
endprocess;
statt:
1
process(CLK)begin
2
ifrising_edge(CLK)then
3
empfangsregister(cnt)<=input;
4
endif;
5
endprocess;
Aber das wäre für den Anfang etwas verwirrend geswesen ;-)
Man möge mir diese Unzulänglichkeit nachsehen.
Willst du in einer Testbench ein Schieberegister simulieren, oder
willst du eine Testbench für ein Schieberegister realisieren?
Welche Steuersignale hast du ausser dem Takt und den Daten?
EDIT:
Mach doch besser einen neuen Thread auf, als einen alten zu kapern.