Philosophical placement of counter

L

Lango

Hello. I normally have the control description (normally a fsm)
separate from the datapath. Supposing I need a counter to time the
duration of some of the control outputs, for example, enable this mux
for 1000 cycles before doing anything else, would you declare the
counter inside or outside of the fsm architecture?
 
K

KJ

Hello.  I normally have the control description (normally a fsm)
separate from the datapath.

That's a religious thing, not a design requirement or anything that
necessarily improves anything...but OK.
Supposing I need a counter to time the
duration of some of the control outputs, for example, enable this mux
for 1000 cycles before doing anything else, would you declare the
counter inside or outside of the fsm architecture?

Depends on what you mean by 'fsm architecture'. For the sake of
discussion, I'll assume that you mean a VHDL 'process'. Next, I
wouldn't get philosophical about it but instead focus on the
readability and maintainability of the design. A VHDL process that
requires paging back and forth in order to be understood likely does
not rank very high on the readability and maintainability score (which
is subjective). Given no other information about what function is to
be accomplished in the entity I would most likely start like the code
I've shown below.

If either the counter logic or the FSM logic (or other logic in the
architecture) grew to the point where the process gets to be too long,
I would likely break it up into separate clocked processes just to try
to keep related things together and try to make it a bit easier to
understand.

Long processes can spawn use of numerous variables. While variables
are a good thing, assignments to variables create a line in the sand.
Prior to the assignment, the variable means one thing, after the
assignment it means something else. Since code generally tends to
grow over time and not shrink it can cause problems if the code starts
to look a bit unwieldy since you can't easily move things around
without destroying the function. In my example, I didn't use
variables so the logic implementing the FSM and the logic implementing
the counter could be rearranged as separate block for readability
however I see fit...it's all happening in parallel.

You having the take that control logic must be separated from control
logic you've put up some artificial barriers that are philosophical
but do not contribute to making a design more robust or maintainable.
As I wrote it, the counter is separated from the control logic but not
because of any philosophy. The two functions are loosely coupled (the
FSM tells the counter to start, the counter tells the FSM when it's
done) so the logic to implement each tends to not want to live inside
the other.

A more pragmatic approach that will lead to better design is to view
sub-functions to see if they fit the model of 'request/acknowledge' or
'start/complete' 'command/wait' (whichever works for you). You'll
find that in many cases they do. In this case, the FSM commands the
counter to start (via being in a particular state...or could've been
as a separate signal if you prefer) and the counter then signals back
when it completes (via the 'Done_Counting' signal). Once you take
that viewpoint, you'll probably see that the logic for sub-functions
naturally separates into more readable, maintainable code.

Kevin Jennings
----
architecture RTL of Something is
type t_STATES is (Idle, Start_Count, Done);
signal Current_State: t_STATES;
constant MAX_COUNT: natural := 999;
signal Counter: natural range 0 to MAX_COUNT;
signal Done_Counting: std_ulogic;
begin
process(Clock)
begin
if rising_edge(Clock) then
------------
-- FSM logic
------------
if (Reset = '1') then
Current_State <= Idle;
else
case Current_State is
when Idle =>
-- Code to wait for event to start timing
when Start_Count =>
if (Done_Counting = '1') then
Current_State <= Done;
end if;
when Done =>
-- Code to take us back to idle presumably
end case;
end if;

------------------------
-- Output signal counter
------------------------
if ((Current_State /= Start_Count)
or (Counter = MAX_COUNT) then
Counter <= 0;
else
Counter <= Counter + 1;
end if;
end process;

Done_Counting <= '1' when (Counter = MAX_COUNT) else '0';
 
L

Lango

That's a religious thing, not a design requirement or anything that
necessarily improves anything...but OK.


Depends on what you mean by 'fsm architecture'.  For the sake of
discussion, I'll assume that you mean a VHDL 'process'.  Next, I
wouldn't get philosophical about it but instead focus on the
readability and maintainability of the design.  A VHDL process that
requires paging back and forth in order to be understood likely does
not rank very high on the readability and maintainability score (which
is subjective).  Given no other information about what function is to
be accomplished in the entity I would most likely start like the code
I've shown below.

If either the counter logic or the FSM logic (or other logic in the
architecture) grew to the point where the process gets to be too long,
I would likely break it up into separate clocked processes just to try
to keep related things together and try to make it a bit easier to
understand.

Long processes can spawn use of numerous variables.  While variables
are a good thing, assignments to variables create a line in the sand.
Prior to the assignment, the variable means one thing, after the
assignment it means something else.  Since code generally tends to
grow over time and not shrink it can cause problems if the code starts
to look a bit unwieldy since you can't easily move things around
without destroying the function.  In my example, I didn't use
variables so the logic implementing the FSM and the logic implementing
the counter could be rearranged as separate block for readability
however I see fit...it's all happening in parallel.

You having the take that control logic must be separated from control
logic you've put up some artificial barriers that are philosophical
but do not contribute to making a design more robust or maintainable.
As I wrote it, the counter is separated from the control logic but not
because of any philosophy.  The two functions are loosely coupled (the
FSM tells the counter to start, the counter tells the FSM when it's
done) so the logic to implement each tends to not want to live inside
the other.

A more pragmatic approach that will lead to better design is to view
sub-functions to see if they fit the model of 'request/acknowledge' or
'start/complete' 'command/wait' (whichever works for you).  You'll
find that in many cases they do.  In this case, the FSM commands the
counter to start (via being in a particular state...or could've been
as a separate signal if you prefer) and the counter then signals back
when it completes (via the 'Done_Counting' signal).  Once you take
that viewpoint, you'll probably see that the logic for sub-functions
naturally separates into more readable, maintainable code.

Kevin Jennings
----
architecture RTL of Something is
  type t_STATES is (Idle, Start_Count, Done);
  signal Current_State: t_STATES;
  constant MAX_COUNT: natural := 999;
  signal Counter:  natural range 0 to MAX_COUNT;
  signal Done_Counting: std_ulogic;
begin
  process(Clock)
  begin
    if rising_edge(Clock) then
      ------------
      -- FSM logic
      ------------
      if (Reset = '1') then
        Current_State <= Idle;
      else
        case Current_State is
          when Idle =>
            -- Code to wait for event to start timing
          when Start_Count =>
            if (Done_Counting = '1') then
              Current_State <= Done;
            end if;
          when Done =>
            -- Code to take us back to idle presumably
        end case;
      end if;

      ------------------------
      -- Output signal counter
      ------------------------
      if ((Current_State /= Start_Count)
      or (Counter = MAX_COUNT) then
        Counter <= 0;
      else
        Counter <= Counter + 1;
      end if;
  end process;

Done_Counting <= '1' when (Counter = MAX_COUNT) else '0';

Thanks for your post. I mostly agree with you, I'd put the counter
along with the fsm logic, as opposed to the datapath code placeholder,
mostly because the 'passing data' never interacts with the counter, it
is more like an 'fsm helper', so it is more readable if it described
nearby. I however feel that having the control logic separate is
usually helpful. This can happen in any levels, and this does not
mean that there is no control logic in the blocks of code that make up
what one normally considers the datapath. For example, a filter that
is receiving a BT656 video stream and only outputs active video
(filtering out blanking, etc) is seen by a higher level as a datapath
block (filter), but its description may actually be composed of a
datapath/fsm type of design as well. On a related note, lets say that
when one block finishes a task, another block must be activated. One
might feel tempted to connect the done signal of one to the start
signal of the other, and the fsm wouldn't even see that net. However,
I feel that that connection is more an optimization step than good
design practice, simply because a future requirement might introduce
additional conditions - when b1 finishes, then b2 starts, UNLESS blah
blah blah - and inserting this new condition can be more easily added
by modifying the fsm's code, than having to modify the datapath. Of
course, there can be many exceptions, and I share the pragmatic point
of view of just wirte code what works, although I sometimes find
myself relocating code so that it becomes more modular and reusable,
and somehow matches the mental structure that I have of the design,
sometimes even at the slight expense of efficiency.
 
A

Andy

I tend to put timers and state machines in the same process, but a
little differently:

process (clk) is -- omit reset for brevity
variable timer natural range 0 to start_time;
varible state is (start, waiting, done);
begin
if rising_edge(clk) then
timeout := timer - 1 < 0;
if not timeout then
timer := timer - 1;
end if;

case state is
when start=>
if event1 then
timer := start_time;
state := waiting;
end if;
when waiting=>
if timeout then
state := done;
end if;
when done =>
...
end case;
end if;
end process;

I prefer NOT to have code outside of the state machine itself (the
case statement) query a state variable. I prefer the state machine to
tell that code to do something rather than have that code ask the
state machine. This way, if i change up my state names, etc. it has no
impact on other code. Likewise nothing but the state machine is
relevant to state encoding optimizations.

When used with these restrictions, a variable for the state works
great, and it limits scope to the local process anyway.

Andy
 
M

Mike Treseler

Andy said:
I tend to put timers and state machines in the same process, but a
little differently:

Me too, but a little more differently.
process (clk) is -- omit reset for brevity
variable timer natural range 0 to start_time;
varible state is (start, waiting, done);
begin
if rising_edge(clk) then
timeout := timer - 1 < 0;
if not timeout then
timer := timer - 1;
end if;

case state is
when start=>
if event1 then
timer := start_time;
state := waiting;
end if;
when waiting=>
if timeout then
state := done;
end if;
when done =>
...
end case;
end if;
end process;

Nice example of inferring rather than describing the counter wires.
I prefer NOT to have code outside of the state machine itself (the
case statement) query a state variable. I prefer the state machine to
tell that code to do something rather than have that code ask the
state machine. This way, if i change up my state names, etc. it has no
impact on other code. Likewise nothing but the state machine is
relevant to state encoding optimizations.

I also like to keep as much as possible inside the box
and handshake at as high a level as I can stand,
because all of the internal wiring is inferred for free.
When used with these restrictions, a variable for the state works
great, and it limits scope to the local process anyway.

I will note that it works great for the counter as well,
and since Lango put 'philosophy' in the subject,
I will claim that I might just as well
declare all of my counter related registers as
variables in the same process.

So what's the downside?
> KJ wrote:

Which is another way of saying that variable updates are immediate.
This does mean that I can't make a movable output process as in KJ's
example:
> Done_Counting <= '1' when (Counter = MAX_COUNT) else '0';

because a variable, say done_counting_v describes
the D side rather than the Q side of the output.
I can say done_port <= done_v; either at the bottom
of the process or at just the right place inside.

So what's the upside to dumping the
cozy schematic/netlist feel while I'm inside the box?

1. The synthesis code reads like
(choose one: C, perl, python, java, <your_fave>, ...)
so it is easier to test software math models
without tying up a license.
2. It is easy to collect blocks of code (aka: refactor)
into vhdl functions or procedures.
3. Tracing code in modelsim actually does something useful.
4. Wires for free.

-- Mike Treseler
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top