Variable vs. Signal on indexing

Andy wrote:
When I first started flirting with variables in VHDL synthesis,
understanding how they mapped to hardware took some time. OTOH, when I
first started coding VHDL at all, understanding the non-sequential
nature of signal assignment statements in sequential code took a while
too.
Yes, it is a big hill to climb in either case.
But once on top of the "variable" hill,
I can write a sequential description
that is easy for me to understand a year from now.

Let say I want to describe a phase accumulator
in the traditional manner.
With a mult-process design, I might
say something like:

accum_s <= '0' & accum_s(accum_s'length - 2 downto 0)
+ ('0' & addend_c);

in one process and pick up the
output msb in another process:

msb <= accum_s(accum_s'length - 1);

Thats not too bad, but it's pretty easy
to get a count or length off by one.

I prefer a single process description
like this that does exactly the same thing.
...
accum_v := accum_v + addend_c; -- add magic number
msb_v := accum_v(accum_v'length-1); -- save the carry bit
accum_v(accum_v'length-1) := '0'; -- clear carry for next time
...
-- Now use msb_v however I like down here ...

To me, there is no comparison.
It's like calculus vs Laplace transforms.


-- Mike Treseler
 
Jim Lewis wrote:

Sure. You and Mike ready to take it up? These things
take volunteers.
Send me an email.

I could free up an hour a week, but I can't
tinker with my production tools setup.

Everything I do is already covered by
VHDL-93 and I am not interested in
trying to convince a vendor how to run
his business.

I would be interested in documenting
synthesis techniques that already work
on my tools, but I am not sure this would
have any effect on the 2004 revision.

-- Mike Treseler
 
Jim Lewis wrote:
Sure. You and Mike ready to take it up? These things
take volunteers. The 2004 revision needs to be re-anlayzed
with this in mind to see if it is missing or if it is there
and I missed seeing it. There is also some need for adding
more attributes and perhaps renaming fsm_complete (particularly
if no-one has implemented it) to fsm_safe (as that is what it
does).
OK, I see your point, but did you see mine? If the majority of
synthesis vendors are already coming to a common ground, is there a
need for a published standard, or to restrict oneself to it? If the
value in synthesis development is in supporting new features and coding
styles, should we wait for standards to be written embracing them
before we ask tool vendors to implement them? The synthesis market for
FPGA's is very competitive, and one vendor is not about to let another
have a valuable feature all to themselves for very long, which keeps
the fire stoked for continuous innovation to provide, however fleeting,
some kind of market advantage. Standardization would do nothing for
this type of innovation except provide a common framework to document
what is already being done.

As for this particular issue (signal assignments from expressions of
variables after the clocked clause), synthesis vendors have just
realized that this is another simulation behavior that is relatively
easy to implement in hardware, so they've done it. The movement away
from "templates" has fostered this innovation by looking at synthesis
in a whole new light: implementing behavior instead of structure.

Also, not the variables themselves, but _references_ to them imply
storage or not (and thus register or not); the same variable name may
represent a combinatorial value in one reference, and a register value
in another.
Obvious to the quick and the sharp. Subtle to new users
who are overwhelmed by other pieces of the design and
design flow.
I say it is overwhelming because we are still trying to teach new users
"this is how you code a register; this is how you code a gate". It's
like starting out teaching a new C coder how to create a piece of
assembler code by writing C.

Except for specialized cases of synchronization and driving causal
inputs, does it really matter where the flops are and where the gates
are, as long as they are spread out enough to meet timing (automated by
many tools already), and the functional and latency (in terms of clock
cycles) requirements are met? Specifically, if re-timing is used, why
worry about register placement in the circuit, since it will all get
rearranged any way? And if you don't have to worry about it, why not
code in a style that frees you from having to worry about it?

And the difference can even be dynamically controlled (e.g.
a prior write may be conditional).
Exciting :). Sometimes registered, sometimes combo logic.
A brave new world :) And much more difficult to do without variables.
Just don't think of it in terms of registers or combo, think of it in
terms of delayed or not (by a clock cycle).

Andy
 
Mike Treseler wrote:
Andy wrote:
When I first started flirting with variables in VHDL synthesis,
understanding how they mapped to hardware took some time. OTOH, when I
first started coding VHDL at all, understanding the non-sequential
nature of signal assignment statements in sequential code took a while
too.

Yes, it is a big hill to climb in either case.
But once on top of the "variable" hill,
I can write a sequential description
that is easy for me to understand a year from now.

Let say I want to describe a phase accumulator
in the traditional manner.
With a mult-process design, I might
say something like:

accum_s <= '0' & accum_s(accum_s'length - 2 downto 0)
+ ('0' & addend_c);

in one process and pick up the
output msb in another process:

msb <= accum_s(accum_s'length - 1);

Thats not too bad, but it's pretty easy
to get a count or length off by one.
It is very clear to me and I can visualize the underlying circuit.

I prefer a single process description
like this that does exactly the same thing.
...
accum_v := accum_v + addend_c; -- add magic number
msb_v := accum_v(accum_v'length-1); -- save the carry bit
accum_v(accum_v'length-1) := '0'; -- clear carry for next time
...
-- Now use msb_v however I like down here ...
It is fine if this is a C segment. For RTL hardware description, I
can't relate the code to the circuit and must rely on the black magic
of synthesis software.

To me, there is no comparison.
It's like calculus vs Laplace transforms.


-- Mike Treseler
To me, there is also no comparison. "Black magic" is never my
preference.

Mike G.
 
mikegurche@yahoo.com wrote:

It is fine if this is a C segment.
Fine as in understandable?

For RTL hardware description, I
can't relate the code to the circuit
One way to do this is to click up the RTL viewer.

and must rely on the black magic
of synthesis software.
It's all LUTs and flops.
We rely on synthesis to get it right in either case.
My point is that synthesis *does* get it right.



-- Mike Treseler
 
Mike Treseler wrote:
mikegurche@yahoo.com wrote:

It is fine if this is a C segment.

Fine as in understandable?

For RTL hardware description, I
can't relate the code to the circuit

One way to do this is to click up the RTL viewer.

and must rely on the black magic
of synthesis software.

It's all LUTs and flops.
We rely on synthesis to get it right in either case.
My point is that synthesis *does* get it right.



-- Mike Treseler
You have more faith on synthesis software than I do :)

Mike G.
 
Jonathan Bromley wrote:
On 18 Jan 2007 07:47:25 -0800, "jandecaluwe"
jandecaluwe@gmail.com> wrote:

hardware designers have a tendency to label signals as
"basic" and variables as "advanced". It should really be the
other way around. Odd, isn'it?

Yes; but then, many things about simulation and synthesis
are odd.

For example: it's odd that, in a discrete-event simulator,
all the simulator's time is spent processing activity that
occurs in zero simulated time; the passage of simulated
time costs the simulator essentially nothing (merely
replacing the current value of NOW with the value
at which the next event occurs).

Given the usual RTL paradigm, the relationship between
VHDL signals and the hardware they represent can be
explained rather easily. The relationship between VHDL
variables and the hardware they represent (or don't!)
is much subtler.

It's difficult to get hardware folk to think in terms of
functionality, when they (quite properly) need to
spend much of their working lives worrying about
structure and timing.

I have huge sympathy for the position held by Mike
Treseler and others - that you should use variables
in fairly complicated synchronous processes to describe
the functionality you want, and let the synthesis tool
fuss about the structure - but that position works
only for people who have enough insight to be able
to envisage what hardware structures they're describing,
without actually laying-out the gates and flops. For
beginners, and traditional paper-and-pencil designers,
it's tough.
--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.

I write codes in both VHDL and Verilog. The variable and signal in a
VHDL process roughly translate to the non-blocking and blocking
statements in Verilog. Almost all Verilog coding guidelines recommend
not to mix the non-blocking and blocking statements. It will be much
easier to convert codes between the two languages if VHDL code follows
the same guideline.

Anyone wish to comment from this perspective?

Mike G.
 
mikegurche@yahoo.com wrote:

I write codes in both VHDL and Verilog. The variable and signal in a
VHDL process roughly translate to the non-blocking and blocking
statements in Verilog. Almost all Verilog coding guidelines recommend
not to mix the non-blocking and blocking statements. It will be much
easier to convert codes between the two languages if VHDL code follows
the same guideline.
I have translated single process vhdl designs into verilog
using register variables. It works fine if you follow
the template, but it is possible to create races
in verilog that are not possible in vhdl.

Here's an example:

http://home.comcast.net/~mike_treseler/div10.v
http://home.comcast.net/~mike_treseler/div10.pdf
 
On 19 Jan 2007 15:04:52 -0800, mikegurche@yahoo.com wrote:

I write codes in both VHDL and Verilog. The variable and signal in a
VHDL process roughly translate to the non-blocking and blocking
statements in Verilog. Almost all Verilog coding guidelines recommend
not to mix the non-blocking and blocking statements.
Not mine.

There *is* such a coding guideline, and it is vital for synthesis -
so much so that just about every synthesis tool will choke if you
violate it:
** Never mix blocking and nonblocking
** assignments to the same variable.
The guidelines (and I agree, they are many) that deprecate any mixture
of blocking and nonblocking assignments in the same process
are simply wrong and should be treated with disdain - unless they
are applied to enforce a painfully low lowest-common-denominator
style on engineers you are unable or unwilling to trust.

It will be much
easier to convert codes between the two languages if VHDL code follows
the same guideline.
Wrong. It will be much easier to convert synthesisable code if
your Verilog code follows the VHDL model. In clocked processes,

** make nonblocking assignments to variables declared at the
** module level, imitating the semantics of VHDL signals;
**
** make blocking assignments to variables declared locally
** in the named begin...end block that forms the body of your
** clocked process, imitating the semantics of VHDL variables.

Not only does this make the code VHDL-friendly, it makes it
more stylish. Local variables are a good thing for isolating
and hiding local design decisions.

Anyone wish to comment from this perspective?
Yes. I wish to say, very loudly, that the extremely conservative
coding guidelines that you mentioned are absurdly restrictive.
--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 
Andy,
OK, I see your point, but did you see mine? If the majority of
synthesis vendors are already coming to a common ground, is there a
need for a published standard, or to restrict oneself to it?
Its value is more obvious when you buy IP. If they certify it
to a standard and your tool supports that standard, then it will
work.

And the difference can even be dynamically controlled (e.g.
a prior write may be conditional).
Exciting :). Sometimes registered, sometimes combo logic.

A brave new world :) And much more difficult to do without variables.
Just don't think of it in terms of registers or combo, think of it in
terms of delayed or not (by a clock cycle).
I was being facetious. In general I like keeping a tight
eye on the hardware I am creating. I have not come across
a hardware structure for which I really desired to do this.

Going further, reading your first statement, I am guessing
that you mean something like the following.

LoadEnProc : process
variable AVar : std_logic ;
begin
wait until Clk = '1' ;
if (LoadEn = '1') then
AVar := A ;
end if ;
AReg2 <= AVar ;
end process ; -- LoadEnProc

Note, the above code also exhibits some odd quirks in
register implementation that may (like the previous problem
I posted in this thread) be fixed during place and route.

Cheers,
Jim
 
Jonathan Bromley wrote:
On 19 Jan 2007 15:04:52 -0800, mikegurche@yahoo.com wrote:

I write codes in both VHDL and Verilog. The variable and signal in a
VHDL process roughly translate to the non-blocking and blocking
statements in Verilog. Almost all Verilog coding guidelines recommend
not to mix the non-blocking and blocking statements.

Not mine.

There *is* such a coding guideline, and it is vital for synthesis -
so much so that just about every synthesis tool will choke if you
violate it:
** Never mix blocking and nonblocking
** assignments to the same variable.
The guidelines (and I agree, they are many) that deprecate any mixture
of blocking and nonblocking assignments in the same process
are simply wrong and should be treated with disdain - unless they
are applied to enforce a painfully low lowest-common-denominator
style on engineers you are unable or unwilling to trust.

It will be much
easier to convert codes between the two languages if VHDL code follows
the same guideline.

Wrong. It will be much easier to convert synthesisable code if
your Verilog code follows the VHDL model. In clocked processes,

** make nonblocking assignments to variables declared at the
** module level, imitating the semantics of VHDL signals;
**
** make blocking assignments to variables declared locally
** in the named begin...end block that forms the body of your
** clocked process, imitating the semantics of VHDL variables.

Not only does this make the code VHDL-friendly, it makes it
more stylish. Local variables are a good thing for isolating
and hiding local design decisions.

Anyone wish to comment from this perspective?

Yes. I wish to say, very loudly, that the extremely conservative
coding guidelines that you mentioned are absurdly restrictive.
--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
Jonathan,

The guidelines I metioned is from

"Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill"

http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA.pdf

which is voted best paper in SNUG2000.

It will be nice if you can provide some links for other guidelines.

Mike G.
 
Jan,
Never needed to code a CRC which treated multiple bits
in a single clock cycle? How did you/would you solve that?
Same as I code any other hardware - sketch the picture,
code the picture. Much time spent understanding hardware
and picture. Little time spent coding and synthesis.

Only use variables when the situation demands it.

Cheers,
Jim
 
KJ wrote:
Actually, if you focus on correct function first and foremost I suspect
you'll be up and running faster. The 'implications' of whether you use
signals or variables are rather transparent....i.e. who cares if the
function being implemented is correct?
This much I can agree with. Like Mike G, I aways start from
the picture. I spend lots of time understanding the picture
I want to code, and then I code.

I think everyone's design process is personal and tuned to
what makes them successful. I think up front, because I
enjoy the challenge and I hate incremental debug and tuning.
Once I start coding it goes fast and so does debug.

To the absolute newbie, variables are easier to grasp since
you can step ... Snip
The rest I think we will just have to agree to disagree.

Cheers,
Jim
 
Jim Lewis wrote:
Jan,

Never needed to code a CRC which treated multiple bits
in a single clock cycle? How did you/would you solve that?


Same as I code any other hardware - sketch the picture,
code the picture. Much time spent understanding hardware
and picture. Little time spent coding and synthesis.
What I do is to put a for-loop over the bits around
the bit-serial equation (which is trivially related to
the polynomial), and leave the optimization of the
xor-network to a tool. Little time spent overall.

Remarks:

* variable semantics are mandatory for this, because
each loop iteration is influenced by the previous one.
* this can be done both in a combinatorial block or
a clocked one. In the latter case, the CRC variable
will result both in hardware registers and combo
logic.

Only use variables when the situation demands it.
That is so vague that everybody has to agree with it.
For example, to those who are happy with using HDL design
as a textual form of schematic entry, the situation will
never demand it. Good for them.

To me, the meaningful way to discuss the desirability
of variables is in terms of their influence on design
productivity.

Regards,

Jan

--
Jan Decaluwe - Resources bvba - http://www.jandecaluwe.com
Losbergenlaan 16, B-3010 Leuven, Belgium
From Python to silicon:
http://myhdl.jandecaluwe.com
 
Jan
What I do is to put a for-loop over the bits around
the bit-serial equation (which is trivially related to
the polynomial), and leave the optimization of the
xor-network to a tool. Little time spent overall.

Remarks:

* variable semantics are mandatory for this, because
each loop iteration is influenced by the previous one.
* this can be done both in a combinatorial block or
a clocked one. In the latter case, the CRC variable
will result both in hardware registers and combo
logic.

Only use variables when the situation demands it.

That is so vague that everybody has to agree with it.
For example, to those who are happy with using HDL design
as a textual form of schematic entry, the situation will
never demand it. Good for them.

To me, the meaningful way to discuss the desirability
of variables is in terms of their influence on design
productivity.
Add to that a meaningful example with at least a sketch
of the solution, as you have provided, and then we can
have a meaningful discussion.

While I recommend not using variables in RTL for the first
couple of designs, in class I also show compelling places to
use variables - such as forming a select for a case expression
or XOR reduction.

Getting back to what Andy said:
-- the background
Also, not the variables themselves, but _references_ to them imply
storage or not (and thus register or not); the same variable name may
represent a combinatorial value in one reference, and a register value
in another.
-- the statement that worries me:
And the difference can even be dynamically controlled (e.g.
a prior write may be conditional). The point is the synthesis tool will
use a register or not (or a mux between the two) to implement the coded
behavior.
My read of this says:
process (clk)
begin
if rising_edge(Clk) then
if (le = '1') then
MyVar := ...
end if ;
MySig <= MyVar ;
end if ;
end process ;

In the past, I have seen synthesis tools create a register for
MyVar even when it was redundant with the one for MySig.

For those of you just tuning in, I realize that some, but not
all, synthesis tools support the following:
process (clk)
begin
if rising_edge(Clk) then
if (le = '1') then
MyVar := ...
end if ;
end if ;
MySig <= MyVar ;
end process ;

So personally I would not use either of the above.
 
I imagine that Cliff's reasoning here is that of a trainer who
wishes to be conservative - note that Jim Lewis uses exactly
the same reasoning when avoiding discussion of variables in
his VHDL training classes. We, similarly, avoid the use of
variables (and locals in Verilog) in our introductory training,
but we slip in the possibility as soon as we are able.
We believe it gives users a powerful additional tool for
expressing their design intent.
Just to be fair, ...
while I recommend not using variables in RTL for the first
couple of designs, in class I also show compelling places to
use variables - such as forming a select for a case expression
or XOR reduction. XOR reduction is just plain ugly without
variables - thank goodness it is being added to the language.

This is simply telling those who already feel burdened with
enough information that they do not need to worry about
learning this yet. However, by showing them the other examples
it gives them the idea that some day they do need to learn it.
For verification classes we fully utilize the whole language.

One always needs to be careful about making guidelines.
Something that seems trivial, such as always reset state registers
can have exceptions - such as when all registers are reset by
BIST and BIST always runs before the system becomes active and
leaves the system in the reset state.

Cheers,
Jim
 
On Mon, 22 Jan 2007 08:40:48 -0800, Jim Lewis
<jim@synthworks.com> wrote:

Just to be fair, ...
while I recommend not using variables in RTL for the first
couple of designs, in class I also show compelling places to
use variables[...]
Sincere apologies if I misrepresented you. I was trying
only to exemplify a reasonable difference of opinion
that might exist.

For verification classes we fully utilize the whole language.
Absolutely. It's kinda tough to use TEXTIO without
variables! Any differences of opinion are surely only
related to coding styles for synthesis.

The "variables, yes or no" question arose again today in a
training class I'm delivering. As usual, shades of opinion
ranged all the way from
"hey, that's cool, I'm going to start using that" to
"no, I'll keep the combinational and clocked logic in
separate processes where I can see what they look like".
Ultimately, what matters is maintainability and robustness of
code, and - as others have said - that has much more to
do with quality of thinking than quality of coding gudelines.

One always needs to be careful about making guidelines.
Wisely said :)
--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 
Jim Lewis wrote:


Add to that a meaningful example with at least a sketch
of the solution, as you have provided, and then we can
have a meaningful discussion.
http://groups.google.com/groups/search?q=hecht+next_crc

-- Mike Treseler
 
Jim Lewis wrote:
Getting back to what Andy said:
-- the background
Also, not the variables themselves, but _references_ to them imply
storage or not (and thus register or not); the same variable name may
represent a combinatorial value in one reference, and a register value
in another.

-- the statement that worries me:
And the difference can even be dynamically controlled (e.g.
a prior write may be conditional). The point is the synthesis tool will
use a register or not (or a mux between the two) to implement the coded
behavior.

My read of this says:
process (clk)
begin
if rising_edge(Clk) then
if (le = '1') then
MyVar := ...
end if ;
MySig <= MyVar ;
end if ;
end process ;

In the past, I have seen synthesis tools create a register for
MyVar even when it was redundant with the one for MySig.
The reference to MyVar in the assignment to MySig is a combinatorial
reference to MyVar if le='1', and a registered reference if le ='0'.
So, the synthesis will put a mux on the input to the MySig register to
make that choice. The interesting optimization is that the mux before
MySig looks amazingly like the clock enable mux on the MyVar register.
And if the same mux feeds two registers, then the registers are
redundant, and one can be optimized out. I suspect this optimization
has to do with at which point the synthesis tool selects a clock
enabled register, with the mux built in, for MyVar. If it does it too
soon, the optimization is not available when needed.

A larger point is, do you always code to get exactly the hardware
structure you want? If I always, only coded in a manner to which any
synthesis tool would give me the exact same implementation, I'd have to
write out a structural VHDL netlist. The same issue is present when and
if you use automatic register replication, or register retiming. In
either of these cases, the hardware you envisioned to write the
description will no longer exist when those optimizations are done. My
point is the link between code and hardware is getting looser and
looser as tools advance. The only thing that remains the same is that
the synthesis tool will give you hardware which will behave the same as
the code, on a cycle by cycle basis.

Now, suppose you had written:

....
MySig <= MyVar + 1;
....

Now the two registers are not redundant, but the mux is shareable in
both places. However, for performance reasons, most synthesizers will
not share them (i.e. they'll use a clock enabled register for MyVar,
and blend the mux in with the incrementor for MySig)

I suppose if I was in the business of writing code for all kinds of
customers to use, on all kinds of tools, I'd get a lot more
conservative in my coding practices. As it is, I've had pretty good
luck getting results by telling vendor A that vendor B already does
this.

For those of you just tuning in, I realize that some, but not
all, synthesis tools support the following:
process (clk)
begin
if rising_edge(Clk) then
if (le = '1') then
MyVar := ...
end if ;
end if ;
MySig <= MyVar ;
end process ;

So personally I would not use either of the above.
Say you added the same incrementor to this one. The incrementor would
be after the MyVar register, and the assignment to MySig is
combinatorial. So MySig is now a combinatorial output from a clocked
process!

This one is handled by the tools I use, so I use it. Maybe instead of
arguing over whether it is a good idea to use it, we should be opening
up support cases with those vendors who don't support it. Again,
"...but Vendor B (Synplicity) handles this correctly" works amazingly
well! (A really sad story about "thousands of lines of code, riddled
with this stuff, works with Vendor B, but we'd like to use Vendor A's
tool, if it just worked on this code" can't hurt either.)

Andy
 
Mike Treseler wrote:

If I put the port assignment
outside of the clocked clause,
no pipeline register is inferred.

clked : process(clk)
variable cnt_v : count_t;
begin
if rising_edge(clk) then
cnt_v := cnt_v + 1; -- increment on rising edge
end if;
cnt <= cnt_v; -- wire assignment outside of clocked clause
end process clked;
I never used this template, but started to think about it because
of my work on MyHDL.

The question I have is: is there an equivalent (also synthesizable)
version for Verilog? At first sight, I don't think so, but perhaps
I'm wrong.

Background: there is a MyHDL to Verilog converter, and a MyHDL to
VHDL converter is well under way. When finished, IP developers will
have a way to generate equivalent Verilog and VHDL code from a single
source. Of course, this requires that equivalent templates exist
in both languages. See also:

http://myhdl.jandecaluwe.com/doku.php/dev:whatsnew:0.6#template_transformation

Thanks,

Jan

--
Jan Decaluwe - Resources bvba - http://www.jandecaluwe.com
Losbergenlaan 16, B-3010 Leuven, Belgium
From Python to silicon:
http://myhdl.jandecaluwe.com
 

Welcome to EDABoard.com

Sponsor

Back
Top