Order of the synchronous operations

Consider the very simple VHDL code at the end of the message. For each clock cycle two operations are done:

1) A counter is incremented; 2) The bit 0 of the counter is checked. If it's '0', an output flag is triggered.

If you simulate the post-translated model of this module with the ModelSim starter edition shipped with the last WebPack, the behaviour is correct (or at least is what I expect to be): the flag is triggered at the very first clock cycle. When you exit from reset CNT is zero, so that at the first clock its first bit is zero too. If you instead simulate the behavioural model (i.e. plain VHDL) of the same module, the flag is triggered at the *second* clock! Apparently the simulator considers the increment operation as it were synchronous, so that when CNT(0) is checked, it has already the value 1. In fact, if you put the increment statement *after* the if statement, the behaviour changes again and the flag is correctly triggered at the first clock.

My question is quite simple: is this a simulator bug, or did I always misintepreted the synchronous circuits at their own very basic level?

Thank you!

library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity test is Port ( CLK : in STD_LOGIC; O : out STD_LOGIC; RESET : in STD_LOGIC); end test;

architecture Behavioral of test is begin main: process( CLK, reset) variable CNT: std_logic_vector(15 downto 0); begin if RESET='1' then CNT := X"0000"; O

Reply to
dalai lamah
Loading thread data ...

Variable assignments are processed sequentially while signal assignments are processed synchronously... at least in principle. Since 'CNT' is a variable, it incrementation becomes effective immediately and the check done after the incrementation occurs on the incremented value.

If you used a signal instead, the test would happen on the CNT value that was in effect before the process was evaluated - signal assignments become effective after an event's processing (re-evaluate all affected processes) is completed.

With the code you posted, the correct behavior is for the flag to be set on the first cycle... and if you put the increment after the test, the correct behavior becomes the flag being set on the second cycle. If you used a signal instead of a variable, the test result would be independent from its position relative to assignment operations since the test would be done on inputs as of the moment the process' evaluation was triggered - the previous cycle's output.

Since not all synthesis and simulation tools agree on how to deal with variables in synthesizable code, some weird bugs can come up so it is generally better to use signals which more closely (less ambiguously) represents how synchronous hardware works.

Reply to
Daniel S.

Not after reset, though. After reset, your output flag is stuck at zero.

Really? That sounds wrong to me.

Exactly as it should.

Also exactly as it should.

Not in the behavioural case.

As Daniel S. said, it's not "basic" - people often get confused about the behaviour of variables in VHDL clocked processes. The action of the behavioural VHDL that you describe is exactly as it should be. The question that needs answering is: why does the post- synthesis simulation not match it? On the first clock after reset, CNT:=CNT+1 immediately updates CNT to X"0001". Consequently the assignment to O does not happen. On the next clock, though, CNT is incremented to X"0002" and O should be set. Immediately after the assignment CNT:=CNT+1, the variable CNT represents the next-state value of CNT - in other words, the value presented to the D-inputs of the CNT register. Output O should be synchronously set if this next-state value has '0' in its LSB. If your observation is correct, then the Xilinx simulation or place-and-route tools are in error and you should raise a bug report.

--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
 Click to see the full signature
Reply to
Jonathan Bromley

Un bel giorno Daniel S. digitò:

I didn't know that! I (almost) always use signals instead of variables, therefore I've never had a chance to notice the difference.

I agree. I barely can see how variables can be implemented in a real design, if they work that way; probably most of the times the tools just convert them as signals, like Xilinx MAP and PAR apparently do.

Thank you!

--
emboliaschizoide.splinder.com
Reply to
dalai lamah

Originally, variables were intended only for testbench usage to simplify dynamic sequential test sequences.

As far as synthesis is concerned, both signals and variables become nets and FFs, the only difference between the two is how assignments are handled. Signals get their new value after the trigger event has been processed while variables get their new value immediately after assignment. For synthesizable constructs, the tools simply duplicate or cascade logic and FFs to mimic the sequential variable assignments.

BTW, in my original reply I overlooked the "='0'" and wrote thinking your were checking the LSB for '1' so everything is backwards.

Reply to
Daniel S.

Here are my examples of designs using variables exclusively:

formatting link
These examples have been tested on almost all the the FPGA synthesis tools, and these agree perfectly on how to deal with variables. Modelsim also agrees. Do you have a single example to support your assertion above?

-- Mike Treseler

Reply to
Mike Treseler

Originally, VHDL was intended to document the *behavior* of ASICs. Simulators based on VHDL came later.

-- Mike Treseler

Reply to
Mike Treseler

Un bel giorno Mike Treseler digitò:

If you try my example with the post-translated or the post-PAR model generated by Xilinx tools, you will get the same result by putting the increment operation before or after the if statement:

[...] elsif CLK='1' and CLK'event then CNT := CNT+1; if CNT(0)='0' then O
Reply to
dalai lamah

I do not use variables in my designs, none of the people I have worked with and none of the projects I have worked on so far used variables in synthesizable code either.

Variables for synthesis might work but none of the people I have ever worked with ever recommended it and many have reported problems of one sort or another with that - variables used to be testbench-only after all. This was years ago but the general mindset for the major projects I have worked on is to never use a feature that once proved to cause unnecessary simulation or synthesis problems... at the very least not until many tool revisions after the last known related bugs had been fixed - researcher hate hunting down tool bugs and ASIC people hate bumping tape-outs due to last minute bugs or scrapping masks/wafers because third-party tool bugs related to some new feature were found a little too late.

Even if variables have been fully supported by all simulation and synthesis tools for the last couple of years, it does not reduce their potential for inducing unnecessary confusion - newbies already get confused enough with simple HDL-based RTL design as it is and I cannot think of any good reason (other than academic) to use variables instead of signals for synthesis.

IMO, variables for synthesis are a mostly unnecessary and potentially confusing convenience.

Reply to
Daniel S.
[...]

You are not alone in your point of view, but I profoundly disagree and I think you are missing some important opportunities for simplifying and clarifying your RTL. If you restrict yourself only to signals, you are in effect laying-out every register in the design by hand.

--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
 Click to see the full signature
Reply to
Jonathan Bromley

This is simply untrue. They give completely different results.

At least, it was untrue when I tried it using ISE 8.2i-SP2 (I haven't got around to upgrading to version 9 yet). In both cases ISE gave me exactly the result I would expect.

Please double-check that you haven't done something strange. In particular, please check that your test stimulus does not release reset too close to an active clock edge. The counter and flip-flop devices need some recovery time after reset is released before they will respond reliably to a clock, but in a behavioural RTL simulation that recovery time is zero.

--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
 Click to see the full signature
Reply to
Jonathan Bromley

Yes. Quartus and ISE both give different results for the cases cited. The "count last" case puts the counter in front of the registers:

formatting link
The "count first" case puts the counter in the middle:
formatting link

-- Mike Treseler

Reply to
Mike Treseler

I use them all the time. As a practical aid to clear code ;-)

Here is a simple process which increments/decrements a count. Much simpler with a variable.

process (Clk) variable delta: integer range -1 to +1; begin if rising_edge(Clk) then delta := 0; if Write then .....; delta := delta+1; end if; if Read then .....; delta := delta-1; end if; Count

Reply to
Tim

Explicitly laying down as many of the registers from an RTL design as possible is a nearly universally accepted design practice even if it does not yield the shortest/cleanest/simplest code. It also cuts down on the number of nameless nets in the output.

What sort and how much of this code simplification are we talking about? For the stuff I can think of, it simply boils down to splitting the combinational and register parts... this can indeed account for a fair number of extra lines (four lines to implement a counter instead of two) but is otherwise trivial.

Reply to
Daniel S.

On Sat, 14 Apr 2007 23:33:11 -0400, "Daniel S." wrote:

Not "universally accepted" by me. I don't like designing netlists. I have synthesis tools to do that for me. I also, of course, reserve the right to design a netlist when it suits my purpose - in particular, for certain corners of a design where procedural code is not a clear description of what's going on, and a structural description makes more sense. Those places are few.

I don't really see this as relevant. *Any* design will have many renamed nets after synthesis.

Here are my key reasons for using variables and heavily procedural descriptions of logic:

1) Variables that represent intermediate subexpressions in combinational logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Useful in both combinational and sequential processes. Pre-calculating a complicated subexpression is often useful for the sake of code clarity, and may also be useful as an optimisation hint. Here's an example from something I wrote yesterday (in Verilog, but you get the idea): // dont_relinquish = 0; // find what the most recent requester wants req_A = 0; req_W = 0; if (last_master[0]) begin req_A = M0_ADDR; req_W = M0_WRITE; end else if (last_master[1]) begin req_A = M1_ADDR; req_W = M1_WRITE; end // any reason why the current master must stay in control? if (back_to_back) begin if ( atomic_bursts && (req_A == last_addr + 1'b1) && (req_W == last_write) ) // next step in a burst transfer dont_relinquish = 1; if ( atomic_rmw && (req_A == last_addr) && req_W && !last_write ) // next step of a read-modify-write dont_relinquish = 1; end if (!dont_relinquish) // start arbitration for a new master... // Variables dont_relinquish, req_A, req_W (and many others) are relevant only within this clocked process. Without them, the final "if" test in this code snippet would have been grotesque. If those variables had instead been signals, they would have unacceptably cluttered the declaration of the architecture and required another process (or continuous assignment) to compute them. As variables they can be hidden in the process, where they belong. By giving these variables a new value at the start of the process, as I've done here, I can be entirely confident that they synthesise to combinational logic with neither latch nor flipflop. 2) Variables that represent internal state of a process, not relevant to the rest of the architecture ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Similar idea to (1), but using the value calculated on a previous pass through the clocked process - so the variable synthesises to a flop Variables "last_write", "last_addr" and "back_to_back" in my code fragment (above) work like this. Again I don't want the top level of my architecture cluttered with signals that are relevant only to a single process. (Admittedly you can do that with VHDL BLOCKs as well, but it's much harder work.) 3) Visibility of next-state value ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here's a paraphrase of how I prefer to write state machines with registered outputs:

process (clock, reset) variable state: state_type; begin if reset = '1' then state := idle; outputs if start = '1' then state := run; end if; ... end case; --- variable "state" now holds NEXT-state value --- calculate outputs case state is when idle =>

outputs outputs

Reply to
Jonathan Bromley

I only said that being more explicit reduces that number, not prevent any from appearing.

These two sound a lot like two sides of the "keep related code together" coin. For the synchronous stuff, VHDL allows local signals.

Looks to me like you are adding the state machine's delay to all your output logic by doing this way... I do similar stuff but design the pipeline to account for the cycle delay between the state machine and synchronous output logic to shave nanoseconds off my delays, no need to pollute the architecture with a 'next state' signal here but it does make other things (like flow control) trickier.

The distance between using modular signals and local variables is very short and in now way comparable to the effort level between C/C++ and ASM/bin... different coding styles != significantly different effort.

But yes, in the end only the final results matter.

Reply to
Daniel S.

...

Absolutely. The single most important contributor to readable, maintainable code in *any* language, especially when combined with "keep irrelevant stuff hidden".

Really? Only in blocks and generates, I think. Personally I find blocks to be clumsy and hard work, and there are surely more synth tools that have trouble with blocks than have trouble with variables.

[...]

Indeed so. It is not usually a problem, as FSM output decoding logic tends to be rather simple. But I agree that this is an issue that needs to be watched.

Pipeline control is always tricky. The big benefit I perceive in "my" coding style for FSMs is that it faithfully preserves the logic of the state diagram, in a very maintainable way, whilst adding output registers for free. I certainly don't dispute that sometimes you have to do special tweaks to the RTL to achieve timing goals.

[...]

Sure; and, as I was at some pains to say, I'm moderately happy to agree to differ. And, of course, whatever I say about coding styles and idioms, I'll always find a situation where I prefer - or need - to break my own guidelines.

Thanks

--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
 Click to see the full signature
Reply to
Jonathan Bromley

Un bel giorno Jonathan Bromley digitò:

You are right, probably I made some confusion with the various tests. I tried again, and the post-translated model gave the expected results in both cases (increment before and increment after).

Anyway, I'm still very skeptical about the use of variables in synchronous designs. Take some code like this:

variable x: integer:=1; [...] x := x*(x+1); if x

Reply to
dalai lamah

Variables in a single process provide an abstraction that I have not been able to duplicate using signals in a single architecture. Sometimes I like to suspend time in order to describe value creation by successive approximation.

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

accum_s But yes, in the end only the final results matter.

Indeed. Everyone's brain is wired up a little differently and I don't touch working code unless I have to.

-- Mike Treseler

Reply to
Mike Treseler

I was skeptical as well, until I found some good examples and tried them with my own tools.

Note that variables must be declared inside a process. Initialization is useful for simulation, but variables need to be reset for synthesis. ______________ my_process: process(reset, clock) is variable x: integer; begin if reset = '1' then x := 1; elsif rising_edge(clock) then ______________

Synthesis creates a new node and a new block of logic each time a variable is updated.

I sometimes use this feature to "show my work" in creating a complex value, but I have never found a reason to use the intermediate values as in the example above.

I update each variable once per clock tick using a template like this:

-------------------------------------------------------------------------------

-- Process Template -- Always exactly the same:

------------------------------------------------------------------------------- begin -- process template if reset = '1' then -- Assumes synched trailing edge reset init_regs; -- procedure elsif rising_edge(clock) then update_regs; -- procedure end if; -- Synchronous init optional update_ports; -- procedure end process sync_template; end architecture synth;

-------------------------------------------------------------------------------

Most variables are internal registers but others have their values assigned to a port once per clock tick.

I have not found this to be the case. All I can say is try it and see.

-- Mike Treseler

Reply to
Mike Treseler

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.