Cluso99 said...
Brad: The compiler needs a new option "HubObj $8000" to move the hub pointer up to the next 32KB. It also needs to compile code up to $FFFF (64KB). It would look as though the Prop now has 64KB of ram to the compiler. The programmer would be responsible for ensuring that only cog pasm code was placed above the 32KB limit. I think this may only be a simple mod to the compiler.
I'm not entirely sure this is going to be as easy as you think its going to be.
I'm assuming that you are talking about moving PASM code (and ONLY PASM code) that is exclusively loaded into cogs up above $7FFF. Remember that the DAT section in each object starts at the beginning of the object, so any data or variables you have in the DAT section *must* be below $7FFF.
If you use a "HubObj $8000", what precisely do you want to occur?
Compiling hub code is all well and good, and your code will start at $8000, but how do you launch that cog from Spin then ?
cognew ($8000, @par) is going to try and do funky things, unless you plan on subverting coginit/cognew in the interpreter. Of course then you can't simply use cognew/coginit from PASM either.
Alternatives do exist. Carl wrote a serial object that loads the cog and then uses the code space for buffers. I have written a TV object that loads a cog then uses the code space for line buffers.. you just need to be a little clever. I get the idea you want to store your cog binary code above $7FFF and then swap it in 512 longs at a time, boot a cog from it and then move on to the next one. How do you actually plan on using it ?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cardinal Fang! Fetch the comfy chair.
Brad: Yes you raise something I missed - the headers around the dat object. Will have to think that one out a bit more.
Basically what you said (offline) about the load sequence makes sense - I think we are both on the same page, just expressing it slightly different·with a slight variation.
I thought a fixed program (issued in binary form file) which would be loaded into ram and executed to perform the replaced booter code in the prop. Then the first 32KB would be loaded to hub and stored in eeprom, then the final 32KB would be loaded into the upper 32KB eeprom, then the prop would be reset and the prop would boot the new eeprom code which includes a facility to load upper 32KB sections into cog. If the feature was not used, then the code would be loaded as normal.
Heater: I couldn't see any reason not to make it exactly $8000. Do you have a reason? The current booter actually fills the full 32KB with zeros, so it is probably easier for the compiler to zero fill to $8000 if it finds this line. But I guess it doesn't have to be $8000 either but somehow we are going to need to overcome the issue of the object headers (which I forgot).
There is no need for the cog code to be exactly 2KB, it can still be overlapped. I was not thinking of a modified ram interpreter, but rather a call to a stub loader passing par and the actual cog eeprom address. You see, it is necessary to get the cog's par to be set correctly, and it cannot be referring to code >32KB.
I'm imagining this, add a DATH section type (instead of DAT) for PASM code to be in the high memory or call it whatever.
Then in there use ORG xxx to specify where in high memory it goes
DAT ' Normal DAT section
org 0 'Normal org
bla
bla
DATH 'DAT section to be loaded high
org $8xxx 'Where to put it in high
mov bla,bla
fit
.
.
Now whenever the compiler sees this it places the code in the correct user specified high location. Thus making otherwise used HUB space free for more SPIN code/data.
User code then is responsible for loading that PASM from the high end of the EEPROM and getting a COG started.
With a bit of simple post extraction users could take those PASM parts out of the binary for use in SD card or whatever as well.
This could also be used for blobs of constant data, like tables and strings, it is a kind of DAT section after all.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
I am just wondering what the object headers are being used for in the pasm code, other than to step through the spin and var objects.
I seem to recall the compiler places local variables at the end of the code. Brad, is this correct? This will still need to be done below the 32KB limit, so it complicates the compiler.
However, I cannot see any use for the headers by the DAT COG objects. Certainly, the Interpreter does not use these for loading cogs with pasm code, only for spin code. For pasm, the par register is loaded with a constant that can be anything and the pasm code knows what to do with it (i.e. if it is an address for parameters, etc then it must be in lower 32KB, otherwise it is a constant which does not matter)
Steve, I was trying to simplify the compiler extensions and make the user responsible for where (s)he places the dat code rather than the compiler having to determine this.
This way, the user can choose which cog pasm code is loaded in lower and which is loaded in upper.
However, Brad raised the point about the object headers and these cannot go past the 32K boundary. These are the headers that the spin interpreter uses to locate global and local variables, pub and pri calls, etc. It steps through these headers. I do not have a much of an understanding about these. Best place to look at them is a homespun listing.
It's not a show stopper, just something to be overcome as simply as possible.
My Prop Proto Boards (non USB) have 64KB eeproms. I believe so has the USB version. Don't know about the others.
Heater said... User code then is responsible for loading that PASM from the high end of the EEPROM and getting a COG started.
This needs to be done for the user automatically, and yes it could be expanded later to get it from anywhere. It need to be kept simple for the user.
Some compiler changes so that special DAT sections, rather than being compiled into the 32K spin binary, get compiled into a separate 32K binary file with some kind of directory containing an identifier (like a long .. possibly with a 4 letter identifier), a starting location in the 32K image, and a length. The compiler would start with an object (in a file set by a compiler option) containing a short program that just copies these DAT sections to the 2nd half of a 64K EEPROM without the short program (just the directory and the DAT sections). The program would use a simple I2C driver in Spin, something like Basic_I2C_Driver. Typically the user would download this 2nd program to RAM and execute it thus preparing the EEPROM.
The other compiler change would involve another object specified by an option along with some "syntactic sugar" to make the call look as much like a COGNEW or COGINIT as possible. It would use a temporary 2K buffer at the high end of RAM to load the assembly program from the EEPROM given the identifier, again using a short Spin routine to do the reading. A COGINIT or COGNEW could be done and the buffer then reused for something else. It would be possible to write a cog loader that doesn't need a buffer by starting a short program in the cog that only waits for a flag to be set, then transfers a long from hub memory to cog memory, increments the cog address, and clears the flag. This small routine could probably run from "shadow memory" in the cog. Its last transfer would be of a JMP to location zero in the cog to start the newly loaded program. All of the EEPROM I/O would be done by Spin code in the "main" cog.
1. We need to indicate to the compiler that the following PASM or data that would normally be in DAT should actually be placed outside the normal 32K HUB/EEPROM space and into some higher EEPROM space. Hence the idea of DATH (DAT High) whatever name you wan to give it.
As I said this could be used for data tables and strings as well as actual PASM code,
2. We need to know where in the high EEPROM space the compiler has put this so that it can be loaded eventually. Isuggested using "org" which may be an incorrect use. What about DATH $8XXX ? Who says sections can't have addesses? With this one could still use "org" within DAT with it's original meaning
So far this seems like a relatively simple thing for the compiler to do. It would require some user code to fetch those PASM/data blobs from high EEPROM and use them as data or something to load a COG with.
I'm not sure I would go down the road of fixing the compiler to load "high PASM blobs" to cogs automatically. Who says they are in EEPROM? maybe I've put extracted them to SD card. How can the compiler, in general, possibly know what is actually to be loaded and run anyway?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Honestly I think this is being made more complicated than it needs to be, to implement this the way some people are envisioning requires a special compiler, a special program loader and and a special indexing method to be able to access addresses beyond 32K. If you guys are intent on following this direction, I would recommend swaping the two banks. For the most part assembly programs are a permanently run in cog. It would be far easier to have them loaded into the Propeller first (lower 32K) get them loaded into thier cogs with forced pointers (the pointer into upper 32K they will eventually point to), then load the entire upper 32K into the propeller which has the spin code and data and bootstrap into the code once it's loaded. This is much simpler than trying to piecemeal portions of the upper 32K into the Propeller.
But I don't think that this is really necessary to begin with. I helped a customer develop a method for reclaiming assembly areas for data by simply placing a function in the object that contains the assembly code to be reused to return the address of the assembly routine. Then the calling function parcels out the space by generating new addresses by using base + offset. This is basically treating the memory area as a heap. While he wanted to keep things as simple as possible, it wouldn't be much trouble to create a heap manager that takes a size argument and returns an base address of allocated memory, then it would move it's internal base pointer for the next call, when it runs out of space from one heap it moves to the next heap. The only time this wouldn't be much help is when you need a very large block of consecutive memory, such as a video buffer.
The org must still function as normally. We just need a new command/option that tells the compiler to place the following code above 32KB boundary.
Loading to eeprom is not an issue. There are many simple ways. We have to overcome the object format problems first.
@Mike: Yes along the lines you suggest. But we need to keep it simple with as few changes to the compiler(s) as possible so as to not introduce any bugs, and keep it simple for the user. One binary/hex makes more sense.
@Paul: Need to keep it simple and that will require a few compiler mods. It could be done as 2 seperate compiles, but then the source becomes extremely complicated for the user. Your method does not allow for loading cogs later. It may be necessary to determine which routines are to be loaded depending on what is happening. It also allows for on-the-fly cog loading. This way, the maximum memory will be available in hub for whatever the user decides. I realise that Parallax would like to keep control of the compiler, but the Jeanie is out of the box and it will only help Parallax anyways. After all, only advanced users will use the extensions.
I see the following:
There needs to be a small table and stub pasm code resident in the hub. When a cognew/init is issued, the stub would be called with the table pointed to so that the correct high eeprom section can be loaded to the cog. I see most of the code loaded directly to the cog, with only the last bit loaded into hub to be copied over the stub (which does the eeprom I2C), and a goto $000 cog at the end of the 496 longs being placed in cog.
It is only necessary to load the length required (reduces loading time).
Here is my take on the table and stub (which would be provided as an include file)
..... user code here..... (note @ maybe incorrect for labels - not debugged of course)
'-----------------------------------------------------------------------------------------------------
'===== HIGH LOAD TABLE AND ROUTINES RESIDENT IN HUB (and lower 32KB eeprom) =====
DAT
org $000
'===== DAT TABLE OF HIGH MEMORY PASM ROUTINES =====
' format: size << 23 | start-location
e_pasm01 long (@pasm01_end - @pasm01) << 23 | (@pasm01) '\\ set by user
e_pasm02 long (@pasm02_end - @pasm02) << 23 | (@pasm02) '||
...more table entries for more routines... '//
'===== STUB COG PASM CODE GOES HERE ======
'Note this code has to allow for the stub code to actually be offset inside the cog.
'The first table at cog $000 is the table for the pasm code to be loaded from high eeprom and it
' contains the length in the top 9 bits and the location in the eeprom in the lower 18 bits.
' They execute as nop's.
stub .... '\\ provided by include file
...pasm stub loader code goes here '||
'||
'===== BUFFER REQUIRED FOR MOVING BETWEEN EEPROM AND COG ===== '||
'length to be determined but should be much less than 2KB '||
tempbuf long 0[noparse][[/noparse]256] 'length probably can be less '||
'//
'==============================================================
'-----------------------------------------------------------------------------------------------------
..... more user code here ......
'-----------------------------------------------------------------------------------------------------
HUBORG $8000 'for code above 32KB eeprom
'-----------------------------------------------------------------------------------------------------
DAT
org $000
pasm01 ..... user cog pasm code (loaded in high eeprom)
.....
pasm01_end 'end of code to be loaded for this cog
'-----------------------------------------------------------------------------------------------------
DAT
org $000
pasm01 ..... user cog pasm code (loaded in high eeprom)
.....
pasm01_end 'end of code to be loaded for this cog
'-----------------------------------------------------------------------------------------------------
...etc
The call would be like this:
cognew @e_pasm02,@parval 'loads cog from upper 32KB eeprom "pasm02" via the stub "e_pasm02" passing the PAR as required by user
The eeprom does not have to be limited to 64KB. In the method above, there is no reason it could not be up to 256KB. By changing the stub include file (the actual cog loader), the cog code could be located anywhere (SD card, etc).
Also, the code in high eeprom could also be tables, overlays, LMM or anything. Once the basic method is there, anything is possible. Note however, it will be slower.
Looks reasonable. Having some reference methodology pays dividends.
This is all achievable without compiler mods of course; as long as it's documented well enough for the target audience, and can be automated, that's fine. Target audience definition can be illusive though. If I wasn't busy on something else, I would just do it ... and I will at some point anyway I'm sure regardless of what shows here. Funny I have 4.5 protoboards and I never thought about them having 64K eeprom
And yes, it CAN all be done without compiler mods but it is more difficult and harder to document. I liked the fact it can be expanded to 256KB eeproms simply
Brad & Michael: Do you have an include file function? I know it was discussed at one point.
INCLUDE <filename> 'where <filename> may include or omit partial or full drive/path, optional xxxx.spin extension
The compiler just inserts the lines from the file into the source stream. No restrictions.
Cluso99 said...
Brad & Michael: Do you have an include file function? I know it was discussed at one point.
INCLUDE <filename> 'where <filename> may include or omit partial or full drive/path, optional xxxx.spin extension
The compiler just inserts the lines from the file into the source stream. No restrictions.
I've played with it, but I want to make sure that it plays properly with the list file first before I go any further.
Its certainly on my todo list, I've been thinking about it on and off for a while.
You could always use the command line compiler and a pre-processor like cpp in the mean time I guess.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cardinal Fang! Fetch the comfy chair.
1. Would you be willing to accept a diminished cog RAM capacity due to the necessity of including a loader in each cog?
2. Or is it more reasonable to set aside a 2K hub RAM area into which the asm code is loaded from high EEPROM before starting the cog?
3. Sometimes it's handy to preload cog variables into the hub's DAT space during a Start routine before doing the COGNEW. Is sacrificing this capability a reasonable price to pay for increased asm space?
1. No way. I'd rather have a slow loader taking up a couple of longs of spin - probably as part of a more generic I2C driver.
2. No problems on this one. That RAM area is only going to turn into Ethernet buffers or SD card buffers once the COG loading process has completed.
3. Definitely, I think gaining the extra 2K back from HUBRAM is worth it. In the case of JDCogSerial I thought about moving the variable initialisation code to the end of the PASM section; allowing it to become part of the FIFO buffers once the working variables had been loaded. In the end I didn't bother with trying to reclaim those 11 longs! (I wrote some really cryptic PASM, Instead).
Regards,
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Carl Jacobs
JDForth - Forth to Spin Compiler http://www.jacobsdesign.com.au/software/jdforth/jdforth.php
Includes: FAT16 support for SD cards. Bit-bash Serial at 2M baud. 32-bit floating point maths.·Fib(28) in 0.86 seconds. ~3x faster than spin, ~40% larger than spin.
1. There will be no cog ram used - period! I will use the shadow ram.
2. I think about·500 Bytes may be required from hub memory and possibly may not be reclaimable, but ALL your cog code could now be in higher eeprom, so you are reclaiming all cog space.
3. If·you·need to preload·hub DAT space, you will have to have your code in·hub space. You can try·passing variables via hub, but you will not be able modify hub ram.
So, do you want a simple way to place cog code in upper eeprom and reclaim most of hub or not???
Here are some choices and comparisons (choice descriptions follow):
Spin Code Spin Data Implementation/ Reclaimed PASM Waste/ COGs
Choice Usable HUB For Loader Use Difficulty Spin Code KB Rewrites Wasted
A.SL ~30KB-EE&Loader 2KB Easy Up to ~14K None None
B.PHL 30KB 2KB Moderate Up to ~14K None 1
B2.PHL 31.5KB 0.5KB Moderate Up to ~15K None None
C.PL 32KB-EE-loader Loader size Hard Up to ~15K All None
D.APL 32KB-EE/4-loader Loader size-EE Hardest Up to ~15K Some? None
E.BF 32KB None Moderate Up to 16K None None
F.POS 32KB None Done Up to 16K None None
G.NIL 32KB-PASM None Done None None None
A. Spin-loader (SL) · 1. Allocate 2K of HUB DAT for COG load space. · 2. Spin includes EEPROM read (or other) code. · 3. Spin loads HUB and starts each COG. · 4. Spin uses 2K for Heap or other purposes.
B. PASM-HUB-loader (PHL) · 1. Allocate 2K of HUB DAT for COG load swap space. · 2. 2K HUB initially holds PASM loader. · 3. PASM loader includes EEPROM read code. · 4. PASM loads HUB and starts each COG. · 5. Spin uses 2K for Heap or other purposes.
B2. PASM-HUB-loader (PHL) @Cluso99 · 1. PASM stub loader and COG load swap space in hub (target total <0.5KB) · 2. PASM loader includes EEPROM read code. · 3. PASM loads COG and starts each COG.
No other cogs used.
C. PASM-loader (PL) · 1. Spin includes PASM loader. · 2. PASM loader includes all loader/EEPROM read code. · 3. PASM loads its cog-enumerated block. · 4. PASM restarts itself.
D. Alternate PASM-loader (APL) · 1. Spin includes Primary/Secondary role PASM loader. · 2. Spin launches PASM loader on all COGs. · 3. COG number (1 or 7?) determines PASM role. · 4. Spin includes EEPROM read (or other) code. Spin = PASM/4. · 5. PASM Primary manages Spin load of Secondary cog-enumerated blocks. · 6. PASM Secondaries contains mem-copy and re-start command code. · 7. PASM Primary loads and restarts itself.
E. Baker-Flush (BF) · 1. Target program with API wrappers live in upper 32K. · 2. Loader and all target PASM code boot normally. · 3. One PASM section loads upper 32K to HUB and reboots Spin. · 4. Target boots and can load one PASM driver.
F. Prop-O/S (POS) · 1. Use Mike's Prop-O/S -OR- · 2. Use another loader feature O/S
G. Don't bother with any of this. (NIL)
@Cluso99
My 0.5 Propeller Protoboard is the one where the Prop PLL was destroyed by an over-current accident.
Steve:
My idea for loading the upper eeprom into cog was to load a table followed by a stub loader into cog. This would be a short program to read blocks ($40 bytes I think) from eeprom direct into the cog. When the cog stub is about to overwrite itself, the pasm would shift into LMM zero footprint mode (requiring some hub space for the LMM) to read the final blocks into cog, then jump to cog $000 to execute the newly loaded code. The only reason it is not all in LMM is to speed the load up. If the load is shorter than 2KB then the LMM may not need to be executed. So, I think that we may only require about 0.5KB of hub space, but certainly not the whole 2K (including heap space).
I have only worked on starting a cog in pasm whereby the code resides in eeprom above 32KB. The cog may be started by a cognew or coginit instruction as per normal (but the pasm start is the table, par is normal) from spin or pasm. Existing methods of starting cogs and spin residing in hub start (and code) as normal.
In getting this running I would propose to do it in 2 stages. Firstly place all the cog pasm stub code in hub and have a 2K buffer in cog. Once working, shrink the code to reduce hub footprint and speedup loading.
So, currently I propose no changes to the Interpreter or loader. And it can be done without changing the compiler, but is just more complex (have to manually edit the lower hub table)
:-( Pitty about the other 0.5 Prop.
PS: I don't see any of this as being complex. The bits of code required for reading and writing eeprom exist already, just not the upper section, although I know others are already doing this. I already have zero footprint modified LMM running with my debugger, so I understand how to do it.
All memory must be available in the cog (all 496 longs) otherwise there is no point in this exercise. And we want to minimise the hub memory. If cogs do not need to be re-loaded, all space used in the hub table and stub·can be reclaimed. Otherwise I hope 0.5KB will be all that will be required.
Oh, and did anyone notice? I said this method will work with up to 256KB of eeprom. Because the table must have bits 18-21 zero to force a nop. Bits 0-17 give an 18 bit cog start address in eeprom. Bits 23-31 are used for the 9 bit·load length - if zero then the full 512 (well 496) longs will be loaded.
@Cluso99:
Sounds like a minimum-impact / maximum reclaimation scheme [noparse]:)[/noparse]
Can you please summarize the steps for your method and qualify the impact for comparison categories?
I'll add it to the post above.
How do you propose getting code into EEPROM? Looks like a two step process in any method.
Paul's approach (the Baker Flush) can have a downloader stuff the upper 32K for EEPROM or HUB for RAM testing.
I am very attracted to not writing EEPROM often during development.
Cluso99 said...
The only reason it is not all in LMM is to speed the load up.
The LMM won't slow things down at all. The EEPROM R/W code has to have major delays in it anyway to keep the data rate within the EEPROM's specs. I think this is a good approach, because it maximally reduces the hub footprint.
@Steve: Yes will need to minimise writes to eeprom. Development should debug the code being placed high while the code is still low.This will be a user problem that requires a warning. I will do the detail tonight (currently out)
It's just one of those things - isn't it? We think about it for ages, without doing it. When we're finally challenged about it, it's not too hard.
I've taken the dumbest approach possible. If you've read my posts, then you'll know what I've done.
The zip file consists of three top-level applications, which I'll discuss shortly. All of them deal with the object JDCogSerial which was recently submitted to the object exchange.
JDCogSerial.spin has been split into two files:
x_JDCogSerial_pasm.spin - which is the pasm part and a call to the saver and verifyer.
x_JDCogSerial.spin - which is the spin that's leftover, and a call to the loader.
x_Config.spin is included in all x_??? objects and determines the memory map of the EEPROM.
x_saver.spin is used to put the object PASM into EEPROM, it is also used to verify the object.
x_loader.spin is used to get the object back out of EEPROM.
The loader and saver use simple bit bash I2C.
Now for the top-level applications:
1) MemoryWaster.spin - uses JDCogSerial directly, and compiles to 213 longs.
2) x_Main.spin - is used to save the object PASM to EEPROM. Who cares about the size?
3) Main.spin - load the object from EEPROM and use it, and compiles to 151 longs.
The loader itself compiles to 85 longs, so *most* of the HUBRAM has been reclaimed. The "leftover" x_loader contains generic I2C access routines that may be used for other purposes if desired.
Regards,
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Carl Jacobs
JDForth - Forth to Spin Compiler http://www.jacobsdesign.com.au/software/jdforth/jdforth.php
Includes: FAT16 support for SD cards. Bit-bash Serial at 2M baud. 32-bit floating point maths.·Fib(28) in 0.86 seconds. ~3x faster than spin, ~40% larger than spin.
@Carl: How true. I had a quick look at your code - great stuff.
May I be so bold as to suggest the following. There should be at least·3 phases in getting this done.
Phase 1
Initially we use two distinctly separate code blocks. One is for the upper 32KB eeprom (for debugged cog pasm code), and the other is for the lower 32KB eeprom (for spin, data, cog stub loader from upper eeprom, and maybe fast or non-debugged cog pasm).
Both are independantly compiled and the user will be initially responsible for manually setting the upper load addresses (and optional length) within the lower code block·(Phase 2 will fix this by a compiler option).
Upper 32KB eeprom (for debugged cog pasm code)
This block contains a spin program which is executed upon load (by the normal PropLoader using ram option). Also contained in this file is(are) the cog pasm code block(s). The spin program will copy (program) the whole 32KB Hub Ram into the upper 32KB Eeprom (Phase 2 will check to see if the code is the same as already stored in the upper eeprom,·and if so, skip the reprogramming of the eeprom). The spin program may (or may not) include pasm code.
Carl, may I suggest you do this??? You seem to have the I2C·eeprom code under control.
This now covers getting the code into the upper 32KB eeprom. No changes are required to the compiler or proploader for this phase.
Lower 32KB eeprom (for spin, data, cog loader from upper eeprom, and maybe fast or non-debugged cog pasm)
This block contains the users spin programs and any cog pasm programs which the user decides should still be in lower eeprom (and hub resident). In addition to this will be a block of code which contains:
A table of cog upper eeprom addresses (18 bits, for later expansion) and optional length (9 bits) of longs to load. Length=0 means whole 496 longs. In phase 1, the user will be responsible for entering the addresses and lengths directly into the source code before compilation (Phase 2 will hopefully see Brads and Michaels compilers modified to do this automatically).
A stub boot code to load the upper eprom cog code into a temporary 2KB buffer in hub and then into cog (or directly into cog, or a mix of both). This code will also be responsible for any conflicts in use of the buffers. The user may be able to recover this space once all loading has taken place.
A 2KB buffer in hub as described in the previous paragraph. The user may be able to recover this space once all loading has taken place.
(Phase 2 will see the stub boot code and the 2KB buffer reduced in size to hopefully achieve less than 0.5KB footprint in hub space).
I am happy to write the stub boot code, but Carl, may I ask you to do the eeprom reading for me???
Phase 2
See notes above.
Request Brad and Michael modify their compilers to allow a single set of files. Brad has indicated he is willing to do this.
Phase 3
Once the compiler is done,·Brad has indicated he is interested in writing a windoze/linux/mac PropLoader, which would·load the prop in a single·function. This would see the project completed and very user friendly, with full backward compatability.
Does anyone have any objections, suggestions or comments ???
Comments
I'm not entirely sure this is going to be as easy as you think its going to be.
I'm assuming that you are talking about moving PASM code (and ONLY PASM code) that is exclusively loaded into cogs up above $7FFF. Remember that the DAT section in each object starts at the beginning of the object, so any data or variables you have in the DAT section *must* be below $7FFF.
If you use a "HubObj $8000", what precisely do you want to occur?
Compiling hub code is all well and good, and your code will start at $8000, but how do you launch that cog from Spin then ?
cognew ($8000, @par) is going to try and do funky things, unless you plan on subverting coginit/cognew in the interpreter. Of course then you can't simply use cognew/coginit from PASM either.
Alternatives do exist. Carl wrote a serial object that loads the cog and then uses the code space for buffers. I have written a TV object that loads a cog then uses the code space for line buffers.. you just need to be a little clever. I get the idea you want to store your cog binary code above $7FFF and then swap it in 512 longs at a time, boot a cog from it and then move on to the next one. How do you actually plan on using it ?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cardinal Fang! Fetch the comfy chair.
Basically what you said (offline) about the load sequence makes sense - I think we are both on the same page, just expressing it slightly different·with a slight variation.
I thought a fixed program (issued in binary form file) which would be loaded into ram and executed to perform the replaced booter code in the prop. Then the first 32KB would be loaded to hub and stored in eeprom, then the final 32KB would be loaded into the upper 32KB eeprom, then the prop would be reset and the prop would boot the new eeprom code which includes a facility to load upper 32KB sections into cog. If the feature was not used, then the code would be loaded as normal.
Heater: I couldn't see any reason not to make it exactly $8000. Do you have a reason? The current booter actually fills the full 32KB with zeros, so it is probably easier for the compiler to zero fill to $8000 if it finds this line. But I guess it doesn't have to be $8000 either but somehow we are going to need to overcome the issue of the object headers (which I forgot).
There is no need for the cog code to be exactly 2KB, it can still be overlapped. I was not thinking of a modified ram interpreter, but rather a call to a stub loader passing par and the actual cog eeprom address. You see, it is necessary to get the cog's par to be set correctly, and it cannot be referring to code >32KB.
Hope this makes sense.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Prop Tools under Development or Completed (Index)
http://forums.parallax.com/showthread.php?p=753439
My cruising website http://www.bluemagic.biz
Then in there use ORG xxx to specify where in high memory it goes
DAT ' Normal DAT section
org 0 'Normal org
bla
bla
DATH 'DAT section to be loaded high
org $8xxx 'Where to put it in high
mov bla,bla
fit
.
.
Now whenever the compiler sees this it places the code in the correct user specified high location. Thus making otherwise used HUB space free for more SPIN code/data.
User code then is responsible for loading that PASM from the high end of the EEPROM and getting a COG started.
With a bit of simple post extraction users could take those PASM parts out of the binary for use in SD card or whatever as well.
This could also be used for blobs of constant data, like tables and strings, it is a kind of DAT section after all.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
I seem to recall the compiler places local variables at the end of the code. Brad, is this correct? This will still need to be done below the 32KB limit, so it complicates the compiler.
However, I cannot see any use for the headers by the DAT COG objects. Certainly, the Interpreter does not use these for loading cogs with pasm code, only for spin code. For pasm, the par register is loaded with a constant that can be anything and the pasm code knows what to do with it (i.e. if it is an address for parameters, etc then it must be in lower 32KB, otherwise it is a constant which does not matter)
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Prop Tools under Development or Completed (Index)
http://forums.parallax.com/showthread.php?p=753439
My cruising website http://www.bluemagic.biz
Added: ·@Heater, Having any luck extracting·PASM to a binary?· I'll do it if you like; give the word ....
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
Post Edited (jazzed) : 1/15/2009 5:35:30 PM GMT
This way, the user can choose which cog pasm code is loaded in lower and which is loaded in upper.
However, Brad raised the point about the object headers and these cannot go past the 32K boundary. These are the headers that the spin interpreter uses to locate global and local variables, pub and pri calls, etc. It steps through these headers. I do not have a much of an understanding about these. Best place to look at them is a homespun listing.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Prop Tools under Development or Completed (Index)
http://forums.parallax.com/showthread.php?p=753439
My cruising website http://www.bluemagic.biz
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
My Prop Proto Boards (non USB) have 64KB eeproms. I believe so has the USB version. Don't know about the others.
Heater said... User code then is responsible for loading that PASM from the high end of the EEPROM and getting a COG started.
This needs to be done for the user automatically, and yes it could be expanded later to get it from anywhere. It need to be kept simple for the user.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Prop Tools under Development or Completed (Index)
http://forums.parallax.com/showthread.php?p=753439
My cruising website http://www.bluemagic.biz
-Phil
Some compiler changes so that special DAT sections, rather than being compiled into the 32K spin binary, get compiled into a separate 32K binary file with some kind of directory containing an identifier (like a long .. possibly with a 4 letter identifier), a starting location in the 32K image, and a length. The compiler would start with an object (in a file set by a compiler option) containing a short program that just copies these DAT sections to the 2nd half of a 64K EEPROM without the short program (just the directory and the DAT sections). The program would use a simple I2C driver in Spin, something like Basic_I2C_Driver. Typically the user would download this 2nd program to RAM and execute it thus preparing the EEPROM.
The other compiler change would involve another object specified by an option along with some "syntactic sugar" to make the call look as much like a COGNEW or COGINIT as possible. It would use a temporary 2K buffer at the high end of RAM to load the assembly program from the EEPROM given the identifier, again using a short Spin routine to do the reading. A COGINIT or COGNEW could be done and the buffer then reused for something else. It would be possible to write a cog loader that doesn't need a buffer by starting a short program in the cog that only waits for a flag to be set, then transfers a long from hub memory to cog memory, increments the cog address, and clears the flag. This small routine could probably run from "shadow memory" in the cog. Its last transfer would be of a JMP to location zero in the cog to start the newly loaded program. All of the EEPROM I/O would be done by Spin code in the "main" cog.
1. We need to indicate to the compiler that the following PASM or data that would normally be in DAT should actually be placed outside the normal 32K HUB/EEPROM space and into some higher EEPROM space. Hence the idea of DATH (DAT High) whatever name you wan to give it.
As I said this could be used for data tables and strings as well as actual PASM code,
2. We need to know where in the high EEPROM space the compiler has put this so that it can be loaded eventually. Isuggested using "org" which may be an incorrect use. What about DATH $8XXX ? Who says sections can't have addesses? With this one could still use "org" within DAT with it's original meaning
So far this seems like a relatively simple thing for the compiler to do. It would require some user code to fetch those PASM/data blobs from high EEPROM and use them as data or something to load a COG with.
I'm not sure I would go down the road of fixing the compiler to load "high PASM blobs" to cogs automatically. Who says they are in EEPROM? maybe I've put extracted them to SD card. How can the compiler, in general, possibly know what is actually to be loaded and run anyway?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Chadonnay and old Cabernet are my favorites.
I also enjoy New Zeland Sauvignon Blanc.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
But I don't think that this is really necessary to begin with. I helped a customer develop a method for reclaiming assembly areas for data by simply placing a function in the object that contains the assembly code to be reused to return the address of the assembly routine. Then the calling function parcels out the space by generating new addresses by using base + offset. This is basically treating the memory area as a heap. While he wanted to keep things as simple as possible, it wouldn't be much trouble to create a heap manager that takes a size argument and returns an base address of allocated memory, then it would move it's internal base pointer for the next call, when it runs out of space from one heap it moves to the next heap. The only time this wouldn't be much help is when you need a very large block of consecutive memory, such as a video buffer.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Paul Baker
Post Edited (Paul Baker) : 1/16/2009 12:40:21 AM GMT
Loading to eeprom is not an issue. There are many simple ways. We have to overcome the object format problems first.
@Mike: Yes along the lines you suggest. But we need to keep it simple with as few changes to the compiler(s) as possible so as to not introduce any bugs, and keep it simple for the user. One binary/hex makes more sense.
@Paul: Need to keep it simple and that will require a few compiler mods. It could be done as 2 seperate compiles, but then the source becomes extremely complicated for the user. Your method does not allow for loading cogs later. It may be necessary to determine which routines are to be loaded depending on what is happening. It also allows for on-the-fly cog loading. This way, the maximum memory will be available in hub for whatever the user decides. I realise that Parallax would like to keep control of the compiler, but the Jeanie is out of the box and it will only help Parallax anyways. After all, only advanced users will use the extensions.
I see the following:
There needs to be a small table and stub pasm code resident in the hub. When a cognew/init is issued, the stub would be called with the table pointed to so that the correct high eeprom section can be loaded to the cog. I see most of the code loaded directly to the cog, with only the last bit loaded into hub to be copied over the stub (which does the eeprom I2C), and a goto $000 cog at the end of the 496 longs being placed in cog.
It is only necessary to load the length required (reduces loading time).
Here is my take on the table and stub (which would be provided as an include file)
The call would be like this:
The eeprom does not have to be limited to 64KB. In the method above, there is no reason it could not be up to 256KB. By changing the stub include file (the actual cog loader), the cog code could be located anywhere (SD card, etc).
Also, the code in high eeprom could also be tables, overlays, LMM or anything. Once the basic method is there, anything is possible. Note however, it will be slower.
Does this make sense?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Prop Tools under Development or Completed (Index)
http://forums.parallax.com/showthread.php?p=753439
My cruising website http://www.bluemagic.biz
Post Edited (Cluso99) : 1/16/2009 3:50:08 AM GMT
This is all achievable without compiler mods of course; as long as it's documented well enough for the target audience, and can be automated, that's fine. Target audience definition can be illusive though. If I wasn't busy on something else, I would just do it ... and I will at some point anyway I'm sure regardless of what shows here. Funny I have 4.5 protoboards and I never thought about them having 64K eeprom
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
And yes, it CAN all be done without compiler mods but it is more difficult and harder to document. I liked the fact it can be expanded to 256KB eeproms simply
Brad & Michael: Do you have an include file function? I know it was discussed at one point.
INCLUDE <filename> 'where <filename> may include or omit partial or full drive/path, optional xxxx.spin extension
The compiler just inserts the lines from the file into the source stream. No restrictions.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Prop Tools under Development or Completed (Index)
http://forums.parallax.com/showthread.php?p=753439
My cruising website http://www.bluemagic.biz
I've played with it, but I want to make sure that it plays properly with the list file first before I go any further.
Its certainly on my todo list, I've been thinking about it on and off for a while.
You could always use the command line compiler and a pre-processor like cpp in the mean time I guess.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cardinal Fang! Fetch the comfy chair.
1. Would you be willing to accept a diminished cog RAM capacity due to the necessity of including a loader in each cog?
2. Or is it more reasonable to set aside a 2K hub RAM area into which the asm code is loaded from high EEPROM before starting the cog?
3. Sometimes it's handy to preload cog variables into the hub's DAT space during a Start routine before doing the COGNEW. Is sacrificing this capability a reasonable price to pay for increased asm space?
-Phil
2. For the 8080 emulator, no, I want all the HUBRAM for 8080
3. Not worried.
8080 is perhaps a freak program and I'm going to reclaim RAM "manually" at some point. Otherwise I feel option 2 makes more sense generally.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
2. No problems on this one. That RAM area is only going to turn into Ethernet buffers or SD card buffers once the COG loading process has completed.
3. Definitely, I think gaining the extra 2K back from HUBRAM is worth it. In the case of JDCogSerial I thought about moving the variable initialisation code to the end of the PASM section; allowing it to become part of the FIFO buffers once the working variables had been loaded. In the end I didn't bother with trying to reclaim those 11 longs! (I wrote some really cryptic PASM, Instead).
Regards,
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Carl Jacobs
JDForth - Forth to Spin Compiler http://www.jacobsdesign.com.au/software/jdforth/jdforth.php
Includes: FAT16 support for SD cards. Bit-bash Serial at 2M baud. 32-bit floating point maths.·Fib(28) in 0.86 seconds. ~3x faster than spin, ~40% larger than spin.
2. I think about·500 Bytes may be required from hub memory and possibly may not be reclaimable, but ALL your cog code could now be in higher eeprom, so you are reclaiming all cog space.
3. If·you·need to preload·hub DAT space, you will have to have your code in·hub space. You can try·passing variables via hub, but you will not be able modify hub ram.
So, do you want a simple way to place cog code in upper eeprom and reclaim most of hub or not???
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Prop Tools under Development or Completed (Index)
http://forums.parallax.com/showthread.php?p=753439
My cruising website http://www.bluemagic.biz
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
A. Spin-loader (SL)
· 1. Allocate 2K of HUB DAT for COG load space.
· 2. Spin includes EEPROM read (or other) code.
· 3. Spin loads HUB and starts each COG.
· 4. Spin uses 2K for Heap or other purposes.
B. PASM-HUB-loader (PHL)
· 1. Allocate 2K of HUB DAT for COG load swap space.
· 2. 2K HUB initially holds PASM loader.
· 3. PASM loader includes EEPROM read code.
· 4. PASM loads HUB and starts each COG.
· 5. Spin uses 2K for Heap or other purposes.
B2. PASM-HUB-loader (PHL) @Cluso99
· 1. PASM stub loader and COG load swap space in hub (target total <0.5KB)
· 2. PASM loader includes EEPROM read code.
· 3. PASM loads COG and starts each COG.
No other cogs used.
C. PASM-loader (PL)
· 1. Spin includes PASM loader.
· 2. PASM loader includes all loader/EEPROM read code.
· 3. PASM loads its cog-enumerated block.
· 4. PASM restarts itself.
D. Alternate PASM-loader (APL)
· 1. Spin includes Primary/Secondary role PASM loader.
· 2. Spin launches PASM loader on all COGs.
· 3. COG number (1 or 7?) determines PASM role.
· 4. Spin includes EEPROM read (or other) code. Spin = PASM/4.
· 5. PASM Primary manages Spin load of Secondary cog-enumerated blocks.
· 6. PASM Secondaries contains mem-copy and re-start command code.
· 7. PASM Primary loads and restarts itself.
E. Baker-Flush (BF)
· 1. Target program with API wrappers live in upper 32K.
· 2. Loader and all target PASM code boot normally.
· 3. One PASM section loads upper 32K to HUB and reboots Spin.
· 4. Target boots and can load one PASM driver.
F. Prop-O/S (POS)
· 1. Use Mike's Prop-O/S -OR-
· 2. Use another loader feature O/S
G. Don't bother with any of this. (NIL)
@Cluso99
My 0.5 Propeller Protoboard is the one where the Prop PLL was destroyed by an over-current accident.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
Post Edited (jazzed) : 1/18/2009 6:56:31 AM GMT
My idea for loading the upper eeprom into cog was to load a table followed by a stub loader into cog. This would be a short program to read blocks ($40 bytes I think) from eeprom direct into the cog. When the cog stub is about to overwrite itself, the pasm would shift into LMM zero footprint mode (requiring some hub space for the LMM) to read the final blocks into cog, then jump to cog $000 to execute the newly loaded code. The only reason it is not all in LMM is to speed the load up. If the load is shorter than 2KB then the LMM may not need to be executed. So, I think that we may only require about 0.5KB of hub space, but certainly not the whole 2K (including heap space).
I have only worked on starting a cog in pasm whereby the code resides in eeprom above 32KB. The cog may be started by a cognew or coginit instruction as per normal (but the pasm start is the table, par is normal) from spin or pasm. Existing methods of starting cogs and spin residing in hub start (and code) as normal.
In getting this running I would propose to do it in 2 stages. Firstly place all the cog pasm stub code in hub and have a 2K buffer in cog. Once working, shrink the code to reduce hub footprint and speedup loading.
So, currently I propose no changes to the Interpreter or loader. And it can be done without changing the compiler, but is just more complex (have to manually edit the lower hub table)
:-( Pitty about the other 0.5 Prop.
PS: I don't see any of this as being complex. The bits of code required for reading and writing eeprom exist already, just not the upper section, although I know others are already doing this. I already have zero footprint modified LMM running with my debugger, so I understand how to do it.
All memory must be available in the cog (all 496 longs) otherwise there is no point in this exercise. And we want to minimise the hub memory. If cogs do not need to be re-loaded, all space used in the hub table and stub·can be reclaimed. Otherwise I hope 0.5KB will be all that will be required.
Oh, and did anyone notice? I said this method will work with up to 256KB of eeprom. Because the table must have bits 18-21 zero to force a nop. Bits 0-17 give an 18 bit cog start address in eeprom. Bits 23-31 are used for the 9 bit·load length - if zero then the full 512 (well 496) longs will be loaded.
Is this making sense yet?·
Guess I better unpack my prop and get to work...
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Prop Tools under Development or Completed (Index)
http://forums.parallax.com/showthread.php?p=753439
My cruising website http://www.bluemagic.biz
Post Edited (Cluso99) : 1/16/2009 3:26:02 PM GMT
Sounds like a minimum-impact / maximum reclaimation scheme [noparse]:)[/noparse]
Can you please summarize the steps for your method and qualify the impact for comparison categories?
I'll add it to the post above.
How do you propose getting code into EEPROM? Looks like a two step process in any method.
Paul's approach (the Baker Flush) can have a downloader stuff the upper 32K for EEPROM or HUB for RAM testing.
I am very attracted to not writing EEPROM often during development.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
-Phil
@Steve: Yes will need to minimise writes to eeprom. Development should debug the code being placed high while the code is still low.This will be a user problem that requires a warning. I will do the detail tonight (currently out)
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Prop Tools under Development or Completed (Index)
http://forums.parallax.com/showthread.php?p=753439
My cruising website http://www.bluemagic.biz
I've taken the dumbest approach possible. If you've read my posts, then you'll know what I've done.
The zip file consists of three top-level applications, which I'll discuss shortly. All of them deal with the object JDCogSerial which was recently submitted to the object exchange.
JDCogSerial.spin has been split into two files:
x_JDCogSerial_pasm.spin - which is the pasm part and a call to the saver and verifyer.
x_JDCogSerial.spin - which is the spin that's leftover, and a call to the loader.
x_Config.spin is included in all x_??? objects and determines the memory map of the EEPROM.
x_saver.spin is used to put the object PASM into EEPROM, it is also used to verify the object.
x_loader.spin is used to get the object back out of EEPROM.
The loader and saver use simple bit bash I2C.
Now for the top-level applications:
1) MemoryWaster.spin - uses JDCogSerial directly, and compiles to 213 longs.
2) x_Main.spin - is used to save the object PASM to EEPROM. Who cares about the size?
3) Main.spin - load the object from EEPROM and use it, and compiles to 151 longs.
The loader itself compiles to 85 longs, so *most* of the HUBRAM has been reclaimed. The "leftover" x_loader contains generic I2C access routines that may be used for other purposes if desired.
Regards,
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Carl Jacobs
JDForth - Forth to Spin Compiler http://www.jacobsdesign.com.au/software/jdforth/jdforth.php
Includes: FAT16 support for SD cards. Bit-bash Serial at 2M baud. 32-bit floating point maths.·Fib(28) in 0.86 seconds. ~3x faster than spin, ~40% larger than spin.
May I be so bold as to suggest the following. There should be at least·3 phases in getting this done.
Phase 1
Initially we use two distinctly separate code blocks. One is for the upper 32KB eeprom (for debugged cog pasm code), and the other is for the lower 32KB eeprom (for spin, data, cog stub loader from upper eeprom, and maybe fast or non-debugged cog pasm).
Both are independantly compiled and the user will be initially responsible for manually setting the upper load addresses (and optional length) within the lower code block·(Phase 2 will fix this by a compiler option).
Upper 32KB eeprom (for debugged cog pasm code)
This block contains a spin program which is executed upon load (by the normal PropLoader using ram option). Also contained in this file is(are) the cog pasm code block(s). The spin program will copy (program) the whole 32KB Hub Ram into the upper 32KB Eeprom (Phase 2 will check to see if the code is the same as already stored in the upper eeprom,·and if so, skip the reprogramming of the eeprom). The spin program may (or may not) include pasm code.
Carl, may I suggest you do this??? You seem to have the I2C·eeprom code under control.
This now covers getting the code into the upper 32KB eeprom. No changes are required to the compiler or proploader for this phase.
Lower 32KB eeprom (for spin, data, cog loader from upper eeprom, and maybe fast or non-debugged cog pasm)
This block contains the users spin programs and any cog pasm programs which the user decides should still be in lower eeprom (and hub resident). In addition to this will be a block of code which contains:
A table of cog upper eeprom addresses (18 bits, for later expansion) and optional length (9 bits) of longs to load. Length=0 means whole 496 longs. In phase 1, the user will be responsible for entering the addresses and lengths directly into the source code before compilation (Phase 2 will hopefully see Brads and Michaels compilers modified to do this automatically).
A stub boot code to load the upper eprom cog code into a temporary 2KB buffer in hub and then into cog (or directly into cog, or a mix of both). This code will also be responsible for any conflicts in use of the buffers. The user may be able to recover this space once all loading has taken place.
A 2KB buffer in hub as described in the previous paragraph. The user may be able to recover this space once all loading has taken place.
(Phase 2 will see the stub boot code and the 2KB buffer reduced in size to hopefully achieve less than 0.5KB footprint in hub space).
I am happy to write the stub boot code, but Carl, may I ask you to do the eeprom reading for me???
Phase 2
See notes above.
Request Brad and Michael modify their compilers to allow a single set of files. Brad has indicated he is willing to do this.
Phase 3
Once the compiler is done,·Brad has indicated he is interested in writing a windoze/linux/mac PropLoader, which would·load the prop in a single·function. This would see the project completed and very user friendly, with full backward compatability.
Does anyone have any objections, suggestions or comments ???
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Prop Tools under Development or Completed (Index)
http://forums.parallax.com/showthread.php?p=753439
My cruising website http://www.bluemagic.biz
Post Edited (Cluso99) : 1/17/2009 1:38:43 PM GMT