Skip to content

Commit

Permalink
first pass Emebedded radae_tx doing sensible things
Browse files Browse the repository at this point in the history
  • Loading branch information
drowe67 committed Sep 20, 2024
1 parent 4f23cdc commit e0ccd67
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 47 deletions.
19 changes: 19 additions & 0 deletions embed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,22 @@ python3 embed_dec.py
<snip>
loss: 0.145
```
# Test 3 - radae_tx as C program

First pass at the a C callable version of `radae_tx`. Have hard coded a few arguments for convenience, and it's a C application (rather than a library). If this was in library form we would be ready for linking with other C applications.

1. Generate some test data, and run `embed/radae_tx.py` with Python top level to test Python code.
```
cd radae/build
cmake ..
ctest -V -R radae_tx_embed
```

2. Build and C top level/embedded Python version:
```
gcc radae_tx.c -o radae_tx $(python3.10-config --cflags) $(python3.10-config --ldflags --embed)
cat ../features_in.f32 | PYTHONPATH="." ./radae_tx radae_tx > tx.f32
diff tx.f32 ../rx.f32
```
`diff` shows Python ctest and C/Embedded version have same output. Haven't made this a ctest yet as not sure how to do `gcc` step in cmake such that's it's reasonably portable.

129 changes: 88 additions & 41 deletions embed/radae_tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,47 @@
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include "numpy/arrayobject.h"

#define NARGS 2
/* help function to call a Python "getter" function with no arguments that returns a long */
long call_getter(PyObject *pModule, char func_name[]) {
PyObject *pFunc, *pValue;
pFunc = PyObject_GetAttrString(pModule, func_name);
long ret;

if (pFunc && PyCallable_Check(pFunc)) {
pValue = PyObject_CallObject(pFunc, NULL);
if (pValue != NULL) {
ret = PyLong_AsLong(pValue);
Py_DECREF(pValue);
}
else {
Py_DECREF(pFunc);
PyErr_Print();
fprintf(stderr,"Call to %s failed\n", func_name);
// TODO when porting to library modify function to return error code, caller shuts down gracefully
exit(1);
}
}
else {
if (PyErr_Occurred())
PyErr_Print();
fprintf(stderr, "Cannot find function \"%s\"\n", func_name);
// TODO: fix when ported to library
exit(1);
}
Py_XDECREF(pFunc);

return ret;
}

int main(void)
{
PyObject *pName, *pModule, *pFunc;
PyObject *pValue;
//PyObject *pArgs;
PyObject *pArgs;
char *python_name = "radae_tx";
char *func_name = "get_nb_floats";
char *do_radae_tx_func_name = "do_radae_tx";
char *do_eoo_func_name = "do_eoo";
long nb_floats, Nmf, Neoo;

Py_Initialize();

Expand All @@ -27,63 +59,78 @@ int main(void)
Py_DECREF(pName);

if (pModule != NULL) {
pFunc = PyObject_GetAttrString(pModule, func_name);
/* pFunc is a new reference */

nb_floats = call_getter(pModule, "get_nb_floats");
Nmf = call_getter(pModule, "get_Nmf");
Neoo = call_getter(pModule, "get_Neoo");
fprintf(stderr, "nb_floats: %ld Nmf: %ld Neoo: %ld\n", nb_floats, Nmf, Neoo);

pFunc = PyObject_GetAttrString(pModule, do_radae_tx_func_name);
if (pFunc && PyCallable_Check(pFunc)) {
#ifdef T
pArgs = PyTuple_New(NARGS);

// first two args from command line
pValue = PyLong_FromLong(atol(argv[3]));
PyTuple_SetItem(pArgs, 0, pValue);
pValue = PyLong_FromLong(atol(argv[4]));
PyTuple_SetItem(pArgs, 1, pValue);
pArgs = PyTuple_New(2);

// 3rd Python function arg - set up numpy array
long dims = 3;
float arr_in[] = {1.0,2.0,3.0};
pValue = PyArray_SimpleNewFromData(1, &dims, NPY_FLOAT, arr_in);
// 1st Python function arg - numpy array of float features
float buffer_f32[nb_floats];
pValue = PyArray_SimpleNewFromData(1, &nb_floats, NPY_FLOAT, buffer_f32);
if (pValue == NULL) {
PyErr_Print();
fprintf(stderr,"Error setting up numpy array\n");
fprintf(stderr,"Error setting up numpy array for buffer_f32\n");
}
PyTuple_SetItem(pArgs, 2, pValue);
PyTuple_SetItem(pArgs, 0, pValue);

// 4th Python arg is a numpy array used for output to C
float arr_out[] = {0.0,0.0,0.0};
pValue = PyArray_SimpleNewFromData(1, &dims, NPY_FLOAT, arr_out);
// 2nd Python arg is a numpy array used for output to C
float tx_out[2*Nmf];
pValue = PyArray_SimpleNewFromData(1, &Nmf, NPY_CFLOAT, tx_out);
if (pValue == NULL) {
PyErr_Print();
fprintf(stderr,"Error setting up numpy array\n");
fprintf(stderr,"Error setting up numpy array for tx_out\n");
}
PyTuple_SetItem(pArgs, 3, pValue);
#endif

// do the function call
pValue = PyObject_CallObject(pFunc, NULL);
//Py_DECREF(pArgs);
if (pValue != NULL) {
printf("Result of call: %ld\n", PyLong_AsLong(pValue));
Py_DECREF(pValue);

// not sure how to return arrays but can modify input arrays in place as a hack
//printf("returned array: %f %f %f\n", arr_out[0], arr_out[1], arr_out[2]);
PyTuple_SetItem(pArgs, 1, pValue);

// We are assuming once args are set up we can make repeat call with the same args, even though
// data in arrays changes
while((unsigned)nb_floats == fread(buffer_f32, sizeof(float), nb_floats, stdin)) {
// do the function call
PyObject_CallObject(pFunc, pArgs);
fwrite(tx_out, 2*sizeof(float), Nmf, stdout);
fflush(stdout);
}
else {
Py_DECREF(pFunc);
Py_DECREF(pModule);

Py_DECREF(pArgs);
}
else {
if (PyErr_Occurred())
PyErr_Print();
fprintf(stderr, "Cannot find function \"%s\"\n", do_radae_tx_func_name);
}
Py_XDECREF(pFunc);

// End of Over
pFunc = PyObject_GetAttrString(pModule, do_eoo_func_name);
if (pFunc && PyCallable_Check(pFunc)) {

pArgs = PyTuple_New(1);

// Python arg is a numpy array used for output to C
float eoo_out[2*Neoo];
pValue = PyArray_SimpleNewFromData(1, &Neoo, NPY_CFLOAT, eoo_out);
if (pValue == NULL) {
PyErr_Print();
fprintf(stderr,"Call failed\n");
return 1;
fprintf(stderr,"Error setting up numpy array for eoo_out\n");
}
PyTuple_SetItem(pArgs, 0, pValue);
PyObject_CallObject(pFunc, pArgs);
fwrite(eoo_out, 2*sizeof(float), Neoo, stdout);
fflush(stdout);
Py_DECREF(pArgs);
}
else {
if (PyErr_Occurred())
PyErr_Print();
fprintf(stderr, "Cannot find function \"%s\"\n", func_name);
fprintf(stderr, "Cannot find function \"%s\"\n", do_eoo_func_name);
}
Py_XDECREF(pFunc);

Py_DECREF(pModule);
}
else {
Expand Down
20 changes: 14 additions & 6 deletions embed/radae_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,15 @@
nb_floats = model.Nzmf*model.enc_stride*nb_total_features
# number of output csingles per processing frame
Nmf = int((model.Ns+1)*(model.M+model.Ncp))
# number of output csingles for EOO frame
Neoo = int((model.Ns+2)*(model.M+model.Ncp))

def get_nb_floats():
return nb_floats
def get_Nmf():
return Nmf
def get_Neoo():
return Neoo

def do_radae_tx(buffer_f32,tx_out):

Expand All @@ -96,6 +100,12 @@ def do_radae_tx(buffer_f32,tx_out):
# not very Pythonic but works (TODO work out how to return numpy vecs to C)
np.copyto(tx_out,tx)

# send end of over frame
def do_eoo(tx_out):
eoo = model.eoo
eoo = eoo.cpu().detach().numpy().flatten().astype('csingle')
np.copyto(tx_out,eoo)

if __name__ == '__main__':
tx_out = np.zeros(Nmf,dtype=np.csingle)
while True:
Expand All @@ -104,10 +114,8 @@ def do_radae_tx(buffer_f32,tx_out):
break
buffer_f32 = np.frombuffer(buffer,np.single)
do_radae_tx(buffer_f32,tx_out)
if use_stdout:
sys.stdout.buffer.write(tx_out)
sys.stdout.buffer.write(tx_out)

if use_stdout:
eoo = model.eoo
eoo = eoo.cpu().detach().numpy().flatten().astype('csingle')
sys.stdout.buffer.write(eoo)
eoo_out = np.zeros(Neoo,dtype=np.csingle)
do_eoo(eoo_out)
sys.stdout.buffer.write(eoo_out)

0 comments on commit e0ccd67

Please sign in to comment.