1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4import logging
5
6from datetime import datetime
7from enum import Enum
8from glob import glob, iglob
9from pathlib import Path
10
11from lxml.etree import XMLSyntaxError
12from zipfile import ZipFile
13
14from matrix_runner import main, matrix_axis, matrix_action, matrix_command, matrix_filter, \
15    ConsoleReport, CropReport, TransformReport, JUnitReport
16
17
18@matrix_axis("device", "d", "Device(s) to be considered.")
19class DeviceAxis(Enum):
20    CM0 = ('Cortex-M0', 'CM0')
21    CM0plus = ('Cortex-M0plus', 'CM0plus')
22    CM3 = ('Cortex-M3', 'CM3')
23    CM4 = ('Cortex-M4', 'CM4')
24    CM7 = ('Cortex-M7', 'CM7')
25    CM23 = ('Cortex-M23', 'CM23')
26    CM23S = ('Cortex-M23S', 'CM23S')
27    CM23NS = ('Cortex-M23NS', 'CM23NS')
28    CM33 = ('Cortex-M33', 'CM33')
29    CM33S = ('Cortex-M33S', 'CM33S')
30    CM33NS = ('Cortex-M33NS', 'CM33NS')
31    CM35P = ('Cortex-M35P', 'CM35P')
32    CM35PS = ('Cortex-M35PS', 'CM35PS')
33    CM35PNS = ('Cortex-M35PNS', 'CM35PNS')
34    CM55 = ('Cortex-M55', 'CM55')
35    CM55S = ('Cortex-M55S', 'CM55S')
36    CM55NS = ('Cortex-M55NS', 'CM55NS')
37    CM85 = ('Cortex-M85', 'CM85')
38    CM85S = ('Cortex-M85S', 'CM85S')
39    CM85NS = ('Cortex-M85NS', 'CM85NS')
40    CA5 = ('Cortex-A5', 'CA5')
41    CA7 = ('Cortex-A7', 'CA7')
42    CA9 = ('Cortex-A9', 'CA9')
43#    CA5NEON = ('Cortex-A5neon', 'CA5neon')
44#    CA7NEON = ('Cortex-A7neon', 'CA7neon')
45#    CA9NEON = ('Cortex-A9neon', 'CA9neon')
46
47    def has_bl(self):
48        return self in [
49            DeviceAxis.CM23NS,
50            DeviceAxis.CM33NS,
51            DeviceAxis.CM35PNS,
52            DeviceAxis.CM55NS,
53            DeviceAxis.CM85NS
54        ]
55
56    @property
57    def bl_device(self):
58        bld = {
59            DeviceAxis.CM23NS: 'CM23S',
60            DeviceAxis.CM33NS: 'CM33S',
61            DeviceAxis.CM35PNS: 'CM35PS',
62            DeviceAxis.CM55NS: 'CM55S',
63            DeviceAxis.CM85NS: 'CM85S'
64        }
65        return bld[self]
66
67
68@matrix_axis("compiler", "c", "Compiler(s) to be considered.")
69class CompilerAxis(Enum):
70    AC6 = ('AC6')
71    GCC = ('GCC')
72    IAR = ('IAR')
73    CLANG = ('Clang')
74
75    @property
76    def image_ext(self):
77        ext = {
78            CompilerAxis.AC6: 'axf',
79            CompilerAxis.GCC: 'elf',
80            CompilerAxis.IAR: 'elf',
81            CompilerAxis.CLANG: 'elf',
82        }
83        return ext[self]
84
85    @property
86    def toolchain(self):
87        ext = {
88            CompilerAxis.AC6: 'AC6',
89            CompilerAxis.GCC: 'GCC',
90            CompilerAxis.IAR: 'IAR',
91            CompilerAxis.CLANG: 'CLANG'
92        }
93        return ext[self]
94
95
96@matrix_axis("optimize", "o", "Optimization level(s) to be considered.")
97class OptimizationAxis(Enum):
98    NONE = ('none')
99    BALANCED = ('balanced')
100    SPEED = ('speed')
101    SIZE = ('size')
102
103
104MODEL_EXECUTABLE = {
105    DeviceAxis.CM0: ("FVP_MPS2_Cortex-M0", []),
106    DeviceAxis.CM0plus: ("FVP_MPS2_Cortex-M0plus", []),
107    DeviceAxis.CM3: ("FVP_MPS2_Cortex-M3", []),
108    DeviceAxis.CM4: ("FVP_MPS2_Cortex-M4", []),
109    DeviceAxis.CM7: ("FVP_MPS2_Cortex-M7", []),
110    DeviceAxis.CM23: ("FVP_MPS2_Cortex-M23", []),
111    DeviceAxis.CM23S: ("FVP_MPS2_Cortex-M23", []),
112    DeviceAxis.CM23NS: ("FVP_MPS2_Cortex-M23", []),
113    DeviceAxis.CM33: ("FVP_MPS2_Cortex-M33", []),
114    DeviceAxis.CM33S: ("FVP_MPS2_Cortex-M33", []),
115    DeviceAxis.CM33NS: ("FVP_MPS2_Cortex-M33", []),
116    DeviceAxis.CM35P: ("FVP_MPS2_Cortex-M35P", []),
117    DeviceAxis.CM35PS: ("FVP_MPS2_Cortex-M35P", []),
118    DeviceAxis.CM35PNS: ("FVP_MPS2_Cortex-M35P", []),
119    DeviceAxis.CM55: ("FVP_MPS2_Cortex-M55", []),
120    DeviceAxis.CM55S: ("FVP_MPS2_Cortex-M55", []),
121    DeviceAxis.CM55NS: ("FVP_MPS2_Cortex-M55", []),
122    DeviceAxis.CM85: ("FVP_MPS2_Cortex-M85", []),
123    DeviceAxis.CM85S: ("FVP_MPS2_Cortex-M85", []),
124    DeviceAxis.CM85NS: ("FVP_MPS2_Cortex-M85", []),
125    DeviceAxis.CA5: ("FVP_VE_Cortex-A5x1", []),
126    DeviceAxis.CA7: ("FVP_VE_Cortex-A7x1", []),
127    DeviceAxis.CA9: ("FVP_VE_Cortex-A9x1", []),
128#    DeviceAxis.CA5NEON: ("_VE_Cortex-A5x1", []),
129#    DeviceAxis.CA7NEON: ("_VE_Cortex-A7x1", []),
130#    DeviceAxis.CA9NEON: ("_VE_Cortex-A9x1", [])
131}
132
133def config_suffix(config, timestamp=True):
134    suffix = f"{config.compiler[0]}-{config.optimize[0]}-{config.device[1]}"
135    if timestamp:
136        suffix += f"-{datetime.now().strftime('%Y%m%d%H%M%S')}"
137    return suffix
138
139
140def project_name(config):
141    return f"Validation.{config.compiler}_{config.optimize}+{config.device[1]}"
142
143
144def bl_project_name(config):
145    return f"Bootloader.{config.compiler}_{config.optimize}+{config.device.bl_device}"
146
147
148def output_dir(config):
149    return f"Validation/outdir"
150
151
152def bl_output_dir(config):
153    return f"Bootloader/outdir"
154
155
156def model_config(config):
157    return f"../Layer/Target/{config.device[1]}/model_config.txt"
158
159
160def build_dir(config):
161    return f"build/{config.device[1]}/{config.compiler.toolchain}/{config.optimize}"
162
163
164@matrix_action
165def clean(config):
166    """Build the selected configurations using CMSIS-Build."""
167    yield cbuild_clean(f"{project_name(config)}/{project_name(config)}.cprj")
168
169
170@matrix_action
171def build(config, results):
172    """Build the selected configurations using CMSIS-Build."""
173
174    logging.info("Compiling Tests...")
175
176    yield cbuild(config)
177
178    if not all(r.success for r in results):
179        return
180
181    file = f"build/CoreValidation-{config_suffix(config)}.zip"
182    logging.info("Archiving build output to %s...", file)
183    with ZipFile(file, "w") as archive:
184        for content in iglob(f"{build_dir(config)}/**/*", recursive=True):
185            if Path(content).is_file():
186                archive.write(content)
187
188
189@matrix_action
190def extract(config):
191    """Extract the latest build archive."""
192    archives = sorted(glob(f"build/CoreValidation-{config_suffix(config, timestamp=False)}-*.zip"), reverse=True)
193    yield unzip(archives[0])
194
195
196@matrix_action
197def run(config, results):
198    """Run the selected configurations."""
199    logging.info("Running Core Validation on Arm model ...")
200    yield model_exec(config)
201
202    try:
203        results[0].test_report.write(f"build/CoreValidation-{config_suffix(config)}.junit")
204    except RuntimeError as ex:
205        if isinstance(ex.__cause__, XMLSyntaxError):
206            logging.error("No valid test report found in model output!")
207        else:
208            logging.exception(ex)
209
210
211@matrix_command()
212def cbuild_clean(project):
213    return ["cbuild", "-c", project]
214
215
216@matrix_command()
217def unzip(archive):
218    return ["bash", "-c", f"unzip {archive}"]
219
220
221@matrix_command()
222def preprocess(infile, outfile):
223    return ["arm-none-eabi-gcc", "-xc", "-E", infile, "-P", "-o", outfile]
224
225
226@matrix_command()
227def cbuild(config):
228    return ["cbuild", "--toolchain", config.compiler.toolchain, "--update-rte", \
229             "--context", f".{config.optimize}+{config.device[1]}", \
230             "Validation.csolution.yml" ]
231
232
233@matrix_command(test_report=ConsoleReport() |
234                            CropReport('<\?xml version="1.0"\?>', '</report>') |
235                            TransformReport('validation.xsl') |
236                            JUnitReport(title=lambda title, result: f"{result.command.config.compiler}."
237                                                                    f"{result.command.config.optimize}."
238                                                                    f"{result.command.config.device}."
239                                                                    f"{title}"))
240def model_exec(config):
241    cmdline = [MODEL_EXECUTABLE[config.device][0], "-q", "--simlimit", 100, "-f", model_config(config)]
242    cmdline += MODEL_EXECUTABLE[config.device][1]
243    cmdline += ["-a", f"{build_dir(config)}/{output_dir(config)}/Validation.{config.compiler.image_ext}"]
244    if config.device.has_bl():
245        cmdline += ["-a", f"{build_dir(config)}/{bl_output_dir(config)}/Bootloader.{config.compiler.image_ext}"]
246    return cmdline
247
248
249@matrix_filter
250def filter_iar(config):
251    return config.compiler == CompilerAxis.IAR
252
253
254if __name__ == "__main__":
255    main()
256