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?
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?
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.
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)
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, firstLedPin
shl ledOnMask, ledsToLight ' shift to one past last LED
The numeric equivilent of those instructions are also similar.
long 7505280[B]15
[/B] long 7505280[B]18[/B]
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:
[B] long 16
[/B] long 16711680
long 40000000
[B] long 8[/B]
Are the same to the Prop as these four lines:
[B]firstLedPin long 16
[/B]ledMask long %0000_0000_1111_1111_0000_0000_0000_0000
waitCycles long 40_000_000
[B]ledsToLight long 8[/B]
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.
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
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.
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!
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.
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.
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
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
ADD result, result2
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!
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.
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.
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).
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.
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
@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
Comments
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 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)
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?
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
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)
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:
The numeric equivilent of those instructions are also similar.
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:
Are the same to the Prop as these four lines:
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.
That makes sense theoretically, I'm just still not sure how the magic happens
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.
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.
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.
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.
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
ADD result, result2
result is now equal to current * 10?
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:
To:
I haven't tested the modification, but I don't see why it wouldn't work.
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).
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.
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
That worked Duane, thanks.
I then copied it to the DAT section and it still worked!