"structured assembler" for gas...

Go To Last Post
45 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Has anyone managed to implement "structured assembler" macros for the gnu assembler? You know, like:

  cpi r17, 12
  _ife
    ;;do stuff.
  _else
    ;; do other stuff
  _endif

I've seen two separate ways to do this, one of which relies on macros being able to do stringification of symbol values, and one of which relies on being able to set ".org" (or equivalent) backward.
Apparently neither of these is possible in the gnu assembler :-(

OTOH, I've recently seen some assembler that hints that there might be some commonly used "brute-force" techniques for addressing stringification (at least for a limited range of values), so perhaps someone has already implemented these after all?

Have they? Are there "structured assembler" macros for gas?

(web searches seem pretty hopeless...)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Quote:
Are there "structured assembler" macros for gas?

I know you'll hate me for the answer, but yes there is. It's called "C". :wink:

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Rats, Johan beat me to it, I was going to say exactly the same thing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why not give a practical example ?

You can easily make for(), while(), if() constructions with macros.

I used to do exactly that before I discovered C.

Note that you generally end up with brute force, but it may be worth the convenience.

If you use a HLL, the compiler will remove unused code and impossible branches. With ASM you can revel in your ignorance.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

First, I agree with the others about c.
Just played a little. Since you can reuse numerical labels you could simply do something like this

.macro _ifeq1
        breq .+2                ; Skip next instruction if Z set
        rjmp 99f                ; Jump to _else1
.endm

.macro _else1
        rjmp 98f                ; Skip the else block
99:     
.endm

.macro _endif1
98:
.endm

but that will require that you always use an _else1. One solution to that could be

.macro _ifeq2
        else_used = 0           ; Reset 
        breq .+2                ; Skip next instruction if Z set
        rjmp 99f                ; Jump to _else2 or _endif2
.endm

.macro _else2
        else_used = 1           ; tell _endif2 that else have been used
        rjmp 98f                ; Skip the else block
99:     
.endm

.macro _endif2
.if else_used == 1
98:
.else
99:
.endif
.endm

Support for nesting would be a little harder I guess.

=======================================================================
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
You can easily make for(), while(), if() constructions with macros.

Such as? Allowing nesting and everything?
I've done this in several assemblers, pre-gnu. There were whole mainframe operating systems that made extensive use of similar macros. There are pre-processors that do it now (with relatively arbitrary target assemblers), touted for use in classes and such. So it's not like there is zero interest in such a thing. Sometimes you just need more determinism than C can provide, and there is no reason that using assembler should be any more painful than it has to be.

A "standardized" gas-based solution would immediately enable such structure in a whole gaggle of cpus, not just AVRs. Including some for which (AFAIK) gas is the only available assembler.

I *think* that it ought to be possible by using local labels. Some of the code I have looks a lot like it spends most of its effort generating the equivalent of "local labels." But if it's so easy, where is the "standard implementation" ??

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes. You can do a certain amount of nesting. After all, you are using standard constructions. e.g.

.macro FOR start, condition, after

You just pass the relevant macro/subroutine_name for start, etc.

Yes, it is fairly hairy to write complex constructions. Yes, you have to use local labels all over the place.

I used to do this for 6502 ASM some 30 years ago. Admittedly I wrote my own assembler and have forgotten the syntax. But the principle is exactly the same for GNU assemblers. just different syntax.

It was convenient for the 6502 to have 16-bit macros for most operations, and to have primitive loop constructions. However, nowadays I would simply use C. Then hand-optimise.

Seriously, most C haters tend to be so because they think that C has complex syntax and rules. I can assure you that complex ASM macros are far harder to get your head around.

They tend to be .MACRO haters too. They certainly would never consider #define or even M4 etc.

So most ASM programmers never get further than simple macros like LOAD and STORE. If they do, then their 'individuality' comes to prominence. ---- i.e. they don't want to use no standard anything.

If you were to quote an example problem, I suspect our Scandivanian colleagues would have some good suggestions.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
If you were to quote an example problem

What "problem" ? Clearly this sort of thing is just "nice to have", helping you get rid of all those silly labels. For instance, there's no reason that the hypothetical strlen function needs two extra labels (loop start and null branch destination):

strlen: clr r10
        _begin                          ;Start of loop
          ld r17, X+
          and r17, r17                  ;check for end of string
          _if ne
             inc r10                    ;  nope.  inc count and
             _loop                      ;  loop always...
          _endif
        ret
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Would you consider a preprocessor of some sort? Clearly you could just write something in your favourite language that reads the source you write and expands out your favoured macro syntax or you may be able to achieve it with an existing, powerful macro language like GNU m4.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Say that you wanted a macro called STRLEN that takes a literal address.

     .macro STRLEN ads
     ldi  XH,hi8(ads)
     ldi  XL,lo8(ads)
     ldi  R10, -1
1:   inc  R10
     ld   R17,X+
     tst  R17
     brne 1b
     .endm

You might find it handy to have a LDIX macro

     .macro LDIX value
     ldi  XH,hi8(value)
     ldi  XL,lo8(value)
     .endm

You might find it handy to have a TSTBRNE macro

     .macro TSTBRNE reg, dest
     tst  reg
     brne dest
     .endm

I am sure that gas has got sufficient rope for punters to hang themselves with. There comes a time when some 'aids' become more trouble than they are worth.

I can't see the point in your '_begin' and '_loop' macros. For a start, they don't seem to have any arguments. So you would need to use .set expressions in the definition which are inherently global. Hence you destroy all nestability.

Macros are sequences rather than subroutines. So you would actually do something like this:

strlen_func:
    PUSH16 X
    push   R17 
    STRLEN message_address
    pop    R17
    POP16  X
    ret

In practice, I can see sense in using 16-bit arithmetic macros. I can see sense in FOR, WHILE, UNTIL macros.

In all honesty, I would just write a strlen function straight off.

If you want to see some Atmel Assembler2 macros in action, just look at the CodeVision Evaluation LST files for a few tips.

Note that Assembler2 is completely crap at macros. avr-as is far more capable.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Would you consider a preprocessor of some sort?

I'd rather not. Parsing complexity becomes large. Besides, it's already been done...
http://www.plantation-production...

Here's a macro-based implementation and description that work for assemblers other than gas:
http://dkeenan.com/AddingStructu...

Quote:
Say that you wanted a macro

Yes, yes. I'm familiar with traditional macros.
Implementing the "structure" macros is a bit more complex. Typically "if" and "else" generate a single branch to a created target label (could be local), and "else" and "end" create the necessary labels. Add a counter or two to handle nesting/etc. The implementation(s) I'm looking at (Macro-10 and MASM) use two or three separately counted namespaces; otherwise you might not ever need more than gas's local labels...

I actually have some code that works, but it requires code like:

.macro _gtbeg2jmp ind, jmp
	.if (\ind == 0)
	\jmp __BEG0
	.elseif (\ind == 1)
	\jmp  __BEG1
	.elseif (\ind == 2)
	\jmp  __BEG2
	.elseif (\ind == 3)
	\jmp __BEG3
	.elseif (\ind == 4)
	\jmp __BEG4
;;;
;;; And etc for as many clauses as you might have
;;;
	.else
	.print "struct.asm macro begctr overflow"
	.err
	.endif
	.endm

Which would be OK except for the "and etc" part. The other counter does "nesting level" (I think. I guess part of the problem is I never figured out exactly how these work), so it decrements as well. Perhaps local labels could be used for one of the counters, and a more limited implementation for the other.

But like I said, it would be best if there was already a well-accepted version...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Aren't those labels global? Can you use them more than one time in one file? (Edit: I see, they are all going to be different, so there may be many of them).
By the way, I made a better solution for the optional else

.macro  _ifeq3
        breq    .+2             ;Skip next if Z set
        rjmp    99f             ;Jump to else or endif
.endm

.macro  _else3
        rjmp    98f             ;Jump to endif
99:     
.endm

.macro  _endif3
99:                             ;Label for if
98:                             ;Label for else
.endm

With nesting support (one level implemented). Labels are reused.

.macro  iflabel num
.if \num == 99
99:
.elseif \num == 98
98:
.elseif \num == 97
97:
.elseif \num == 96
96:
.else
        .error "Too deep nesting"
.endif
.endm


.macro rjmpfn num
.if \num == 99
        rjmp    99f
.elseif \num == 98
        rjmp    98f
.elseif \num == 97
        rjmp    97f
.elseif \num == 96
        rjmp    96f
.else
        .error "Too deep nesting"
.endif
.endm


iflevel = 101

.macro  _ifeq
        iflevel = iflevel - 2   ;Set next iflevel
        breq .+2                ;Skip next if Z set
        rjmpfn  iflevel         ;Jump to else or endif
.endm

.macro  _else
        rjmpfn  iflevel - 1     ;Jump to endif
        iflabel iflevel         ;Label for if
.endm

.macro  _endif
.if iflevel > 99
        .error "_endif without _ifXX (next two errors are wrong)"
.endif
        iflabel iflevel         ;Label for if
        iflabel iflevel - 1     ;Label for else
        iflevel = iflevel + 2   ;Go back one iflevel
.endm
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And instead of the test _ifeq one can use the more general

#define BRCAT(cond) br ## cond

;;; _if cond, cond can be XX in all brXX.
;;; That is: bc, bs, cc, cs, eq, ge, hc, hs, id, ie,
;;; lo, lt, mi, ne, pl, sh, tc, ts, vc or vs
.macro  _if cond
        iflevel = iflevel - 2   ;Set next iflevel
        BRCAT(\cond) .+2        ;Skip next if cond is true
        rjmpfn  iflevel         ;Jump to else or endif
.endm
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm pretty sure that last example combining cpp macros and asm macros won't work; if it did, things would be a lot easier. (cpp gets done once, before any ASM macro processing at all gets done.)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Actually it does work. But you're right, it expands to br\cond, so the cpp thing is not needed at all (brain fart, still learning gas .macro's). This is better

.macro  _if cond
        iflevel = iflevel - 2   ;Set next iflevel
        br\cond .+2             ;Skip next if cond is true
        rjmpfn  iflevel         ;Jump to else or endif
.endm
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

At least someone gets the idea!
Hmm. So gas will treat:

test: cpi r27, 13  /* if (r27 == 13) */
      brne 1f
        /* If clause */
        ldi r27, 10 ; blah blah
        rjmp 2f ; jump past else clause
1:    /* else clause */
      andi r27, ~('A' ^ 'a')
1: ;; would be end of if statement, if there was no else
2: ;; always end of else clause

correctly (the "if" jump jumping to the closest local lable of 1), and then I can reuse 1 and 2 in the next if statement as well?
That could be very useful; That would make the required range of the .if chain be based on only the nesting level, rather than total number of if statements. Just the sort of insight I was looking for! Thanks.

(The usual "handle this set of conditions" can be done with an additional set of macros):

.macro _br_not_e dest
   brne \dest
.end

.macro _if cond
  _br_not_\cond 1:
.endif
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:
So gas will treat:
...
correctly (the "if" jump jumping to the closest local lable of 1), and then I can reuse 1 and 2 in the next if statement as well?
Yes, those reusable numerical labels are very handy for short jumps (not very descriptive but you write in a comment what's going on anyway). From the manual
Quote:
Here is an example:

1: branch 1f
2: branch 1b
1: branch 2f
2: branch 1b

Which is the equivalent of:

label_1: branch label_3
label_2: branch label_1
label_3: branch label_4
label_4: branch label_3

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Things are coming along nicely.
Although, I also discovered that I've been looking at old gas documentation, and more recent versions have an "alternate macro mode" enabled by ".altmacro", which has some (most? All?) of the features I was complaining about not having. Sigh.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

which has some (most? All?) of the features I was complaining about not having

LOCAL by any chance? ;-) When you started this thread I had a look at the manual and some examples of using .altmacro but assumed (wrongly it turns out) that you would have also hit those already. My thought was that because gas is generic across many architectures and not just AVR that someone/somewhere must have already done the _if/_else/_endif thing even if the underlying opcodes were i386 or ARM or whatever. Unfortunately it's surprisingly difficult to Google for as things like if/else are very common words and don't help the search :-(

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

no. I don't think "local" is appropriate; it looks like it would be TOO local. "
EXPR" and "\()" look especially useful, along with the additional string delimiters. I suspect that I could get the original macro structure to work as originally implemented, though I'm finding the local label solution to be much nicer...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

(Hmm. "LOCAL" might turn out to be useful for "loop" structures, where you generally have a backward reference to a new label. It's less useful for IF/etc because you need a forward reference to the created unique symbol.)

The other side of the coin is that given the existence of .altmacro, it seems even more surprising that what I want to do hasn't already been done.
One of the few nice things to say about gas is that it provides a pretty consistent "infrastructure syntax" across a lot of different architectures...

The test code looks like this:

IFTEST:	cpi r16, 'I'
	_if e
	   call dummyfunc
	   mov r2, r24
	   sei
	_endif

IFELSETEST:	cpi r16, 'E'
	_if e
	   call dummyfunc
	   lpm
	   cli
	_else
	  ldi r17, 'X'
	_endif

NESTIFTEST:		
	cpi r27, 12
	_if e
	  ldi r16, 5
	  cpi r27, 13
	  _if ts
	    eor r16, r16
	  _else
	    add r16, r20
	  _endif
	  ldi r17, 123
	_endif
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

FWIW, I think I have this in a more-or-less publishable state. It does _if, _else, _elseif, _endif, _do, _while, _until, and _break

The use of local labels and .altmacro made this relatively easy, making me all the more surprised that it doesn't already exist.

https://github.com/WestfW/struct...

Comments welcome, of course.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Any new status?

I, too, am interested in structuring macros for use in AVR assembler coding. I used similar macros ("Concept-14") for over 15 years while coding mainframe assembler. At first, I couldn't see the point, but quickly grew to understand the benefits for maintenance and future development.

Having recently rediscovered MCUs, I have a lot to relearn across many aspects. I am not at a place yet where I can evaluate the OP's progress, but I know that this (structuring macros) is a piece that I will need soon.

And to those that feel that "C" is the answer to the OP's request, I add my thoughts:

"C" is a nasty language that never should have escaped from Bell Labs a half-century ago. It has the conflicting attributes of being powerful, readily available and requiring no discipline whatsoever to code in it.

It is the coding equivalent of giving knives and sticks to little boys. Assembler is more like C4: More powerful, but it takes some intelligence and knowledge to make it do anything.

Meh... opinions... everybody has one.

Cheers!
Pat

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

"C" is a nasty language that never should have escaped from Bell Labs a half-century ago. It has the conflicting attributes of being powerful, readily available and requiring no discipline whatsoever to code in it.
So C++ then? ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ding! Ding! Ding! Absolutely! Like putting lipstick on a pig AND trying to teach it to sing! :D

I know... "C doesn't make bad code... bad programmers make bad code". "C" just seems to be a bad-programmer magnet. (Though assembler gets its share of oxygen-wasters.)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I know this is 4.5 years late, but thanks to westfw, who posted a link to my method above, I have now updated it so it uses brute-force stringification to generate labels (instead of .ORGing backwards) and so can be adapted to any assembler that allows macros, including gas. See

http://dkeenan.com/AddingStructu...

Last Edited: Sat. Jan 13, 2018 - 02:21 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Neat...

 

I came up with the following recursive macro.

Do most assemblers support recursion of macros?  The initial implementation that I was familiar with used the ability of a macro to redefine other macros, which it turns out is pretty uncommon...

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:

Neat...

...

Do most assemblers support recursion of macros?  The initial implementation that I was familiar with used the ability of a macro to redefine other macros, which it turns out is pretty uncommon...

Thanks Bill. That's a really good question. Of course I'm only using the ability of a macro to invoke another macro (including itself), not define another macro. But regarding macro invocation recursion, I don't know enough assemblers to say. However I am emboldened in my renewed claim of "any assembler", by Garth Wilson's independent implementation, which I just learned about,

http://wilsonminesco.com/Structu...

for an assembler that doesn't even allow one macro to invoke another. He uses file inclusion to work around it.

 

Also, those brute-force-stringification macros could be written even more brute-forcedly without recursion, for say 999 labels, by using 999 string comparison macro IFs, although you'd probably want to write a program to write those macros. :-) And possibly it could be done using macro REPEATs if the assembler has them.

 

I'd be interested to know if you can do it for gas without using .altmacro.

Last Edited: Sat. Jan 13, 2018 - 04:23 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Too bad pas2asm no longer exists. It was not GNU, but with it you were able to write ASM code like this:

delay5ms:
	push r17
	push r18
	for r17 := 0 to 6 do
	begin
	  ldi r18,$DC
	  while r18 <> 0 do
	  begin
	    dec r18
	  end;
	  nop
	  nop
	end;
	pop r18
	pop r17
	ret

http://web.archive.org/web/20031...

http://web.archive.org/web/20031...

 

There was another tool called c4asm but it didn't support AVR at the time:

https://www.avrfreaks.net/forum/b...

 

Last Edited: Wed. Jan 17, 2018 - 11:21 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks avra, for telling us about those tools.

 

The system of macros that westfw, Garth Wilson and I use, lets you do almost the same thing.

Quick Reference: http://dkeenan.com/StructuredCon...

 

Mine seems to be unique in providing structured short-circuit AND and OR. And they generate optimum code.

Last Edited: Wed. Jan 17, 2018 - 01:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Better late than never ...

 

A couple of years ago I have developed a pre-compiler called s'AVR (= structured AVR assembly language) for the 8-bit AVR controllers, which does exactly what obviously a few of you have been looking for (long time ago).

 

Above structured pas2asm example would look like this using the s'AVR syntax:

delay5ms_1:
    push r17
    push r18
    FOR r17 := #7
        ldi r18,$DC    
        WHILE r18 <> #0
            dec r18
            nop
            nop
        ENDW
    ENDF
    pop r18
    pop r17
    ret

; Flat AVR ASM code generated by s'AVR:

delay5ms_1:
    push r17
    push r18
    ;01// FOR r17 := #7
    LDI  r17,7
_L1:
    ldi  r18,$DC
    ;02// WHILE r18 <> #0
_L4:
    TST  r18
    BREQ _L6
    dec  r18
    nop
    nop
    ;02// ENDW
    RJMP _L4
_L6:
    ;01// ENDF
    DEC   r17
    BRNE  _L1
    pop   r18
    pop   r17
    ret

; or simply two nested FOR loops for almost the same result:

delay5ms_2:
    push r17
    push r18
    FOR r17 := #7
        FOR r18 := #$dc
            nop
            nop
        ENDF
    ENDF
    pop r18
    pop r17
    ret

; generated flat AVR ASM code:

delay5ms_2:
    push r17
    push r18
    ;01// FOR r17 := #7
    LDI  r17,7
_L8:
    ;02// FOR    r18 := #$dc
    LDI  r18,$dc
_L11:
    nop
    nop
    ;02// ENDF
    DEC  r18
    BRNE _L11
    ;01// ENDF
    DEC  r17
    BRNE _L8
    pop  r18
    pop  r17
    ret

These are the Control Structure Directives implemented in s'AVR:

IF condition [THEN] 		; test for first branch
	StatementSequence
ELSEIF condition [THEN]	        ; additional branch test(s), multiple and optional
	StatementSequence
ELSE 				; last branch, optional
	StatementSequence
ENDI 				; end of IF structure
-------------------------
LOOP 				; start of endless loop
	StatementSequence	; exit LOOP by EXIT, EXITIF, assembly jump
				; or branch, RET, interrupt or AVR reset only
ENDL 				; end of LOOP structure
-------------------------
WHILE condition 		; test at start of the loop
	StatementSequence
ENDW 				; end of WHILE loop
-------------------------
REPEAT 				; start of the REPEAT loop
	StatementSequence
UNTIL condition 		; test at end of the loop
-------------------------
FOR RegisterAssign 		; FOR loop with initialized loop counter
	StatementSequence
ENDF 				; decrement and test for loop counter = zero
-------------------------
EXIT 				; leave one structure level unconditionally
EXITIF condition 		; conditional exit from actual structure (one level)
EXITIF condition TO label 	; conditional jump out of a structure 

s'AVR is designed to run under Windows (both stand-alone* and embedded in Atmel Studio), but works unter Linux as well (using an EasyBuild script).

More details see the English manual: http://www.led-treiber.de/sAVR-LITE_Manual_2.21.pdf

 

Plenty of s'AVR stuff (including many source code examples) is discussed on my private website. 
If interested, start here: http://www.led-treiber.de/html/leds_soft.html and select wanted items to the left.
Unfortunately currently all is in German language. sad

 

If anyone is interested in the s'AVR Lite program, please drop me a message and I will send it as ZIP (or 7Z) file individually - it's for free (and free of malicious stuff).

Just comments on my software would be appreciated after evaluating.
 

* The EXE file is <600 kByte.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw,  If you're developing on Linux one option is M4 (i.e., /usr/bin/m4).   You can do a lot with M4 but it will require some time to get your hands around it.

(If you've ever installed a software package with "configure" you might be interested to know those scripts are generated with M4.)

 

EDIT: I see someone already proposed m4.

Last Edited: Tue. May 7, 2019 - 09:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I really wanted something that didn't involve an extra process step.

I've actually used M4 before (a long time ago), and found it pretty ugly.

An advantage of using the macro capability of assembler is that your macros can use some knowledge of the symbol properties, rather than being merely text processors (like m4 or the C pre-processor.)

In any case, I wound up with something that I consider suitable. (https://github.com/WestfW/structured_gas)  However, I really haven't written any assembly code complex enough that it would have been worthwhile to use these.   As Clawson and others have pointed out, C or C++ usually works fine (and the cases where it doesn't are usually handled by very short code sequences.)

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

s'AVR,

 

I take it you've heard of this new fangled gadget they're calling "C" ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Clawson,

 

good you remind me. wink

 

I started writing C code under Unix in the 80’s (after having studied Kernighan & Ritchie, edition 1978 and some additional literature).

Even under MS-DOS I initially used MS Quick C - a 2.6 kg software package, including thirteen 5 ¼" diskettes (still on the shelf) ...

 

However, for writing PC software running under Windows, later I decided to go for Delphi (Pascal), which is used for my s’AVR software as well.

 

But for µC code I still prefer structured assembly language (some history see here: http://www.led-treiber.de/html/historisches.html) and it will be very hard to convince me of those "new-fangled gadgets", even though I use them to compare results against s’AVR based code (among others), for example see here: http://www.led-treiber.de/html/ackermann.html

 

Interesting regarding AVR are my results of AF(3,6):

 

AF Result CPU Language MHz Time/sec AI/(MHz*ms) Comment Code Size/Bytes
AF(3,6) 509 ATmega1284 s'AVR 18 0,124 4,4 s'AVR and AVR assembler 30
AF(3,6) 509 ATmega328 s'AVR 16 0,140 4,4 s'AVR and AVR assembler 30
AF(3,6) 509 ATmega328 Arduino C 16 0,260 8,2 avr-gc++ compiler, Option -Os, int/int  
AF(3,6) 509 ATmega328 Arduino C 16 0,205 6,4 avr-gc++ compiler, Option -Os, byte/int 48
AF(3,6) 509 ATmega328 avr gcc 16 0,237 7,4 avr-gcc compiler, Option -O, uint8_fast/int 56

 

If the C code and/or the options I have used can be improved, please let me know.

 

Results of AF(3,9) are very interesting when comparing ATmega1284 (used due to available RAM size for the stack) and s‘AVR against other CPUs and languages.

And the winner is ...

 

@ westfw:

 

When s’AVR is properly set up (once) under Atmel Studio, it is only a single extra mouse click before starting the final assembly procedure.

 

The advantage of structured assembly against pure (flat) assembly language finally is huge, especially when writing complex programs.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I stopped when the first sentence (after translation) said:

The Ackermann function 1 ] (AF) is a strongly recursive function

So, yeah, that sounds like a very valid test for an AVR microcontroller - tons of recursion going on in micros - err not!?!

 

Implement a heating control system or an LCD alarm clock and do so in C or structured assembler and then see how the size/speed compares.  Sure C is not as tight as hand crafted Asm (though once Asm starts to use macroised, standard solutions for things like FOR and IF/ELSE I suspect the advantage is smaller) but the point is that C is more easily maintainable.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Clawson,

 

Hm, not sure whether writing s’AVR code for a clock is a big challenge. It will not be more time-consuming compared to C, I guess.

At least the clock should finally run at the same speed. smiley

 

I have published a s’AVR based library for LED driver TM1638 (also for MAX7219/ AS1107), see http://www.led-treiber.de/html/leds_display.html

The corresponding TM1638 demo software includes a real-time clock as well (among various software counters and others, all zipped together for download, including the generated flat asm code).

 

This is the structured s’AVR code* of the realtime clock section of the TM1638 demo programs:

 

    LOOP                            ; now loop the various demo programs
        rcall   TM1638_CLEAR
        ldi     AKKU3, 3            ; "SELECT..", Text block 3
        rcall   TM1638_PRINT_TXT8
        rcall   delay500ms          ; if S1+S8 are pushed continuously
        clr     BUTTONS
        REPEAT
            rcall   TM1638_KEYPAD   ; check the TM1638 buttons
        UNTIL BUTTONS <> #0         ; wait until any button pressed
        EXITIF  BUTTONS, S8         ; button S8 leaves demos to TEST KEY
                                    ; select demo by button S1..S7
        IF BUTTONS, S1
		.
		.
		.
        ELSEIF BUTTONS, S7          ; show the real-time clock until S8
        ; S3 = hour settings
        ; S4 = minute settings
        ; S5 = reset seconds, but only within hrs/min settings
        ; S1 = decrement hrs/min, can hold down for successive decrement
        ; S2 = increment hrs/min, can hold down for successive increment
        ; S7 = leave settings, but stay in the CLOCK loop and prevent S1/S2/S5
        ; S8 = exit clock time and jump to SELECT loop
            ldi     AKKU3, 5                ; Textblock "CLOCK TIME"
            rcall   TM1638_PRINT_MOVETEXT
            rcall   TM1638_CLEAR
            ldi     yl,low(clk10ms)         ; begin of clock values in SRAM
            ldi     yh,high(clk10ms)
            LOOP                                ; CLOCK loop
                clr     BUTTONS
                REPEAT
                    rcall   TM1638_PRINT_CLOCK  ; update clock time
                    rcall   TM1638_KEYPAD       ; check the TM1638 buttons
                UNTIL BUTTONS <> #0             ; wait until any button pressed
                IF BUTTONS, S3                  ; set hours
                    LOOP                        ; loop until S4, S7 or S8
                        clr     BUTTONS
                        REPEAT
                            rcall   TM1638_PRINT_CLOCK  ; update clock time
                            rcall   TM1638_KEYPAD   ; check the TM1638 buttons
                        UNTIL BUTTONS <> #0     ; wait until any button pressed
                        IF BUTTONS, S2          ; increment
                            ldd     AKKU,y+3    ; get hours
                            inc     AKKU
                            std     y+13,AKKU
                            IF AKKU > #23
                                clr AKKU
                                std y+13,AKKU   ; reset hrs
                            ENDI
                            ldd     AKKU,y+13
                            std     y+3,AKKU    ; restore hours
                        ELSEIF BUTTONS, S1      ; decrement
                            ldd     AKKU,y+3
                            subi    AKKU,1      ; (dec would not affect CY)
                            std     y+13,AKKU
                            IF C
                                ldi AKKU,23
                                std y+13,AKKU   ; roll over
                            ENDI
                            ldd     AKKU,y+13
                            std     y+3,AKKU    ; restore hours
                        ELSEIF BUTTONS, S5      ; reset seconds
                            ldi     AKKU,1
                            std     y+9,AKKU    ; mark reset seconds
                        ENDI
                        rcall   TM1638_PRINT_CLOCK  ; update clock time
                        EXITIF BUTTONS, S8      ; exit clock time
                        EXITIF BUTTONS, S7      ; exit clock settings
                        EXITIF BUTTONS, S4      ; to switch to minutes
                        rcall   delay500ms      ; for S1/S2 repeat cycle
                    ENDL
                ELSEIF BUTTONS, S4              ; set minutes
                    LOOP                        ; loop until S3, S7 or S8
                        clr     BUTTONS
                        REPEAT
                            rcall   TM1638_PRINT_CLOCK  ; update clock time
                            rcall   TM1638_KEYPAD   ; check the TM1638 buttons
                        UNTIL BUTTONS <> #0     ; wait until any button pressed
                        IF BUTTONS, S2          ; increment
                            ldd     AKKU,y+2
                            inc     AKKU
                            std     y+12,AKKU
                            IF AKKU > #59
                                clr AKKU
                                std y+12,AKKU   ; reset min
                            ENDI
                            ldd     AKKU,y+12
                            std     y+2,AKKU    ; restore minutes
                        ELSEIF BUTTONS, S1      ; decrement
                            ldd     AKKU,y+2    ; min
                            subi    AKKU,1      ; (dec would not affect CY)
                            std     y+12,AKKU
                            IF C
                                ldi AKKU,59
                                std y+12,AKKU   ; roll over
                            ENDI
                            ldd     AKKU,y+12
                            std     y+2,AKKU    ; restore minutes
                        ELSEIF BUTTONS, S5      ; reset seconds
                            ldi     AKKU,1
                            std     y+9,AKKU    ; mark reset seconds
                        ENDI
                        rcall   TM1638_PRINT_CLOCK  ; update clock time
                        EXITIF BUTTONS, S8      ; exit clock time
                        EXITIF BUTTONS, S7      ; exit clock settings
                        EXITIF BUTTONS, S3      ; to switch to hours
                        rcall   delay500ms      ; for S1/S2 repeat cycle
                    ENDL
                ENDI
                EXITIF BUTTONS, S8              ; leave CLOCK
            ENDL
            EXITIF BUTTONS, S8                  ; leave CLOCK to SELECT
            rcall   TM1638_LEDS_BF              ; LEDs back and forth
        ENDI                                    ; end of selected loops
    ENDL

Isn’t this neat structured AVR assembly code?

 

As the real-time clock software covers xtal tolerance (part of a 10 msec timer interrupt routine), the clock can be as precise as down to ±1 sec per month.

 

For even more precision I have added a DCF77 radio clock interface in the meantime (using TM1640 for time and date, without buttons; not published yet).

 

The s’AVR code has been written from scratch without any reference to other DCF77 software and was running reliably and very immune against DCF77 signal noise from the beginning. It is running since more than a year exactly synchronously to my reference https://uhr.ptb.de (under Windows 10 even with optional speaker output).

 

* I’m convinced that all my s’AVR code is maintainable at least as good as any C code, but much better than flat assembly code.

As usual, it always depends on how well structured a program is written.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

s'AVR wrote:
Isn’t this neat structured AVR assembly code?
Yes, almost C like! cheeky

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, in the meantime I did a true comparison between AVR GCC (standard Atmel Studio 7 settings) and s’AVR running on an ATmega1284 – now without using any recursion. wink

 

However, it is not heating control but number crunching instead, to be more specific: calculating 1000 factorial (and more) at full precision (all 2568 decimal digits in case of 1000!).

 

The result is clear-cut for the given application: s’AVR (= structured AVR assembly language) needs less than half the program memory and is 10 times faster compared to AVR GCC.

 

The source code and some background regarding the algorithm being used for both program versions can be found on my private website*: http://www.led-treiber.de/html/fakultat.html

 

Back to the (structured) AVR ASM roots!**

 

s’AVR

 

* Sorry, still in German language besides the s’AVR source code.

But as long as C is your preferred programming language, you won’t need any comments at all. cheeky

 

** Again, if any AVR ASM enthusiasts are interested: The s’AVR Lite precompiler is for free (and without any commercial background). Just send me a PM. The manual is available for download.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

s'AVR wrote:

...

** Again, if any AVR ASM enthusiasts are interested: The s’AVR Lite precompiler is for free (and without any commercial background). Just send me a PM. The manual is available for download.

This looks quite nifty.

Does it have versions for other MCUs, or is it easy to port ?

I see a note about old delphi - did you try on FreePASCAL/Lazarus ? - a command line version should be more portable ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Who-me wrote:

Does it have versions for other MCUs, or is it easy to port ?

Yes, being called SXp it did support the SX family from former Scenix/Ubicom and could also be used for quite old PIC16C5x, see http://www.led-treiber.de/html/h...

s'AVR is based on SXp, but got much more features.

 

Porting s'AVR to another MCU would depend on the MCU architecture (and certainly on the demand and on my leisure time).

s'AVR is designed to be very small (< 600 kbyte) and to compile very fast. It is based on compiler literature from Niklaus Wirth.

 

Who-me wrote:

I see a note about old delphi - did you try on FreePASCAL/Lazarus ? - a command line version should be more portable ?

 

I'm aware about Embarcadero (Delphi Community Edition, e. g.) and Lazarus (expecially for Linux), but kept my pretty old Delphi 5 (a licensed version), as the later free versions surprisingly did perform worse, even though offering more features.

 

The old Delphi also allows that the generated program can run under both 32-bit and 64-bit Windows versions (and Wine).

 

s'AVR (and SXp) among others also supports a command line interface from the beginning, see manual.

That's how s'AVR can be embedded seamlessly into Atmel Studio.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The result is clear-cut for the given application: s’AVR (= structured AVR assembly language) needs less than half the program memory and is 10 times faster compared to AVR GCC.

                    fakul[j] = a % 10;                      // Einer

                    a = a / 10;

                    b = a % 10;                             // Zehner = Übertrag

 

; MOD_DIV10.mac
; AVR Assembler macro for MODULO and DIV

 

Hmmph.  It hardly seems like a fair comparison, when the core of your asm code is a highly optimized 8bit-cascading divide/modulo function (macro), while the C code ends up using standard full-fledged 16bit division functions.  I guess it is a bit disappointing that gcc doesn't optimize for the constant divisor (especially 10!) - at least it only calls a single function to get both dividend and remainder...  It would be interesting to see the performance comparison if you put in an equivalent "C" optimized divmod10 function...

 

I bet you could get the C code to be only about 4x slower than the asm!

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have not look into the details but I assume that a never can be more than 99.(2 digits) 

If that is the case then OP's  /10  %10 can be done faster. (how much depend on the program structure, if too optimized it will cost more moves :) ).

 

 

Last Edited: Sun. Jun 23, 2019 - 12:17 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I agree that a speed factor of 10 looks too bad for GCC when being compared against s’AVR.

 

So I checked the GCC program again and – what surprise – a little change makes a big difference:

 

When variables a and b are also defined as uint8_t (instead of integer) the generated code looks quite different, as divmodhi4 is only used for variable zi (which must be at least 16 bit). Then for the other "a / 10" and "a % 10" a macro like code is generated by GCC (similar to s'AVR).

 

As mentioned in footnote [1] variable "a" is never bigger than 89. Obviously the compiler cannot optimize when a and b are defined as integers.

 

Finally the GCC program is just 1.8 times slower than s'AVR.

However, now the pure factorial code is 2.7 times longer than the s’AVR code.

 

I will do some additional tests and change the article on my website accordingly.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Remember that the ASM code also can be optimized, something like 20%(speed) (unless it has to be written in nice s'AVR)