Vortex 2.0 changes:

+ Microarchitecture optimizations
+ 64-bit support
+ Xilinx FPGA support
+ LLVM-16 support
+ Refactoring and quality control fixes

minor update

minor update

minor update

minor update

minor update

minor update

cleanup

cleanup

cache bindings and memory perf refactory

minor update

minor update

hw unit tests fixes

minor update

minor update

minor update

minor update

minor update

minor udpate

minor update

minor update

minor update

minor update

minor update

minor update

minor update

minor updates

minor updates

minor update

minor update

minor update

minor update

minor update

minor update

minor updates

minor updates

minor updates

minor updates

minor update

minor update
This commit is contained in:
Blaise Tine
2023-10-19 20:51:22 -07:00
parent d69a64c32c
commit c1e168fdbe
1309 changed files with 247412 additions and 311463 deletions

View File

@@ -1,5 +1,18 @@
#!/usr/bin/env python3
# coding=utf-8
# Copyright © 2019-2023
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
@@ -25,8 +38,7 @@ translation_rules = [
# preprocessor directives
(re.compile(r'`include\s+.*$'), r''),
(re.compile(r'`ifdef'), r'#ifdef'),
(re.compile(r'`ifndef'), r'#ifndef'),
(re.compile(r'`elif'), r'#elif'),
(re.compile(r'`ifndef'), r'#ifndef'),
(re.compile(r'`else'), r'#else'),
(re.compile(r'`define'), r'#define'),
(re.compile(r'`endif'), r'#endif'),
@@ -37,7 +49,8 @@ translation_rules = [
# literals
(re.compile(r"\d+'d(\d+)"), r'\1'),
(re.compile(r"\d+'b([01]+)"), r'0b\1'),
(re.compile(r"\d+'h([\da-fA-F]+)"), r'0x\1')
(re.compile(r"128'h([\da-fA-F_]+)"), r'"\1"'),
(re.compile(r"\d+'h([\da-fA-F]+)"), r'0x\1')
]
with open(args.output, 'w') as f:
@@ -45,8 +58,8 @@ with open(args.output, 'w') as f:
// auto-generated by gen_config.py. DO NOT EDIT
// Generated at {date}
// Translated from VX_config.vh:
'''[1:].format(date=datetime.now()), file=f)
// Translated from {input}:
'''[1:].format(date=datetime.now(), input=args.input), file=f)
with open(args.input, 'r') as r:
lineno = 0
for line in r:

129
hw/scripts/gen_sources.sh Executable file
View File

@@ -0,0 +1,129 @@
#!/bin/bash
# Copyright © 2019-2023
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
defines=()
includes=()
externs=()
output_file=""
global_file=""
copy_folder=""
prepropressor=0
defines_str=""
includes_str=""
# parse command arguments
while getopts D:I:J:O:G:C:Ph flag
do
case "${flag}" in
D) defines+=( ${OPTARG} )
defines_str+="-D${OPTARG} "
;;
I) includes+=( ${OPTARG} )
includes_str+="-I${OPTARG} "
;;
J) externs+=( ${OPTARG} )
includes_str+="-I${OPTARG} "
;;
O) output_file=( ${OPTARG} );;
G) global_file=( ${OPTARG} );;
C) copy_folder=( ${OPTARG} );;
P) prepropressor=1;;
h) echo "Usage: [-D<macro>] [-I<include-path>] [-J<external-path>] [-O<output-file>] [-C<dest-folder>: copy to] [-G<global_header>] [-P: macro prepropressing] [-h help]"
exit 0
;;
\?)
echo "Invalid option: -$OPTARG" 1>&2
exit 1
;;
esac
done
if [ "$global_file" != "" ]; then
directory=$(dirname "$global_file")
mkdir -p "$directory"
{
# dump defines into a global header
for value in ${defines[@]}; do
arrNV=(${value//=/ })
if (( ${#arrNV[@]} > 1 ));
then
echo "\`define ${arrNV[0]} ${arrNV[1]}"
else
echo "\`define $value"
fi
done
} > $global_file
fi
if [ "$copy_folder" != "" ]; then
# copy source files
mkdir -p $copy_folder
for dir in ${includes[@]}; do
find "$dir" -maxdepth 1 -type f | while read -r file; do
ext="${file##*.}"
if [ $prepropressor != 0 ] && { [ "$ext" == "v" ] || [ "$ext" == "sv" ]; }; then
verilator $defines_str $includes_str -E -P $file > $copy_folder/$(basename -- $file)
else
cp $file $copy_folder
fi
done
done
fi
if [ "$output_file" != "" ]; then
{
if [ "$global_file" == "" ]; then
# dump defines
for value in ${defines[@]}; do
echo "+define+$value"
done
fi
for dir in ${externs[@]}; do
echo "+incdir+$(realpath $dir)"
done
for dir in ${externs[@]}; do
find "$(realpath $dir)" -maxdepth 1 -type f -name "*_pkg.sv" -print
done
for dir in ${externs[@]}; do
find "$(realpath $dir)" -maxdepth 1 -type f \( -name "*.v" -o -name "*.sv" \) ! -name "*_pkg.sv" -print
done
if [ "$copy_folder" != "" ]; then
# dump include directories
echo "+incdir+$(realpath $copy_folder)"
# dump source files
find "$(realpath $copy_folder)" -maxdepth 1 -type f -name "*_pkg.sv" -print
find "$(realpath $copy_folder)" -maxdepth 1 -type f \( -name "*.v" -o -name "*.sv" \) ! -name "*_pkg.sv" -print
else
# dump include directories
for dir in ${includes[@]}; do
echo "+incdir+$(realpath $dir)"
done
# dump source files
for dir in ${includes[@]}; do
find "$(realpath $dir)" -maxdepth 1 -type f -name "*_pkg.sv" -print
done
for dir in ${includes[@]}; do
find "$(realpath $dir)" -maxdepth 1 -type f \( -name "*.v" -o -name "*.sv" \) ! -name "*_pkg.sv" -print
done
fi
} > $output_file
fi

View File

@@ -0,0 +1,46 @@
# Copyright © 2019-2023
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
proc parse_vcs_list {flist_path} {
set f [split [string trim [read [open $flist_path r]]] "\n"]
set flist [list ]
set dir_list [list ]
set def_list [list ]
foreach x $f {
if {![string match "" $x]} {
# If the item starts with +incdir+, directory files need to be added
if {[string match "#*" $x]} {
# get rid of comment line
} elseif {[string match "+incdir+*" $x]} {
set trimchars "+incdir+"
set temp [string trimleft $x $trimchars]
set expanded [subst $temp]
lappend dir_list $expanded
} elseif {[string match "+define+*" $x]} {
set trimchars "+define+"
set temp [string trimleft $x $trimchars]
set expanded [subst $temp]
lappend def_list $expanded
} else {
set expanded [subst $x]
lappend flist $expanded
}
}
}
#puts $flist
#puts $dir_list
#puts $def_list
return [list $flist $dir_list $def_list]
}

View File

@@ -1,224 +0,0 @@
{
"version": 1,
"include_paths":[
"../dpi",
"../rtl",
"../rtl/afu",
"../rtl/cache",
"../rtl/fp_cores",
"../rtl/interfaces",
"../rtl/libs"
],
"includes":[
"../rtl/VX_config.vh",
"../rtl/VX_platform.vh",
"../rtl/VX_define.vh",
"../rtl/VX_gpu_types.vh",
"../rtl/fp_cores/VX_fpu_types.vh",
"../rtl/fp_cores/VX_fpu_define.vh",
"../rtl/cache/VX_cache_define.vh"
],
"modules": {
"afu": {
"submodules": {
"vortex": {"type":"Vortex", "enabled":true}
}
},
"Vortex": {
"submodules": {
"cluster": {"type":"VX_cluster", "count":"`NUM_CLUSTERS"},
"l3cache": {"type":"VX_cache", "enabled":"`L3_ENABLE", "params":{"NUM_BANKS":"`L3NUM_BANKS"}}
}
},
"VX_cluster": {
"submodules": {
"core": {"type":"VX_core", "count":"`NUM_CORES", "enabled":true},
"l2cache": {"type":"VX_cache", "enabled":"`L2_ENABLE", "params":{"NUM_BANKS":"`L2NUM_BANKS"}}
}
},
"VX_core": {
"submodules": {
"pipeline": {"type":"VX_pipeline", "enabled":true},
"mem_unit": {"type":"VX_mem_unit", "enabled":true}
}
},
"VX_pipeline": {
"submodules": {
"fetch": {"type":"VX_fetch", "enabled":true},
"decode": {"type":"VX_decode", "enabled":true},
"issue": {"type":"VX_issue", "enabled":true},
"execute": {"type":"VX_execute", "enabled":true},
"commit": {"type":"VX_commit", "enabled":true}
}
},
"VX_fetch": {
"submodules": {
"warp_sched": {"type":"VX_warp_sched"},
"icache_stage": {"type":"VX_icache_stage"}
}
},
"VX_warp_sched": {},
"VX_icache_stage": {},
"VX_decode": {},
"VX_issue": {},
"VX_execute": {
"submodules": {
"lsu_unit": {"type":"VX_lsu_unit"},
"gpu_unit": {"type":"VX_gpu_unit"}
}
},
"VX_commit": {},
"VX_lsu_unit": {},
"VX_gpu_unit": {},
"VX_mem_unit": {
"submodules": {
"dcache": {"type":"VX_cache", "params":{"NUM_BANKS":"`DCACHE_NUM_BANKS"}},
"icache": {"type":"VX_cache", "params":{"NUM_BANKS":"1"}}
}
},
"VX_cache": {
"submodules": {
"bank": {"type":"VX_bank", "count":"NUM_BANKS"}
}
},
"VX_bank": {}
},
"taps": {
"afu": {
"!cmd_type":3,
"!state":2,
"?cci_sRxPort_c0_mmioRdValid":1,
"?cci_sRxPort_c0_mmioWrValid":1,
"mmio_hdr_address":16,
"mmio_hdr_length":2,
"cci_sRxPort_c0_hdr_mdata":16,
"?cci_sRxPort_c0_rspValid":1,
"?cci_sRxPort_c1_rspValid":1,
"?cci_sTxPort_c0_valid":1,
"cci_sTxPort_c0_hdr_address":42,
"cci_sTxPort_c0_hdr_mdata":16,
"?cci_sTxPort_c1_valid":1,
"cci_sTxPort_c1_hdr_address":42,
"cci_sTxPort_c2_mmioRdValid":1,
"!cci_sRxPort_c0TxAlmFull":1,
"!cci_sRxPort_c1TxAlmFull":1,
"avs_address":26,
"!avs_waitrequest":1,
"?avs_write_fire":1,
"?avs_read_fire":1,
"avs_byteenable":64,
"avs_burstcount":4,
"avs_readdatavalid":1,
"cci_mem_rd_req_ctr":26,
"cci_mem_wr_req_ctr":26,
"cci_rd_req_ctr":26,
"cci_rd_rsp_ctr":3,
"cci_wr_req_ctr":26,
"?cci_wr_req_fire":1,
"?cci_wr_rsp_fire":1,
"?cci_rd_req_fire":1,
"?cci_rd_rsp_fire":1,
"!cci_pending_reads_full":1,
"!cci_pending_writes_empty":1,
"!cci_pending_writes_full": 1,
"?afu_mem_req_fire": 1,
"afu_mem_req_addr": 26,
"afu_mem_req_tag": "`VX_MEM_TAG_WIDTH+1",
"?afu_mem_rsp_fire": 1,
"afu_mem_rsp_tag": "`VX_MEM_TAG_WIDTH+1"
},
"afu/vortex": {
"!reset": 1,
"?mem_req_fire": 1,
"mem_req_addr": 32,
"mem_req_rw": 1,
"mem_req_byteen":"`VX_MEM_BYTEEN_WIDTH",
"mem_req_data":"`VX_MEM_DATA_WIDTH",
"mem_req_tag":"`VX_MEM_TAG_WIDTH",
"?mem_rsp_fire": 1,
"mem_rsp_data":"`VX_MEM_DATA_WIDTH",
"mem_rsp_tag":"`VX_MEM_TAG_WIDTH",
"busy": 1
},
"afu/vortex/cluster/core/pipeline/fetch/warp_sched": {
"?wsched_scheduled": 1,
"wsched_schedule_uuid": "`UUID_BITS",
"wsched_active_warps": "`NUM_WARPS",
"wsched_stalled_warps": "`NUM_WARPS",
"wsched_schedule_tmask": "`NUM_THREADS",
"wsched_schedule_wid": "`NW_BITS",
"wsched_schedule_pc": 32
},
"afu/vortex/cluster/core/pipeline/fetch/icache_stage": {
"?icache_req_fire": 1,
"icache_req_uuid": "`UUID_BITS",
"icache_req_addr": 32,
"icache_req_tag":"`ICACHE_CORE_TAG_ID_BITS",
"?icache_rsp_fire": 1,
"icache_rsp_uuid": "`UUID_BITS",
"icache_rsp_data": 32,
"icache_rsp_tag":"`ICACHE_CORE_TAG_ID_BITS"
},
"afu/vortex/cluster/core/pipeline/issue": {
"?issue_fire": 1,
"issue_uuid": "`UUID_BITS",
"issue_tmask":"`NUM_THREADS",
"issue_ex_type":"`EX_BITS",
"issue_op_type":"`INST_OP_BITS",
"issue_op_mod":"`INST_MOD_BITS",
"issue_wb": 1,
"issue_rd":"`NR_BITS",
"issue_rs1":"`NR_BITS",
"issue_rs2":"`NR_BITS",
"issue_rs3":"`NR_BITS",
"issue_imm": 32,
"issue_use_pc": 1,
"issue_use_imm": 1,
"gpr_rs1":"`NUM_THREADS * 32",
"gpr_rs2":"`NUM_THREADS * 32",
"gpr_rs3":"`NUM_THREADS * 32",
"?writeback_valid": 1,
"writeback_uuid": "`UUID_BITS",
"writeback_tmask":"`NUM_THREADS",
"writeback_rd":"`NR_BITS",
"writeback_data":"`NUM_THREADS * 32",
"writeback_eop": 1,
"!scoreboard_delay": 1,
"!dispatch_delay": 1
},
"afu/vortex/cluster/core/pipeline/execute/lsu_unit": {
"?dcache_req_fire":"`NUM_THREADS",
"dcache_req_uuid": "`UUID_BITS",
"dcache_req_addr":"`NUM_THREADS * 32",
"dcache_req_rw": 1,
"dcache_req_byteen":"`NUM_THREADS * 4",
"dcache_req_data":"`NUM_THREADS * 32",
"dcache_req_tag":"`LSUQ_ADDR_BITS",
"?dcache_rsp_fire":"`NUM_THREADS",
"dcache_rsp_uuid": "`UUID_BITS",
"dcache_rsp_data":"`NUM_THREADS * 32",
"dcache_rsp_tag":"`LSUQ_ADDR_BITS"
},
"afu/vortex/cluster/core/pipeline/execute/gpu_unit": {
"?gpu_rsp_valid": 1,
"gpu_rsp_uuid": "`UUID_BITS",
"gpu_rsp_tmc": 1,
"gpu_rsp_wspawn": 1,
"gpu_rsp_split": 1,
"gpu_rsp_barrier": 1
},
"afu/vortex/l3cache/bank, afu/vortex/cluster/l2cache/bank, afu/vortex/cluster/core/mem_unit/dcache/bank, afu/vortex/cluster/core/mem_unit/icache/bank": {
"?valid_st0": 1,
"?valid_st1": 1,
"addr_st0": 32,
"addr_st1": 32,
"is_fill_st0": 1,
"is_mshr_st0": 1,
"miss_st0": 1,
"?crsq_stall": 1,
"?mreq_alm_full": 1,
"?mshr_alm_full": 1
}
}
}

View File

@@ -1,829 +1,177 @@
#!/usr/bin/env python3
import os
# Copyright © 2019-2023
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import argparse
import xml.etree.ElementTree as ET
import re
import json
import argparse
import math
vl_include_re = re.compile(r"^\s*`include\s+\"(.+)\"")
vl_define_re = re.compile(r"^\s*`define\s+(\w+)(\([\w\s,]*\))?(.*)")
vl_ifdef_re = re.compile(r"^\s*`(ifdef|ifndef|elsif)\s+(\w+)\s*$")
vl_endif_re = re.compile(r"^\s*`(endif|else)\s*$")
vl_expand_re = re.compile(r"`([0-9a-zA-Z_]+)")
vl_int_re = re.compile(r"\d+'s*h([\da-fA-F]+)")
exclude_files = []
include_dirs = []
macros = []
br_stack = []
def parse_vl_int(text):
str_hex = re.sub(vl_int_re, r'\1', text)
return int(str_hex, 16)
def translate_ternary(text):
def source_loc(xml_doc, xml_loc):
loc = xml_loc.split(",")
file_id = loc[0]
start_line = loc[1]
start_col = loc[2]
end_line = loc[3]
end_col = loc[4]
file = xml_doc.find(".//file/[@id='" + file_id + "']").get("filename")
return file + " (" + start_line + ":" + start_col + "-" + end_line + ":" + end_col + ")"
def skip_space(text, i, ln, step):
while (i >= 0) and (i < ln):
c = text[i]
if not c.isspace():
break
i += step
return i
def skip_expr(text, i, ln, step):
paren = 0
checkparen = True
while (i >= 0) and (i < ln):
c = text[i]
if checkparen and (((step < 0) and (c == ')')) or ((step > 0) and (c == '('))):
paren += 1
elif checkparen and (((step < 0) and (c == '(')) or ((step > 0) and (c == ')'))):
if (0 == paren):
break
paren -= 1
if (0 == paren):
i = skip_space(text, i + step, ln, step)
checkparen = False
continue
elif (0 == paren) and not (c.isalnum() or (c == '_')):
break
i += step
return (i - step)
def parse_ternary(text):
ternary = None
ln = len(text)
for i in range(1, ln):
c = text[i]
if not (c == '?'):
continue
# parse condition expression
i0 = skip_space(text, i - 1, ln, -1)
if (i < 0):
raise Exception("invalid condition expression")
i1 = skip_expr(text, i0, ln, -1)
if (i1 > i0):
raise Exception("invalid condition expression")
# parse true expression
i2 = skip_space(text, i + 1, ln, 1)
if (i2 >= ln):
raise Exception("invalid true expression")
i3 = skip_expr(text, i2, ln, 1)
if (i3 < i2):
raise Exception("invalid true expression")
# parse colon
i4 = skip_space(text, i3 + 1, ln, 1)
if (i4 >= ln):
raise Exception("invalid colon")
if not (text[i4] == ':'):
raise Exception("missing colon")
# parse false expression
i5 = skip_space(text, i4 + 1, ln, 1)
if (i5 >= ln):
raise Exception("invalid false expression")
i6 = skip_expr(text, i5, ln, 1)
if (i6 < i5):
raise Exception("invalid false expression")
ternary = (i0, i1, i2, i3, i5, i6)
break
return ternary
while True:
pos = parse_ternary(text)
if pos is None:
break
# convert to python ternary
newText = text[:pos[1]] + text[pos[2]:pos[3]+1] + " if " + text[pos[1]:pos[0]+1] + " else " + text[pos[4]:pos[5]+1] + text[pos[5]+1:]
text = newText
return text
def parse_func_args(text):
args = []
arg = ''
l = len(text)
if text[0] != '(':
raise Exception("missing leading parenthesis: " + text)
paren = 1
for i in range(1, l):
c = text[i]
if c == '(':
paren += 1
elif c == ')':
if paren == 0:
raise Exception("mismatched parenthesis: (" + i + ") " + text)
paren -= 1
if paren == 0:
l = i
break
if c == ',' and paren == 1:
if arg.strip():
args.append(arg)
arg = ''
else:
arg += c
if paren != 0:
raise Exception("missing closing parenthesis: " + text)
if arg.strip():
args.append(arg)
return (args, l)
def load_include_path(dir):
if not dir in include_dirs:
print("*** include path: " + dir)
include_dirs.append(dir)
def resolve_include_path(filename, parent_dir):
if os.path.basename(filename) in exclude_files:
return None
if os.path.isfile(filename):
return os.path.abspath(filename)
search_dirs = include_dirs
if parent_dir:
search_dirs.append(parent_dir)
for dir in search_dirs:
filepath = os.path.join(dir, filename)
if os.path.isfile(filepath):
return os.path.abspath(filepath)
raise Exception("couldn't find include file: " + filename + " in " + parent_dir)
def remove_comments(text):
text = re.sub(re.compile("/\*.*?\*/",re.DOTALL ), "", text) # multiline
text = re.sub(re.compile("//.*?\n" ), "\n", text) # singleline
return text
def add_macro(name, args, value):
macro = (name, args, value)
macros.append(macro)
'''
if not args is None:
print("*** token: " + name + "(", end='')
for i in range(len(args)):
if i > 0:
print(', ', end='')
print(args[i], end='')
print(")=" + value)
def parse_dtype_width(xml_doc, dtype_id):
xml_type = xml_doc.find(".//typetable/*[@id='" + dtype_id + "']")
if xml_type.tag == "packarraydtype" or xml_type.tag == "unpackarraydtype":
sub_dtype_id = xml_type.get("sub_dtype_id")
base_width = parse_dtype_width(xml_doc, sub_dtype_id)
const = xml_type.iter("const")
left = parse_vl_int(next(const).get("name"))
right = parse_vl_int(next(const).get("name"))
return base_width * (left - right + 1)
elif xml_type.tag == "structdtype":
width = 0
for member in xml_type.iter("memberdtype"):
sub_dtype_id = member.get("sub_dtype_id")
width = width + parse_dtype_width(xml_doc, sub_dtype_id)
return width
elif xml_type.tag == "uniondtype":
width = 0
for member in xml_type.iter("memberdtype"):
sub_dtype_id = member.get("sub_dtype_id")
width = max(width, parse_dtype_width(xml_doc, sub_dtype_id))
return width
else:
print("*** token: " + name + "=" + value)
'''
def find_macro(name):
for macro in macros:
if macro[0] == name:
return macro
return None
def expand_text(text, params):
def re_pattern_args(args):
p = "(?<![0-9a-zA-Z_])("
i = 0
for arg in args:
if i > 0:
p += "|"
p += arg
i += 1
p += ")(?![0-9a-zA-Z_])"
return p
class DoReplParam(object):
def __init__(self, params):
self.params = params
self.expanded = False
def __call__(self, match):
name = match.group(1)
self.expanded = True
return self.params[name]
class DoReplMacro(object):
def __init__(self):
self.expanded = False
self.has_func = False
def __call__(self, match):
name = match.group(1)
macro = find_macro(name)
if macro:
if not macro[1] is None:
self.has_func = True
else:
self.expanded = True
return macro[2]
return "`" + name
def repl_func_macro(text):
expanded = False
match = re.search(vl_expand_re, text)
if match:
name = match.group(1)
macro = find_macro(name)
if macro:
args = macro[1]
value = macro[2]
if not args is None:
str_args = text[match.end():].strip()
f_args = parse_func_args(str_args)
if len(args) == 0:
if len(f_args[0]) != 0:
raise Exception("invalid argments for macro '" + name + "': value=" + text)
else:
if len(args) != len(f_args[0]):
raise Exception("mismatch number of argments for macro '" + name + "': actual=" + len(f_args[0]) + ", expected=" + len(args))
pattern = re_pattern_args(args)
params = {}
for i in range(len(args)):
params[args[i]] = f_args[0][i]
dorepl = DoReplParam(params)
value = re.sub(pattern, dorepl, value)
str_head = text[0:match.start()]
str_tail = text[match.end() + f_args[1]+1:]
text = str_head + value + str_tail
expanded = True
if expanded:
return text
return None
changed = False
iter = 0
while True:
if iter > 65536:
raise Exception("Macro recursion!")
has_func = False
while True:
params_updated = False
if not params is None:
do_repl = DoReplParam(params)
pattern = re_pattern_args(params)
new_text = re.sub(pattern, do_repl, text)
if do_repl.expanded:
text = new_text
params_updated = True
do_repl = DoReplMacro()
new_text = re.sub(vl_expand_re, do_repl, text)
has_func = do_repl.has_func
if not (params_updated or do_repl.expanded):
break
text = new_text
changed = True
if not has_func:
break
expanded = repl_func_macro(text)
if not expanded:
break
text = expanded
changed = True
iter += 1
if changed:
return text
return None
def parse_include(filename, nesting):
print("*** parsing: " + filename + "...")
if nesting > 99:
raise Exception("include recursion!")
#print("*** parsing '" + filename + "'...")
content = None
with open(filename, "r") as f:
content = f.read()
# remove comments
content = remove_comments(content)
# parse content
prev_line = None
for line in content.splitlines(False):
# skip empty lines
if re.match(re.compile(r'^\s*$'), line):
continue
# merge multi-line lines
if line.endswith('\\'):
if prev_line:
prev_line += line[:len(line) - 1]
else:
prev_line = line[:len(line) - 1]
continue
if prev_line:
line = prev_line + line
prev_line = None
# parse ifdef
m = re.match(vl_ifdef_re, line)
if m:
key = m.group(1)
cond = m.group(2)
taken = find_macro(cond) is not None
if key == 'ifndef':
taken = not taken
elif key == '"elsif':
br_stack.pop()
br_stack.append(taken)
#print("*** " + key + "(" + cond + ") => " + str(taken))
continue
# parse endif
m = re.match(vl_endif_re, line)
if m:
key = m.group(1)
top = br_stack.pop()
if key == 'else':
br_stack.append(not top)
#print("*** " + key)
continue
# skip disabled blocks
if not all(br_stack):
continue
sub_dtype_id = xml_type.get("sub_dtype_id")
if sub_dtype_id != None:
return parse_dtype_width(xml_doc, sub_dtype_id)
left = xml_type.get("left")
right = xml_type.get("right")
if left != None and right != None:
return int(left) - int(right) + 1
return 1
# parse include
m = re.match(vl_include_re, line)
if m:
include = m.group(1)
include = resolve_include_path(include, os.path.dirname(filename))
if include:
parse_include(include, nesting + 1)
continue
# parse define
m = re.match(vl_define_re, line)
if m:
name = m.group(1)
args = m.group(2)
if args:
args = args[1:len(args)-1].strip()
if args != '':
args = args.split(',')
for i in range(len(args)):
args[i] = args[i].strip()
else:
args = []
value = m.group(3)
add_macro(name, args, value.strip())
def parse_var_name(xml_doc, xml_node):
if xml_node.tag == "varref":
return xml_node.get("name")
elif xml_node.tag == "varxref":
name = xml_node.get("name")
dotted = xml_node.get("dotted")
return dotted + '.' + name
else:
raise ET.ParseError("invalid probe entry" + source_loc(xml_doc, xml_node.get("loc")))
return name
def parse_sel_name(xml_doc, xml_node):
name = parse_var_name(xml_doc, xml_node.find("*"))
const = xml_node.iter("const")
offset = parse_vl_int(next(const).get("name"))
#size = parse_vl_int(next(const).get("name"))
return name + '_' + str(offset)
def parse_array_name(xml_doc, xml_node):
if xml_node.tag == "arraysel":
name = parse_array_name(xml_doc, xml_node.find("*"))
xml_size = xml_node.find("const").get("name")
array_size = parse_vl_int(xml_size)
name = name + '_' + str(array_size)
else:
name = parse_var_name(xml_doc, xml_node)
return name
def parse_vl_port(xml_doc, xml_node, signals):
total_width = 0
if xml_node.tag == "concat":
for xml_child in xml_node.findall("*"):
total_width = total_width + parse_vl_port(xml_doc, xml_child, signals)
elif xml_node.tag == "varref" or xml_node.tag == "varxref":
name = parse_var_name(xml_doc, xml_node)
dtype_id = xml_node.get("dtype_id")
signal_width = parse_dtype_width(xml_doc, dtype_id)
signals.append([name, signal_width])
total_width = total_width + signal_width
elif xml_node.tag == "sel":
name = parse_sel_name(xml_doc, xml_node)
dtype_id = xml_node.get("dtype_id")
signal_width = parse_dtype_width(xml_doc, dtype_id)
signals.append([name, signal_width])
total_width = total_width + signal_width
elif xml_node.tag == "arraysel":
name = parse_array_name(xml_doc, xml_node)
dtype_id = xml_node.get("dtype_id")
signal_width = parse_dtype_width(xml_doc, dtype_id)
signals.append([name, signal_width])
total_width = total_width + signal_width
else:
raise ET.ParseError("invalid probe entry: " + source_loc(xml_doc, xml_node.get("loc")))
return total_width
def parse_xml(filename, max_taps):
xml_doc = ET.parse(filename)
modules = {}
xml_modules = xml_doc.findall(".//module/[@origName='VX_scope_tap']")
for xml_module in xml_modules:
scope_id = parse_vl_int(xml_module.find(".//var/[@name='SCOPE_ID']/const").get("name"))
triggerw = parse_vl_int(xml_module.find(".//var/[@name='TRIGGERW']/const").get("name"))
probew = parse_vl_int(xml_module.find(".//var/[@name='PROBEW']/const").get("name"))
module_name = xml_module.get("name")
modules[module_name] = [scope_id, triggerw, probew]
taps = []
xml_instances = xml_doc.iter("instance")
for xml_instance in xml_instances:
if (max_taps != -1 and len(taps) >= max_taps):
break
defName = xml_instance.get("defName")
module = modules.get(defName)
if module is None:
continue
triggers = []
probes = []
w = parse_vl_port(xml_doc, xml_instance.find(".//port/[@name='triggers']/*"), triggers)
if w != module[1]:
raise ET.ParseError("invalid triggers width: actual=" + str(w) + ", expected=" + str(module[1]))
w = parse_vl_port(xml_doc, xml_instance.find(".//port/[@name='probes']/*"), probes)
if w != module[2]:
raise ET.ParseError("invalid probes width: actual=" + str(w) + ", expected=" + str(module[2]))
signals = probes
for trigger in triggers:
signals.append(trigger)
loc = xml_instance.get("loc")
hier = xml_doc.find(".//cell/[@loc='" + loc + "']").get("hier")
path = hier.rsplit(".", 1)[0]
taps.append({"id":module[0],
"width":module[1] + module[2],
"signals":signals,
"path":path})
def parse_includes(includes):
# change current directory to include directory
old_dir = os.getcwd()
script_dir = os.path.dirname(os.path.realpath(__file__))
os.chdir(script_dir)
for include in includes:
parse_include(include, 0)
load_include_path(os.path.dirname(include))
# restore current directory
os.chdir(old_dir)
def load_defines(defines):
for define in defines:
key_value = define.split('=', 2)
name = key_value[0]
value = ''
if len(key_value) == 2:
value = key_value[1]
add_macro(name, None, value)
def load_config(filename):
with open(filename, "r") as f:
config = json.load(f)
print("condfig=", config)
return config
def eval_node(text, params):
def clog2(x):
l2 = math.log2(x)
cl = math.ceil(l2)
return int(cl)
if not type(text) == str:
return text
expanded = expand_text(text, params)
if expanded:
text = expanded
try:
__text = text.replace('$clog2', '__clog2')
__text = translate_ternary(__text)
__text = __text.replace('||', 'or')
__text = __text.replace('&&', 'and')
e = eval(__text, {'__clog2': clog2})
return e
except (NameError, SyntaxError):
return text
def gen_vl_header(file, modules, taps):
header = '''
`ifndef VX_SCOPE_DEFS
`define VX_SCOPE_DEFS
'''
footer = '`endif'
def signal_size(size, mn):
if type(size) == int:
if (size != mn):
return "[" + str(size-1) + ":0]"
else:
return ""
else:
return "[" + size + "-1:0]"
def create_signal(key, ports):
if not key in ports:
ports[key] = []
return ports[key]
def dic_insert(gdic, ldic, key, value, enabled):
if enabled:
ldic[key] = value
if key in gdic:
return False
if enabled:
gdic[key] = None
return True
def trigger_name(name, size):
if type(size) == int:
if size != 1:
return "(| " + name + ")"
else:
return name
else:
return "(| " + name + ")"
def trigger_subscripts(asize):
def Q(arr, ss, asize, idx, N):
a = asize[idx]
if (a != 0):
for i in range(a):
tmp = ss + '[' + str(i) + ']'
if (idx + 1) < N:
Q(arr, tmp, asize, idx + 1, N)
else:
arr.append(tmp)
else:
if (idx + 1) < N:
Q(arr, ss, asize, idx + 1, N)
else:
arr.append(ss)
if asize is None:
return [""]
ln = len(asize)
if (0 == ln):
return [""]
arr = []
Q(arr, "", asize, 0, ln)
return arr
def visit_path(alltaps, ports, ntype, paths, modules, taps):
curtaps = {}
if (len(paths) != 0):
spath = paths.pop(0)
snodes = modules[ntype]["submodules"]
if not spath in snodes:
raise Exception("invalid path: " + spath + " in " + ntype)
snode = snodes[spath]
stype = snode["type"]
enabled = True
if "enabled" in snode:
enabled = eval_node(snode["enabled"], None)
subtaps = visit_path(alltaps, ports, stype, paths, modules, taps)
scount = 0
if "count" in snode:
scount = eval_node(snode["count"], None)
params = None
if "params" in snode:
params = snode["params"]
new_staps = []
nn = "SCOPE_IO_" + ntype
pp = create_signal(nn, ports)
for key in subtaps:
subtap = subtaps[key]
s = subtap[0]
a = subtap[1]
t = subtap[2]
aa = [scount]
sa = signal_size(scount, 0)
if a:
for i in a:
x = eval_node(i, params)
aa.append(x)
sa += signal_size(x, 0)
if dic_insert(alltaps, curtaps, spath + '/' + key, (s, aa, t), enabled):
skey = key.replace('/', '_')
if enabled:
pp.append("\toutput wire" + sa + signal_size(s, 1) + " scope_" + spath + '_' + skey + ',')
new_staps.append(skey)
ports[nn] = pp
if (0 == scount):
nn = "SCOPE_BIND_" + ntype + '_' + spath
pp = create_signal(nn, ports)
for st in new_staps:
if enabled:
pp.append("\t.scope_" + st + "(scope_" + spath + '_' + st + "),")
else:
pp.append("\t`UNUSED_PIN (scope_" + st + "),")
ports[nn] = pp
else:
nn = "SCOPE_BIND_" + ntype + '_' + spath + "(__i__)"
pp = create_signal(nn, ports)
for st in new_staps:
if enabled:
pp.append("\t.scope_" + st + "(scope_" + spath + '_' + st + "[__i__]),")
else:
pp.append("\t`UNUSED_PIN (scope_" + st + "),")
ports[nn] = pp
else:
nn = "SCOPE_IO_" + ntype
pp = create_signal(nn, ports)
for tk in taps:
trigger = 0
name = tk
size = eval_node(taps[tk], None)
if name[0] == '!':
name = name[1:]
trigger = 1
elif name[0] == '?':
name = name[1:]
trigger = 2
if dic_insert(alltaps, curtaps, name, (size, None, trigger), True):
pp.append("\toutput wire" + signal_size(size, 1) + " scope_" + name + ',')
ports[nn] = pp
return curtaps
toptaps = {}
with open(file, 'w') as f:
ports = {}
alltaps = {}
for key in taps:
skey_list = key.split(',')
_taps = taps[key]
for skey in skey_list:
#print('*** processing node: ' + skey + ' ...')
paths = skey.strip().split('/')
ntype = paths.pop(0)
curtaps = visit_path(alltaps, ports, ntype, paths, modules, _taps)
for tk in curtaps:
toptaps[tk] = curtaps[tk]
print(header, file=f)
for key in ports:
print("`define " + key + ' \\', file=f)
for port in ports[key]:
print(port + ' \\', file=f)
print("", file=f)
print("`define SCOPE_DECL_SIGNALS \\", file=f)
i = 0
for key in toptaps:
tap = toptaps[key]
name = key.replace('/', '_')
size = tap[0]
asize = tap[1]
sa = ""
if asize:
for a in asize:
sa += signal_size(a, 0)
if i > 0:
print(" \\", file=f)
print('\t wire' + sa + signal_size(size, 1) + " scope_" + name + ';', file=f, end='')
i += 1
print("", file=f)
print("", file=f)
print("`define SCOPE_DATA_LIST \\", file=f)
i = 0
for key in toptaps:
tap = toptaps[key]
trigger = tap[2]
if trigger != 0:
continue
name = key.replace('/', '_')
if i > 0:
print(", \\", file=f)
print("\t scope_" + name, file=f, end='')
i += 1
print("", file=f)
print("", file=f)
print("`define SCOPE_UPDATE_LIST \\", file=f)
i = 0
for key in toptaps:
tap = toptaps[key]
trigger = tap[2]
if trigger == 0:
continue
name = key.replace('/', '_')
if i > 0:
print(", \\", file=f)
print("\t scope_" + name, file=f, end='')
i += 1
print("", file=f)
print("", file=f)
print("`define SCOPE_TRIGGER \\", file=f)
i = 0
for key in toptaps:
tap = toptaps[key]
if tap[2] != 2:
continue
size = tap[0]
asize = tap[1]
sus = trigger_subscripts(asize)
for su in sus:
if i > 0:
print(" | \\", file=f)
print("\t(", file=f, end='')
name = trigger_name("scope_" + key.replace('/', '_') + su, size)
print(name, file=f, end='')
print(")", file=f, end='')
i += 1
print("", file=f)
print("", file=f)
print(footer, file=f)
return toptaps
def gen_cc_header(file, taps):
header = '''
#pragma once
struct scope_module_t {
const char* name;
int index;
int parent;
};
struct scope_tap_t {
int width;
const char* name;
int module;
};
'''
def flatten_path(paths, sizes):
def Q(arr, ss, idx, N, paths, sizes):
size = sizes[idx]
if size != 0:
for i in range(sizes[idx]):
tmp = ss + ('/' if (ss != '') else '')
tmp += paths[idx] + '_' + str(i)
if (idx + 1) < N:
Q(arr, tmp, idx + 1, N, paths, sizes)
else:
arr.append(tmp)
else:
tmp = ss + ('/' if (ss != '') else '')
tmp += paths[idx]
if (idx + 1) < N:
Q(arr, tmp, idx + 1, N, paths, sizes)
else:
arr.append(tmp)
arr = []
Q(arr, "", 0, len(asize), paths, asize)
return arr
# flatten the taps
fdic = {}
for key in taps:
tap = taps[key]
size = str(tap[0])
trigger = tap[2]
if (trigger != 0):
continue
paths = key.split('/')
if (len(paths) > 1):
name = paths.pop(-1)
asize = tap[1]
for ss in flatten_path(paths, asize):
fdic[ss + '/' + name ] = [size, 0]
else:
fdic[key] = [size, 0]
for key in taps:
tap = taps[key]
size = str(tap[0])
trigger = tap[2]
if (trigger == 0):
continue
paths = key.split('/')
if (len(paths) > 1):
name = paths.pop(-1)
asize = tap[1]
for ss in flatten_path(paths, asize):
fdic[ss + '/' + name ] = [size, 0]
else:
fdic[key] = [size, 0]
# generate module dic
mdic = {}
mdic["*"] = ("*", 0, -1)
for key in fdic:
paths = key.split('/')
if len(paths) == 1:
continue
paths.pop(-1)
parent = 0
mk = ""
for path in paths:
mk += '/' + path
if not mk in mdic:
index = len(mdic)
mdic[mk] = (path, index, parent)
parent = index
else:
parent = mdic[mk][1]
fdic[key][1] = parent
with open(file, 'w') as f:
print(header, file=f)
print("static constexpr scope_module_t scope_modules[] = {", file=f)
i = 0
for key in mdic:
m = mdic[key]
if i > 0:
print(',', file=f)
print("\t{\"" + m[0] + "\", " + str(m[1]) + ", " + str(m[2]) + "}", file=f, end='')
i += 1
print("", file=f)
print("};", file=f)
print("", file=f)
print("static constexpr scope_tap_t scope_taps[] = {", file=f)
i = 0
for key in fdic:
size = fdic[key][0]
parent = fdic[key][1]
paths = key.split('/')
if len(paths) > 1:
name = paths.pop(-1)
else:
name = key
if i > 0:
print(',', file=f)
print("\t{" + size + ", \"" + name + "\", " + str(parent) + "}", file=f, end='')
i += 1
print("", file=f)
print("};", file=f)
return {"version":"0.1.0", "taps":taps}
def main():
parser = argparse.ArgumentParser(description='Scope headers generator.')
parser.add_argument('-vl', nargs='?', default='scope-defs.vh', metavar='file', help='Output Verilog header')
parser.add_argument('-cc', nargs='?', default='scope-defs.h', metavar='file', help='Output C++ header')
parser.add_argument('-D', nargs='?', action='append', metavar='macro[=value]', help='define macro')
parser.add_argument('-I', nargs='?', action='append', metavar='<includedir>', help='include directory')
parser.add_argument('config', help='Json config file')
parser.add_argument('-o', nargs='?', default='scope.json', metavar='o', help='Output JSON manifest')
parser.add_argument('-n', nargs='?', default=-1, metavar='n', type=int, help='Maximum number of taps to read')
parser.add_argument('xml', help='Design XML descriptor file')
args = parser.parse_args()
print("args=", args)
global exclude_files
global include_dirs
global macros
global br_stack
if args.I:
for dir in args.I:
load_include_path(dir)
if args.D:
load_defines(args.D)
config = load_config(args.config)
exclude_files.append(os.path.basename(args.vl))
if "include_paths" in config:
for path in config["include_paths"]:
load_include_path(path)
if "includes" in config:
parse_includes(config["includes"])
taps = gen_vl_header(args.vl, config["modules"], config["taps"])
gen_cc_header(args.cc, taps)
#print("args=", args)
scope_taps = parse_xml(args.xml, args.n)
with open(args.o, "w") as f:
json.dump(scope_taps, f, ensure_ascii=False, indent=4)
if __name__ == '__main__':
main()
main()

73
hw/scripts/sv2v.sh Executable file
View File

@@ -0,0 +1,73 @@
#!/bin/bash
# Copyright © 2019-2023
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# this script uses sv2v and yosys tools to run.
# sv2v: https://github.com/zachjs/sv2v
# yosys: http://www.clifford.at/yosys/
# exit when any command fails
set -e
source=""
includes=()
macro_args=""
output_file=out.v
usage() { echo "$0 usage:" && grep " .)\ #" $0; exit 0; }
[ $# -eq 0 ] && usage
while getopts "t:s:o:I:D:h" arg; do
case $arg in
t) # source
top=--top=${OPTARG}
;;
s) # source
source=${OPTARG}
;;
o) # output-file
output_file=${OPTARG}
;;
I) # include directory
includes+=(${OPTARG})
;;
D) # macro definition
macro_args="$macro_args -D${OPTARG}"
;;
h | *)
usage
exit 0
;;
esac
done
# process include paths
inc_args=""
for dir in "${includes[@]}"
do
inc_args="$inc_args -I$dir"
done
# process source files
file_args=$source
for dir in "${includes[@]}"
do
for file in $(find $dir -maxdepth 1 -name '*.v' -o -name '*.sv' -type f)
do
echo "file: $file"
file_args="$file_args $file"
done
done
# system-verilog to verilog conversion
sv2v $top $macro_args $inc_args $file_args -v -w $output_file