And a few other nice things. The bootloader now has an embedded wozmon! If you know its offset, you can jump to it from the app.
310 lines
7.4 KiB
Python
Executable File
310 lines
7.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import collections
|
|
import glob
|
|
import itertools
|
|
import os
|
|
import re
|
|
|
|
|
|
# global config
|
|
toolchain_path = f"/opt/xpack-riscv-none-elf-gcc-14.2.0-3"
|
|
toolchain_prefix = "riscv-none-elf-"
|
|
builddir = "build"
|
|
outdir = "out"
|
|
linker_script = "apps/app.ld"
|
|
hostcflags = "-g -std=c++20 -fprofile-instr-generate -fcoverage-mapping"
|
|
hostlibs = "-lgtest -lgmock -lgtest_main"
|
|
hostldflags = "-fprofile-instr-generate -fcoverage-mapping"
|
|
include_dirs = [
|
|
"hal",
|
|
"hal/uart",
|
|
"hal/lib/common",
|
|
]
|
|
|
|
include_flags = [f"-I{os.path.relpath(i, builddir)}" for i in include_dirs]
|
|
project_flags = [
|
|
]
|
|
cpp_flags = ["-DNDEBUG"] + include_flags + project_flags
|
|
common_flags = [
|
|
"-g",
|
|
"-Wall",
|
|
"-Wextra",
|
|
"-flto",
|
|
"-march=rv32i_zicsr",
|
|
"-ffunction-sections",
|
|
"-Oz",
|
|
]
|
|
|
|
cc_flags = common_flags
|
|
cxx_flags = common_flags + ["-std=c++20", "-fno-rtti", "-fno-exceptions", "-Wno-missing-field-initializers"]
|
|
ldflags = [
|
|
"-Oz",
|
|
"-g",
|
|
"-Wl,--gc-sections",
|
|
"-Wl,--print-memory-usage",
|
|
"-flto",
|
|
"-march=rv32i_zicsr",
|
|
]
|
|
|
|
|
|
def get_cxx_flags():
|
|
return cpp_flags + cxx_flags
|
|
|
|
|
|
def get_cc_flags():
|
|
return cpp_flags + cc_flags
|
|
|
|
|
|
def get_ldflags():
|
|
return ldflags
|
|
|
|
|
|
def add_cpp_flag(flag):
|
|
cpp_flags.append(flag)
|
|
|
|
|
|
def gen_rules():
|
|
tools = {"cxx": "g++", "cc": "gcc", "as": "as", "objcopy": "objcopy"}
|
|
|
|
tc_path = toolchain_path
|
|
tc_prefix = toolchain_prefix
|
|
|
|
rules = f"""
|
|
rule cxx
|
|
command = $cxx -MMD -MT $out -MF $out.d {' '.join(get_cxx_flags())} -c $in -o $out
|
|
description = CXX $out
|
|
depfile = $out.d
|
|
deps = gcc
|
|
|
|
rule cc
|
|
command = $cc -MMD -MT $out -MF $out.d {' '.join(get_cc_flags())} -c $in -o $out
|
|
description = CC $out
|
|
depfile = $out.d
|
|
deps = gcc
|
|
|
|
rule as
|
|
command = $as $in -o $out
|
|
description = AS $out
|
|
|
|
rule link
|
|
command = $cxx {' '.join(ldflags)} -Wl,-T$linker_script -o $out $in $libs
|
|
description = LINK $out
|
|
|
|
rule objcopy
|
|
command = $objcopy -O binary $in $out
|
|
description = OBJCOPY $out
|
|
|
|
rule hostcxx
|
|
command = clang++ -MMD -MT $out -MF $out.d {hostcflags} -c $in -o $out
|
|
description = HOSTCXX $out
|
|
depfile = $out.d
|
|
deps = gcc
|
|
|
|
rule hostlink
|
|
command = clang++ {hostldflags} -o $out $in {hostlibs}
|
|
description = HOSTLINK $out
|
|
|
|
rule profdata
|
|
command = llvm-profdata merge -sparse $profraw -o $out
|
|
description = PROFDATA
|
|
|
|
rule cov
|
|
command = llvm-cov show --output-dir cov -format html --instr-profile $profdata $objects && touch $out
|
|
description = COV
|
|
|
|
rule hosttest
|
|
command = LLVM_PROFILE_FILE=$in.profraw ./$in && touch $out
|
|
"""
|
|
|
|
for var, tool in tools.items():
|
|
toolpath = os.path.join(tc_path, "bin", f"{tc_prefix}{tool}")
|
|
yield f"{var} = {toolpath}"
|
|
|
|
for line in rules.splitlines():
|
|
yield line
|
|
|
|
|
|
def get_suffix_rule(filename, cxx_rule="cxx", cc_rule="cc"):
|
|
suffix = filename.split(".")[-1]
|
|
return collections.defaultdict(
|
|
lambda: None,
|
|
{
|
|
"c": cc_rule,
|
|
"cc": cxx_rule,
|
|
"cpp": cxx_rule,
|
|
"s": "as",
|
|
},
|
|
)[suffix]
|
|
|
|
|
|
def make_cxx_rule(name, cflags=()):
|
|
cflags = " ".join(cflags + get_cxx_flags())
|
|
rule = f"""
|
|
rule {name}
|
|
command = $cxx -MMD -MT $out -MF $out.d {cflags} -c $in -o $out
|
|
description = CXX $out
|
|
depfile = $out.d
|
|
deps = gcc
|
|
"""
|
|
return rule.splitlines()
|
|
|
|
|
|
def make_cc_rule(name, cflags=()):
|
|
cflags = " ".join(cflags + get_cc_flags())
|
|
rule = f"""
|
|
rule {name}
|
|
command = $cc -MMD -MT $out -MF $out.d {cflags} -c $in -o $out
|
|
description = CC $out
|
|
depfile = $out.d
|
|
deps = gcc
|
|
"""
|
|
return rule.splitlines()
|
|
|
|
|
|
def source_set(name, sources, cflags=()):
|
|
builds = [
|
|
(os.path.relpath(s, builddir), re.sub(r"\.\w+", ".o", s))
|
|
for s in sources
|
|
if get_suffix_rule(s) is not None
|
|
]
|
|
|
|
lines = []
|
|
|
|
cxx_rule = "cxx"
|
|
cc_rule = "cc"
|
|
if cflags:
|
|
cxx_rule = f"cxx_{name}"
|
|
lines += make_cxx_rule(cxx_rule, cflags=cflags)
|
|
cc_rule = f"cc_{name}"
|
|
lines += make_cc_rule(cc_rule, cflags=cflags)
|
|
|
|
for i, o in builds:
|
|
rule = get_suffix_rule(i, cxx_rule=cxx_rule, cc_rule=cc_rule)
|
|
if rule is None:
|
|
continue
|
|
lines.append(f"build {o}: {rule} {i}")
|
|
|
|
return [b[1] for b in builds], lines
|
|
|
|
|
|
def build_source_set(source_set):
|
|
for line in source_set[1]:
|
|
yield line
|
|
|
|
|
|
def build_image(source_set, elf_out, dependencies=(), bin_out=None, linker_script=linker_script):
|
|
# to make it builddir-relative
|
|
linker_script = os.path.relpath(linker_script, builddir)
|
|
|
|
elf_out = os.path.relpath(elf_out, builddir)
|
|
|
|
objects, lines = source_set
|
|
for line in lines:
|
|
yield line
|
|
for objs, _ in dependencies:
|
|
objects += objs
|
|
objects = " ".join(objects)
|
|
|
|
yield f"build {elf_out}: link {objects} | {linker_script}"
|
|
yield f" linker_script = {linker_script}"
|
|
if bin_out is not None:
|
|
bin_out = os.path.relpath(bin_out, builddir)
|
|
yield f"build {bin_out}: objcopy {elf_out}"
|
|
|
|
|
|
def build_test(name, sources):
|
|
builds = [
|
|
(os.path.relpath(s, builddir), f"{name}_" + re.sub(r"\.\w+", ".o", s))
|
|
for s in sources
|
|
]
|
|
|
|
out = name
|
|
|
|
for i, o in builds:
|
|
rule = "hostcxx"
|
|
yield f"build {o}: {rule} {i}"
|
|
|
|
objects = " ".join(b[1] for b in builds)
|
|
|
|
yield f"build {out}: hostlink {objects}"
|
|
yield f"build {out}.run: hosttest {out}"
|
|
|
|
|
|
def make_coverage(binaries):
|
|
bins = " ".join(binaries)
|
|
profraw = " ".join(f"{x}.profraw" for x in binaries)
|
|
objects = " ".join(f"--object {x}" for x in binaries)
|
|
testruns = " ".join(f"{x}.run" for x in binaries)
|
|
yield f"build profdata: profdata | {testruns}"
|
|
yield f" profraw = {profraw}"
|
|
yield f"build cov/index.html: cov {bins} | profdata"
|
|
yield f" profdata = profdata"
|
|
yield f" objects = {objects}"
|
|
|
|
|
|
hal = source_set("hal", [
|
|
"hal/intc.cc",
|
|
"hal/interrupts.cc",
|
|
"hal/start.cc",
|
|
"hal/lib/common/xil_assert.c",
|
|
"hal/uart/xuartlite.c",
|
|
"hal/uart/xuartlite_stats.c",
|
|
"hal/uart/xuartlite_intr.c",
|
|
])
|
|
|
|
bootloader = source_set("bootloader", glob.glob("./bootloader/**/*.cc", recursive=True))
|
|
bootloader_image = build_image(
|
|
bootloader,
|
|
dependencies=[hal],
|
|
elf_out="out/bootloader.elf",
|
|
linker_script="bootloader/bootloader.ld",
|
|
)
|
|
|
|
def app_image(app):
|
|
return build_image(
|
|
source_set(app, glob.glob(f"./apps/{app}/**/*.cc", recursive=True)),
|
|
dependencies=[hal],
|
|
elf_out=f"out/{app}.elf",
|
|
bin_out=f"out/{app}.bin",
|
|
)
|
|
|
|
all = [
|
|
build_source_set(hal),
|
|
bootloader_image,
|
|
|
|
app_image("helloworld"),
|
|
app_image("wozmon"),
|
|
app_image("timer"),
|
|
]
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(description='Generate ninja build files.')
|
|
parser.add_argument('--version', required=True,
|
|
help='version tag (typically from `git describe`)')
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
add_cpp_flag(f'-DGIT_VERSION_TAG=\\"{args.version}\\"')
|
|
header = gen_rules()
|
|
lines = itertools.chain(header, *all)
|
|
|
|
if not os.path.exists(builddir):
|
|
os.mkdir(builddir)
|
|
|
|
with open(os.path.join(builddir, "build.ninja"), "w") as f:
|
|
f.write("\n".join(lines))
|
|
f.write("\n")
|
|
|
|
print(
|
|
f'Configure done. Build with "ninja -C {builddir}". Output will be in {outdir}/'
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|