Resolving record with enumerated type

A

Analog_Guy

Guest
I am trying to implement a transaction based client/server testbench
using records, but am having difficulty getting to the bottom of my
compilation errors regarding my transaction records (which contain an
enumerated type).

******Signal 'cpu_cmd' has multiple drivers but is not a resolved
signal.

I am trying to base the testbench on the approaches taken by Janick
Bergeron (code snippets), Jim Lewis (records of INOUT with only
STD_LOGIC) and Ben Cohen (records of either IN or OUT with various
element types). Each presents a slightly different case, but I am
trying to come to my own understanding of the testbench structure.

I am wanting to use records of INOUT with all STD_LOGIC elements,
except for one element which is an enumerated type.

In a CPU BFM package, I have defined the following:

TYPE CPU_INSTR_TYPE IS (CPU_READ, CPU_WRITE);
--
TYPE CPU_CMD_TYPE IS RECORD
instr : CPU_INSTR_TYPE;
addr : STD_LOGIC_VECTOR(0 TO 15);
data : STD_LOGIC_VECTOR(0 TO 15);
END RECORD CPU_CMD_TYPE;
--
SIGNAL cpu_cmd : CPU_CMD_TYPE;

Essentially, this record communicates between a test control entity and
a CPU BFM, and is defined as INOUT at the top-level (i.e. for both of
the modules described). With the above method, the code compiles, but
when I load the testbench in ModelSim I receive the "multiple drivers"
error.

I have tried eliminating the global signal declaration that appears
above, and defining cpu_cmd at the top-level, but that doesn't make
much of a difference. Instead, I get the same error during
compilation.

I am assuming the fact that CPU_INSTR_TYPE is unresolved and the
records are INOUT is leading to the 'multiple drivers' error.

Are there any suggestions to get to the bottom of this issue? Do I
have to separate the 'instr' element into a separate record (doesn't
seem to be as clean)? If I want mixed elements in a record am I going
to have to separate the record elements into an IN record and an OUT
record?
 
Analog_Guy wrote:
I am trying to implement a transaction based client/server testbench
using records, but am having difficulty getting to the bottom of my
compilation errors regarding my transaction records (which contain an
enumerated type).

In a CPU BFM package, I have defined the following:

TYPE CPU_INSTR_TYPE IS (CPU_READ, CPU_WRITE);
--
TYPE CPU_CMD_TYPE IS RECORD
instr : CPU_INSTR_TYPE;
addr : STD_LOGIC_VECTOR(0 TO 15);
data : STD_LOGIC_VECTOR(0 TO 15);
END RECORD CPU_CMD_TYPE;
--
SIGNAL cpu_cmd : CPU_CMD_TYPE;
snip
Essentially, this record communicates between a test control entity and
a CPU BFM, and is defined as INOUT at the top-level (i.e. for both of
the modules described). With the above method, the code compiles, but
when I load the testbench in ModelSim I receive the "multiple drivers"
error.
The cpu_cmd signal can not be an inout of more than one entity....and
what you've said (I think) is that it is an 'inout' of two
modules....and even if that's not what you meant, that is the complaint
that the simulator is giving you...hence there are multiple
drivers....even if only certain elements of 'cpu_cmd' are driven from
only one entity. For example, cpu_cmd.instr comes out of entity #1;
cpu_cmd.addr and cpu_cmd.data come out of entity #2. Doesn't matter.
It's an error because
- cpu_cmd is the signal
- cpu_cmd is not a resolved signal
- cpu_cmd is an inout (therefore an output) of more than one entity.

You don't want to lump what will be inputs and outputs of various
entities into a single record and try to make what appears to be a
'cleaner' implementation. Things start to choke as soon as you need to
type in the VHDL mode and realize that's it's not an 'in', it's not an
'out'....hmmm, must be an 'inout' then. The day of reckoning comes
when you stitch those components together and can't simulate because of
the multiple drivers.

KJ
 
Until we get record element directions (something similar to
SystemVerilog interfaces), I suggest you to separate the elements and
bundle them into input elements and output elements. This is not as
clean, but it will give you less headache.

-- Amal
 
Analog_Guy wrote:

I am trying to implement a transaction based client/server testbench
using records, but am having difficulty getting to the bottom of my
compilation errors regarding my transaction records (which contain
an enumerated type).

******Signal 'cpu_cmd' has multiple drivers but is not a resolved
signal.

I am trying to base the testbench on the approaches taken by Janick
Bergeron (code snippets), Jim Lewis (records of INOUT with only
STD_LOGIC) and Ben Cohen (records of either IN or OUT with various
element types). Each presents a slightly different case, but I am
trying to come to my own understanding of the testbench structure.

I am wanting to use records of INOUT with all STD_LOGIC elements,
except for one element which is an enumerated type.

In a CPU BFM package, I have defined the following:

TYPE CPU_INSTR_TYPE IS (CPU_READ, CPU_WRITE);
--
TYPE CPU_CMD_TYPE IS RECORD
instr : CPU_INSTR_TYPE;
addr : STD_LOGIC_VECTOR(0 TO 15);
data : STD_LOGIC_VECTOR(0 TO 15);
END RECORD CPU_CMD_TYPE;
--
SIGNAL cpu_cmd : CPU_CMD_TYPE;

Essentially, this record communicates between a test control entity
and a CPU BFM, and is defined as INOUT at the top-level (i.e. for
both of
the modules described). With the above method, the code compiles,
but when I load the testbench in ModelSim I receive the "multiple
drivers" error.

I have tried eliminating the global signal declaration that appears
above, and defining cpu_cmd at the top-level, but that doesn't make
much of a difference. Instead, I get the same error during
compilation.

I am assuming the fact that CPU_INSTR_TYPE is unresolved and the
records are INOUT is leading to the 'multiple drivers' error.
No, CPU_CMD_TYPE is unresolved, that's the problem. So it needs to be
a resolved type.

Furthermore, because data goes in two directions (request from the
client (testbench) to the server (BFM), acknowledge and/or results
from the server to the client), you'll need some way to orchestrate
this.

I have done that with two aditional record fields: req and ack.
Ack is a boolean, req is an enumeration type, conveying the required
action/request. It is similar to your CPU_INSTR_TYPE, with an added
member req_none (idle state, no request).

With req and ack a handshake can be implemented between the
client and the server. Req and ack are also used by the resolution
function to determine the actual value the cpu_cmd signal is going to
get, thus the direction of the data flow.

I've created an extra level of record elements in cpu_cmd_type:
data and control. The latter contains the req and ack field, the
first the data needed for the commands. Just to make the distinction
between the purpose of the various fields. This also makes the
implementation of this signal type for various BFMs quite similar.
There is always a data field, but it contents varies between the
different models.

Some code.

In the package:

----------------------------------------------------------------------
-- Type definitions for cmd channel
----------------------------------------------------------------------

-- Action to be performed
--
TYPE cpu_req_type IS
(
req_none,

req_cpu_read,
req_cpu_write
);

-- Type definition to hold the data for a request of a setting.
-- Used in cpu_cmd_utype.
--
TYPE cpu_data_type IS
RECORD
addr : std_logic_vector(15 DOWNTO 0);
data : std_logic_vector(15 DOWNTO 0);
END RECORD cpu_data_type;

-- Type definition to hold the control fields of the
-- communication channel between the server and client.
-- Used in cpu_cmd_utype.
--
TYPE cpu_control_type IS
RECORD
req : cpu_req_type;
ack : boolean;
END RECORD cpu_control_type;

-- Type definition for the signal wich will serve as the
-- communication channel to and from the server
--
TYPE cpu_cmd_utype IS
RECORD
data : cpu_data_type;
control : cpu_control_type;
END RECORD cpu_cmd_utype;

-- Type must be a resolved type, because client end server will both
-- drive the communication channel. This also makes it possible to
-- have more than one client (an extra prio field in
-- cpu_control_type could be needed though).
--
TYPE cpu_cmd_arr_utype IS ARRAY(integer RANGE <>) OF cpu_cmd_utype;
FUNCTION resolve_cpu_cmd
(
s : cpu_cmd_arr_utype
) RETURN cpu_cmd_utype;
SUBTYPE cpu_cmd_type IS resolve_cpu_cmd cpu_cmd_utype;


In the package body:

----------------------------------------------------------------------
-- Resolution function for cpu_cmd_utype
----------------------------------------------------------------------
--
-- This function makes it possible to use a single signal for
-- sending requests from the client to the server, and sending data
-- from the server to the client. Having more than one client is
-- also made possible by this function.
--
FUNCTION resolve_cpu_cmd
(
s : cpu_cmd_arr_utype
) RETURN cpu_cmd_utype IS
VARIABLE result : cpu_cmd_utype;
VARIABLE active_req_drivers : natural;
VARIABLE active_ack_drivers : natural;
BEGIN
-- Evaluate all signal drivers. Data from the client (req)
-- takes precedence.
--
FOR i IN s'RANGE LOOP
ASSERT s(i).control.req = req_none OR NOT s(i).control.ack
REPORT "resolve_com: What are you, a client or a server?"
SEVERITY failure;

IF s(i).control.req /= req_none THEN
inc(active_req_drivers);
result := s(i); -- Data direction: from client to server
END IF;

IF s(i).control.ack THEN
inc(active_ack_drivers);
IF active_req_drivers = 0 THEN
result := s(i); -- Data direction: from server to client
END IF;
END IF;
END LOOP;

-- Concurrent requests or acknowledges should not occur
--
ASSERT active_req_drivers <= 1
REPORT "resolve_com: More than one request active!"
SEVERITY failure;
ASSERT active_ack_drivers <= 1
REPORT "resolve_com: More than one acknowlegde active!"
SEVERITY failure;

-- More than one simultaneous request (or acknowledge) should
-- result in no request (or acknowledge) at all. That way,
-- undeterministic results are avoided.
--
IF active_req_drivers > 1 THEN
result.control.req := req_none;
END IF;
result.control.ack := active_ack_drivers = 1;

RETURN result;
END FUNCTION resolve_cpu_cmd;


The hanshake procedure between the client and server is as follows:

The client makes a request by assigning the appropriate data fields
and setting control.req to the required action. It then waits on the
ack of the server. After receiving the ack, the client sets
control.req to req_none. This can be wrapped in a general procedure.

PROCEDURE do_req
(
CONSTANT req : IN cpu_req_type;
SIGNAL control : INOUT cpu_control_type
) IS
BEGIN
control.req <= req;
WAIT UNTIL control.ack;
control.req <= req_none;

-- Allow back-to back calls of commands. Also needed so data
-- from the server is not overruled by data from the client.
--
WAIT FOR 0 ns;
END PROCEDURE do_req;


The server process waits on a request from the client. On receiving
the request, it removes its own acknowledge (from a previous cycle).
This allows the data to go from the client to the server. Then it
executes the request (or as I usually do: hand the request to the
actual process that implements the BFM). If the request has been
executed, the server places the data to be sent back (if any) on the
data field of the cpu_cmd_type signal, and sets the control.ack field
to true, so data actually goes from the server to the client.

So the server process looks like:

server: PROCESS IS
BEGIN
-- Wait for command from client. Only then, remove the ack.
-- Ack works as a "data from server is valid" indication, if
-- any data is tranferred back to the client.
--
WAIT UNTIL cpu_cmd.control.req /= req_none;
cpu_cmd.control.ack <= false;
WAIT FOR 0 ns; -- For requests that do not consume time

-- Determine the kind of the request and handle it
--
CASE cpu_cmd.control.req IS
WHEN req_cpu_write =>
trigger the BFM process with a write command and wait until
it has finished

WHEN req_cpu_read =>
trigger the BFM process with a read command and wait until
it has finished

cpu_cmd.data.data <= read_value; -- from BFM

WHEN req_none =>
REPORT "Impossible choice"
SEVERITY failure;

END CASE;

-- Notify client that the request has been handled and that
-- data from the server is valid (until a new request is
-- received).
--
cpu_cmd.control.ack <= true;
END PROCESS server;


Now you need to create the cpu_read and cpu_write procedures that will
be actually be called from the testbench. These procedure together
form the procedural interface of your BFM. These procedure use the
do_req procedure. They all have a signal inout parameter of type
cpu_cmd_type.

The BFM (instantiated in the testbench) has an inout port of type
cpu_cmd_type. A signal connected to this port acts as the single
communication channel with the BFM (which was the ultimate goal).

The cpu_read and write procedures would look like:

PROCEDURE cpu_read
(
SIGNAL cmd : INOUT cpu_cmd_type;
CONSTANT addr : IN std_logic_vector(15 DOWNTO 0);
VARIABLE data : OUT std_logic_vector(15 DOWNTO 0)
) IS
BEGIN
cmd.data.addr <= addr;
do_req(req_cpu_read, cmd.control);
data := cmd.data.data;
END PROCEDURE cpu_read;

PROCEDURE cpu_write
(
SIGNAL cmd : INOUT cpu_cmd_type;
CONSTANT addr : IN std_logic_vector(15 DOWNTO 0);
CONSTANT data : IN std_logic_vector(15 DOWNTO 0)
) IS
BEGIN
cmd.data.addr <= addr;
cmd.data.data <= data;
do_req(req_cpu_write, cmd.control);
END PROCEDURE cpu_read;

Are there any suggestions to get to the bottom of this issue? Do I
have to separate the 'instr' element into a separate record (doesn't
seem to be as clean)? If I want mixed elements in a record am I
going to have to separate the record elements into an IN record and
an OUT record?
No, the resolution function takes care of all that, as you hopefully
can see now.

--
Paul.
 

Welcome to EDABoard.com

Sponsor

Back
Top