PDA

View Full Version : Problem with PASM: "Source register/constant cannot exceed \$1FF"

turbosupra
04-08-2012, 01:58 PM
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.

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

average joe
04-08-2012, 02:15 PM
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_Acula
04-08-2012, 02:21 PM
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 joe
04-08-2012, 02:23 PM
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.

*edit*
Beat me to the punch DOC, I hate when my fingers are faster than my brain.

Duane Degn
04-08-2012, 02:49 PM
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.

turbosupra
04-08-2012, 02:55 PM
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 joe
04-08-2012, 02:59 PM
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 Degn
04-08-2012, 03:45 PM
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 joe
04-08-2012, 03:52 PM
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.

turbosupra
04-08-2012, 04:06 PM
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

turbosupra
04-08-2012, 04:17 PM
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.

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_Jim
04-08-2012, 04:31 PM
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

turbosupra
04-08-2012, 05:15 PM
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?

turbosupra
04-08-2012, 05:20 PM
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.

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 Green
04-08-2012, 05:25 PM
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 Degn
04-08-2012, 05:28 PM
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 joe
04-08-2012, 05:53 PM
@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

' I - initialise

' 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
' 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)

' Do the command: Y,Z
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
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
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 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.

MagIO2
04-08-2012, 06:01 PM
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.

turbosupra
04-08-2012, 06:55 PM
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.

turbosupra
04-08-2012, 06:56 PM
Thanks average joe, I will try debugging that way

Mike Green
04-08-2012, 07:02 PM
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.

04-08-2012, 07:32 PM
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 joe
04-08-2012, 07:39 PM
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.

MagIO2
04-08-2012, 07:42 PM
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
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 Degn
04-08-2012, 07:58 PM
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 waitTil, cnt
outerLoop mov currentLed, ledsToLight
shl ledOnMask, ledsToLight ' shift to one past last LED
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
waitCycles long 40_000_000
ledsToLight long 8
' The variables below don't take up room in hub RAM
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 Degn
04-08-2012, 08:05 PM
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 waitTil, cnt
outerLoop mov currentLed, ledsToLight
shl ledOnMask, ledsToLight ' shift to one past last LED
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
waitCycles long 40_000_000
ledsToLight long 8
' The variables below don't take up room in hub RAM
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 joe
04-08-2012, 08:21 PM
Wow, you really are a masochist! Very nice work though!

turbosupra
04-09-2012, 02:10 AM
I appreciate the code Duane, I'll be trying to learn from it tomorrow. Thank you

turbosupra
04-09-2012, 02:28 AM
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?

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
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)

04-09-2012, 03:06 AM
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:

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:

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:

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)

MagIO2
04-09-2012, 11:34 AM
@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.

turbosupra
04-09-2012, 02:10 PM
Ok,

Good morning, thank you both, I think I understand now.

The instruction code formats its longs with –INSTR– ZCRI –CON– –DEST– –SRC– . And with math, up to 511 bits it can do within the actual constraints of its format. Above a value of 511 it requires a pointer to continue these math operations, because the pointer name can fit inside of the constraints of the instruction, while the pointer value (above 511) cannot. That leads me to believe that there is some sort of sandbox, outside of the instruction, where math is actually done that always has a full 32 bits available for computation?

So let's say I'm trying to still do the following

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

current is 26400 and previous7Avg is 13200

I'd have to do something like the following (I know there are no mul/div functions, I used those for illustration)

div current, ten
mul current, hundred

div previous7Avg, ten
mul previous7Avg, hundredten

cmp current, previous7Avg WZ, WC

long ten 10
long hundred 100
long hundredten 110

This way, I'm using addresses and staying away from literals and the constraint of 511, the compiler won't complain and my math should actually work?

turbosupra
04-09-2012, 02:28 PM
Hi Duane,

Is the difference between the 2 programs just static vs dynamic values?

One calculates the numeric values and the other has them hard programmed in?

The one with just numbers, I can't make sense of how it works :)

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.

04-09-2012, 03:16 PM
You are getting really close now!

The instructions are just numbers. In fact, there are just COG memory locations, and they just contain numbers. Some numbers happen to be valid instructions! That is what the compiler / assembler does for us. We find it hard to just remember which numbers to use for instructions. So we use names of instructions and labeles for memory addresses and such, leaving the task of sorting out the numbers to a computer, which is very good at it.

Most numbers are just numbers however. Duane is just trying to show how "a number" isn't anything special. There are just COG memory locations, and there is a program counter too. When the program counter is pointed at one of those COG memory locations, it's going to take the number there, or the bits, and treat it like an instruction. That is all that happens. One of the things we need to watch for is losing control of the COG. If we know where the program counter will go, the numbers that are instructions that mean something to us will happen. If we make a mistake, and it gets pointed somewhere we don't know is valid instructions, then it just does stuff... generally not useful.

Yes, you put your bigger values into COG memory locations, so that instructions can point to them and perform operations. That's exactly the workflow you need. Often, it's worth thinking about all the values a person needs, and factor those down to a reasonable set, because the more values one has, the shorter the PASM program can be, unless it somehow overwrites those values with ones that would be meaningful instructions. (nothing is off the table in PASM)

Duane Degn
04-09-2012, 04:00 PM
Hi Duane,

Is the difference between the 2 programs just static vs dynamic values?

One calculates the numeric values and the other has them hard programmed in?

The one with just numbers, I can't make sense of how it works :)

While the two programs look different to human eyes, they look identical to the Propeller. They both work exactly the same when loaded into a Prop.

If I were a true masochist, I'd try to figure out what these numbers were without the Prop telling me (one feature of the program).

As potatohead explained, instead of typing in a program as a list of numbers, the Prop Tool lets us use nicknames that are easier for humans to read.

You may notice when two PASM lines are very similar:

shl ledOnMask, ledsToLight ' shift to one past last LED

The numeric equivilent of those instructions are also similar.

long 750528015
long 750528018

We know the last nine bits is the source, so it looks like the source "firstLedPin" (the human friendly name) is the sixteenth long in the cog (we start counting with zero so 15 is the 16th long).

The next line's source is long 18 (19th of cog) with is where "ledsToLight" is stored.

These four lines:

long 16
long 16711680
long 40000000
long 8

Are the same to the Prop as these four lines:

firstLedPin long 16
waitCycles long 40_000_000
ledsToLight long 8
I'm sure glad the Prop Tool does this all automatically and we don't have to count memory locations and fill all the bits of a number ourselves.

turbosupra
04-09-2012, 05:15 PM
Ok, so the numeric values in Duane's code (I would have actually expected a 32bit binary string for this instead) represent what the labels/registers are doing?

That makes sense theoretically, I'm just still not sure how the magic happens :)

04-09-2012, 06:16 PM
Yes.

It's all just numbers. Inside the Prop, there is an ALU (arithmatic logic unit) which does the math and logic operations. There are 32 bits in that thing, plus the carry for larger operations. As the programmer, the only worry is argument size, type and location.

And he could have done a 32 bit binary string. In fact, you can, and should. That would be a great exercise. Convert the numbers he's got there into binary and insure the program and by definition, the values all match.

In old, ancient times, when I was first learning assembly language on Apple computers, we had this exact same discussion. The first programs were done just by entering bytes into the RAM. Hex byte values: \$A0, \$CC, \$1E, \$45, \$23, etc.... Once they got put in, just point the CPU at them, with a "GO" command and see what happens!

This isn't much different, only we've got the nice PC tools today.

IMHO, there really aren't registers like we see on other CPUs. Props have internal counters, the ALU, program counter, flags and such. Those are registers, along with the special purpose ones, CNT, INA, OUTA, etc... Typically, "register" refers to a storage entity or control entity within the CPU that isn't addressed like RAM memory storage is. Maybe there is the A register, or R1 - R8, etc... On the Prop, one could think of the JMP instruction more like mov, where the destination is the PC register, instead of a COG memory address. That's really a register in the sense I think you are looking for.

From there, every address in the COG just contains numbers. It is really a memory to memory design in every other way. One nice thing about this is self-modifying code is easy, one arguably less nice thing is self-modifying code is necessary too.

I hate to invoke something new here, but you can also put values in the HUB. The workflow would be rdbyte, rdword, rdlong into a COG "register", do the math, then make a decision, then put the result back into the HUB, with wrbyte, wrword, wrlong. In this way, the entire COG is registers, with you able to define as many as you need with simple labels and such. If you have a lot of values, or multiple COGS will operate on them, the HUB is the way to go. I'll stop there though, let's get this sorted first. :)

MagIO2
04-09-2012, 06:26 PM
Why do you want to divide by ten and then multiply by 100? This only makes sense if you want to get rid of the last 2 digits in the end-result. Otherwise it would be wiser to simply do a multiply by 10!

26400 / 10 * 100 = 2640 * 100 = 264000
26400 * 10 = 264000

the difference is here - let current be 26423:

26423 / 10 * 100 = 2642 * 100 = 264200
26423 * 10 = 264230

Same question for the other calculation! Doesn't your original calculation add inaccuracy?

If you only want to multiply by 10 you can hardcode a multiply by 10 instead of having a general loop the multiplies any factors.
MOV result, current
SHL result, #2 ' this is equal to *4
ADD result, current ' this makes it *5
SHL result, #1 ' here we have * 10

another add result, current would make it *11 which is what you need in the other formula.

tonyp12
04-09-2012, 06:43 PM
COG ram is simply 512 longs of 32bit "bytes/blocks", The Prop does not know the difference between Code and Data.
You just have to make sure the Program Counter does not walk in to Data territory by having a jump instruction at the end of code block.

You can point to any cog ram location by just using a 9bit address as cog ram is rather small, where you store a 32bit value to use.

turbosupra
04-09-2012, 06:50 PM
Ok, my brain is having a dimly lit LED above it, start to illuminate with that last paragraph. The alu sounds like what I was calling a sandbox, so that makes a lot of sense.

I tried to convert Duane's numbers to binary, but something is missing. It still prints out the same text, but the functionality is wrong because some of the numbers aren't negative. I thought you made the MSB a 1 to represent a negative, but that didn't work? The first block of code is the MSB =1 code, and the second block is with no 32nd bit on the negative numbers.

DAT
org

entry long %1101000101111111110110000010000
long %11011111010000111101011000010000
long %11011111010000111101001000001111
long %11111111010000111101001111101111
long %1011111010000111101010111101110
long %11011111000000111101100111111111
long %101100101111000010011000001111
long %101100101111000010011000010010
long %101000101111000010011000010101
long %1101000101111111110100000010011
long %1000001111000010101000010100
long %10000111010000111101001111101111
long %1100100101111111110100000010011
long %10011011000000111101010111111011
long %1011100011111000000000000000100
long %10000
long %111111110000000000000000
long %10011000100101101000000000
long %1000

noLongerPasm byte 0

DAT
org

entry long %1101000101111111110110000010000
long %1011111010000111101011000010000
long %1011111010000111101001000001111
long %1111111010000111101001111101111
long %1011111010000111101010111101110
long %1011111000000111101100111111111
long %101100101111000010011000001111
long %101100101111000010011000010010
long %101000101111000010011000010101
long %1101000101111111110100000010011
long %1000001111000010101000010100
long %0000111010000111101001111101111
long %1100100101111111110100000010011
long %0011011000000111101010111111011
long %1011100011111000000000000000100
long %10000
long %111111110000000000000000
long %10011000100101101000000000
long %1000

noLongerPasm byte 0

turbosupra
04-09-2012, 07:02 PM
The reason I did that in spin is for resolution compensation, IIRC the *110 was causing 32bit roll over with some numbers, at one point I was also using 105/95 as well. I believe it does add a little inaccuracy, that was at the expense of not having to use floats. You may do a facepalm with my most recent topic, but it was a huge stretch for my abilities and I don't even know if the logic flow is any good, I'm learning!

That multiply by 10 is a pretty sweet trick! :-D

Since I am using 90 and 110 now, I think you are right, I could get away with 9/10/11 instead of 90/100/110!

Thanks Mag, I like that binary math trick you did. I guess any multiply is never more then an add of the original value, plus a few SHL commads?

Could you do:
MOV result, current
MOV result2, current
SHL result, #3 ' this is equal to *8
SHL result2, #1 ' this is equal to *2
result is now equal to current * 10?

Why do you want to divide by ten and then multiply by 100? This only makes sense if you want to get rid of the last 2 digits in the end-result. Otherwise it would be wiser to simply do a multiply by 10!

26400 / 10 * 100 = 2640 * 100 = 264000
26400 * 10 = 264000

the difference is here - let current be 26423:

26423 / 10 * 100 = 2642 * 100 = 264200
26423 * 10 = 264230

Same question for the other calculation! Doesn't your original calculation add inaccuracy?

If you only want to multiply by 10 you can hardcode a multiply by 10 instead of having a general loop the multiplies any factors.
MOV result, current
SHL result, #2 ' this is equal to *4
ADD result, current ' this makes it *5
SHL result, #1 ' here we have * 10

another add result, current would make it *11 which is what you need in the other formula.

Duane Degn
04-09-2012, 07:11 PM
Okay, now the forum software (or my computer) wont let me use the quote feature.

Your comment about making the MSB "1" to make a binary negative isn't correct. There's more to it than that but I don't really know what the "more" is.

I think it would be easier to let the Spin program to the work of printing binary instead of integers.

Just change:

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

To:

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.bin(long[localPtr], 32)
localPtr += 4
repeat while localPtr < @noLongerPasm
Debug.Str(string(13, " long %"))
Debug.bin(long[localPtr], 32)
localPtr += 4

I haven't tested the modification, but I don't see why it wouldn't work.

Duane Degn
04-09-2012, 07:19 PM
Brad, If you still want to convert the integers to binary yourself and you're using the Windows calculator in "programmers" mode, click the button next to "Dword" to limit it to 32 bits.

When I convert "-1598281232" to binary this way I get "10100000101111000010100111110000". I think this is different than the two binary values you listed (though I'm not absolutely sure it's the same as what the Prop would give).

MagIO2
04-09-2012, 07:24 PM
SHL result2, #2 ' this is equal to *2

this is *4 and NOT *2. SHL is a binary multiplication so to say:
13 = %00001101
shift it once makes it
%00011010 = 26
shift it twice makes it
%00110100 = 52

But yes, a multiplication is simply to know about when to add and when to shift: multiply x by 10 means
*2 *2 +x *2 = *4 +x *2 = *5 *2 = *10
multiply x by 9 is
*2 *2 *2 +x = *8 +x = *9

It is possible to write a loop which allows to multiply any factors. But if you only have a little amount of constants to multiply with, it might make sense to do the multiplication with a dedicated sequence of shift and add-instructions.

MagIO2
04-09-2012, 07:34 PM
Making a binary number a negative number is called a two's complement. So, you invert it (binary) and then add 1

1 = %0001 -> make it negative: - %0001 = %1110 + 1 = %1111

This is because this way the math of binary numbers negative and positive is still working:
-1 + 1 = %1111 + %0001 = %0000 ( on a 4 bit CPU of course ;o)

actually it would be %10000, but on a 4 bit CPU this MSB would be cut off.
-1 + 2 = %1111 + %0010 = %0001

turbosupra
04-09-2012, 07:43 PM
@Mag, ok now I see what went wrong. The converter I used must not have done binary values correctly and when I tried to correct for that I did it incorrectly by not inverting, etc. That bitshift keeps getting me with the syntax, ugh, I will beat it into my brain though so I use the bit place holder number instead of the bit numeric representation.

That worked Duane, thanks.

I then copied it to the DAT section and it still worked! :)

DAT
org

entry long %01101000101111111110110000010000
long %10100000101111000010100111110000
long %10100000101111000010110111110001
long %10000000101111000010110000010001
long %10100000101111000010101000010010
long %10100000111111000010011000000001
long %00101100101111000010011000001111
long %00101100101111000010011000010010
long %00101000101111000010011000010101
long %01101000101111111110100000010011
long %00001000001111000010101000010100
long %11111000101111000010110000010001
long %01100100101111111110100000010011
long %11100100111111000010101000000101
long %01011100011111000000000000000100
long %00000000000000000000000000010000
long %00000000111111110000000000000000
long %00000010011000100101101000000000
long %00000000000000000000000000001000

noLongerPasm byte 0