Why not mix concurrent and synchronous assignments in the sa

  • Thread starter Per Christian Corneliusse
  • Start date
P

Per Christian Corneliusse

Guest
I've been working as a VHDL designer for about one year now, and I also have some experience with Verilog. One aspect of VHDL that I don't like is that there is no way to mix concurrent and synchronous assignments in the same process. I you want to create a Mealy-type state machine, you have to create one synchronous process and one concurrent process. This takes up a lot of additional space in the code since the concurrent process has to replicate the process-case-when structure. And worse, this will (especially for large state machines) place synchronous and concurrent assignments for each state on very different places in the code, making it hard to read and maintain.

Creating additional _next-signals that are written to in the concurrent process and back to the corresponding synchronous signals in the synchronous process (e.g., state <= state_next) can be a more elegant solution, especially if most of the outputs of the state machine are concurrent. But I imagine a language with a separate concurrent assignment operator would be a lot more elegant, see a hypothetical example below:

process (clk_i'rising) -- Defines clock for synchronous assignments
begin
case state is
when s_idle =>
if req_i = '1' then
fifo_read %= '1'; -- Concurrent assignment
state <= s_read_data; -- Synchronous assignment
end if;
(...)
end case;
end process;

However, I haven't really thought this completely through and the fact that Verilog has the same problem leads me to believe that there's some good reason for not doing this that I haven't thought of. Any insights?
 
On Sunday, September 2, 2012 7:04:43 AM UTC-4, Per Christian Corneliussen wrote:
I've been working as a VHDL designer for about one year now, and I also have some experience with Verilog. One aspect of VHDL that I don't like is that there is no way to mix concurrent and synchronous assignments in the same process. I you want to create a Mealy-type state machine, you have to create one synchronous process and one concurrent process. This takes up a lot of additional space in the code since the concurrent process has to replicate the process-case-when structure. And worse, this will (especially for large state machines) place synchronous and concurrent assignments for each state on very different places in the code, making it hard to read and maintain. Creating additional _next-signals that are written to in the concurrent process and back to the corresponding synchronous signals in the synchronous process (e.g., state <= state_next) can be a more elegant solution, especially if most of the outputs of the state machine are concurrent. But I imagine a language with a separate concurrent assignment operator would be a lot more elegant, see a hypothetical example below: process (clk_i'rising) -- Defines clock for synchronous assignments begin case state is when s_idle => if req_i = '1' then fifo_read %= '1'; -- Concurrent assignment state <= s_read_data; -- Synchronous assignment end if; (...) end case; end process; However, I haven't really thought this completely through and the fact that Verilog has the same problem leads me to believe that there's some good reason for not doing this that I haven't thought of. Any insights?
- Stop thinking in terms of 'Mealy' and 'Moore'.
- Toss out books that pontificate on how 'two process' or 'three process' is the way to design
- Code up the functional behavior you want to implement so that it is functionally correct
- Start to use variables when you get to things where you would otherwise need to copy/paste code inside your process.

Your aim here is to have the following structure:
- Single clocked process with all of the complex logic
- Possibly some loose concurrent statements for things that need to not wait a clock cycle. When you've got it down correctly, you'll find that the logic in these concurrent statements is not complex. If you find yourself longing for 'if' or 'case' statements in these concurrent statements, then you need to go back to your single clocked process and do more work.

In the example you posted, I would modify if at follows:

process (clk_i) -- Defines clock for synchronous assignments
begin
wait until rising_edge(clk_i); -- Or use "if rising_edge(clk_i) then..."
case state is
when s_idle =>
if req_i = '1' then
-- Move out fifo_read %= '1'; -- Concurrent assignment
state <= s_read_data; -- Synchronous assignment
end if;
(...)
end case;
end process

fifo_read <= to_std_logic(state = s_idle) and req_i;

Where 'to_std_logic' is a function that you can write that simply takes as input a boolean and returns a std_ulogic.

Kevin Jennings
 
On 9/2/2012 9:34 PM, KJ wrote:
On Sunday, September 2, 2012 7:04:43 AM UTC-4, Per Christian Corneliussen wrote:
I've been working as a VHDL designer for about one year now, and I also have some experience with Verilog. One aspect of VHDL that I don't like is that there is no way to mix concurrent and synchronous assignments in the same process. I you want to create a Mealy-type state machine, you have to create one synchronous process and one concurrent process. This takes up a lot of additional space in the code since the concurrent process has to replicate the process-case-when structure. And worse, this will (especially for large state machines) place synchronous and concurrent assignments for each state on very different places in the code, making it hard to read and maintain. Creating additional _next-signals that are written to in the concurrent process and back to the corresponding synchronous signals in the synchronous process (e.g., state<= state_next) can be a more elegant solution, especially if most of the outputs of the state machine are concurrent. But I imagine a language wi
th a separate concurrent assignment operator would be a lot more elegant, see a hypothetical example below: process (clk_i'rising) -- Defines clock for synchronous assignments begin case state is when s_idle => if req_i = '1' then fifo_read %= '1'; -- Concurrent assignment state<= s_read_data; -- Synchronous assignment end if; (...) end case; end process; However, I haven't really thought this completely through and the fact that Verilog has the same problem leads me to believe that there's some good reason for not doing this that I haven't thought of. Any insights?

- Stop thinking in terms of 'Mealy' and 'Moore'.
- Toss out books that pontificate on how 'two process' or 'three process' is the way to design
- Code up the functional behavior you want to implement so that it is functionally correct
- Start to use variables when you get to things where you would otherwise need to copy/paste code inside your process.

Your aim here is to have the following structure:
- Single clocked process with all of the complex logic
- Possibly some loose concurrent statements for things that need to not wait a clock cycle. When you've got it down correctly, you'll find that the logic in these concurrent statements is not complex. If you find yourself longing for 'if' or 'case' statements in these concurrent statements, then you need to go back to your single clocked process and do more work.

In the example you posted, I would modify if at follows:

process (clk_i) -- Defines clock for synchronous assignments
begin
wait until rising_edge(clk_i); -- Or use "if rising_edge(clk_i) then..."
case state is
when s_idle =
if req_i = '1' then
-- Move out fifo_read %= '1'; -- Concurrent assignment
state<= s_read_data; -- Synchronous assignment
end if;
(...)
end case;
end process

fifo_read<= to_std_logic(state = s_idle) and req_i;

Where 'to_std_logic' is a function that you can write that simply takes as input a boolean and returns a std_ulogic.

Kevin Jennings
I'm at a loss here. First, I don't know what the %= symbols are
supposed to mean and I can't find it anywhere. Secondly, if the OP
intends this to mean a concurrent signal assignment, how could that
possibly work correctly in simulation? I take it to mean that all of
the conditionals define the logic without regard to the clocked process
qualifier. But in simulation how would the concurrent statement be
reached when the logic inputs change? The concurrent assignment would
not be executed until the next clock edge resulting in a big mismatch
between simulation and expected behavior, not to mention synthesis.

Am I missing something?

Rick
 
You can drive combinatorial outputs from synchronous process registers if you use variables for the registers:

process (clk, rst) is
-- variable declarations for registers
begin
if rst then -- boolean rst
state := init;
elsif rising_edge(clk) then
case state is
when...
state := ...
end case;
end if;
-- combinatorial outputs
fifo_rd <= '0'; -- default assignment to avoid latches
case state is
when s_idle =>
fifo_rd <= '1';
when others =>
null;
end case;
end process;

However, you cannot use a synchronous process to create a combinatorial output from input signals (as mentioned by rickman; the process would also have to execute when those signals changed too).

I also echo what KJ said. Focus on the cycle-based behavior you need, and code that. Too many textbooks focus on how to code registers and combinatorial logic. If you ever use retiming or pipelining optimizations, your finely crafted RTL that clearly shows where registers are will no longer tell you where the registers are anyway.

Once you do that, then you will understand that it is not the assignment or variable/signal that determines whether a register is inferred. Just like latches are inferred when a combinatorial process has to remember a previously assigned value, registers are inferred whenever a clocked process has to remember a previously assigned value. When you use variables in a synchronous process, you you have control over whether a register is inferred or not.

Andy
 
On 9/7/2012 9:50 AM, Andy wrote:
You can drive combinatorial outputs from synchronous process registers if you use variables for the registers:

process (clk, rst) is
-- variable declarations for registers
begin
if rst then -- boolean rst
state := init;
elsif rising_edge(clk) then
case state is
when...
state := ...
end case;
end if;
-- combinatorial outputs
fifo_rd<= '0'; -- default assignment to avoid latches
case state is
when s_idle =
fifo_rd<= '1';
when others =
null;
end case;
end process;

However, you cannot use a synchronous process to create a combinatorial output from input signals (as mentioned by rickman; the process would also have to execute when those signals changed too).

I also echo what KJ said. Focus on the cycle-based behavior you need, and code that. Too many textbooks focus on how to code registers and combinatorial logic. If you ever use retiming or pipelining optimizations, your finely crafted RTL that clearly shows where registers are will no longer tell you where the registers are anyway.

Once you do that, then you will understand that it is not the assignment or variable/signal that determines whether a register is inferred. Just like latches are inferred when a combinatorial process has to remember a previously assigned value, registers are inferred whenever a clocked process has to remember a previously assigned value. When you use variables in a synchronous process, you you have control over whether a register is inferred or not.

Andy
This is useful info to know. I'm not sure I will ever code this way.
Partly because it can result in more verbose code if the state registers
are needed outside of the process and partly because others who aren't
familiar with the limitations of this technique may have difficulty
making mods. But it is a potentially useful thing to know, if nothing
else because it sheds some additional light on the language.

Rick
 
Rick,

Yes, unfortunately, the techniques of using variables for registers and/or combinatorial logic is not widely known or taught, and even less so, the ability to create combinatorial output logic from registered variables in a synchronous process is rarely covered.

One side thought: if a state variable (signal) is needed outside its process, I generally view that as "a bad thing". State variables should be "private", allowing changes to the state machine itself (states, transitions, etc.., but not outputs) to remain isolated from external consequences. If another process needs to know when something should be done, the state machine should create an output signal to communicate exactly that (and nothing more) to the other process.

To mitigate the public nature of signals in two-process FSM implementations (on those rare occasions when they are a good idea), it is preferable to include both processes (combinatorial and clocked) in a block statement (with no other processes), and declare the state signal within that block. This allows access by the two processes that really need the state information, while hiding it from other processes within the architecture. Blocks can be thought of as a light-weight entity/architecture, with transparent access to the enclosing scope (e.g. the architecture, generate or block that encloses the block). Though blocks are allowed to have ports/maps, if you need to use a port map on a block, then you probably need to use an entity/architecture instead.

Andy
 
On 9/10/2012 10:33 AM, Andy wrote:
Rick,

Yes, unfortunately, the techniques of using variables for registers and/or combinatorial logic is not widely known or taught, and even less so, the ability to create combinatorial output logic from registered variables in a synchronous process is rarely covered.

One side thought: if a state variable (signal) is needed outside its process, I generally view that as "a bad thing". State variables should be "private", allowing changes to the state machine itself (states, transitions, etc., but not outputs) to remain isolated from external consequences. If another process needs to know when something should be done, the state machine should create an output signal to communicate exactly that (and nothing more) to the other process.

To mitigate the public nature of signals in two-process FSM implementations (on those rare occasions when they are a good idea), it is preferable to include both processes (combinatorial and clocked) in a block statement (with no other processes), and declare the state signal within that block. This allows access by the two processes that really need the state information, while hiding it from other processes within the architecture. Blocks can be thought of as a light-weight entity/architecture, with transparent access to the enclosing scope (e.g. the architecture, generate or block that encloses the block). Though blocks are allowed to have ports/maps, if you need to use a port map on a block, then you probably need to use an entity/architecture instead.

Andy
Hi Andy,

Thanks for the suggestions. I have never structured my code quite that
much before, but it sounds like a good habit. I typically have no
trouble understanding my code and I expect most others could read it
easily as well, but every once in a while I have a module that I just
can't seem to code cleanly. I think this sort of structure would make
it more understandable.

This block of code was the source of an error that made it through
simulation and was caught in bench testing, but I don't like for things
to get that far. It's better if the customer never sees your bugs.

Rick
 
On 9/20/2012 9:56 AM, Per Christian Corneliussen wrote:
On Tuesday, September 4, 2012 9:40:03 PM UTC+2, rickman wrote:

I'm at a loss here. First, I don't know what the %= symbols are
supposed to mean and I can't find it anywhere.

I should have been more clear. The code example I gave is not supposed to be valid VHDL, it's in a hypothetical language with a different syntax. The idea is that<= is the synchronous assignment operator and %= the concurrent assignment operator. The first line ("process (clk_i'rising)") defines the clock to be used for synchronous assignments in the process. I realize that this wouldn't work with the current simulation model. A simulator for this language would need to run through the process whenever any of the signals used in concurrent assignments change (ignoring synchronous assignments) then again on positive clock edges, doing both synchronous and concurrent assignments.

Note that KJ's attempt to fix my code to valid VHDL illustrates the problem I tried to describe in the original post; moving the fifo_read assignment out of the process like this makes the code harder to read (especially if the synchronous process was 500+ lines.)

What I was hoping for here was some ideas to why this kind of language would be bad, or perhaps confirmation that it would be better.
Not trying to be critical, but if you don't understand why you can't
generally create async assignments in a sync process, then you don't
understand VHDL. The limitations of VHDL mostly are traceable to how to
corresponds to hardware.

Since you are using a hypothetical language, I can't say for sure, but I
would assume the statement process (clk_i'rising) means the same thing
as in VHDL, trigger the process on the clock rising edge. That means
the process won't run when inputs to the async assignment change and the
simulation won't be valid.

Of course as Andy indicated, the case where you can create async
assignments in a sync process is when the async assignments only depend
on variables that are being assigned in the process. Although the async
signals depend on the variables, the variables depend on the clock and
have no delta delay, so are updated immediately. So in effect, the
signals do depend on the clock, just indirectly.

The one limitation of doing this is if you need the state variable
outside the process, you have to assign it to a signal requiring more
code. Also, this signal will have the same timing as the async signals
which, theoretically, might require logic. In a pre-layout simulation
this would not show delta delays between the sync and async signals.
Not sure if this really matters to most simulations. I prefer for my
simulations to look as much like real logic as possible.

Rick
 
On Tuesday, September 4, 2012 9:40:03 PM UTC+2, rickman wrote:

I'm at a loss here. First, I don't know what the %= symbols are
supposed to mean and I can't find it anywhere.
I should have been more clear. The code example I gave is not supposed to be valid VHDL, it's in a hypothetical language with a different syntax. The idea is that <= is the synchronous assignment operator and %= the concurrent assignment operator. The first line ("process (clk_i'rising)") defines the clock to be used for synchronous assignments in the process. I realize that this wouldn't work with the current simulation model. A simulator for this language would need to run through the process whenever any of the signals used in concurrent assignments change (ignoring synchronous assignments) then again on positive clock edges, doing both synchronous and concurrent assignments.

Note that KJ's attempt to fix my code to valid VHDL illustrates the problem I tried to describe in the original post; moving the fifo_read assignment out of the process like this makes the code harder to read (especially if the synchronous process was 500+ lines.)

What I was hoping for here was some ideas to why this kind of language would be bad, or perhaps confirmation that it would be better.
 
By having some statements that are only executed on some events, and other statements that are only executed on other events, you are going to have a devil of a time avoiding latches when the process triggers but the concurrent assignment is not executed.

Memory elements (latches, registers, RAM, etc.) are inferred when the description requires remembering a value assigned at a previous simulation time or delta. This usually happens when an assignment is not (or has not yet, in the case of a variable assignment) executed during a process's execution cycle (wake-up to suspend). If the process is combinatorial, a latch is inferred to remember the value. If the process is clocked, a register is inferred to remember the value.

In your example, the sensitiviy list (apparently you want it to be implied) would also need to contain the state signal.

If your example had a reset for the registers, then you would have some conditional logic surrounding the concurrent statement that would have to be ignored for the concurrent assignment (e.g. you want to concurrently assign it whether reset is active or not). Yet you also have conditional logic (the case statement) that you don't want to ignore. And yet when the state is not s_idle, you want to concurrently assign it to something, right? (to avoid the latch!)

Hopefully you can see what a mine-field of issues this seemingly simple, little statement would create...

VHDL (and verilog) synthesis starts to make a lot more sense when you understand how hardware is inferred from code BEHAVIOR, instead of from code structure. Unfortunately, most texts focus on synthesis of code structure.

Andy
 
On Thursday, September 20, 2012 7:08:21 PM UTC+2, Andy wrote:
By having some statements that are only executed on some events, and other statements that are only executed on other events, you are going to have a devil of a time avoiding latches when the process triggers but the concurrent assignment is not executed.
The idea is that the concurrent assignments are always executed when the process triggers, while the synchronous assignments are only executed on clock events. Do you still believe this would make it harder to avoid latches? Synchronous signal assignments in VHDL always result in registers, so that these assignments aren't always executed when the process triggers shouldn't really create any problems as far as I can see.

In your example, the sensitiviy list (apparently you want it to be implied) would also need to contain the state signal.
Yes. And yes, the idea is that the sensitivity list is implied, much like process(all) in VHDL-2008.

If your example had a reset for the registers, then you would have some conditional logic surrounding the concurrent statement that would have to be ignored for the concurrent assignment (e.g. you want to concurrently assign it whether reset is active or not). Yet you also have conditional logic (the case statement) that you don't want to ignore. And yet when the state is not s_idle, you want to concurrently assign it to something, right? (to avoid the latch!)
Good point, I didn't think of that. I think it should still be possible to model resets though, although you would have to use a different coding style. Specifically you could add an independent "if reset then" at the end of the process where you do your resets. These assignments would override any assignments that may have been executed earlier in the process.

VHDL (and verilog) synthesis starts to make a lot more sense when you understand how hardware is inferred from code BEHAVIOR, instead of from code structure.
I'm not sure what you mean. I recently realized that it actually is possible to mix concurrent and sequential assignments in a single process in VHDL. See the example below.

process (clk, state, a)
begin
case state is
when s_one =>
x <= '1';
if rising_edge(clk) then
state <= s_two;
end if;
when s_two =>
x <= a;
if rising_edge(clk) then
state <= s_one;
end if;
end case;
end process;

This isn't very pretty, but it's close to what I want, I guess. This is valid VHDL, it can be simulated and works as expected in Modelsim, with no warnings. The problem is that this code won't synthesize, at least not with Altera's tools. In the documentation for the error message this code produces they have written that "You might be attempting to infer a register using a coding style that Integrated Synthesis does not support".

By the way, contrary to what you wrote earlier, this works:

process (clk, state, a)
begin
if rising_edge(clk) then
case state is
when s_one =>
state <= s_two;
when s_two =>
state <= s_one;
end case;
end if;
case state is
when s_one => x <= '1';
when s_two => x <= a;
end case;
end process;

It will compile fine with Altera's tools with no warnings and the generated circuit looks like what I'm expecting. I wouldn't be surprised if it doesn't compile in other synthesis tools, however, because frankly, the code structure does make a difference.
 
When you start mixing sensitivity lists, beware that synthesis completely ignores them, but the simulator faithfully follows them. The result is often a mismatch between the behavior of the RTL model and that of the resulting gate level model. Your simulation of the RTL may convince you that what you have desinged works fine, but the synthesized HW may not.

My point about latches in your original example was that if you were not in the right state (or were in reset), there was no concurrent assignment to execute. That would create a latch on the concurrently assigned signal. The solution would be simple (default concurrent assignments at the beginning of the process, before the if-reset-then statement). This is also the best way to avoid latches in standard combinatorial processes.

Your proposed execution model differs very significantly from how a normal executable SW model would execute. Having specific parts of the code that "magically" execute (or not) under specific, implied, conditions makes the code's behavior much less evident to the reader. And if the behavior is much less evident, it is much more likely to be incorrect.

If you really want to mix concurrent and sequential behavior in the same context, consider the two-process (combinatorial and clocked) model, with all of the "logic" in the combinatorial process, and only the registers in the clocked process. It would work well for what you want to do, it is available today, and it does not require adding confusing and very difficult to implement new features to the language. I would recommend pairing the two processes in a block statement, and locally declare those signals that are only accessed by the two processes. This keeps the "external" signals separated from the "internal" signals, and promotes maintainability by preventing unexpected external dependencies on otherwise internal signals. This is one reason I prefer to use variables in processes, because of the inability to access them externally. The only time I use a signal is when one process needs to talk to another.

Andy
 
On Friday, September 21, 2012 3:11:50 PM UTC+2, Andy wrote:
When you start mixing sensitivity lists, beware that synthesis completely ignores them, but the simulator faithfully follows them. The result is often a mismatch between the behavior of the RTL model and that of the resulting gate level model. Your simulation of the RTL may convince you that what you have desinged works fine, but the synthesized HW may not.
Yeah, I think you're right, it's a bad idea to use weird syntax like this with VHDL. However, in this particular case, with this particular synthesis tool, it did work. (I manually checked the gate-level output of the synthesis.)

My point about latches in your original example was that if you were not in the right state (or were in reset), there was no concurrent assignment to execute. That would create a latch on the concurrently assigned signal. The solution would be simple (default concurrent assignments at the beginning of the process, before the if-reset-then statement).
Sure, I understood your point perfectly the first time. Having default assignments in the beginning would work too I suppose.

Having specific parts of the code that "magically" execute (or not) under specific, implied, conditions makes the code's behavior much less evident to the reader. And if the behavior is much less evident, it is much more likely to be incorrect.
I guess I would agree with that in the general case. But is this really that hard? I mean, the only "magic condition" here is the clock event, and there's different assignment operators for the two types of assignments. I don't see the problem.

If you really want to mix concurrent and sequential behavior in the same context, consider the two-process (combinatorial and clocked) model, with all of the "logic" in the combinatorial process, and only the registers in the clocked process. It would work well for what you want to do, it is available today, and it does not require adding confusing and very difficult to implement new features to the language.
Yeah, I mentioned this approach in the original post, actually. I do this sometimes, it can be a good way to design. But if most of the signals are synchronous rather than concurrent, it can get quite messy. I realize that this language doesn't exist and that VHDL is what we have now, but I still don't think it hurts to discuss any weaknesses of the language and what a better language would look like.
 

Welcome to EDABoard.com

Sponsor

Back
Top