2121import logging
2222import os
2323import pathlib
24+ import platform
2425import shutil
2526import subprocess
2627import tarfile
3940
4041_LOG = logging .getLogger (__name__ )
4142
43+ AOT_SUCCESS_TOKEN = "AOT_TEST_SUCCESS"
44+ AOT_FAILURE_TOKEN = "AOT_TEST_FAILURE"
45+
4246
4347class AOTTestModel (NamedTuple ):
4448 """Class to describe a model under test
@@ -64,6 +68,41 @@ class AOTTestModel(NamedTuple):
6468 params : Optional [Dict [str , np .array ]] = None
6569
6670
71+ class AOTTestRunner (NamedTuple ):
72+ """Class to describe a test runner for AOT code
73+
74+ Parameters
75+ ----------
76+ makefile: str
77+ Premade Makefile to use from the AOT test folder
78+ prologue: str
79+ Code to prepend to the main function
80+ includes: List[str]
81+ Additional includes required to run the AOT test runner
82+ parameters: Map[str, str]
83+ Additional parameters to pass to the make command
84+ """
85+
86+ makefile : str = "default"
87+ prologue : str = ""
88+ includes : List [str ] = []
89+ parameters : Dict [str , str ] = {}
90+
91+
92+ AOT_DEFAULT_RUNNER = AOTTestRunner ()
93+
94+ # AOT Test Runner using the Arm® Corstone™-300 Reference Systems
95+ # see: https://developer.arm.com/ip-products/subsystem/corstone/corstone-300
96+ AOT_CORSTONE300_RUNNER = AOTTestRunner (
97+ makefile = "corstone300" ,
98+ prologue = """
99+ uart_init();
100+ """ ,
101+ includes = ["uart.h" ],
102+ parameters = {"NPU_VARIANT" : "256" },
103+ )
104+
105+
67106def mangle_name (mod_name , name ):
68107 mod_name = mangle_module_name (mod_name )
69108 return mod_name + "_" + name
@@ -112,20 +151,41 @@ def convert_to_list(x):
112151def parametrize_aot_options (test ):
113152 """Parametrize over valid option combinations"""
114153
154+ skip_i386 = pytest .mark .skipif (
155+ platform .machine () == "i686" , reason = "Reference system unavailable in i386 container"
156+ )
115157 interface_api = ["packed" , "c" ]
116158 use_unpacked_api = [True , False ]
117- use_calculated_workspaces = [True , False ]
159+ test_runner = [AOT_DEFAULT_RUNNER , AOT_CORSTONE300_RUNNER ]
160+
161+ all_combinations = itertools .product (interface_api , use_unpacked_api , test_runner )
118162
119- all_combinations = itertools .product (interface_api , use_unpacked_api , use_calculated_workspaces )
120163 # Filter out packed operators with c interface
121164 valid_combinations = filter (
122- lambda parameters : not (parameters [0 ] == "c" and parameters [1 ] == False ),
165+ lambda parameters : not (parameters [0 ] == "c" and not parameters [1 ]),
123166 all_combinations ,
124167 )
125168
126- return pytest .mark .parametrize (
127- ["interface_api" , "use_unpacked_api" , "use_calculated_workspaces" ],
169+ # Only use reference system for C interface and unpacked API calls
170+ valid_combinations = filter (
171+ lambda parameters : not (
172+ parameters [2 ] == AOT_CORSTONE300_RUNNER
173+ and (parameters [0 ] == "packed" or not parameters [1 ])
174+ ),
128175 valid_combinations ,
176+ )
177+
178+ # Skip reference system tests if running in i386 container
179+ marked_combinations = map (
180+ lambda parameters : pytest .param (* parameters , marks = skip_i386 )
181+ if parameters [2 ] == AOT_CORSTONE300_RUNNER
182+ else parameters ,
183+ valid_combinations ,
184+ )
185+
186+ return pytest .mark .parametrize (
187+ ["interface_api" , "use_unpacked_api" , "test_runner" ],
188+ marked_combinations ,
129189 )(test )
130190
131191
@@ -160,7 +220,7 @@ def subprocess_log_output(cmd, cwd, logfile):
160220 return proc .wait ()
161221
162222
163- def emit_main_prologue (main_file , workspace_bytes ):
223+ def emit_main_prologue (main_file , custom_prologue , workspace_bytes ):
164224 # Add TVM_RUNTIME_ALLOC_ALIGNMENT_BYTES because of memory alignment.
165225 main_file .write (
166226 f"#define WORKSPACE_SIZE ({ workspace_bytes } + TVM_RUNTIME_ALLOC_ALIGNMENT_BYTES)\n "
@@ -185,6 +245,7 @@ def emit_main_prologue(main_file, workspace_bytes):
185245int main(){\n
186246"""
187247 )
248+ main_file .write (custom_prologue )
188249
189250
190251def emit_main_data (main_file , input_map , output_list , mod_name ):
@@ -297,11 +358,11 @@ def emit_main_compare(main_file, output_list, mod_name):
297358 main_file .write (f"for (int i = 0; i<{ actual_data_name } { i } _len; i++){{\n " )
298359 if is_float_dtype :
299360 main_file .write (
300- f'if (fabs({ actual_data_name } { i } [i]-{ expected_data_name } { i } [i]) > 0.001f){{\n \t printf("ko \\ n");\n \t return -1;}}\n '
361+ f'if (fabs({ actual_data_name } { i } [i]-{ expected_data_name } { i } [i]) > 0.001f){{\n \t printf("{ AOT_FAILURE_TOKEN } \\ n");\n \t return -1;}}\n '
301362 )
302363 else :
303364 main_file .write (
304- f'if ({ actual_data_name } { i } [i]!={ expected_data_name } { i } [i]){{\n \t printf("ko \\ n");\n \t return -1;}}\n '
365+ f'if ({ actual_data_name } { i } [i]!={ expected_data_name } { i } [i]){{\n \t printf("{ AOT_FAILURE_TOKEN } \\ n");\n \t return -1;}}\n '
305366 )
306367 main_file .write ("}\n " )
307368
@@ -312,36 +373,40 @@ def emit_main_init_memory_manager(main_file):
312373
313374
314375def emit_main_epilogue (main_file ):
315- main_file .write ('printf("ok \\ n");' )
376+ main_file .write (f 'printf("{ AOT_SUCCESS_TOKEN } \\ n");' )
316377 main_file .write ("return 0;" )
317378 main_file .write ("}\n " )
318379
319380
320- def emit_main_common_includes (main_file ):
381+ def emit_main_common_includes (main_file , custom_includes ):
321382 main_file .write ("#include <stdio.h>\n " )
322383 main_file .write ("#include <math.h>\n " )
323384 main_file .write ('#include "tvm/runtime/c_runtime_api.h"\n ' )
324385 main_file .write ('#include "tvm/runtime/crt/stack_allocator.h"\n ' )
386+ for include in custom_includes :
387+ main_file .write (f'#include "{ include } "\n ' )
325388
326389
327390def emit_main_micro_include (main_file , mod_name ):
328391 main_file .write (f"#include <{ mangle_module_name (mod_name )} .h>\n " )
329392
330393
331- def create_main (test_name , models , output_path , interface_api , workspace_bytes ):
394+ def create_main (
395+ test_name , models , output_path , custom_includes , custom_prologue , interface_api , workspace_bytes
396+ ):
332397 file_path = pathlib .Path (f"{ output_path } /" + test_name ).resolve ()
333398 # create header file
334399 raw_path = file_path .with_suffix (".c" ).resolve ()
335400 with open (raw_path , "w" ) as main_file :
336- emit_main_common_includes (main_file )
401+ emit_main_common_includes (main_file , custom_includes )
337402
338403 if interface_api == "c" :
339404 for model in models :
340405 emit_main_micro_include (main_file , model .name )
341-
342- emit_main_prologue (main_file , workspace_bytes )
343406 for model in models :
344407 emit_main_data (main_file , model .inputs , model .outputs , model .name )
408+
409+ emit_main_prologue (main_file , custom_prologue , workspace_bytes )
345410 emit_main_init_memory_manager (main_file )
346411
347412 if interface_api == "c" :
@@ -396,9 +461,10 @@ def extract_main_workspace_size_bytes(extract_dir):
396461
397462def compile_and_run (
398463 models : Union [List [AOTTestModel ], AOTTestModel ],
464+ runner : AOTTestRunner ,
399465 interface_api ,
400466 use_unpacked_api ,
401- use_calculated_workspaces ,
467+ debug_calculated_workspaces = False ,
402468 workspace_byte_alignment = 8 ,
403469 enable_op_fusion = True ,
404470):
@@ -414,7 +480,7 @@ def compile_and_run(
414480 models = [models ]
415481
416482 # The calculated workspaces will not account for stack allocator tags used for debugging
417- if not use_calculated_workspaces :
483+ if debug_calculated_workspaces :
418484 cflags += "-DTVM_CRT_STACK_ALLOCATOR_ENABLE_LIFO_CHECK "
419485
420486 config = {"tir.disable_vectorize" : True }
@@ -452,10 +518,7 @@ def compile_and_run(
452518 t = tarfile .open (tar_file )
453519 t .extractall (base_path )
454520
455- if use_calculated_workspaces :
456- workspace_bytes += extract_main_workspace_size_bytes (base_path )
457- else :
458- workspace_bytes += 16384 * 1024
521+ workspace_bytes += extract_main_workspace_size_bytes (base_path )
459522
460523 for key in model .inputs :
461524 create_header_file (
@@ -480,31 +543,41 @@ def compile_and_run(
480543 "test.c" ,
481544 models ,
482545 build_path ,
546+ runner .includes ,
547+ runner .prologue ,
483548 interface_api ,
484549 workspace_bytes ,
485550 )
486551
487552 # Verify that compiles fine
488553 file_dir = os .path .dirname (os .path .abspath (__file__ ))
489554 codegen_path = os .path .join (base_path , "codegen" )
490- makefile = os .path .join (file_dir , "aot_test.mk" )
491- make_cmd = (
492- f"make CFLAGS='{ cflags } ' -f { makefile } build_dir="
493- + build_path
555+ makefile = os .path .join (file_dir , f"{ runner .makefile } .mk" )
556+ custom_params = " " .join ([f" { param } ='{ value } '" for param , value in runner .parameters .items ()])
557+ make_command = (
558+ f"make -f { makefile } build_dir={ build_path } "
559+ + f" CFLAGS='{ cflags } '"
494560 + f" TVM_ROOT={ file_dir } /../../../.."
561+ + f" AOT_TEST_ROOT={ file_dir } "
495562 + f" CODEGEN_ROOT={ codegen_path } "
496563 + f" STANDALONE_CRT_DIR={ tvm .micro .get_standalone_crt_dir ()} "
564+ + custom_params
497565 )
498566
499567 compile_log_path = os .path .join (build_path , "test_compile.log" )
500- ret = subprocess_log_output (make_cmd , "." , compile_log_path )
568+ compile_command = f"{ make_command } aot_test_runner"
569+ ret = subprocess_log_output (compile_command , "." , compile_log_path )
501570 assert ret == 0
502571
503572 # Verify that runs fine
504573 run_log_path = os .path .join (build_path , "test_run.log" )
505- ret = subprocess_log_output ("./aot_test_runner" , build_path , run_log_path )
574+ run_command = f"{ make_command } run"
575+ ret = subprocess_log_output (run_command , build_path , run_log_path )
506576 assert ret == 0
507577
578+ with open (run_log_path ) as run_log :
579+ assert AOT_SUCCESS_TOKEN in run_log .read ()
580+
508581
509582def generate_ref_data (mod , input_data , params = None , target = "llvm" ):
510583 """Generate reference data through executing the relay module"""
0 commit comments