View unanswered posts | View active topics It is currently Thu Mar 28, 2024 6:42 pm



Reply to topic  [ 775 posts ]  Go to page Previous  1 ... 26, 27, 28, 29, 30, 31, 32 ... 52  Next
 Thor Core / FT64 
Author Message

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
A couple of minor errors run into and fixed tonight, neither affected running software.

Shift immediate instructions were decoding the wrong bit as part of the immediate value. This resulted in shifts of more than 32 bits shifting by 32 less than they should. Impact minimal.
The SLTU instruction was encoded with the wrong opcode by the assembler. It was using the SLT opcode. Impact minimal.

There is something funny going on with the board I think. I get the 2 second startup delay routine running but it no longer lights the LEDS to indicate the run. LEDS do light up at a later point. So it's not an external physical connection problem.

I'm trying to get drawing happening for the frame buffer display. I think the frame buffer is working as it appears to be able to dump random data from memory. But I want to confirm it by having known data dumped from memory. I plan on posting the frame buffer with photos or a video of a demo running.

_________________
Robert Finch http://www.finitron.ca


Sat Apr 13, 2019 5:27 am
Profile WWW
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
Hi robfinch,

I joined the forums very recently and your thread is already very long to be able to fully read it in a short time, so you have surely covered what I'm going to ask. I understand that your Instruction Set Architecture is variable length and essentially CISC like. It also covers complex multi-cycle instructions such as character string or byte array manipulation. I would also assume that the set is quite (if not totally) orthogonal, and covers a fair range of several length data types. This all remains me the VAX-11 instruction set architecture, which I loved with passion back in the mid 80's, although of course your Thore Core is a different beast incorporating may features that were not possible 30-35 years ago.

However, after reading the first pages of this thread, I am unable to find a post describing your instruction set. I'm sure you must have eventually posted it, but as said, I'm new on the forums and I need some time to read it all. So in the mean time any quick pointers would be appreciated. My background is software development and I have developed complex multithreaded applications in the past, as well as some basic compilers, so I fully understand them and what's involved, but I never met hardware design until very recently, so there are some aspects of your build that I currently can only understand partially. Nevertheless, I am interested to know which assembler and compiler are you using. Sorry, if I am asking a bit off topic or an already covered question.

Thanks!


Sat Apr 13, 2019 6:45 am
Profile

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
It sounds like you have hit upon the original Thor core. I’ve stopped working on it in favor of FT64. The latest version is vastly different and more resembles something like a MIPS or ARM core.
A lot of “features” have been dropped from the original Thor core including code address registers, predication, segmentation and string instructions. The core now only supports three different length instructions (16,32 or 48-bit).
Given the number of operations I would hardly call it a RiSC processor, but it does borrow some from RiSCV. Most of the instructions are single cycle (or less). A couple of exceptions are multiply and divide. One notable oddity is that there is a push instruction but no corresponding pop instruction.
I wrote a big 300+ page document describing the ISA among other things.
http://github.com/robfinch/Cores/blob/m ... T64v7.docx
Posted below are some charts and opcode maps of the ISA which are a bit out of date. While the original ISA had string instructions, they are not supported in the v7 ISA. Except for bitfield instructions which are 48-bit formats, instructions are basically 32-bit. There are compressed 16-bit instructions that expand into equivalent 32-bit forms. There are 48-bit instruction forms which are identical to the 32-bit forms except that the constant field has been extended by 16-bits.
Compressed instructions are making up about 50%+ of the instruction stream.
Quote:
I am interested to know which assembler and compiler are you using.

I’m using a macro-assembler and compiler both I wrote myself. Both tools are pretty primitive. The assembler is multi-pass and is taking about 20 passes to assemble everything (because of the number of files included). It keeps resolving more and more phase errors in each pass. The assembler is a bit on the sluggish side because it was simply written to begin with and uses a giant switch statement rather than a jump table for processing opcodes. It also doesn’t tokenize the source for subsequent processing. So, lots of room for improvement. There’s roughly 120,000 lines of assembly to assemble, so it takes the assembler a few minutes.
The compiler is two-pass and does do some basic optimizations. It supports some C++ features (classes, method overloading, but not operator overloading) but I’ve not used it that way so I don’t know how well they work. I just started working on garbage collection associated with the compiler. I hope to have system managed garbage collection with compiler support.
http://github.com/robfinch/Cores/blob/m ... 4/doc/CC64 Language Reference.docx
Attachment:
File comment: FT64v7 Compressed Instructions
FT64v7 Compressed Instruction Formats.png
FT64v7 Compressed Instruction Formats.png [ 46.53 KiB | Viewed 6491 times ]

Attachment:
File comment: FT64v7 Instruction Formats
FT64v7 Instruction Formats.png
FT64v7 Instruction Formats.png [ 98.69 KiB | Viewed 6491 times ]

Attachment:
File comment: FT64v7 Opcode tables
FT64v7 Opcode Tables.pdf [85.48 KiB]
Downloaded 279 times

_________________
Robert Finch http://www.finitron.ca


Sun Apr 14, 2019 2:31 am
Profile WWW

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
Got the LEDS displaying again. Updated the documentation for the multi-port memory controller. The routine to draw color bars onscreen is hanging while doing a store to dram. There appears to be no ack signal coming back from the dram controller. So, I dumped the chip select and ack signals for the cpu and bitmap controller. And I note that it should be possible to get a higher resolution display out of the controller, if I were willing to double the size of the read cache in the memory controller.

Eliminated a state from the sprite controller’s state machine.

I decided to add an ack timeout to the memory controller. This is a bit of a kludge, but it will keep the system from hanging waiting for the memory controller. The ack was being tested only to ensure that the memory controller didn’t transition to the IDLE state too soon. The same thing can be accomplished using a short timer.
I got past that ack issue and ran into another one. This time instruction cache load stalled in the load state, probably waiting for an ack again. I wonder if ack pulses are coming through a bit too narrow to be picked up by the clock edge.

The following pic shows dram access timing.
Attachment:
File comment: FT64SoC Memory Timing
MemoryTiming.png
MemoryTiming.png [ 208.54 KiB | Viewed 6475 times ]

_________________
Robert Finch http://www.finitron.ca


Mon Apr 15, 2019 3:10 am
Profile WWW

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
Moved some of the signals in the memory controller to the mem_ui_clk domain from the 40MHz clock domain. Most of the memory controller now runs at 100MHz. Some additional registration of signals and pipelining was required. The workload distribution per clock was altered slightly. The goal is to improve the timing in the memory controller, several signals were not meeting timing requirements. The number of address reservations the controller supports was made a parameter. The size of the read cache for the frame buffer channel was doubled in size (it’s now 128B).

The memory bandwidth available to the frame buffer is measured at approximately 180MB/s. This is about 1/4 the max theoretical bandwidth of the boards ddr3 ram. It should be enough to support 800x600x32bpp mode.

Managed to improve the compiler output slightly. It used to output a constant indexing operation as a load of a temporary register followed by an register indirect with displacement address mode. Now it simply outputs the two constants with an addition between them. This removes a temporary register from use and shortens code.
Old:
Code:
ldi   $t1,#123
lw   $t2,myvar[$t1]

Newer:
Code:
lw   $t2,myvar+123

The Operand compiler type was modified to support references to two expression nodes.

_________________
Robert Finch http://www.finitron.ca


Tue Apr 16, 2019 3:43 am
Profile WWW

Joined: Wed Jan 09, 2013 6:54 pm
Posts: 1780
robfinch wrote:
The memory bandwidth available to the frame buffer is measured at approximately 180MB/s. This is about 1/4 the max theoretical bandwidth of the boards ddr3 ram. It should be enough to support 800x600x32bpp mode.

By this, do you mean that the CPU uses 3/4 of the bandwidth and the frame buffer is left with 1/4??


Tue Apr 16, 2019 5:34 am
Profile

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
Quote:
By this, do you mean that the CPU uses 3/4 of the bandwidth and the frame buffer is left with 1/4??

OMG. Memory is faster than the system.

Although I wish it were possible, the cpu just isn’t up to running that fast. The maximum memory bandwidth available from the DDR3 memory is just under 800MB/s. 400MHz clock x 16-bit transfers. Under 800MB/s because it takes about 20 clocks to initiate a transfer, after that data can be streamed. The trick is to stream as much data as possible. I’m not sure what the realistically available bandwidth is, but I was not counting on anywhere near the max as the system does a lot of random access. Maybe 50% of max is more realistic.

The cpu is running only at 20MHz so doesn’t use a ton of bandwidth (maybe about 50MB/s depending on the instruction mix). It also has a dedicated memory bus to the boot rom which transfers instructions to the cache at a rate of 160MB/s. Other controllers can freely access the dram while the cpu is doing an instruction cache load from the rom. There are other devices in the system also making use of the ram (graphics controller, sprite controller, audio, ethernet, sd card.). I suspect the graphics controller would consume a lot of bandwidth when drawing.
If all the devices were to access the read caches at the same time, and all the channels were used, it’d be about 2.5GB/s bandwidth in use. The 64-bit bus width really boosts the numbers for the transfer rates.

I plan on getting the cpu to run a bit faster once it’s working. It’s not likely to break 40MHz on the current board.

I managed to get the compiler to eliminate the return block code for small routines that don’t use the stack. This reduced the size of many small routines to just a few lines of code.

_________________
Robert Finch http://www.finitron.ca


Wed Apr 17, 2019 3:59 am
Profile WWW

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
For shifts, the second source operand valid bit was being set incorrectly. Most of the time this caused a shift immediate instruction to wait until the register specified by the ‘B’ register field was valid. Since the ‘B’ register is not used by immediate shifts this would be a performance issue of waiting unnecessarily. The error was a hold over from a different decoding of shift instructions. The decoding change got missed when shift instructions were switched to a new format.
Something is amiss in the following function which waits for approximately 10ms. The loop never terminates. This is used by the keyboard processing routines, so they are locking up. The neat thing is this loop was working and has suddenly stopped working with the last system build. It’s strange because there weren’t any changes to the processor in the system build.
Code:
                           _Wait10ms:
FFFFFFFFFFFC0E16 C3 30                                push   r3
FFFFFFFFFFFC0E18 C4 30                              push  r4
FFFFFFFFFFFC0E1A 05 60 08 00                        csrrd   r3,#$002,r0      ; get orginal count
                           .0001:
FFFFFFFFFFFC0E1E 05 80 08 00                          csrrd   r4,#$002,r0
FFFFFFFFFFFC0E22 BA 40                                sub      r4,r4,r3
FFFFFFFFFFFC0E24 30 44 82 00                          blt     r4,r0,.0002         ; shouldn't be -ve unless counter overflowed
FFFFFFFFFFFC0E28 46 84 80 1A 06 00                    slt      r4,r4,#100000      ; about 10ms at 10 MHz
FFFFFFFFFFFC0E2E 84 FC                                bne      r4,r0,.0001
                           .0002:
FFFFFFFFFFFC0E30 C4 50                                lw      r4,[sp]
FFFFFFFFFFFC0E32 E3 50                                lw      r3,8[sp]
FFFFFFFFFFFC0E34 80 21                                ret      #16

Function unit bypassing has been reconfigured to be active. This has been disabled in order to reduce the size of the core and make for shorter build times. The cost is about 15 to 30% in performance if bypassing is disabled.
I spent quite a bit of time fixing a bug due to a misnamed signal. I had the following line loading sprite image data, which caused a blank image.
Code:
   m_spriteBmp[spriteno] <= dat_i;

It should have been:
Code:
   m_spriteBmp[spriteno] <= m_dat_i;

The difference is that dat_i is an undefined signal so Verilog defaults it to zero, hence the blank image. m_dat_i is the signal that is actually connected between memory and the sprite controller.
Sure, the synthesizer spits out a warning. But there’s about 5,000 warnings in the project and I don’t have time to go through them all every build. Most of the warnings can be ignored.

_________________
Robert Finch http://www.finitron.ca


Thu Apr 18, 2019 3:05 am
Profile WWW

Joined: Wed Jan 09, 2013 6:54 pm
Posts: 1780
robfinch wrote:
Sure, the synthesizer spits out a warning. But there’s about 5,000 warnings in the project and I don’t have time to go through them all every build. Most of the warnings can be ignored.


In my working life, when we had a situation where we couldn't get rid of all warnings, we would instead have a script which filtered out known warnings - any new warnings we could then treat as errors, and either fix them or add them to the filter.


Thu Apr 18, 2019 5:07 am
Profile
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
I am not familiarised with FPGAs (or to be honest, I don't know anything about them), but I'm kind of surprised that such many warnings can not be removed by small code changes or by setting warning filters on the system. This is the usual way to deal with intrusive warnings on software compilers.

Any small insight into that subject would be appreciated, such as for example what are the most common warnings that you may get?. Sorry if this question is markedly 'naive' and I appreciate your patience. Just a basic reply will suffice. Thanks in advance.


Thu Apr 18, 2019 7:21 am
Profile

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
Quote:
but I'm kind of surprised that such many warnings can not be removed by small code changes or by setting warning filters on the system. This is the usual way to deal with intrusive warnings on software compilers.
Most of the warnings have been removed by small code changes that could be, otherwise there might be 10's of thousands of warnings (there's about 300 files begin built). The toolset seems to warn about everything. But it's good because it does break things down into errors/critical warnings/warnings. I fix all the errors and critical warnings. There's warnings in vendor supplied code.
One common warning is about size mismatches. Verilog allows one to assign a shorter vector of bits to a longer one (it automatically zero extends it) Every time this is done, the compiler spits out a warning, and it's actually fairly common to do. There are other warnings about the usage of block vs distributed rams and optimal performance. Warnings about insufficient pipelining.

There are options that allow one to disable some warnings but I haven't bothered.

Reading the timing report to identify improvements is fun too. It's about 50,000 lines.

I got the assembler to output some instruction statistics (not finished yet). Mostly similar to what one would find in a textbook, with the exception of a low number of branches. I'm not sure why there's such a low percentage of branches. It could be compiler output isn't as optimized as typical, leading to more 'other' instructions.

Instruction Statistics
Loads: 7617 (20.505573%)
Stores: 5644 (15.194099%)
Branches: 4128 (11.112906%)
Calls: 1837 (4.945351%)
Returns: 595 (1.601788%)
Adds: 5375 (14.469929%)
Luis: 237 (0.638023%)
Moves: 3851 (10.367200%)
CMoves: 50 (0.134604%)
Sets: 263 (0.708017%)
Total: 37146

_________________
Robert Finch http://www.finitron.ca


Thu Apr 18, 2019 8:02 am
Profile WWW

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
Changed the decoding for the EXEC instruction. This instruction had mostly empty fields but was assigned an opcode at the root level. It has now been re-assigned to an R1 type opcode. This freed up a field at the root level for the SEQ instruction.

Added the SEQ instruction to the ISA. It turns out that the ‘==’ operator was implemented incorrectly using a combination of XNOR and REDOR. It should have been a REDAND operation. Since REDAND isn’t in the instruction set, the easiest thing to do was to simply include SEQ.

Got rid of the immediate form of the XNOR instruction. It wasn’t being used anywhere once SEQ was added.

I thought I would be clever and modify the compiler to evaluate multiple subexpressions in logical ‘and’ and ‘or’ expressions, if the number of instructions in the subexpression isn’t too great. The idea was to get rid of branches by using logic operations. Even though the subexpression might not be needed, code would be generated for it, because it would be faster to execute the code and throw away the results than to include a branch operation.

Well I thought about it some more and realized it can’t be done due to the way ‘C’ handles logical expressions. Consider the expression: if (ptr && *ptr==’a’). It might not be safe to execute the *ptr==’a’ part of the expression because ptr could be null. So, a branch around that part of the expression is needed. Faster code could be generated if the compiler knew the expression was safe to optimize, so I’ve been thinking about how to indicate that to the compiler. My thought was to introduce new logical operators that indicate safe expressions. Perhaps ‘&&&’ and ‘|||’ for when one really wants fast code.

One optimization that the compiler could do to reduce the number of branches, is to use a conditional move instruction when the ternary operator is implemented. If the number of instructions required to evaluate an operand is small, then the operand is computed even though it might not be used. The compiler only does this if it determines that there is no function call in the list of instructions for the operand. The called function has an unknown number of lines of code to it, and it may contain calls for I/O devices. Unnecessarily reading an I/O device can be bad. Normally C does not allow evaluating both source operands, instead it branches to select one of the operands.

I had the compiler generating this code for the hook operator. The issue is that it could be unsafe.
Code:
                           ;       putch(sign=='-' ? ' ' : padchar);
FFFFFFFFFFFD0EAC 0B D0 B4 00                                seq         $t1,$r16,#45
FFFFFFFFFFFD0EB0 04 E0 80 00                                ldi         $t2,#32
FFFFFFFFFFFD0EB4 20 1E E5 00                                lc          $t3,56[$fp]
FFFFFFFFFFFD0EB8 42 A6 1C 04 00 A4                          cmovenz     $t0,$t1,$t2,$t3

_________________
Robert Finch http://www.finitron.ca


Fri Apr 19, 2019 3:12 am
Profile WWW
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
Hi Rob,

What you explain is one of the classic issues of logical expression compilation. The C language specification clearly states that logical expressions will be executed from left to right and that any partial result cancelling the right hand side of the expression will short circuit any further evaluation of it. Your example code
Code:
if (ptr && *ptr=='a')
is the most flagrant example that a correct compiler must follow that rule, to prevent that sentence to crash when ptr is zero.

Unfortunately, the strict following of the rule requires the generation of branching code in most cases, which may significantly slow down execution if the processor is heavily pipelined. So modern compilers usually try to avoid the mentioned rule if the evaluation of the right hand side will not cause any undesired side effects or will affect performance significantly.

The simplest approach is to always follow the short-circuit rule if there are assignations or function calls on the right side, and decide whether it's safe or optimal to break the rule in the remaining cases. The later is trickier than it may appear because even sub expressions with no assignments or function calls can create side effects. For example, let's consider that the compiler has exception throwing enabled for division by zero and you place a division on the right side. This may crash the program. Or let's suppose that the compiler keeps track of status register changes for code optimisation, and you place an addition on the right side. The status register values after sentence evaluation may not be the same if the right side is actually executed or if it is short-circuited. Or let's suppose you have an array access on the right side, the index may go out of bounds if it's executed. These are just a few additional examples to the ones already mentioned, but the possible problematic cases do not end here.

The same issues also apply to the conditional ternary operator, so I think you can't really make a difference. Many earlier compilers, specially the ones aimed at architectures where branching was not that expensive, just would religiously adhere to the short-circuit rule in all cases in order to prevent any issues. If I had to build my own compiler, I think I would just adhere to the rule at all times, as this is a more difficult aspect to optimise than it appears.


Fri Apr 19, 2019 9:18 am
Profile

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
FT64 compiler will be following the short-circuit rule to remain 'C' compatible and for safe compiles.

I thought about an intermediate representation for the compiler and came up with a non-human readable one that serializes compiler objects as hex codes. I had also thought of using XML. The compiler currently manipulates the list of instructions passed to the peephole optimizer to perform some optimizations. The trick will be to convert things into an IR while keeping the compiler functioning.

Some more instruction statistics:
Code:
Instruction Statistics
Loads:      8160 (21.729289%)
Stores:     5831 (15.527388%)
Pushes:     2225 (5.924959%)
Branches:   4265 (11.357282%)
Calls:        1838 (4.894416%)
Returns:      598 (1.592416%)
Adds:         6473 (17.236972%)
Ands:          396 (1.054510%)
Ors:          430 (1.145048%)
Xors:          287 (0.764253%)
Lshifts:     648 (1.725561%)
shifts:        215 (0.572524%)
Luis:          237 (0.631108%)
Moves:       4034 (10.742151%)
CMoves:          0 (0.000000%)
Sets:          231 (0.615131%)
Floatops:    362 (0.963971%)
others:     1323 (3.523021%)
Total:     37553

number of bytes: 110676.000000
number of instructions: 37553
number of compressed instructions: 22584
2.947195 bytes (23 bits) per instruction
Compression ratio: 28.982829%
Number of long branches: 0

Loads, stores and pushes account for over 40% of instructions. The long branch format (27 bit displacement vs 11 bit) may be eliminated, it doesn't seem too useful.
Sticking to the short-circuit rule eliminated the need for conditional moves, the only place the compiler used one. Conditional moves may also be eliminated.

_________________
Robert Finch http://www.finitron.ca


Sat Apr 20, 2019 3:10 am
Profile WWW

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
Played around with string literals handled by the compiler today. Allowing the size of the character to be specified by prepending the string constant with ‘B’ for byte (8-bit), ‘C’ for char (16-bit), ‘H’ for half (32-bit), or ‘W’ for word (64-bit). The default is to use 16-bit characters.
For example,
Code:
B”? = display help”
encodes the string as byte characters
However, in-lined string constants are always 16-bit characters.

Fixed up the _Stroul function of the C standard library to support 64-bit integers.

Added the ‘|||’, ‘&&&’, and ‘??’ safe operators to the language. These operators inform the compiler that it’s safe to optimize to use operators rather than branches. They shouldn’t break existing ‘C’ programs, although ‘&&&’ might have issues.
Added a ‘redor’ optimization to remove extra reduction ors if the value is known to already be a Boolean value.

With safe operators, no branches in the following code. In this case we know the ternary operation is safe because it’s all character constants.
Code:
                           ;          buf[count] = ch=='0' ?? 0 : ch=='1' ?? 1 : '\0';

FFFFFFFFFFFDA7D8 02 AB 04 3C                                shl         $t0,$r11,#1
FFFFFFFFFFFDA7DC 0B EC C0 00                                seq         $t2,$r12,#48
FFFFFFFFFFFDA7E0 88 10                                      ldi         $t3,#0
FFFFFFFFFFFDA7E2 0B 4C C5 00                                seq         $t5,$r12,#49
FFFFFFFFFFFDA7E6 AB 10                                      ldi         $t6,#1
FFFFFFFFFFFDA7E8 42 2A 2D 00 00 A4                          cmovenz     $t4,$t5,$t6,$r0
FFFFFFFFFFFDA7EE 42 C7 A0 04 00 A4                          cmovenz     $t1,$t2,$t3,$t4
FFFFFFFFFFFDA7F4 16 0E 98 92                                sc          $t1,[$r14+$t0]

Code:
                           xstoul_67:

                           ;    sign = *sc == '-' ||| *sc == '+' ?? *sc++ : '+';

FFFFFFFFFFFCB4B8 20 0B 05 00                                lc          $t3,[$r11]
FFFFFFFFFFFCB4BC 0B E8 B4 00                                seq         $t2,$t3,#45
FFFFFFFFFFFCB4C0 20 2B 05 00                                lc          $t4,[$r11]
FFFFFFFFFFFCB4C4 0B 09 AD 00                                seq         $t3,$t4,#43
FFFFFFFFFFFCB4C8 02 C7 A0 25                                or          $t1,$t2,$t3
FFFFFFFFFFFCB4CC 8B 01                                      add         $r11,$r11,#2
FFFFFFFFFFFCB4CE 20 EB 04 00                                lc          $t2,[$r11]
FFFFFFFFFFFCB4D2 42 A6 9C 15 00 A6                          cmovenz     $t0,$t1,$t2,#43
FFFFFFFFFFFCB4D8 24 BE 17 FF                                sc          $t0,-36[$fp]

_________________
Robert Finch http://www.finitron.ca


Sun Apr 21, 2019 3:16 am
Profile WWW
Display posts from previous:  Sort by  
Reply to topic   [ 775 posts ]  Go to page Previous  1 ... 26, 27, 28, 29, 30, 31, 32 ... 52  Next

Who is online

Users browsing this forum: No registered users and 7 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:  
cron
Powered by phpBB® Forum Software © phpBB Group
Designed by ST Software