nodalis-compiler 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +134 -0
  2. package/package.json +59 -0
  3. package/src/compilers/CPPCompiler.js +272 -0
  4. package/src/compilers/Compiler.js +108 -0
  5. package/src/compilers/JSCompiler.js +293 -0
  6. package/src/compilers/iec-parser/parser.js +4254 -0
  7. package/src/compilers/st-parser/expressionConverter.js +155 -0
  8. package/src/compilers/st-parser/gcctranspiler.js +237 -0
  9. package/src/compilers/st-parser/jstranspiler.js +254 -0
  10. package/src/compilers/st-parser/parser.js +367 -0
  11. package/src/compilers/st-parser/tokenizer.js +78 -0
  12. package/src/compilers/support/generic/json.hpp +25526 -0
  13. package/src/compilers/support/generic/modbus.cpp +378 -0
  14. package/src/compilers/support/generic/modbus.h +124 -0
  15. package/src/compilers/support/generic/nodalis.cpp +421 -0
  16. package/src/compilers/support/generic/nodalis.h +798 -0
  17. package/src/compilers/support/generic/opcua.cpp +267 -0
  18. package/src/compilers/support/generic/opcua.h +50 -0
  19. package/src/compilers/support/generic/open62541.c +151897 -0
  20. package/src/compilers/support/generic/open62541.h +50357 -0
  21. package/src/compilers/support/jint/nodalis/Nodalis.sln +28 -0
  22. package/src/compilers/support/jint/nodalis/NodalisEngine/ModbusClient.cs +200 -0
  23. package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.cs +817 -0
  24. package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.csproj +16 -0
  25. package/src/compilers/support/jint/nodalis/NodalisEngine/OPCClient.cs +172 -0
  26. package/src/compilers/support/jint/nodalis/NodalisEngine/OPCServer.cs +275 -0
  27. package/src/compilers/support/jint/nodalis/NodalisPLC/NodalisPLC.csproj +19 -0
  28. package/src/compilers/support/jint/nodalis/NodalisPLC/Program.cs +197 -0
  29. package/src/compilers/support/jint/nodalis/NodalisPLC/bootstrap.bat +5 -0
  30. package/src/compilers/support/jint/nodalis/NodalisPLC/bootstrap.sh +5 -0
  31. package/src/compilers/support/jint/nodalis/build.bat +25 -0
  32. package/src/compilers/support/jint/nodalis/build.sh +31 -0
  33. package/src/compilers/support/nodejs/IOClient.js +110 -0
  34. package/src/compilers/support/nodejs/modbus.js +115 -0
  35. package/src/compilers/support/nodejs/nodalis.js +662 -0
  36. package/src/compilers/support/nodejs/opcua.js +194 -0
  37. package/src/nodalis.js +174 -0
@@ -0,0 +1,798 @@
1
+ // Copyright [2025] Nathan Skipper
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ /**
16
+ * @description Nodalis PLC Header
17
+ * @author Nathan Skipper, MTI
18
+ * @version 1.0.2
19
+ * @copyright Apache 2.0
20
+ */
21
+ #pragma once
22
+ #include <iostream>
23
+ #include <cstdint>
24
+ #include <string>
25
+ #include <cctype>
26
+ #include <chrono>
27
+ #include <type_traits> // for std::is_same
28
+ #include <math.h>
29
+ #include <vector>
30
+ #include <regex>
31
+ #include <stdexcept>
32
+ #define JSON_USE_IMPLICIT_CONVERSIONS 1
33
+ #define JSON_USE_WIDE_STRING 1
34
+ #include "json.hpp"
35
+ using json = nlohmann::json;
36
+
37
+ #pragma region "Program Timing"
38
+ extern uint64_t PROGRAM_COUNT;
39
+ extern std::chrono::steady_clock::time_point PROGRAM_START;
40
+ /**
41
+ * Provides the number of milliseconds since the program started.
42
+ * @returns Returns a ulong of the elapsed time, in milliseconds.
43
+ */
44
+ uint64_t elapsed();
45
+ #pragma endregion
46
+ #pragma region "Memory Handling"
47
+
48
+ /**
49
+ * Defines the total memory block for this PLC. This memory is a grid of 64x16 "sheets" or pages of memory.
50
+ * The first column is the row of 64, the second column is 16 registers for that sheet.
51
+ * Addresses are reserved based on MTI's standard registers, AI (physical inputs), AO (physical outputs),
52
+ * SW (switch inputs from HMIs), LD (LED outputs to HMIs), BI, BO, CI, CO (all free memory locations for logical operations)
53
+ * PROTECT, BREACH, TROUBLE, STAT1, STAT2, MISC1, MISC2, MISC3.
54
+ * Standard IEC address references break down in the following ways:
55
+ * %I - corresponds to the AI register MEMORY[x][0]. If requesting %IX0, the sheet row would be calculated by r = floor((0*8)/64) - which would yield 0.
56
+ * To reference each individual byte, we would get uint8_t* bytes = &MEMORY[r][0]; We then can reference the byte within this row by getting b = 0 % 8;
57
+ * We can then get the value of that byte by referencing bytes[b];
58
+ * %Q - Same as %I, except uint8_t* bytes = &MEMORY[r][1];
59
+ * %M - Virtual memory used for program interface. This takes up the other 14 columns in a row. A reference to %MX[a], where a is a numerical byte address would be used to calculate
60
+ * r = floor((a*8)/(64*14)). If a is 0, this would yield row 0. If a is 112, it would yield 1. The column would be c = floor(a/(8*14)) + 2, so 0 would yield 2 and 112 would also yield 2.
61
+ * The byte would be obtained by b = a % 8, so 0 would yield 0, and 112 would yield 0.
62
+ */
63
+ extern uint64_t MEMORY[64][16];
64
+
65
+ inline std::string toLowerCase(const std::string& input) {
66
+ std::string result = input;
67
+ for (size_t i = 0; i < result.size(); ++i) {
68
+ result[i] = std::tolower(result[i]);
69
+ }
70
+ return result;
71
+ }
72
+
73
+ /**
74
+ * Defines the memory space designations for use in getting memory addresses.
75
+ */
76
+ enum MEMORY_SPACE : int {
77
+ I, //input memory space
78
+ Q, //output memory space
79
+ M, //Virtual memory space
80
+ };
81
+
82
+ /**
83
+ * Parses a ST address reference into a vector with the memory space, type, byte index, and bit broken out.
84
+ * @param address A string representing the ST address.
85
+ * @returns Returns a vector with four elements: the memory space (Input, Output, or Virtual), the width in bits, the address index, and the bit.
86
+ */
87
+ inline std::vector<int> parseAddress(const std::string& address) {
88
+ std::regex pattern(R"(%([IQM])([XBWDL])(\d+)(?:\.(\d+))?)", std::regex::icase);
89
+ std::smatch match;
90
+
91
+ if (std::regex_match(address, match, pattern)) {
92
+ std::string space = match[1].str(); // I, Q, M
93
+ std::string type = match[2].str(); // X, W, D, etc.
94
+ std::string index = match[3].str(); // 0, 1, ...
95
+ std::string bit = match[4].matched ? match[4].str() : ""; // bit if present
96
+
97
+
98
+ int ispace = -1;
99
+ int addr = -1;
100
+ int ibit = -1;
101
+ int width = -1;
102
+ if(toLowerCase(space) == "m"){
103
+ ispace = MEMORY_SPACE::M;
104
+ }
105
+ else if(toLowerCase(space) == "q"){
106
+ ispace = MEMORY_SPACE::Q;
107
+ }
108
+ else if(toLowerCase(space) == "i"){
109
+ ispace = MEMORY_SPACE::I;
110
+ }
111
+
112
+ if(toLowerCase(type) == "x"){
113
+ width = 8;
114
+ }
115
+ else if(toLowerCase(type) == "w"){
116
+ width = 16;
117
+ }
118
+ else if(toLowerCase(type) == "d"){
119
+ width = 32;
120
+ }
121
+
122
+ if(bit != ""){
123
+ ibit = std::stoi(bit);
124
+ }
125
+
126
+
127
+ addr = std::stoi(index);
128
+ return {ispace, width, addr, ibit};
129
+ }
130
+
131
+ throw std::invalid_argument("Invalid address format: " + address);
132
+ }
133
+
134
+ /**
135
+ * Gets a byte pointer to a memory address in a certain memory space.
136
+ * @param space The memory space from which to get the address
137
+ * @param addr The byte index to pull from.
138
+ * @returns Returns a byte pointer to the memory address, or 0 if there is no memory at the given address.
139
+ */
140
+ inline uint8_t* getMemoryByte(int space, int addr){
141
+ uint8_t* ret = 0;
142
+ int r = -1, c = 0, b = 0;
143
+ switch(space){
144
+ case MEMORY_SPACE::Q:
145
+ r = floor((addr*8)/64);
146
+ c = 1;
147
+ b = addr % 8;
148
+ break;
149
+ case MEMORY_SPACE::I:
150
+ r = floor((addr*8)/64);
151
+ c = 0;
152
+ b = addr % 8;
153
+ break;
154
+ case MEMORY_SPACE::M:
155
+ r = floor((addr*8)/(64*14));
156
+ c = floor(addr/112) + 2;
157
+ b = addr % 8;
158
+ break;
159
+ }
160
+ if(r >= 0){
161
+ ret = (uint8_t*)(&MEMORY[r][c]) + b;
162
+ }
163
+ return ret;
164
+ }
165
+ /**
166
+ * Gets a word pointer to a memory address in a certain memory space.
167
+ * @param space The memory space from which to get the address.
168
+ * @param addr The word index to pull from.
169
+ * @returns Returns a word pointer to a memory address, or 0 if there is no memory at the given address.
170
+ */
171
+ inline uint16_t* getMemoryWord(int space, int addr){
172
+ return (uint16_t*)getMemoryByte(space, addr * 2);
173
+ }
174
+ /**
175
+ * Gets a double word pointer to a memory address in a certain memory space.
176
+ * @param space The memory space from which to get the address
177
+ * @param addr The double word index to pull from.
178
+ * @return Returns a double word pointer to a memory address, or 0 if there is no memory at the given address.
179
+ */
180
+ inline uint32_t* getMemoryDWord(int space, int addr){
181
+ return (uint32_t*) getMemoryByte(space, addr*4);
182
+ }
183
+
184
+ /**
185
+ * Reads the 32 bit value at a given address.
186
+ * @param address The address of the memory to get the 32 bit value from.
187
+ * @returns Returns a 32 bit value
188
+ */
189
+ uint32_t readDWord(std::string address);
190
+ /**
191
+ * Reads the 16 bit value at a given address.
192
+ * @param address The address of the memory to get the 16 bit value from.
193
+ * @returns Returns a 16 bit value
194
+ */
195
+ uint16_t readWord(std::string address);
196
+ /**
197
+ * Reads the 8 bit value at a given address.
198
+ * @param address The address of the memory to get the 8 bit value from.
199
+ * @returns Returns a 8 bit value
200
+ */
201
+ uint8_t readByte(std::string address);
202
+ /**
203
+ * Reads the bit value at a given address.
204
+ * @param address The address of the memory to get the bit value from.
205
+ * @returns Returns a boolean value indicating the status of the bit.
206
+ */
207
+ bool readBit(std::string address);
208
+ /**
209
+ * Writes a 32 bit value to an address in memory.
210
+ * @param address The address of memory to write to.
211
+ * @param value The 32 bit value to write to memory.
212
+ */
213
+ void writeDWord(std::string address, uint32_t value);
214
+ /**
215
+ * Writes a 16 bit value to an address in memory.
216
+ * @param address The address of memory to write to.
217
+ * @param value The 16 bit value to write to memory.
218
+ */
219
+ void writeWord(std::string address, uint16_t value);
220
+ /**
221
+ * Writes a 8 bit value to an address in memory.
222
+ * @param address The address of memory to write to.
223
+ * @param value The 8 bit value to write to memory.
224
+ */
225
+ void writeByte(std::string address, uint8_t value);
226
+ /**
227
+ * Writes a bit value to an address in memory.
228
+ * @param address The address of memory to write to.
229
+ * @param value The bit value to write to memory.
230
+ */
231
+ void writeBit(std::string address, bool value);
232
+ /**
233
+ * Gets the bit value from a variable
234
+ * @param var A pointer to the variable from which to get the bit.
235
+ * @param bit The number of the bit to get
236
+ * @returns Returns the state of the bit.
237
+ */
238
+ bool getBit(void* var, int bit);
239
+ /**
240
+ * Sets the bit in a variable.
241
+ * @param var A pointer to the variable to which to set the bit.
242
+ * @param bit The bit to set.
243
+ * @param value The state to set the bit to.
244
+ */
245
+ void setBit(void* var, int bit, bool value);
246
+ #pragma endregion
247
+ #pragma region "Reference Handling"
248
+
249
+ /**
250
+ * The RefVar class provides a means of declaring a variable with a reference to memory, similar to a pointer.
251
+ */
252
+ template<typename T>
253
+ class RefVar {
254
+ private:
255
+ /**
256
+ * The address of the memory.
257
+ */
258
+ std::string address;
259
+ /**
260
+ * The cached value of the address
261
+ */
262
+ T cache;
263
+
264
+ public:
265
+ /**
266
+ * Constructs a new RefVar object based on a given address
267
+ * @param addr The address to reference.
268
+ */
269
+ RefVar(const std::string& addr) : address(addr) {
270
+ cache = read();
271
+ }
272
+
273
+ virtual ~RefVar() = default;
274
+ /**
275
+ * Provides an assignment operator for RefVar so that it acts just like a primitive variable.
276
+ * @param value The value to assign.
277
+ */
278
+ RefVar<T>& operator=(T value) {
279
+ cache = value;
280
+ write(value);
281
+ return *this;
282
+ }
283
+
284
+ /**
285
+ * Provides a reference operator to provide a reference to this RefVar.
286
+ */
287
+ RefVar<T>& operator&(){
288
+ return *this;
289
+ }
290
+
291
+ /**
292
+ * Provides an expression operator so that a RefVar object can be used in a statement like any other variable and return its memory value.
293
+ */
294
+ operator T() const {
295
+ return read();
296
+ }
297
+
298
+ private:
299
+ /**
300
+ * Reads the value of the reference from memory.
301
+ */
302
+ T read() const {
303
+ if constexpr (std::is_same_v<T, bool>) {
304
+ return readBit(address);
305
+ } else if constexpr (std::is_same_v<T, uint8_t>) {
306
+ return readByte(address);
307
+ } else if constexpr (std::is_same_v<T, uint16_t>) {
308
+ return readWord(address);
309
+ } else if constexpr (std::is_same_v<T, uint32_t>) {
310
+ return readDWord(address);
311
+ } else {
312
+ static_assert(!std::is_same_v<T, T>, "Unsupported type for RefVar");
313
+ }
314
+ }
315
+ /**
316
+ * Writes the value to the memory referenced.
317
+ * @param value The value to assign to the memory.
318
+ */
319
+ void write(T value) const {
320
+ if constexpr (std::is_same_v<T, bool>) {
321
+ writeBit(address, value);
322
+ } else if constexpr (std::is_same_v<T, uint8_t>) {
323
+ writeByte(address, value);
324
+ } else if constexpr (std::is_same_v<T, uint16_t>) {
325
+ writeWord(address, value);
326
+ } else if constexpr (std::is_same_v<T, uint32_t>) {
327
+ writeDWord(address, value);
328
+ } else {
329
+ static_assert(!std::is_same_v<T, T>, "Unsupported type for RefVar");
330
+ }
331
+ }
332
+ };
333
+
334
+ /**
335
+ * Gets a bit from a RefVar object.
336
+ * @param var a reference to the RefVar object
337
+ * @param bit The bit to read.
338
+ * @returns Returns the state of the bit.
339
+ */
340
+ template<typename T>
341
+ bool getBit(RefVar<T>& var, int bit){
342
+ T ref = var;
343
+ return getBit(&ref, bit);
344
+ }
345
+ /**
346
+ * Sets a bit in a RefVar object
347
+ * @param var A reference to the RefVar object.
348
+ * @param bit The bit to set.
349
+ * @param value The state to set the bit to.
350
+ */
351
+ template<typename T>
352
+ void setBit(RefVar<T>& var, int bit, bool value){
353
+ T ref = var;
354
+ setBit(&ref, bit, value);
355
+ var = ref;
356
+ }
357
+ #pragma endregion
358
+ #pragma region "IO Handling"
359
+ /**
360
+ * Handles the aquisition of IO inputs and the application of IO outputs.
361
+ */
362
+ void superviseIO();
363
+
364
+ // Identifies direction of I/O mapping
365
+ enum class IOType {
366
+ Input,
367
+ Output
368
+ };
369
+
370
+ /**
371
+ * Defines a single mapping between a remote IO module address and an internal address in the PLC.
372
+ */
373
+ class IOMap {
374
+ public:
375
+ /**
376
+ * The direction of the IO interface.
377
+ */
378
+ IOType direction;
379
+ /**
380
+ * The unique identifier of the module. This can be the IP address or a unit ID.
381
+ */
382
+ std::string moduleID;
383
+ /**
384
+ * The port for the module communications port. In TCP/IP coms, this is the TCP port. In serial, this is the serial port.
385
+ */
386
+ std::string modulePort;
387
+ /**
388
+ * The name of the protocol for this map.
389
+ */
390
+ std::string protocol;
391
+ /**
392
+ * Additional properties, as defined by the protocol.
393
+ */
394
+ json additionalProperties;
395
+ /**
396
+ * The remote address, as it is understood by the protocol.
397
+ */
398
+ std::string remoteAddress; // e.g. "40001"
399
+ /**
400
+ * The local address, which is a memory address reference.
401
+ */
402
+ std::string localAddress; // e.g. "%MW1"
403
+ /**
404
+ * The bit of the address.
405
+ */
406
+ int bit = -1; // Optional bit index
407
+ /**
408
+ * The width of the input to read from the remote and store in the address.
409
+ */
410
+ int width = 16; // 8, 16, or 32
411
+ /**
412
+ * The interval at which the module should be polled for this address.
413
+ */
414
+ int interval = 500;
415
+ /**
416
+ * The last time the module was polled, in Milliseconds.
417
+ */
418
+ uint64_t lastPoll = 0;
419
+ /**
420
+ * Constructs a new IOMap object based on a string of JSON.
421
+ * @param A string of JSON properties.
422
+ */
423
+ IOMap(std::string mapJson);
424
+ IOMap();
425
+ };
426
+
427
+ /**
428
+ * The IOClient is an abstract class implemented by all protocol clients that will be used in Nodalis.
429
+ */
430
+ class IOClient {
431
+ public:
432
+ /**
433
+ * Indicates whether the IOClient is connected to its remote module.
434
+ */
435
+ bool connected;
436
+ IOClient(const std::string& protocol);
437
+ virtual ~IOClient() = default;
438
+
439
+ void addMapping(const IOMap& map);
440
+ bool hasMapping(std::string localAddress);
441
+
442
+ void poll(); // Reads and writes mapped I/O
443
+
444
+ const std::string& getProtocol() const;
445
+ const std::string& getModuleID() const;
446
+ protected:
447
+ std::string protocol;
448
+ std::string moduleID;
449
+ std::vector<IOMap> mappings;
450
+ uint64_t lastAttempt = 0;
451
+
452
+ // Must be implemented by derived classes
453
+ virtual bool readBit(const std::string& remote, int& result) = 0;
454
+ virtual bool writeBit(const std::string& remote, int value) = 0;
455
+ virtual bool readByte(const std::string& remote, uint8_t& result) = 0;
456
+ virtual bool writeByte(const std::string& remote, uint8_t value) = 0;
457
+ virtual bool readWord(const std::string& remote, uint16_t& result) = 0;
458
+ virtual bool writeWord(const std::string& remote, uint16_t value) = 0;
459
+ virtual bool readDWord(const std::string& remote, uint32_t& result) = 0;
460
+ virtual bool writeDWord(const std::string& remote, uint32_t value) = 0;
461
+ virtual void connect() = 0;
462
+ };
463
+
464
+ extern std::vector<std::unique_ptr<IOClient>> Clients;
465
+
466
+ IOClient* findClient(IOMap map);
467
+ std::unique_ptr<IOClient> createClient(IOMap& map);
468
+
469
+
470
+ void mapIO(std::string map);
471
+
472
+ #pragma endregion
473
+
474
+ #pragma region "Standard Function Blocks"
475
+
476
+ class TP{
477
+ public:
478
+ bool Q;
479
+ bool IN;
480
+ uint64_t PT;
481
+ uint64_t ET;
482
+
483
+
484
+ void operator()(){
485
+ Q = false;
486
+ if(!lastIN && IN){
487
+ lastIN = IN;
488
+ ET = 0;
489
+ startTime = 0;
490
+ }
491
+ if(IN){
492
+ Q = true;
493
+ }
494
+ else if(lastIN && !IN){
495
+ if(startTime == 0){
496
+ startTime = elapsed();
497
+ }
498
+ ET = elapsed() - startTime;
499
+ if(PT >= ET){
500
+ Q = true;
501
+ }
502
+ else{
503
+ lastIN = false;
504
+ }
505
+ }
506
+ }
507
+ private:
508
+ bool lastIN = false;
509
+ uint64_t startTime = 0;
510
+ };
511
+
512
+ // TON: On-delay timer
513
+ class TON {
514
+ public:
515
+ bool IN;
516
+ uint64_t PT;
517
+ bool Q = false;
518
+ uint64_t ET = 0;
519
+
520
+ void operator()() {
521
+ if (IN) {
522
+ if (startTime == 0) {
523
+ startTime = elapsed();
524
+ }
525
+ ET = elapsed() - startTime;
526
+ Q = ET >= PT;
527
+ } else {
528
+ startTime = 0;
529
+ ET = 0;
530
+ Q = false;
531
+ }
532
+ }
533
+
534
+ private:
535
+ uint64_t startTime = 0;
536
+ };
537
+
538
+ // TOF: Off-delay timer
539
+ class TOF {
540
+ public:
541
+ bool IN;
542
+ uint64_t PT;
543
+ bool Q = false;
544
+ uint64_t ET = 0;
545
+
546
+ void operator()() {
547
+ if (IN) {
548
+ Q = true;
549
+ startTime = 0;
550
+ ET = 0;
551
+ } else if (Q) {
552
+ if (startTime == 0) {
553
+ startTime = elapsed();
554
+ }
555
+ ET = elapsed() - startTime;
556
+ if (ET >= PT) {
557
+ Q = false;
558
+ }
559
+ }
560
+ }
561
+
562
+ private:
563
+ uint64_t startTime = 0;
564
+ };
565
+
566
+ // Boolean Logic Gates
567
+ #define BOOL_GATE(NAME, EXPR) \
568
+ class NAME { \
569
+ public: \
570
+ bool IN1 = false; \
571
+ bool IN2 = false; \
572
+ bool OUT = false; \
573
+ void operator()() { OUT = (EXPR); } \
574
+ };
575
+
576
+ BOOL_GATE(AND, IN1 && IN2)
577
+ BOOL_GATE(OR, IN1 || IN2)
578
+ BOOL_GATE(XOR, IN1 != IN2)
579
+ BOOL_GATE(NOR, !(IN1 || IN2))
580
+ BOOL_GATE(NAND, !(IN1 && IN2))
581
+ #undef BOOL_GATE
582
+
583
+ class NOT {
584
+ public:
585
+ bool IN = false;
586
+ bool OUT = false;
587
+ void operator()() { OUT = !IN; }
588
+ };
589
+
590
+ class ASSIGNMENT {
591
+ public:
592
+ bool IN = false;
593
+ bool OUT = false;
594
+ void operator()() { OUT = IN; }
595
+ };
596
+
597
+ // Set/Reset flip-flops
598
+ class SR {
599
+ public:
600
+ bool S1 = false;
601
+ bool R = false;
602
+ bool Q1 = false;
603
+
604
+ void operator()() {
605
+ if (R) Q1 = false;
606
+ if (S1) Q1 = true;
607
+ }
608
+ };
609
+
610
+ class RS {
611
+ public:
612
+ bool S = false;
613
+ bool R1 = false;
614
+ bool Q1 = false;
615
+
616
+ void operator()() {
617
+ if (S) Q1 = true;
618
+ if (R1) Q1 = false;
619
+ }
620
+ };
621
+
622
+ // Rising-edge Trigger
623
+ class R_TRIG {
624
+ public:
625
+ bool CLK = false;
626
+ bool OUT = false;
627
+
628
+ void operator()() {
629
+ OUT = CLK && !lastCLK;
630
+ lastCLK = CLK;
631
+ }
632
+
633
+ private:
634
+ bool lastCLK = false;
635
+ };
636
+
637
+ // Falling-edge Trigger
638
+ class F_TRIG {
639
+ public:
640
+ bool CLK = false;
641
+ bool OUT = false;
642
+
643
+ void operator()() {
644
+ OUT = !CLK && lastCLK;
645
+ lastCLK = CLK;
646
+ }
647
+
648
+ private:
649
+ bool lastCLK = false;
650
+ };
651
+
652
+ // Up Counter
653
+ class CTU {
654
+ public:
655
+ bool CU = false;
656
+ bool R = false;
657
+ uint16_t PV = 0;
658
+ uint16_t CV = 0;
659
+ bool Q = false;
660
+
661
+ void operator()() {
662
+ if (R) {
663
+ CV = 0;
664
+ } else if (CU && !lastCU) {
665
+ CV++;
666
+ }
667
+ Q = CV >= PV;
668
+ lastCU = CU;
669
+ }
670
+
671
+ private:
672
+ bool lastCU = false;
673
+ };
674
+
675
+ // Down Counter
676
+ class CTD {
677
+ public:
678
+ bool CD = false;
679
+ bool LD = false;
680
+ uint16_t PV = 0;
681
+ uint16_t CV = 0;
682
+ bool Q = false;
683
+
684
+ void operator()() {
685
+ if (LD) {
686
+ CV = PV;
687
+ } else if (CD && !lastCD && CV > 0) {
688
+ CV--;
689
+ }
690
+ Q = CV == 0;
691
+ lastCD = CD;
692
+ }
693
+
694
+ private:
695
+ bool lastCD = false;
696
+ };
697
+
698
+ // Up/Down Counter
699
+ class CTUD {
700
+ public:
701
+ bool CU = false;
702
+ bool CD = false;
703
+ bool R = false;
704
+ bool LD = false;
705
+ uint16_t PV = 0;
706
+ uint16_t CV = 0;
707
+ bool QU = false;
708
+ bool QD = false;
709
+
710
+ void operator()() {
711
+ if (R) {
712
+ CV = 0;
713
+ } else if (LD) {
714
+ CV = PV;
715
+ } else {
716
+ if (CU && !lastCU) CV++;
717
+ if (CD && !lastCD && CV > 0) CV--;
718
+ }
719
+
720
+ QU = CV >= PV;
721
+ QD = CV == 0;
722
+
723
+ lastCU = CU;
724
+ lastCD = CD;
725
+ }
726
+
727
+ private:
728
+ bool lastCU = false;
729
+ bool lastCD = false;
730
+ };
731
+
732
+ // Comparison blocks
733
+ #define COMP_BLOCK(NAME, EXPR) \
734
+ class NAME { \
735
+ public: \
736
+ uint32_t IN1 = 0, IN2 = 0; \
737
+ bool OUT = false; \
738
+ void operator()() { OUT = (EXPR); } \
739
+ };
740
+
741
+ COMP_BLOCK(EQ, IN1 == IN2)
742
+ COMP_BLOCK(NE, IN1 != IN2)
743
+ COMP_BLOCK(LT, IN1 < IN2)
744
+ COMP_BLOCK(GT, IN1 > IN2)
745
+ COMP_BLOCK(GE, IN1 >= IN2)
746
+ COMP_BLOCK(LE, IN1 <= IN2)
747
+ #undef COMP_BLOCK
748
+
749
+ class MOVE {
750
+ public:
751
+ uint32_t IN = 0;
752
+ uint32_t OUT = 0;
753
+ void operator()() { OUT = IN; }
754
+ };
755
+
756
+ class SEL {
757
+ public:
758
+ bool G = false;
759
+ uint32_t IN0 = 0, IN1 = 0;
760
+ uint32_t OUT = 0;
761
+ void operator()() { OUT = G ? IN1 : IN0; }
762
+ };
763
+
764
+ class MUX {
765
+ public:
766
+ bool K = false;
767
+ uint32_t IN0 = 0, IN1 = 0;
768
+ uint32_t OUT = 0;
769
+ void operator()() { OUT = K ? IN1 : IN0; }
770
+ };
771
+
772
+ class MIN {
773
+ public:
774
+ uint32_t IN1 = 0, IN2 = 0;
775
+ uint32_t OUT = 0;
776
+ void operator()() { OUT = std::min(IN1, IN2); }
777
+ };
778
+
779
+ class MAX {
780
+ public:
781
+ uint32_t IN1 = 0, IN2 = 0;
782
+ uint32_t OUT = 0;
783
+ void operator()() { OUT = std::max(IN1, IN2); }
784
+ };
785
+
786
+ class LIMIT {
787
+ public:
788
+ uint32_t MN = 0, IN = 0, MX = 0;
789
+ uint32_t OUT = 0;
790
+ void operator()() {
791
+ if (IN < MN) OUT = MN;
792
+ else if (IN > MX) OUT = MX;
793
+ else OUT = IN;
794
+ }
795
+ };
796
+
797
+
798
+ #pragma endregion