LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Labview FPGA Modelsim testbench design

Solved!
Go to solution

Hey folks, I have a question on proper testbench design.  I am trying to figure out how to 'write' to inputs of a design in parallel.

Here is the situation I’m trying to figure out – probably a simple question that might be VHDL related –but who knows. I have a very simple design that adds two numbers, and returns the sum.  C = A + B.  I want the values A and B to change at the same time.  That is, I want it to compute (17 + 17), then (12 + 8), then (0 + 70), and I want those two values A and B to change at the same time. However, when I do ‘write’ functions in my testbench, each write is sequential rather than parallel.  This means that I get the following values:

 

Initial conditions:  a = 0, b = 0, c = 0.

First write:  a = 17, b = 0, c = 17  *** not accurate to the way the hardware SHOULD work ***

2nd write: a = 17, b = 17, c = 34  ***case I want ***

3rd write: a = 12, b = 17, c = 29 *** not accurate to the way the hardware SHOULD work ***

4th write: a = 12, b = 8 , c = 20  *** case I want ***

 The computation of A+B = C is correct, however, the timing in the waveform is incorrect, because the testbench does not issue the writes to A and B in parallel, the way it is designed in the block diagram, and presumably the way it will work in the FPGA.  I believe this is a testbench issue which is probably easily solved by coding it differently, but I’m unfamiliar with the proper way to do it.

  

0 Kudos
Message 1 of 9
(4,399 Views)

Hi JJ,

 

Could you post the code you have written so that we can look through it to see what is causing this issue? I'm missing something in your explanation because even if the writes aren't in true parallel (although that should change on the FPGA) the code shouldn't be executing the multiplication before both writes are made.

 

Regards,

Peter W.

0 Kudos
Message 2 of 9
(4,350 Views)

Here is a sample testbench I made.  It adds 17 and 33.  The simulation shows 17 showing up in one indicator, several clocks later, 33 shows up, and one clock later, the valid result (50) shows up.

 

  begin

    MainStimulusProcess:process
      variable fiErrorStatus : tErrorStatus;
        --=====================================================================
        --Control and Indicator Data Variables 
        -----------------------------------------------------------------------
        -- Use these variables with the NiFpga_Write and NiFpga_Read commands
        -- for controls and indicators to guarantee the correct data type read
        -- or write operation is performed. Refer to the auto-generated
        -- file, PkgRegister.vhd, in the nifpga directory for the register
        -- offsets for the controls and indicators.
        -----------------------------------------------------------------------
        variable a_ctl_0_Data: tI16;
        variable c_ind_1_Data: tI16;
        variable b_ctl_2_Data: tI16;
        --=====================================================================

        begin

        PrintHostInterfaceStatus("Downloading",HostInterfaceStatus);
        NiFpga_Download
         (FiClock => FiClock,
          fiHostToTargetInterface => fiHostToTargetInterface,
          fiTargetToHostInterface => fiTargetToHostInterface,
          fiHostToTargetReady => fiHostToTargetReady,
          fiTargetToHostReady =>  fiTargetToHostReady,
          fiErrorStatusOut => fiErrorStatus);

        PrintHostInterfaceStatus("Opening",HostInterfaceStatus);
        NiFpga_Open (attr => (kNiFpga_OpenAttribute_NoRun => '1', others => '0'),
          FiClock => FiClock,
          fiHostToTargetInterface => fiHostToTargetInterface,
          fiTargetToHostInterface => fiTargetToHostInterface,
          fiHostToTargetReady => fiHostToTargetReady,
          fiTargetToHostReady =>  fiTargetToHostReady,
          fiErrorStatusIn => fiErrorStatus,
          fiErrorStatusOut => fiErrorStatus);

        PrintHostInterfaceStatus("Running",HostInterfaceStatus); 
        NiFpga_Run (attr => (kNiFpga_RunAttribute_WaitUntilDone => '0', others => '0'),
          FiClock => FiClock,
          fiHostToTargetInterface => fiHostToTargetInterface,
          fiTargetToHostInterface => fiTargetToHostInterface,
          fiHostToTargetReady => fiHostToTargetReady,
          fiTargetToHostReady =>  fiTargetToHostReady,
          fiErrorStatusIn => fiErrorStatus,
          fiErrorStatusOut => fiErrorStatus);

        --*********************************************************************
        --
        -- INSERT HOST INTERFACE PROCEDURES HERE
        --
        --  For more info on Host Interface procedures, see the heading above.
        --
        --  This is an example of how to read an indicator:
        --  NiFpga_Read (
        --    Address => <constant in PkgRegister k[IndicatorName]_ind>,
        --    Data => <variable [IndicatorName]_ind_Data>,
        --    FiClock => FiClock,
        --    fiHostToTargetInterface => fiHostToTargetInterface,
        --    fiTargetToHostInterface => fiTargetToHostInterface,
        --    fiHostToTargetReady => fiHostToTargetReady,
        --    fiTargetToHostReady =>  fiTargetToHostReady,
        --    fiErrorStatusIn => fiErrorStatus,
        --    fiErrorStatusOut => fiErrorStatus);
        --
        --  This is an example of how to write a control:
        --  NiFpga_Write (
        --    Address => <constant in PkgRegister k[ControlName]_ctl>,
        --    Data => <variable [ControlName]_ctl_Data>,
        --    FiClock => FiClock,
        --    fiHostToTargetInterface => fiHostToTargetInterface,
        --    fiHostToTargetReady => fiHostToTargetReady,
        --    fiErrorStatusIn => fiErrorStatus,
        --    fiErrorStatusOut => fiErrorStatus);
        --*********************************************************************

 -- This is an example of how to write a control:

    -- NiFpga_Write (
    --  Address => <constant in PkgRegister k[ControlName]_ctl>,
    --  Data => <variable [ControlName]_ctl_Data>,
    --  FiClock => FiClock,
    --  fiHostToTargetInterface => fiHostToTargetInterface,
    --  fiHostToTargetReady => fiHostToTargetReady,
    --  fiErrorStatusIn => fiErrorStatus,
    --  fiErrorStatusOut => fiErrorStatus);


    --*********************************************************************
		--get addresses from niFPGA/PkgRegister
		--get data variables from above in this file.
		-- data			 addr
	    -- a_ctl_0_Data: ka_ctl_0
        -- c_ind_1_Data: kc_ind_1 
        -- b_ctl_2_Data: kb_ctl_2		
   
		a_ctl_0_Data := to_signed(17, a_ctl_0_Data'length);
		b_ctl_2_Data := to_signed(33, b_ctl_2_Data'length);
		
		
		NiFpga_Write(
			 Address => ka_ctl_0,
			 Data => a_ctl_0_Data,
			 FiClock => FiClock,
			 fiHostToTargetInterface => fiHostToTargetInterface,
			 fiHostToTargetReady => fiHostToTargetReady,
			 fiErrorStatusIn => fiErrorStatus,
			 fiErrorStatusOut => fiErrorStatus);
			 
		
		
		NiFpga_Write(
			 Address => kb_ctl_2,
			 Data => b_ctl_2_Data,
			 FiClock => FiClock,
			 fiHostToTargetInterface => fiHostToTargetInterface,
			 fiHostToTargetReady => fiHostToTargetReady,
			 fiErrorStatusIn => fiErrorStatus,
			 fiErrorStatusOut => fiErrorStatus);


		NiFpga_Read(
			 Address => kc_ind_1,
			 Data => c_ind_1_Data,
			 FiClock => FiClock,
			 fiHostToTargetInterface => fiHostToTargetInterface,
			 fiHostToTargetReady => fiHostToTargetReady,
			 fiTargetToHostInterface => fiTargetToHostInterface,
			 fiTargetToHostReady => fiTargetToHostReady,
			 fiErrorStatusIn => fiErrorStatus,
			 fiErrorStatusOut => fiErrorStatus);

        PrintHostInterfaceStatus("Closing",HostInterfaceStatus); 
        NiFpga_Close (
          FiClock => FiClock,
          fiTargetToHostInterface => fiTargetToHostInterface,
          fiHostToTargetInterface => fiHostToTargetInterface,
          fiHostToTargetReady => fiHostToTargetReady,
          fiTargetToHostReady =>  fiTargetToHostReady,
          fiErrorStatusIn => fiErrorStatus,
          fiErrorStatusOut => fiErrorStatus);

        PrintHostInterfaceStatus("Idle",HostInterfaceStatus); 
        fiStopSim <= true;
        wait;

      end process MainStimulusProcess;

 

0 Kudos
Message 3 of 9
(4,342 Views)

@JJMontante wrote:

Here is a sample testbench I made.  It adds 17 and 33.  The simulation shows 17 showing up in one indicator, several clocks later, 33 shows up, and one clock later, the valid result (50) shows up.


Hi JJ,

 

When you say the value is showing up in one indicator, is that specific to the "a" control or do you mean the "c" result is showing 17 and then showing 33 briefly before showing 50? If it is 3 separate indicators then I think what you are seeing is expected behavior since it will take some number of ticks to send the data to each control and then one more to execute the addition.

 

Do you have the FPGA code that you are interacting with, and could you post it online as well?

 

Regards,

Peter W.

0 Kudos
Message 4 of 9
(4,327 Views)

I first followed the Modelsim tutorial to completion where you create an Incrementer.  After following the full tutorial (and having the resulting simulation export build-spec still part of the project), I modified the block diagram from B = A + 1, to C = A + B.  Then I rebuild and relaunch the simulation export.

 

The first screenshot shows my design in its entirety:  A + B = C.  

 

The second screenshot is the simulation in Modelsim.  I have two ovals marked on there, and annotated 1. and 2.  Circle 1 shows that the two controls are updated separately (and a different times) due to writing via a 'bus' interface.  This is not how an adder should work.  If you look at the testbench code, you see the two writes commands issued separately.  This corresponds to the behavior I see, but it is NOT desirable, because there is an invalid computation because the actual hardware should not have its inputs updated serially via a bus.  

 

Circle 2 shows that the A and B do not update coincident to each other, but does show that the computation of the result happens in 1 clock cycle as expected.

 

What I want to know is how do I write testbench structures such that A and B change coincident to the same clock edge, rather than get updated serially via this bus interface (which was the way that the tutorial produces a testbench)?

 

 

 

 

Download All
0 Kudos
Message 5 of 9
(4,325 Views)

I got these messages from a guy on tech support since there have been few replies on the forum:

================ E-mail from Tech Support =====================

"I investigated your issue your further and spoke to the LabVIEW FPGA

Product Support Engineer about it. I also saw your discussion forum post.

It is expected that both the controls on your test bench will not get

updated at the same time.

 

In order to write controls and read indicators on the FPGA, you either have

to change these through the interactive interface in LabVIEW or through a

host VI.  Through a host VI, you have to send individual write commands

across the interface bus that connects the host to the FPGA.  So, the

simulation results you are seeing are accurate.  Furthermore, unless you

can click and change both controls on the interactive interface in less

than 25 nanoseconds and have both of those sent across the bus that fast

(not possible), you still will not have both controls written

simultaneously.

When creating the testbench, you are essentially mimicking a host VI.  The

functions that are called are the ones used by the C Interface or the host

interface in LabVIEW. In your testbench, the first functions are Open,

Download, and Run.  Then, you write the controls and read the inidicator

and then the Close function is called; just like a host VI.  This is why

the write commands are issued separately across a bus. To summarize, there

is no possible way to actually write both of these controls simultaneously

in the real world, hence there is no possible way to write both

simultaneously in simulation.  If you created an FPGA VI that waited for a

start signal and wrote the controls before the single-cycle loop started,

then, you would see a single clock cycle to update the indicator because

both controls would already be primed."

 

=========== End of Email from Tech Support =====================

 

I understand that is how controls and indicators are updated, and I must act within those bounds.  However, imagine for a second that this adder is a sub-VI and it's A and B inputs are fed by two pieces of clocked data from the calling VI.  They will be changing synchronous to each other.  Or suppose that those A and B inputs are driven by the socket CLIP - is this a circumstance where the labview tool would create  different mechanism in the testbench to handle two  CLIP outputs to my adder?  There are any number of valid mechanisms to create 2 inputs to an adder (let alone any more complex design) that need to be simulated with all IO changing simultaneously.  Otherwise, I will CONSTANTLY be simulating invalid data.  Each control takes 5 clock cycles to be written after the previous control, so at best it takes 5 x (# of controls) clock cycles of invalid data to get 1 clock cycle of valid data.   

 

0 Kudos
Message 6 of 9
(4,286 Views)

Simple Answer:

 

The simulation feature as it exists today mimics the standard host interface to, hopefully, make it easy for someone to drop into the simulator and try to debug their application. Therefore, if you want to synchronize values going into a block from multiple sources in the FPGA, you'll need to write that code in the FPGA so the generated VHDL will properly handle it. A simple approach would be to implement a simple state machine (loop and case structure) that waits until you hit a boolean control to perform the operation. You can then write all the necessary state to the FPGA vi using the standard flow and then once everything is settled, touch the boolean to cause the system to actually use the data.

 

Harder Answer:

 

In theory, you can write a small VI that performs the operation you want and drop it into a blank top-level VI. When you generate the VHDL, you can then access the interface to the VI under test (subvi) directly from your test bench. You'll be responsible for toggling the appropriate enable chain control signals to get the VI to simulate properly, but it's possible. Essentially, Enable In goes high until Enable Out goes high, then assert Enable Clear for one cycle and start over again. Note, with this approach you'll have a very tough time if your VI under test uses resources FIFOs or Memories as those resource connections are routed through the interface of the VI.

0 Kudos
Message 7 of 9
(4,280 Views)

I see that this thread is a little old, but I want to mention one obvious issue is that you are in one process in your test bench.

The test bench will always run a process sequentially.

If you want to update the inputs simultaneously you will at least need to have different process.

 

With that said, I see several issues in what you are trying to do. You have a SCTL with only an adder in it tied to front panel variables.

 

Remember that every time you switch into a different time domain labview will toss extra mechanisms to prevent data loss or at least give it it's best shot. You probably don't want this to happen. I find it to be bad practice to have SCTL's inside subVI's, unless you are positive that you are never going to call that subVI from another SCTL. (can't have a SCTL inside a SCTL)

 

 

 

 

 


Engineering - The art of applied creativity  ~Theo Sutton
Message 8 of 9
(4,141 Views)
Solution
Accepted by JJMontante

@MrQuestion wrote:

I see that this thread is a little old, but I want to mention one obvious issue is that you are in one process in your test bench.

The test bench will always run a process sequentially.

If you want to update the inputs simultaneously you will at least need to have different process.

 

You can effectively update simultaneously with regards to simulation time by running multiple sequential statements within a single long process. Though the simulator software will view the statements at different real times, the simulation output will display the signal changes as coincident unless a wait-statement is used. The lack of concurrence is because of using the Write method, rather than directly updating one of the inputs - and the write method enforces the bus protocol.

 

With that said, I see several issues in what you are trying to do. You have a SCTL with only an adder in it tied to front panel variables.

 

  

 

Remember that every time you switch into a different time domain labview will toss extra mechanisms to prevent data loss or at least give it it's best shot. You probably don't want this to happen. I find it to be bad practice to have SCTL's inside subVI's, unless you are positive that you are never going to call that subVI from another SCTL. (can't have a SCTL inside a SCTL)

 

There are no multiple time domains in this example (other than the PCIe behind the scenes), and coming from a VHDL/VERILOG background, I realize that you don't put a clocked process in a clocked process- it doesn't make sense.  I have exercised proper practice by using one SCTL at the top level that was shown in the block diagram picture I had attached.  

 

 

 


That all being said, the best solution to the issue that I was given by Kristin H. (thanks Kristin) is to use a cluster as a single input and/or single output for a wrapper in the testbench.  The wrapper will call a subVI (your actual top level), and send the unbundled signals to the inputs on this subVI guaranteeing a true parallel update to the inputs of your VI.  

 

The whole issue was predicated on my lack of understanding of what the Write and Read methods were actually doing (emulating host activities via PCIe), rather than simply acting as methods to directly write to variables which would otherwise have given me the parallel updates I'd expected.  However, I am long past this issue.  

 

-J

Message 9 of 9
(4,135 Views)