diff options
author | Martin Braun <martin.braun@ettus.com> | 2016-08-01 18:17:41 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2016-08-09 12:42:52 -0700 |
commit | 3bf4b000f7d9a7f4af82c21753556ede7e8df6e3 (patch) | |
tree | 2228d7eb58c4d83d91192cb9b6a908e4e49f6317 /host/lib/rfnoc/nocscript/gen_basic_funcs.py | |
parent | c5b076173e2d866f3ee99c113a37183c5ec20f0b (diff) | |
download | uhd-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-x | host/lib/rfnoc/nocscript/gen_basic_funcs.py | 474 |
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() |