Shop OBEX P1 Docs P2 Docs Learn Events
Forcing exit of 'waitpeq' if no match occurs — Parallax Forums

Forcing exit of 'waitpeq' if no match occurs

HarleyHarley Posts: 997
edited 2007-12-23 00:30 in Propeller 1
Don't know if this subject has been discussed before.

In case a match doesn't happen for a 'waitpeq', is there a way for SPIN to kill the cog in question (say via COGSTOP)?

And if so, can the same assembly routine be reinvoked after the stop?

If so, how much time does it take for this to be available again? (Hopefully the whole program doesn't have to be downloaded from EEPROM again; that takes seconds)

If there are any threads on this subject, steer me to them please.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Harley Shanko
«1

Comments

  • AleAle Posts: 2,363
    edited 2007-12-19 20:39
    You can maybe "implement" a pseudo waitpeq with timeout, comparing the pin status and waiting with waitcnt.

    You can kill a cog with waitstop, no problem, and you can anytime relaunch with the same code in another cog or the same. I do not see a problem, but you will need two threads, one to wait and one of timeout and a shared variable to indicate that the waitpeq stopped waiting or not.
  • Jean-MarcJean-Marc Posts: 5
    edited 2007-12-19 20:44
    My understanding is that you want to implement a kind of timeout on the wait. Note that you cannot do anything with the COG while waiting, so from a functional point of view you could as well do a busy loop and test for timeout or whatever condition requiring a premature exit of the wait. My understanding is that using waitpne would lower power consumption, so this may still be desirable.

    A possibility would be to do a waitpne (with a reversed bit in the state), and add an additional pin (not used externally) in the mask, with the corresponding state at 0. Another cog could set this pin to trigger the end of the waitpne, for example on a timeout or whatever other condition. The waiting cog should check if the waited pin was changed or if the timeout pin was set.

    Depending on the interface circuit, you could even use the same input pin, with another cog making it an output and forcing a value so that the waiting cog continues.

    I hope this help finding an alternative solution to a brutal stop and reload.
    --- Jean-Marc

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Jean-Marc
  • deSilvadeSilva Posts: 2,967
    edited 2007-12-19 21:04
    Jean-Marc,
    lots of interesting ideas - all have their obvious grave drawbacks...
    But that depends on the specific situation.

    The original idea to reload the same COG is not as bad as it may sound... The question is: What is exactly the situation when you encounter a time-out?

    It takes around 500 * 16 ticks * 12,5 ns = 100 µs to reload a COG (this happens from the HUB RAM of course , not from the EEPROM).
  • HarleyHarley Posts: 997
    edited 2007-12-19 21:09
    Thank you Ale and Jean-Marc. But the waitpeq has to be no longer than that one instruction takes; am waiting for Prop#2 for better delays.

    @ Ale, I didn't find a 'waitstop' in SPIN but the COGSTOP is probably what you meant. That instruction doesn't imply that the same cog can't be reused. Because hanging on a waitpeq (like waiting for a breakpoint to occur) might happen many times. And there aren't enough cogs left if they cannot be reused.

    From reading the Spin pages on COGINIT and COGSTOP, it appears there should be a way to stop a hung up waitpeq cog, then restart it with COGINIT.

    Mike Green, anyone else, does this sound right?

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Harley Shanko
  • HarleyHarley Posts: 997
    edited 2007-12-19 21:22
    Thanks also, deSilva, for the timing answer.

    So, does all of the code downloaded from EEPROM stay in hub RAM to be used over and over if needed? Like the assembly code which the waitpeq resides in?

    That 100 µsec wouldn't be noticable for the user; human reaction times aren't that quick. ("Wanna see it again?")

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Harley Shanko

    Post Edited (Harley) : 12/19/2007 9:40:08 PM GMT
  • deSilvadeSilva Posts: 2,967
    edited 2007-12-19 21:29
    Yes, this is so. You start the COG-program first using COGNEW, keeping the returned COG-Id cid.
    When you need to relaod it use COGINIT(cid,...)
    That's what COGINIT has been made for
  • SapiehaSapieha Posts: 2,964
    edited 2007-12-19 21:34
    Hi All.

    My solution on this is.

    Have one pin dedicated to timeout with litle external hardware.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Nothing is impossible, there are only different degrees of difficulty.

    Sapieha
  • HarleyHarley Posts: 997
    edited 2007-12-19 21:43
    @deSilva,
    Beginning to comprehend this situation.

    @Sapieha,
    Even with using two Props, I'm short on cogs and I/O pins. So the suggestion is appreciated, but must not do that unless nothing else works.

    Now, back to the coding 'drawing board' to solve this problem.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Harley Shanko
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2007-12-20 01:44
    Harley,

    If you can spare just one more pin, you can do what you want in the same cog. The idea is to use WAITPNE, masking to two pins: your input pin, and a second pin set as as output and controlled by one of the counters. Set up the counter for for NCO mode with an output on the second pin. Set its FRQx register to be 1. Just before the WAITPNE, set the counter's PHSx register to $8000_0000 - timeout_clocks. When the timer times out, the pin will change state from 0 to 1, which your WAITPNE will catch. You can then check the state of the counter's output pin to see if a timeout occurred. If not, it was your input pin.

    -Phil
  • HannoHanno Posts: 1,130
    edited 2007-12-20 02:19
    Hi Harley,
    ViewPort v2 uses waitpne to trigger on signals. If the required signal isn't found, ViewPort can reset the trigger- by stopping the measuring cogs (up to 4 when sampling at 80mhz) and then restarting. As deSilva pointed out, stopping and starting cogs is very fast.
    Hanno
  • Tracy AllenTracy Allen Posts: 6,662
    edited 2007-12-20 05:18
    Suppose the signal awaited comes from a switch or transistor collector, and it will pull the pin low to end the WAITpxx. Instead of a pullup resistor, use a capacitor in parallel with a resistor from the pin to Vss, and the time constant of the RC will be the timeout mechanism. Just before executing the WAITpxx, make the pin an output high and charge the capacitor. The wait will end on whichever comes first, the event or the RC timeout. CNT can be used to distinguish. A hack perhaps, but may be appropriate in some situations lacking spare pins or a cog-based watchdog.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2007-12-20 07:08
    Tracy,

    That's certainly more appealing than the only single-pin, single-cog solution I could come up with. (It involved burning out the p-cbannel driver on the pin, thus converting it to open-drain so it could be shared with a counter. shakehead.gif )

    Of course, one could reuse a pin for the counter, like SCL perhaps, that's only used at startup (unless the app itself accesses EEPROM).

    -Phil
  • deSilvadeSilva Posts: 2,967
    edited 2007-12-20 08:44
    Another disadvantage of all extra pin solutions or external manipulations is: They cost time to filter out after the event waited for.

    A "second cog watch dog" comes closer to "exception handling" which causes no delay in the common case.

    Post Edited (deSilva) : 12/20/2007 5:08:31 PM GMT
  • Nick MuellerNick Mueller Posts: 815
    edited 2007-12-20 14:21
    Maybe I do have a solution:

    The key-sentence is:
    "A pin is an output if *any* active cog sets it to an output"

    Now if you protect the input-pin with an R so it doesn't short circuit when it suddenly becomes an output, you can have the watchdog-cog set that pin to output and switch it to such a state, that the timed-out cog reads the level he needs to break the waitpeq.


    Didn't verify that, so maybe you've got something to laugh.

    Nick

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Never use force, just go for a bigger hammer!

    The DIY Digital-Readout for mills, lathes etc.:
    YADRO
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2007-12-20 17:13
    The larger issue here may be more of how the app is being factored, rather than how to combine a timeout with waiting for a pin — interesting though the latter might be. In a traditional processor, these matters are typically handled wth interrupts: one for pin changes and another for a timer. If a pin change never occurs, it's no big deal: the ISR just never executes, and a countdown timer, controlled by the timer ISR handles the timeout.

    In the Propeller, each cog can act as an "interrupt system". It would be easy to implement both a pin-change "ISR" cog and a timer "ISR" cog. The pin-change cog simply waits, using a masked WAITPNE with the last pin states observed. When a pin changes state, that fact would be processed by the "ISR" the same as it would be in a traditional processor, with the information it produces communicated to hub variables for other cogs to read. If it never excutes, because nothing changed, so what? It can just sit there, no harm is done, and it doesn't need to be restarted. The timer "interrupt" can handle any watchdogging that needs to take place. This is more in line with deSilva's approach.

    Whether a two-extra-cog "interrupt" solution is wasteful of precious resources or has too high a latency depends entirely on the overall application. For Harley's app, we really don't have enough information to make that judgement.

    -Phil
  • deSilvadeSilva Posts: 2,967
    edited 2007-12-20 17:47
    This is a very good point, Phil!
    COGs can be used for four purposes (though maybe even more...)
    (1) Running parallel code to increase algorithmic performance
    (2) Supervising hard- or software states by polling
    (3) Activating additional timer/counter/video logic
    (4) Depositing machine code to take advantage of the generic processor speed

    So what is waste? You can argue that waste is all which does not take advantage of all four offers. But the propeller way is just to use overkill!

    It is the same waste as using a Controller with CAN-bus and not using the CAN-bus. Stupid, isn't it? Why pay $3 more for it rather than use the version without CAN!

    Don't think I have forgotten the original problem! Why use WAITxxx in the first place? It gives you a VERY PRECISE return wrt to the event, within a fraction of an instruction time. This is "Hardware Polling". There is no other way (of course you can always wish for a combined WAITCNTPEQPNE smile.gif ).

    "Software Polling" takes at least some instructions
    :rep   AND mask, INA WZ, NR
    IF_Z  DJNZ timeout, #:rep
    IF_Z  JMP  #handleTimeout
    
    ' here you can react - 200 ns after the event worst case
    

    Post Edited (deSilva) : 12/20/2007 5:54:21 PM GMT
  • Nick MuellerNick Mueller Posts: 815
    edited 2007-12-20 19:02
    Harleys point was:
    "But the waitpeq has to be no longer than that one instruction takes;"

    So you can't have loops. And I have to reason, that there is no time left for communication between cogs.


    Nick

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Never use force, just go for a bigger hammer!

    The DIY Digital-Readout for mills, lathes etc.:
    YADRO
  • deSilvadeSilva Posts: 2,967
    edited 2007-12-20 19:36
    Thank you for stating that again, Nick! I am very well aware of it, and this was exactly why I designed my above posting just as it is...
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2007-12-21 22:06
    This topic has really managed to cause an itch that needs more scratching. As an exercize, I've tried to figure out what's the tightest software loop that could be used to test one input bit and a watchdog timer, resulting in a three-way branch (pin change, timeout, none of the above). It can be done in eight clocks using both CTRA and CTRB, but I wonder if the same could be accomplished with just one counter.

    -Phil
  • deSilvadeSilva Posts: 2,967
    edited 2007-12-21 22:09
    Could you post it?
    In my example I need 12 to 16 ticks before I can act according to the event waited for...
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2007-12-21 22:44
    [b]con[/b]
    
    pin_no          = 0             '0 to 31
    pin_state       = 0             '0 or 1
    phase           = 0             '0 to 7. Correct value yet to be determined.
    timeout         = 1000000
    
    ...
    
    [b]dat[/b]
    
                  [b]mov[/b]       [b]ctra[/b],modea
                  [b]mov[/b]       [b]frqa[/b],#1
                  [b]mov[/b]       [b]phsa[/b],#0
                  [b]mov[/b]       [b]ctrb[/b],modeb
                  [b]mov[/b]       [b]frqb[/b],#1
                  [b]mov[/b]       [b]phsb[/b],watchdog
    
    tsttimout     [b]tjnz[/b]      [b]phsb[/b],#tstpin
    
    ' Code to execute for a timeout goes here.
    
    tstpin        [b]tjz[/b]       [b]phsa[/b],#tsttimout
    
    ' Code to execute for the pin change goes here.
    
    modea         [b]long[/b]      (%01000 + pin_state) << 30 | pin_no
    modeb         [b]long[/b]      %00100 << 30 | pin_no
    watchdog      [b]long[/b]      -timeout & $FFFF_FFF8 | phase   
    
    
    



    Counter A is set to increment whenever the pin is of the desired state. Counter B is set as an NCO and increments continuously to zero. Its "ouput pin" is set (as a dummy) to the pin being tested. Since DIRA for this pin will equal zero, the NCO output will affect only OUTA and not the pin itself. The last three bits of FRQB have to be set in such a way that PHSB reaches zero when it's tested. I don't know, without further analysis or testing, what this value (for phase in the above program fragment) would be.

    -Phil

    Update: Corrected to add # to the jump targets. Not that it matters since, as deSilva points out below, PHSx won't read properly in the destination field (TJZ and TJNZ instructions, above).

    Post Edited (Phil Pilgrim (PhiPi)) : 12/22/2007 1:06:23 AM GMT
  • mirrormirror Posts: 322
    edited 2007-12-21 23:15
    I had a similar, but different, problem.

    One thing this thread doesn't address is if you've got multiple pins that need to be scanned for changes. I wanted to monitor 8 pins P8-P15. I'm expecting about 2000 transitions per second per pin - which means that in the following code about 98% of the time is spent at the waitpne instruction.·The transitions seen·are written to a circular buffer. A second cog does the processing of the circular buffer - as well as timeouts and additional filtering. The nett result is that the majority of transitions are time-stamped with 12ns accuracy - with about 2% of the transitions being accurate to about only 1us - but this is oK in my case.

    I've not shown any of the·FIFO buffer setup and cog initialisation, so you'd need to do that before using this code. The actual code I use does a couple of other things, and is more efficient, but this gives a fair idea of the logic.

                        ORG
    ScanEntry
                        mov     CntAt,#0
                        rcr     CntAt,#1  wc        'Clear the carry flag - scan INA port.
                        jmp     #FirstTime
    ScanInputs
                        waitpne LastIn,MaskIn       'Wait for a pin change
    FirstTime
                        mov     CntAt,CNT           'Store the time of the change...
                        mov     ReadIn,INA          'Get the pins
                        and     ReadIn,MaskIn       '...but only those of interest
                        cmp     ReadIn,LastIn wz    'See if it's a glitch
        if_e            jmp     #ScanInputs         'Ignore Glitches (<50ns pulses)
                        mov     LastIn,ReadIn       'Keep real changes   
    
                        wrlong  LastIn,DestPtr      'Write input state to memory
                        cmp     DestPtr,EndPtr wz   'End of memory?
        if_z            mov     DestPtr,StartPtr    'Yes - Go back to start.
        if_nz           add     DestPtr,#4          'No - Keep counting.
                        wrlong  CntAt,DestPtr       'Write time of change to memory
                        cmp     DestPtr,EndPtr wz   'End of memory?
        if_z            mov     DestPtr,StartPtr    'Yes - Go back to start.    if_nz           add     DestPtr,#4          'No - Keep counting.
                        wrlong  DestPtr,WritePtr    'Inform that write is complete
                        jmp     #ScanInputs
     
    WritePtr           long    0               'The last address updated with digital info
    StartPtr           long    0               'The first location we can write to
    EndPtr             long    0               'The last location we can write to
    DestPtr            long    0               'Current location that we're writing to
    LastIn             long    0               'Previous state of inputs
    ReadIn             long    0               'Current state of inputs
    MaskIn             long    $0000ff00       'P8 to P15
    CntAt              long    0               'Clock value on change
    
    
  • deSilvadeSilva Posts: 2,967
    edited 2007-12-22 00:27
    Phil,
    I fear it will not work... It's not the missing # with the labels, but you will not read PHSx correctly at the dest-position.
  • deSilvadeSilva Posts: 2,967
    edited 2007-12-22 00:43
    mirror,
    Good work! However, you will not only mis-time 2% of your signals as you say, but loose between 4/10,000 and 1%, depending on the actual distribution. It can be even more, as this is an interarrival time problem, statistically speaking.

    This is the setting:
    A young man entertains two girl friends, living at different ends of the town. He himself lives in the center taking the underground either to one direction or the other. Both trains leave from the same platform and both run every 10 minutes. He enters the station randomly (and daily!) and takes the first train leaving.

    At the end of the month he finds that he has visited one girl friend twice as often as the other one. How can that be?

    ("She is blond!" is the wrong answer.)
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2007-12-22 01:01
    deSilva,

    Yup, right on both counts. The # thing is perennially vexatious and, I fear, always will be. (It's one of the reasons I'm so keen on getting preprocessor hooks in the IDE.) And the source register bit — how inconvenient! — is already in Tricks & Traps. Ouch! blush.gif

    -Phil
  • mirrormirror Posts: 322
    edited 2007-12-22 01:58
    deSilva said...
    mirror,
    Good work! However, you will not only mis-time 2% of your signals as you say, but loose between 4/10,000 and 1%, depending on the actual distribution. It can be even more, as this is an interarrival time problem, statistically speaking.
    Fortunately not - I just happen to know that the pulses I'm expecting are at least 1.5us long. But of course for shorter pulses (less than about 1us) you are correct. Considering that the system being replaced had a resolution of 1us - I'm not too unhappy.

    The trains may run every 10 minutes, but they depart at:
    10:00 -· Train A
    10:03 -· Train B
    10:10 -· Train A
    10:13 -· Train B
    etc.
    So you have a higher probability of catching Train A.

    BTW: I've not seen this conundrum before!
    ·
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2007-12-22 07:22
    Okay, my previous attempt to wait for a pin change with a timeout in an eight-clock loop without auxiliary pins was a bust. Here's one that I think will work (and that uses only one local counter):

    [b]CON[/b]
    
    pin_no        = 0             '0 to 31
    pin_edge      = 0             '0 (low-to-high) or 1 (high-to-low)
    timeout_time  = 100000
    
    phase         = 0             'Value to be determined
    
    ...
    
    [b]DAT[/b]
                  [b]mov[/b]       timeout_cmp,[b]cnt[/b]
                  [b]add[/b]       timeout_cmp,timeout_add
    
                  [b]mov[/b]       [b]ctra[/b],modea
                  [b]mov[/b]       [b]frqa[/b],#pin_change - tst_timeout
                  [b]mov[/b]       [b]phsa[/b],#tst_timeout
                  
    tst_timeout   [b]cmp[/b]       timeout_cmp,[b]cnt[/b] [b]wz[/b]
    tst_pin [b]if_nz[/b] [b]jmp[/b]       [b]phsa[/b]
    
    timeout       'Code for timeout starts here.
    
    pin_change    'Code for pin change starts here.
    
    modea         [b]long[/b]      (%01010 | pin_edge << 2) << 26 | pin_no
    modeb         [b]long[/b]      %00100 << 26 | pin_no
    timeout_add   [b]long[/b]      timeout_time & $FFFF_FFF8 | phase
    timeout_cmp   [b]res[/b]       1     
    
    
    


    The timeout test compares the computed compare time to CNT. Again, the value for phase needs to be determined either analytically or empirically so that equality can be reached when the CMP is executed. The subsequent JMP gets executed only if there was no timeout. PHSA is used to hold the target address of the JMP, which normally points back to the beginning of the two-instruction loop. When the pin changes, though, PHSA points to pin_change instead. (For this to work, the time from edge-to-like-edge must be eight clocks or more. Otherwise PHSA could increment twice between tests.)

    -Phil
  • deSilvadeSilva Posts: 2,967
    edited 2007-12-22 09:37
    Phil, this is - ingenious!
    One can stabilize it a little bit more by jumping away in the time-out situation, thus PHSA being 3, so one can add 24 NOPs at the beginning of the signal processing part smile.gif

    Just kidding... In fact this can be utilized to notice the number of events within the 100 ns loop, but it will counterfight the very purpose of your fine example.

    I think Mirror's example gives a good and new inside to the nature of the problem.
    In fact we are talking about Nyquist, which sometimes gets lost when using "digital talk".
    "What frequency can I handle?" ist the same question as: "What interarrival time of pulses can I handle?" When you habe a good idea of pulse length (and glitch characteristic) - as Mirror has - than this is easy.

    For the throughput of signal processing it will be of little interest whether you catch the µs-pulse some nanoseconds earlier or later....

    However: You sometimes want to know it's precise phase, and then these nano seconds are your accuracy! I think this is the case in Mirror's example and in your exercise: getting the time!

    This is where "Capture and Compare" units are used for in standard microcontrollers..

    Post Edited (deSilva) : 12/22/2007 7:04:38 PM GMT
  • Tracy AllenTracy Allen Posts: 6,662
    edited 2007-12-22 21:48
    Very clever Phil, using phsa for redirection on pin change instead of making it force a pin change! That could open many possibilities.

    It seems troublesome to adjust phase as a compile time constant, and won't it change with the onset of hub instructions and other waitxxx? Can it be determined or forced at run time just before entering the watchdog routine? (I don't know, I'm asking!)

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2007-12-22 22:34
    Tracy,

    timeout_cmp always has to be reinitialized before entering the test loop. As long as this is done a fixed number of clocks ahead of the first compare, the value for phase will not change, since it's dependent only on the number of clocks (mod 8) between the initialization and the compare. This can easily be accomplished by jumping to the initialization section after processing each timeout or pin-change.

    Initializing FRQA needn't be in this section at all. Initializing CTRA could be eliminated from this section, too, if you're always looking for the same edge. If not, it should remain where it is, and the pin-change routine can flip the polarity bit in modea before jumping back to the beginning.

    Here's how things might be rearranged:

    [b]CON[/b]
    
    pin_no        = 0             '0 to 31
    pin_edge      = 0             '0 (low-to-high) or 1 (high-to-low)
    timeout_time  = 100000
    
    phase         = 0             'Value to be determined
    
    ...
    
    [b]DAT[/b]
                  [b]mov[/b]       [b]frqa[/b],#pin_change - tst_timeout
    
    again_dif     [b]mov[/b]       [b]ctra[/b],modea
    
    again_same    [b]mov[/b]       timeout_cmp,[b]cnt[/b]
                  [b]add[/b]       timeout_cmp,timeout_add
                  [b]mov[/b]       [b]phsa[/b],#tst_timeout
                  
    tst_timeout   [b]cmp[/b]       timeout_cmp,[b]cnt[/b] [b]wz[/b]
    tst_pin [b]if_nz[/b] [b]jmp[/b]       [b]phsa[/b]
    
    timeout       'Code for timeout starts here.
                  ...
                  [b]jmp[/b]       #again_same
    
    pin_change    'Code for pin change starts here.
                  ...
                  [b]xor[/b]       modea,flip
                  [b]jmp[/b]       #again_dif
    
    modea         [b]long[/b]      (%01010 | pin_edge << 2) << 26 | pin_no
    flip          [b]long[/b]      1<<28
    timeout_add   [b]long[/b]      timeout_time & $FFFF_FFF8 | phase
    timeout_cmp   [b]res[/b]       1     
    
    
    



    -Phil

    Post Edited (Phil Pilgrim (PhiPi)) : 12/22/2007 10:39:22 PM GMT
Sign In or Register to comment.