Wie bekomme ich folgenden Pseudocode am besten in AVR Assembler
verpackt?
IF (r16 == 0 AND r17 == 0) THEN rjmp target
Grundsätzlich geht es mir um Sprünge, wenn ein AND-Konstrukt wahr ist
und lesbar sollte es auch noch sein. Mit cpi kann man ja leider nur
einen Wert vergleichen.
Mit cp r16,r17 kann ich zwar prüfen, ob sie gleich sind, aber nicht, ob
sie gleich und 0 sind.
Du kannst einfach nach einem Vergleich zum nächsten Vergleich springen.
Oder beide Register verodern und dann springen.
Oder den Test auf 0 mit CPC kaskadieren.
Frank schrieb:> Mit cp r16,r17 kann ich zwar prüfen, ob sie gleich sind, aber nicht, ob> sie gleich und 0 sind.
Du hast Dich aber schon mal mit dem ASM Mnemonics beschäftigt?
Bedingte Spünge, Zero flag etc. sind Dir ein Begriff?
Peter D. schrieb:> Du kannst einfach nach einem Vergleich zum nächsten Vergleich springen.
genau.
tst r16
brne nicht_null
tst r17
brne nicht_null
beide_null
Frank schrieb:> Wie bekomme ich folgenden Pseudocode am besten in AVR Assembler> verpackt?>> IF (r16 == 0 AND r17 == 0) THEN rjmp target>> Grundsätzlich geht es mir um Sprünge, wenn ein AND-Konstrukt wahr ist> und lesbar sollte es auch noch sein. Mit cpi kann man ja leider nur> einen Wert vergleichen.>> Mit cp r16,r17 kann ich zwar prüfen, ob sie gleich sind, aber nicht, ob> sie gleich und 0 sind.
Doch, denn es gibt ja das Zero-Flag
or r16, r17
brne target
Frank schrieb:> Grundsätzlich geht es mir um Sprünge, wenn ein AND-Konstrukt wahr ist
Dafür langt cp r16,r17.
Frank schrieb:> aber nicht, ob sie gleich und 0 sind.
Wenn sie beide Null sind sind sie auch gleich.
LostInMusic schrieb:>>IF (r16 == 0 AND r17 == 0) THEN rjmp target>> clr r0> cp r16, r0> cpc r17, r0> breq target
Guter Trick! Das kann man sogar auch noch für komplexere ANDs erweitern.
1
IF (r0 == 12 AND r1 == 20 AND r2 == 24) THEN rjmp target
Du brauchst nur ein Zwischenregister:
ldi r20, 12
cp r16, r20
ldi r20, 20
cpc r17, r20
ldi r20, 24
cpc r18, r20
breq target
Man kann die "ldi" problemlos dazwischenschieben, weil "ldi" ja keine
Flags verändert.
Fips Asm schrieb:> Falk B. schrieb:>> or r16, r17>> brne target>> ... was aber nur für den Fall von beide Null feststellt daß die Register> gleich sind!
aber genau was er wollte - beide 0
Die Aktion ändert allerdings den Wert in r16, falls man den noch braucht
also ggf. vorher umkopieren.
Sascha
> IF (r16 == 12 AND r17 == 20 AND r18 == 24) THEN rjmp targetLostInMusic schrieb:> Du brauchst nur ein Zwischenregister:> ldi r20, 12> cp r16, r20> ldi r20, 20> cpc r17, r20> ldi r20, 24> cpc r18, r20> breq target
Das ist doch Unsinn.
Lass R16=14 sein -> 14-12 -> Kein Z, Kein C
Lass R17=22 sein -> 22-20 -> Kein Z, Kein C
Wenn R18=24 -> 24-24 -> Z gesetzt -> SPRUNG!
Und das obwohl R16 und R17 ihre Bedingung nicht erfüllen.
Sascha W. schrieb:> aber genau was er wollte - beide 0> Die Aktion ändert allerdings den Wert in r16, falls man den noch braucht> also ggf. vorher umkopieren.
Und genau deshalb braucht
Fips Asm schrieb:> tst r16> brne nicht_null> tst r17> brne nicht_null> hier sind beide_null
auch nicht mehr Code.
Allgemein für den Fall
IF (r16 == 12 AND r17 == 20 AND r18 == 24)
werden r16,r17,r18 nacheinander mit cpi 12,20,24 getestet und bei einer
Ungleichheit rausgesprungen = Bedingung nicht erfüllt. Kürzer geht es
nicht!
Per schrieb:> Frank schrieb:>> aber nicht, ob sie gleich und 0 sind.>> Wenn sie beide Null sind sind sie auch gleich.
Ja.
Aber bei anderen Werten auch.
Ich glaube, dass ich damals das mit XOR gemacht habe.
>Wenn R18=24 -> 24-24 -> Z gesetzt -> SPRUNG!>Und das obwohl R16 und R17 ihre Bedingung nicht erfüllen.
Du hast keine Ahnung. Lies in der Hilfe/Doku nach, wie es sich mit dem
Z-Flag bei "cpc" verhält. Danach verstehst Du, warum meine Variante
funktioniert.
LostInMusic schrieb:> wie es sich mit dem> Z-Flag bei "cpc" verhält
"Previous value remains unchanged when result is zero,
cleared otherwise"
Hast Recht. CPC kann das Z-Flag höchstens löschen. CP kann beides. Böse
Wissenslücke meinerseits. Danke.
Peter D. schrieb:> Noch ne Variante:> movw r25:r24, r17:r16> adiw r25:r24, 0> breq both_zero
Du bist ja immer noch beim Null-Testen.
Es ging doch längst schon um
Frank schrieb:> IF (r16 == x AND r17 == y AND r18 == z) THEN rjmp target
Außerdem brauchts zusätzliche Register.
Das einfache tst mag langweilig ausschaun ist aber am sparsamsten.
Fips Asm schrieb:> Falk B. schrieb:>> or r16, r17>> brne target>> ... was aber nur für den Fall von beide Null feststellt daß die Register> gleich sind!
Das war doch auch die Anforderung?!
Der obige Code tut genau, was gefordert wird. Er springt, sobald einer
der beiden werte nicht null ist. Für den fall r16 == 0 && r17 == 0 ghet
es einfach weiter.
M. H. schrieb:> Fips Asm schrieb:>>> Falk B. schrieb:>>> or r16, r17>>> brne target>>>> ... was aber nur für den Fall von beide Null feststellt daß die Register>> gleich sind!>> Das war doch auch die Anforderung?!> Der obige Code tut genau, was gefordert wird. Er springt, sobald einer> der beiden werte nicht null ist. Für den fall r16 == 0 && r17 == 0 ghet> es einfach weiter.
Ja. Aber speziell nur für den Null-Test.
Nicht für den allgemeineren Vergleich anderer und mehrerer Werte.
Und es wurde schon gesagt, dieser Nulltest hat einen dicken Pferdefuß:
Er kann r16 verändern.
>"Previous value remains unchanged when result is zero, cleared otherwise"
Yes, Sir. Darauf basiert das "richtige" Funktionieren von cpc, sbc und
sbci.
> tst r16> brne nicht_null> tst r17> brne nicht_null> hier sind beide_null
Was auch in dieser Modifikation seinen Zweck erfüllen täte:
tst r16
brne PC+2
tst r17
brne PC+2
rjmp target
LostInMusic schrieb:> Darauf basiert das "richtige" Funktionieren von cpc, sbc und sbci.
CPC und SBC hatte ich bislang nur beim Vergleich /Subtraktion von 16 Bit
Werten im Einsatz. Dabei steht ja eher das C-Flag im Mittelpunkt.
In der Verliebtheit in tricky Lösungen lässt sich übrigens leicht
übersehen, daß das einfache cpi + brne immer noch kürzer UND
register-sparsamer UND vor allem leichter zu überblicken ist. Sir. ;-)
> Falk B. schrieb:> or r16, r17> brne target
Sollte es nicht:
1
or r16, r17
2
beq target
heißen?
Und die Anforderung das auf irgend etwas anderes als auf ›0‹ getestet
werden soll, sowie die Anforderung das Register erhalten bleiben müssen
wurde nirgends gestellt.
Norbert schrieb:> Und die Anforderung das auf irgend etwas anderes als auf ›0‹ getestet> werden soll,Frank schrieb:> Grundsätzlich geht es mir um Sprünge, wenn ein AND-Konstrukt wahr istNorbert schrieb:> sowie die Anforderung das Register erhalten bleiben müssen> wurde nirgends gestellt
Da hast Du Recht. Ist von Fall zu Fall verschieden. Unnötige/unbemerkte
Veränderung bleibt aber unschön und kann von Fall zu Fall zur Falle
werden ...
Fips Asm schrieb:> Unnötige/unbemerkte> Veränderung bleibt aber unschön und kann von Fall zu Fall zur Falle> werden ...
Deshalb muß man sich als Assemblerprogrammierer zuerst mal einige Regeln
aufstellen. Z.B. welche Register als zerstörbare Arbeitsregister
verwendet werden, welche zur Parameterübergabe, welche als Returnwerte
usw.
Man kann sich auch einige Register für Interrupts reservieren, das spart
dann Push/Pop-Orgien.
IF (r16 == 0 AND r17 == 0) THEN rjmp target
Norbert schrieb:> Sollte es nicht:> or r16, r17> beq target> heißen?
breq target. Genau. Nur wenn beide Null sind wird das Z Flag gesetzt und
Target angesprungen. Statt bei brne/breq könnte der Gedankenfehler aber
auch beim (falschen) target gelegen haben.
Peter D. schrieb:> Deshalb muß man sich als Assemblerprogrammierer zuerst mal einige Regeln> aufstellen. Z.B. welche Register als zerstörbare Arbeitsregister> verwendet werden, welche zur Parameterübergabe, welche als Returnwerte> usw.
Behutsame Register-Spezialisierung tut sicher ganz gut, das kann aber
schnell in unflexibel und unnötig einschränkend ausarten, womit man sich
einiger Freiheiten beraubt die gerade das Assembler-Programmieren so
anregend macht.
Es gilt einfach die Übersicht zu bewahren!
Peter D. schrieb:> Man kann sich auch einige Register für Interrupts reservieren, das spart> dann Push/Pop-Orgien.
Ich sichere dazu X,Y,Z (die Perlen unter den Registern) schnellstmöglich
(denn Speed hat hier Priorität) mit movw in r10-r15 und versuche dann
damit im Interrupt auszukommen. Oft gelingts.
>das kann aber schnell in unflexibel und unnötig einschränkend ausarten, womit man
sich einiger Freiheiten beraubt ..
Man muss sich zu jedem Zeitpunkt über die Register-Belegung im klaren
sein, um nicht hier und da Register mit Werten (falsch) zu überschreiben
und damit Fehler zu produzieren. (Bsp. ein paar Register nur für Temp.
reservieren)
Fips Asm schrieb:> Es gilt einfach die Übersicht zu bewahren!
Ohne Regeln ist es nur ungemein schwerer, die Übersicht zu behalten und
die Fehlerhäufigkeit steigt stark an. Man ist dann nur am Fehler
ausbessern und der Code ist kaum wartbar und erweiterbar.
Oder der Code besteht zu >50% aus Push/Pop, weil ja jedes Register
woanders auch verwendet werden könnte.
Ich würde Assembler aber auch nicht mehr für produktiven Code verwenden,
das ist einfach zu aufwendig und zeitraubend. Die Zeit kann man viel
besser in die Optimierung des Ablaufs und der Algorithmen stecken.
Peter D. schrieb:> Ohne Regeln ist es nur ungemein schwerer, die Übersicht zu behalten und> die Fehlerhäufigkeit steigt stark an.
Nun, man sollte auch nicht vergessen daß wir hier nur von
vergleichsweise einfachen 8Bittern reden. Und es ist es wie überall
Übungssache.
> Oder der Code besteht zu >50% aus Push/Pop, weil ja jedes Register> woanders auch verwendet werden könnte.
Zum Glück hat der AVR wirklich reichlich Register als daß man diese
Quote jemals auch nur zu einem Bruchteil erreichen müsste.
Peter D. schrieb:> Ich würde Assembler aber auch nicht mehr für produktiven Code verwenden
Das Wörtchen "Ich" ist dabei entscheidend. Und (nicht genannt) Hobby.
> Die Zeit kann man viel> besser in die Optimierung des Ablaufs und der Algorithmen stecken.
Gerade die Kenntnis des Ablaufs kombiniert mit totaler Code-Freiheit
kombiniert mit besserer Controller-Kenntnis bringt das Quentchen mehr
Optimierungspotential. Die andere Frage ist natürlich ob es in der
konkreten Anwendung überhaupt drauf ankommt. Allein die Möglichkeit dazu
ist aber etwas was so manches "Ich" fasziniert und inspiriert.
chris schrieb:> add R16,R17> tst R16> breq target
Das scheint mir doch etwas Problem-behaftet zu sein.
In genau 2*255 Fällen.
Wenn R16+R17 zu 256 (0)+carry addieren.
Fips Asm schrieb:> Gerade die Kenntnis des Ablaufs kombiniert mit totaler Code-Freiheit> kombiniert mit besserer Controller-Kenntnis bringt das Quentchen mehr> Optimierungspotential.
Ist auch meine Erfahrung. Optimierung auf Assemblerebene bringt nur
wenig (ein Quentchen) im Vergleich zum Zeitaufwand. Ich nenne das
Mikrooptimierung.
Optimierung durch Durchdenken des Ablaufs bringt dagegen oft erheblich
mehr.
Z.B. sieht man nicht selten, daß die gleiche Zahl im Multiplexinterrupt
immer wieder neu zerlegt wird (mittels Divisions-Lib), um das nächste
Digit anzuzeigen. Deutlich effektiver ist es, die Zahl nur neu zu
zerlegen, wenn sie sich auch geändert hat.
Es lohnt sich also, darüber nachzudenken, ob zeitintensiver Code nicht
unnötig oft aufgerufen wird (Profiling).
>Da TST auf Z or N Flag testet
tst testet überhaupt keine Flags, sondern den Operanden. Der Flag-Test
kommt erst beim breq.
>kommen noch die Situationen hinzu in denen eine Addition >127 ergibt.
Nein, weil für breq nur das Z-Flag relevant ist.
Anyway erfüllt "add-tst-breq" natürlich nicht die Anforderung des TO.
LostInMusic schrieb:> tst testet überhaupt keine Flags, sondern den Operanden. Der Flag-Test> kommt erst beim breq.
Ja, stimmt. TST setzt das Z-Flag wenn das Register 0x00 ist.
Das macht aber der davor stehende ADD R16,R17 auch schon.
Also ist - in diesem Fall - der TST ein prima NOP ;-)
Peter D. schrieb:> Optimierung auf Assemblerebene bringt nur> wenig (ein Quentchen) im Vergleich zum Zeitaufwand
Dieses Verhältnis ist schon sehr "Ich" - bezogen.
Also erfahrungsabhängig und abhängig von vorhandenem Code.
> Optimierung durch Durchdenken des Ablaufs bringt dagegen oft erheblich> mehr.
Ganz klar- hier springt am meisten an Gewinn raus.
Ganz egal mit welcher Sprache.
Fips Asm schrieb:> Zum Glück hat der AVR wirklich reichlich Register als daß man diese> Quote jemals auch nur zu einem Bruchteil erreichen müsste.
Also eigentlich(tm) hat er nur 16 Stück 16-Bit Register, die als 32
Halbregister präsentiert werden. Deshalb sind die Register X Y Z auch so
bezeichnet und man greift für gewöhnlich via XL XH auf die untere bzw.
obere Hälfte zu.
Wenn man jetzt darauf besteht, dass die kleinen AVRs 32 Register haben,
dann müsste man den 32-Bittern eigentlich 64 Stück 8-Bit Register
zugestehen ;)
Jan schrieb:> dann müsste man den 32-Bittern eigentlich 64 Stück 8-Bit Register> zugestehen
Gerne. Da kann dann viel verschwendet werden, bei den 8Bittern muß man
schneller auf den Punkt kommen. Für ihre Zwecke reichts aber dicke.
Jan schrieb:> Wenn man jetzt darauf besteht, dass die kleinen AVRs 32 Register haben,> dann müsste man den 32-Bittern eigentlich 64 Stück 8-Bit Register> zugestehen ;)
Na ja, der AVR ist nunmal ein 8-Bitter, da ergeben 8 bit schon ein
vollwertiges Register.
Oliver
Oliver S. schrieb:> Na ja, der AVR ist nunmal ein 8-Bitter, da ergeben 8 bit schon ein> vollwertiges Register.
Naja, ist schon nicht so einfach. Dann würde es Befehle wie adiw und
sbiw ja nicht geben können. Nungut, man kann sagen, dass das Fake ist,
da die Befehle zwei Takte brauchen. Aber der Befehl movw bewegt 16 Bit
in einem Takt. Wie erklärst du dir das mit dem Argument, der (kleine)
AVR ist ein 8-Bitter?
Hi, werfe mal in die Debatte:
statt einfachem addierem add -> addieren mit carry.
adc
Dann das Carrybit mit ror wieder in den "Ziffernbereich"
und weiter...comparen. Und zurück.
ciao
gustav
Jan schrieb:> Aber der Befehl movw bewegt 16 Bit> in einem Takt. Wie erklärst du dir das mit dem Argument, der (kleine)> AVR ist ein 8-Bitter?
Ach je, nur weil da im Befehlssatz noch Platz für drei Befehle mit 16
Bit Breite war, wird der nicht gleich zum 16-Bit Prozessor. Das ist und
bleibt ein reiner 8Bit Prozessor. Das man auch auf dem mit größeren
Wortbreiten hantiert, wenn es erforderlich ist, ändert da nichts daran.
Spätestens, wenn die 16-Bitbwerte in den Speicher zurückmüssen, geht es
wieder brav Byte für Byte.
Oliver
Oliver S. schrieb:> Das ist und> bleibt ein reiner 8Bit Prozessor.
Jepp.
Sinnvollerweise macht man das allein an der Breite der Integer-ALUs
fest. Mit allem anderen ergibt sich keinerlei sinnvolle
Klassifikations-Struktur. Schon seit Jahrzehnten nicht mehr...
Und falls jetzt irgensoein Schlaumichel bezüglich AVR8 mit adiw/sbiw
daherkommt: Bitte erstmal darüber nachdenken, warum die wohl zwei Takte
brauchen. Ganz genau so, als würde man es zu Fuß aus add/adc bzw.
sub/sbc zusammenbauen...
c-hater schrieb:> Sinnvollerweise macht man das allein an der Breite der Integer-ALUs> fest.
Bei dem Versuch, eine klare Definition zu finden, die nicht irgendwo
versagt, wirst du scheitern. Oder ist die Z80 ein 4-Bitter? ;-)
Es hat auch schon bitserielle 8-Bitter gegeben.