Merge pull request #1452 from ucb-bar/uniquify-names-2

uniquify module names
This commit is contained in:
joonho hwangbo
2023-05-06 12:29:32 -07:00
committed by GitHub
8 changed files with 219 additions and 321 deletions

View File

@@ -231,36 +231,24 @@ $(SFC_MFC_TARGETS) &: $(FIRRTL_FILE) $(FINAL_ANNO_FILE) $(SFC_LEVEL) $(EXTRA_FIR
$(SED) -i 's/.*/& /' $(MFC_SMEMS_CONF) # need trailing space for SFC macrocompiler
# DOC include end: FirrtlCompiler
$(TOP_MODS_FILELIST) $(MODEL_MODS_FILELIST) $(ALL_MODS_FILELIST) $(BB_MODS_FILELIST) $(MFC_MODEL_HRCHY_JSON_UNIQUIFIED) &: $(MFC_MODEL_HRCHY_JSON) $(MFC_FILELIST) $(MFC_BB_MODS_FILELIST)
$(base_dir)/scripts/split-module-files.py \
$(TOP_MODS_FILELIST) $(MODEL_MODS_FILELIST) $(ALL_MODS_FILELIST) $(BB_MODS_FILELIST) $(MFC_MODEL_HRCHY_JSON_UNIQUIFIED) &: $(MFC_MODEL_HRCHY_JSON) $(MFC_TOP_HRCHY_JSON) $(MFC_FILELIST) $(MFC_BB_MODS_FILELIST)
$(base_dir)/scripts/uniquify-module-names.py \
--model-hier-json $(MFC_MODEL_HRCHY_JSON) \
--top-hier-json $(MFC_TOP_HRCHY_JSON) \
--in-all-filelist $(MFC_FILELIST) \
--dut $(TOP) \
--model $(MODEL) \
--target-dir $(GEN_COLLATERAL_DIR) \
--out-dut-filelist $(TOP_MODS_FILELIST) \
--out-model-filelist $(MODEL_MODS_FILELIST) \
--in-all-filelist $(MFC_FILELIST) \
--target-dir $(GEN_COLLATERAL_DIR)
--out-model-hier-json $(MFC_MODEL_HRCHY_JSON_UNIQUIFIED) \
--gcpath $(GEN_COLLATERAL_DIR)
$(SED) -e 's;^;$(GEN_COLLATERAL_DIR)/;' $(MFC_BB_MODS_FILELIST) > $(BB_MODS_FILELIST)
$(SED) -i 's/\.\///' $(TOP_MODS_FILELIST)
$(SED) -i 's/\.\///' $(MODEL_MODS_FILELIST)
$(SED) -i 's/\.\///' $(BB_MODS_FILELIST)
$(base_dir)/scripts/uniqify-module-names.py \
--top-filelist $(TOP_MODS_FILELIST) \
--mod-filelist $(MODEL_MODS_FILELIST) \
--gen-collateral-path $(GEN_COLLATERAL_DIR) \
--model-hier-json $(MFC_MODEL_HRCHY_JSON) \
--out-model-hier-json $(MFC_MODEL_HRCHY_JSON_UNIQUIFIED) \
--dut $(TOP) \
--model $(MODEL)
sort -u $(TOP_MODS_FILELIST) $(MODEL_MODS_FILELIST) $(BB_MODS_FILELIST) > $(ALL_MODS_FILELIST)
$(TOP_BB_MODS_FILELIST) $(MODEL_BB_MODS_FILELIST) &: $(BB_MODS_FILELIST) $(MFC_TOP_HRCHY_JSON) $(FINAL_ANNO_FILE)
$(base_dir)/scripts/split-bb-files.py \
--in-bb-f $(BB_MODS_FILELIST) \
--in-top-hrchy-json $(MFC_TOP_HRCHY_JSON) \
--in-anno-json $(FINAL_ANNO_FILE) \
--out-top-bb-f $(TOP_BB_MODS_FILELIST) \
--out-model-bb-f $(MODEL_BB_MODS_FILELIST)
$(TOP_SMEMS_CONF) $(MODEL_SMEMS_CONF) &: $(MFC_SMEMS_CONF) $(MFC_MODEL_HRCHY_JSON_UNIQUIFIED)
$(base_dir)/scripts/split-mems-conf.py \
--in-smems-conf $(MFC_SMEMS_CONF) \
@@ -284,7 +272,7 @@ $(MODEL_SMEMS_FILE) $(MODEL_SMEMS_FIR) &: $(MODEL_SMEMS_CONF) | $(TOP_SMEMS_FILE
# note: {MODEL,TOP}_BB_MODS_FILELIST is added as a req. so that the files get generated,
# however it is really unneeded since ALL_MODS_FILELIST includes all BB files
########################################################################################
$(sim_common_files): $(sim_files) $(ALL_MODS_FILELIST) $(TOP_SMEMS_FILE) $(MODEL_SMEMS_FILE) $(TOP_BB_MODS_FILELIST) $(MODEL_BB_MODS_FILELIST)
$(sim_common_files): $(sim_files) $(ALL_MODS_FILELIST) $(TOP_SMEMS_FILE) $(MODEL_SMEMS_FILE) $(BB_MODS_FILELIST)
sort -u $(sim_files) $(ALL_MODS_FILELIST) | grep -v '.*\.\(svh\|h\)$$' > $@
echo "$(TOP_SMEMS_FILE)" >> $@
echo "$(MODEL_SMEMS_FILE)" >> $@

View File

@@ -1,82 +0,0 @@
#!/usr/bin/env python3
import json
import argparse
from collections import defaultdict
# Schema of *.f emitted by circt
"""
<gen-src-dir>/<long-name>/gen-collateral/SimUART.cc
<gen-src-dir>/<long-name>/gen-collateral/AsyncQueueSource.sv
<gen-src-dir>/<long-name>/gen-collateral/AsyncQueueSink.sv
<gen-src-dir>/<long-name>/gen-collateral/AsyncQueueSource_1.sv
<gen-src-dir>/<long-name>/gen-collateral/AsyncQueueSink_1.sv
<gen-src-dir>/<long-name>/gen-collateral/AsyncQueueSource_2.sv
<gen-src-dir>/<long-name>/gen-collateral/AsyncQueueSink_2.sv
<gen-src-dir>/<long-name>/gen-collateral/AsyncResetSynchronizerShiftReg_w4_d3_i0.sv
"""
def bfs_collect_submodules(tree):
output = set()
q = [(tree['instance_name'], tree['module_name'], tree['instances'])]
while len(q) != 0:
front = q[0]
q.pop(0)
(inst, mod, child) = front
output.add(mod)
for c in child:
q.append((c['instance_name'], c['module_name'], c['instances']))
return output
def write_lines_to_file(lines, file_path):
with open(file_path, "w") as fp:
for line in lines:
fp.write("%s\n" % line)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Create *.model.bb.f and *.top.bb.f blackbox filelists')
parser.add_argument('--in-bb-f', type=str, required=True, help='All blackbox files filelist (includes both MODEL/TOP files)')
parser.add_argument('--in-top-hrchy-json', type=str, required=True, help='List containing hierarchy of top modules (top-module-hierarchy.json)')
parser.add_argument('--in-anno-json', type=str, required=True, help='Anno. file with blackbox annotations')
parser.add_argument('--out-top-bb-f', type=str, required=True, help='List of blackbox files for TOP')
parser.add_argument('--out-model-bb-f', type=str, required=True, help='List of blackbox files for MODEL')
args = parser.parse_args()
# module_path -> list of bb paths (not fully resolved paths)
mod_bb_dict = defaultdict(list)
with open(args.in_anno_json, "r") as f:
anno_data = json.load(f)
for anno in anno_data:
if 'BlackBoxInlineAnno' in anno['class']:
mod_bb_dict[anno['target']].append(anno['name'])
if 'BlackBoxPathAnno' in anno['class']:
mod_bb_dict[anno['target']].append(anno['path'])
with open(args.in_top_hrchy_json) as ihj:
ihj_data = json.load(ihj)
top_inner_modules = bfs_collect_submodules(ihj_data)
with open(args.in_bb_f) as ibf:
lines = ibf.read().splitlines()
tbfs = set()
for mod_path, bb_files in mod_bb_dict.items():
leaf_mod = mod_path.split('.')[-1]
# if matched, add the fully resolved path to the top bb filelist
if leaf_mod in top_inner_modules:
for line in lines:
for bb_file in bb_files:
if bb_file in line:
tbfs.add(line)
# now tbfs should be complete (need to remove tbf files from original bb file for model bb)
mbfs = set()
for line in lines:
if not line in tbfs:
mbfs.add(line)
write_lines_to_file(tbfs, args.out_top_bb_f)
write_lines_to_file(mbfs, args.out_model_bb_f)

View File

@@ -1,111 +0,0 @@
#!/usr/bin/env python3
import json
import argparse
from typing import List, Optional
# Schema of json emitted by circt
"""
{
"instance_name": "TestHarness",
"module_name": "TestHarness",
"instances": [
{
"instance_name": "chiptop",
"module_name": "ChipTop",
"instances": [
{
"instance_name": "system",
"module_name": "DigitalTop",
"instances": [ ]
}, ...
]
},
{
"instance_name": "simdram",
"module_name": "SimDRAM",
"instances": []
},
]
}
"""
def get_modules(js: dict) -> List[str]:
if 'instances' not in js:
return js['module_name']
else:
mods = []
for mod in js['instances']:
mods.extend(get_modules(mod))
return [js['module_name']] + mods
def find_mod_by_name(js: dict, name: str) -> Optional[List[dict]]:
if 'instances' not in js:
return None
else:
mods = []
for mod in js['instances']:
if mod['module_name'] == name:
mods.append(mod)
other_mods = find_mod_by_name(mod, name)
if other_mods is not None:
mods.extend(other_mods)
return mods
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Convert CIRCT (firtool) hierarchy JSON into DUT and test harness filelists')
parser.add_argument('--model-hier-json', type=str, required=True, help='Path to hierarchy JSON emitted by firtool. Must include DUT as a module.')
parser.add_argument('--dut', type=str, required=True, help='Name of the DUT module.')
parser.add_argument('--out-dut-filelist', type=str, required=True, help='Path to output filelist including all modules under the DUT.')
parser.add_argument('--out-model-filelist', type=str, required=True, help='Path to output filelist including all modules under the top-most module but not modules under the DUT.')
parser.add_argument('--in-all-filelist', type=str, required=True, help='Path to input filelist that has all modules (relative paths).')
parser.add_argument('--target-dir', type=str, required=True, help='Path to where module sources are located (combined with --in-all-filelist gives the absolute path to module sources).')
args = parser.parse_args()
with open(args.model_hier_json) as f:
j = json.load(f)
dut_tops = find_mod_by_name(j, args.dut)
assert dut_tops is not None
assert len(dut_tops) == 1
dut_top = dut_tops[0]
dut_mods = set(get_modules(dut_top))
model_mods = set(get_modules(j)) - dut_mods
both_mods = dut_mods.intersection(model_mods)
assert len(both_mods) == 0
with open(args.out_dut_filelist, 'w') as df, \
open(args.in_all_filelist) as fl:
# add paths that correspond to modules to output file
for path in fl:
writeOut = False
for dm in dut_mods:
if dm in path:
writeOut = True
break
# prepend the target directory to get filelist with absolute paths
if writeOut:
if not args.target_dir in path:
df.write(f"{args.target_dir}/{path}")
else:
df.write(f"{path}")
with open(args.out_model_filelist, 'w') as df, \
open(args.in_all_filelist) as fl:
# add paths that correspond to modules to output file
for path in fl:
writeOut = False
for dm in model_mods:
if dm in path:
writeOut = True
break
# prepend the target directory to get filelist with absolute paths
if writeOut:
if not args.target_dir in path:
df.write(f"{args.target_dir}/{path}")
else:
df.write(f"{path}")

View File

@@ -1,100 +0,0 @@
#!/usr/bin/env python3
import json
import argparse
import shutil
import os
import datetime
parser = argparse.ArgumentParser(description="")
parser.add_argument("--top-filelist", type=str, required=True, help="Abs path to <top>.<model>.top.f")
parser.add_argument("--mod-filelist", type=str, required=True, help="Abs path to <top>.<model>.model.f")
parser.add_argument("--gen-collateral-path", dest="gcpath", type=str, required=True, help="Abs path to the gen-collateral directory")
parser.add_argument("--model-hier-json", type=str, required=True, help="Path to hierarchy JSON emitted by firtool. Must include DUT as a module.")
parser.add_argument("--out-model-hier-json", type=str, required=True, help="Path to updated hierarchy JSON emitted by this script.")
parser.add_argument("--dut", type=str, required=True, help="Name of the DUT module.")
parser.add_argument("--model", type=str, required=True, help="Name of the Model module.")
args = parser.parse_args()
MODEL_SFX=args.model + "_UNIQUIFIED"
def bash(cmd):
fail = os.system(cmd)
if fail:
print(f'[*] failed to execute {cmd}')
sys.exit(1)
else:
print(cmd)
def get_filelist(filelist):
fnames = []
with open(filelist) as f:
lines = f.readlines()
for line in lines:
try:
fname = line.split("/")[-1].strip()
fnames.append(fname)
except:
print(f"Something is wrong about this line '{line}'")
return fnames
def update_filelist(cur_file, new_file):
bash(f"echo \"{args.gcpath}/{new_file}\" >> {os.path.join(args.gcpath, args.mod_filelist)}")
def generate_copy(c, sfx):
(cur_name, ext) = os.path.splitext(c)
new_name = cur_name + "_" + sfx
new_file = new_name + ext
cur_file = os.path.join(args.gcpath, c)
new_file = os.path.join(args.gcpath, new_file)
shutil.copy(cur_file, new_file)
bash(f"sed -i s/\"module {cur_name}\"/\"module {new_name}\"/ {new_file}")
return new_file
def dfs_update_modules(tree, common_fnames, visited, top_fnames):
# List of direct submodules to update
childs_to_update = list()
for child in tree['instances']:
# We don't have to change stuff that are under the dut
if (child['module_name'] == args.dut) or (child['module_name'] in visited):
continue
if dfs_update_modules(child, common_fnames, visited, top_fnames):
childs_to_update.append(child['module_name'])
if (child['module_name'] + ".sv") in common_fnames:
child['module_name'] = child['module_name'] + "_" + MODEL_SFX
cur_module = tree['module_name']
cur_file = cur_module + ".sv"
new_file = None
# cur_file is in the common list, or is a ancestor of of them, generate a new file
if (cur_file in common_fnames) or len(childs_to_update) > 0:
new_file = generate_copy(cur_file, MODEL_SFX)
update_filelist(cur_file, os.path.basename(new_file))
for submodule_name in childs_to_update:
if (submodule_name + ".sv") in common_fnames:
bash(f"sed -i s/\"{submodule_name}\"/\"{submodule_name}_{MODEL_SFX}\"/ {new_file}")
visited.add(cur_module)
return (new_file is not None)
def main():
top_fnames = set(get_filelist(args.top_filelist))
mod_fnames = set(get_filelist(args.mod_filelist))
common_fnames = top_fnames.intersection(mod_fnames)
with open(args.model_hier_json) as imhj:
imhj_data = json.load(imhj)
with open(args.out_model_hier_json, "w+") as out_file:
visited = set()
dfs_update_modules(imhj_data, common_fnames, visited, top_fnames)
json.dump(imhj_data, out_file, indent=2)
if __name__ == "__main__":
main()

207
scripts/uniquify-module-names.py Executable file
View File

@@ -0,0 +1,207 @@
#!/usr/bin/env python3
import json
import argparse
import shutil
import os
import sys
parser = argparse.ArgumentParser(description="")
parser.add_argument("--model-hier-json", type=str, required=True, help="Path to hierarchy JSON emitted by firtool. Must include DUT as a module.")
parser.add_argument("--top-hier-json", type=str, required=True, help="Path to hierarchy JSON emitted by firtool. Must include DUT as a module.")
parser.add_argument('--in-all-filelist', type=str, required=True, help='Path to input filelist that has all modules (relative paths).')
parser.add_argument("--dut", type=str, required=True, help="Name of the DUT module.")
parser.add_argument("--model", type=str, required=True, help="Name of the Model module.")
parser.add_argument('--out-dut-filelist', type=str, required=True, help='Path to output filelist including all modules under the DUT.')
parser.add_argument('--out-model-filelist', type=str, required=True, help='Path to output filelist including all modules under the MODEL.')
parser.add_argument("--out-model-hier-json", type=str, required=True, help="Path to updated hierarchy JSON emitted by this script.")
parser.add_argument('--target-dir', type=str, required=True, help='Path to where module sources are located (combined with --in-all-filelist gives the absolute path to module sources).')
parser.add_argument("--gcpath", type=str, required=True, help="Path to gen-collateral")
args = parser.parse_args()
MODEL_SFX=args.model + "_UNIQUIFIED"
def bash(cmd):
fail = os.system(cmd)
if fail:
print(f'[*] failed to execute {cmd}')
sys.exit(1)
else:
print(cmd)
def bfs_collect_modules(tree, child_to_ignore = None):
q = [(tree['instance_name'], tree['module_name'], tree['instances'])]
modules = list()
while len(q) != 0:
front = q[0]
q.pop(0)
(inst, mod, child) = front
modules.append(mod)
for c in child:
if c['module_name'] != child_to_ignore:
q.append((c['instance_name'], c['module_name'], c['instances']))
return modules
def get_modules_in_verilog_file(file):
module_names = list()
with open(file) as f:
lines = f.readlines()
for line in lines:
words = line.split()
if len(words) > 0 and words[0] == "module":
module_names.append(words[1].replace("(", "").replace(")", "").replace(";", ""))
return module_names
def get_modules_in_filelist(verilog_module_filename, cc_filelist):
with open(args.in_all_filelist) as fl:
lines = fl.readlines()
for line in lines:
path = line.strip()
basepath = os.path.basename(path)
ext = basepath.split(".")[-1]
if (ext == "v") or (ext == "sv"):
modules = get_modules_in_verilog_file(os.path.join(args.gcpath, basepath))
for module in modules:
verilog_module_filename[module] = basepath
else:
cc_filelist.append(basepath)
return (verilog_module_filename, cc_filelist)
def get_modules_under_hier(hier, child_to_ignore=None):
with open(hier) as hj:
hj_data = json.load(hj)
modules_under_hier = set(bfs_collect_modules(hj_data, child_to_ignore=child_to_ignore))
return modules_under_hier
def write_verilog_filelist(modules, verilog_module_filename, out_filelist):
written_files = set()
existing_modules = verilog_module_filename.keys()
with open(out_filelist, "w") as df:
for module in modules:
if module in existing_modules:
verilog_filename = verilog_module_filename[module]
if verilog_filename not in written_files:
written_files.add(verilog_filename)
if args.target_dir in verilog_filename:
df.write(f"{verilog_filename}\n")
else:
df.write(f"{args.target_dir}/{verilog_filename}\n")
return written_files
def write_cc_filelist(filelist, out_filelist):
with open(out_filelist, "a") as df:
for path in filelist:
file = os.path.basename(path)
df.write(f"{args.target_dir}/{file}\n")
def generate_copy(c, sfx):
(cur_name, ext) = os.path.splitext(c)
new_name = cur_name + "_" + sfx
new_file = new_name + ext
cur_file = os.path.join(args.gcpath, c)
new_file = os.path.join(args.gcpath, new_file)
shutil.copy(cur_file, new_file)
bash(f"sed -i s/\"module {cur_name}\"/\"module {new_name}\"/ {new_file}")
return new_file
def bfs_uniquify_modules(tree, common_fnames, verilog_module_filename):
q = [(tree['instance_name'], tree['module_name'], tree['instances'], None)]
updated_submodule = set()
existing_modules = verilog_module_filename.keys()
while len(q) != 0:
front = q[0]
q.pop(0)
(inst, mod, child, parent) = front
# external module
if mod not in existing_modules:
assert(len(child) == 0)
continue
cur_file = verilog_module_filename[mod]
# if the module is common, make a copy & update its instance in its parent
new_mod = mod
if mod in common_fnames:
try:
new_file = generate_copy(cur_file, MODEL_SFX)
if parent is not None and ((parent, mod) not in updated_submodule):
parent_file = os.path.join(args.gcpath, verilog_module_filename[parent])
bash(f"sed -i s/\"{mod} \"/\"{mod}_{MODEL_SFX} \"/ {parent_file}")
updated_submodule.add((parent, mod))
# add the uniquified module to the verilog_modul_filename dict
new_mod = mod + "_" + MODEL_SFX
verilog_module_filename[new_mod] = new_file
except:
print(f"No corresponding file for {cur_file}")
# traverse its children
for c in child:
if c['module_name'] != args.dut:
q.append((c['instance_name'], c['module_name'], c['instances'], new_mod))
def dfs_update_modules(tree, common_fnames, visited):
# List of direct submodules to update
childs_to_update = list()
for child in tree['instances']:
# We don't have to change stuff that are under the dut
if (child['module_name'] == args.dut):
continue
if dfs_update_modules(child, common_fnames, visited):
childs_to_update.append(child['module_name'])
if (child['module_name']) in common_fnames:
child['module_name'] = child['module_name'] + "_" + MODEL_SFX
cur_module = tree['module_name']
new_file = None
# cur_file is in the common list, or is a ancestor of of them, generate a new file
if (cur_module in common_fnames) or len(childs_to_update) > 0:
new_file = 1
visited.add(cur_module)
return (new_file is not None)
def uniquify_modules_under_model(modules_under_model, common_modules, verilog_module_filename):
with open(args.model_hier_json) as imhj:
imhj_data = json.load(imhj)
visited = set()
bfs_uniquify_modules(imhj_data, common_modules, verilog_module_filename)
dfs_update_modules (imhj_data, common_modules, visited)
with open(args.out_model_hier_json, "w+") as out_file:
json.dump(imhj_data, out_file, indent=2)
def main():
verilog_module_filename = dict()
cc_filelist = list()
get_modules_in_filelist(verilog_module_filename, cc_filelist)
modules_under_model = get_modules_under_hier(args.model_hier_json, args.dut)
modules_under_top = get_modules_under_hier(args.top_hier_json)
common_modules = modules_under_top.intersection(modules_under_model)
# write top filelist
write_verilog_filelist(modules_under_top, verilog_module_filename, args.out_dut_filelist)
# rename modules that are common
uniquify_modules_under_model(modules_under_model, common_modules, verilog_module_filename)
uniquified_modules_under_model = get_modules_under_hier(args.out_model_hier_json, args.dut)
# write model filelist
write_verilog_filelist(uniquified_modules_under_model, verilog_module_filename, args.out_model_filelist)
write_cc_filelist (cc_filelist, args.out_model_filelist)
if __name__=="__main__":
main()

View File

@@ -186,10 +186,6 @@ MODEL_MODS_FILELIST ?= $(build_dir)/$(long_name).model.f
# list of all blackbox files (may be included in the top/model.f files)
# this has the build_dir appended
BB_MODS_FILELIST ?= $(build_dir)/$(long_name).bb.f
# top blackbox module files to include
TOP_BB_MODS_FILELIST ?= $(build_dir)/$(long_name).top.bb.f
# model blackbox module files to include (not including top blackbox modules)
MODEL_BB_MODS_FILELIST ?= $(build_dir)/$(long_name).model.bb.f
# all module files to include (top, model, bb included)
ALL_MODS_FILELIST ?= $(build_dir)/$(long_name).all.f

View File

@@ -72,7 +72,7 @@ VLSI_RTL = $(build_dir)/syn.f
ifneq ($(CUSTOM_VLOG), )
RTL_DEPS = $(CUSTOM_VLOG)
else
RTL_DEPS = $(TOP_MODS_FILELIST) $(TOP_BB_MODS_FILELIST) $(TOP_SMEMS_FILE)
RTL_DEPS = $(TOP_MODS_FILELIST) $(TOP_SMEMS_FILE)
endif
$(VLSI_RTL): $(RTL_DEPS)
@@ -80,7 +80,7 @@ ifneq ($(CUSTOM_VLOG), )
> $(VLSI_RTL)
$(foreach file,$^,echo $(file) >> $(VLSI_RTL))
else
cat $(TOP_MODS_FILELIST) $(TOP_BB_MODS_FILELIST) | sort -u > $(VLSI_RTL)
cat $(TOP_MODS_FILELIST) | sort -u > $(VLSI_RTL)
echo $(TOP_SMEMS_FILE) >> $(VLSI_RTL)
endif

View File

@@ -10,7 +10,7 @@ $(SIM_CONF): $(sim_common_files)
echo " top_module: $(VLSI_TOP)" >> $@
echo " tb_name: ''" >> $@ # don't specify -top
echo " input_files:" >> $@
for x in $$(cat $(MODEL_MODS_FILELIST) $(MODEL_BB_MODS_FILELIST) | sort -u) $(MODEL_SMEMS_FILE) $(SIM_FILE_REQS); do \
for x in $$(cat $(MODEL_MODS_FILELIST) | sort -u) $(MODEL_SMEMS_FILE) $(SIM_FILE_REQS); do \
echo ' - "'$$x'"' >> $@; \
done
echo " input_files_meta: 'append'" >> $@