Merge pull request #22 from esromneb/ben/wasm
Added Wasm, existing NAPI is untouched
This commit is contained in:
commit
e1c5f08747
15
.gitignore
vendored
15
.gitignore
vendored
@ -1,8 +1,9 @@
|
||||
.nyc_output
|
||||
build
|
||||
coverage
|
||||
node_modules
|
||||
package-lock.json
|
||||
/.nyc_output
|
||||
/build
|
||||
/coverage
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
*.dot
|
||||
tmp
|
||||
vcd_parser.*
|
||||
/tmp
|
||||
/vcd_parser.*
|
||||
/out
|
||||
|
27
.travis.yml
Normal file
27
.travis.yml
Normal file
@ -0,0 +1,27 @@
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
language: node_js
|
||||
node_js:
|
||||
- 14
|
||||
|
||||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- docker run -dit --name emscripten --user "$(id -u):$(id -g)" -v $(pwd):/src trzeci/emscripten:1.39.11-upstream bash
|
||||
|
||||
before_script:
|
||||
- docker exec -it emscripten make wasm
|
||||
|
||||
# script:
|
||||
# - ls -la
|
||||
# - ls -la out
|
||||
# - whoami
|
||||
|
||||
|
||||
|
||||
# - docker run -dit --name dtest -v $(pwd):/src esromneb/simple-clang bash
|
||||
# - docker exec -it dtest make -C src test
|
130
Makefile
Normal file
130
Makefile
Normal file
@ -0,0 +1,130 @@
|
||||
.PHONY: wasm all important clean
|
||||
|
||||
all: wasm
|
||||
wasm: out/vcd.wasm
|
||||
|
||||
|
||||
WASM_MAIN = wasm_main.cpp
|
||||
|
||||
HPP_FILES = \
|
||||
vcd_parser.h \
|
||||
wasm_main.hpp \
|
||||
|
||||
|
||||
CPP_FILES = \
|
||||
vcd_parser.c \
|
||||
vcd_spans.c \
|
||||
|
||||
|
||||
|
||||
# this is a list of all C functions we want to publish to javascript
|
||||
# In the main cpp file, each of these is wrapped in extern "C" {}
|
||||
# the version here has a prepended underscore
|
||||
# all lines must have trailing comma
|
||||
EXPORT_STRING = \
|
||||
"_execute", \
|
||||
"_init", \
|
||||
"_setTrigger", \
|
||||
"_getTime", \
|
||||
|
||||
# warning and error flags
|
||||
CLANG_WARN_FLAGS = \
|
||||
-Wall -Wextra \
|
||||
-Wno-ignored-qualifiers \
|
||||
-Wundef \
|
||||
-Werror=return-type \
|
||||
-Wshadow \
|
||||
# -Wconversion
|
||||
|
||||
|
||||
CLANG_OTHER_FLAGS = \
|
||||
-DVCDWASM \
|
||||
|
||||
|
||||
|
||||
CLANG_O_FLAG = '-O3'
|
||||
|
||||
ifdef NOOPT
|
||||
CLANG_O_FLAG = ' '
|
||||
endif
|
||||
|
||||
ifdef OPT3
|
||||
CLANG_O_FLAG = '-O3'
|
||||
endif
|
||||
|
||||
# works however slows down
|
||||
#-s DISABLE_EXCEPTION_CATCHING=0 \
|
||||
|
||||
out/vcd.wasm: $(WASM_MAIN) $(CPP_FILES) $(HPP_FILES) Makefile
|
||||
mkdir -p out
|
||||
emcc $(WASM_MAIN) $(CPP_FILES) -s WASM=1 -o out/vcd.html \
|
||||
-s DISABLE_EXCEPTION_CATCHING=0 \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s ALLOW_TABLE_GROWTH=1 \
|
||||
-s EXPORTED_FUNCTIONS='[$(EXPORT_STRING) "_main"]' \
|
||||
-s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap", "addOnPostRun", "addFunction", "setValue", "getValue"]' \
|
||||
$(CLANG_O_FLAG) $(CLANG_WARN_FLAGS) $(CLANG_OTHER_FLAGS)
|
||||
|
||||
|
||||
.PHONY: patchlib patchlib1 patchlib2
|
||||
|
||||
# patchlib: patchlib1 patchlib2
|
||||
|
||||
# PTSRC1=lib/BehaviorTree.CPP/src/xml_parsing.cpp
|
||||
# PTPAT1=patch/xml_parsing.patch
|
||||
# # see https://stackoverflow.com/questions/7394290/how-to-check-return-value-from-the-shell-directive
|
||||
# PAPPLIED1 := $(shell patch -R -p0 -s -f --dry-run $(PTSRC1) < $(PTPAT1) 1>&2 2> /dev/null > /dev/null; echo $$?)
|
||||
|
||||
# # patch is pretty annoying to use here
|
||||
# # we would like to apply the patch, or skip if already applied exit 0
|
||||
# # inorder to do this, we first need to run a dry-run in the reverse direction
|
||||
# # then check the exit code, then run it in the forward direction if actually needed
|
||||
# # we also have to do the complicated line above to deal with exit codes
|
||||
# # see https://unix.stackexchange.com/questions/55780/check-if-a-file-or-folder-has-been-patched-already
|
||||
# patchlib1: $(PTPAT1)
|
||||
# ifneq ($(PAPPLIED1),0)
|
||||
# @echo "$(PTSRC1) is unpatched.\n"
|
||||
# patch --forward --reject-file=- $(PTSRC1) < $(PTPAT1)
|
||||
# else
|
||||
# @echo "$(PTSRC1) already patched, skipping..."
|
||||
# endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# PTSRC2=lib/BehaviorTree.CPP/src/tree_node.cpp
|
||||
# PTPAT2=patch/tree_node.patch
|
||||
# PAPPLIED2 := $(shell patch -R -p0 -s -f --dry-run $(PTSRC2) < $(PTPAT2) 1>&2 2> /dev/null > /dev/null; echo $$?)
|
||||
|
||||
# patchlib2: $(PTPAT2)
|
||||
# ifneq ($(PAPPLIED2),0)
|
||||
# @echo "$(PTSRC2) is unpatched.\n"
|
||||
# patch --forward --reject-file=- $(PTSRC2) < $(PTPAT2)
|
||||
# else
|
||||
# @echo "$(PTSRC2) already patched, skipping..."
|
||||
# endif
|
||||
|
||||
|
||||
|
||||
|
||||
.PHONY: all build watch dev start test pretest lint jestc copydist cleandist prepare
|
||||
.PHONY: test testonly
|
||||
|
||||
|
||||
watch:
|
||||
npm run watch
|
||||
|
||||
test:
|
||||
npm run test
|
||||
|
||||
testonly:
|
||||
npm run testonly
|
||||
|
||||
prepare:
|
||||
npm run prepare
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf out/*
|
||||
|
@ -37,6 +37,7 @@ const properties = {
|
||||
mask: 'ptr', // mask (x, z) of the signal on change event
|
||||
digitCount: 'i32',
|
||||
tmpStr: 'ptr',
|
||||
tmpStr2: 'ptr',
|
||||
stackPointer: 'i32',
|
||||
id: 'ptr',
|
||||
napi_env: 'ptr'
|
||||
|
29
docs/Emscripten.md
Normal file
29
docs/Emscripten.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Emscripten
|
||||
This file is a bit of notes on the Emscripten changes to the C code.
|
||||
|
||||
# Makefile
|
||||
I added a `Makefile` to the project. A few key things here.
|
||||
* warnings flags etc to in `CLANG_WARN_FLAGS`
|
||||
* compile time `#define` go in `CLANG_OTHER_FLAGS`
|
||||
* Every .c .cpp file goes in `CPP_FILES`
|
||||
* Every .h .hpp file goes in `HPP_FILES` however not required
|
||||
* This is only required if you want make "sensativity" to work correctly
|
||||
* The emscripten `.js` file is minified by default. to disable this run
|
||||
* `NOOPT=1 make wasm`
|
||||
* Doing this will also compile the c code with `O0`
|
||||
* Any c functions you want to access from javascript must be added to `EXPORT_STRING`
|
||||
* When you add them, you must add a `_` prefix
|
||||
|
||||
# Install
|
||||
I tested this project with `1.39.11`
|
||||
|
||||
```bash
|
||||
git clone https://github.com/emscripten-core/emsdk.git
|
||||
cd emsdk/
|
||||
git pull
|
||||
./emsdk install latest
|
||||
./emsdk activate latest
|
||||
source ./emsdk_env.sh
|
||||
```
|
||||
|
||||
See https://emscripten.org/docs/getting_started/downloads.html
|
@ -2,12 +2,16 @@
|
||||
|
||||
const pkg = require('../package.json');
|
||||
const parser = require('./parser.js');
|
||||
const wasmparser = require('./wasmparser.js');
|
||||
const and = require('./and.js');
|
||||
const activity = require('./activity.js');
|
||||
const wrapper = require('./wrapper.js');
|
||||
|
||||
module.exports = {
|
||||
version: pkg.version,
|
||||
and: and,
|
||||
activity: activity,
|
||||
parser: parser
|
||||
parser: parser,
|
||||
wasmparser: wasmparser,
|
||||
wrapper: wrapper
|
||||
};
|
||||
|
@ -12,9 +12,19 @@ module.exports = () => {
|
||||
const s = new stream.Writable();
|
||||
|
||||
// const lifee = new EventEmitter();
|
||||
|
||||
// gets called by c with 1 argument, a number
|
||||
const lifemit = s.emit.bind(s);
|
||||
|
||||
const triee = new EventEmitter();
|
||||
|
||||
// gets called by c with 5 arguments
|
||||
// string eventName
|
||||
// bigint state->time
|
||||
// int command
|
||||
// bigint state->value
|
||||
// bigint state->mask
|
||||
|
||||
const triemit = triee.emit.bind(triee);
|
||||
let triemit2 = triemit;
|
||||
|
||||
|
68
lib/wasmparser.js
Normal file
68
lib/wasmparser.js
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
const stream = require('stream');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
// let lib = require('bindings')('vcd.node');
|
||||
// const util = require('util');
|
||||
|
||||
|
||||
module.exports = async () => {
|
||||
|
||||
const lib = require('../lib/wrapper.js')();
|
||||
await lib.start();
|
||||
|
||||
const wires = {};
|
||||
const info = {stack: [wires], wires: wires};
|
||||
|
||||
const s = new stream.Writable();
|
||||
|
||||
|
||||
// gets called by c with 1 argument, a string
|
||||
const lifemit = s.emit.bind(s);
|
||||
|
||||
const triee = new EventEmitter();
|
||||
|
||||
// gets called by c with 5 arguments
|
||||
// string eventName
|
||||
// number state->time
|
||||
// int command
|
||||
// int state->value
|
||||
// int state->mask
|
||||
|
||||
const triemit = triee.emit.bind(triee);
|
||||
let triemit2 = triemit;
|
||||
|
||||
const cxt = lib.init(lifemit, triemit, info);
|
||||
|
||||
s._write = function (chunk, encoding, callback) {
|
||||
// console.log('about to write', chunk);
|
||||
lib.execute(cxt, lifemit, triemit2, info, chunk);
|
||||
// console.log(util.inspect(info, {showHidden: true, depth : null, colorize: true}));
|
||||
// console.log(info.stack[0].top);
|
||||
// console.log(info.stack[1]);
|
||||
// console.log(info.stack[0].top == info.stack[1]);
|
||||
callback();
|
||||
};
|
||||
|
||||
s.change = {
|
||||
on: (id, fn) => {
|
||||
triemit2 = triemit;
|
||||
// console.log(id, fn);
|
||||
triee.on(id, fn);
|
||||
const triggerString = triee.eventNames().join(' ') + ' ';
|
||||
lib.setTrigger(cxt, triggerString);
|
||||
},
|
||||
any: fn => {
|
||||
triemit2 = fn;
|
||||
lib.setTrigger(cxt, '\0');
|
||||
}
|
||||
};
|
||||
|
||||
s.info = info;
|
||||
|
||||
s.getTime = () => lib.getTime(cxt);
|
||||
|
||||
s.start = lib.start;
|
||||
|
||||
return s;
|
||||
};
|
179
lib/wrapper.js
Normal file
179
lib/wrapper.js
Normal file
@ -0,0 +1,179 @@
|
||||
'use strict';
|
||||
|
||||
/* global BigInt */
|
||||
|
||||
const dotProp = require('dot-prop');
|
||||
|
||||
function _waitForStart(mod) {
|
||||
return new Promise((resolve)=>{
|
||||
mod.addOnPostRun(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function u8ToBn(u8) {
|
||||
var hex = [];
|
||||
// let u8 = Uint8Array.from(buf);
|
||||
|
||||
u8.forEach(function (i) {
|
||||
var h = i.toString(16);
|
||||
if (h.length % 2) { h = '0' + h; }
|
||||
hex.push(h);
|
||||
});
|
||||
|
||||
hex.reverse();
|
||||
|
||||
return BigInt('0x' + hex.join(''));
|
||||
}
|
||||
|
||||
let startCalled = 0;
|
||||
|
||||
module.exports = () => {
|
||||
// let state = 0; // idle
|
||||
// let total = 0;
|
||||
// let start = 0;
|
||||
|
||||
const c = {};
|
||||
|
||||
const wasm = require('../out/vcd.js');
|
||||
|
||||
let bindCallback;
|
||||
|
||||
const bindCWrap = () => {
|
||||
const w = wasm.cwrap;
|
||||
c.execute = w('execute', 'number', ['number', 'number', 'number', 'number', 'number', 'string']);
|
||||
c.init = w('init', 'number', ['number', 'number', 'number', 'number']);
|
||||
c.getTime = w('getTime', 'number', ['number']);
|
||||
c.setTrigger = w('setTrigger', 'number', ['number', 'string']);
|
||||
};
|
||||
|
||||
const start = async() => {
|
||||
if( !startCalled ) {
|
||||
await _waitForStart(wasm);
|
||||
startCalled++;
|
||||
}
|
||||
bindCWrap();
|
||||
bindCallback();
|
||||
};
|
||||
|
||||
// gets a string from a c heap pointer and length
|
||||
const getString = (name, len) => {
|
||||
const view = wasm.HEAPU8.subarray(name, name+len);
|
||||
|
||||
let string = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
string += String.fromCharCode(view[i]);
|
||||
}
|
||||
return string;
|
||||
};
|
||||
|
||||
let boundInfo;
|
||||
|
||||
let boundSet;
|
||||
let boundGet;
|
||||
|
||||
let ee = [];
|
||||
|
||||
let boundEE0;
|
||||
let boundEE1;
|
||||
|
||||
let context = -1;
|
||||
|
||||
|
||||
// wasm.addFunction can't be called until after
|
||||
// start finishes
|
||||
bindCallback = () => {
|
||||
boundSet = wasm.addFunction(function(name, len, type, v0, v1) {
|
||||
|
||||
let prop = getString(name, len);
|
||||
let tmp;
|
||||
|
||||
switch(type) {
|
||||
// set number
|
||||
case 0:
|
||||
boundInfo[prop] = v0;
|
||||
// console.log(`setting ${prop} to ${boundInfo[prop]}`);
|
||||
break;
|
||||
// set string
|
||||
case 1:
|
||||
boundInfo[prop] = getString(v0, v1);
|
||||
// console.log(`setting ${prop} to ${boundInfo[prop]}`);
|
||||
break;
|
||||
// set string to path
|
||||
case 2:
|
||||
dotProp.set(boundInfo, prop, getString(v0, v1));
|
||||
// console.log(`setting ${prop} to ${getString(v0, v1)}`);
|
||||
break;
|
||||
// path to path (any type)
|
||||
case 3:
|
||||
tmp = dotProp.get(boundInfo, getString(v0, v1));
|
||||
// console.log(`for ${getString(v0, v1)} got ${tmp}, set to ${prop}`);
|
||||
dotProp.set(boundInfo, prop, tmp);
|
||||
break;
|
||||
// create empty object at path
|
||||
case 4:
|
||||
// console.log(`${prop} is new {}`);
|
||||
dotProp.set(boundInfo, prop, {});
|
||||
break;
|
||||
|
||||
default: throw new Error();
|
||||
}
|
||||
|
||||
|
||||
// viiiii means returns void, accepts int int int int int
|
||||
}, 'viiiii');
|
||||
|
||||
boundGet = wasm.addFunction(function(name, len) {
|
||||
let prop = getString(name, len);
|
||||
return prop;
|
||||
}, 'iii');
|
||||
|
||||
|
||||
boundEE0 = wasm.addFunction(function(name, len) {
|
||||
ee[0](getString(name, len));
|
||||
}, 'vii');
|
||||
|
||||
// const char* name, const size_t len, const uint64_t time, const uint8_t command, const int valueWords, const uint64_t* aValue, const uint64_t* aMask);
|
||||
// boundEE1 = wasm.addFunction(function(eventName, l0, time, command, valueWords, value, mask) {
|
||||
boundEE1 = wasm.addFunction(function(eventName, l0, time, command, valueWords, value, mask) {
|
||||
const name = getString(eventName, l0);
|
||||
// console.log(`event name`);
|
||||
// console.log(`event ${name} time ${time} cmd ${command} wrds ${valueWords}`);
|
||||
|
||||
|
||||
const view0 = wasm.HEAPU8.subarray(value, value+(valueWords*8));
|
||||
const view1 = wasm.HEAPU8.subarray(mask, mask+(valueWords*8));
|
||||
|
||||
let bigValue = u8ToBn(view0);
|
||||
let bigMask = u8ToBn(view1);
|
||||
|
||||
// console.log(bigValue.toString(16));
|
||||
|
||||
ee[1](name, time, command, bigValue, bigMask);
|
||||
}, 'viiiiiii');
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
start,
|
||||
c,
|
||||
init: (cb0, cb1, info) => {
|
||||
boundInfo = info;
|
||||
ee[0] = cb0;
|
||||
ee[1] = cb1;
|
||||
context = c.init(boundEE0, boundEE1, boundSet, boundGet);
|
||||
return context;
|
||||
},
|
||||
execute: (ctx, cb0, cb1, info, chunk) => {
|
||||
boundInfo = info;
|
||||
ee[0] = cb0;
|
||||
ee[1] = cb1;
|
||||
c.execute(ctx, boundEE0, boundEE1, boundSet, boundGet, chunk.toString());
|
||||
},
|
||||
setTrigger: (ctx, triggerString) => {
|
||||
return c.setTrigger(ctx, triggerString);
|
||||
},
|
||||
getTime: (ctx) => {
|
||||
return BigInt(c.getTime(ctx));
|
||||
}
|
||||
};
|
||||
};
|
@ -5,6 +5,8 @@
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"test": "eslint bin lib && nyc -r=text -r=lcov mocha",
|
||||
"testonly": "nyc -r=text -r=lcov mocha",
|
||||
"watch": "mocha --watch",
|
||||
"install": "node bin/build.js",
|
||||
"prepare": "node bin/build.js"
|
||||
},
|
||||
@ -31,6 +33,7 @@
|
||||
"dependencies": {
|
||||
"async": "^3.1.0",
|
||||
"bindings": "^1.5.0",
|
||||
"dot-prop": "^5.3.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"llparse": "^7.0.1"
|
||||
},
|
||||
|
@ -71,11 +71,7 @@ $timescale 1ns $end
|
||||
0"}G
|
||||
#200
|
||||
1"}G
|
||||
bzzzzxxxx11110000ZZZZXXXX11110000zzzzxxx`);
|
||||
|
||||
// break in the middle of the number scan
|
||||
|
||||
inst.write( `x11110000zzzzxxxx11110000 {u
|
||||
bzzzzxxxx11110000ZZZZXXXX11110000zzzzxxxx11110000zzzzxxxx11110000 {u
|
||||
#300
|
||||
0"}G
|
||||
b1111000000000000000000000000000000000000000000000000000000000000 {u
|
||||
|
76
test/wasm_any.js
Normal file
76
test/wasm_any.js
Normal file
File diff suppressed because one or more lines are too long
121
test/wasm_basic.js
Normal file
121
test/wasm_basic.js
Normal file
@ -0,0 +1,121 @@
|
||||
'use strict';
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const lib = require('../lib/index.js');
|
||||
|
||||
describe('wasm basic', () => {
|
||||
|
||||
it('typeof vcd', done => {
|
||||
expect(lib.wasmparser).to.be.an('function');
|
||||
done();
|
||||
});
|
||||
|
||||
it('typeof vcd instance', done => {
|
||||
expect(lib.wasmparser()).to.be.an('promise');
|
||||
done();
|
||||
});
|
||||
|
||||
it('fail: foo bar', async () => {
|
||||
const inst = await lib.wasmparser();
|
||||
expect(inst.write(Buffer.from(' foo bar ???'))).to.eq(true);
|
||||
expect(inst.info).to.deep.eq({
|
||||
stack: [{}],
|
||||
status: 'declaration',
|
||||
wires: {}
|
||||
});
|
||||
});
|
||||
|
||||
it('$comment', async () => {
|
||||
const inst = await lib.wasmparser();
|
||||
expect(inst.write(Buffer.from(
|
||||
' \n $comment some text $end $comment more text $end ???'
|
||||
))).to.eq(true);
|
||||
expect(inst.info).to.deep.eq({
|
||||
stack: [{}],
|
||||
status: 'declaration',
|
||||
wires: {}
|
||||
});
|
||||
});
|
||||
|
||||
it('$version', async () => {
|
||||
const inst = await lib.wasmparser();
|
||||
expect(inst.write(`
|
||||
$version Generated by VerilatedVcd $end
|
||||
$date Wed Sep 18 22:59:07 2019
|
||||
$end
|
||||
$timescale 1ns $end
|
||||
|
||||
$scope module top $end
|
||||
$var wire 1 "}G clock $end
|
||||
$scope module leaf $end
|
||||
$var wire 64 {u counter [63:0] $end
|
||||
$upscope $end
|
||||
$scope module fruit $end
|
||||
$var wire 4 u) point [3:0] $end
|
||||
$upscope $end
|
||||
$upscope $end
|
||||
|
||||
$enddefinitions $end
|
||||
`
|
||||
)).to.eq(true);
|
||||
|
||||
expect(inst.write(`
|
||||
|
||||
#1
|
||||
0"}G
|
||||
#2
|
||||
1"}G
|
||||
#300
|
||||
0"}G
|
||||
b1111000000000000 {u
|
||||
#301
|
||||
b0000111100000000 {u
|
||||
#302
|
||||
b0000000011110000 {u
|
||||
#303
|
||||
b0000000000001111 {u
|
||||
`
|
||||
)).to.eq(true);
|
||||
|
||||
expect(inst.info).to.deep.eq({
|
||||
status: 'simulation',
|
||||
varId: 'u)',
|
||||
wires: {
|
||||
top: {
|
||||
clock: '"}G',
|
||||
fruit: {
|
||||
point: 'u)'
|
||||
},
|
||||
leaf: {
|
||||
counter: '{u'
|
||||
}
|
||||
}
|
||||
},
|
||||
stack: [{
|
||||
top: {
|
||||
clock: '"}G',
|
||||
fruit: {
|
||||
point: 'u)'
|
||||
},
|
||||
leaf: {
|
||||
counter: '{u'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
clock: '"}G',
|
||||
fruit: {
|
||||
point: 'u)'
|
||||
},
|
||||
leaf: {
|
||||
counter: '{u'
|
||||
}
|
||||
},
|
||||
{
|
||||
point: 'u)'
|
||||
}]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* eslint-env mocha */
|
129
test/wasm_dump.js
Normal file
129
test/wasm_dump.js
Normal file
@ -0,0 +1,129 @@
|
||||
'use strict';
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const lib = require('../lib/index.js');
|
||||
|
||||
describe('wasm dump', () => {
|
||||
|
||||
let inst;
|
||||
|
||||
// return a promise, and mocha will wait until it resolves
|
||||
before(() => {
|
||||
const fn = async () => {
|
||||
inst = await lib.wasmparser();
|
||||
};
|
||||
return fn();
|
||||
});
|
||||
|
||||
it('simple wasm', done => {
|
||||
|
||||
const dump = [];
|
||||
['"}G', '{u', 'u)'] // array of all signal ids
|
||||
.map(id =>
|
||||
inst.change.on(id, (time, cmd, value, mask) => {
|
||||
dump.push({
|
||||
id,
|
||||
time,
|
||||
cmd,
|
||||
value,
|
||||
mask
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
inst.on('finish', () => {
|
||||
expect(inst.getTime()).to.eq(316n);
|
||||
expect(dump).to.deep.eq([
|
||||
{ id: '"}G', time: 100, cmd: 14, value: 0n, mask: 0n },
|
||||
{ id: '"}G', time: 200, cmd: 15, value: 1n, mask: 0n },
|
||||
{ id: '{u', time: 200, cmd: 19, value: 0xf0f0f0f0f0f0f0f0n, mask: 0xff00ff00ff00ff00n },
|
||||
{ id: '"}G', time: 300, cmd: 14, value: 0n, mask: 0n },
|
||||
{ id: '{u', time: 300, cmd: 19, value: 0xf000000000000000n, mask: 0n },
|
||||
{ id: '{u', time: 301, cmd: 19, value: 0x0f00000000000000n, mask: 0n },
|
||||
{ id: '{u', time: 302, cmd: 19, value: 0x00f0000000000000n, mask: 0n },
|
||||
{ id: '{u', time: 303, cmd: 19, value: 0x000f000000000000n, mask: 0n },
|
||||
{ id: '{u', time: 304, cmd: 19, value: 0x0000f00000000000n, mask: 0n },
|
||||
{ id: '{u', time: 305, cmd: 19, value: 0x00000f0000000000n, mask: 0n },
|
||||
{ id: '{u', time: 306, cmd: 19, value: 0x000000f000000000n, mask: 0n },
|
||||
{ id: '{u', time: 307, cmd: 19, value: 0x0000000f00000000n, mask: 0n },
|
||||
{ id: '{u', time: 308, cmd: 20, value: 0x00000000f0000000n, mask: 0n },
|
||||
{ id: '{u', time: 309, cmd: 19, value: 0x000000000f000000n, mask: 0n },
|
||||
{ id: '{u', time: 310, cmd: 19, value: 0x0000000000f00000n, mask: 0n },
|
||||
{ id: '{u', time: 311, cmd: 19, value: 0x00000000000f0000n, mask: 0n },
|
||||
{ id: '{u', time: 312, cmd: 19, value: 0x000000000000f000n, mask: 0n },
|
||||
{ id: '{u', time: 313, cmd: 19, value: 0x0000000000000f00n, mask: 0n },
|
||||
{ id: '{u', time: 314, cmd: 19, value: 0x00000000000000f0n, mask: 0n },
|
||||
{ id: '{u', time: 315, cmd: 19, value: 0x000000000000000fn, mask: 0n },
|
||||
{ id: '"}G', time: 316, cmd: 15, value: 1n, mask: 0n },
|
||||
])
|
||||
// console.log(dump);
|
||||
done();
|
||||
});
|
||||
|
||||
inst.write(`
|
||||
$version Generated by VerilatedVcd $end
|
||||
$date Wed Sep 18 22:59:07 2019
|
||||
$end
|
||||
$timescale 1ns $end
|
||||
|
||||
$scope module top $end
|
||||
$var wire 1 "}G clock $end
|
||||
$scope module leaf $end
|
||||
$var wire 64 {u counter [63:0] $end
|
||||
$upscope $end
|
||||
$scope module fruit $end
|
||||
$var wire 4 u) point [3:0] $end
|
||||
$upscope $end
|
||||
$upscope $end
|
||||
|
||||
$enddefinitions $end
|
||||
#100
|
||||
0"}G
|
||||
#200
|
||||
1"}G
|
||||
bzzzzxxxx11110000ZZZZXXXX11110000zzzzxxx`);
|
||||
|
||||
// break in the middle of the number scan
|
||||
|
||||
inst.write( `x11110000zzzzxxxx11110000 {u
|
||||
#300
|
||||
0"}G
|
||||
b1111000000000000000000000000000000000000000000000000000000000000 {u
|
||||
#301
|
||||
b0000111100000000000000000000000000000000000000000000000000000000 {u
|
||||
#302
|
||||
b0000000011110000000000000000000000000000000000000000000000000000 {u
|
||||
#303
|
||||
b0000000000001111000000000000000000000000000000000000000000000000 {u
|
||||
#304
|
||||
b0000000000000000111100000000000000000000000000000000000000000000 {u
|
||||
#305
|
||||
b0000000000000000000011110000000000000000000000000000000000000000 {u
|
||||
#306
|
||||
b0000000000000000000000001111000000000000000000000000000000000000 {u
|
||||
#307
|
||||
b0000000000000000000000000000111100000000000000000000000000000000 {u
|
||||
#308
|
||||
B0000000000000000000000000000000011110000000000000000000000000000 {u
|
||||
#309
|
||||
b0000000000000000000000000000000000001111000000000000000000000000 {u
|
||||
#310
|
||||
b0000000000000000000000000000000000000000111100000000000000000000 {u
|
||||
#311
|
||||
b0000000000000000000000000000000000000000000011110000000000000000 {u
|
||||
#312
|
||||
b0000000000000000000000000000000000000000000000001111000000000000 {u
|
||||
#313
|
||||
b0000000000000000000000000000000000000000000000000000111100000000 {u
|
||||
#314
|
||||
b0000000000000000000000000000000000000000000000000000000011110000 {u
|
||||
#315
|
||||
b0000000000000000000000000000000000000000000000000000000000001111 {u
|
||||
#316
|
||||
1"}G
|
||||
`);
|
||||
inst.end();
|
||||
});
|
||||
});
|
||||
|
||||
/* eslint-env mocha */
|
84
test/wasm_events.js
Normal file
84
test/wasm_events.js
Normal file
@ -0,0 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const lib = require('../lib/index.js');
|
||||
|
||||
describe('wasm events', () => {
|
||||
let inst;
|
||||
// return a promise, and mocha will wait until it resolves
|
||||
before(() => {
|
||||
const fn = async () => {
|
||||
inst = await lib.wasmparser();
|
||||
};
|
||||
return fn();
|
||||
});
|
||||
|
||||
it('$enddefinitions', done => {
|
||||
|
||||
inst.on('$enddefinitions', () => {
|
||||
expect(inst.info).to.deep.eq({
|
||||
status: 'simulation',
|
||||
varId: 'u)',
|
||||
wires: {
|
||||
top: {
|
||||
clock: '"}G',
|
||||
fruit: {
|
||||
point: 'u)'
|
||||
},
|
||||
leaf: {
|
||||
counter: '{u'
|
||||
}
|
||||
}
|
||||
},
|
||||
stack: [{
|
||||
top: {
|
||||
clock: '"}G',
|
||||
fruit: {
|
||||
point: 'u)'
|
||||
},
|
||||
leaf: {
|
||||
counter: '{u'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
clock: '"}G',
|
||||
fruit: {
|
||||
point: 'u)'
|
||||
},
|
||||
leaf: {
|
||||
counter: '{u'
|
||||
}
|
||||
},
|
||||
{
|
||||
point: 'u)'
|
||||
}]
|
||||
});
|
||||
});
|
||||
expect(inst.write(`
|
||||
$version Generated by VerilatedVcd $end
|
||||
$date Wed Sep 18 22:59:07 2019
|
||||
$end
|
||||
$timescale 1ns $end
|
||||
|
||||
$scope module top $end
|
||||
$var wire 1 "}G clock $end
|
||||
$scope module leaf $end
|
||||
$var wire 64 {u counter [63:0] $end
|
||||
$upscope $end
|
||||
$scope module fruit $end
|
||||
$var wire 4 u) point [3:0] $end
|
||||
$upscope $end
|
||||
$upscope $end
|
||||
|
||||
$enddefinitions $end
|
||||
`
|
||||
)).to.eq(true);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
/* eslint-env mocha */
|
2
vcd.c
2
vcd.c
@ -124,6 +124,7 @@ METHOD(init) {
|
||||
|
||||
static char triggerString [4096] = " ";
|
||||
static char tmpStr [4096] = " ";
|
||||
static char tmpStr2 [4096] = " ";
|
||||
static uint64_t valueBuf [4096] = {};
|
||||
static uint64_t maskBuf [4096] = {};
|
||||
|
||||
@ -131,6 +132,7 @@ METHOD(init) {
|
||||
state->reason = "NO REASON";
|
||||
state->napi_env = env;
|
||||
state->tmpStr = tmpStr;
|
||||
state->tmpStr2 = tmpStr2;
|
||||
state->value = valueBuf;
|
||||
state->mask = maskBuf;
|
||||
state->digitCount = 0;
|
||||
|
67
vcd_spans.c
67
vcd_spans.c
@ -1,7 +1,22 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "vcd_parser.h"
|
||||
|
||||
#ifndef VCDWASM
|
||||
#include <node_api.h>
|
||||
#else
|
||||
#include "wasm_main.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef VCDWASM
|
||||
typedef void* napi_env;
|
||||
#endif
|
||||
|
||||
|
||||
// #define LOGSPAN
|
||||
// #define LOGSPAN printf("%s\n", __FUNCTION__);
|
||||
|
||||
|
||||
#define ASSERT(val, expr) \
|
||||
if (expr != napi_ok) { \
|
||||
@ -60,6 +75,7 @@ int commandSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char
|
||||
}
|
||||
|
||||
if (state->command == 8) { // $enddefinitions
|
||||
#ifndef VCDWASM
|
||||
napi_value status, undefined, eventName, eventPayload, return_val;
|
||||
ASSERT(status, napi_create_string_latin1(env, "simulation", NAPI_AUTO_LENGTH, &status))
|
||||
ASSERT(state->info, napi_set_named_property(env, state->info, "status", status))
|
||||
@ -68,6 +84,10 @@ int commandSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char
|
||||
// ASSERT(eventPayload, napi_create_string_latin1(env, "payload", NAPI_AUTO_LENGTH, &eventPayload))
|
||||
napi_value* argv[] = { &eventName }; // , &eventPayload };
|
||||
ASSERT(state->lifee, napi_call_function(env, undefined, state->lifee, 1, *argv, &return_val))
|
||||
#else
|
||||
set_property_string("status", "simulation");
|
||||
emit_lifee("$enddefinitions");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -75,17 +95,36 @@ int commandSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char
|
||||
}
|
||||
|
||||
int scopeIdentifierSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char* endp) {
|
||||
#ifndef VCDWASM
|
||||
napi_env env = state->napi_env;
|
||||
// *(endp - 1) = 0; // FIXME NULL termination of ASCII string
|
||||
strcopy(p, endp, state->tmpStr);
|
||||
napi_value name, obj, stack, top;
|
||||
ASSERT(name, napi_create_string_latin1(env, (char*)p, (endp - p - 1), &name))
|
||||
napi_value obj, stack, top;
|
||||
ASSERT(obj, napi_create_object(env, &obj))
|
||||
ASSERT(state->info, napi_get_named_property(env, state->info, "stack", &stack))
|
||||
|
||||
// get the top of the stack in top
|
||||
ASSERT(top, napi_get_element(env, stack, state->stackPointer, &top))
|
||||
|
||||
// set top.prop to new object
|
||||
ASSERT(top, napi_set_named_property(env, top, state->tmpStr, obj))
|
||||
|
||||
state->stackPointer += 1;
|
||||
ASSERT(top, napi_set_element(env, stack, state->stackPointer, obj))
|
||||
#else
|
||||
strcopy(p, endp, state->tmpStr); // load the value into temp string 1
|
||||
|
||||
// set stack[sp].`tmpStr` to {}
|
||||
snprintf(state->tmpStr2, 4096, "stack.%d.%s", state->stackPointer, state->tmpStr);
|
||||
new_object_path(state->tmpStr2);
|
||||
|
||||
// bump
|
||||
state->stackPointer += 1;
|
||||
|
||||
// set stack[sp+1] to the same object as stack[sp].`tmpStr`
|
||||
snprintf(state->tmpStr, 4096, "stack.%d", state->stackPointer);
|
||||
|
||||
set_path_to_path(state->tmpStr, state->tmpStr2);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -95,14 +134,20 @@ int varSizeSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char
|
||||
}
|
||||
|
||||
int varIdSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char* endp) {
|
||||
#ifndef VCDWASM
|
||||
napi_env env = state->napi_env;
|
||||
napi_value varId;
|
||||
ASSERT(varId, napi_create_string_latin1(env, (char*)p, (endp - p - 1), &varId))
|
||||
ASSERT(state->info, napi_set_named_property(env, state->info, "varId", varId))
|
||||
#else
|
||||
strcopy(p, endp, state->tmpStr);
|
||||
set_property_string("varId", state->tmpStr);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int varNameSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char* endp) {
|
||||
#ifndef VCDWASM
|
||||
napi_env env = state->napi_env;
|
||||
// *(endp - 1) = 0; // FIXME NULL termination of ASCII string
|
||||
strcopy(p, endp, state->tmpStr);
|
||||
@ -111,11 +156,21 @@ int varNameSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char
|
||||
ASSERT(top, napi_get_element(env, stack, state->stackPointer, &top))
|
||||
ASSERT(state->info, napi_get_named_property(env, state->info, "varId", &varId))
|
||||
ASSERT(state->info, napi_set_named_property(env, top, state->tmpStr, varId))
|
||||
#else
|
||||
strcopy(p, endp, state->tmpStr);
|
||||
// set
|
||||
// info.stack[sp].`tmpStr` = info.varId
|
||||
snprintf(state->tmpStr2, 4096, "stack.%d.%s", state->stackPointer, state->tmpStr);
|
||||
set_path_to_path(state->tmpStr2, "varId");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int idSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char* endp) {
|
||||
#ifndef VCDWASM
|
||||
napi_env env = state->napi_env;
|
||||
#endif
|
||||
|
||||
const int valueWords = (state->digitCount >> 6) + 1;
|
||||
uint64_t* value = state->value;
|
||||
uint64_t* mask = state->mask;
|
||||
@ -129,6 +184,7 @@ int idSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char* end
|
||||
value[0] = 1;
|
||||
mask[0] = 0;
|
||||
}
|
||||
#ifndef VCDWASM
|
||||
napi_value undefined, eventName, aTime, aCommand, aValue, aMask, return_val;
|
||||
ASSERT(undefined, napi_get_undefined(env, &undefined))
|
||||
ASSERT(eventName, napi_create_string_latin1(env, (char*)p, (endp - p - 1), &eventName))
|
||||
@ -138,6 +194,11 @@ int idSpan(vcd_parser_t* state, const unsigned char* p, const unsigned char* end
|
||||
ASSERT(aMask, napi_create_bigint_words(env, 0, valueWords, mask, &aMask))
|
||||
napi_value* argv[] = {&eventName, &aTime, &aCommand, &aValue, &aMask};
|
||||
ASSERT(state->triee, napi_call_function(env, undefined, state->triee, 5, *argv, &return_val))
|
||||
|
||||
#else
|
||||
strcopy(p, endp, state->tmpStr);
|
||||
emit_triee(state->tmpStr, state->time, command, valueWords, value, mask);
|
||||
#endif
|
||||
}
|
||||
for (int i = 0; i < valueWords; i++) {
|
||||
value[i] = 0;
|
||||
|
202
wasm_main.cpp
Normal file
202
wasm_main.cpp
Normal file
@ -0,0 +1,202 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include "vcd_parser.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/// Typedef used as part of c->js call
|
||||
typedef void externalJsMethodZero(const char* name, const size_t len);
|
||||
// typedef void externalJsMethodOne (const char* name, const size_t len, const uint64_t time, const uint8_t command, const int valueWords, const int aValue, const int aMask);
|
||||
typedef void externalJsMethodOne (const char* name, const size_t len, const int time, const int command, const int valueWords, const int aValue, const int aMask);
|
||||
|
||||
typedef int externalJsGetProperty(const char* name, const size_t len);
|
||||
typedef void externalJsSetProperty(const char* name, const size_t len, const int type, const int v0, const int v1);
|
||||
|
||||
|
||||
|
||||
/// function pointer for c->js
|
||||
static externalJsMethodZero* externalZero = 0;
|
||||
static externalJsMethodOne* externalOne = 0;
|
||||
static externalJsSetProperty* bound_set_property = 0;
|
||||
static externalJsGetProperty* bound_get_property = 0;
|
||||
static struct vcd_parser_s* state;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void set_property_int(const char* name, const int value) {
|
||||
bound_set_property(name, strlen(name), 0, value, 0);
|
||||
}
|
||||
|
||||
void set_property_string(const char* name, const char* value) {
|
||||
bound_set_property(name, strlen(name), 1, (int)value, strlen(value));
|
||||
}
|
||||
|
||||
void set_path_string(const char* name, const char* value) {
|
||||
bound_set_property(name, strlen(name), 2, (int)value, strlen(value));
|
||||
}
|
||||
|
||||
void set_path_to_path(const char* name, const char* value) {
|
||||
bound_set_property(name, strlen(name), 3, (int)value, strlen(value));
|
||||
}
|
||||
|
||||
void new_object_path(const char* name) {
|
||||
bound_set_property(name, strlen(name), 4, 0, 0);
|
||||
}
|
||||
|
||||
int get_property_int(const char* name) {
|
||||
return bound_get_property(name, strlen(name));
|
||||
}
|
||||
|
||||
void emit_lifee(const char* name) {
|
||||
externalZero(name, strlen(name));
|
||||
}
|
||||
|
||||
void emit_triee(const char* name, const int64_t time, const int command, const int valueWords, uint64_t* aValue, uint64_t* aMask) {
|
||||
|
||||
// return;
|
||||
// externalOne(
|
||||
// "hi"
|
||||
// ,2
|
||||
// ,time
|
||||
// ,command
|
||||
// ,0
|
||||
// ,0
|
||||
// ,0
|
||||
// );
|
||||
externalOne(
|
||||
name,
|
||||
strlen(name),
|
||||
time,
|
||||
command,
|
||||
valueWords,
|
||||
(int)aValue,
|
||||
(int)aMask
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// returns context
|
||||
int init(
|
||||
externalJsMethodZero* f0,
|
||||
externalJsMethodOne* f1,
|
||||
externalJsSetProperty* sfn,
|
||||
externalJsGetProperty* gfn
|
||||
) {
|
||||
|
||||
|
||||
state = (struct vcd_parser_s*) malloc(sizeof *state);
|
||||
|
||||
const int32_t error = vcd_parser_init(state);
|
||||
if (error) {
|
||||
cout << "ERROR: " << error << "\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
bound_set_property = sfn;
|
||||
bound_get_property = gfn;
|
||||
externalZero = f0;
|
||||
externalOne = f1;
|
||||
|
||||
state->lifee = 0;
|
||||
state->triee = 0;
|
||||
|
||||
static char triggerString [4096] = " ";
|
||||
static char tmpStr [4096] = " ";
|
||||
static char tmpStr2 [4096] = " ";
|
||||
static uint64_t valueBuf [4096] = {};
|
||||
static uint64_t maskBuf [4096] = {};
|
||||
|
||||
state->trigger = triggerString;
|
||||
state->reason = "NO REASON";
|
||||
state->napi_env = 0;
|
||||
state->tmpStr = tmpStr;
|
||||
state->tmpStr2 = tmpStr2;
|
||||
state->value = valueBuf;
|
||||
state->mask = maskBuf;
|
||||
state->digitCount = 0;
|
||||
|
||||
set_property_string("status", "declaration");
|
||||
|
||||
|
||||
static int context = 0;
|
||||
context++;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int32_t execute(
|
||||
const int context,
|
||||
externalJsMethodZero* f0,
|
||||
externalJsMethodOne* f1,
|
||||
externalJsSetProperty* sfn,
|
||||
externalJsGetProperty* gfn,
|
||||
char* p
|
||||
) {
|
||||
|
||||
// cout << "execute got " << p << "\n";
|
||||
// cout << "execute " << (int)sfn << " and got " << p << "\n";
|
||||
bound_set_property = sfn;
|
||||
bound_get_property = gfn;
|
||||
externalZero = f0;
|
||||
externalOne = f1;
|
||||
|
||||
const size_t plen = strlen(p);
|
||||
|
||||
const int32_t error = vcd_parser_execute(state, p, p + plen);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int setTrigger(const int context, char* triggerString) {
|
||||
state->trigger = malloc(strlen(triggerString));
|
||||
strcpy((char*)state->trigger, triggerString);
|
||||
// cout << "setTrigger() got " << triggerString << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t getTime(const int context) {
|
||||
return state->time;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// void execute(
|
||||
// const int context,
|
||||
// externalJsMethodZero* f0,
|
||||
// externalJsMethodOne* f1,
|
||||
// externalJsSetProperty* sfn,
|
||||
// externalJsGetProperty* gfn,
|
||||
// char* chunk
|
||||
// ) {
|
||||
|
||||
// // cout << "execute got " << p << "\n";
|
||||
// cout << "execute " << (int)sfn << " and got " << chunk << "\n";
|
||||
// bound_set_property = sfn;
|
||||
// bound_get_property = gfn;
|
||||
// externalZero = f0;
|
||||
// externalOne = f1;
|
||||
|
||||
// set_property_int("foo", 10);
|
||||
|
||||
|
||||
// int got = get_property_int("bar");
|
||||
|
||||
// cout << "got " << got << " for bar\n";
|
||||
|
||||
// }
|
||||
|
||||
int main(void) {
|
||||
// cout << "main()\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
} // extern C
|
11
wasm_main.hpp
Normal file
11
wasm_main.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
void set_property_int(const char* name, const int value);
|
||||
void set_property_string(const char* name, const char* value);
|
||||
void set_path_string(const char* name, const char* value);
|
||||
void set_path_to_path(const char* name, const char* value);
|
||||
void new_object_path(const char* name);
|
||||
int get_property_int(const char* name);
|
||||
void emit_lifee(const char* name);
|
||||
void emit_triee(const char* name, const int64_t time, const int command, const int valueWords, uint64_t* aValue, uint64_t* aMask);
|
Loading…
x
Reference in New Issue
Block a user