Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preprocessor for ULP/RTC macros #43

Merged
merged 29 commits into from
Aug 9, 2021
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
79db90f
add units test for the .set directive
wnienhaus Jul 22, 2021
84d734d
add support for left aligned assembler directives (e.g. .set)
wnienhaus Jul 22, 2021
ec81ecc
fix a crash bug where BSS size calculation was attempted on the value…
wnienhaus Jul 22, 2021
c184924
raise error when attempting to store values in .bss section
wnienhaus Jul 29, 2021
25d34b0
fix reference to non-existing variable
wnienhaus Jul 22, 2021
76a81ac
fix typo in comment of instruction definition
wnienhaus Jul 22, 2021
56f4530
add support for the .global directive. only symbols flagged as global…
wnienhaus Jul 22, 2021
9907b10
let SymbolTable.export() optionally export non-global symbols too
wnienhaus Jul 22, 2021
27ab850
support ULP opcodes in upper case
wnienhaus Jul 22, 2021
54b117e
add a compatibility test for the recent fixes and improvements
wnienhaus Jul 22, 2021
feb42dc
add support for evaluating expressions
wnienhaus Jul 22, 2021
87507c9
add a compatibility test for evaluating expressions
wnienhaus Jul 23, 2021
99352a3
docs: add that expressions are now supported
wnienhaus Jul 29, 2021
d76fd26
add preprocessor that can replace simple #define values in code
wnienhaus Jul 23, 2021
4dded94
allow assembler to skip comment removal to avoid removing comments twice
wnienhaus Aug 7, 2021
219f939
fix evaluation of expressions during first assembler pass
wnienhaus Jul 25, 2021
5c3eeb8
remove no-longer-needed pass dependent code from SymbolTable
wnienhaus Jul 26, 2021
3e8c0d5
add support for macros such as WRITE_RTC_REG
wnienhaus Jul 26, 2021
ac1de99
add simple include file processing
wnienhaus Jul 26, 2021
8d88fd1
add support for using a btree database (DefinesDB) to store defines f…
wnienhaus Jul 27, 2021
46f1442
add special handling for the BIT macro used in the esp-idf framework
wnienhaus Jul 27, 2021
2f6ee78
add include processor tool for populating a defines.db from include f…
wnienhaus Jul 28, 2021
69ae946
add compatibility tests using good example code off the net
wnienhaus Jul 28, 2021
4f90f76
add documentation for the preprocessor
wnienhaus Jul 29, 2021
d44384f
fix use of treg field in i_move instruction to match binutils-esp32 o…
wnienhaus Jul 28, 2021
254adf9
allow specifying the address for reg_rd and reg_wr in 32-bit words
wnienhaus Jul 28, 2021
c3bd101
support .int data type
wnienhaus Jul 29, 2021
2a0a39a
refactor: small improvements based on PR comments.
wnienhaus Aug 9, 2021
47d5e8a
Updated LICENSE file and added AUTHORS file
wnienhaus Aug 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Status

The most commonly used simple stuff should work.

Expressions in assembly source code are supported and get evaluated during
assembling. Only expressions evaluating to a single integer are supported.
Constants defined with ``.set`` are supported in expressions.

We have some unit tests and also compatibility tests that compare the output
whether it is identical with binutils-esp32ulp output.

Expand Down
4 changes: 3 additions & 1 deletion esp32_ulp/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

from .util import garbage_collect

from .preprocess import preprocess
from .assemble import Assembler
from .link import make_binary
garbage_collect('after import')


def src_to_binary(src):
assembler = Assembler()
assembler.assemble(src)
assembler.assemble(src, remove_comments=False) # comments already removed by preprocessor
garbage_collect('before symbols export')
addrs_syms = assembler.symbols.export()
for addr, sym in addrs_syms:
Expand All @@ -23,6 +24,7 @@ def main(fn):
with open(fn) as f:
src = f.read()

src = preprocess(src)
binary = src_to_binary(src)

if fn.endswith('.s') or fn.endswith('.S'):
Expand Down
44 changes: 28 additions & 16 deletions esp32_ulp/assemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

from . import opcodes
from .nocomment import remove_comments
from .nocomment import remove_comments as do_remove_comments
from .util import garbage_collect

TEXT, DATA, BSS = 'text', 'data', 'bss'
Expand All @@ -12,9 +12,10 @@


class SymbolTable:
def __init__(self, symbols, bases):
def __init__(self, symbols, bases, globals):
self._symbols = symbols
self._bases = bases
self._globals = globals
self._pass = None

def set_pass(self, _pass):
Expand All @@ -32,7 +33,7 @@ def get_from(self):
def set_sym(self, symbol, stype, section, value):
entry = (stype, section, value)
if symbol in self._symbols and entry != self._symbols[symbol]:
raise Exception('redefining symbol %s with different value %r -> %r.' % (label, self._symbols[symbol], entry))
raise Exception('redefining symbol %s with different value %r -> %r.' % (symbol, self._symbols[symbol], entry))
self._symbols[symbol] = entry

def has_sym(self, symbol):
Expand All @@ -52,8 +53,10 @@ def dump(self):
for symbol, entry in self._symbols.items():
print(symbol, entry)

def export(self):
addrs_syms = [(self.resolve_absolute(entry), symbol) for symbol, entry in self._symbols.items()]
def export(self, incl_non_globals=False):
addrs_syms = [(self.resolve_absolute(entry), symbol)
for symbol, entry in self._symbols.items()
if incl_non_globals or symbol in self._globals]
return sorted(addrs_syms)

def to_abs_addr(self, section, offset):
Expand Down Expand Up @@ -93,11 +96,15 @@ def resolve_relative(self, symbol):
from_addr = self.to_abs_addr(self._from_section, self._from_offset)
return sym_addr - from_addr

def set_global(self, symbol):
self._globals[symbol] = True
pass


class Assembler:

def __init__(self, symbols=None, bases=None):
self.symbols = SymbolTable(symbols or {}, bases or {})
def __init__(self, symbols=None, bases=None, globls=None):
self.symbols = SymbolTable(symbols or {}, bases or {}, globls or {})
opcodes.symbols = self.symbols # XXX dirty hack

def init(self, a_pass):
Expand All @@ -118,7 +125,7 @@ def parse_line(self, line):
"""
if not line:
return
has_label = line[0] not in '\t '
has_label = line[0] not in '\t .'
if has_label:
label_line = line.split(None, 1)
if len(label_line) == 2:
Expand Down Expand Up @@ -150,8 +157,10 @@ def append_section(self, value, expected_section=None):
if expected_section is not None and s is not expected_section:
raise TypeError('only allowed in %s section' % expected_section)
if s is BSS:
# just increase BSS size by value
self.offsets[s] += value
if int.from_bytes(value, 'little') != 0:
raise ValueError('attempt to store non-zero value in section .bss')
# just increase BSS size by length of value
self.offsets[s] += len(value)
else:
self.sections[s].append(value)
self.offsets[s] += len(value)
Expand Down Expand Up @@ -231,9 +240,12 @@ def d_align(self, align=4, fill=None):
self.fill(self.section, amount, fill)

def d_set(self, symbol, expr):
value = int(expr) # TODO: support more than just integers
value = int(opcodes.eval_arg(expr)) # TODO: support more than just integers
self.symbols.set_sym(symbol, ABS, None, value)

def d_global(self, symbol):
self.symbols.set_global(symbol)

def append_data(self, wordlen, args):
data = [int(arg).to_bytes(wordlen, 'little') for arg in args]
self.append_section(b''.join(data))
Expand Down Expand Up @@ -263,16 +275,16 @@ def assembler_pass(self, lines):
continue
else:
# machine instruction
func = getattr(opcodes, 'i_' + opcode, None)
func = getattr(opcodes, 'i_' + opcode.lower(), None)
if func is not None:
instruction = func(*args)
instruction = 0 if self.a_pass == 1 else func(*args)
self.append_section(instruction.to_bytes(4, 'little'), TEXT)
continue
raise Exception('Unknown opcode or directive: %s' % opcode)
raise ValueError('Unknown opcode or directive: %s' % opcode)
self.finalize_sections()

def assemble(self, text):
lines = remove_comments(text)
def assemble(self, text, remove_comments=True):
lines = do_remove_comments(text) if remove_comments else text.splitlines()
self.init(1) # pass 1 is only to get the symbol table right
self.assembler_pass(lines)
self.symbols.set_bases(self.compute_bases())
Expand Down
25 changes: 22 additions & 3 deletions esp32_ulp/opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from uctypes import struct, addressof, LITTLE_ENDIAN, UINT32, BFUINT32, BF_POS, BF_LEN

from .soc import *
from .util import split_tokens, validate_expression

# XXX dirty hack: use a global for the symbol table
symbols = None
Expand Down Expand Up @@ -112,7 +113,7 @@ def make_ins(layout):
unused : 8 # Unused
low : 5 # Low bit
high : 5 # High bit
opcode : 4 # Opcode (OPCODE_WR_REG)
opcode : 4 # Opcode (OPCODE_RD_REG)
""")


Expand Down Expand Up @@ -267,6 +268,20 @@ def make_ins(layout):
ARG = namedtuple('ARG', ('type', 'value', 'raw'))


def eval_arg(arg):
parts = []
for token in split_tokens(arg):
if symbols.has_sym(token):
_, _, sym_value = symbols.get_sym(token)
parts.append(str(sym_value))
else:
parts.append(token)
parts = "".join(parts)
if not validate_expression(parts):
raise ValueError('Unsupported expression: %s' % parts)
return eval(parts)


def arg_qualify(arg):
"""
look at arg and qualify its type:
Expand All @@ -289,8 +304,12 @@ def arg_qualify(arg):
return ARG(IMM, int(arg), arg)
except ValueError:
pass
entry = symbols.get_sym(arg)
return ARG(SYM, entry, arg)
try:
entry = symbols.get_sym(arg)
return ARG(SYM, entry, arg)
except KeyError:
pass
return ARG(IMM, int(eval_arg(arg)), arg)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    try:
        entry = symbols.get_sym(arg)
    except KeyError:
        return ARG(IMM, int(eval_arg(arg)), arg)
    else:
        return ARG(SYM, entry, arg)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seen my previous comment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed now.



def get_reg(arg):
Expand Down
57 changes: 57 additions & 0 deletions esp32_ulp/preprocess.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from . import nocomment
from .util import split_tokens


class Preprocessor:
def __init__(self):
self._defines = {}

def parse_defines(self, content):
result = {}
for line in content.splitlines():
line = line.strip()
if not line.startswith("#define"):
# skip lines not containing #define
continue
line = line[8:].strip() # remove #define
parts = line.split(None, 1)
if len(parts) != 2:
# skip defines without value
continue
identifier, value = parts
tmp = identifier.split('(', 1)
if len(tmp) == 2:
# skip parameterised defines (macros)
continue
value = "".join(nocomment.remove_comments(value)).strip()
result[identifier] = value
self._defines = result
return result

def expand_defines(self, line):
found = True
while found: # do as many passed as needed, until nothing was replaced anymore
found = False
tokens = split_tokens(line)
line = ""
for t in tokens:
lu = self._defines.get(t, t)
if lu != t:
found = True
line += lu

return line

def preprocess(self, content):
self.parse_defines(content)
lines = nocomment.remove_comments(content)
result = []
for line in lines:
line = self.expand_defines(line)
result.append(line)
result = "\n".join(result)
return result


def preprocess(content):
return Preprocessor().preprocess(content)
58 changes: 58 additions & 0 deletions esp32_ulp/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,68 @@

import gc

NORMAL, WHITESPACE = 0, 1


def garbage_collect(msg, verbose=DEBUG):
free_before = gc.mem_free()
gc.collect()
free_after = gc.mem_free()
if verbose:
print("%s: %d --gc--> %d bytes free" % (msg, free_before, free_after))


def split_tokens(line):
buf = ""
tokens = []
state = NORMAL
for c in line:
if ('a' <= c <= 'z') or ('A' <= c <= 'Z') or ('0' <= c <= '9') or c == '_':
if state != NORMAL:
if len(buf) > 0:
tokens.append(buf)
buf = ""
state = NORMAL
buf += c
elif c == ' ' or c == '\t':
if state != WHITESPACE:
if len(buf) > 0:
tokens.append(buf)
buf = ""
state = WHITESPACE
buf += c
else:
if len(buf) > 0:
tokens.append(buf)
buf = ""
tokens.append(c)

if len(buf) > 0:
tokens.append(buf)

return tokens


def validate_expression(param):
for token in split_tokens(param):
state = 0
for c in token:
if c not in ' \t+-*/%()<>&|~x0123456789abcdef':
return False

# the following allows hex digits a-f after 0x but not otherwise
if state == 0:
if c in 'abcdef':
return False
if c == '0':
state = 1
continue

if state == 1:
state = 2 if c == 'x' else 0
continue

if state == 2:
if c not in '0123456789abcdef':
state = 0
return True
2 changes: 1 addition & 1 deletion tests/00_unit_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

set -e

for file in opcodes assemble link ; do
for file in opcodes assemble link util preprocess; do
echo testing $file...
micropython $file.py
done
4 changes: 3 additions & 1 deletion tests/01_compat_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ for src_file in $(ls -1 compat/*.S); do
log_file="${src_name}.log"
micropython -m esp32_ulp $src_file 1>$log_file # generates $ulp_file

pre_file="${src_name}.pre"
obj_file="${src_name}.o"
elf_file="${src_name}.elf"
bin_file="${src_name}.bin"

echo -e "\tBuilding using binutils"
esp32ulp-elf-as -o $obj_file $src_file
gcc -E -o ${pre_file} $src_file
esp32ulp-elf-as -o $obj_file ${pre_file}
esp32ulp-elf-ld -T esp32.ulp.ld -o $elf_file $obj_file
esp32ulp-elf-objcopy -O binary $elf_file $bin_file

Expand Down
Loading