aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/rfnoc/nocscript/gen_basic_funcs.py
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2016-08-01 18:17:41 -0700
committerMartin Braun <martin.braun@ettus.com>2016-08-09 12:42:52 -0700
commit3bf4b000f7d9a7f4af82c21753556ede7e8df6e3 (patch)
tree2228d7eb58c4d83d91192cb9b6a908e4e49f6317 /host/lib/rfnoc/nocscript/gen_basic_funcs.py
parentc5b076173e2d866f3ee99c113a37183c5ec20f0b (diff)
downloaduhd-3bf4b000f7d9a7f4af82c21753556ede7e8df6e3.tar.gz
uhd-3bf4b000f7d9a7f4af82c21753556ede7e8df6e3.tar.bz2
uhd-3bf4b000f7d9a7f4af82c21753556ede7e8df6e3.zip
Merging RFNoC support for X310
Diffstat (limited to 'host/lib/rfnoc/nocscript/gen_basic_funcs.py')
-rwxr-xr-xhost/lib/rfnoc/nocscript/gen_basic_funcs.py474
1 files changed, 474 insertions, 0 deletions
diff --git a/host/lib/rfnoc/nocscript/gen_basic_funcs.py b/host/lib/rfnoc/nocscript/gen_basic_funcs.py
new file mode 100755
index 000000000..702b3e884
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/gen_basic_funcs.py
@@ -0,0 +1,474 @@
+#!/usr/bin/env python
+"""
+Generate the function list for the basic NocScript functions
+"""
+
+import re
+import os
+import sys
+from mako.template import Template
+
+#############################################################################
+# This is the interesting part: Add new functions in here
+#
+# Notes:
+# - Lines starting with # are considered comments, and will be removed from
+# the output
+# - C++ comments will be copied onto the generated file if inside functions
+# - Docstrings start with //! and are required
+# - Function signature is RETURN_TYPE NAME(ARG_TYPE1, ARG_TYPE2, ...)
+# - Function body is valid C++
+# - If your function requires special includes, put them in INCLUDE_LIST
+# - End of functions is delimited by s/^}/, so take care with the indents!
+# - Use these substitutions:
+# - ${RETURN}(...): Create a valid return value
+# - ${args[n]}: Access the n-th argument
+#
+INCLUDE_LIST = """
+#include <boost/math/special_functions/round.hpp>
+#include <boost/thread/thread.hpp>
+"""
+FUNCTION_LIST = """
+CATEGORY: Math Functions
+//! Returns x + y
+INT ADD(INT, INT)
+{
+ ${RETURN}(${args[0]} + ${args[1]});
+}
+
+//! Returns x + y
+DOUBLE ADD(DOUBLE, DOUBLE)
+{
+ ${RETURN}(${args[0]} + ${args[1]});
+}
+
+//! Returns x * y
+DOUBLE MULT(DOUBLE, DOUBLE)
+{
+ ${RETURN}(${args[0]} * ${args[1]});
+}
+
+//! Returns x * y
+INT MULT(INT, INT)
+{
+ ${RETURN}(${args[0]} * ${args[1]});
+}
+
+//! Returns x / y
+DOUBLE DIV(DOUBLE, DOUBLE)
+{
+ ${RETURN}(${args[0]} / ${args[1]});
+}
+
+//! Returns true if x <= y (Less or Equal)
+BOOL LE(INT, INT)
+{
+ ${RETURN}(bool(${args[0]} <= ${args[1]}));
+}
+
+//! Returns true if x <= y (Less or Equal)
+BOOL LE(DOUBLE, DOUBLE)
+{
+ ${RETURN}(bool(${args[0]} <= ${args[1]}));
+}
+
+//! Returns true if x >= y (Greater or Equal)
+BOOL GE(INT, INT)
+{
+ ${RETURN}(bool(${args[0]} >= ${args[1]}));
+}
+
+//! Returns true if x >= y (Greater or Equal)
+BOOL GE(DOUBLE, DOUBLE)
+{
+ ${RETURN}(bool(${args[0]} >= ${args[1]}));
+}
+
+//! Returns true if x < y (Less Than)
+BOOL LT(INT, INT)
+{
+ ${RETURN}(bool(${args[0]} < ${args[1]}));
+}
+
+//! Returns true if x > y (Greater Than)
+BOOL GT(INT, INT)
+{
+ ${RETURN}(bool(${args[0]} > ${args[1]}));
+}
+
+//! Returns true if x < y (Less Than)
+BOOL LT(DOUBLE, DOUBLE)
+{
+ ${RETURN}(bool(${args[0]} < ${args[1]}));
+}
+
+//! Returns true if x > y (Greater Than)
+BOOL GT(DOUBLE, DOUBLE)
+{
+ ${RETURN}(bool(${args[0]} > ${args[1]}));
+}
+
+//! Round x and return it as an integer
+INT IROUND(DOUBLE)
+{
+ ${RETURN}(int(boost::math::iround(${args[0]})));
+}
+
+//! Returns true if x is a power of 2
+BOOL IS_PWR_OF_2(INT)
+{
+ if (${args[0]} < 0) return ${FALSE};
+ int i = ${args[0]};
+ while ( (i & 1) == 0 and (i > 1) ) {
+ i >>= 1;
+ }
+ ${RETURN}(bool(i == 1));
+}
+
+//! Returns floor(log2(x)).
+INT LOG2(INT)
+{
+ if (${args[0]} < 0) {
+ throw uhd::runtime_error(str(
+ boost::format("In NocScript function ${func_name}: Cannot calculate log2() of negative number.")
+ ));
+ }
+
+ int power_value = ${args[0]};
+ int log2_value = 0;
+ while ( (power_value & 1) == 0 and (power_value > 1) ) {
+ power_value >>= 1;
+ log2_value++;
+ }
+ ${RETURN}(log2_value);
+}
+
+//! Returns x % y
+INT MODULO(INT, INT)
+{
+ ${RETURN}(${args[0]} % ${args[1]});
+}
+
+//! Returns true if x == y
+BOOL EQUAL(INT, INT)
+{
+ ${RETURN}(bool(${args[0]} == ${args[1]}));
+}
+
+//! Returns true if x == y
+BOOL EQUAL(DOUBLE, DOUBLE)
+{
+ ${RETURN}(bool(${args[0]} == ${args[1]}));
+}
+
+//! Returns true if x == y
+BOOL EQUAL(STRING, STRING)
+{
+ ${RETURN}(bool(${args[0]} == ${args[1]}));
+}
+
+CATEGORY: Bitwise Operations
+//! Returns x >> y
+INT SHIFT_RIGHT(INT, INT)
+{
+ ${RETURN}(${args[0]} >> ${args[1]});
+}
+
+//! Returns x << y
+INT SHIFT_LEFT(INT, INT)
+{
+ ${RETURN}(${args[0]} << ${args[1]});
+}
+
+//! Returns x & y
+INT BITWISE_AND(INT, INT)
+{
+ ${RETURN}(${args[0]} & ${args[1]});
+}
+
+//! Returns x | y
+INT BITWISE_OR(INT, INT)
+{
+ ${RETURN}(${args[0]} | ${args[1]});
+}
+
+//! Returns x ^ y
+INT BITWISE_XOR(INT, INT)
+{
+ ${RETURN}(${args[0]} ^ ${args[1]});
+}
+
+CATEGORY: Boolean Logic
+//! Returns x xor y.
+BOOL XOR(BOOL, BOOL)
+{
+ ${RETURN}(${args[0]} xor ${args[1]});
+}
+
+//! Returns !x
+BOOL NOT(BOOL)
+{
+ ${RETURN}(not ${args[0]});
+}
+
+//! Always returns true
+BOOL TRUE()
+{
+ return ${TRUE};
+}
+
+//! Always returns false
+BOOL FALSE()
+{
+ return ${FALSE};
+}
+
+CATEGORY: Conditional Execution
+//! Executes x, if true, execute y. Returns true if x is true.
+BOOL IF(BOOL, BOOL)
+{
+ if (${args[0]}) {
+ ${args[1]};
+ ${RETURN}(true);
+ }
+ ${RETURN}(false);
+}
+
+//! Executes x, if true, execute y, otherwise, execute z. Returns true if x is true.
+BOOL IF_ELSE(BOOL, BOOL, BOOL)
+{
+ if (${args[0]}) {
+ ${args[1]};
+ ${RETURN}(true);
+ } else {
+ ${args[2]};
+ }
+ ${RETURN}(false);
+}
+
+CATEGORY: Execution Control
+//! Sleep for x seconds. Fractions are allowed. Millisecond accuracy.
+BOOL SLEEP(DOUBLE)
+{
+ int ms = ${args[0]} / 1000;
+ boost::this_thread::sleep(boost::posix_time::milliseconds(ms));
+ ${RETURN}(true);
+}
+"""
+# End of interesting part. The rest will take this and turn into a C++
+# header file.
+#############################################################################
+
+HEADER = """<% import time %>//
+///////////////////////////////////////////////////////////////////////
+// This file was generated by ${file} on ${time.strftime("%c")}
+///////////////////////////////////////////////////////////////////////
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+/****************************************************************************
+ * This file is autogenerated! Any manual changes in here will be
+ * overwritten by calling nocscript_gen_basic_funcs.py!
+ ***************************************************************************/
+
+#include "expression.hpp"
+#include "function_table.hpp"
+#include <uhd/exception.hpp>
+#include <boost/format.hpp>
+#include <boost/assign/list_of.hpp>
+${INCLUDE_LIST}
+
+#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP
+#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP
+
+namespace uhd { namespace rfnoc { namespace nocscript {
+"""
+
+# Not a Mako template:
+FOOTER="""
+}}} /* namespace uhd::rfnoc::nocscript */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP */
+"""
+
+# Not a Mako template:
+FUNC_TEMPLATE = """
+expression_literal {NAME}(expression_container::expr_list_type &{ARGS})
+{BODY}
+"""
+
+REGISTER_MACRO_TEMPLATE = """#define _REGISTER_ALL_FUNCS()${registry}
+"""
+
+REGISTER_COMMANDS_TEMPLATE = """
+ % if len(arglist):
+ expression_function::argtype_list_type ${func_name}_args = boost::assign::list_of
+ % for this_type in arglist:
+ (expression::TYPE_${this_type})
+ % endfor
+ ;
+ % else:
+ expression_function::argtype_list_type ${func_name}_args;
+ % endif
+ register_function(
+ "${name}",
+ boost::bind(&${func_name}, _1),
+ expression::TYPE_${retval},
+ ${func_name}_args
+ );"""
+
+DOXY_TEMPLATE = """/*! \page page_nocscript_funcs NocScript Function Reference
+% for cat, func_by_name in func_list_tree.iteritems():
+- ${cat}
+% for func_name, func_info_list in func_by_name.iteritems():
+ - ${func_name}: ${func_info_list[0]['docstring']}
+% for func_info in func_info_list:
+ - ${func_info['arglist']} -> ${func_info['retval']}
+% endfor
+% endfor
+% endfor
+
+*/
+"""
+
+def parse_tmpl(_tmpl_text, **kwargs):
+ return Template(_tmpl_text).render(**kwargs)
+
+def make_cxx_func_name(func_dict):
+ """
+ Creates a unique C++ function name from a function description
+ """
+ return "{name}__{retval}__{arglist}".format(
+ name=func_dict['name'],
+ retval=func_dict['retval'],
+ arglist="_".join(func_dict['arglist'])
+ )
+
+def make_cxx_func_body(func_dict):
+ """
+ Formats the function body properly
+ """
+ type_lookup_methods = {
+ 'INT': 'get_int',
+ 'DOUBLE': 'get_double',
+ 'BOOL': 'get_bool',
+ 'STRING': 'get_string',
+ }
+ args_lookup = []
+ for idx, arg_type in enumerate(func_dict['arglist']):
+ args_lookup.append("args[{idx}]->eval().{getter}()".format(idx=idx, getter=type_lookup_methods[arg_type]))
+ return parse_tmpl(
+ func_dict['body'],
+ args=args_lookup,
+ FALSE='expression_literal(false)',
+ TRUE='expression_literal(true)',
+ RETURN='return expression_literal',
+ **func_dict
+ )
+
+def prep_function_list():
+ """
+ - Remove all comments
+ - Split the function list into individual functions
+ - Split the functions into return value, name, argument list and body
+ """
+ comment_remove_re = re.compile(r'^\s*#.*$', flags=re.MULTILINE)
+ func_list_wo_comments = comment_remove_re.sub('', FUNCTION_LIST)
+ func_splitter_re = re.compile(r'(?<=^})\s*$', flags=re.MULTILINE)
+ func_list_split = func_splitter_re.split(func_list_wo_comments)
+ func_list_split = [x.strip() for x in func_list_split if len(x.strip())]
+ func_list = []
+ last_category = ''
+ for func in func_list_split:
+ split_regex = r'(^CATEGORY: (?P<cat>[^\n]*)\s*)?' \
+ r'//!(?P<docstring>[^\n]*)\s*' + \
+ r'(?P<retval>[A-Z][A-Z0-9_]*)\s+' + \
+ r'(?P<funcname>[A-Z][A-Z0-9_]*)\s*\((?P<arglist>[^\)]*)\)\s*' + \
+ r'(?P<funcbody>^{.*)'
+ split_re = re.compile(split_regex, flags=re.MULTILINE|re.DOTALL)
+ mo = split_re.match(func)
+ if mo.group('cat'):
+ last_category = mo.group('cat').strip()
+ func_dict = {
+ 'docstring': mo.group('docstring').strip(),
+ 'name': mo.group('funcname'),
+ 'retval': mo.group('retval'),
+ 'arglist': [x.strip() for x in mo.group('arglist').split(',') if len(x.strip())],
+ 'body': mo.group('funcbody'),
+ 'category': last_category,
+ }
+ func_dict['func_name'] = make_cxx_func_name(func_dict)
+ func_list.append(func_dict)
+ return func_list
+
+def write_function_header(output_filename):
+ """
+ Create the .hpp file that defines all the NocScript functions in C++.
+ """
+ func_list = prep_function_list()
+ # Step 1: Write the prototypes
+ func_prototypes = ''
+ registry_commands = ''
+ for func in func_list:
+ func_prototypes += FUNC_TEMPLATE.format(
+ NAME=func['func_name'],
+ BODY=make_cxx_func_body(func),
+ ARGS="args" if len(func['arglist']) else ""
+ )
+ registry_commands += parse_tmpl(
+ REGISTER_COMMANDS_TEMPLATE,
+ **func
+ )
+ # Step 2: Write the registry process
+ register_func = parse_tmpl(REGISTER_MACRO_TEMPLATE, registry=registry_commands)
+ register_func = register_func.replace('\n', ' \\\n')
+
+ # Final step: Join parts and write to file
+ full_file = "\n".join((
+ parse_tmpl(HEADER, file = os.path.basename(__file__), INCLUDE_LIST=INCLUDE_LIST),
+ func_prototypes,
+ register_func,
+ FOOTER,
+ ))
+ open(output_filename, 'w').write(full_file)
+
+def write_manual_file(output_filename):
+ """
+ Write the Doxygen file for the NocScript functions.
+ """
+ func_list = prep_function_list()
+ func_list_tree = {}
+ for func in func_list:
+ if not func_list_tree.has_key(func['category']):
+ func_list_tree[func['category']] = {}
+ if not func_list_tree[func['category']].has_key(func['name']):
+ func_list_tree[func['category']][func['name']] = []
+ func_list_tree[func['category']][func['name']].append(func)
+ open(output_filename, 'w').write(parse_tmpl(DOXY_TEMPLATE, func_list_tree=func_list_tree))
+
+
+def main():
+ if len(sys.argv) < 2:
+ print("No output file specified!")
+ exit(1)
+ outfile = sys.argv[1]
+ if os.path.splitext(outfile)[1] == '.dox':
+ write_manual_file(outfile)
+ else:
+ write_function_header(outfile)
+
+if __name__ == "__main__":
+ main()