Reading an Array of vectors.

Discussion in 'VHDL' started by Niv (KP), Feb 13, 2009.

  1. Niv (KP)

    Niv (KP) Guest

    I have an interface to a set of identical ADC's, the number of which
    is set by a generic "N", (range of 1 to 16)
    All are triggered & sampled in parallel, so only one clock & enable
    generated, but "N" data paths back

    I have to read the vectors over a CPU bus one at a time.

    As the number of vectors is generic dependant, how to I code the case
    statement for the reads.

    e.g.

    CASE address IS
    WHEN "0000" => read_bus <= adc_bus(0);
    WHEN "0001" => read_bus <= adc_bus(1);

    -- etc
    END CASE;

    What I want to do is loop on 0 to (N-1 ) on the CASE satement.

    The only way I can think of is to create an array 16 wide of (31
    downto 0), default assign all to '0's
    then in a loop of 0 to (N- 1) assign this new array to the incoming
    array.

    I then code the case statement so it has all 16 possible states, and
    reads this new array one slice at a time.
    Software can decide how to read depending on the actual number of ADCs
    instantiated.

    Is there a better way? (Not used arrays of vectors with generic ports
    etc before, if you hadn't already guessed)!
    I could hard code the whole lot for a fixed number of ADC's very
    easily, but RE_USE is key at the moment.

    TIA, Niv.
    Niv (KP), Feb 13, 2009
    #1
    1. Advertising

  2. Niv (KP)

    Ken Cecka Guest

    Niv (KP) wrote:

    > I have an interface to a set of identical ADC's, the number of which
    > is set by a generic "N", (range of 1 to 16)
    > All are triggered & sampled in parallel, so only one clock & enable
    > generated, but "N" data paths back
    >
    > I have to read the vectors over a CPU bus one at a time.
    >
    > As the number of vectors is generic dependant, how to I code the case
    > statement for the reads.
    >
    > e.g.
    >
    > CASE address IS
    > WHEN "0000" => read_bus <= adc_bus(0);
    > WHEN "0001" => read_bus <= adc_bus(1);
    >
    > -- etc
    > END CASE;
    >
    > What I want to do is loop on 0 to (N-1 ) on the CASE satement.
    >
    > The only way I can think of is to create an array 16 wide of (31
    > downto 0), default assign all to '0's
    > then in a loop of 0 to (N- 1) assign this new array to the incoming
    > array.
    >
    > I then code the case statement so it has all 16 possible states, and
    > reads this new array one slice at a time.
    > Software can decide how to read depending on the actual number of ADCs
    > instantiated.
    >
    > Is there a better way? (Not used arrays of vectors with generic ports
    > etc before, if you hadn't already guessed)!
    > I could hard code the whole lot for a fixed number of ADC's very
    > easily, but RE_USE is key at the moment.
    >
    > TIA, Niv.


    I haven't studied the netlist to verify how this gets translated, but the way I've solved this in my projects that seems to simulate and synthesize fine is:

    FOR i IN adc_bus'RANGE LOOP
    IF (address = CONV_STD_LOGIC_VECTOR(i, 5)) THEN
    read_bus <= adc_bus(i);
    END IF;
    END LOOP;

    This should unroll into a series of IF statements at synthesis time - one for each possible value of address.

    Ken
    Ken Cecka, Feb 13, 2009
    #2
    1. Advertising

  3. Niv (KP)

    Ken Cecka Guest

    Ken Cecka wrote:

    > Niv (KP) wrote:
    >
    >> I have an interface to a set of identical ADC's, the number of which
    >> is set by a generic "N", (range of 1 to 16)
    >> All are triggered & sampled in parallel, so only one clock & enable
    >> generated, but "N" data paths back
    >>
    >> I have to read the vectors over a CPU bus one at a time.
    >>
    >> As the number of vectors is generic dependant, how to I code the case
    >> statement for the reads.
    >>
    >> e.g.
    >>
    >> CASE address IS
    >> WHEN "0000" => read_bus <= adc_bus(0);
    >> WHEN "0001" => read_bus <= adc_bus(1);
    >>
    >> -- etc
    >> END CASE;
    >>
    >> What I want to do is loop on 0 to (N-1 ) on the CASE satement.
    >>
    >> The only way I can think of is to create an array 16 wide of (31
    >> downto 0), default assign all to '0's
    >> then in a loop of 0 to (N- 1) assign this new array to the incoming
    >> array.
    >>
    >> I then code the case statement so it has all 16 possible states, and
    >> reads this new array one slice at a time.
    >> Software can decide how to read depending on the actual number of ADCs
    >> instantiated.
    >>
    >> Is there a better way? (Not used arrays of vectors with generic ports
    >> etc before, if you hadn't already guessed)!
    >> I could hard code the whole lot for a fixed number of ADC's very
    >> easily, but RE_USE is key at the moment.
    >>
    >> TIA, Niv.

    >
    > I haven't studied the netlist to verify how this gets translated, but the
    > way I've solved this in my projects that seems to simulate and synthesize
    > fine is:
    >
    > FOR i IN adc_bus'RANGE LOOP
    > IF (address = CONV_STD_LOGIC_VECTOR(i, 5)) THEN
    > read_bus <= adc_bus(i);
    > END IF;
    > END LOOP;
    >
    > This should unroll into a series of IF statements at synthesis time - one
    > for each possible value of address.
    >
    > Ken


    Forgot to mention that you can get the the equivalent of a WHEN OTHERS by adding an initial assignment to read_bus before the loop:

    read_bus <= (OTHERS => '0');
    FOR i IN adc_bus'RANGE LOOP
    IF (address = CONV_STD_LOGIC_VECTOR(i, 5)) THEN
    read_bus <= adc_bus(i);
    END IF;
    END LOOP;
    Ken Cecka, Feb 13, 2009
    #3
  4. Niv (KP)

    Niv (KP) Guest

    On 13 Feb, 17:46, Jonathan Bromley <>
    wrote:
    > On Fri, 13 Feb 2009 17:34:05 +0000, Alan Fitch wrote:
    > >    process(clock)
    > >    begin
    > >      if rising_edge(clock) then
    > >        if enable = '1' then
    > >          for i in 0 to n-1 loop
    > >            if to_integer(unsigned(address)) = i then
    > >              read_bus(i) <= adc_bus(i);
    > >            end if;
    > >          end loop;
    > >        end if;
    > >    end process;

    >
    > >Due to "last assignment wins" a.k.a intertial delay, this will mean
    > >effectively there is a higher priority to higher addresses - but as only
    > >one address can be true (they're mutually exclusive) I would hope
    > >synthesis would produce sensible code,

    >
    > If synthesis doesn't sort out the mutual-exclusivity
    > for itself, you can give it a helpful hint by implying
    > an AND-OR tree instead of the pri-mux.  I've used this
    > with some success in designs that have a large number
    > of readable registers.  
    >
    > Similar overall shape to Alan's solution, but with an
    > extra variable to accumulate the sum-of-products:
    >
    >     process(clock)
    >       variable readback: std_logic_vector(read_bus'range);
    >     begin
    >       if rising_edge(clock) then
    >         readback := (others => '0');  -- start with all-zero
    >         if enable = '1' then
    >           for i in 0 to n-1 loop
    >             if to_integer(unsigned(address)) = i then
    >               readback := readback or adc_bus(i);
    >             end if;
    >           end loop;
    >           read_bus <= readback;
    >         end if;
    >     end process;
    >
    > Note that this iterate-through-the-addresses arrangement
    > also allows for more complex address matching schemes.
    > Instead of the equality test on the address, consider
    > using a user-written address matching function:
    >
    >   if adrs_match(address, i) then ...
    >
    > This allows for sparse address spaces, "registers" that
    > span more than one address (they would then use a few
    > low-order address bits internally to determine their
    > functionality), and "interesting" variable register maps.
    > --
    > 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
    > ://www.MYCOMPANY.com
    >
    > The contents of this message may contain personal views which
    > are not the views of Doulos Ltd., unless specifically stated.- Hide quoted text -
    >
    > - Show quoted text -


    Thanks chaps,
    Plenty of options & food for thought there.
    Regards, Niv.
    Niv (KP), Feb 13, 2009
    #4
  5. Niv (KP)

    Andy Guest

    On Feb 13, 11:27 am, Jim Lewis <> wrote:
    > On the other hand, you can avoid the looping by using the
    > following (one step, no loops, no if statement).  to_integer
    > is from package numeric_std.
    >
    > library ieee ;
    > use ieee.numeric_std.all ;
    > . . .
    >
    > read_bus <= adc_bus(to_integer(unsigned(address))) ;
    >


    This works so long as any possible value on address is within the
    index range of adc_bus (or if you can use the mod operator on address
    to limit it). It is quick and to the point though. Otherwise, the for-
    loop with a comparison of address and each possible index is required.

    Andy
    Andy, Feb 13, 2009
    #5
  6. Niv (KP)

    Andy Guest

    On Feb 13, 11:34 am, Alan Fitch <> wrote:
    >
    > Due to "last assignment wins" a.k.a intertial delay, this will mean
    > effectively there is a higher priority to higher addresses - but as only
    > one address can be true (they're mutually exclusive) I would hope
    > synthesis would produce sensible code,
    >


    You can use an exit statement, and/or alter the direction of the loop
    index range to alter the priority of the unrolled if statements.
    Although in most cases using equality comparisons to the same object
    (address in this case), the synthesis tool will figure out that the
    comparisons are mutually exclusive. Thus no priority is assigned,
    rendering index order and/or exit statements moot, except with regard
    to simulation performance.

    Within vhdl/process contexts, "last assignment wins" and "inertial
    delay" are completely different concepts. Inertial delay has to do
    with multiple waveforms driven onto one signal at different simulation
    times, and which one will take precedence. Inside a process, multiple
    assignments between waits happen in zero simulation time, and there is
    only one value/waveform that actually gets promoted to that process's
    lone driver for the signal (and it is the last assignment that is
    promoted).

    Andy
    Andy, Feb 13, 2009
    #6
  7. Niv (KP)

    Andy Guest

    On Feb 14, 2:58 am, Jonathan Bromley <>
    wrote:
    > On Fri, 13 Feb 2009 13:09:13 -0800 (PST), Andy wrote:
    > >Within vhdl/process contexts, "last assignment wins" and "inertial
    > >delay" are completely different concepts.

    >
    > Sorry Andy, I don't think that is correct at all.
    >
    > >Inertial delay has to do
    > >with multiple waveforms driven onto one signal at different simulation
    > >times

    >
    > Or at the same time.  One consistent set of rules covers all cases.
    >
    > > Inside a process, multiple
    > >assignments between waits happen in zero simulation time

    >
    > For sure.
    >
    > >only one value/waveform that actually gets promoted to that process's
    > >lone driver for the signal (and it is the last assignment that is
    > >promoted).

    >
    > Yes, but that behaviour is only guaranteed BECAUSE OF the
    > inertial delay rules.  Contrast with zero-delay nonblocking
    > assignments in Verilog, which use non-overwriting transport
    > delay; Verilog is obliged to introduce an intuitively
    > sensible, but conceptually tiresome, kludge into the
    > language definition to guarantee that multiple updates
    > on the same signal have the effect that you expect.
    > Even then, the use of transport rather than inertial
    > delay raises the possibility of unwanted zero-width
    > glitches that may or may not be seen as events in
    > simulation.  And the absence of inertial delay for
    > procedural signal assignment makes it impossible to
    > rescind a future scheduled signal update in a reliable
    > manner.
    >
    > So, all in all, inertial delay is rather important for
    > getting sensible last-write-wins behaviour for signal
    > assignment.


    If I read your argument correctly, one should be able to use transport
    delay to allow prior writes (in same process cycle) to show up in the
    waveform...

    Andy
    Andy, Feb 16, 2009
    #7
  8. Niv (KP)

    Andy Guest

    On Feb 16, 1:23 pm, Andy <> wrote:
    > If I read your argument correctly, one should be able to use transport
    > delay to allow prior writes (in same process cycle) to show up in the
    > waveform...
    >
    > Andy


    To illustrate:

    process(clk) is
    begin
    if rising_edge(clk) then
    s <= transport e;
    s <= transport e + 1;
    s <= transport e + 2;
    end if;
    end process;

    Per your argument, for how long should e and e+1 show up before the
    value of s settles on e+2?

    Andy
    Andy, Feb 16, 2009
    #8
  9. Niv (KP)

    Niv (KP) Guest

    On 13 Feb, 19:49, "Niv (KP)" <> wrote:
    > On 13 Feb, 17:46, Jonathan Bromley <>
    > wrote:
    >
    >
    >
    >
    >
    > > On Fri, 13 Feb 2009 17:34:05 +0000, Alan Fitch wrote:
    > > >    process(clock)
    > > >    begin
    > > >      if rising_edge(clock) then
    > > >        if enable = '1' then
    > > >          for i in 0 to n-1 loop
    > > >            if to_integer(unsigned(address)) = i then
    > > >              read_bus(i) <= adc_bus(i);
    > > >            end if;
    > > >          end loop;
    > > >        end if;
    > > >    end process;

    >
    > > >Due to "last assignment wins" a.k.a intertial delay, this will mean
    > > >effectively there is a higher priority to higher addresses - but as only
    > > >one address can be true (they're mutually exclusive) I would hope
    > > >synthesis would produce sensible code,

    >
    > > If synthesis doesn't sort out the mutual-exclusivity
    > > for itself, you can give it a helpful hint by implying
    > > an AND-OR tree instead of the pri-mux.  I've used this
    > > with some success in designs that have a large number
    > > of readable registers.  

    >
    > > Similar overall shape to Alan's solution, but with an
    > > extra variable to accumulate the sum-of-products:

    >
    > >     process(clock)
    > >       variable readback: std_logic_vector(read_bus'range);
    > >     begin
    > >       if rising_edge(clock) then
    > >         readback := (others => '0');  -- start with all-zero
    > >         if enable = '1' then
    > >           for i in 0 to n-1 loop
    > >             if to_integer(unsigned(address)) = i then
    > >               readback := readback or adc_bus(i);
    > >             end if;
    > >           end loop;
    > >           read_bus <= readback;
    > >         end if;
    > >     end process;

    >
    > > Note that this iterate-through-the-addresses arrangement
    > > also allows for more complex address matching schemes.
    > > Instead of the equality test on the address, consider
    > > using a user-written address matching function:

    >
    > >   if adrs_match(address, i) then ...

    >
    > > This allows for sparse address spaces, "registers" that
    > > span more than one address (they would then use a few
    > > low-order address bits internally to determine their
    > > functionality), and "interesting" variable register maps.
    > > --
    > > 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
    > > ://www.MYCOMPANY.com

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

    >
    > > - Show quoted text -

    >
    > Thanks chaps,
    > Plenty of options & food for thought there.
    > Regards, Niv.- Hide quoted text -
    >
    > - Show quoted text -


    I wasn't quite right when I said "N" was a generic!

    I have a package with the following:

    PACKAGE ad7476_pkg IS

    CONSTANT num_of_convs : INTEGER := 5; -- Number of parallel
    ADC's.


    TYPE d_conv_bus IS ARRAY(num_of_convs-1 DOWNTO 0)
    OF STD_LOGIC_VECTOR(31 DOWNTO 0);

    TYPE d_convs_in IS ARRAY(num_of_convs-1 DOWNTO 0)
    OF STD_LOGIC;

    END ad7476_pkg;

    The "d_conv_bus" is used to declare the o/p port on teh ADC controller
    and the
    i/p port on my address decoder, so the results get read in ascending
    order (loop dependent).
    The ADC controller also has an i/p port of "d_convs_in", which
    promulgates to the top level of the block.

    I would like to instnace this top level block with a generic that
    overrides the package constant
    "num_of_convs", but can't figure how to do this.

    As it is, with the constant 5, all works fine, and I can read the
    values back as suggested in earlier posts.
    I'd just like to make it more flexible.

    TIA, Niv.
    Niv (KP), Feb 17, 2009
    #9
  10. Niv (KP)

    Andy Guest

    On Feb 16, 2:13 pm, Jonathan Bromley <>
    wrote:
    > On Mon, 16 Feb 2009 11:23:28 -0800 (PST), Andy wrote:
    > >If I read your argument correctly, one should be able to use transport
    > >delay to allow prior writes (in same process cycle) to show up in the
    > >waveform...

    >
    > I'm not 100% sure I understand what you mean, so please
    > excuse me if I've misunderstood.
    >
    > A transport signal assignment in VHDL:
    >
    >   S <= transport EXPR after T;
    >
    > schedules an update of S to EXPR at time NOW+T,
    > and also deletes any pending updates on S for
    > times >= NOW+T.  Pending updates on S for
    > times < NOW+T are preserved, however.
    >


    In my way of thinking, Inertial and Transport mechanisms are opposite
    of each other. If (and I don't think it does) the inertial delay
    mechanism is causing some behavior (discarding all but the last of all
    non-delayed outputs in the delta cycle following the process cycle),
    then transport delay ought to avoid that behavior (allow the multiple
    outputs to appear), which we both agree will not happen.

    > You can't get more than one update on a signal
    > in a given delta cycle in VHDL, no matter what you do.


    THIS is exactly what I'm getting at. The fact that the last assignment
    in a process cycle wins (esp. among assignments with no delays) has
    nothing to do with inertial delay, it has to do with the fact that all
    of the (zero delay) signal updates from a single process cycle are
    effective in the same delta cycle, and there is no smaller subdivision
    of "time" to spread multiple updates over. So the last assignment is
    chosen as the update for that delta cycle, and earlier ones are
    discarded.

    In other words, the reason that multiple delta cycles are not used,
    and therefore only one (any one) assignment must be chosen to be
    promoted, has nothing to do with inertial delay. I understand your
    assertion that the reason the last one is chosen is at least
    consistent with, if not controlled by, the inertial delay mechanism.

    Perhaps to help illustrate this:

    process (e)
    variable temp : unsigned(s'range);
    begin
    temp := e;
    temp := e+1;
    temp := e+2;
    s <= temp;
    end process;

    Is the above any different from:

    process (e)
    begin
    s <= e;
    s <= e+1;
    s <= e+2;
    end process;

    Maybe this is all in how the simulator optimizes execution (i.e.
    removing prior writes up front, rather than using the signal update
    (inertial) mechanism to do so). Just for the record, I think we both
    agree there is absolutely no difference WRT synthesis.

    Thank you for the explanation of verilog behavior. It reinforces my
    desire not to migrate to verilog (or SystemVerilog, if it has these
    same issues).

    Andy
    Andy, Feb 17, 2009
    #10
  11. Niv (KP)

    Guest

    On Feb 17, 9:44 am, Andy <> wrote:
    > On Feb 16, 2:13 pm, Jonathan Bromley <>
    > wrote:
    >
    >
    >
    > > On Mon, 16 Feb 2009 11:23:28 -0800 (PST), Andy wrote:
    > > >If I read your argument correctly, one should be able to use transport
    > > >delay to allow prior writes (in same process cycle) to show up in the
    > > >waveform...

    >
    > > I'm not 100% sure I understand what you mean, so please
    > > excuse me if I've misunderstood.

    >
    > > A transport signal assignment in VHDL:

    >
    > >   S <= transport EXPR after T;

    >
    > > schedules an update of S to EXPR at time NOW+T,
    > > and also deletes any pending updates on S for
    > > times >= NOW+T.  Pending updates on S for
    > > times < NOW+T are preserved, however.

    >
    > In my way of thinking, Inertial and Transport mechanisms are opposite
    > of each other. If (and I don't think it does) the inertial delay
    > mechanism is causing some behavior (discarding all but the last of all
    > non-delayed outputs in the delta cycle following the process cycle),
    > then transport delay ought to avoid that behavior (allow the multiple
    > outputs to appear), which we both agree will not happen.
    >
    > > You can't get more than one update on a signal
    > > in a given delta cycle in VHDL, no matter what you do.

    >
    > THIS is exactly what I'm getting at. The fact that the last assignment
    > in a process cycle wins (esp. among assignments with no delays) has
    > nothing to do with inertial delay, it has to do with the fact that all
    > of the (zero delay) signal updates from a single process cycle are
    > effective in the same delta cycle, and there is no smaller subdivision
    > of "time" to spread multiple updates over. So the last assignment is
    > chosen as the update for that delta cycle, and earlier ones are
    > discarded.
    >
    > In other words, the reason that multiple delta cycles are not used,
    > and therefore only one (any one) assignment must be chosen to be
    > promoted, has nothing to do with inertial delay. I understand your
    > assertion that the reason the last one is chosen is at least
    > consistent with, if not controlled by, the inertial delay mechanism.
    >
    > Perhaps to help illustrate this:
    >
    > process (e)
    >   variable temp : unsigned(s'range);
    > begin
    >   temp := e;
    >   temp := e+1;
    >   temp := e+2;
    >   s <= temp;
    > end process;
    >
    > Is the above any different from:
    >
    > process (e)
    > begin
    >   s <= e;
    >   s <= e+1;
    >   s <= e+2;
    > end process;
    >
    > Maybe this is all in how the simulator optimizes execution (i.e.
    > removing prior writes up front, rather than using the signal update
    > (inertial) mechanism to do so). Just for the record, I think we both
    > agree there is absolutely no difference WRT synthesis.
    >
    > Thank you for the explanation of verilog behavior. It reinforces my
    > desire not to migrate to verilog (or SystemVerilog, if it has these
    > same issues).
    >
    > Andy


    Never one to leave a dead horse undisturbed, I'll throw in a few
    kicks, too. I think there's a bit of confusion arising because of the
    unconcscious meanings we associate with the word "inertial", as well
    as what the inertial versus transport concept is applied to.

    Inertial (to me at least) implies that some kind of time filtering or
    pulse rejection is happening, while transport is more of a pure
    transmission line delay. However, this intuition can be misleading.
    This separation only comes into play for non-zero delays, and is not
    really an accurate picture of what happens in the simplest case. In
    the simplest case (where there is no delay specified), the inertial
    assignment
    s <= e;
    is completely identical to
    s <= e after 0 ns;
    which is also completely identical to
    s <= e after 0 ns reject 0 NS;
    which is also completely identical to
    s <= transport e after 0 ns;

    The rules of the language say that for each assignment statement in
    one process (i.e. assignment to the same driver), you can think of the
    current state of the driver as a queue of scheduled events, and the
    waveform on the right hand side of the assignment as another list of
    events. The assignment is just the act of merging these event lists.
    The transport and inertial concepts, as Jonathan pointed out, only
    change what happens to events earlier than NOW+t1, for t1 the earliest
    time in your waveform. For the usual "s <= e;" statement, t1 is zero,
    and there's no difference between transport and inertial for that
    case.

    The "magic" of resolving everything so that it does what we expect is
    because the rules make it so that the driver only ever has one event
    scheduled for a given time. This is simply to ensure sanity of the
    queue - you will never have six things scheduled up for time now+0. It
    has nothing to do with whether the assignment is inertial, or
    transport, or has zero or non-zero time delay. When the time in
    question is "now", then the rules mean that

    s <= e;
    s <= e+1;
    s <= e+2;

    make the first waveform element in the driver queue (scheduled for
    "now", hence the next delta cycle) keep getting overwritten at each
    assignment, before the process ever blocks. The first two assignments
    effectively disappear before they're ever acted on.

    The other fine point is that transport and inertial refer only to one
    specific assignment statement. It is completely possible to mix
    inertial and transport assignments in a process:

    s <= transport e after 5 ns;
    s <= e+1 after 0 ns reject 10 ns;
    s <= e + 2;

    and still have completely well-defined behaviour.

    - Kenn
    , Feb 17, 2009
    #11
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    8
    Views:
    13,601
    AlyBrat
    Apr 13, 2009
  2. Shannon
    Replies:
    2
    Views:
    512
    Shannon
    Feb 9, 2007
  3. Dan
    Replies:
    12
    Views:
    14,187
    devil_Jit
    Sep 26, 2010
  4. Replies:
    3
    Views:
    691
    Shadowman
    Mar 26, 2008
  5. Guest
    Replies:
    0
    Views:
    438
    Guest
    Sep 14, 2005
Loading...

Share This Page