![]() |
|
|
|||||||
![]() |
VHDL - Resolving record with enumerated type |
|
|
Thread Tools | Search this Thread |
|
|
#1 |
|
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 |
|
|
|
|
#2 |
|
Posts: n/a
|
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 |
|
|
|
#3 |
|
Posts: n/a
|
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 |
|
|
|
#4 |
|
Posts: n/a
|
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. |
|