Shop OBEX P1 Docs P2 Docs Learn Events
Propeller Assembly Q&A — Parallax Forums

Propeller Assembly Q&A

Paul BakerPaul Baker Posts: 6,351
edited 2009-10-06 17:52 in Propeller 1
SailerMan said...
Thanks... Why don't you move this section of the tread to another post.
In any acount My problem lies in the fact that I have a hard time thinking low level.
Let's start out really small.

·······
Pub Main|Index,Count
     Repeat Index From 0 to 10
       Count+=1



····
Sure, the equivalent assembly code for this would look something like:

 
loop  add  Count, #1     'add 1 to the value of count
      djnz Index, #loop  'decrement Index, if Index is not equal to 0 jump to loop, else continue 
...
Index long 11
Count long 0
 

Note that Index is set to 11, thats because your Spin loop executes 11 times (0 + 1-10). If you needed to go through this loop more than once, you would have to re-initialize Index by performing "mov Index, #11" before entering loop. The # you see in the code means it's an immediate value, IOW it is the value itself, if for instance you said "add Count, 1" instead, it would take the value in address 1 of the cog's memory and add that to Count. The # works for any value between 0 and 511, because there are only nine bits that can be stored for an immediate value.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Paul Baker
Propeller Applications Engineer

Parallax, Inc.
«13

Comments

  • SailerManSailerMan Posts: 337
    edited 2006-12-16 01:08
    [size=2][code]
    Loop add Count, #1 'add 1 to the value of count 
    djnz Index, #loop 'decrement Index, if Index is not equal to 0 jump to loop, else continue 
    ... 
    Index long 11 
    Count long 0
    



    [/code][/size]
    In Order For the Above Code to run, What other code must I add? I am not at home so I can't test. Would the below work?
    Pub Start
        Cognew(@Begin,0)
     
    DAT
     
    Begin    ORG
     
    Loop     add  Count, #1      'add 1 to the value of count 
             djnz Index, #loop   'Decrement Index and Jump to #loop if >0
     
    Index long 11 
    Count long 0
    


    Why use·Count Long·0?· Does·Count Res·1 Do the Same Thing?
    You Said,· "· If you needed to go through this loop more than once, you would have to re-initialize Index by performing "mov Index, #11" "
    [size=2][code]
    Pub Start
        Cognew(@Begin,0)
     
    DAT
     
    Begin    ORG
     
    Loop     add  Count, #1      'add 1 to the value of count 
             djnz Index, #loop   'Decrement Index and Jump to #loop if >0
             Mov Index,#11
             jmp #Loop
    Index long 11 
    Count long 0
    

    [/code][/size]




    Would this work?·

    Why is there a "#" in front of The Loop label?

    Thanks for your help,

    Eric


    ·
  • Paul BakerPaul Baker Posts: 6,351
    edited 2006-12-16 01:41
    Yes the code will work (you should also insert
    CON
    _clkmode = xtal1 + pll16x
    _xinfreq = 5_000_000

    at the top too)

    The only issue is the cog will execute this without telling the external world what it's doing, but it will constantly be counting up in Count (because Count carries its value into the next set of loops, "mov Count, #0" will reset it's value).

    The # is in front of Loop because all labels are resolved into an address, using the # tells it to use the address itself, otherwise it will grab the value in Loop and jump to that value, not the address. In this case it will grab the lowest nine bits at address 0 (which is what Label resolves to), located there is "add Count, #1", the lowest nine bits of that instruction is the source register or #1, so it will jump to address 1 not 0 (to "djnz Index, #loop" not "Loop add Count, #1" like we want).

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Paul Baker
    Propeller Applications Engineer

    Parallax, Inc.
  • SailerManSailerMan Posts: 337
    edited 2006-12-16 02:08
    If I wanted to add more that 511 to Count how would I do that out of curiosity?

    Now let's take this a little further.

    I want to send count to the outside world via Pins 0-7 as a binary led Arrangment. What would be the basic (Long)·way of doing it.

    Thanks,

    Eric
  • Paul BakerPaul Baker Posts: 6,351
    edited 2006-12-16 03:11
    To uses values greater than 511, you can use math to generate the value or you can declare the value like "somevalue· LONG $1234_5678" then use that value in your calculations like "add Count, somevalue".

    To write the value out to pins 0-7 you would do something like this:
    Pub Start
        Cognew(@Begin,0)
     
    DAT
     
    Begin    ORG
             mov  dira, #$7F      'set pins 0-7 to output (bits 0-7 set)
    Loop     add  Count, #1      'add 1 to the value of count 
             mov  outa, Count    'write count's value to pins 0-7
             djnz Index, #loop   'Decrement Index and Jump to #loop if >0
             Mov Index,#11       'reset index
             mov Count,#0        'reset count's value
             jmp #Loop
     
    Index long 11 
    Count long 0
     
    
    



    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Paul Baker
    Propeller Applications Engineer

    Parallax, Inc.

    Post Edited (Paul Baker (Parallax)) : 12/16/2006 5:07:50 AM GMT
  • acantostegaacantostega Posts: 105
    edited 2006-12-16 04:43
    This reminds of "The Little Schemer"/"The Little Lisper" smile.gif
  • SailerManSailerMan Posts: 337
    edited 2006-12-17 14:23
    Paul,

    Thanks for this...OK I'm understanding a little.

    How about a simple "IF-THEN-ELSE" condition.

    If A=B Then
    Equal=Equal+1
    Else
    NotEqual=NotEqual+1

    What is the difference between

    Variable Long 100

    And

    Variable Res 1·, and then later Mov Variable,#100

    Regards,
    Eric
  • Mike GreenMike Green Posts: 23,101
    edited 2006-12-17 15:56
                 cmp    A,B       nz          ' If A = B Then
        if_z    add     Equal,#1           '   Equal=Equal+1
        if_nz  add     NotEqual,#1      ' Else NotEqual=NotEqual+1
    
    


    Note the above is suitable for small If/Then statements.
    For complicated statement, you should use jumps like
                 cmp    A,B       nz          ' If A = B Then
        if_nz  jmp    #:thenIf            '   Equal=Equal+1
                 add    Equal,#1
                 jmp    #:endIf              ' Else NotEqual=NotEqual+1
    :thenIf  add     NotEqual,#1
    :endIf
    
    


    When you use a "long 100", the value "100" is compiled into your code
    as the initial value of "Variable". If your program changes it, it is changed
    until the program is reloaded. If you use the "Res 1" and "Mov Variable,#100",
    the value is undefined (probably zero) until the "Mov" instruction is executed.
    If the "Mov" is in your initialization, "Variable" gets initialized during initialization.
    (The first is static, the second is dynamic and depends on the flow of your program).
    Usually, the first form is used when Variable is actually a constant for your program
    or if the program will always be reloaded from RAM to COG for initialization.
  • CJCJ Posts: 470
    edited 2006-12-17 16:04
                  sub VariableA, VariableB wz, nr   'check equallity, set Z flag and leave variables as they were using the NoResult flag
            if_e  add Equal, #1                     'if equal, do this. skipped if not equal
            if_ne add NotEqual, #1                  'if not equal, do this. skipped if equal
                                                                                                   
    Equal         long 0
    NotEqual      long 0
    VariableA     res 1
    VariableB     res 1
    
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Who says you have to have knowledge to use it?

    I've killed a fly with my bare mind.

    Post Edited (CJ) : 12/17/2006 4:21:16 PM GMT
  • SailerManSailerMan Posts: 337
    edited 2006-12-17 16:12
    What is the NZ at the end of the 1st Line?

    Is the ":" used for in this case.

    Thanks for all of this information. It is really helping to understand.
  • Mike GreenMike Green Posts: 23,101
    edited 2006-12-17 16:50
    Sorry, mis-typed. The nz should be wz.

    The ":" is used for a local label. These are just like normal labels, but can be redefined. Essentially, the space in your program between regular labels is a "local label space". Any local label defined there is different from the same local label defined between some other regular labels. They're intended for the case where you need a label say for a jump, but that label isn't needed elsewhere and the name itself isn't particularly important. (page 348 in the manual).
  • SailerManSailerMan Posts: 337
    edited 2006-12-17 19:17
    That's what I thought... about ":" with a label, I just couldn't find it in the manual.

    CJ, Thanks... A second way of doing it.
  • CJCJ Posts: 470
    edited 2006-12-17 19:36
    mine and mike's first one actually compile to the same machine code, the cmp instruction is just a sub with NR as the default, and if_e is the same as if_z, if_ne is the same as if_nz,

    good examples of the readability features used in the syntactical setup

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Who says you have to have knowledge to use it?

    I've killed a fly with my bare mind.
  • Dennis FerronDennis Ferron Posts: 480
    edited 2007-02-24 08:03
    I have some further questions about Propeller assembly:

    There are several things I expected to be able to do, but don't see explicit instructions for. I assume there are tricks to build these operations out of other operations instead.

    1. How do I invert all the bits in a register? I expected it would be coded "not Mask". (Mask is the name of my register.) But there is no "not" instruction. I ended up having to use "xor Mask, #$FF".

    2. How do I explicitly set/clear the Z flag? I've been using the Z flag to hold the return values from my functions which return booleans. It's very convenient because I can end the function with a "cmp" and pick right up after the function call with an "if_z", and I don't have to worry about creating a temporary register to hold the value. But now I've run into a function which has more complicated logic, and I can't narrow that down to a single cmp. So I want to explicitly code "setz" in one part of the function, and "clrz" in another, to return true or false. Except I can't because there is no setz! I thought about "mov reg, #0 NR, WZ", but it doesn't seem very elegant. Is there a better way to set the Z flag?
  • Jasper_MJasper_M Posts: 222
    edited 2007-02-24 08:46
    Dennis Ferron said...
    I have some further questions about Propeller assembly:

    There are several things I expected to be able to do, but don't see explicit instructions for. I assume there are tricks to build these operations out of other operations instead.

    1. How do I invert all the bits in a register? I expected it would be coded "not Mask". (Mask is the name of my register.) But there is no "not" instruction. I ended up having to use "xor Mask, #$FF".

    2. How do I explicitly set/clear the Z flag? I've been using the Z flag to hold the return values from my functions which return booleans. It's very convenient because I can end the function with a "cmp" and pick right up after the function call with an "if_z", and I don't have to worry about creating a temporary register to hold the value. But now I've run into a function which has more complicated logic, and I can't narrow that down to a single cmp. So I want to explicitly code "setz" in one part of the function, and "clrz" in another, to return true or false. Except I can't because there is no setz! I thought about "mov reg, #0 NR, WZ", but it doesn't seem very elegant. Is there a better way to set the Z flag?

    1. That's the only good way to NOT a register. But of course you need to use

    xor Reg, all_ones
    ...
    all_ones long -1 ' -1 = all ones

    if the reg contents are larger than one byte.

    2. The mov 0, #0 nr,wz and mov 0, #1 nr, wz is a good way to set/clear the Z flag. I haven't used it, I usually write cmp 0,0 wz to set Z and cmp all_ones, #0 wz to clear it (the all_ones would be the notmask). Neither of these is elegant, but it isn't the number one priority when writing in ASM anyway.

    Also, to set and clear the carry flag: test all_ones, #0 wc to clear, test all_ones, #1 wc to set it.
  • Paul BakerPaul Baker Posts: 6,351
    edited 2007-02-24 09:01
    You understand correctly on both accounts, I frequently reserve two constants: Zero and Ones to assist in generating "tricks". I use "TEST Ones, Ones WZ" and "TEST Zero, Zero WZ" for clrz and setz respectively, but there are several means for doing it.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Paul Baker
    Propeller Applications Engineer

    Parallax, Inc.
  • JamesxJamesx Posts: 132
    edited 2007-02-24 11:35
    This is a great thread. I'm learning a lot from these clear, simple demonstrations of common coding necessities.

    Jim C
  • potatoheadpotatohead Posts: 10,261
    edited 2007-02-24 15:16
    Re: elegance, seems to me an explicit set or clear of the zero flag would be a whole instruction right. Either you blow 32 bits or you don't, so why not just allow the user to do something like:

    mov reg, #0 NR, WZ

    and be done with it?

    I've enjoyed this thread too. For me, the prop assembly makes some hard things fairly easy. I do often get tripped up on easy things though!

    The smaller number of actual opcodes does make hunting for solutions easier though. IMHO, this is a good thing. Having written assembly on some CPU's with very large numbers of opcodes, I find this refreshing. At first, it seemed really limited, but now it's really making some sense. Blowing 32 bits on just a set to a flag, is gonna happen dedicated opcode or not right? In that mindset, the above is perfectly reasonable.
  • James LongJames Long Posts: 1,181
    edited 2007-02-24 16:28
    Hey someone make this thread a sticky......and continue expanding on it......I really need to try all the above....I have no clue about assembly.

    James L
  • Dennis FerronDennis Ferron Posts: 480
    edited 2007-02-24 18:30
    As I understand it though, the immediate field can only hold... is it 9 bits? So "xor reg, #$1FF" will work, but "xor reg, #$FFFF_FFFF" will not invert all of the bits of a 32-bit register, right? In such cases you would have to store $FFFF_FFFF in another register and use the "xor reg1, reg2" form.

    I really do like Propeller assembly. I grew up on 8086 assembly - literally I grew up on it, I spent every day after school for years in high school hacking assembly on a DOS computer - but I find the Propeller assembly much more orthogonal than a CISC machine like the 8086. By orthogonal I mean that the pieces of the instructions in Propeller assembly can be mixed and matched to create a much richer set of operations. On a CISC machine the instructions are all different sizes and you can't combine pieces of them, but there are also little 1-byte instructions for doing things like setting/clearing flags. So sometimes I go to do something on the Propeller and go "Smile! There's no instruction for that!" but it really doesn't matter in the long run; it's just a new set of tricks to learn - I had to go through the same process of learning tricks to make up for things the 8086 doesn't do, which I can tell you is a lot more frustrating than Propeller assembly!

    Actually Propeller assembly is not frustrating at all. Though it is a new thing to learn, the instructions pretty much do what I think they are going to do without weird side effects. When it does surprise me it is usually a good surprise. Some examples:

    When I looked up what the "movs" instruction does (move to source reg), I suddenly realized: instead of self modifying code being a dirty hack, this processor has op-codes with direct support for self-modifying code! Wicked!

    Then I got an error message where it said it wanted me to label my "ret" statement in a subroutine I wrote. WTF? So I looked it up in the Propeller Manual, and found out that the Propeller uses self-modifying code to do function calls without a stack! Wow!
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2007-02-24 18:45
    There are also situations where you might want to set boh flags to a certain state at once.

    cmp zero, zero wz, wc ' z=1, c=0
    cmp neg1, zero wz, wc ' z=0, c=1
    cmp zero, neg1 wz, wc ' z=0, c=0
    add neg1, #1 nr, wz, wc ' z=1, c=1

    zero long 0
    neg1 long -1 ' all ones

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com
  • Jasper_MJasper_M Posts: 222
    edited 2007-02-24 18:56
    Yes, the #1FF works, ie. it works up to 9 bits (yeah, I said 1 byte but everyone makes mistakes [noparse]:)[/noparse]). I've never even tried X86 ASM, but I've done ASM on PIC and Z80. PIC asm is extremely annoying - it has only 1 accumulator register, ie. one instruction can only access 1 RAM location and the other register has to be W (the accumulator)... That's what I call frustrating : D
  • Dennis FerronDennis Ferron Posts: 480
    edited 2007-02-25 00:48
    Yeah that's one of the big differences between 6502 assembly and x86 assembly. For the 6502, the CPU is the bottleneck and RAM is fast in comparison, and you have only a single accumulator. On the 8086 architecture on the other hand, the CPU is fast and RAM is the bottleneck. You have more registers on the 8086 so to make fast code you do all sorts of tricks to try to hold as much of the "problem" in registers as possible. Almost any amount of juggling registers is worthwhile if it saves you a single RAM access; most of my programs consist of sometimes as much as a hundred lines of register operations crunching numbers between every RAM access. You can't do that on the 6502 - it has a different "rhythm" to it - on the 6502 you do only a little between each load/store.

    But the Propeller is completely different from both of those. For the Propeller it's more like having 512 registers and your program is stored *in* the registers as well! So you can do things in Propeller assembly that are just mind boggling.
  • Dennis FerronDennis Ferron Posts: 480
    edited 2007-02-25 03:45
    Earlier I said, "I've been using the Z flag to hold the return values from my functions which return booleans."

    Well let me just say this is a BAD idea. I've just spent the last few hours trying to debug my code and the way it's acting made no sense whatsoever.

    Finally I read in the manual that the CALL instruction clears the Z flag!!! So no matter what I did to set the Z flag, I was always returning false!
  • Mike GreenMike Green Posts: 23,101
    edited 2007-02-25 03:58
    The CALL instruction is not supposed to clear the Z flag unless you have the WZ effect specified in the instruction. If you read the manual carefully, you'll see that the various instruction descriptions talk about the meaning of the Z flag, but this meaning is not stored in the Z flag unless the WZ effect is included in the instruction. The same thing is true for the C flag and the WC effect. If the Z flag is in fact being set, there may be a problem with the assembler setting the WZ bit when it shouldn't be set.
  • Dennis FerronDennis Ferron Posts: 480
    edited 2007-02-25 04:29
    The strange results I've seen may have been due to leaving the # off jumps! That will really cause strange behavior too. So I'm not sure if the call instruction was overwriting my Z flag or not.
  • rjo_rjo_ Posts: 1,825
    edited 2007-02-25 04:30
    Mike,

    1. I have already read through the descriptions and satisfied myself that there is no facility for shared cog memory for passing data from one cog without going through the Hub. Is this right?

    2. Is anyone building an encryption object? I could do that. There actually is enough memory available in the HUB to do good video stuff... but it would have to be encrypted on the way in and the way out. I was thinking of just running the video through a smart differential amplifier, for which there are probably better uses.





    Thanks

    Rich
  • Mike GreenMike Green Posts: 23,101
    edited 2007-02-25 05:49
    Rich,
    There is no such thing as shared cog memory. The only way to pass data from one cog to another without going through hub memory is to use a shared I/O pin. If you have an I/O pin you're not using for anything, you can transmit data serially with one cog and "listen" to the I/O pin with another cog.
  • AleAle Posts: 2,363
    edited 2007-07-12 10:51
    I just found this thread, out of curiosity...

    I have read many books and datasheets about processors and uCs, but none of them are useful to learn assembler or better put, the way assembler works. An exception is a book titled:
    "Assembler for the 8086 by John Socha and Peter Norton" From 1986. (Those guys if you have been in this for long enough, nc anybody ?)

    Besides is an old book and for the 8086 and thus DOS related, it is very good at explaining how the assembler language/processor works.
    After some time, just reading a datasheet gives me enough to start with a new instruction set. (May be that is why I have problems with OO programming smilewinkgrin.gif ).

    I'd recommend it to anybody how wants to learn asm.

    I got a Spanish translation, so It should exist in more than 2 languages.
  • Fred HawkinsFred Hawkins Posts: 997
    edited 2007-07-14 11:31
    I am trying to understand Call, Jmpret, Ret.

    Question: does the Call and Jmpret behavior (stores the pc + 1 return address at RetinstAddr) happen at compile, load or execution? My expectation now is that it happens
    at execution (thereby letting multiple callers to a routine).

    As I understand Call, the assembler looks for a label with "_ret" and modifies the Jmp code there at compile time. So this question: can you modify the "wrong" opcode? That is, not have a Ret instruction at the _ret label? Or is will the assembler enforce good coding habits?
  • Mike GreenMike Green Posts: 23,101
    edited 2007-07-14 13:46
    Interestingly, someone just pointed out that these, and the JMP instruction, are all the same instruction except for the NR flag.

    Anyway, the storing of the return address happens at execution time (it has to ... think about it).

    Call and return are "macros" in that they're shortcuts that are handled by the assembler for convenience. The Call is really just a JmpRet and the Return is just a Jmp. When the assembler sees a "Call #Routine", it produces a "JmpRet Routine_ret,#Routine". When the assembler sees a "Ret", it produces a "Jmp #0". The assembler does no checking of what's at the "Routine_ret" label. It's normally not the job of an assembler to enforce good coding habits. It might be useful as an option for an assembler to issue warning messages, but this assembler/compiler has no provision for warning messages.
Sign In or Register to comment.