Problem with signal drivers

  • Thread starter Christoph M. Wintersteiger
  • Start date
C

Christoph M. Wintersteiger

Hello there!

I'm not a pro in vhdl, just another student who has problems getting things
working like the teachers want to. We need to design a simple risc cpu,
without pipelining and all the fancy stuff, and actually I got everything
working quite nice so far, except for one quite important thing:

Assume all operations take either 2 or 3 cycles (logic ones 2, load/store
3). Within the first cycle, the program counter is increased (through the
ALU - no seperate adder for the pc). Naturally I need to fetch the opcode of
the next instruction within the last cycle... and here comes my problem: The
register that holds the opcode is written upon the rising edge of the clock,
while i need this new opcode right in the same cycle (the first cycle (pc =
pc+1) is the same for most operations, but not all, e.g. JMPs).
Now the rising edge of the clock writes the driver for the opcode register,
and my main control process has a
 
C

Christoph M. Wintersteiger

grr don't you hate it when you hit the wrong key at the wrong time? .... msg
continued:

Now the rising edge of the clock writes the driver for the opcode register,
and my main control process has a

case RegOpCode is
when "011010"...

So, in the end this means, that the driver for RegOpCode is updated at the
same (Simulation-) time as the case asks for the value, which naturally
returns the old value, that RegOpCode held one cycle before. I've been
tinkering with this for two days now and I couldn't think of any useful
solution idea (yeah, a pipeline would work, but it's supposed to be a risc
without pipeline...). I can't simply let all instructions run for another
cycle, as I'm limited to those 2/3 cycles by the assignment. I can't even
use RegOpCode'Drinving_value, as then the compiler complains about the
signal not being driven by the process/entity asking for the driving value.
And postponing the whole process kinda screwed everything up, so that
probably ain't a smart idea either.

Any ideas? Should I ditch the whole thing and start from scratch? Or am I
just missing something that's so obvious that it can't be seen?

(Btw: Using ModelSim 5.7g)

Thankful for every comment,
CM Wintersteiger
 
R

Ralf Hildebrandt

Christoph M. Wintersteiger wrote:

... and here comes my problem: The
register that holds the opcode is written upon the rising edge of the clock,
while i need this new opcode right in the same cycle ...

If you model the opcode-register as a latch, it would be transparent.

If you don't like latches (or it is impossible to use them), model a
signal "opcode_next", that is pure combinational logic and holds the
value of the next incoming opcode. (-> It will be hazarderous, so take
care of this.)

Now sample opcode_next into the opcode-register and use this signal also
for some different calculations.

opcode_next --------opcode register
|
|----other stuff


Again: It will have glitches! Therefore you have to sample the "other
stuff" later, when it is stable.

Iff opcode_next arrives quite early, and the "other stuff" can be
computed fast too, the "other stuff" can be sampled with the same edge,
like the opcode register samples the data.


Ralf
 
C

Christoph M. Wintersteiger

Yeah, I had the idea of "pipelining" the opcodes too, but it doesn't work,
as I don't know when the next opcode is a Load-Immediate (in case of a
loadi, the next byte in memory is not an opcode, but an immediate value).
RegOpCode would then be overwritten with the immediate value. I could live
with that, but then I don't get the immediate value anymore, because if I
had to read it from RegOpCode, I'd only get the first 5 bits, because the
operand values are stored in seperate registers, which can't be accessed
from this entity. Also a JMP would make the "pre-cached" value in RegOpCode
invalid which wouldn't be a problem either, if I wouldn't have to introduce
another cycle for the JMP, so I can pre-cache again... And JMP may only take
two cycles, they say.

I didn't quite understand what you meant with reusing the opcode_next for
some other calculations; Just to save on the number of signals? I'm not
concerned with that... heh

Thanks,
CM Wintersteiger
 
O

Oliver Dillinger

Hi!
the next instruction within the last cycle... and here comes my problem: The
register that holds the opcode is written upon the rising edge of the clock,
while i need this new opcode right in the same cycle (the first cycle (pc =
pc+1) is the same for most operations, but not all, e.g. JMPs).
Now the rising edge of the clock writes the driver for the opcode register,
and my main control process has a

I don't know if this RISC CPU calls itself "Prol" :) , but I don't
think that there is a problem.
Assuming that you are modeling without timing making the description
suitable for synthesis, the simulator should behave as follows:
Imagine simulation time is currently 10 ns. The opcode is at the D input
of the opcode register and the rising edge of the clock comes. The
assignment statement should be something like

if rising_edge(iClk) then
RegOpcode <= NewOpcode;
end if;

A new value is written into RegOpcode's driver, but because there is no
delay specified, the time stamp is still 10 ns. The control path of the
CPU, which is (almost) pure combinatorial logic, is of course sensitive
to RegOpcode. The simulator recognizes this situation (a signal to be
updated at the current simulation time and a process that is sensitive
to that change) and inserts a delta delay. That is an infinitely small
amount of time passing; now RegOpcode contains the new value and the
dependent logic is able to react.
You can watch that behaviour in ModelSim's list window, where all signal
updates are shown. The current delta cycle can also be seen in the
bottom status bar next to the simulation time.
Regarding the tip using latches: I would strongly discurage from using
them as they have nothing lost in a synchronous design and can lead to
severe timing issues.

Good luck,
Oliver
 
C

Christoph M. Wintersteiger

You absolutely got me. It's the PROL16 - you had the pleasure of
implementing it, too?

Thank you very much for the tip with the list window; up to now I've
always closed the list window, as I had no idea what it was good for -
now I know that it can be very helpful.

The behaviour you described can be seen there, although it's kind of
reversed. It first evaluates the process (that generates delta cycles
+1, +2, +3) and then sees that RegOpCode has changed (delta cycle
+10). If only I could tell the Simulator to do it the other way around
- Is there a way to?

My first idea was that maybe the order of the elements in the
sensitivity list could help, but I'm too tired to try right now.
So instead of
main: process(ZuluClk, Reset, RegOpCode) is ...
it would work if I said
main: process(RegOpCode, ZuluClk, Reset) is ...

*scratch head* I wouldn't believe that until I've seen it.

Anyways, nice to see someone from around here. Btw, I'm studying
Computer Science at the University in Linz - Do you have the VLSI
Design course in Hagenberg too?

Greetings,
CM Wintersteiger
 
R

Ralf Hildebrandt

Christoph said:
I didn't quite understand what you meant with reusing the opcode_next for
some other calculations; Just to save on the number of signals? I'm not
concerned with that... heh

When you are fetching data from RAM, address is valid for some time and
chipselect (cs) is it too for at least a half clock cycle. Lets have a
look at a skew:

---<address>-----
------_____----- cs
^ Here you will sample the incoming data (flipflop)
(into opcode register)
------<data>-----
^^^^ But the new opcode is available earlier (not hazard-free)

If you have some time (the hazards vanish soon), you could use this new
data signal for further calculations before it is sampled into a register.



If you would use a latch for opcore register, that is activated by
chipselect, you would have the same result. (latch is transparent)


=> Use the incoming data as soon, as they appear on the data bus. Take
care, that they are hazarderous and sample the result, when shure, that
all hazards are gone.


Again: This solution strongly depends, if there is time (slow clock) for
doing that.

Ralf
 
C

Christoph M. Wintersteiger

When you are fetching data from RAM, address is valid for some time and
chipselect (cs) is it too for at least a half clock cycle. Lets have a
look at a skew:

---<address>-----
------_____----- cs
^ Here you will sample the incoming data (flipflop)
(into opcode register)
------<data>-----
^^^^ But the new opcode is available earlier (not hazard-free)

If you have some time (the hazards vanish soon), you could use this new
data signal for further calculations before it is sampled into a register.

Yeah, you're right of course, I have that data available for half a
clock cycle before its sampled. I think I'll give this idea a shot
lateron, however I can imagine that this solves my problem for the
simulation, but would be quite a bit of a problemmaker in hardware,
because the opcode changes while settings depending on the value of
the opcode are being made; Well actually no - the last cycle should be
almost the same for all the operations, I'll check that out.
 
C

Christoph M. Wintersteiger

So instead of
main: process(ZuluClk, Reset, RegOpCode) is ...
it would work if I said
main: process(RegOpCode, ZuluClk, Reset) is ...

I just tried it, and it doesn't make a difference. It doesn't even
matter if I include RegOpCode in the sensitivity list at all - It's
always assigned last. I wish I could set priorities or something.
 
O

Oliver Dillinger

Yeah, the PROL rocks tha house hrhr. I'm doing Hardware/Software Systems
Engineering in Hagenberg and we coded it in 5th semester.

Do you do everything in only one process? Or is this the implementation
of the control path. If yes, why isn't it sensitive to the counter for
the current cycle? It is also a good practise to separate sequential and
combinatorial logic into 2 processes.
I just tried it, and it doesn't make a difference. It doesn't even
matter if I include RegOpCode in the sensitivity list at all - It's
always assigned last. I wish I could set priorities or something.

You can't set a priority, all processes are evaluated in random order. I
think the problem is somewhere else.

Maybe you could describe what the Prol does/does not. This would be
helpful...
 
C

Christoph M. Wintersteiger

Yeah, the PROL rocks tha house hrhr. I'm doing Hardware/Software Systems
Engineering in Hagenberg and we coded it in 5th semester.


Do you do everything in only one process? Or is this the implementation
of the control path. If yes, why isn't it sensitive to the counter for
the current cycle? It is also a good practise to separate sequential and
combinatorial logic into 2 processes.

Yeah, I have one process which basically just has a big case ... like:

case RegOpCode is
when opSleep => ...
when opAdd => ...
when opInc => ...

and in each path of the case, i have another case for the cycle
counter, like:

when opSleep =>
case Counter is
when "001" =>
when "010" =>
....

This is of course just the control unit. Everything else, like the
registers, datapath, alu is implemented seperately, actually they all
are implemented purely combinatorial.

You ask why it's not sensitive for the counter? Well, the cycle
counter is a variable here, but I'm still not convinced that this's
the best way to do it. What would I win if it was sensitive to the
counter?
You can't set a priority, all processes are evaluated in random order. I
think the problem is somewhere else.

Maybe you could describe what the Prol does/does not. This would be
helpful...

Well, what it does is, that in the first cycle of a command, it does
what it should have been doing in the first cycle for the last
command. Like when I have a store (3 cycles) and then an inc (2
cycles), it should set the write strobe for the memory in the first of
the three store-cycles, so that the value can be written to the memory
during the second cycle then. What happens with my code, is that
during the first cycle of the store, it does the first cycle of
whatever the last command was, and then during the first cycle of the
inc it does what it should've been doing in the first cycle of the
store, while the rest of the cycles are okay... thus the writes to the
memory are completely off. And I didn't run any code with JMPs through
it yet, but I'm pretty sure that they'd **** things up pretty much.

In the list window, during the first cycle of a command (counter =
001) i can see that it first checks RegOpCode for the case, then goes
through the case (I can see it doing the changes that are being done
inside the case), and THEN updates RegOpCode in the last delta.

Greetings,
CM Wintersteiger
 
O

Oliver Dillinger

Christoph said:
You ask why it's not sensitive for the counter? Well, the cycle
counter is a variable here, but I'm still not convinced that this's
the best way to do it. What would I win if it was sensitive to the
counter?
Where do you save the counter? And how and when is it updated?
Well, what it does is, that in the first cycle of a command, it does
what it should have been doing in the first cycle for the last
command. Like when I have a store (3 cycles) and then an inc (2
cycles), it should set the write strobe for the memory in the first of
the three store-cycles, so that the value can be written to the memory
during the second cycle then. What happens with my code, is that
during the first cycle of the store, it does the first cycle of
whatever the last command was, and then during the first cycle of the
inc it does what it should've been doing in the first cycle of the
store, while the rest of the cycles are okay... thus the writes to the
memory are completely off. And I didn't run any code with JMPs through
it yet, but I'm pretty sure that they'd **** things up pretty much.

In the list window, during the first cycle of a command (counter =
001) i can see that it first checks RegOpCode for the case, then goes
through the case (I can see it doing the changes that are being done
inside the case), and THEN updates RegOpCode in the last delta.

Hm, what does it when executing the very first command, when there was
none before?

I don't know your code, so I'm just guessing: Simulation for the first
cycle starts, the driver for RegOpcode is attached, and the process for
the controller is evaluated (still with the old opcode). The case
selects the opcode and the current cycle, which is 1. Maybe you are
updating the counter value *now*, so that you are in cycle 2. In the
next delta cycle, the right opcode is loaded, but the prol is in the
wrong cycle. This would explain the behaviour you are describing.
It would be cool if you posted some code, to have a look at it...
 
C

Christoph M. Wintersteiger

Where do you save the counter? And how and when is it updated?

I just changed it to a signal, which gives other interesting effects
now.

signal Counter : std_ulogic_vector(2 downto 0);
Hm, what does it when executing the very first command, when there was
none before?

I don't know your code, so I'm just guessing: Simulation for the first
cycle starts, the driver for RegOpcode is attached, and the process for
the controller is evaluated (still with the old opcode). The case
selects the opcode and the current cycle, which is 1. Maybe you are
updating the counter value *now*, so that you are in cycle 2. In the
next delta cycle, the right opcode is loaded, but the prol is in the
wrong cycle. This would explain the behaviour you are describing.
It would be cool if you posted some code, to have a look at it...

Yes, now that I changed the counter variable to a signal, it seems
that while in Cycle 2 it reads 1 from the Counter. I made a seperate
process that takes care of updating the counter:

c: process(ZuluClk, Reset) is
begin
if (Reset = '1') then
Counter <= "001";
elsif (ZuluClk'event and ZuluClk = '1') then
if (LastCycle = '0') then
Counter <= (Counter(1), Counter(0), '0');
else
Counter <= "001";
end if;
end if;
end process;

(LastCycle is a signal that is set to '1' when a command is in the
last cycle that it needs)

Then follows the main process, which sets the signals for the
datapath. I've stripped most opcode parts from this one, they're all
similiar and I don't want my teacher to see that I'm posting working
code on here; I'm pretty sure that he reads the group, too :)

Just a few minutes ago, I was thinking it over again and wondered if
the resets of the various signals in the beginning of each cycle would
be the reason for some of the problems, because in the first delta
cycle they'd be zero while in the next deltas they could change back
to '1' or so. (I've marked them in the code so you know which part I
mean)

main: process(ZuluClk, Reset, Counter, RegOpCode, LastCycle) is

variable LastCarry, LastZero : std_ulogic;

procedure IncPC is
begin
SelPC <= '1'; -- Select PC as SideA
AluFunc <= "0011"; -- Choose INC as function
ClkEnPc <= '1'; -- Enable writing of RegPC
end procedure;

procedure FetchOpc is
begin
SelAddr <= '0'; -- Select PC as memory address
ClkEnOpCode <= '1'; -- Prepare RegOpCode to save new opcode
end procedure;

procedure LogicalSave is
begin
SelLoad <= '0'; -- Select aluresult as RegFile input
ClkEnRegFile <= '1'; -- Tell RegFile to save
end procedure;

begin
if (Reset='1') then
LastCycle <= '1';
SetRdStrobe <= '0';
SetWrStrobe <= '0';
ResetC <= '1';
elsif (ZuluClk'event and ZuluClk='1') then

-- I MEANT THE RESETS FROM HERE
ClkEnPC <= '0';
ClkEnRegFile <= '0';
ClkEnOpcode <= '0';
SelPC <= '0';
SelAddr <= '0';
SelLoad <= '0';
-- pragma translate_off
ClkEnMem <= '0';
-- pragma translate_on
SetRdStrobe <= '0';
SetWrStrobe <= '0';
-- ... TO HERE

if (LastCycle = '1' and Counter = "001") then
LastCycle <= '0';
end if;

case RegOpCode is
when opcSleep =>
LegalOpCodePresent <= '1';
case Counter is
when "001" =>
LastCycle <= '1';
-- pragma translate_off
report ("Ending Simulation upon opcSLEEP") severity
failure;
-- pragma translate_on
when "010" => null;
when "100" => null;
when others =>
-- pragma translate_off
report("Invalid Counter value") severity error;
-- pragma translate_on
end case;
when opcLOADI =>
LegalOpCodePresent <= '1';
case Counter is
when "001" =>
IncPC;
SetRdStrobe <= '1'; -- Tell the memory to send
when "010" =>
SelAddr <= '0'; -- Select PC as memory adress
SelLoad <= '1'; -- Select memory data as RegFile
input
ClkEnRegFile <= '1'; -- Prepare regfile to save
loaded value
IncPC;
SetRdStrobe <= '1'; -- Tell the memory to send (for
fetch)
when "100" =>
FetchOpc;
LastCycle <= '1';
when others =>
-- pragma translate_off
report("Invalid Counter value") severity error;
-- pragma translate_on
end case;
when opcSTORE =>
LegalOpCodePresent <= '1';
case Counter is
when "001" =>
IncPC;
SetWrStrobe <= '1'; -- Tell the memory to save
when "010" =>
SelAddr <= '1'; -- Select SideB as memory adress
SetRdStrobe <= '1'; -- Tell the memory to send (for
fetch)
when "100" =>
FetchOpc;
LastCycle <= '1';
when others =>
-- pragma translate_off
report("Invalid Counter value") severity error;
-- pragma translate_on
end case;
when opcADD =>
LegalOpCodePresent <= '1';
case Counter is
when "001" =>
IncPC;
SetRdStrobe <= '1'; -- Tell the memory to send (for
fetch)
when "010" =>
LogicalSave;
CarryIn <= '0'; -- Don't add a carry
ALUFunc <= "0101"; -- Select a+b+c as aluresult
FetchOpc;
LastCycle <= '1';
when "100" =>
null;
when others =>
-- pragma translate_off
report("Invalid Counter value") severity error;
-- pragma translate_on
end case;

... more cases with other commands

when others =>
if (RegOpCode/=opcNOP) then
LegalOpCodePresent <= '0';
-- pragma translate_off
report ("Illegal Opcode") severity warning;
-- pragma translate_on
else
LegalOpCodePresent <= '1';
end if;
case Counter is
when "001" =>
if (ResetC ='0') then
IncPC;
else
ResetC <= '0';
end if;
SetRdStrobe <= '1'; -- Tell the memory to send (for
fetch)
when "010" =>
FetchOpc;
LastCycle <= '1';
when "100" => null;
when others =>
-- pragma translate_off
report("Invalid Counter value") severity error;
-- pragma translate_on
end case;
end case;
end if;
end process;
end architecture;
 
C

Christoph M. Wintersteiger

Okay, I just played around with the code a bit, and, what can I say, I
got it *almost* working. Now the only problem remaining, is that the
counter value read is wrong, like in Cycle 2 it reads the value that
the counter had in Cycle 1. I just changed
if (LastCycle = '1' and Counter = "001") then
to
if (LastCycle = '1') then

I think this looks promising, but still I have no clue why it's doing
that.
 
C

Christoph M. Wintersteiger

I think this looks promising, but still I have no clue why it's doing
that.

- No. When that problem is solved, I have the other problem again;
RegOpCode delivers the old Opcode. Argh.
 
C

Christoph M. Wintersteiger

Okay, I've run out of ideas, and despite the fact that latches could
**** everything up, I gave them a try and it worked right from the
start. I think I'll just leave it at that for now, and have a word
about it with my teacher next time.

Thanks a lot for your help :)

Greetings,
CM Wintersteiger
 
O

Oliver Dillinger

Cool thing :)
Okay, I've run out of ideas, and despite the fact that latches could
**** everything up, I gave them a try and it worked right from the
start. I think I'll just leave it at that for now, and have a word
about it with my teacher next time.

Thanks a lot for your help :)

Greetings,
CM Wintersteiger
 
D

Don Golding

If you are using one process you can use variables for all of your registers
and temporary storage as they are updated immediately.

1) Load variables with the signal values.
2) Process code
3) assign signals with result variables.

I hope this helps,
Don
 

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,755
Messages
2,569,537
Members
45,021
Latest member
AkilahJaim

Latest Threads

Top