6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Sep 21, 2024 2:39 am

All times are UTC




Post new topic Reply to topic  [ 17 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Mon Aug 04, 2014 11:17 am 
Offline

Joined: Mon Aug 05, 2013 10:43 pm
Posts: 258
Location: Southampton, UK
While my homemade system is not 6502 based (it's 6809 instead), I think the problems/challenges I'm having are sufficiently generic that I'm sure everyone here would be interested in any possible solutions.

So my computer is becoming fairly advanced now. It has RAM bank-switching, timer interrupts, a DUART, sounder, a 16bit IDE interface, with glue logic in a CPLD... and most recently I've added video, in the form a V9958. That was a whole load of fun to get working, and I can now run my machine monitor on an RGB screen, albeit with input coming form the serial interface until I add a keyboard. I'm really pleased with this progress.

Everyone likes a video, so here's a short one:

http://www.youtube.com/watch?v=bbLPGtZADf4

Yes, the scrolling is slow. This is because I'm unnecessary resetting the VDC's address pointer on each and every character write.

The problem I'm having is with organising the software. What I want is a simple library of routines, such that a "program" can be written in such a way that it doesn't have to care if it is outputting to the DUART or the VDC (ignoring graphics for the moment).

I already have a jump table mechanism between "user" code and the monitor, but what I need is some form of IO abstraction on top of that. I already have character and buffer in and out routines, which are what I've hacked up to make the V9958 output the text. But I need something much more sophisticated.

Sorry for the vagueness. :( Any tips are gratefully received.

_________________
8 bit fun and games: https://www.aslak.net/


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 04, 2014 12:03 pm 
Offline

Joined: Wed Jan 08, 2014 3:31 pm
Posts: 578
There's already a good OS for 6809 machines called Nitros9 http://sourceforge.net/p/nitros9/wiki/Main_Page/ and you might want to consider a port of it.

I'm curious though, why the 6809 over a 65816?


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 04, 2014 12:54 pm 
Offline

Joined: Mon Aug 05, 2013 10:43 pm
Posts: 258
Location: Southampton, UK
Martin_H wrote:
There's already a good OS for 6809 machines called Nitros9 http://sourceforge.net/p/nitros9/wiki/Main_Page/ and you might want to consider a port of it.


That's really interesting, thanks. I'd heard of OS-9 but never had the pleasure of using it. I'm guessing porting the graphics part, at least, would be extremely difficult. Be great to find out how it works though, since a multi-tasking executive is something I've dreamed about implementing.

Quote:
I'm curious though, why the 6809 over a 65816?


That's a very good question. The simple answer is I hadn't heard of the '816 when I started out with this project back in May last year. However, I'm still glad I went for the 6809. Whilst the NMOS fabrication results in obscene power usage (1W!) and the bus driving is weedy, the programming model is still way ahead of the 65[C]02 IMO and probably almost as good as the 816, the segmented memory addition notwithstanding. But even that doesn't interest me much and seems kludgy (but fast) compared to traditional bank switching.

If anyone has any more compelling reasons to prefer the 65816 over the 6809 I'd sure like to hear them. Though for this project, it's a bit too late....

I'd like to look of the 6309 too, but some parts of that grate with me. eg. the non orthogonal stack operations on the new registers. But the fact that it is pin-compatible make it an obvious choice for me.

_________________
8 bit fun and games: https://www.aslak.net/


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 04, 2014 1:57 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
Quote:
If anyone has any more compelling reasons to prefer the 65816 over the 6809 I'd sure like to hear them.
The 6809 is an very impressive chip, IMO, and even more impressive is its next-generation CMOS reiteration, the 6309.

I also like the '816. The list of its advantages is short, but the points on the list are quite compelling:

  • the '816 is still in production, and boasts much higher clock speeds than the 6809/6309.
  • the body of pre-existing software that'll run on the '816 is more extensive than what'll run on the '09.
  • the '816 manages its memory in banks that are a full 64K in size. This is a crushing advantage (as compared to sub-64K "window" schemes) if the application involves a lot of random accesses into megabyte-scale data structures. And of course even a clunky windowing scheme is something the '09 user needs to add using external hardware. The 816's system is not only superior; it's integrated on-chip. (Which BTW makes it standard for all users, unlike add-on hardware.)

Quote:
I'd like to look of the 6309 too, but some parts of that grate with me. eg. the non orthogonal stack operations on the new registers. But the fact that it is pin-compatible make it an obvious choice for me.
I wonder why you hesitate, given that use of the 6309's new registers is optional, but the 6809's obscene power consumption is mandatory. BTW another major advantage of 6309 over 6809 is faster execution due to fewer dead bus cycles.

-- Jeff

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 04, 2014 6:39 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
Aslak3 wrote:
The problem I'm having is with organising the software. What I want is a simple library of routines, such that a "program" can be written in such a way that it doesn't have to care if it is outputting to the DUART or the VDC (ignoring graphics for the moment).

This sounds related to how Forth does the outputting. You can specify the desired output device before doing your outputting, whether in the routine or before it is called. It's pretty standard to have at least CONSOLE and PRINTER, then I also have RS232 and SS22, plus AUXDEV which could be anything, for example an I²C or SPI port, or something I devised on the spot for experimenting with a new IC I'm considering using in a new product design for example. Specifying one of these puts the appropriate number (which is just a constant, like 0 for CONSOLE, 1 for PRINTER, 2 for RS232, etc.) in variable OUTDEV which is used by the "emit" and "type" routines to know what to do with data that's supposed to be outputted. If you specify AUX_OUTPUT, emit and type will get the address of the necessary routine from variables AUXemit and AUXtype. If you have two or more special output types, and want to send data to one and then another and then another, you can keep changing which routine the AUXemit and AUXtype variables point to. Now you can add support for as many output types as you want without recompiling emit and type (which are the internals called by EMIT and TYPE).

Code:
   CONSOLE  CR        \ This line will make the monitor start a new line.
            248 EMIT  \ In the ANSI character set, this will display the degree symbol.
            ." This string will get sent to the monitor."
   PRINTER  CR        \ This will make the printer start a new line.
            ." This string will get sent to the printer."
   RS232    ." This string will get sent out the RS-232 port."

." (pronounced "dot quote") above compiles the literal string at compile time, then calls up TYPE at run time to handle the string.

The commands to print or display or send something are the same regardless of destination, and it will go wherever the last output routing command says to. If there are error conditions, they will be handled appropriately for the output device chosen. For example, if the printer is offline or out of paper, it will be handled differently from the CTS being false and timing out on the RS-232 port. In a multiasking system, each task would keep its own copies of where it's routing the output. It would need to use semaphores of course so for example you don't have two tasks simultaneously sending something to a printer and mixing stuff up. You could expand the way the output devices are specified, so for example if you had two printers, your OS could tell each of two tasks which printer it will use when it wants to print. The task could still operate other ports if there's no conflict, but when it wants to print, it won't print on the wrong printer.

Cursor positions are kept in an array that has a cell for each device, so if you switch from one device to another and then come back, you still know where you are. OUT returns the address holding the applicable cursor position.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 04, 2014 10:51 pm 
Offline

Joined: Sun Nov 08, 2009 1:56 am
Posts: 395
Location: Minnesota
Possibly the easiest thing to do is what many small computer systems have done in the past: hard-associate certain numbers with particular devices, then always use the numbers when you want to interact with those devices.

For example, on the C64 "device 0" is always the keyboard (read only), "device 1" is always the datasette, "device 2" is always the modem, "device 3" is always the screen, and so on.

The Kernel jump table has entries for:

- "open file" - needs at least a device and file number; this associates a file with a device and does whatever the device needs to initialize
- "set input file" - by file number; all subsequent reads will come from the device associated with this file
- "set output file" - by file number; all subsequent writes will go to the device associated with this file
- "read" - inputs a character from the current input file (and whatever device that corresponds to)
- "write" - outputs a character to the current output file (and whatever device that corresponds to)
- "close file" - removes a file from the file table and releases any other resources used (including the device, if necessary)

File numbers are distinct from device numbers mainly because some devices can support multiple open files at one time (eg., disk drives).

Commodore also wrote the Kernel so file numbers 128 to 255 (ie., with the high bit set) flag that line feeds are to automatically follow any carriage return output - which is of course a vendor-specific thing. In general the mapping of device numbers to specific devices is also vendor-specific and somewhat arbitrary (it's nice if there's a logical pattern to the assignments, but hardly necessary).


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 05, 2014 2:30 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8390
Location: Midwestern USA
Aslak3 wrote:
The problem I'm having is with organising the software. What I want is a simple library of routines, such that a "program" can be written in such a way that it doesn't have to care if it is outputting to the DUART or the VDC (ignoring graphics for the moment).

In other words, you need a kernel. Regardless of operating system, just about all kernel I/O is implemented in four distinct functions:

  • OPEN: returns a channel number (aka file number, file handle, etc.) to be used to communicate with a named device. For example, in the UNIX world, one would write:

    Code:
    FD = open("/dev/tty",O_RDWR)

    which says to create an internal data channel to the device /dev/tty, which would be the user's terminal. The O_RDWR value is a flag that tells the kernel that the data channel is read/write—it is also possible to have read-only or write-only as permissible operations. When the open operation has been completed, a file descriptor, which is a small positive integer, will be returned in the variable FD (the value -1 is returned in FD if the open fails).

    OPEN usually performs device-specific initialization steps, such as allocating I/O buffers and initializing data structures needed by the kernel to access the device. Generally speaking, these details are invisible to the calling application.

  • CLOSE: closes a previously established data channel. In UNIX, the channel that was opened in the above example would be closed by:

    Code:
    close(fd)

    where FD is the file descriptor. CLOSE breaks the channel and in the case of mass storage devices, may flush buffers to assure that all data has been written to disk, tape, etc. Data structures that were allocated by OPEN are freed for reuse.

  • READ: gets one or more bytes from an OPENed channel and deposits them into a buffer. In UNIX:

    Code:
    nbytes = read(FD,*buffer,count)

    where FD is the file descriptor, *buffer is a pointer to a buffer into which bytes are to be stored and count is the desired number of bytes to read. nbytes indicates the number of bytes actually read, which could be less than what was requested, usually indicating that the end of the file has been reached, or zero, indicating that no data is available.

  • WRITE: puts one or more bytes to an OPENed channel, fetching them from a buffer. In UNIX:

    Code:
    nbytes = write(FD,*buffer,count)

    where FD is the file descriptor, *buffer is a pointer to a buffer from which bytes are to be gotten and count is the desired number of bytes to write. nbytes indicates the number of bytes actually written, which could be less than what was requested, usually indicating that the device to which the data is being written is full.

In UNIX, the shell (BASH in Linux) normally OPENs three channels to the user's terminal: read, write and write. These channels are respectively referred to as stdin (standard in), stdout (standard out) and stderr (standard error). stdin is read for keystrokes, output is normally written to stdout, and error messages (e.g., file not found) are written to stderr. I/O to any device other than the user's terminal must be established with OPEN.

Of course, my above examples are in C. However, the principles are the same in assembly language, only the code is different.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 05, 2014 11:26 am 
Offline

Joined: Mon Aug 05, 2013 10:43 pm
Posts: 258
Location: Southampton, UK
Dr Jefyll wrote:
I also like the '816. The list of its advantages is short, but the points on the list are quite compelling:

the '816 manages its memory in banks that are a full 64K in size. This is a crushing advantage (as compared to sub-64K "window" schemes) if the application involves a lot of random accesses into megabyte-scale data structures. And of course even a clunky windowing scheme is something the '09 user needs to add using external hardware. The 816's system is not only superior; it's integrated on-chip. (Which BTW makes it standard for all users, unlike add-on hardware.)


This is where things get interesting, huh? While segmenting memory lets you do things you can't do with bank switching, it is still (IMO) a horrible kludge. I've done my share of WIN16 programming (in C) to know that segmented memory has it's own share of problems. Plus I'm not actually dealing with large datasets in my computer. At least, not yet. I will find out how the '816 deals with segmentation though, just for my own interest. eg. can you specify a full address as an immediate value, or do you have to load the segment register separately, does the segment register roll over on index register overflow etc.

In summary, I prefer the simplicity of bank switching against something that works but feels almost as cumbersome as bank switching. That said, I'm (again) only looking at this from my narrow point of view of someone wanting an MPU that is as nice to program in as possible in an assembly-level environment. In point of fact, I only added a bank switching mechanism to my CPLD because I had some spare 512Kx8 SRAMs around the place and I wanted to see if I could implement bank switching myself (it was trivial to do, of course).

Also, I'm only doing this for my own benefit, so the fact that the 6809 isn't being made doesn't really bother me. Typical hobbyist mentality I guess. :)

Quote:
I wonder why you hesitate, given that use of the 6309's new registers is optional, but the 6809's obscene power consumption is mandatory. BTW another major advantage of 6309 over 6809 is faster execution due to fewer dead bus cycles.


Couple of negatives against the 6309 for me:

  • My preferred cross-assembler (ASxxx) does not support it.
  • Documentation seems a bit sketchy, or at least I'm not able to find a totally comprehensive doc similar to the Motorola programming guide for the 6809.
  • The additions diminish some of the "orthogonal-ness" of the 6809, which I find jarring.
  • I'm now pretty much on a first-name basis with the 6809 and know it rather well, and don't want to learn something new whilst there's more interesting things to be doing (like writing Aslak-OS...)

But in any case, I've ordered a couple so I can play around. :)

_________________
8 bit fun and games: https://www.aslak.net/


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 05, 2014 3:59 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
Quote:
can you specify a full address as an immediate value [...] does the segment register roll over on index register overflow
Haven't familiarized yourself with '816 memory addressing? No problem; there's always another learning curve! :) This one's intriguing & enjoyable, IMO.

Don't make the mistake of supposing the 6502 -> 65816 memory-addressing upgrade mirrors the 8080 -> 8086 memory-addressing upgrade. Notably, the '816 scheme is not well characterized by the word "segmentation" (although I don't wish to debate terminology). Study it and you will see what it can do. BTW the answer to both of your questions above is yes. (The bank address register doesn't roll over, but you can index into the next bank, which I think is what you meant.)

Quote:
I'm not actually dealing with large datasets in my computer. At least, not yet.
Fair enough. But when you get there -- and actually start coding -- I think you'll find where the bottleneck lies with sub-64K "window" schemes. IE: to accept and deploy a full, linear address you're obliged to do a lot of masking and shifting -- because the size of your window is 16K or whatever rather than 64K. If that's unclear, just take a moment and try writing a "simple" code snippet to do nothing more than accept a full, linear address and fetch a byte from that address. (I mean using a window of 16K for example.)

The '816 can accept and deploy a full, linear address "as is" -- without masking & shifting and the drastic speed hit that results. Even the 8086 segmented model beats a sub-64K windowing scheme in regard to treating the entire memory as a linear space. And a linear space is what you want if the application will be doing lots of pointer arithmetic at run-time (as when large data structures are to be navigated).

all for now... Congrats on the 6309 purchase -- I'm envious! :D

Jeff

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 05, 2014 4:39 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
Aslak3 wrote:
eg. can you specify a full address as an immediate value, or do you have to load the segment register separately, does the segment register roll over on index register overflow etc.

  • There are also the long addressing modes, so that without separately setting the program bank, you can do long jumps and subroutine calls, including indirects. (For indirects, the 3-byte address is held at the location specified by the instruction.)

  • Absolute indexed addressing modes can transparently take you across bank boundaries for the one instruction, so an array of any length up to 64K can straddle a bank boundary.

  • Long jumps and subroutine calls and returns change the program bank automatically. Long data references do not change the data bank; so at any time, you can reach for data in another bank, and subsequent absolute data references will still be in the old intended bank.

  • The stack is always in bank 0, and the stack pointer is 16-bit.

  • The direct page is always in bank 0. It is like zero page but it can be moved around, and does not have to start on a page boundary. Each task can have its own direct page.

The two bank registers are, in a sense, two more index registers, with their low 16 bits are locked to 0000, and if you look at it that way, you can have double indexing, with one level of indexing being to specify where a particular task's work space is. The direct-page register is similar in that regard, as noted above. My only '816 experience is actually with the '802; but although I can long for the 65Org32, the key with the 816's banking appears to be to make it work for you rather than against you. It is an absolutely marvelous design compared to the paging and banking problems I've had to deal with the PIC microcontrollers I've been working with.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 05, 2014 9:56 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8390
Location: Midwestern USA
Aslak3 wrote:
While segmenting memory lets you do things you can't do with bank switching, it is still (IMO) a horrible kludge.

As pointed out by Jeff, the 65C816 memory scheme is not segmentation like what Intel 8088/8086 programmers deal with. Also as pointed out by Jeff and Garth, one can "seamlessly" index across 64KB (bank) boundaries or use [<dp>] addressing to read or write anywhere in the contiguous memory space. It's entirely possible to never touch DB (data bank register) and use 24 bit addresses or DP indirect addressing to cover the full range of memory.

The one real advantage to setting DB to a working bank is that all addressing can be reduced to 16 bits, which is slightly faster than 24 bits. It's a judgment call that one develops from experience.

Quote:
...can you specify a full address as an immediate value, or do you have to load the segment register separately, does the segment register roll over on index register overflow etc.

There is no "segment register." There is DB, the data bank register, and PB the program bank register. PB matters when the '816 is fetching an opcode or an operand. DB matters when the '816 is fetching or storing anything other than an opcode or operand and 16 bit addressing is in use. DB, as I mentioned, can be "ignored" by using 24 bit addressing, by indexing across a 64KB boundary or both. A short code example explains the "both" scenario, which is the most flexible of the three possibilities:

Code:
         sep #%00110000        ;8 bit registers
         ldx #<$00fffe         ;LSB of a 16 bit address
         ldy #>$00fffe         ;MSB of a 16 bit address
         lda #^$00fffe         ;BSB ($00)
         stx dpptr             ;set up 24 bit...
         sty dpptr+1           ;address in...
         sta dpptr+2           ;direct page
         lda #$ab              ;set DB to bank $AB...
         pha                   ;which will be...
         plb                   ;the default bank
         rep #%00010000        ;16 bit index registers
         ldy #0000             ;16 bit index value
         lda [dpptr],y         ;reads from $00FFFE, not $ABFFFE
         ldy #0002             ;new 16 bit index value
         sta [dpptr],y         ;writes to $010000, not to $AC0000

Note that the last instruction, while indexing into bank $01, does not affect DB at all. The programmer can set a new bank for read/write access by writing a different value into DPPTR+2 and not touch DB.

As Garth mentioned, all direct page and stack accesses are in bank $00, regardless of the value in DB. Also, when the '816 handles an interrupt it pushes PB, along with PC and SR, and then loads PB with $00, which means the front ends of all five interrupt service routines must be in bank $00. However, the ISRs can execute in some other bank by using JML to go to the non-bank $00 address.

It's not at all complicated.

Quote:
In summary, I prefer the simplicity of bank switching against something that works but feels almost as cumbersome as bank switching.

Bank switching is inherently not simple for the eight bit assembly language programmer because a bank is of necessity some fraction of the 16 bit address space supported by the MPU. The '816 makes it easier to touch the entire address space because no specialized logic is required.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 09, 2014 6:25 am 
Offline
User avatar

Joined: Sun Dec 29, 2002 8:56 pm
Posts: 452
Location: Canada
Sounds like it's time to use indirect addressing in calling functions. Call the input/output routines indirectly through pointers. And use the OPEN command to set the pointers to the appropriate routines.
As BDD mentioned, OPEN/CLOSE/READ/WRITE are the basic I/O functions.

The character output routine would look like:

OUTCHAR:
JMP ($outvec)

Where outvec points to the desired routine, set by the OPEN subroutine.

I'd use fixed constants to represent the devices as another poster mentioned.
My own enhanced 6809 core, which has 32 bit addressing, via prefix instructions isn't quite as far along software wise; but there is a set of simple ROM routines on my github account.

_________________
http://www.finitron.ca


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 09, 2014 3:01 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
BigDumbDinosaur wrote:
Bank switching is inherently not simple for the eight bit assembly language programmer because a bank is of necessity some fraction of the 16 bit address space supported by the MPU. [emphasis added]
Earlier I referred to the same difficulty BDD mentioned. Ideally you want to treat the larger address space as one linear continuum, but there's no efficient way to do that with an odd-sized window such as 16K. Try writing a "simple" code snippet to accept a full, linear address and fetch a byte from that address and you'll see.

But it's incorrect to say "a bank is of necessity some fraction of the 16 bit address space," although I agree almost every memory-expansion scheme out there is designed as if this were true. Exceptions exist which use banks that are a full 64K in size. These include

  • the '816 (of course)
  • the MOS 6509 -- which needlessly spoils the deal by locating 2 bytes of IO at 0000 of every bank -- and... (ahem)
  • the KimKlone [/plug] :mrgreen:

Aslak3, it might be fairly doable to implement 64K banks for your 6809/6309 memory expansion as well. I can elaborate if the idea appeals to you.

cheers,
Jeff

ps to Rob: "enhanced 6809 core, which has 32 bit addressing"? :shock: Whoa! 8)

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 09, 2014 9:24 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8390
Location: Midwestern USA
Dr Jefyll wrote:
But it's incorrect to say "a bank is of necessity some fraction of the 16 bit address space,"..

I, of course, was referring only to MPUs with a 16 bit address bus and no hardware support for bits 16-23.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 09, 2014 11:38 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
To clarify, the KK uses the 65C02 -- an MPU with a 16 bit address bus and no hardware support for bits 16-23. I added my own external memory-expansion hardware, as many others have done, but was able to avoid adopting a bank size that's some fraction of the MPU's 16 bit address space.

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 17 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: Google [Bot] and 15 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: