Starter Question on VHDL and Opinion

  • Thread starter Brad Smallridge
  • Start date
B

Brad Smallridge

Hello comp.lang.vhdl,

I am designing a specialized SDRAM controller and have a question about the
best way to set up some timing signals. I generally have a good idea about
what signals have to issued at certain clock times. I set up a rather long
clock cycle, separating the sync from the combo logic like this:

p1: process (fastclk3)
begin
if (fastclk3'event and fastclk3 = '1') then
fastclkcnt <= fastclkcntnext;
mras <= mrasnext;
.... and so on

p2: process (fastclkcnt)
begin

if ( fastclkcnt = 50 ) then
fastclkcntnext <= "011110"; --30
else
fastclkcntnext <= fastclkcnt + 1;
end if;
else
fastclkcntnext <= "000000";

And I was going to finish off with the outputs like this

if (fastclkcnt = 2 ) then -- PRECHARGE
mrasnext <= '0';
mcasnext <= '1';
mwenext <= '0';
mdqmnext <= '1';
elsif (fastclkcnt = 4 ) then -- REFRESH
mrasnext <= '0';
mcasnext <= '1';
mwenext <= '0';
mdqmnext <= '1';
elsif (fastclkcnt = 12 ) then -- REFRESH
mrasnext <= '0';
mcasnext <= '1';
mwenext <= '0';
mdqmnext <= '1';

.... and so on, and quite a few I might add


BUT one author I was reading stated that evertime I use a <= I am generating
another driver for the signal and that I should be doing something like
this:

mrasnext <= '0' when fastclkcnt = 2
'1' when fstclkcnt =4
'1' when fastclkcnt =12

Sort of grouping each output into one statement.
And but, what a drag, I have to re-figure the outputs at each clock every
time I play with a timing change.

Can somebody give me a strategy for 1) producing efficient firmware and 2)
looks clean and 3) can be changed easily.

Thanks in advance.

Brad
 
J

Jonathan Bromley

Brad Smallridge said:
Hello comp.lang.vhdl,

I am designing a specialized SDRAM controller and have a question about the
best way to set up some timing signals. I generally have a good idea about
what signals have to issued at certain clock times. I set up a rather long
clock cycle, separating the sync from the combo logic like this:

p1: process (fastclk3)
begin
if (fastclk3'event and fastclk3 = '1') then
fastclkcnt <= fastclkcntnext;
mras <= mrasnext;
... and so on

It's probably a good idea to provide asynchronous reset on
this kind of process, so that everything is initialised
in a completely reliable way.
p2: process (fastclkcnt)
begin

if ( fastclkcnt = 50 ) then
fastclkcntnext <= "011110"; --30
else
fastclkcntnext <= fastclkcnt + 1;
end if;
else
fastclkcntnext <= "000000";

And I was going to finish off with the outputs like this

if (fastclkcnt = 2 ) then -- PRECHARGE
mrasnext <= '0';
mcasnext <= '1';
mwenext <= '0';
mdqmnext <= '1';
elsif (fastclkcnt = 4 ) then -- REFRESH
mrasnext <= '0';
mcasnext <= '1';
mwenext <= '0';
mdqmnext <= '1';
elsif (fastclkcnt = 12 ) then -- REFRESH
mrasnext <= '0';
mcasnext <= '1';
mwenext <= '0';
mdqmnext <= '1';
... and so on, and quite a few I might add

Some ideas for tidying that up, later in this message.

BUT one author I was reading stated that evertime I use a <= I am generating
another driver for the signal and that I should be doing something like
this:

mrasnext <= '0' when fastclkcnt = 2
'1' when fstclkcnt =4
'1' when fastclkcnt =12

Sort of grouping each output into one statement.
And but, what a drag, I have to re-figure the outputs at each clock every
time I play with a timing change.

Yup. Either you read a duff book, or you misinterpreted it.

You DON'T get a new driver for every <= assignment. You
get a new driver for every process that assigns to the signal.
Multiple processes, multiple drivers. One process, one driver.

So, within a process, you can make as many assignments to a
given signal as you wish, and you get only one driver.
Even better, if you make multiple assignments to the signal
in a single pass through the process (no time delay), then
the last-executed assignment is the only one that does
anything...

process (blah)
begin
foo <= '0'; --- default, correct in most cases
if horrible_set_of_conditions then
foo <= '1'; --- overrides default in this case
...

The only possible confusion here is: what is meant by "a process"?
Obviously an explicit process...begin...end process is indeed
a process. But you also get a process from a simple concurrent
signal assignment at the top level of an architecture:

architecture doohickey of dingbat is
signal S: std_logic;
begin
S <= A and B; -- one driver
...
S <= P or Q; -- another driver
...
process (N, P)
begin
S <= '0'; -- a third driver...
if P = '1' then
S <= not N; -- still the third driver

There are a few other things that can create processes, but
what I've said above is a reasonable start.
Can somebody give me a strategy for 1) producing efficient firmware and 2)
looks clean and 3) can be changed easily.

Now that's a MUCH tougher question :)

Broadly I think you're on the right track, but you may find
that it's better to implement your memory controller as a
classical state machine rather than decoding everything off
a counter. You'll no doubt find plenty of examples in
various books. If your "fast clock" is so fast that you
need to spend many cycles in a given state, it can be
a good idea to create a small auxiliary counter. Here's
an example. There are some declarations missing, but
I hope you get the general idea. You need to
"use ieee.numeric_std.all;" to get the "unsigned" data
type for your counter. This state machine generates
cycles for an entirely fictitious memory device which
requires MemEnable to be asserted for many clocks
before and after the single-clock MemStrobe pulse.

architecture ....
type state_type is (Idle, Precharge, Strobe, Recovery);
signal state: state_type;
signal count: unsigned(3 downto 0); -- 4-bit aux counter
...
begin
...
process (clock, reset)
begin
if reset = '1' then
-- Reset everything.
state <= Idle;
count <= (others => '0');
MemEnable <= '0';
MemStrobe <= '1';
elsif rising_edge(clock) then
-- Clocked logic
count <= count - 1;
case state is
when Idle =>
if Start = '1' then
-- Go into the Precharge state.
state <= Precharge;
MemEnable <= '1';
count <= "0011";
end if; -- Otherwise, stay in Idle
when Precharge =>
if count = 0 then
-- Go into Strobe
state <= Strobe;
MemStrobe <= '1';
end if;
when Strobe =>
-- This state lasts only one clock
MemStrobe <= '0';
state <= Recovery;
count <= "1001";
when Recovery =>
if count = 0 then
state <= idle;
MemEnable <= '0';
end if;
end case;
end if;
end process;
....

Now, I've probably stirred a bit of a hornet's nest here,
because different folk have widely different opinions
about how to do these kinds of thing. Note, for example,
that I've allowed the timeout downcounter to decrement on
every cycle even when it's not in use. I did that because
it centralises the decrement operation in one line of code
(near the top of the process) so the synthesis tool has
no excuse for failing to recognise it as a counter. Some
people prefer to embed the decrement operations in the
relevant places in the code, and accept the risk that
synthesis may not optimise it so well (although in practice
most modern tools are pretty good with that kind of thing).
The obvious alternative is to put the counter in a
separate block of logic and pass "count enable" and "load
enable" signals to it from the state machine process;
personally I find that's a bit clunky, but others think
it's clear and maps well on to hardware.

Note too that I have used a single process, rather than
splitting the state machine into clocked registers and
asynchronous next-state logic. Personally I feel pretty
strongly about this, because IMO it makes the code much
cleaner and saves no end of worry about latches and suchlike,
and it means that I need only set up signals in situations
when they change (because they're all held in flip-flops).
Some people stick with two-process style, claiming it
improves the optimisation, or better fits their mental model
of a state machine. Take your pick.

Finally, if I were coding this myself I would probably have
used variables rather than signals for the state and count,
but as you are just getting to grips with VHDL I thought it
was probably best to keep things straightforward. Some
writers and trainers advise that you always avoid variables
in synthesisable logic; I strongly disagree, but it's
definitely true that using variables requires a bit more
understanding of what's going on.

HTH
--
Jonathan Bromley, Consultant

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

Doulos Ltd. Church Hatch, 22 Market Place, Ringwood, Hampshire, BH24 1AW, UK
Tel: +44 (0)1425 471223 mail: (e-mail address removed)
Fax: +44 (0)1425 471573 Web: http://www.doulos.com

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

Brad Smallridge

Thanks Jonathan. That was really helpful for me because I had read about
state machine design, thought that it might be useful for my design, but had
not been able to grasp exactly how the code would look. Your example code
made it crystal clear on how I should proceed. I don't see the VHDL
solutions right away since I am a VHDL beginner and I use to design in
schematic capture using mostly moving hot one design. Your code idea looks
cleaner and it does seem that the hardware may be significantly reduced
since the counter is much smaller.

I haven't coded it yet, however. I got the SDRAM working last night and
decide to play with it a bit before I try different design approaches. I
also in the future would like to have several states interleaved in the
cycle. Several Reads for example. SDRAM can do that to different banks. I
suppose with your design I should set up a state for each bank? Or perhaps
there is a cleaner way to do that as well.

Your opinion about the separation of combinatorial logic and registers is
one that I am still looking for an answer. To tell you the truth, I
originally coded the SDRAM without such separation, prefering it since it
was more consise. But when it didn't seem to work right away I then
separated. It started working later, do to some other problems that I fixed,
so I don't know if the separation helped or not. Perhaps the documentation
of the design would be better for one type or the other, I don't know. I am
using a Cypress 39K if that helps anyone out there give me an idea.

Thanks to for clearing up the concept of drivers, at least, within one
process. As a beginner I still don't understand what a process entails.
Should one have a lot of smaller processes? Should I have a process for each
output like one book seem to suggest. Very confusing. I wish they would
invent a language where everybody was forced to be on the same page. VHDL
seems to be the antithesis of this idea. What a nightmare it must be to
write the compiler, er, synthesizer? for this language.

Brad Smallridge
 
B

Brian Drummond

Jonathan's suggestions are excellent, I just want to add a minor style
suggestion...

Instead of

the output assignments can be condensed as follows...

-- within a process, only one driver for mctrl is created
mctrl <= "1111"; -- default case
if (fastclkcnt = 2 ) then -- PRECHARGE
-- if (state = PRECHARGE ) then -- cleaner version
mctrl <= "0101";
elsif (fastclkcnt = 4 ) then -- REFRESH
mctrl <= "0101";
elsif (fastclkcnt = 12 ) then -- REFRESH
mctrl <= "0101";
elsif...

Outside the process, separate parallel assignments are performed only
once.

mrasnext <= mctrl(3);
mcasnext <= mctrl(2);
mwenext <= mctrl(1);
mdqmnext <= mctrl(0);

This can make the state machine tidier and easier to follow.
(Since mctrl has to be visible outside the process, it is a signal,
e.g. std_logic_vector)

- Brian
 

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

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top