Shop OBEX P1 Docs P2 Docs Learn Events
Problem with PASM: "Source register/constant cannot exceed $1FF" — Parallax Forums

Problem with PASM: "Source register/constant cannot exceed $1FF"

turbosupraturbosupra Posts: 1,088
edited 2012-04-09 11:43 in Propeller 1
All three of these lines of code generate this error. Through searching I saw that some were setting constants, to an up to 31 bit signed value and then using cmp or mov with a pointer to that address. I can't do that with my code, as I'm doing math based on changing variables. How do you handle large equations with variables in PASM, that are larger than 9 bits? For additional information, they have to be done real time, and can't be set in spin before I launch the PASM cog.

Thanks for reading.
cmp     ((current/10)*100), ((previous7Avg/10)*110) WZ, WC

cmp     ((current/10)*100), ((previous7Avg/10)*90) WZ, WC

mov     toothRpm, (((clk/previous7Avg)*60)/36) WZ, WC

«1

Comments

  • average joeaverage joe Posts: 795
    edited 2012-04-08 06:15
    It is not possible to do computations like that with PSAM. Those would need to be done in SPIN or broken down into order of operations. Each instruction does one instruction. Psudo would be. :
    t1 = current
    t1 / 10
    t1* 100
    t2 = previous7Ave
    t2 / 10
    t2 * 110
    "cmp t1, t2 WZ, WC"
    
    All three statements need to be processed for this 1 instruction, 1 operation coding. Also PASM does not have native divide support.
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2012-04-08 06:21
    other pasm experts will no doubt chime in soon too

    all constants >511 need to be declared as longs, usually in a group at the end of the pasm program. Those 0-511 can be declared in the code. So you do end up with slightly odd code like:
    mov a,one_thousand

    Second, you can't do multiplies and divides. Those are going to have to be done with some custom multiply and divide routines. These exist and there are many types, eg one byte times one byte is shorter code than one long times one long. So you have to think about the limits of the values.

    Third, sometimes it is better to work with a different scale, eg multiply all your numbers times 256, do the multiply and divide, then at the end, divide by 256. I chose 256 because it is better than, say, 100, because you can multiply/divide numbers like 64,128,256 with bitshifts. It may be worth thinking about all the divisions you have and converting those to powers of two, then scaling the multiplies accordingly. Divisions are harder than multiplies. So you have a multiply there by 60 then a divide by 36. You could do that instead with a multiply by 427 then a divide by 256. The divide by 256 is a shr #8. You can get cunning too about the multiplies by breaking them down into bitshifts and adds. Then you don't necessarily need a multiply subroutine.

    Fourth, you can't put things in brackets. All of that has to be expanded out to one single instruction at a time. Before converting spin to pasm, the first thing I tend to do is take all the code in brackets and expand it out to single spin commands. Then convert each of those to pasm.
  • average joeaverage joe Posts: 795
    edited 2012-04-08 06:23
    I found divide code.http://forums.parallax.com/showthread.php?127824-I-need-some-help-with-a-32-bit-PASM-divide-routine...
    Also. If you are going to use a number larger than $1FF (Dec ) you need to declare it as a long like
    Dat
    *asm code here*
    'initialized variables
    LargeNum            long       $0000_0200 'number larger than $1ff
    
    I remembered there is no multiply either so look at this too http://forums.parallax.com/showthread.php?132608-PASM-multiply-questions
    Don't forget the literal indicator either.
    add LargeNum, #1ff
    
    *edit*
    Beat me to the punch DOC, I hate when my fingers are faster than my brain.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2012-04-08 06:49
    Another thing to keep in mind (there are lots of things to keep in mind when using PASM) is each line of code is a long.
    The 32 bits of PASM code is divided up into groups of bits to hold the instruction, distination (address only), other stuff like flags and instruction code and finally the last nine bits holds either an address or a number to use directly as the source (one of the 32 bits tells the Prop which one). Nine bits is enough to hold the address of any location in the cog since its size is 2K (or nine bits). If you're not using an address but an actual number, it has to fit within the 9-bit source section of the PASM line.

    I haven't read Pototohead's tutorial but deSilva's covers this. It took me several readings of deSilva tutorial to understand much of it.
  • turbosupraturbosupra Posts: 1,088
    edited 2012-04-08 06:55
    Thanks everyone, it's going to take a while to get my brain wrapped around this and the bit shift division/multiplication.

    When you declare a number larger than 511 like
    LargeNum            long       $0000_0200 'number larger than $1ff
    

    does the compiler automatically know that particular long is not to be formatted as a typical register with 9 source, 9 destination bits, etc? Does it matter what value you initialize it to, as long as it is larger than 511?
  • average joeaverage joe Posts: 795
    edited 2012-04-08 06:59
    The formatting will always be the same. If you decide to use the D-field, S-field, or I-field, they are there. Otherwise it won't matter.
    Initialization depends on your needs. You could even initialize it to 0, doesn't matter.
    Zero        long          0
    
  • Duane DegnDuane Degn Posts: 10,588
    edited 2012-04-08 07:45
    If you were really clever (and a masochist), you could write your PASM code all as long list of longs. (example: "long <some number equivalent to the PASM code>")
    A line of PASM instruction or a declared long just sets the 32 bits of that particular long to a certain value.

    When you launch the cog, the cog "brain" (I know there's a better term) will assume the first line is an instruction and try to use it as such and then continue to the next long unless the current long (using it as an instruction) tells it to execute some other long (as with jmp). It doesn't care how you wrote the code (using instructions or just numbers).
  • average joeaverage joe Posts: 795
    edited 2012-04-08 07:52
    One really powerful feature is the MOVI, MOVS, and MOVD instructions. I'm not real comfortable with these but you can use them for self modifying code. It might just come in handy.

    http://forums.parallax.com/showthread.php?125188-PASM-self-modifying-code-the-best-method-to-do-this
  • turbosupraturbosupra Posts: 1,088
    edited 2012-04-08 08:06
    So then how does it work when you initialize it to 512/$0000_0200 or any number beyond the 9 bit register?

    The formatting will always be the same. If you decide to use the D-field, S-field, or I-field, they are there. Otherwise it won't matter.
    Initialization depends on your needs. You could even initialize it to 0, doesn't matter.
    Zero        long          0
    
  • turbosupraturbosupra Posts: 1,088
    edited 2012-04-08 08:17
    Although I have been known to be a masochist, unfortunately this is above my understanding of PASM, as I'm not even sure what you are suggesting.


    Duane Degn wrote: »
    If you were really clever (and a masochist), you could write your PASM code all as long list of longs. (example: "long <some number equivalent to the PASM code>")
    A line of PASM instruction or a declared long just sets the 32 bits of that particular long to a certain value.

    When you launch the cog, the cog "brain" (I know there's a better term) will assume the first line is an instruction and try to use it as such and then continue to the next long unless the current long (using it as an instruction) tells it to execute some other long (as with jmp). It doesn't care how you wrote the code (using instructions or just numbers).
  • RS_JimRS_Jim Posts: 1,768
    edited 2012-04-08 08:31
    When you assign a value to a long, that value can be anything from $0000_0000 to $FFFF_FFFF. The long label becomes the value at compile time.
    [/code]
    locallabel Mov regnumber,largenumber
    [code]
    Puts the contents of largenumber in the register regnumber at locallabel (label is optional)

    Hopee this helps
    Jim
  • turbosupraturbosupra Posts: 1,088
    edited 2012-04-08 09:15
    Hi Jim

    Thank you, that does help.

    So at compile time, I can set a long to a value of anything from 0 to 11111111111111111111111111111111 when I initialize it in PASM. And this assignment can change the long from being a general purpose register to an (up to) 32bit numeric value? But at this point, it becomes almost a constant?

    How come this can't be done during execution time? It seems like it should be able to be done then as well?
  • turbosupraturbosupra Posts: 1,088
    edited 2012-04-08 09:20
    This is great advice. I will try to multiply/divide by something to the power of two as you've advised. So is the advice to break it down line by line and then convert it, I can see the value in that.

    Until I learn how to debug PASM, I can't try to get very cunning with it. Right now my debug is trial and error, which really hurts efficiency.


    I may try some of the routines that average joe posted as well, it'd be much nicer if I could just use a method.


    Dr_Acula wrote: »
    other pasm experts will no doubt chime in soon too

    all constants >511 need to be declared as longs, usually in a group at the end of the pasm program. Those 0-511 can be declared in the code. So you do end up with slightly odd code like:
    mov a,one_thousand

    Second, you can't do multiplies and divides. Those are going to have to be done with some custom multiply and divide routines. These exist and there are many types, eg one byte times one byte is shorter code than one long times one long. So you have to think about the limits of the values.

    Third, sometimes it is better to work with a different scale, eg multiply all your numbers times 256, do the multiply and divide, then at the end, divide by 256. I chose 256 because it is better than, say, 100, because you can multiply/divide numbers like 64,128,256 with bitshifts. It may be worth thinking about all the divisions you have and converting those to powers of two, then scaling the multiplies accordingly. Divisions are harder than multiplies. So you have a multiply there by 60 then a divide by 36. You could do that instead with a multiply by 427 then a divide by 256. The divide by 256 is a shr #8. You can get cunning too about the multiplies by breaking them down into bitshifts and adds. Then you don't necessarily need a multiply subroutine.

    Fourth, you can't put things in brackets. All of that has to be expanded out to one single instruction at a time. Before converting spin to pasm, the first thing I tend to do is take all the code in brackets and expand it out to single spin commands. Then convert each of those to pasm.
  • Mike GreenMike Green Posts: 23,101
    edited 2012-04-08 09:25
    Everything in the cog's memory (512 longs) is a 32-bit long value. Everything. Some of these long values are executed as instructions and the cog's execution unit separates the 32-bit values into pieces ... 9-bits here, 9-bits there, 6-bits here, etc. The special move instructions MOVD / MOVS / MOVI can modify parts of a 32-bit value and leave the rest alone.

    Note that this is different from the hub memory which is organized as 32K bytes. These can be treated as longs (4 bytes) or words (2 bytes) or as single bytes. In the case of longs and words, their addresses have to be at appropriate multiples of the size. The RDxxxx and WRxxxx instructions throw away the least significant bit or 2 bits of the hub address to make this so.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2012-04-08 09:28
    turbosupra wrote: »
    Although I have been known to be a masochist, unfortunately this is above my understanding of PASM, as I'm not even sure what you are suggesting.

    I was kind of in a hurry when I wrote that post. After rereading it, I'd be surprised if many people understood it (including those versed in PASM). I'll try to come up with an example.
  • average joeaverage joe Posts: 795
    edited 2012-04-08 09:53
    @Duane, before I discovered LMM I was thinking about doing "functions" that would be used rarely this way. Then they could be loaded from a binary file from external memory when needed. A thing of the past now.
    @Turbo, I'm pretty new to PASM myself but have worked in assembly before. If you can afford a few pins, debugging by leds can be a huge help. When all your leds are used, one way I debug things is declare a space in main memory for passing stuff between spin and asm. This can be done with RDLONG, RDWORD, RDBYTE, and WRLONG... and so on. The easiest example I can think of is Cluso's ramblade driver... the portions in question go
    CON
    ''Dracblade driver for talking to a ram chip via three latches
    '' Modified code from Cluso's triblade
    ''also includes commands to move blocks of data to the ILI9325 touchscreen display
    ''Modified for counters by Joe for ssd1289
    ' DoCmd (command_, hub_address, ram_address, block_length)                                                         
    
    ' I - initialise     
    ' W - Move block_length bytes from hub_address to ram_address
    ' R - Move block_length bytes from ram_address to hub_address
    
    '  Y - moves data to the display from hub address, length times
    '  Z - moves command to the display from hub address, length times
    
    VAR
    
    ' communication params(5) between cog driver code - only "command" and "errx" are modified by the driver
       long  command, hubaddrs, ramaddrs, blocklen, errx, cog ' rendezvous between spin and assembly (can be used cog to cog)
    '        command  = R, W, H I,D etc =0 when operation completed by cog
    '        hubaddrs = hub address for data buffer
    '        ramaddrs = ram address for data ($0000 to $FFFF)
    '        blocklen = ram buffer length for data transfer
    '        errx     = returns =0 (false=good), else <>0 (true & error code)
    '        cog      = cog no of driver (set by spin start routine)
       
    PUB start_ram : err_
    
    ' Initialise the Drac Ram driver. No actual changes to ram as the read/write routines handle this
      command := "I"
      cog := 1 + cognew(@tbp2_start, @command)
      if cog == 0
        err_ := $FF                 ' error = no cog
      else
        repeat while command        ' driver cog sets =0 when done
        err_ := errx                ' driver cog sets =0 if no error, else xx = error code
    
    PUB stop_ram
       if cog
          cogstop(cog~ - 1)      
    
    PUB DoCmd(command_, hub_address, ram_address, block_length) : err_
    ' Do the command: Y,Z
      hubaddrs := hub_address       ' hub address start
      ramaddrs := ram_address       ' ram address start
      blocklen := block_length      ' block length
      command  := command_          ' must be last !!
    ' Wait for command to complete and get status
      repeat while command          ' driver cog sets =0 when done
      err_ := errx                  ' driver cog sets =0 if no error, else xx = error code
    
    '***********************************************************************************************************
    DAT
    '                  First part tests ok
    '' +--------------------------------------------------------------------------+
    '' | Dracblade Ram Driver (with grateful acknowlegements to Cluso)            |
    '' +--------------------------------------------------------------------------+
                            org     0
    tbp2_start    ' setup the pointers to the hub command interface (saves execution time later
                                          '  +-- These instructions are overwritten as variables after start
    comptr                  mov     comptr, par     ' -|  hub pointer to command                
    hubptr                  mov     hubptr, par     '  |  hub pointer to hub address            
    ramptr                  add     hubptr, #4      '  |  hub pointer to ram address            
    lenptr                  mov     ramptr, par     '  |  hub pointer to length                 
    errptr                  add     ramptr, #8      '  |  hub pointer to error status           
    cmd                     mov     lenptr, par     '  |  command  I/R/W/G/P/Q                  
    hubaddr                 add     lenptr, #12     '  |  hub address                           
    ramaddr                 mov     errptr, par     '  |  ram address                           
    len                     add     errptr, #16     '  |  length                                
    err                     nop                     ' -+  error status returned (=0=false=good) 
    ' Initialise hardware tristates everything and read/write set the pins
    init                    mov     err, #0                  ' reset err=false=good
                            mov     dira,zero                ' tristate the pins
    done                    wrlong  err, errptr             ' status  =0=false=good, else error x
                            wrlong  zero, comptr            ' command =0 (done)
    ' wait for a command (pause short time to reduce power)
    pause
    '                        mov     ctr, delay      wz      ' if =0 no pause
    '              if_nz     add     ctr, cnt
    '              if_nz     waitcnt ctr, #0                 ' wait for a short time (reduces power)
                            rdlong  cmd, comptr     wz      ' command ?
                  if_z      jmp     #pause                  ' not yet
    'decode command    
    
    This passes 4 variables in, and one variable out.
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-04-08 10:01
    OMG ... I failed ... should do suicide with my avatar here on the forum ... I should not be allowed to waste every readers time ...

    I thought we already talked about this intermediate values and that these can only have 9 bits because they have to fit into the 9 bit source part of an instruction ... and all values higher than that have to be stored in a long when talking about a PASM-section.
    I also thought we already talked about the fact that you can't do calculation in PASM that easy.

    What the propeller tool currently tries to do is calculate those values for storage in destination and source part of an instruction. But it can NEVER calculate values stored in these variables, it only uses their address. This sometimes makes sense, but not in your case, as you really want to deal with the content of variables!

    Ok .. the point with math is, that the propeller tool has 2 different ways of how to treat / * + - and such. One way is when it's used in a CON - section. In a CON you define values to be represented by a constant name. So, whenever such a name is found in the code later on, it's replaced by exactly this value. If you use calculations in these definitions, the compiler is doing those calculations on compile-time.
    For example:
    CON
      MAX_RPM = 10_000
      NUM_OF_TEETH = 36
      MAX_TEETH_PER_SEC = MAX_RPM * 36 / 60
    

    If you use calculations in SPIN, the compiler really generates code out of it, which is executed on runtime.

    Also variable names from a DAT-block are locically treated different in SPIN than in PASM. If you use a DAT-variable in SPIN it's actually reading FROM/writing INTO the memory location. If a variable-name is used in PASM, the memory address (a COG-RAM address) is encoded into the destination/source-address-field of the PASM instruction.

    The point is now, that in PASM calculations have the same meaning as in the CON-blocks whereas the variable name is actually treated like an address in the PASM section. That's why:
    cmp ((current/10)*100), ((previous7Avg/10)*110) WZ, WC
    can be a valid code snippet which really can be compiled, but it's NEVER doing what you expect.
    ((current/10)*100) means: take the address of the variable current, divide it by 10 and multiply the result by 100, which will work if the address of current is between 0 and 51.
  • turbosupraturbosupra Posts: 1,088
    edited 2012-04-08 10:55
    Thanks for the repy Mag

    I'm not sure what you mean by that first line, but we did talk about those things and your explanations were the only reason I was able to get this far, even if "this far" is no where. I remember you saying math is quite difficult in PASM, and as reluctant as I was to try it, I'm not sure I have a choice given the speed that I apparently need.

    I'm still not clear how if everything is a long in cog ram ... then how some longs are defined as registers with different groups of bits blocked together inside of the 32 bit long, and some longs are a 32 bit block as a single grouping, but I'll keep reading. At this point, since I was able to generate the signals exactly with spin methods, I'm just looking for the ability to compile and start experimenting/fixing my code to see if I can get it to work.
  • turbosupraturbosupra Posts: 1,088
    edited 2012-04-08 10:56
    Thanks average joe, I will try debugging that way
  • Mike GreenMike Green Posts: 23,101
    edited 2012-04-08 11:02
    Everything is a long in cog ram, but you can write all the bits of any 32-bit value out on a piece of paper and draw boxes around parts of the 32 bits and call one box the source field and another box the instruction field and so on. It's still a 32-bit value written out one bit at a time as 1s and 0s in order. You can label the bits any way you want. The cog's CPU has "labels" for various pieces with the least significant 9 bits going to the part of the CPU that deals with the source address of an instruction. The same bits go as a single group of 32 to the part of the CPU that does arithmetic and each bit also goes to a different I/O pin logic circuit.
  • potatoheadpotatohead Posts: 10,261
    edited 2012-04-08 11:32
    Maybe it's helpful to understand there are no "registers" like we find in most other CPU's. Well, there is the program counter and the flags, I/O and such, but just ignore that for a moment.

    When the COG is started, it copies 2K of data (512 longs) from the HUB, into the COG memory space. HUB memory is byte, word, long addressable. COG memory space is only LONG addressable. Once the COG is full, program execution begins at COG address 0, the very first LONG in the COG. This happens no matter what. So if we put real program number values in there, the COG will do something useful. If there are just numbers in there, the COG will just do something. :)

    The only difference between program and data is the programmer intent. A LONG is a LONG. So, if a LONG is going to be a program instruction, it can only hold 9 bits of information, because the rest is needed for instruction purposes. If a LONG is going to be data, it can hold 32 bits of information, because it's not going to be used as an instruction. Instructions might reference other instructions, and that's generally what the 9 bits are for, that and for specifying small values that make sense in the context of a program. Number of times to shift, etc...

    When you put complex math into PASM statements as you have done, the compiler evaluates those and attempts to put the result into the memory. Those values go into HUB memory, and are static. The result will be pre-determined prior to your program actually running. Once you fire off a PASM COG, that HUB data gets copied to the COG, and your program starts!

    Typical practice is to organize your PASM with your program at the beginning, starting at COG address 0, and to pack in all the values it needs at the end. It all goes in a DAT block. I like to use two DAT statements per PASM program image. The first one is the program, and it's starts with the org 0,and contains the program. I'll type DAT again, to get the color change, then put all my values there. The 'distance' between the start of the program, and the last value needs to be less than the number of LONGS that get copied to the COG, or there will be some values that don't make it!

    If you have too many values to stuff into a COG, or need to work with bigger sets of memory, arrays, buffers, etc... then you leave them in the HUB, using the PASM HUB operations to operate on them from PASM. wrbyte, rdbyte, wrlong, rdlong, etc....

    The key to remember is in the COG, a long is a long. If you put an instruction in it, there isn't room for big values. If you don't put an instruction in it, there is room for big values. When you use the "#" to combine a value and an instruction, you've only got 9 bits of room for that value.

    From there, it's always memory to memory. Take the instruction 'add destination, #source' as opposed to 'add destination, source' The first form has the "#", meaning the number to add to the destination and the result to store in the destination is provided as part of the instruction itself! That's only 9 bits worth. The second form indicates that no value is supplied as part of the add instruction, meaning it comes from one of the other COG LONGS. That's 32 bits worth.

    PASM is not a load, store design. It's a memory to memory design, with instructions and data all existing in the same LONG addressable memory space.
  • average joeaverage joe Posts: 795
    edited 2012-04-08 11:39
    PASM is a bit difficult to grasp at first, mostly because of how easy and flexible spin is. HOWEVER, once you get the basics the rest gets A LOT easier. Just remember, no matter what... PHYSICALLY, EVERYTHING is a long. Programming is where we differentiate what is what. This is all done in how we use those PHYSICAL longs. ie, declaring a byte - variable in spin is just making 1/4 of that long the variable and the rest can be used for 3 more byte variables. This means you can do quite a bit, accessing chunks of the long somewhere, and the whole long in other places. I have a few examples of this if you need them.
    My best advice. Take what you have in spin and turn it into a flowchart. Then take that flow chart and start over in PASM one instruction at a time. This is what I did when re-writing my display drivers in PASM. They worked, but have been quite improved since I got a better grasp on the actual display hardware. Start with the basics. Start a cog at an address, and return pass or fail. Then pass some stuff in, return it and check the results.
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-04-08 11:42
    There are only 2 different types of longs in COG-RAM:
    1. The longs that are part of YOUR program path -> these are decoded by the COG like explained on some other thread, containing instruction, conditional flags, modifiers, destination and source address.

    These longs you usually define by using PASM instructions, but as Duane said, with the description given in the propeller manual, you could also set each bit of those longs manually to make it an instruction.

    The program path always starts at ORG 0 and some special instruction like JMP, CALL ... are used by your program to define the rest of the program path.

    2. The longs that are NOT part of your program path. For the COG themself these longs don't mean anything! YOUR code tells the COG how to use those longs. You can tell the COG to use a long as a 32 bit counter for example:
    DAT
    ORG 0
      myProg    add  myCntr, #1
                jmp  #myProg
      myCntr    long 0
    FIT 496
    
    But your code could also use the COG-RAM not being part of the program path to be byte-values:
    DAT
    ORG 0
    myProg    
    myTmpVar       mov DIRA, 8lsb          ' set DIRA, so that Pin0-Pin7 are output pins 
                                           ' if the instruction has been executed, it's no longer needed, as the program path never
                                           ' comes back, so it can be reused as myTmpVar
    myProgLp_1
                   mov myTmpVar, myStr     ' copy the 4 8bit-values into Tmp, as the prop is a pure 32bit controller
                            
                   shr  myTmpVar, myCntr   ' shift 24 bit to the right, so that "A" is placed in bits 0-8
                   and myTmpVar, 8lsb      ' make sure that no other bits are set
                   mov OUTA, myTmpVar      ' output "A" on Pin0 - Pin7
                   sub  myCntr, #8 WC      ' subtract 8 from the shift-counter, so there is 24-8=16, 16-8=8, 8-8=0 in myCntr
    if_NC          jmp  #myProgLp_1        ' in case the carry-flag is not set, do the next iteration  
    
                   ' if carry is set, we are done with all 4 bytes, so start over from the first one
                   mov myCntr, #24         ' reset shift-counter
                   jmp  #myProgLp_1
    
    8lsb           long $0000_00ff
    myCntr         long 24
    myStr          long "ABCD"
    FIT 496
    
    So, for all longs which have a different meaning than a 32bit long, YOU have to tell the COG what to do with these - how to extract the parts.

    Hope this makes things a bit clearer ;o)
  • Duane DegnDuane Degn Posts: 10,588
    edited 2012-04-08 11:58
    I missed a bunch of posts while I was writing my "masochistic" version of PASM.
    I'm not sure if this example will help much but I think it's cool so I'll post it.

    I wrote a program to output the PASM section as long values. The PASM section cycles through the eight LEDs on a QuickStart board and write the current LED number to a variable in hub RAM.

    Writing to hub RAM is one way of debuging PASM.

    Here's the code (non-masochistic version):
    {{
      Example PASM program that lights LEDs
      on a QuickStart board.
    }}
    CON
      _CLKMODE = XTAL1 + PLL16X
      _CLKFREQ = 80_000_000
      _DebugBaud = 57_600
      _DebugX = 2 
      _DebugY = 28
      
    VAR
      long debugValue
      
    OBJ
      Debug : "Parallax Serial Terminal"
      
    PUB Main | previousDebugValue, localPtr
      waitcnt(clkfreq * 3 + cnt)    ' time to open terminal window.
      previousDebugValue := -1      ' some imposible value 
      cognew(@entry, @debugValue)
      Debug.Start(_DebugBaud)
      Debug.Clear
      Debug.Str(string(13, "The PASM section of code in this program could"))
      Debug.Str(string(13, "be written using the following long values:", 13))
      localPtr := @entry
      Debug.Str(string(13, "entry                   long    ")) ' we still need a label to launch PASM
      Debug.dec(long[localPtr])
      localPtr += 4
      repeat while localPtr < @noLongerPasm
        Debug.Str(string(13, "                        long    ")) 
        Debug.dec(long[localPtr])
        localPtr += 4
      Debug.Str(string(13, 13, "Now lets watch what else the program does."))
      Debug.Str(string(13, "(It turns on and off LEDs and sends debug"))
      Debug.Str(string(13, "information to the hub.)"))
      
      repeat
        if debugValue <> previousDebugValue 
          Debug.Position(_DebugX, _DebugY)
          Debug.Str(string("LED On (1 - 8) = "))
          Debug.dec(debugValue) 
          previousDebugValue := debugValue
        
    DAT
                            org
    entry                   or      dira, ledMask   ' make LED pins outputs
                            mov     debugAddress, par       ' copy address stored in par
                            mov     waitTil, cnt
                            add     waitTil, waitCycles
    outerLoop               mov     currentLed, ledsToLight
    innerLoop               mov     ledOnMask, #1
                            shl     ledOnMask, firstLedPin
                            shl     ledOnMask, ledsToLight  ' shift to one past last LED
                            shr     ledOnMask, currentLed
                            or      outa, ledOnMask         ' turn on the LED
                            wrlong  currentLed, debugAddress  ' write the value of currentLed to hub RAM
                            waitcnt waitTil, waitCycles     ' waits and then adds waitCycle to waitTil                       
                            andn    outa, ledOnMask         ' turn off the LED
                            djnz    currentLed, #innerLoop
                            jmp     #outerLoop
                            
    firstLedPin             long 16
    ledMask                 long %0000_0000_1111_1111_0000_0000_0000_0000
    waitCycles              long 40_000_000
    ledsToLight             long 8
    ' The variables below don't take up room in hub RAM
    ledOnMask               res 1
    debugAddress            res 1                   ' reserve a place for address of debug variable
    currentLed              res 1                   ' reserve a place hold which LED is on
    waitTil                 res 1
                            fit
    noLongerPasm  byte 0
                               
    

    This is the output from the above code.
    The PASM section of code in this program could
    be written using the following long values:
    entry                   long    1757408272
                            long    -1598281232
                            long    -1598280207
                            long    -2135151599
                            long    -1598281198
                            long    -1594087935
                            long    750528015
                            long    750528018
                            long    683419157
                            long    1757407251
                            long    138160660
                            long    -121885679
                            long    1690298387
                            long    -453236219
                            long    1551630340
                            long    16
                            long    16711680
                            long    40000000
                            long    8
    Now lets watch what else the program does.
    (It turns on and off LEDs and sends debug
    information to the hub.)
      LED On (1 - 8) = 2
    

    The last character, "2" in this case, continues to cycle from 8 to 1, starting again at 8.

    This last number is being changed by the PASM code.

    I copied the output starting at "entry" down to "long 8" and pasted it between "org" and "fit" of the original program (overwriting the PASM code).

    Here's the modified program.
    {{
      Example PASM program that lights LEDs
      on a QuickStart board.
    }}
    CON
      _CLKMODE = XTAL1 + PLL16X
      _CLKFREQ = 80_000_000
      _DebugBaud = 57_600
      _DebugX = 2 
      _DebugY = 28
      
    VAR
      long debugValue
      
    OBJ
      Debug : "Parallax Serial Terminal"
      
    PUB Main | previousDebugValue, localPtr
      waitcnt(clkfreq * 3 + cnt)    ' time to open terminal window.
      previousDebugValue := -1      ' some imposible value 
      cognew(@entry, @debugValue)
      Debug.Start(_DebugBaud)
      Debug.Clear
      Debug.Str(string(13, "The PASM section of code in this program could"))
      Debug.Str(string(13, "be written using the following long values:", 13))
      localPtr := @entry
      Debug.Str(string(13, "entry                   long    ")) ' we still need a label to launch PASM
      Debug.dec(long[localPtr])
      localPtr += 4
      repeat while localPtr < @noLongerPasm
        Debug.Str(string(13, "                        long    ")) 
        Debug.dec(long[localPtr])
        localPtr += 4
      Debug.Str(string(13, 13, "Now lets watch what else the program does."))
      Debug.Str(string(13, "(It turns on and off LEDs and sends debug"))
      Debug.Str(string(13, "information to the hub.)"))
      
      repeat
        if debugValue <> previousDebugValue 
          Debug.Position(_DebugX, _DebugY)
          Debug.Str(string("LED On (1 - 8) = "))
          Debug.dec(debugValue) 
          previousDebugValue := debugValue
        
    DAT
                            org
    entry                   long    1757408272
                            long    -1598281232
                            long    -1598280207
                            long    -2135151599
                            long    -1598281198
                            long    -1594087935
                            long    750528015
                            long    750528018
                            long    683419157
                            long    1757407251
                            long    138160660
                            long    -121885679
                            long    1690298387
                            long    -453236219
                            long    1551630340
                            long    16
                            long    16711680
                            long    40000000
                            long    8
                            fit
    noLongerPasm  byte 0
                               
    

    Notice the difference in the PASM section of this program and the top program I posted. While they look very different, they run exactly the same!

    Pretty cool!?

    My first attempt at posting this was eaten by the forum software so I'm posting this without previewing it. I'm currently editing it and attaching the two programs as Spin files so refresh the screen every so often until this last sentence disappears.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2012-04-08 12:05
    I wasn't able to edit the above post. The forum software attempted to eat it again.

    Besides attaching these two files, I was going to bold the PASM section of code from the two programs.

    I hope all this code isn't too annoying but I'm posting just the PASM sections again and I want to emphasize these to blocks of code are identical as far as the Propeller is concerned.
    entry                   or      dira, ledMask   ' make LED pins outputs
                            mov     debugAddress, par       ' copy address stored in par
                            mov     waitTil, cnt
                            add     waitTil, waitCycles
    outerLoop               mov     currentLed, ledsToLight
    innerLoop               mov     ledOnMask, #1
                            shl     ledOnMask, firstLedPin
                            shl     ledOnMask, ledsToLight  ' shift to one past last LED
                            shr     ledOnMask, currentLed
                            or      outa, ledOnMask         ' turn on the LED
                            wrlong  currentLed, debugAddress  ' write the value of currentLed to hub RAM
                            waitcnt waitTil, waitCycles     ' waits and then adds waitCycle to waitTil                       
                            andn    outa, ledOnMask         ' turn off the LED
                            djnz    currentLed, #innerLoop
                            jmp     #outerLoop
                            
    firstLedPin             long 16
    ledMask                 long %0000_0000_1111_1111_0000_0000_0000_0000
    waitCycles              long 40_000_000
    ledsToLight             long 8
    ' The variables below don't take up room in hub RAM
    ledOnMask               res 1
    debugAddress            res 1                   ' reserve a place for address of debug variable
    currentLed              res 1                   ' reserve a place hold which LED is on
    waitTil                 res 1
    

    Now the same code as above written as longs.
    entry                   long    1757408272
                            long    -1598281232
                            long    -1598280207
                            long    -2135151599
                            long    -1598281198
                            long    -1594087935
                            long    750528015
                            long    750528018
                            long    683419157
                            long    1757407251
                            long    138160660
                            long    -121885679
                            long    1690298387
                            long    -453236219
                            long    1551630340
                            long    16
                            long    16711680
                            long    40000000
                            long    8
    
  • average joeaverage joe Posts: 795
    edited 2012-04-08 12:21
    Wow, you really are a masochist! Very nice work though!
  • turbosupraturbosupra Posts: 1,088
    edited 2012-04-08 18:10
    I appreciate the code Duane, I'll be trying to learn from it tomorrow. Thank you
  • turbosupraturbosupra Posts: 1,088
    edited 2012-04-08 18:28
    Ok, this is getting at the heart of my question I think!

    With PASM's add function as used in your short counter program, you have the following "format" for myCntr

    –INSTR– ZCRI –CON– –DEST– –SRC–
    100000 001i 1111 ddddddddd sssssssss

    So how would it store something larger than the destination address (512) if add formats the long in the previously mentioned manner? (–INSTR– ZCRI –CON– –DEST– –SRC–)

    If add is preprogrammed to "format" the long register that way, how does that change? Does the literal (#1) tell the add method to ignore it's normal register format and just increment the literal number + the originating value? How can you tell it to just format the long without any instructions, flags, destination or source?


    MagIO2 wrote: »
    There are only 2 different types of longs in COG-RAM:
    1. The longs that are part of YOUR program path -> these are decoded by the COG like explained on some other thread, containing instruction, conditional flags, modifiers, destination and source address.

    These longs you usually define by using PASM instructions, but as Duane said, with the description given in the propeller manual, you could also set each bit of those longs manually to make it an instruction.

    The program path always starts at ORG 0 and some special instruction like JMP, CALL ... are used by your program to define the rest of the program path.

    2. The longs that are NOT part of your program path. For the COG themself these longs don't mean anything! YOUR code tells the COG how to use those longs. You can tell the COG to use a long as a 32 bit counter for example:
    DAT
    ORG 0
      myProg    add  myCntr, #1
                jmp  #myProg
      myCntr    long 0
    FIT 496
    
    But your code could also use the COG-RAM not being part of the program path to be byte-values:

    So, for all longs which have a different meaning than a 32bit long, YOU have to tell the COG what to do with these - how to extract the parts.

    Hope this makes things a bit clearer ;o)
  • potatoheadpotatohead Posts: 10,261
    edited 2012-04-08 19:06
    The answer to this comes down to the "I" in ZCRI. I = Immediate = "#" in the address. With that bit set, the source is a literal, contained in the 9 bits of the instruction allocated for source VALUES. With the bit reset, or 0, those 9 bits refer to source ADDRESSES.

    Either way, it's just a number. With the # present, the number is used for the value in the addition directly. Without the # present, the number contained in the instruction is actually the address of the value needed for the addition.

    Say there are two add instructions. One at COG address 2, and one at COG address 3. They are:

    add total, #45
    add total, 45

    The label "total" is associated with COG address 10, and that COG address contains the value 5. COG address 45 contains the value 10.

    The first add instruction takes the number 45 contained in it's source bits, adds it to the value 5 contained in the COG address 10, associated with the label "total", and writes the sum of that, value 50, into COG address 10, again referenced by label "total" This could be: "add 10, 45" too, it's just that we don't see that because we use labels.

    The second add instruction takes the number 45, contained in it's source bits and instead of using it as a value directly, uses it as a COG memory ADDRESS, fetching the value 10, adds it to the value 5 contained in COG address 10, referenced by label "total", writing the sum of that, 15, into COG address 10.

    Destination is always an address. Source can be either an address or a value, depending on whether or not # is present in the instruction.

    Now, it might be more clear what happens when something bigger than 9 bits gets specified with the # present! There literally is no room for the data to go. All the other bits are spoken for. The correct form for such values is to put a label on them, so the source ADDRESS of that value can be properly specified.

    Say everything above is the same, but we want to add $1055. This is too big. So, we put the label "too_big" on a free COG memory address thus:

    too_big long 1055

    Say that lives at COG memory address 50. Now the add instruction would look like:

    add total, too_big

    When the prop tool compiles it, the instruction ends up being "add 10, 50", where the 10 is the address referenced by the label "total", and the 50 is the address referenced by the label "too_big". It then sees that COG address 50 contains the value 1055, and it sees that COG memory address 10 contains the value 5, does the add and writes the value 1060 into COG address 10, which is referenced by label "total"

    Now, interestingly, one could do the add like this:

    add total, #too_big

    And it will work too! What happens? The prop tool compiles that to be: add 10, #50, which is an entirely different thing! Now, instead of adding the value at COG address 50, it will just add the ADDRESS, as if it were a VALUE, resulting in the VALUE 55 being written to COG address 10.

    Look at the two pictures of the Prop Tool, I've attached. Edit: I've added one more to cover the more common case of just including a value as part of an instruction. (add-03a.jpg)
    700 x 309 - 37K
    727 x 324 - 32K
    736 x 333 - 38K
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-04-09 03:34
    @Turbo:
    No, myCntr does not have this format! Only PASM-instructions have this format -> all longs that are on the program path. For all other longs you have free control of the meaning of the 32bits. Easiest case is, that you use them as longs. That's what the counter program is doing! It simply counts up the whole range of a long from 0 to 2^32-1 starting over from 0 again.
    But as COG-RAM is very limited, you'll sometimes add code to pack smaller pieces toghether in one long. That's what the second example shows. Using one long for data that fits into a byte is a waste you sometimes can't efford.
Sign In or Register to comment.