Vortex 2.0 changes:

+ Microarchitecture optimizations
+ 64-bit support
+ Xilinx FPGA support
+ LLVM-16 support
+ Refactoring and quality control fixes
This commit is contained in:
Blaise Tine
2023-10-19 20:51:22 -07:00
parent d69a64c32c
commit d47cccc157
1300 changed files with 247321 additions and 311189 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