Forum: Mikrocontroller und Digitale Elektronik AVR als EPROM benutzen (C-Optimierung)


von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Hallo,

für ein Bastelprojekt brauchte ich mal eben einen kleinen ~16KB Speicher 
und wollte mangels EPROM-Brenner dafür einen ATMEGA32 missbrauchen, also 
mal eben schnell in C gehämmert:
1
#include <avr/io.h>
2
#include <avr/pgmspace.h>
3
#include <inttypes.h>
4
5
#define F_CPU 8000000UL
6
7
const uint8_t speicher[16384] PROGMEM = {0xDE, 0xAD, 0xBE, 0xEF};
8
9
int main(void) {
10
11
 DDRA  = 0x00;
12
 DDRB  = 0x00;
13
 DDRC  = 0xFF;
14
 
15
 while(1) {
16
  PORTC = pgm_read_byte(&speicher[(PINB << 8) | PINA]);  
17
 }
18
19
 return 0;
20
}

Weil ich dann noch wissen wollte wie langsam das Ganze wird habe ich es 
mir noch im Disassembler angeschaut und raus kamen 15 Takte mit 
folgendem Code:
1
PORTC = pgm_read_byte(&speicher[(PINB << 8) | PINA]);
2
3
IN        R24,0x16       In from I/O location
4
IN        R30,0x19       In from I/O location
5
MOV       R25,R24        Copy register
6
LDI       R24,0x00       Load immediate
7
LDI       R31,0x00       Load immediate
8
OR        R30,R24        Logical OR
9
OR        R31,R25        Logical OR
10
SUBI      R30,0xAC       Subtract immediate
11
SBCI      R31,0xFF       Subtract immediate with carry
12
LPM       R30,Z          Load program memory
13
OUT       0x15,R30       Out to I/O location
14
RJMP      PC-0x000B      Relative jump

Wie unschwer zu sehen kann man die beiden LDI und die beiden OR auch 
entfernen da sie nichts bringen.
Wenn ich den Inline-Assembler bemühe bekomme ich das auch ohne Probleme 
hin:
1
  asm volatile(
2
   "IN R31,0x16" "\n\t"
3
   "IN R30,0x19" "\n\t"
4
   "SUBI R30,0xAC" "\n\t"
5
   "SBCI R31,0xFF" "\n\t"
6
   "LPM R30,Z" "\n\t"
7
   "OUT 0x15,R30" "\n\t"
8
  );

Nur eigentlich wollte ich es ohne asm machen.

Jemand eine Idee wie ich den C-Compiler (gcc) dazu bekomme es ohne die 
unnötigen OR zu machen? Mit nem Union wirds noch 3 Takt länger...

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Tim T. schrieb:
> PORTC = pgm_read_byte(&speicher[(PINB << 8) | PINA]);

Was passiert, wenn Du stattdessen

> PORTC = pgm_read_byte(&speicher[(PINB << 8) + PINA]);


verwendest?

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Die Idee hatte ich auch nach dem Posten schon:
1
PORTC = pgm_read_byte(&speicher[(PINB << 8) + PINA]);
2
3
IN        R24,0x16       In from I/O location
4
IN        R25,0x19       In from I/O location
5
MOV       R19,R24        Copy register
6
LDI       R18,0x00       Load immediate
7
MOVW      R30,R18        Copy register pair
8
ADD       R30,R25        Add without carry
9
ADC       R31,R1         Add with carry
10
SUBI      R30,0xAC       Subtract immediate
11
SBCI      R31,0xFF       Subtract immediate with carry
12
LPM       R30,Z          Load program memory
13
OUT       0x15,R30       Out to I/O location
14
RJMP      PC-0x000B      Relative jump

Bleiben 15 Takte, nur aus anderem Grund...

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Mit ner Union:
1
static inline uint16_t lh2w( uint8_t l, uint8_t h )
2
{
3
  union{
4
    struct{
5
      uint8_t l;
6
      uint8_t h;
7
    }b;
8
    uint16_t w;
9
  } out;
10
  out.b.l = l;
11
  out.b.h = h;
12
  return out.w;
13
}
14
//..
15
  PORTC = pgm_read_byte(&speicher[lh2w( PINA, PINB )]);
1
    4070:  89 b3         in  r24, 0x19  ; 25
2
    4072:  96 b3         in  r25, 0x16  ; 22
3
    4074:  9c 01         movw  r18, r24
4
    4076:  f9 01         movw  r30, r18
5
    4078:  ec 5a         subi  r30, 0xAC  ; 172
6
    407a:  ff 4f         sbci  r31, 0xFF  ; 255
7
    407c:  e4 91         lpm  r30, Z+
8
    407e:  e5 bb         out  0x15, r30  ; 21
9
    4080:  f7 cf         rjmp  .-18       ; 0x4070 <main+0x8>

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Mit ner Union:
>
>
1
> static inline uint16_t lh2w( uint8_t l, uint8_t h )
2
> {
3
>   union{
4
>     struct{
5
>       uint8_t l;
6
>       uint8_t h;
7
>     }b;
8
>     uint16_t w;
9
>   } out;
10
>   out.b.l = l;
11
>   out.b.h = h;
12
>   return out.w;
13
> }
14
> //..
15
>   PORTC = pgm_read_byte(&speicher[lh2w( PINA, PINB )]);
16
>
>
1
>     4070:  89 b3         in  r24, 0x19  ; 25
2
>     4072:  96 b3         in  r25, 0x16  ; 22
3
>     4074:  9c 01         movw  r18, r24
4
>     4076:  f9 01         movw  r30, r18
5
>     4078:  ec 5a         subi  r30, 0xAC  ; 172
6
>     407a:  ff 4f         sbci  r31, 0xFF  ; 255
7
>     407c:  e4 91         lpm  r30, Z+
8
>     407e:  e5 bb         out  0x15, r30  ; 21
9
>     4080:  f7 cf         rjmp  .-18       ; 0x4070 <main+0x8>
10
>

Hmm, wird bei mir was größer:
1
PORTC = pgm_read_byte(&speicher[lh2w( PINA, PINB )]);
2
IN        R24,0x19       In from I/O location
3
IN        R25,0x16       In from I/O location
4
5
out.b.l = l;
6
STD       Y+1,R24        Store indirect with displacement
7
8
out.b.h = h;
9
STD       Y+2,R25        Store indirect with displacement
10
11
PORTC = pgm_read_byte(&speicher[lh2w( PINA, PINB )]);
12
LDD       R30,Y+1        Load indirect with displacement
13
LDD       R31,Y+2        Load indirect with displacement
14
SUBI      R30,0xAC       Subtract immediate
15
SBCI      R31,0xFF       Subtract immediate with carry
16
LPM       R30,Z          Load program memory
17
OUT       0x15,R30       Out to I/O location
18
RJMP      PC-0x000A      Relative jump

von Ludwig (Gast)


Lesenswert?

Mit __flast und Optimierung (-Os):

1
#include <avr/io.h>
2
#include <inttypes.h>
3
4
const __flash uint8_t speicher[16384] = {0xDE, 0xAD, 0xBE, 0xEF};
5
6
int main(void)
7
{
8
9
  DDRA  = 0x00;
10
  DDRB  = 0x00;
11
  DDRC  = 0xFF;
12
  
13
  while(1) 
14
  {
15
    PORTC = speicher[(PINB << 8) | PINA];
16
  }
17
18
  return 0;
19
}
1
  while(1) 
2
  {
3
    PORTC = speicher[(PINB << 8) | PINA];
4
    4074:  86 b3         in  r24, 0x16  ; 22
5
    4076:  e9 b3         in  r30, 0x19  ; 25
6
    4078:  f0 e0         ldi  r31, 0x00  ; 0
7
    407a:  f8 2b         or  r31, r24
8
    407c:  ec 5a         subi  r30, 0xAC  ; 172
9
    407e:  ff 4f         sbci  r31, 0xFF  ; 255
10
    4080:  84 91         lpm  r24, Z
11
    4082:  85 bb         out  0x15, r24  ; 21
12
  }
13
    4084:  f7 cf         rjmp  .-18       ; 0x4074 <main+0x8>

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Tim T. schrieb:
> Hmm, wird bei mir was größer:
>
1
> PORTC = pgm_read_byte(&speicher[lh2w( PINA, PINB )]);
2
> IN        R24,0x19       In from I/O location
3
> IN        R25,0x16       In from I/O location
4
> 
5
> out.b.l = l;
6
> STD       Y+1,R24        Store indirect with displacement
7
> 
8
> out.b.h = h;
9
> STD       Y+2,R25        Store indirect with displacement
10
> 
11
> PORTC = pgm_read_byte(&speicher[lh2w( PINA, PINB )]);
12
> LDD       R30,Y+1        Load indirect with displacement
13
> LDD       R31,Y+2        Load indirect with displacement
14
> SUBI      R30,0xAC       Subtract immediate
15
> SBCI      R31,0xFF       Subtract immediate with carry
16
> LPM       R30,Z          Load program memory
17
> OUT       0x15,R30       Out to I/O location
18
> RJMP      PC-0x000A      Relative jump
19
>

Hatte noch -O3 drin, mit -Os wird daraus:
1
PORTC = pgm_read_byte(&speicher[lh2w( PINA, PINB )]);
2
IN        R24,0x19       In from I/O location
3
IN        R25,0x16       In from I/O location
4
5
out.b.h = h;
6
MOVW      R18,R24        Copy register pair
7
8
PORTC = pgm_read_byte(&speicher[lh2w( PINA, PINB )]);
9
MOVW      R30,R18        Copy register pair
10
SUBI      R30,0xAC       Subtract immediate
11
SBCI      R31,0xFF       Subtract immediate with carry
12
LPM       R30,Z          Load program memory
13
OUT       0x15,R30       Out to I/O location
14
RJMP      PC-0x0008      Relative jump
Sind dann 12 Takte, immer noch 2 mehr als mit inline ASM...

von c-hater (Gast)


Lesenswert?

Tim T. schrieb:

> Nur eigentlich wollte ich es ohne asm machen.

Warum nur? Das ist doch Unsinn.
1
.INCLUDE "m32def.inc"
2
.EQU FCLOCK=8000000
3
4
.ORG 0
5
.DB 0xDE, 0xAD, 0xBE, 0xEF
6
;hier noch die anderen 16380 Bytes einfügen
7
8
.ORG FIRSTBOOTSTART
9
main:
10
 in ZL,PINA    ; 1
11
 in ZH,PINB    ; 1
12
 lpm R0,Z      ; 3
13
 out PORTC,R0  ; 1
14
 rjmp main     ; 2
15
               ;--
16
               ; 8

Sogar der Quelltext ist deutlich kürzer als dein C-Quelltext. Und man 
braucht nicht tiefsinnige Gedanken wälzen, was wohl der Compiler daraus 
machen wird, sondern kann sofort direkt ablesen, das die Latenz der 
Konstruktion 1µs sein wird. Was im übrigen deutlich weniger ist, als 
das beste, was du mit C und allen Tricks erreichen kannst...

Und der einzige "Trick" ist hier übrigens, den Code in den 
Bootloaderbereich zu packen, damit sich für den Datenbereich eine für 
den Zweck günstige Speicherorganisation ergibt.

Asm rules. Definitely.

von Karl M. (Gast)


Lesenswert?

Sehr schön gelöst - hut ab ! :-)

c-hater schrieb:

>> Nur eigentlich wollte ich es ohne asm machen.
>
> Warum nur? Das ist doch Unsinn.
:
:
> Sogar der Quelltext ist deutlich kürzer als dein C-Quelltext. Und man
> braucht nicht tiefsinnige Gedanken wälzen, was wohl der Compiler daraus
> machen wird, sondern kann sofort direkt ablesen, das die Latenz der
> Konstruktion 1µs sein wird. Was im übrigen deutlich weniger ist, als
> das beste, was du mit C und allen Tricks erreichen kannst...
>
> Und der einzige "Trick" ist hier übrigens, den Code in den
> Bootloaderbereich zu packen, damit sich für den Datenbereich eine für
> den Zweck günstige Speicherorganisation ergibt.
>
> Asm rules. Definitely.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

c-hater schrieb:

> Sogar der Quelltext ist deutlich kürzer als dein C-Quelltext. Und man
> braucht nicht tiefsinnige Gedanken wälzen, was wohl der Compiler daraus
> machen wird, sondern kann sofort direkt ablesen, das die Latenz der
> Konstruktion 1µs sein wird. Was im übrigen deutlich weniger ist, als
> das beste, was du mit C und allen Tricks erreichen kannst...
>
> Und der einzige "Trick" ist hier übrigens, den Code in den
> Bootloaderbereich zu packen, damit sich für den Datenbereich eine für
> den Zweck günstige Speicherorganisation ergibt.

Stimmt, unter diesen Voraussetzungen kann C natürlich auch nicht 
gleichziehen, hatte die Variante mit dem inline Assembler und 10 Takten 
schon als Optimum angesehen. So kann man natürlich auch SUBI und SBCI 
einsparen, daran hatte ich garnicht gedacht. Was mich bei der C-Variante 
aber eher interessiert hat, ist die Tatsache wie schlecht der Compiler 
diese relativ einfache Aufgabe löst, egal wie man es ihm sagt.

>
> Asm rules. Definitely.

In diesem Fall, ja.

von S. R. (svenska)


Lesenswert?

Tim T. schrieb:
> Was mich bei der C-Variante aber eher interessiert hat, ist die Tatsache
> wie schlecht der Compiler diese relativ einfache Aufgabe löst, egal wie
> man es ihm sagt.

Mit C hast du keine 100%ige Kontrolle über den Chip. Das ist für 
Assemblerjünger ein furchtbarer Nachteil und für C-Freunde ein 
großartiger Vorteil (Portabilität und so).

Daraus folgt, dass du mit C nie den letzten Taktzyklus erreichen kannst. 
Aber du kommst (bei hinreichend komplexen Programmen) nahe an das ran, 
was ein geübter Assemblerprogrammierer auch hinschreiben könnte, wenn er 
denn könnte. Bei hinreichend komplexen Programmen ist nämlich das 
menschliche Hirn auch zu klein. ;-)

Für solch kleine Programme ist ASM das Mittel der Wahl.

von c-hater (Gast)


Lesenswert?

Tim T. schrieb:

>> Asm rules. Definitely.
>
> In diesem Fall, ja.

Du hast es immer noch nicht begriffen. Ein C-Compiler kann bestenfalls 
mal für Teilstücke genausogut werden, wie ein erfahrener 
Asm-Programmierer.

Und: Das ist noch nicht mal das Schlimmste an der Sache. Der Compiler 
wird nie so gut wie ein erfahrener Asm-Programmierer abwägen können, wo 
eine Optimierung besonders nützlich ist, selbst wenn sie an anderer 
Stelle massive Nachteile verursacht. Dem Compiler fehlt dazu auf Grund 
seiner Funktionsweise schlicht der Überblick über das "Große Ganze".

Besonders deutlich wird dieser Nachteil bei ISRs. Deswegen reite ich 
auch so gern genau darauf rum. ;o)

Der Compiler weiss hier einfach nicht, wie häufig sie durchlaufen werden 
und kann dementsprechend nicht beurteilen, wie stark ein gesparter Takt 
hier die Performance des Gesamtsystems aufmöbeln würde, selbst wenn 
dadurch in irgendeiner hinterwichtigen Funktion in main() mal 30 oder 
meinetwegen auch 1000 Takte mehr anfallen. Das kann er einfach nicht, 
weil die Informationen dazu fehlen.

Ein auch nur halbwegs brauchbarer Assemblerprogrammierer kann das aber. 
Der weiss immer, wo die Säge wirklich klemmt...

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

c-hater schrieb:
> Tim T. schrieb:
>
>>> Asm rules. Definitely.
>>
>> In diesem Fall, ja.
>
> Du hast es immer noch nicht begriffen. Ein C-Compiler kann *bestenfalls*
> mal für Teilstücke genausogut werden, wie ein erfahrener
> Asm-Programmierer.

Doch, ich denke ich habe so einiges begriffen, nur habe ich den 
Assembler vor ca. 10 Jahren zu den Akten gelegt weil die Projekte zu 
komplex, die CPUs schnell genug, die Lebenszeit zu wertvoll und die 
Compileroptimierungen deutlich besser wurden. Meistens reichen mir 90% 
Leistung eben einfach aus.

> Und: Das ist noch nicht mal das Schlimmste an der Sache. Der Compiler
> wird nie so gut wie ein erfahrener Asm-Programmierer abwägen können, wo
> eine Optimierung besonders nützlich ist, selbst wenn sie an anderer
> Stelle massive Nachteile verursacht. Dem Compiler fehlt dazu auf Grund
> seiner Funktionsweise schlicht der Überblick über das "Große Ganze".

Und genau dieser Überblick geht bei vielen, auch der erfahreneren 
Programmierern, ab einer bestimmten Größe über die Klippe, von der 
Entwicklungszeit ganz zu schweigen.

> Besonders deutlich wird dieser Nachteil bei ISRs. Deswegen reite ich
> auch so gern genau darauf rum. ;o)
>
> Der Compiler weiss hier einfach nicht, wie häufig sie durchlaufen werden
> und kann dementsprechend nicht beurteilen, wie stark ein gesparter Takt
> hier die Performance des Gesamtsystems aufmöbeln würde, selbst wenn
> dadurch in irgendeiner hinterwichtigen Funktion in main() mal 30 oder
> meinetwegen auch 1000 Takte mehr anfallen. Das kann er einfach nicht,
> weil die Informationen dazu fehlen.
>
> Ein auch nur halbwegs brauchbarer Assemblerprogrammierer kann das aber.
> Der weiss immer, wo die Säge wirklich klemmt...
Für sowas gibts Profiler...

von Carl D. (jcw2)


Lesenswert?

Ein wirklich guter Hand-Assemblierer (und auch mancher Compiler) würde 
weiter knapp 2 Takte dadurch einsparen, daß er schreibt:
1
.INCLUDE "m32def.inc"
2
.EQU FCLOCK=8000000
3
4
.ORG 0
5
.DB 0xDE, 0xAD, 0xBE, 0xEF
6
;hier noch die anderen 16380 Bytes einfügen
7
8
.ORG FIRSTBOOTSTART
9
main:
10
;1
11
 in ZL,PINA    ; 1
12
 in ZH,PINB    ; 1
13
 lpm R0,Z      ; 3
14
 out PORTC,R0  ; 1
15
;2
16
 in ZL,PINA    ; 1
17
 in ZH,PINB    ; 1
18
 lpm R0,Z      ; 3
19
 out PORTC,R0  ; 1
20
;3
21
 in ZL,PINA    ; 1
22
 in ZH,PINB    ; 1
23
 lpm R0,Z      ; 3
24
 out PORTC,R0  ; 1
25
...
26
;511
27
 in ZL,PINA    ; 1
28
 in ZH,PINB    ; 1
29
 lpm R0,Z      ; 3
30
 out PORTC,R0  ; 1
31
32
 rjmp main     ; 2
33
               ;--
34
               ; 2+n*6;  n=511 und etwas Platz für rjmp
35
               ; -> 6.004 Takte/EProm-Read
Macht 376ns Zykluszeit bei 16MHz Mega32.

Der bisher beste Assembler-Programmierer kam auf 500ns. 33% langsamer!

;-)

von Carl D. (jcw2)


Lesenswert?

Und nicht vergessen, die Fuses richtig zu setzen!

von Arduinoquäler (Gast)


Lesenswert?

Carl D. schrieb:
> Macht 376ns Zykluszeit bei 16MHz Mega32.

1 Zyklus bei 16 Mhz = 62.5ns

Das Schleife Aufrollen in Assembler hat mir beim Apple
auch immer viel Spass gemacht.

von Carl D. (jcw2)


Lesenswert?

Arduinoquäler schrieb:
> Carl D. schrieb:
>> Macht 376ns Zykluszeit bei 16MHz Mega32.
>
> 1 Zyklus bei 16 Mhz = 62.5ns

Falsch!

Ein Zyklus der EProm-Simulationsschleife, oder auch die max. 
Zugriffszeit des simulierten EProms. Das wäre je nach Alter des Chip gar 
nicht mal so schlecht.

Man könnte natürlich auch auf ein paar aufgerollte Durchläufe 
verzichten, um am Anfang der Bootloaders ein freies Pin abzufragen und 
bei Bedarf einen wirklichen Loader in den freien 14k davor anspringen. 
Der könnte dann schon fast ein Terminal via (SW-)UART zum Hexeditor 
machen, zum interaktiven Editiren des EProm-Inhalts, oder eben schnöd 
zum "hochladen".

c-hater:
> Ein auch nur halbwegs brauchbarer Assemblerprogrammierer kann das aber.
> Der weiss immer, wo die Säge wirklich klemmt...
q.e.d

: Bearbeitet durch User
von Arduinoquäler (Gast)


Lesenswert?

Carl D. schrieb:
> Falsch!

Miss(t)verständis. Ich meinte einen Prozessorzyklus.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

c-hater schrieb:
> Du hast es immer noch nicht begriffen. Ein C-Compiler kann bestenfalls
> mal für Teilstücke genausogut werden, wie ein erfahrener
> Asm-Programmierer.
Ich würde es eher andersrum sagen:
Ein Assemblerprogrammierer kann bestenfalls
mal ausgewählte Teilstücke optimieren. Er kann aber nur mit viel 
Zeitaufwand große und komplexe Programme optimieren.

So einen kleinen Witz von Programm kann man in Assembler leicht mal 
"besser" schreiben. Allerdings ist ein normales oder gar größeres 
Programm eben.in C schneller geschrieben und somit schneller am Markt.

von Carl D. (jcw2)


Lesenswert?

Lothar M. schrieb:
> So einen kleinen Witz von Programm kann man in Assembler leicht mal
> "besser" schreiben.

Manchmal aber noch nicht mal das. Trotz Assembler-Liebe.

;-)

von M. K. (sylaina)


Lesenswert?

Carl D. schrieb:
> Macht 376ns Zykluszeit bei 16MHz Mega32.
Und warum schreibst du dann
1
...
2
 .EQU FCLOCK=8000000
3
...
? hihihi
Spass beiseite, Daumen hoch! ;)

von Carl D. (jcw2)


Lesenswert?

M. K. schrieb:
> Carl D. schrieb:
>> Macht 376ns Zykluszeit bei 16MHz Mega32.
> Und warum schreibst du dann
>
1
...
2
>  .EQU FCLOCK=8000000
3
> ...
4
>
> ? *hihihi*
> Spass beiseite, Daumen hoch! ;)
Auch ohne Spaß: hat das irgend eine Bedeutung, wenn man keine 
Timer-/UBR-/sonstwas-taktabhängies damit berechnet?
Vor kurzem hatten wir hier jemand, der hat seinen Takt durch Angabe 
einer schrägen F_CPU "getrimmt".
Und er meinte, das hat funktioniert! ????

Der TO muß ich sowieso um den richtigen Takt (16MHz Quarz) und das 
direkt Starten des Bootloaders per passender Fuse selber kümmern.

Zudem hab ich das nur beim Assembler-Fan kopiert ;-)

: Bearbeitet durch User
von Ludwig (Gast)


Lesenswert?

> Der bisher beste Assembler-Programmierer kam auf 500ns. 33% langsamer!

Sehe ich anders.

Ein Eprom hat eine garantierte Zugriffszeit. Die beträgt bei beiden 
Programm 8 Takte.

von Carl D. (jcw2)


Lesenswert?

Wenn man spitzfindig sein will, dann ist auch das falsch!
Adresse wird angelegt am Start von "LPM". Dann dauert es 3+1+6(8) Takte.

Wenn die Adresse zwischen den beiden "IN"'s geändert wird, sind die 
Ausgangs-Daten sogar falsch!

Die allerbeste Simulation würde wohl auf den Namen 2716 hören.

Aber meine "Competition" war eher "schneller kann nur der 
Assembler-Mann".
Und ich mag eher C++ als C.

von Peter D. (peda)


Lesenswert?

Carl D. schrieb:
> Macht 376ns Zykluszeit bei 16MHz Mega32.

Das ist Quatsch, es kommt auf den Worst case an, nicht auf den 
Mittelwert.
Wird bei der 8 Zyklen Version die Adresse direkt vor "in ZL,PINA" 
geändert, braucht es weitere 14 Zyklen ehe die Daten gültig sind 
(AVR-Eingänge werden einen Zyklus vorher gelatcht, daher 14).
Die langsamsten 2716 erlaubten max 450ns Delay. Um die zu simulieren 
müßte der AVR mit 14/450e-9 = 32MHz takten.

Den /OE muß man aber mit extra Treibern machen, der geht nicht in SW 
(max 100ns Delay).

von DocMartin (Gast)


Lesenswert?

Nur mal zum Klugschei...:
In den ASm Beispielen fehlt noch die Initialisierung (Dir).

Martin

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Carl D. schrieb:
> Wenn man spitzfindig sein will, dann ist auch das falsch!
> Adresse wird angelegt am Start von "LPM". Dann dauert es 3+1+6(8) Takte.
Mit dem Latchen der EingangsPins sogar etwas mehr noch, im Worst Case.

EDIT: Peter war schneller, hatte nicht neu geladen...

> Wenn die Adresse zwischen den beiden "IN"'s geändert wird, sind die
> Ausgangs-Daten sogar falsch!
Das ist klar, spielt bei meiner Anwendung aber keine Rolle, da ich nach 
anlegen der Adresse lange genug Zeit habe bis die Daten abgerufen 
werden, mit der Optimierung jetzt erst recht.

>
> Die allerbeste Simulation würde wohl auf den Namen 2716 hören.
Wäre nur etwas zu klein, aber stimmt mit nem 27(C)128 würde es 
sicherlich am einfachsten gehen nur die Emulation ist schneller zusammen 
gezimmert als nen Prommer zu basteln und auch flexibler.

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

Peter D. schrieb:
> Carl D. schrieb:
>> Macht 376ns Zykluszeit bei 16MHz Mega32.
>
> Das ist Quatsch, es kommt auf den Worst case an, nicht auf den
> Mittelwert.
> Wird bei der 8 Zyklen Version die Adresse direkt vor "in ZL,PINA"
> geändert, braucht es weitere 14 Zyklen ehe die Daten gültig sind
> (AVR-Eingänge werden einen Zyklus vorher gelatcht, daher 14).
> Die langsamsten 2716 erlaubten max 450ns Delay. Um die zu simulieren
> müßte der AVR mit 14/450e-9 = 32MHz takten.
>
> Den /OE muß man aber mit extra Treibern machen, der geht nicht in SW
> (max 100ns Delay).

Vieles davon hatte ich schon 2,5h vorher geschrieben.
Natürlich ist das keine perfekte Simulation. Vielleicht braucht der TO 
auch das auch nur 16k LED-Pattern abzulegen, dann kann die Simulation 
gut genug sein. Aber diese Voraussetzungen sind uns nicht bekannt.
Man kann natürlich den AVR-Ausgangsport auf "Floating" bringen. Wie muß 
ich wohl nicht extra schreiben.

So zusammengefaßt:
Carl D. schrieb:
> Die allerbeste Simulation würde wohl auf den Namen 2716 hören.

<ironie>
Warum hat eigentlich noch niemand festgestellt, daß auch das Pinout des 
Ersatz-Chips nicht stimmt? Der Stromverbrauch (nach Intel DB) ist auch 
nicht gleich.
</ironie>

Meine eigentlich Antwort war auf das Statement: "Jeder mittelprächtige 
Assembler-Programmierer macht das besser" von jemand, der C-Allergie 
hat, und offenbar auch bei Assembler noch was dazu lernen kann ;-)

PS: schon lustig, was hier passiert, wenn man nur einen 
Verbesserungsvorschlag macht, der sich außer der Laufzeit nur in dem 
alle 511 "Zyklen" dazukommenden Hickser in Form eines rjmp 
unterscheidet. Alle anderen Probleme waren schon zuvor drin.

von Peter D. (peda)


Lesenswert?

Carl D. schrieb:
> Warum hat eigentlich noch niemand festgestellt, daß auch das Pinout des
> Ersatz-Chips nicht stimmt?

Das hat mich auch schon gewundert.
Ich hätte nen (fast) pinkompatiblen 28C256 genommen. Und wenn man kein 
Programmiergerät hat, ist das mit nem AVR auf Lochraster auch schnell 
hingefrickelt.

von avr (Gast)


Lesenswert?

Carl D. schrieb:
> Macht 376ns Zykluszeit bei 16MHz Mega32.

bringt leider nur nichts, denn es interessiert nicht die typ. 
Zugriffszeit, sondern die Maximale. Und die liegt immer noch bei 8 
Takten/500ns.

von Nop (Gast)


Lesenswert?

c-hater schrieb:
> Dem Compiler fehlt dazu auf Grund
> seiner Funktionsweise schlicht der Überblick über das "Große Ganze".

Dafür gibt's Pragmas, mit denen man Hotspots in -O3 umschließen kann. 
Oder meinetwegen auch -O2, während man den Rest des Programmes mit -Os 
baut. Und Expect-Builtins für Abfragen in Hotspots, die meistens ein 
bestimmtes Ergebnis haben.

Profile Driven Optimisation gibt's auch noch, aber da muß man dann 
erstmal die Profiling-Daten wieder auf den Host bekommen, was eine Menge 
Gefrickel wird.

Für ISRs, die sehr schnell sein müssen, hast Du aber schon Recht, klar. 
Wenn es auf jeden Takt ankommt, geht nichts über Assembler. Zumal ISRs, 
die schnell sein müssen, aus demselben Grund ja auch kurz sein müssen, 
so daß das in Assembler in vertretbarer Zeit realisierbar ist.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Peter D. schrieb:
>
> Das hat mich auch schon gewundert.
Der EPROM ist über nen Pfostenstecker auf ner eigenen Sub-Platine 
untergebracht die ich komplett neu mache, also Pinout ist egal. 
Abgesehen davon finde ich den AVR einfacher zu routen als den 27er, wer 
hat sich nur so ein Pinout ausgedacht.

> Ich hätte nen (fast) pinkompatiblen 28C256 genommen. Und wenn man kein
Der dann auch gerne mal das dreifache des AVR kostet, dazu kommt das ich 
die Atmels in der Grabbelkiste hatte und den EEPROM hätte ich erst 
bestellen müssen, dann hätte ich aber auch direkt nen 27C128/27C256 
bestellen können.

> Programmiergerät hat, ist das mit nem AVR auf Lochraster auch schnell
> hingefrickelt.
Auch klar, nur wenn ich eh schon mehr Arbeit investieren muss, wäre mir 
mal eben was auf Lochraster für die eine Aufgabe wohl wieder nicht gut 
genug gewesen. Dann muss es natürlich direkt was mit USB Anschluss mit 
verschiedenen Spannungen und für verschiedene Typen auch mit 16 Bit 
Datenbus sein. Natürlich ohne Jumper sondern alles über Software 
einstellbar.
Ist aber auch noch in Planung für trübe Wintertage.

von c-hater (Gast)


Lesenswert?

Carl D. schrieb:

> Ein wirklich guter Hand-Assemblierer (und auch mancher Compiler) würde
> weiter knapp 2 Takte dadurch einsparen, daß er schreibt:
[...]

Nein, denn ein guter Assemblerprogrammierer weiss natürlich, dass hier 
das Ausrollen der Schleife rein garnix bringt. Entscheidend für die 
Latenz ist immer der worst case und der bleibt auch beim Ausrollen der 
Schleife exakt gleich, denn irgendwie muss auch die ausgerollte Schleife 
letztlich geschlossen werden.

von c-hater (Gast)


Lesenswert?

Nop schrieb:

> Profile Driven Optimisation gibt's auch noch, aber da muß man dann
> erstmal die Profiling-Daten wieder auf den Host bekommen, was eine Menge
> Gefrickel wird.

Vor allem muss man die Profilingdaten erstmal gewinnen können. Das 
kostet nämlich auf jeden Fall zwangsläufig zusätzliche Takte und wenn 
schon ohne Profiling 110% der verfügbaren Rechenzeit genutzt werden 
müssten und die Anwendung deshalb nicht "geht", dann ist es sehr 
unwahrscheinlich, dass der Verbrauch von noch mehr Rechenzeit die 
Anwendung dazu bringt, nunmehr korrekt zu laufen. Was in der 
Endkonsequenz heißt: du kannst die Profilingdaten unmittelbar an der 
Quelle entsorgen, denn sie geben nicht mal näherungsweise das Profil der 
Anwendung wieder, so wie sie eigentlich laufen sollte.

Kurzfasssung: Profiling ist zur Optimierung von "engen" 
interruptbasierten Systemen ein völlig unbrauchbares Verfahren.

von c-hater (Gast)


Lesenswert?

Carl D. schrieb:

> Auch ohne Spaß: hat das irgend eine Bedeutung, wenn man keine
> Timer-/UBR-/sonstwas-taktabhängies damit berechnet?

Tut man doch. Bei so einem "Projekt" ist natürlich das primäre 
Optimierungsziel die Latenzzeit. Und um die berechnen zu können, braucht 
man neben der Zahl der Takte die Taktfrequenz. Deswegen steht sie 
einfach mit im Quelltext, auch wenn sie für die Codegenerierung selber 
natürlich in diesem Fall völlig bedeutungslos ist. Man hätte sie auch 
einfach als Kommentar hinschreiben können. Aber warum sollte man?

Im originalen C-Code stand sie übrigens auch schon drinne, obwohl sie 
dort genauso "nutzlos" war. Who cares? Die Deklaration kostet zu 
Laufzeit rein garnix!

von Nop (Gast)


Lesenswert?

c-hater schrieb:
> Kurzfasssung: Profiling ist zur Optimierung von "engen"
> interruptbasierten Systemen ein völlig unbrauchbares Verfahren.

Ja, das ergibt Sinn. Zumal mir noch eingefallen ist, daß zumindest der 
GNU-Profiler einfach nur den PC regelmäßig abtastet. Dadurch erfährt man 
zwar, welche Funktionen die meiste Zeit brauchen, aber nichts über 
Latenz.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Lothar M. schrieb:
> c-hater schrieb:
>> Du hast es immer noch nicht begriffen. Ein C-Compiler kann bestenfalls
>> mal für Teilstücke genausogut werden, wie ein erfahrener
>> Asm-Programmierer.
> Ich würde es eher andersrum sagen:
> Ein Assemblerprogrammierer kann bestenfalls
> mal ausgewählte Teilstücke optimieren. Er kann aber nur mit viel
> Zeitaufwand große und komplexe Programme optimieren.
>
> So einen kleinen Witz von Programm kann man in Assembler leicht mal
> "besser" schreiben. Allerdings ist ein normales oder gar größeres
> Programm eben.in C schneller geschrieben und somit schneller am Markt.

 Niemand bei klarem Verstand wird irgendetwas größeres als 10-16KB in
 Assembler schreiben - und das auch nur unter Zwang.

 Für so etwas schreibt man sich eigene Bibliotheken und ruft diese
 dann auf. Ich habe mal früher (als ich noch Lust und Zeit dazu hate)
 einen Tiny-Compiler geschrieben, nur um for-next, while, if-then, case
 und solch nützliches Zeug zu haben. Wird heute noch benutzt für
 schnelle Steuerungen (und ich meine schnell in Ausführungszeit und
 schnell geschrieben).

 Aber schon bei Stringbehandlung und dynamischen Zuweisungen ist es
 mit Assembler vorbei - wie schon gesagt, kein normaler Mensch tut
 sich so etwas an.

 Aber genauso ist es total verkehrt, sich bei zeitkritischen Anwendungen
 auf C zu verlassen, vor allem wenn die Optimierung eingeschaltet ist.

 Für mich hat sich folgendes als optimal herausgestellt:
 Hochsprache für Gerüst, Libraries in Assembler für spezielle
 Funktionen, Prozeduren und ISR, Parameterübergabe über Stack und ein
 universelles Array für den Rest.

 Da kann weder Assembler pur, noch eine Hochsprache alleine mithalten.

von nicht"Gast" (Gast)


Lesenswert?

Marc V. schrieb:
> Niemand bei klarem Verstand wird irgendetwas größeres als 10-16KB in
>  Assembler schreiben - und das auch nur unter Zwang.

10-16K in Assembler? Das ist schon sportlich. Ich würde meine Grenze bei 
1k setzen :).

Für mich sieht es einfacher aus. Assembler nur dann, wenn es unbedingt 
nötig ist. Ich weiß ja nicht, was ihr so für Zeugs schreibt, bei mir ist 
aber selten etwas so Zeitkritisch, dass es auf einzelne Takte ankommt.

von c-hater (Gast)


Lesenswert?

Marc V. schrieb:

>  Aber schon bei Stringbehandlung und dynamischen Zuweisungen ist es
>  mit Assembler vorbei

Das ist doch kompletter Unsinn. Gerade so triviales Zeug wie ein Satz 
der üblichen Funktionen in C zum String-Processing ist in Asm ebenso 
leicht zu behandeln wie in C. Man macht es in Asm einfach genau so, wie 
C es macht. Zumal nicht selten die Stringroutinen in C-Compilern auch 
einfach nur architekturabhängig hinzugelinkte *ASM*-Routinen sind. Weil 
die eben optimal für die jeweilige Zielarchitektur sind...

Übrigens... Was zum Teufel sollen "dynamische Zuweisungen" sein?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

nicht"Gast" schrieb:
> Marc V. schrieb:
>> Niemand bei klarem Verstand wird irgendetwas größeres als 10-16KB in
>>  Assembler schreiben - und das auch nur unter Zwang.
>
> 10-16K in Assembler? Das ist schon sportlich. Ich würde meine Grenze bei
> 1k setzen :).

 Ich hab mal 7,5KB Assembler und 0,5KB Tabellen in eine alte Mega
 reingeguetscht.
 Nie wieder so etwas geschrieben noch versucht, kenne aber Leute die
 mehr als 20KB Assembler geschrieben haben.

 Ich kenne aber auch Leute die Nachts russisches roullete fahren...

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

c-hater schrieb:
> Übrigens... Was zum Teufel sollen "dynamische Zuweisungen" sein?

 LOL.
 Das Gegenteil von "statischen Zuweisungen", oder wie siehst du das ?

von Wilhelm M. (wimalopaan)


Lesenswert?

Wer heute noch an den Optimierungsmöglichkeiten moderner Compiler 
zweifelt, der sollte sich auch einmal die Beiträge von Jason Turner für 
die Cpp2016
ansehen oder einfach auf Youtube die Cpp-Weekly-Episodes.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

c-hater schrieb:
> Das ist doch kompletter Unsinn. Gerade so triviales Zeug wie ein Satz
> der üblichen Funktionen in C zum String-Processing ist in Asm ebenso
> leicht zu behandeln wie in C. Man macht es in Asm einfach genau so, wie

 Schon mal versucht mit Strings und Arrays in Assembler zu arbeiten ?
 Und ich meine richtige Strings und Arrays, nicht etwa fixed length
 Strings wie "String*15" oder so ?

 Ich glaube nicht...

 Ansonsten wüsstest du was "dynamische Zuweisung", Konstruktor und
 Destruktor, Wertübergabe ByVal und nicht ByRef ist und so.

 Aber für jemanden der behauptet, ein BitArray kann man manipulieren,
 ohne die Werte irgendwo im Speicher zu haben, durchaus möglich...

von Carl D. (jcw2)


Lesenswert?

Wilhelm M. schrieb:
> Wer heute noch an den Optimierungsmöglichkeiten moderner Compiler
> zweifelt, der sollte sich auch einmal die Beiträge von Jason Turner für
> die Cpp2016
> ansehen oder einfach auf Youtube die Cpp-Weekly-Episodes.

Das ist wirklich ein Beispiel, was man erreichen kann, wenn man mental 
nicht völlig eingerostet ist. Ich hab das schon in der Woche nach der 
CppCon gesehen, kurz nachdem es online war.
Nur wie soll das jemand verstehen, der in seinem "Spezialgebiet" von 
einem C++-Liebhaber rechts überholt wird ;-)

von Wilhelm M. (wimalopaan)


Lesenswert?

Man kann sich im Compiler-Explorer den generierten Code sofort ansehen:

http://gcc.godbolt.org/

Leider unterstützt der Compiler-Explorer nur gcc-4.5.3 für den AVR, für 
die IA32/IA64 bis zur aktuellen Version 6.2.

Ich kann eigentlich jedem nur empfehlen, sich das mal anzusehen. 
Jedenfalls bin ich froh, mit einem modernen C++14/C++17 eine Sprache zu 
haben, in der ich sehr expressiv und sicher formulieren kann und die 
trotzdem zu einen extrem optimierten Code führt. Außer für StartUp-Code 
sehr ich keinen Bedarf mehr für Assembler.

von M. K. (sylaina)


Lesenswert?

nicht"Gast" schrieb:
> Ich weiß ja nicht, was ihr so für Zeugs schreibt, bei mir ist
> aber selten etwas so Zeitkritisch, dass es auf einzelne Takte ankommt.

Ja, das "Problem" kenn ich. Mir liegt C auch eher als ASM und ich hatte 
bisher nur ein einziges Projekt wo es auf jeden Takt ankam.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.