plc structured text loop delay - plc

I am trying to have a loop where it will start at 100 and drop until it hits to a point where the while condition no longer holds true.
I started with
While Solar_Power_House_W_Solar_PER <= OneHundred AND BatChargePercent < OneHundred DO
State_Dis_Charge := false
FOR PLC_SetLoopChargeValue:= 100 TO 0 By -1 DO
ConvertoReal := INT_TO_LREAL(PLC_SetLoopChargeValue);
Divide := ConvertoReal DIV(100);
PLC_SetCharge := Divide;
PLC_Charge := 1500 * PLC_SetCharge;
RB_Charge := PLC_Charge;
Visual_RBPower := 1500 * PLC_SetCharge; (*Charge *)
END_FOR;
The problem I believe I have with this is that it cycles too fast so the condition never gets out of the while loop because it takes a while for the system to update so I thought of adding a delay portion:
While Solar_Power_House_W_Solar_PER <= OneHundred AND BatChargePercent < OneHundred DO
State_Dis_Charge := false;
wait(IN:=not wait.Q , PT:=T#50ms);
if Wait.Q Then
FOR PLC_SetLoopChargeValue:= 100 TO 0 By -1 DO
ConvertoReal := INT_TO_LREAL(PLC_SetLoopChargeValue);
Divide := ConvertoReal DIV(100);
PLC_SetCharge := Divide;
PLC_Charge := 1500 * PLC_SetCharge;
RB_Charge := PLC_Charge;
Visual_RBPower := 1500 * PLC_SetCharge; (*Charge *)
END_FOR;
END_IF;
END_WHILE;
How I think it should work is every 50ms 1 for loop should run. Currently nothing happens every 50ms.

You have to consider that WHILE and FOR are executed synchronously. It means blocking. It means that interpreter do not execute next line, until previous line is finished.
This means that "running to fast" cannot apply here. It does not matter how fast it runs, the execution of the lines will be always in order.
The only thing I would change and loop not from 100 to 0 but vice versa from 0 to 100, because I am not sure this backward will work fine. And then all you have to change:
ConvertoReal := INT_TO_LREAL(100 - PLC_SetLoopChargeValue);
You do now show all code it is VERY HARD to judge but if FOR loom is complete it totally make no sense. You calculate some variables but you do not use them there. You know that you cannot use them outside of your FOR loop, right? Because outside of your FOR loop those variable will be always same value of last loop.
In your second example your FOR loop, although it might work, you should not use timer to run the loop inside the loop. Because loops are synchronous and times async.
As I understand you task you do not need WHILE at all. With this approach your program execution of other parts will be blocked until 100%. That might take a while as I can see. So you have to use IF.
IF Solar_Power_House_W_Solar_PER <= OneHundred AND BatChargePercent < OneHundred DO
// ....
END_IF;
The difference is significant. With WHILE it will block your program till WHILE finish and other parts will not be executed for this long, in the same PLC cycle FOR might be executed so many times.
With IF if will run FOR one time per one PLC cycle and actualy doe snot change your logic.
If you would share your full code or at least parts where variables you have here are used so that the whole picture is visible, you might get a better help. Edit your post and I'll edit my comment.

With this answer im only adressing your issue with the for loop not being executed every 50ms.
The other answers why the while loop cant be exited are correct unless the variables Solar_Power_House_W_Solar_PER and BatChargePercent aren't changed in a parrellel thread.
I suggest wait is a TON function block. Please mind that names of FBs are case sensitive: wait.Q is possibly unequal Wait.Q. I think that is the main reason your for loop is not executed, because you check the output of another FB. Maybe check your declaration list for doubles with higher or lower cases.
Another possibility is, that your condition for the while loop isn't met at all and you didn't notice. In this case the for loop wouldn't be executed too of course.

Related

Roblox Leaderstats not updating/only updating once

Roblox Leaderstats aren't updating correctly.
When told to update leaderstats, it sometimes doesn't update, and other times, it only updates once.
Code is below:
game.ReplicatedStorage.sleep.OnServerEvent:Connect(function(plr)
local en = plr.leaderstats.Energy
en.Value += 1
print(en.value)
wait()
local speed = 5.07 * ((en.Value / 10) + 1)
plr.Character:WaitForChild("Humanoid").WalkSpeed = speed
print(speed)
end)
On times it doesn't update, the print(en.value) says 1 while the leaderstat stays as 0.
When leaderstat only updates once, the print only updates once as well.
Edit: The leaderstat was defined/created in a previous script.
I completely forgot to answer this, but I am using +=, you would assume it would add to the current amount as the hint says, but it doesn't
Old Code: en.Value += 1
What I do now: en.Value = en.Value + 1
You are declaring the "en" variable right before you add one to the value in this function. This means that every time you run this code, you will reset the "en" variable. If you declare the variable outside of the scope of this function, it should get rid of your issues.

How to understand the beat in chisel language?

I am studying the design of riscv-Boom. It is designed with chisel. When I read its source code, I always cannot understand when the signals in these circuits will be obtained. Normal programs are executed one by one. But hardware languages like chisel are not.
https://github.com/riscv-boom/riscv-boom/blob/master/src/main/scala/exu/issue-units/issue-slot.scala
For example, the link above is the source code of Issue-slot in Boom.
103: val next_uop = Mux(io.in_uop.valid, io.in_uop.bits, slot_uop)
113: state := io.in_uop.bits.iw_state
126: next_state := state
127: next_uopc := slot_uop.uopc
128: next_lrs1_rtype := slot_uop.lrs1_rtype
129: next_lrs2_rtype := slot_uop.lrs2_rtype
155 slot_uop := io.in_uop.bits
208: for loop
This is some code in the IssueSlot class in the link above. For the chisel hardware language, these := should mean that they are connected together by wires. So do these signals change at the same time? For example, when io.in_uop.valid is true, does the above code assign values at the same time?
For example, the current uop is fmul, and in_uop= is fadd. When io.in_uop.valid, the above code will be executed at the same time. But there is a problem.
origin
uop = fmux
when io.in_uop.valid
103: val next_uop = io.in_uop.bits (fadd uop)
113: state := io.in_uop.bits.iw_state (fadd state)
126: next_state := state (fmux state)
127: next_uopc := slot_uop.uopc (fmux uopc)
128: next_lrs1_rtype := slot_uop.lrs1_rtype (fmux lrs1_rtype )
129: next_lrs2_rtype := slot_uop.lrs2_rtype (fmux lrs2_rtype )
155 slot_uop := io.in_uop.bits (faddlrs2_rtype )
208: for loop
When io.in_uop.valid is true, the transmit slot at this time will be input fadd information. At the same time, the original fmul information will still be output to next-related signals. This should be unreasonable. Where does the problem occur?
For the for loop of line 207. I still find it difficult to understand. Will the for loop be executed in one beat? For example, if I use a for loop to traverse a queue, when did the for loop finish?
If anyone is willing to answer me, I would be extremely grateful!
first
The a := b expression mean that b is connected to a yes. b is the source and a the sink.
If a new connection to a is done after in the code, it will be replaced.
In the following code, a is connected to c:
a := b
a := c
This writing could be strange but it's usefull to set a default value in conditionnal branch.
For example :
a := b
when(z === true.B){
a := c
}
By default, a will be connected to b except when z is true.
second
Do not forget that Chisel is an HDL generator. It generate hardware code, some keywords are pure Scala like if, for, ... and other are hardware chisel keyword like when, Mux, ...
Then the for loop in line 208 is executed at the code generation stage. And it will generate some hardware chisel when mux code.
I'd highly recommend you spend some time with the Chisel Bootcamp. It can really help you grasp the generator aspects of chisel.

How should I handle Codesys SYS_TIME overflow after only 49 days?

The SYS_TIME function in ABB PLC / codesys programming returns a DWORD indicating the number of milliseconds since the PLC was turned on. (or perhaps hard reset / other event? Cannot find documentation of this.)
The max size of a DWORD in Codesys is 232-1 = 4,294,967,295.
This means SYS_TIME overflows after only 49.7 days!
Can anyone confirm exactly what the SYS_TIME function returns after 49.7 days has elapsed? Does it integer overflow and start counting from zero again?
This has important ramifications for using SYS_TIME for functions such as warning how long it is since some event occurred. (e.g. a read of a remote device via modbus).
Assuming it is just an integer overflow and SYS_TIME resets to zero, then the programmer can deal with this by e.g. resetting the variable they are using to record the last known event time:
(* Assuming now, last_event_time are suitably declared DWORDs *)
now := SYS_TIME(TRUE);
IF last_event_time > now THEN
last_event_time := 0;
END_IF
(* continue, performing check of how long since last event occurred etc.... *)
I'm hoping there is something I have missed that offers an alternative approach.
However - This is a GOTCHA that could trip up a PLC programmer who hadn't thought of this, causing an apparently fully functioning PLC program that has been tested extensively to fail after 49 days of use in the field.
Would be very helpful if there was an alternative to SYS_TIME that returns an LWORD, good for 5 billion years of uninterrupted service :-)
NB - I believe this function may be specific to the ABB AC500 range of PLCs, rather than a standard Codesys function, so this question is mostly directed at ABB & ABB PLC programmers.
There are few options, but I would use either one of the following
Read system date and time and keep it updated. You will get 1 second resolution when using DT. It's easy to do comparison between DTs when you convert them to DWORD first (epoch time). See my old answer for Codesys time update. Note that if you calculate something like DT1-DT2, you will get a result of TIME -> Same overflow problem is possible. That's why DT_TO_DWORD would be good idea.
Make your own time. Your PLC has a cycle time, that should always be precise the same. Use it for you calculation.
This is a simple example with different data types.
Note that it's also possible to read cycle time using some Codesys library if required, not sure which one though. See for example this.
PROGRAM PRG_Time
VAR CONSTANT
TASK_CYCLE_TIME_MS : WORD := 10; //Update this!
END_VAR
VAR_OUTPUT
Total : LREAL;
TotalMilliseconds : LWORD;
TotalSeconds : DWORD;
Milliseconds : WORD;
END_VAR
Total := Total + TASK_CYCLE_TIME_MS / 1000.0;
TotalMilliseconds := TotalMilliseconds + TASK_CYCLE_TIME_MS;
Milliseconds := Milliseconds + TASK_CYCLE_TIME_MS;
//Calculate seconds
IF Milliseconds >= 1000 THEN
TotalSeconds := TotalSeconds + 1;
Milliseconds := Milliseconds - 1000;
END_IF
The LREAL is precise enough for milliseconds. So basically here is a SYS_TIME as LWORD (the TotalMilliseconds)
Looking further into this, I have confirmed two things:
SYS_TIME is definitely an ABB AC500 specific function, included in library SysInt_AC500_V10.lib.
Quote from the online help for ABB AC500: "An overflow is reached after 49 days. After this, the counter restarts at 0.". So that answers the part about the behaviour of the function.
The second part of my question (what is the best way of handling this?) remains open...

How to modify bit bash sequence for write delays and read delays of DUT?

I have a DUT were the writes takes 2 clock cycles and reads consume 2 clock cycles before it could actually happen, I use regmodel and tried using inbuilt sequence uvm_reg_bit_bash_seq but it seems that the writes and reads happens at 1 clock cycle delay, could anyone tell what is the effective way to model 2 clock cycle delays and verify this, so that the DUT delays are taken care.
Facing the following error now,
Writing a 1 in bit #0 of register "ral_pgm.DIFF_STAT_CORE1" with initial value 'h0000000000000000 yielded 'h0000000000000000 instead of 'h0000000000000001
I have found one way of doing it, took the existing uvm_reg_single_bit_bash_seq and modified by adding p_sequencer and added 2 clock cycle delays after write and read method calls as per the DUT latency, this helped me in fixing the issue as well added a get call after write method to avoid fetching old value after read operation.
...
`uvm_declare_p_sequencer(s_master_sequencer)
rg.write(status, val, UVM_FRONTDOOR, map, this);
wait_for_clock(2); // write latency
rg.read(status, val, UVM_FRONTDOOR, map, this);
wait_for_clock(2); // read latency
if (status != UVM_IS_OK) begin
`uvm_error("mtm_reg_bit_bash_seq", $sformatf("Status was %s when reading register \"%s\" through map \"%s\".",
status.name(), rg.get_full_name(), map.get_full_name()));
end
val = rg.get(); // newly added method call (get) to fetch value after read
...
task wait_for_clock( int m = 1 );
repeat ( m ) begin
#(posedge p_sequencer.vif.CLK);
end
endtask: wait_for_clock
...

Full Circular Queue?

We are just learning about circular queue in class, and I got a few questions.
Since we define the tail as the empty space next to the last value, such as shown below:
|1| |3|4|5|6|
The head will be pointing to the number 3, and the tail will be pointing to the empty space between 1 and 3. I am confused on what happens if that space is filled up, so for example below:
|1|2|3|4|5|6|
Then the head will still be pointing to 3, but the tail needs to be pointing to the next box after the blank box before, thus it will be pointing to 3, or the header. What should I do about this?
When this situation occurs, your queue is full. You have a few options when to deal with new items that can be pushed:
discard the pushed event: only when some other item is popped can now items be pushed again. Neither head nor tail is updated.
Example: Think of an event queue that has filled up, new requests are simple ignored.
discard (pop) the oldest event on the queue: in this case you update both the head and tail pointer one place.
Example: buffering incomming image frames from a webcam for processing. For a 'live' feed you may prefer to discard the older frames when the processing has a hickup.
create a bigger queue: that is, allocate more memory on the fly
Example: you use a circular queue since it an efficient implementation that doesn't require memory allocation most of the time when you push items. However, you do not want to loose items on the queue so you allow reallocating more memory once in a while
What the right action is depends on your application.
PS.: Your implementation is based on keeping an empty slot in your queue to distinguish full and empty buffer. Another option is to keep a counter on the number of elements in your queue to make this distinction. More info can be found on Circular buffer (Wikipedia).
As I thought of it, a circular queue is circular in part because it does not get "filled up". It will simply always hold a set number of elements, throwing out old ones as needed.
So you will never fill up the empty space; if you insert a new element, you'll remove an old element, so there will still be an empty space.
In other words, in your diagram, if you insert a new number, say 0, your a queue that looks like the following (assuming 1 is the last element, 3 the first):
|1| |3|4|5|6|
it will then look as follows:
| |0|3|4|5|6|
However, some implementations of a circular queue simply throw an exception/error when their full length is reached, if that's what you want. Check out for example this.
Here is the most succinct explanation about queues that I
have ever found. You can expand on your queues based on this
foundation. Source: "Algorithms," by Robert Sedgewick.
const max=100;
var queue: aray[0..max]of integer;
head,tail: integer;
procedure put(v:integer);
begin
queue[tail] := v;
tail := tail + 1;
if (tail > max) then tail := 0;
end;
function get: integer;
begin
get := queue[head];
head := head + 1;
if (head > max) then head := 0;
end;
procedure queueinitialize;
begin
head := 0;
tail := 0;
end;
function queueempty: boolean;
begin
queueempty := (head = tail);
end;
"It is necessary to maintain two indices, one to the beginning of the queue (head) and one to the end (tail). The contents of the queue are all the elements in the array between head and tail, taking into account the "wraparound" back to 0 when the end of the array is encountered. If head = tail then the queue is defined to be empty; if head = tail + 1,
or tail = max and head = 0, it is defined to be full."
When the head and tail points to the same place then we say that the queue is full.No more elements can be added.To add any element ,you will have to remove an element from the queue.With this the head will get incremented and a space is again generated.