Jump to content
IGNORED

Method for consistent timing


mrvan

Recommended Posts

HI all, I'm looking for a method to determine how much "time" has elapsed. I'm using GCC with interrupts disabled and they are disabled due to hints in code fragment comments I've found that suggest they are challenging in C. If interrupts were enabled, I'm pretty sure I could simply count them and have a consistent timing source. Are there any other options? 

Link to comment
Share on other sites

Your could use clock_gettime().  You can provide various options to get elapsed clock time, process time, monotonic time, etc 

 

(oh, are you running on a console - nvm.  How about writing a value to the tms9901 timer and then reading it back?  It still counts down even with ints disabled AFAIR)

  • Like 1
Link to comment
Share on other sites

I use the 9901 timer in my Forth system as a reference timer.

 

I let it run continuously and just read it when I need times. I do it this way because I use it across multiple tasks so I don't want to reset it willy-nilly. 

 

I put the 9901 timer with a fixed delay, in a high-level loop for longer duration timing. 

 

I know this looks pretty strange coming from my alternate universe, but I will happily answer any questions.  :) 

 

You will have re-write this RPN assembler code and put the opcodes on the left side etc, to have the core routines.

My system runs with interrupts on so you can remove the LIMI instructions. 

 

TOS is an alias for R4 in the code below. 

\ TICKTOCK.HSF  TMS9901 hardware timer interface for Camel 99 Forth

\ credit to: http://www.unige.ch/medecine/nouspikel/ti99/tms9901.htm#Timer
\ improvements based on code from Tursi Atariage
\ TMR! now loads from the Forth stack
\ Apr 2023  Went back to a new TICKS word that is more efficient. 
\           MS min= 32 milliseconds

\ timer resolution:  64 clock periods, thus 64*333 = 21.3 microseconds
\ Max Duration    :  ($3FFF) 16383 *64*333 ns = 349.2 milliseconds

[CC] DECIMAL
TARGET-COMPILING
CODE TMR!   ( n -- )         \ load TMS9901 timer from stack
             0 LIMI,
             R12 CLR,        \ CRU addr of TMS9901 = 0
             0   SBO,        \ SET bit 0 to 1, Enter timer mode
             R12 INCT,       \ CRU Address of bit 1 = 2 , I'm not kidding
             TOS 14 LDCR,    \ Load 14 BITs from TOS into timer
            -1  SBZ,         \ reset bit 0, Exits clock mode, starts decrementer
             2 LIMI,
             TOS POP,        \ refill tos cache register from memory stack 
             NEXT,           \ return to Forth interpreter
             ENDCODE

CODE TMR@   ( -- n)         \ read the TMS9901 timer
             0 LIMI,
             TOS PUSH,
             R12 2 LI,      \ cru = 1 (honest, 2=1)
            -1 SBO,         \ SET bit 0 TO 1, Enter timer mode
             TOS 14 STCR,   \ READ TIMER (14 bits)
            -1 SBZ,         \ RESET bit 1, exit timer mode
             2 LIMI,
             NEXT,          \ return to Forth interpreter 
             ENDCODE

 

The rest of this is very Forthy but I have commented it to help you re-write it in a more idiomatic way for C. 

 

Since your C code is native code it is faster than Forth so I suspect you won't want to factor this out.

DT takes two args, the 1st timer read and the 2nd timer read and subtracts them. Then  gets the difference of the intermediate result and an input arg sitting on the return stack.

CODE DT ( T1 T2 -- n)  
 *SP TOS SUB,  \ t1-t2  
     TOS ABS,     
 *RP TOS SUB,  \ subtract from value on Return stack
  NEXT, 
ENDCODE 


TICKS is the primary timer. It keeps reading the timer while the difference between two readings minus n is negative (less than 0) 

This version also calls PAUSE to switch to the next task in the queue while it waits. (Not required of course)

: TICKS ( n -- ) \ ** n(max) = 4000 ~= 100 ms ** 
  >R
  TMR@
  BEGIN  
    TMR@ DT 0<
  WHILE
    PAUSE
  REPEAT
  R> 2DROP
;


Final piece is the milli-second timer called MS() 

It uses a 1500 TICKS timer as a 32mS delay inside a loop.   

Given n as the input arg. it divides n by 32 with a right shift and goes into a loop n/32 times.

I used such a big time because TICKS is essentially controlling the time-slices in my cooperative system. 

32mS seemed to work best for my system.  You could make a 1mS inner timer and make it very accurate I think. 
 

\ 1500 TICKS ~= to 32mS 
: MS ( n -- ) 5 RSHIFT 0 ?DO  1500 TICKS  LOOP ; 

 

I hope this proves to be useful although I understand it will take some work to convert it to C. 

  • Like 1
Link to comment
Share on other sites

2 hours ago, TheBF said:

I use the 9901 timer in my Forth system as a reference timer.

 

I let it run continuously and just read it when I need times. I do it this way because I use it across multiple tasks so I don't want to reset it willy-nilly. 

 

I put the 9901 timer with a fixed delay, in a high-level loop for longer duration timing. 

 

I know this looks pretty strange coming from my alternate universe, but I will happily answer any questions.  :) 

 

You will have re-write this RPN assembler code and put the opcodes on the left side etc, to have the core routines.

My system runs with interrupts on so you can remove the LIMI instructions. 

 

TOS is an alias for R4 in the code below. 

\ TICKTOCK.HSF  TMS9901 hardware timer interface for Camel 99 Forth

\ credit to: http://www.unige.ch/medecine/nouspikel/ti99/tms9901.htm#Timer
\ improvements based on code from Tursi Atariage
\ TMR! now loads from the Forth stack
\ Apr 2023  Went back to a new TICKS word that is more efficient. 
\           MS min= 32 milliseconds

\ timer resolution:  64 clock periods, thus 64*333 = 21.3 microseconds
\ Max Duration    :  ($3FFF) 16383 *64*333 ns = 349.2 milliseconds

[CC] DECIMAL
TARGET-COMPILING
CODE TMR!   ( n -- )         \ load TMS9901 timer from stack
             0 LIMI,
             R12 CLR,        \ CRU addr of TMS9901 = 0
             0   SBO,        \ SET bit 0 to 1, Enter timer mode
             R12 INCT,       \ CRU Address of bit 1 = 2 , I'm not kidding
             TOS 14 LDCR,    \ Load 14 BITs from TOS into timer
            -1  SBZ,         \ reset bit 0, Exits clock mode, starts decrementer
             2 LIMI,
             TOS POP,        \ refill tos cache register from memory stack 
             NEXT,           \ return to Forth interpreter
             ENDCODE

CODE TMR@   ( -- n)         \ read the TMS9901 timer
             0 LIMI,
             TOS PUSH,
             R12 2 LI,      \ cru = 1 (honest, 2=1)
            -1 SBO,         \ SET bit 0 TO 1, Enter timer mode
             TOS 14 STCR,   \ READ TIMER (14 bits)
            -1 SBZ,         \ RESET bit 1, exit timer mode
             2 LIMI,
             NEXT,          \ return to Forth interpreter 
             ENDCODE

 

The rest of this is very Forthy but I have commented it to help you re-write it in a more idiomatic way for C. 

 

Since your C code is native code it is faster than Forth so I suspect you won't want to factor this out.

DT takes two args, the 1st timer read and the 2nd timer read and subtracts them. Then  gets the difference of the intermediate result and an input arg sitting on the return stack.

CODE DT ( T1 T2 -- n)  
 *SP TOS SUB,  \ t1-t2  
     TOS ABS,     
 *RP TOS SUB,  \ subtract from value on Return stack
  NEXT, 
ENDCODE 


TICKS is the primary timer. It keeps reading the timer while the difference between two readings minus n is negative (less than 0) 

This version also calls PAUSE to switch to the next task in the queue while it waits. (Not required of course)

: TICKS ( n -- ) \ ** n(max) = 4000 ~= 100 ms ** 
  >R
  TMR@
  BEGIN  
    TMR@ DT 0<
  WHILE
    PAUSE
  REPEAT
  R> 2DROP
;


Final piece is the milli-second timer called MS() 

It uses a 1500 TICKS timer as a 32mS delay inside a loop.   

Given n as the input arg. it divides n by 32 with a right shift and goes into a loop n/32 times.

I used such a big time because TICKS is essentially controlling the time-slices in my cooperative system. 

32mS seemed to work best for my system.  You could make a 1mS inner timer and make it very accurate I think. 
 

\ 1500 TICKS ~= to 32mS 
: MS ( n -- ) 5 RSHIFT 0 ?DO  1500 TICKS  LOOP ; 

 

I hope this proves to be useful although I understand it will take some work to convert it to C. 

OK, this should work once the kinks are out. I read through the source documentation at http://www.unige.ch/medecine/nouspikel/ti99/tms9901.htm#Timer to help me understand more.

The timer is a bit limited (short lived) but I can work with that. There's a seemingly concise example there.

 

I'm having difficulty finding the exact ASM syntax built into the gcc compiler to move a word from a register to a variable. I have a handful of examples, but all of them transfer a variable's value to a register, such as:

 

   __asm__("MOV %0, R12\n\tsbo 0" : : "r"(crubase));

 

I'm thinking I need something like

   __asm__("MOV R2, %0\n" : : "r"(int_v));

in the last asm line in clock_read below. That "r" is likely wrong...

 

Not surprisingly the code doesn't work correctly as the variable k in clock_read is never updated so always returns 99 rather than the timer's current value.

 

My test code is:

 

 

#include <stdio.h>

#include <string.h>

 

void clock_start () {

   __asm__(

      "       CLR  R12         ; CRU base of the TMS9901\n"

      "       SBO  0           ; Enter timer mode\n"

      "       LI   r1,0x3FFF   ; Maximum value\n"

      "       INCT R12         ; Address of bit 1\n"

      "       LDCR r1,14       ; Load value\n"

      "       DECT R12         ; TBD\n"

      "       SBZ  0           ; Exit clock mode and start decrementer"

   );

}

 

void clock_read (int *v) {

   int k = 99;

   __asm__(

      "       CLR  R12         ; CRU base of the TMS9901\n"

      "       SBO  0           ; Enter timer mode\n"

      "       STCR r2,15       ; Read current value (plus mode bit)\n"

      "       SRL  r2,1        ; Get rid of mode bit\n"

      "       LDCR R12,15      ; Clear Clock register, and exit timer mode\n"

//    "       S    r2,R1       ; How many cycles were done?\n"

      "       MOV  r2,%0\n" : : "w"(k)

   );

   *v = k;

}

 

int main (int argc, char *argv[]) {

   fputs ("hello world!\n", stdout); // print hello world

 

   int v = 0;

 

   clock_start ();

 

   fputs ("delay a little\n", stdout);

 

   for (int i = 0; i < 16; i++) {

      clock_read (&v);

      fputs (int2str (v), stdout);

      fputs ("\n", stdout);

   }

 

   return 0;   

}

 

 

 

 

  • Like 1
Link to comment
Share on other sites

22 hours ago, mrvan said:

I'm having difficulty finding the exact ASM syntax built into the gcc compiler to move a word from a register to a variable.

I was able to get some test code working using "=r", and also by removing one of the colons.  Try this:

 

__asm__(
      "       CLR  R12         ; CRU base of the TMS9901\n"
      "       SBO  0           ; Enter timer mode\n"
      "       STCR r2,15       ; Read current value (plus mode bit)\n"
      "       SRL  r2,1        ; Get rid of mode bit\n"
      "       LDCR R12,15      ; Clear Clock register, and exit timer mode\n"
//    "       S    r2,R1       ; How many cycles were done?\n"
      "       MOV  r2,%0\n" : "=r"(k)
);

 

 

  • Like 1
Link to comment
Share on other sites

1 hour ago, chue said:

I was able to get some test code working using "=r", and also by removing one of the colons.  Try this:

 

__asm__(
      "       CLR  R12         ; CRU base of the TMS9901\n"
      "       SBO  0           ; Enter timer mode\n"
      "       STCR r2,15       ; Read current value (plus mode bit)\n"
      "       SRL  r2,1        ; Get rid of mode bit\n"
      "       LDCR R12,15      ; Clear Clock register, and exit timer mode\n"
//    "       S    r2,R1       ; How many cycles were done?\n"
      "       MOV  r2,%0\n" : "=r"(k)
);

 

 

Thanks, chue, I confirmed that it is indeed reading the timer value. Do you have a source for understanding these ASM/GCC bindings? 

 

Something is wrong in my code in that the timer gets frozen and never reads another number after first read. I know there's a means to freeze the timer output for reading, perhaps I'm freezing it and then not unfreezing. It's unfortunately late and I have a big day so need to pause for now so I spin down before sleep. Small victories, thank you!

 

I should note that I'm currently running in the MAME emulator as I regularly use a Mac, and if not bad enough it's also a ARM processor. So even my GCC compiler is running in an x86 Linux docker container. 😕  I would like to see that Classic99 timer debugger.

 

Link to comment
Share on other sites

In your code above, the clock_read() code clears the timer which disables it.  Is that intentional?  Datasheet here explains how the timer works: https://www.stuartconner.me.uk/tms99110_breadboard/downloads/tms9901_datasheet.pdf

I don't think you can freeze the timer.  Once armed (any non zero value) it starts decrementing and continues after you leave clock mode.

  • Like 1
Link to comment
Share on other sites

__asm__(
      "       CLR  R12         ; CRU base of the TMS9901\n"
      "       SBO  0           ; Enter timer mode\n"
      "       STCR r2,15       ; Read current value (plus mode bit)\n"
      "       SRL  r2,1        ; Get rid of mode bit\n"
      "       LDCR R12,15      ; Clear Clock register, and exit timer mode\n"
//    "       S    r2,R1       ; How many cycles were done?\n"
      "       MOV  r2,%0\n" : "=r"(k)
);

If you are using this code you are entering "timer mode" which stops the decrementer.

You forgot to add the SBZ instruction after reading the timer which will restart the decrementer. 

 

You can see that here in my version.

  R12 2 LI,      \ cru = 1 (honest, 2=1) 
 -1 SBO,         \ SET bit 0 TO 1, Enter timer mode
  R4 14 STCR,    \ READ TIMER (14 bits)
 -1 SBZ,         \ RESET bit 1, exit timer mode

 

It also looks like the correct CRU address of the timer is 1 which requires loading R12 with 2. :)  

But I see that you are reading/writing 15 bits versus my 14 so maybe that works too.

I am certain however that you must exit timer mode to restart the decrementer.

You might want to try my values and see if it works differently. ?

  • Like 2
Link to comment
Share on other sites

2 hours ago, TheBF said:

You forgot to add the SBZ instruction after reading the timer which will restart the decrementer. 

Actually the trick here is loading the full word includes bit zero so the SBZ isn't needed.

 

2 hours ago, TheBF said:

I am certain however that you must exit timer mode to restart the decrementer.

My understanding is that the timer decrements regardless of what mode the chip is in, but can only be read in timer mode.  A copy is taken on entry to timer mode so it can be read even though it continues decrementing.

  • Like 1
Link to comment
Share on other sites

12 hours ago, mrvan said:

Thanks, chue, I confirmed that it is indeed reading the timer value. Do you have a source for understanding these ASM/GCC bindings? 

 

Something is wrong in my code in that the timer gets frozen and never reads another number after first read. I know there's a means to freeze the timer output for reading, perhaps I'm freezing it and then not unfreezing. It's unfortunately late and I have a big day so need to pause for now so I spin down before sleep. Small victories, thank you!

 

I should note that I'm currently running in the MAME emulator as I regularly use a Mac, and if not bad enough it's also a ARM processor. So even my GCC compiler is running in an x86 Linux docker container. 😕  I would like to see that Classic99 timer debugger.

 

I'm able to run Classic99.exe on my M2 macbook via the latest wine64 version. You also need the (custom?) classic99 64-bit build to use with it.

Not sure if the GCC compiler would run using wine or not.

 

 

  • Like 1
Link to comment
Share on other sites

2 hours ago, khanivore said:

Actually the trick here is loading the full word includes bit zero so the SBZ isn't needed.

The thing I was looking at was LDCR 15 bits is ~50 cycles where the SBO/SBZ pair are 24 in total. 

But I am not sure how that actually plays out inside TI-99.

  • Like 1
Link to comment
Share on other sites

For what it's worth I can confirm that your code to read the timer works perfectly in my system where the timer is running continuously. 

INCLUDE DSK1.ASM9900

CODE T@ ( -- n)
           R4 PUSH, 
          R12 CLR,      \ ; CRU base of the TMS9901\n"
            0 SBO,      \ ; Enter timer mode\n"
        R4 15 STCR,     \ ; Read current value (plus mode bit)\n"
        R4  1 SRL,      \ ; Get rid of mode bit\n"
       R12 15 LDCR,     \ ; Clear Clock register, and exit timer mode\n"             
        NEXT,
ENDCODE 

 

  • Like 1
Link to comment
Share on other sites

On 10/25/2023 at 12:41 PM, TheBF said:

For what it's worth I can confirm that your code to read the timer works perfectly in my system where the timer is running continuously. 

INCLUDE DSK1.ASM9900

CODE T@ ( -- n)
           R4 PUSH, 
          R12 CLR,      \ ; CRU base of the TMS9901\n"
            0 SBO,      \ ; Enter timer mode\n"
        R4 15 STCR,     \ ; Read current value (plus mode bit)\n"
        R4  1 SRL,      \ ; Get rid of mode bit\n"
       R12 15 LDCR,     \ ; Clear Clock register, and exit timer mode\n"             
        NEXT,
ENDCODE 

 

Thank you for checking that--very helpful. Given that, I'm thinking the immediate code setting and reading the timer is correct but I'm stepping on registers because I'm not pushing/popping them on the stack before and after using them (or at least squirreling away the values and restoring them). I've been pondering that having seen the pushes/pops in your code.

 

  • Like 1
Link to comment
Share on other sites

1 hour ago, mrvan said:

Thank you for checking that--very helpful. Given that, I'm thinking the immediate code setting and reading the timer is correct but I'm stepping on registers because I'm not pushing/popping them on the stack before and after using them (or at least squirreling away the values and restoring them). I've been pondering that having seen the pushes/pops in your code.

 

That sounds like a good point.   

I can't be much help in the GCC environment but since the compiler makes best use of registers I suspect that you can't assume any register is free when you do your own thing. 

Forth is not a "smart" compiler so registers are statically allocated for a few things and there are a few free registers. I happen to use R4 as a cache (accumulator) for the data stack so that's what the pushing and popping is all about in my case. 

The 9900 only needs one instruction for popping a register and two to push. I think GCC uses R10 for the stack pointer. (??)

I have some info around here somewhere that Tursi gave me on the GCC environment and register usage. I will search around.

 

  • Like 1
Link to comment
Share on other sites

29 minutes ago, jedimatt42 said:

Do you mean using TIPI as a timer or that GCC libti99 has a 9901 solution?

Link to comment
Share on other sites

14 hours ago, TheBF said:

Do you mean using TIPI as a timer or that GCC libti99 has a 9901 solution?

libti99 has a solution for waiting with interrupts off for the VDP interrupt to occur, and then you can count refresh cycles... The 'this' I refer to is the vague original question... 

 

init program, reset counter ( set to zero )

 

enter your main loop... ( build your mini-RTOS ) 

 

wait for the VDP interrupt at the top of your loop. increment your counter...  each increment is 1/60th second since the last increment, provided your loop body completes within 1/60th a second. Thus the operations in your loop have to hold that RTOS sort of contract... Depending on what you are trying to do this is pretty achievable.  And probably good for game loops. 

 

counter = 0
while true
  wait for vdp
  counter += 1
  handle overflow. or don't. depends on your needs.
  // do some light math to decide which functions should operate this cycle... 
  if every even frame, update sound chip
  if every odd frame, read player input
  if every frame, update sprite locations

 

If your on a PAL machine it is 1/50th a second... I have code here to figure out if it is a pal system: https://github.com/jedimatt42/fcmd/blob/81cabb051d4626a1367b4162107a1d9992f41a59/b10_isPal.c#L4

 

Now, I didn't really intend to bring up TIPI... but I use a technique like the above to handle the keyboard input prompt in ForceCommand, and after a minutes worth of ticks I then read the realtime clock(TIPI or IDE or Corcomp) if present, and update the time on the screen. But that isn't suitable for time critical usage, as reading the RTC might be thousands of instructions via DSRLNK.

 

-------

I'd be interested in seeing the 9901 technique taken to completion... Different use cases need different approaches... from reading this thread, I still have no idea what the use case is. 

 

@mrvan you can teach GCC which registers you are using in your inline assembly by declaring them... that should make it handle recovering them off the stack appropriately... The code below clobbers r12, and it declares it in the " : " <outputs> " : " <inputs> " : " <clobbers> " section... I believe the clobbers section is a comma separated list... so you should be able to do things like "r4,r12" to tell the compiler that both registers need to be restored. I always read the generated assembly gcc produces to be sure it does what I want with these tricky bits. 

 

    __asm__(
      "clr r12\n\t"
      : 
      :
      : "r12"
    );

 

 

https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

6 hours ago, jedimatt42 said:

I'd be interested in seeing the 9901 technique taken to completion... Different use cases need different approaches... from reading this thread, I still have no idea what the use case is. 

The only thing I can think of is it gives you a higher resolution/no overhead polled timer.

I use it for everything, even timing my cursor flash on the console. 

I think it could be useful for shaping music envelopes as well.

 

Link to comment
Share on other sites

On 10/28/2023 at 6:21 AM, TheBF said:

The only thing I can think of is it gives you a higher resolution/no overhead polled timer.

I use it for everything, even timing my cursor flash on the console. 

I think it could be useful for shaping music envelopes as well.

 

Sorry folks, I meant to respond more quickly. Lots of travel and sight seeing.

 

The primary use case for wanting to know how much time has elapsed is to support a method of cooperative multi-tasking, which somewhat mimics the old Windows concept by performing other tasks/executing other programs when a program  is relatively idle with the request for user input being the most common. I'm currently using this to play sound and speech in the background, flash the cursor, and support user-defined background services. All of them are essentially daemons. Without any timing source each of them currently spins in its own main loop attempting to self-time and perform its action without burning too much CPU time. My intend is to create an outer loop scheduler that determines time elapsed and then notifies each to perform its action at the appropriate time. Polling works well enough for sound and speech, other than burning more CPU than needed. Other things, such as flashing the player ID on a game when that player is playing is another example. Although the timing isn't critical it looks bad if not relatively stable.

 

The timer seems like it should work pretty well. 

 

 

  • Like 1
Link to comment
Share on other sites

On 10/27/2023 at 11:25 PM, jedimatt42 said:

@mrvan you can teach GCC which registers you are using in your inline assembly by declaring them... that should make it handle recovering them off the stack appropriately... The code below clobbers r12, and it declares it in the " : " <outputs> " : " <inputs> " : " <clobbers> " section... I believe the clobbers section is a comma separated list... so you should be able to do things like "r4,r12" to tell the compiler that both registers need to be restored. I always read the generated assembly gcc produces to be sure it does what I want with these tricky bits. 

 

    __asm__(
      "clr r12\n\t"
      : 
      :
      : "r12"
    );

Nice. Thank you for this description of the outputs/inputs/clobber syntax. I'll let you know how it goes.

Link to comment
Share on other sites

3 minutes ago, mrvan said:

Sorry folks, I meant to respond more quickly. Lots of travel and sight seeing.

 

The primary use case for wanting to know how much time has elapsed is to support a method of cooperative multi-tasking, which somewhat mimics the old Windows concept by performing other tasks/executing other programs when a program  is relatively idle with the request for user input being the most common. I'm currently using this to play sound and speech in the background, flash the cursor, and support user-defined background services. All of them are essentially daemons. Without any timing source each of them currently spins in its own main loop attempting to self-time and perform its action without burning too much CPU time. My intend is to create an outer loop scheduler that determines time elapsed and then notifies each to perform its action at the appropriate time. Polling works well enough for sound and speech, other than burning more CPU than needed. Other things, such as flashing the player ID on a game when that player is playing is another example. Although the timing isn't critical it looks bad if not relatively stable.

 

The timer seems like it should work pretty well. 

 

 

Not sure if this helps; traditional  Forth systems (and mine) put a call to the scheduler into I/O primitives. This makes things schedule automatically while waiting for I/O.

And the time delay routine does the same thing. 

Might work in your use case?

Link to comment
Share on other sites

13 hours ago, TheBF said:

Not sure if this helps; traditional  Forth systems (and mine) put a call to the scheduler into I/O primitives. This makes things schedule automatically while waiting for I/O.

And the time delay routine does the same thing. 

Might work in your use case?

Yes, that makes absolute sense and should work in my intended use case.

  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...