I would like to measure the execution time of a structured text (ST) program. The task associated with the program is running at 10 ms.
How do I measure execution time?
You can use free TwinCAT library Tc2_Utilities that has a function block Profiler.
https://infosys.beckhoff.com/english.php?content=../content/1033/tcplclib_tc2_utilities/35053195.html&id=1344160655692967299
The "Profiler" function block can be used to allow the execution time of PLC code to be measured.
The Infosys page has an example code also:
VAR
Profiler1 : PROFILER;
END_VAR
Profiler1(START := TRUE, RESET := TRUE);
//Do something here
Profiler1(START := FALSE);
//Now Profiler1.Data has the execution time
Of course, you can use Profiler but just for demonstration purpose you can measure execution time like this.
PROGRAM PLC_PRG
VAR
tStart: TIME; (* Time program start *)
tWork : TIME; (* Execution time *)
END_VAR
(* First line of main program *)
tStart := TIME();
// Your program here
(* Last line of your program *)
tWork := TIME() - tStart;
END_PROGRAM
Related
I'm pretty new to the EtherCAT/CANopen universe and trying to implement a custom slave.
The slave is passing conformance test so far and want to write to one of my Slave Data Objects, the slave is attached to a CX5120, which is found by the XAE and also shows the Slave device.
For that, I copied my ESI-file to the TwinCAT folder (C:\TwinCAT\3.1\Config\Io\EtherCAT).
I've created a small Structured Text PLC program that uses FB_EcCoESdoWrite to write data to address 0x607A. But when I set it active and try to connect, Visual Studio tells me that the device needs at least one Sync Master. Also, when setting bExecute to TRUE, I'm getting an error from the function. As far as I understand, I have to link variables between my ST program and the slave, but I don't see the need of linking variables because afaik the function call should manage the transmission? What are the steps to write to a SDO of an ESC? Can someone tell me what I'm missing or has a small example at hand?
PROGRAM MAIN
VAR
heartbeat : UINT;
fbSdoWrite : FB_EcCoESdoWrite;
sNetId : T_AmsNetId := '5.76.204.148.1.1'; (* NetId of EtherCAT Master *)
nSlaveAddr : UINT := 1001; (* Port Number of EtherCAT Slave *)
nIndex : WORD := 16#607A; (* CoE Object Index *)
nSubIndex : BYTE := 0; (* Subindex of CoE Object *)
nValue : UINT := 16#AAAA; (* variable to be written to the CoE Object *)
bExecute : BOOL; (* rising edge starts writing to the CoE Object *)
bError : BOOL;
nErrId : UDINT;
END_VAR
fbSdoWrite(
sNetId := sNetId,
nSlaveAddr := nSlaveAddr,
nIndex := nIndex,
nSubIndex := nSubIndex,
pSrcBuf := ADR(nValue),
cbBufLen := SIZEOF(nValue),
bExecute := bExecute
);
IF NOT fbSdoWrite.bBusy THEN
bExecute := FALSE;
IF NOT bError THEN
(* write successful *)
bError := FALSE;
nErrId := 0;
ELSE
(* write failed *)
bError := fbSdoWrite.bError;
nErrId := fbSdoWrite.nErrId;
END_IF
fbSdoWrite(bExecute := FALSE);
END_IF
Fixed problem by linking variable from PLC code to DevState-input of the device.
Linking to plain InfoData doesn't seem to work though.
You should assign a task to your devices who is responsible to read/write data. double click your master device, go to EtherCAT tab and click on Sync Unit Assignment
there select your terminals then available tasks and apply!
I need to run some code every time the PLC starts. This code should only be run once and then never again until the PLC is restarted. I initialize some global variables and validate the persistent data before allowing the main PLC to run. This is because the actions of the machine can be damaging if some of these variables are not setup correctly.
Is there a way to start/stop the other PLC tasks? I noticed TwinCAT doesn't support initialization and shutdown interrupts for PLC tasks.
TwinCAT has a 'PlcTaskSystemInfo' struct containing a boolean for FirstCycle. You can use that to run the initializing code only once.
VAR fbGetCurTaskIdx: GETCURTASKINDEX; (* Further example+explanation in Infosys *)
fbGetCurTaskIdx();
IF _TaskInfo[fbGetCurTaskIdx.index].FirstCycle THEN
(* Initialization code here *)
ELSE
(* Normal code here *)
END_IF;
I don't know of a way to start/stop individual PLC tasks. You can start/stop a runtime though.
But perhaps it can be as simple as this code below, which will only run when your PLC starts.
VAR initialized: BOOL := FALSE;
IF NOT initialized THEN
(* Run your initialization code here *)
initialized := TRUE;
END_IF
(* Rest of your program here *)
Edit:
I used a state machine inside the initialization code to help with the task allowed time issue.
Example:
VAR
Initialized : BOOL := FALSE;
Init_State : UINT := 0;
END_VAR
IF NOT Initialized THEN
(* Initialization State Machine *)
CASE Init_State OF
0: (* First step in initialization *)
Init_State := Init_State + 1;
1: (* Second step in initialization *)
Init_State := Init_State + 1;
.
.
.
n: (* Last step in initialization *)
Initialized := TRUE;
END_CASE
END_IF
I thought the peek function of uvm_reg returned the value in 0 simulation time. Since I needed this functionality, I implemented all my HDL backdoor access paths. This is the code I am using in my scoreboard
while (state == DISABLE) begin
uvm_reg_data_t val = 'hDEADBEEF;
uvm_status_e status;
`uvm_info(get_name(), "Start peek", UVM_LOW)
my_reg_block.my_reg.peek(status, val);
`uvm_info(get_name(), "End peek", UVM_LOW)
assert (val == 'h0)
#posedge(my_vif.clk); //Advance clock
end
My intention was: On every clock cycle, in zero simulation time, assert that my_reg is 0 when the state==DISABLE.
In simulation run, I notice this is fine until around the time that my_reg is changing. At the point, Start peek -> End peek takes about 10 clock cycles. In this time, my state is no longer DISABLE and ofcourse val != 'h0. Why does peek take so long to return?
I am using Questasim 10.4a
It may take some time, because peek is a SystemVerilog task, not a function.
Function will be executed in 0 Simulation Time, but Tasks can have the
timing delays as well.
Here is it's definition.
virtual task peek( output uvm_status_e status,
output uvm_reg_data_t value,
input string kind = "",
input uvm_sequence_base parent = null,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0 )
This is a solution for mutual exclusion problem from one of my exercises:
var blocked : array[0..1] of boolean; (* blocked is an array of two Boolean elements *)
turn : 0..1; (* turn can have value 0 or 1 *)
procedure P ( id : integer )
begin
repeat
blocked[id] := true;
while turn ≠ id do
begin
while blocked[1 – id] do; (* a do-nothing loop to implement busy waiting *)
turn := id
end;
< critical section >
blocked[id] := false;
< remainder of procedure >
until false
end;
begin (* main program *)
blocked[0] := false; blocked[1] := false;
turn := 0;
parbegin (* begin parallel execution of processes *)
P(0); P(1) (* invoke two concurrent processes, both to execute procedure P *)
Parend (* end parallel execution *)
end.
I am unable to understand when exactly a process executes particular statement in the code above. I know we can have multiple execution scenarios.
I want to know if this uncertainty of order of execution exists all the time.
And, is there any way to figure out what execution scenario will occur?
Thanks!
I use this code to wait for a specific simulation time
initial begin
$display("A");
wait($time>1000);
$display("B");
end
the simulation result is:
A
I didnot see B printed.
If I use following code, it works.
while($time <1000) #1;
Is it because vcs needs to judge the wait condition once any viriable in the condition statement changes, $time is changing too frequently so vcs doesnot allow this usage?
#Tudor 's answer enlighten me. I tried #Tudor 's code with some modification. It turns out when wait(func(arglist)); vcs only retry to evaluate the function when arglist changes. Because $time has no args, vcs will only evaluate $time the 1st time, won't retry.
module top;
int the_time = 0;
int in_arg = 0;
function int the_time_f(int in);
return the_time;
endfunction // the_time_f
initial begin
$display("A");
// This works because 'the_time' is a variable
//wait(the_time > 10);
// This doesn't work because 'the_time_f' is a function
wait(the_time_f(in_arg) >10);
$display("B at %t", $time);
end
initial begin
#10ns;
the_time = 11;
#10ns;
in_arg = 1;
#20ns;
$finish();
end
endmodule // top
got following result
A
B at 20ns
This seems to be a gray area in the standard. In section 9.4 Procedural timing controls of the IEEE Std 1800-2012 Standard, it's mentioned that event control can be either implicit (changed of nets or variables) or explicit (fields of type event). $time is a system function, not a variable. I've also tried using a function for the wait and it also doesn't work:
module top;
int the_time = 0;
function int the_time_f();
return the_time;
endfunction // the_time_f
initial begin
$display("A");
// This works because 'the_time' is a variable
//wait(the_time > 10);
// This doesn't work because 'the_time_f' is a function
wait(the_time_f() > 10);
$display("B");
end
initial begin
#10ns;
the_time = 11;
#20ns;
$finish();
end
endmodule // top
Waiting on a change of a variable works fine, but waiting for a change on a function's return value doesn't work. IMHO, the compiler should have flagged this as a compile error (same for using $time) since it seems to just ignore the statement.
In an event control #(expression) or wait(expression) that suspends a process, SystemVerilog scheduling semantics requires an event to evaluate the expression (called an evaluation event. See section 4.3 Event Simulation of the 1800-2012 LRM) If an expression includes a function, only the arguments to that function are visible to cause an event evaluation (There is an exception for class methods in at a write to any member of the object in the method call will cause an event) See section 9.4.2 Event control
In an event driven simulation, the value of time is just an attribute of the current time slot, it is never an event. The simulator processes all events for the current time slot in a queue, and when that queue is empty, it advances time to the next time slot queue. So it might simulate time slots 0,5,7,10, skipping over the unmentioned times. Using your while loop, that would create a time sot for every consecutive time unit between 0 and 1000 - extremely inefficient.
So just use
#(1000); // wait for 1000 relative time units