Minitest for different IOSTANDARDs

Signed-off-by: Maciej Kurc <mkurc@antmicro.com>
This commit is contained in:
Maciej Kurc 2019-11-29 13:54:11 +01:00
parent da33c29914
commit e8b05c6b27
7 changed files with 815 additions and 0 deletions

View File

@ -0,0 +1,29 @@
proc dump_iobs {file_name} {
set fp [open $file_name w]
puts $fp "tile,site_name,site_type,clock_region,bank,is_bonded,is_clock,is_global_clock,is_vref"
foreach tile [get_tiles *IOB33*] {
foreach site [get_sites -of_objects $tile] {
set site_type [get_property SITE_TYPE $site]
set clock_region [get_property CLOCK_REGION $site]
set is_bonded [get_property IS_BONDED $site]
set pin [get_package_pins -of_objects $site]
set bank [get_property BANK $pin]
set is_clock [get_property IS_CLK_CAPABLE $pin]
set is_global_clock [get_property IS_GLOBAL_CLK $pin]
set is_vref [get_property IS_VREF $pin]
puts $fp "$tile,$site,$site_type,$clock_region,$bank,$is_bonded,$is_clock,$is_global_clock,$is_vref"
}
}
close $fp
}
create_project -force -in_memory -name dump_iobs -part $::env(VIVADO_PART)
set_property design_mode PinPlanning [current_fileset]
open_io_design -name io_1
dump_iobs "iobs-$::env(VIVADO_PART).csv"

View File

@ -0,0 +1,74 @@
PART=xc7a50tfgg484-1
VIVADO_PART=xc7a50tfgg484-1
BIT2FASM_ARGS= --part "$(XRAY_DIR)/database/artix7/$(PART)" --verbose
DESIGN_FILES=$(wildcard design*.v)
CSV_FILES=$(subst .v,.csv,$(DESIGN_FILES))
FASM_FILES=$(subst .v,.fasm,$(DESIGN_FILES))
.PHONY: all clean designs analysis
.PRECIOUS: %.bit %.fasm %.csv
all: analysis
clean:
@rm -rf design_*.v
@rm -rf design_*.json
@rm -rf design_*.csv
@rm -rf design_*.fasm
@rm -rf design_*.bit
@rm -rf design_*.dcp
@rm -rf designs.ok
@rm -rf *.log
@rm -rf *.jou
@rm -rf *.xml
@for f in build-*; \
do \
rm -rf $$f; \
done
@rm -rf .Xil
@rm -rf snippets
@rm -rf features.csv
@rm -rf results.json
@rm -rf unknown_bits.jl
iobs-$(VIVADO_PART).csv: ../dump_iobs.tcl
env VIVADO_PART=$(VIVADO_PART) $(XRAY_VIVADO) -mode batch -source ../dump_iobs.tcl -nojournal -log dump_iobs.log
designs.ok: iobs-$(VIVADO_PART).csv generate.py
env VIVADO_PART=$(VIVADO_PART) python3 ./generate.py
touch designs.ok
designs: designs.ok
%.bit: %.v designs.ok ../syn+par.tcl
mkdir -p build-$(basename $@)
cd build-$(basename $@) && env PROJECT_NAME=$(basename $@) VIVADO_PART=${VIVADO_PART} $(XRAY_VIVADO) -mode batch -source ../../syn+par.tcl -nojournal -log ../$@.log
rm -rf *.backup.log
%.fasm: %.bit
$(XRAY_BIT2FASM) $(BIT2FASM_ARGS) $< > $@
@sort -u -o $@ $@
%.csv: %.fasm
python3 ./analyze.py --fasm $< --design $(basename $@).json -o $@
analysis: features.csv unknown_bits.jl
features.csv: $(CSV_FILES) designs.ok
@head -n 1 $(word 1,$(CSV_FILES)) >features.csv
@rm -rf features.csv.tmp
@for f in $(CSV_FILES); \
do \
tail -n +2 $$f >>features.csv.tmp; \
done
@sort features.csv.tmp >>features.csv
@rm -rf features.csv.tmp
unknown_bits.jl: $(FASM_FILES) designs.ok
@cat $(FASM_FILES) | grep unknown_bit | cut -d: -f 2 | sort -u >unknown_bits.jl
snippets: features.csv
mkdir -p snippets
cd snippets && python3 ../snippets.py ../$<

View File

@ -0,0 +1,41 @@
# IOSTANDARD feature correlation minitest
This test checks which fasm features present in the db are set for given IO settings (IOSTANDARD + DRIVE + SLEW) and for which IOB type (IBUF, OBUF, IOBUF). It also checks what features are set on unused IOBs of a given bank in which active IOBs are instantiated. To avoid conflicts, a single iosettings combination is used within an IO bank.
## Running the minitest
1. Dump IOBs and generate verilog designs
`
make designs
`
2. (can skip this and go to 3) Synthesize designs, decode bitstreams to fasm files, correlate with database features
`
make analysis
`
3. Generate verilog and XML snippets to be used in cell definition, techmap and architecture definition
`
make snippets
`
## How it works
At first IOB information is dumped from Vivado. Relevant IOB attributes are: IO bank, is it a Vref input, is the IOB bonded.
Next a set of design is generated. Each design contains two IBUFs, two OBUFs and one IOBUF (if IOSTANDARD allows it) per bank.
These designs are synthesized and their bitstreams are disassembled to fasms.
Each fasm is compared agains the prjxray db to see what features are activated for given IO settings. Results of that checks are stored in CSV files which are then concatenated to one named "features.csv". Unknown bits are reported and stored in the "unknown_bits.jl" file.
The "features.csv" consists of one row per IO settings and one column per fasm feature. Each "cell" contains a string identifying for which types of IOB a given feature was active:
- "I" input
- "O" output
- "T" inout
- "U" active in unused IOBs of the same bank
Finally during code snippet generation the "feature.csv" file is read back by a python script and code snippets for cell definition, cell techmap and XML architecture definition are generated. The goal is to automatically generate mappings between parameters of Vivado's IOB cells to VPR IOB cells.

View File

@ -0,0 +1,165 @@
import os
import argparse
import json
import random
from collections import defaultdict
from prjxray import util
import fasm
# =============================================================================
def load_iob_segbits():
"""
Loads IOB segbits from the database to get fasm feature list.
This function assumes:
- LIOB33/RIOB33 have the same features.
- IOB_Y0 and IOB_Y1 have the same features (at least those of interest).
"""
features = []
fname = os.path.join(util.get_db_root(), "segbits_liob33.db")
with open(fname, "r") as fp:
for line in fp:
line = line.strip()
if len(line) == 0:
continue
# Parse the line
fields = line.split(maxsplit=1)
feature = fields[0]
bits = fields[1]
# Parse the feature
parts = feature.split(".", maxsplit=3)
if len(parts) >= 3 and parts[1] == "IOB_Y0":
features.append(".".join(parts[2:]))
return features
def correlate_features(features, tile, site, set_features):
"""
Correlate each feature with the fasm disassembly for given tile/site.
"""
result = []
for feature in features:
full_feature = "{}.{}.{}".format(tile, site, feature)
if full_feature in set_features:
result.append((
feature,
True,
))
else:
result.append((
feature,
False,
))
return result
def run():
"""
Main.
"""
# Parse arguments
parser = argparse.ArgumentParser()
parser.add_argument(
"--design", type=str, required=True, help="Design JSON file")
parser.add_argument(
"--fasm", type=str, required=True, help="Decoded fasm file")
parser.add_argument(
"-o", type=str, default="results.csv", help="Output CSV file")
parser.add_argument("-j", type=str, default=None, help="Output JSON file")
args = parser.parse_args()
# Load IOB features
features = load_iob_segbits()
# Load the design data
with open(args.design, "r") as fp:
design = json.load(fp)
# Load disassembled fasm
fasm_tuples = fasm.parse_fasm_filename(args.fasm)
set_features = fasm.fasm_tuple_to_string(fasm_tuples).split("\n")
# Correlate features for given IOB types
results = []
for region in design:
result = dict(region["iosettings"])
for l in ["input", "output", "inout", "unused_sites"]:
# TODO: Check if this is true eg. for all unused sites, not just
# one random site.
tile, site = random.choice(region[l]).split(".")
matches = correlate_features(features, tile, site, set_features)
result[l] = matches
results.append(result)
# Save results
if args.j:
with open(args.j, "w") as fp:
json.dump(results, fp, indent=2, sort_keys=True)
# Save results to CSV
with open(args.o, "w") as fp:
csv_data = defaultdict(lambda: {})
# Collect data
for result in results:
iostandard = result["iostandard"]
drive = result["drive"]
slew = result["slew"]
if drive is None:
drive = "_FIXED"
iosettings = "{}.I{}.{}".format(iostandard, drive, slew)
for feature in sorted(features):
I = [f[1] for f in result["input"] if f[0] == feature and f[1]]
O = [
f[1] for f in result["output"] if f[0] == feature and f[1]
]
T = [f[1] for f in result["inout"] if f[0] == feature and f[1]]
U = [
f[1]
for f in result["unused_sites"]
if f[0] == feature and f[1]
]
s = [
"I" if len(I) > 0 else "",
"O" if len(O) > 0 else "",
"T" if len(T) > 0 else "",
"U" if len(U) > 0 else "",
]
csv_data[iosettings][feature] = "".join(s)
# Write header
line = [""] + sorted(features)
fp.write(",".join(line) + "\n")
# Write data
for iosettings in sorted(csv_data.keys()):
data = csv_data[iosettings]
line = [iosettings
] + [data[feature] for feature in sorted(features)]
fp.write(",".join(line) + "\n")
if __name__ == "__main__":
run()

View File

@ -0,0 +1,295 @@
import os
import random
import json
from collections import defaultdict
try:
random.seed(int(os.getenv("SEED"), 16))
except TypeError:
pass
# =============================================================================
def load_iob_sites(file_name):
"""
Loads IOB site dump from the given CSV file.
"""
iob_sites = defaultdict(lambda: [])
with open(file_name, "r") as fp:
fp.readline()
for line in fp:
fields = line.split(",")
if len(fields) != 9:
continue
iob_sites[fields[3]].append(
{
"tile": fields[0],
"name": fields[1],
"type": fields[2],
"bank": fields[4],
"is_bonded": bool(int(fields[5])),
"is_clock": bool(int(fields[6])),
"is_global_clock": bool(int(fields[7])),
"is_vref": bool(int(fields[8])),
})
return iob_sites
# =============================================================================
IOBUF_NOT_ALLOWED = [
'HSTL_I',
'HSTL_I_18',
'SSTL18_I',
]
def gen_iosettings():
"""
A generator function which yields all possible IO settings combintions.
"""
IOSTANDARDS = (
'LVCMOS12',
'LVCMOS15',
'LVCMOS18',
'LVCMOS25',
'LVCMOS33',
'LVTTL',
'SSTL135',
# Those are available but not currently fuzzed.
# 'SSTL135_R',
# 'SSTL15',
# 'SSTL15_R',
# 'SSTL18_I',
# 'SSTL18_II',
# 'HSTL_I',
# 'HSTL_I_18',
# 'HSTL_II',
# 'HSTL_II_18',
# 'HSUL_12',
# 'MOBILE_DDR',
)
DRIVES = defaultdict(lambda: [None])
DRIVES.update(
{
"LVTTL": [4, 8, 12, 16, 24],
"LVCMOS12": [4, 8, 12],
"LVCMOS15": [4, 8, 12, 16],
"LVCMOS18": [4, 8, 12, 16, 24],
"LVCMOS25": [4, 8, 12, 16],
"LVCMOS33": [4, 8, 12, 16],
})
SLEWS = ("SLOW", "FAST")
for iostandard in IOSTANDARDS:
for drive in DRIVES[iostandard]:
for slew in SLEWS:
yield {"iostandard": iostandard, "drive": drive, "slew": slew}
# =============================================================================
def run():
"""
Main.
"""
# Load IOB data
iob_sites = load_iob_sites("iobs-{}.csv".format(os.getenv("VIVADO_PART")))
# Generate designs
iosettings_gen = gen_iosettings()
design_index = 0
while True:
# Generate clock regions
region_data = []
for region in iob_sites:
# Get IO settings
try:
iosettings = next(iosettings_gen)
except StopIteration:
break
# Get sites
sites = [
site["name"] for site in iob_sites[region] if site["is_bonded"]
and not site["is_vref"] and "SING" not in site["tile"]
]
if not len(sites):
continue
# Select 5 random sites (IBUF, IBUF, OBUF, OBUF, IOBUF)
used_sites = random.sample(sites, 5)
unused_sites = list(set(sites) - set(used_sites))
# Store data
region_data.append(
{
"region": region,
"iosettings": iosettings,
"unused_sites": unused_sites,
"input": used_sites[0:2],
"output": used_sites[2:4],
"inout": used_sites[4:5],
})
# No more
if len(region_data) == 0:
break
# Generate the design
num_inp = len(region_data) * 2
num_out = len(region_data) * 2
num_ino = len(region_data)
verilog = """
module top (
input wire [{num_inp}:0] inp,
inout wire [{num_ino}:0] ino,
output wire [{num_out}:0] out
);
""".format(num_inp=num_inp - 1, num_ino=num_ino - 1, num_out=num_out - 1)
for i, data in enumerate(region_data):
use_ino = data["iosettings"]["iostandard"] not in IOBUF_NOT_ALLOWED
iostandard = data["iosettings"]["iostandard"]
drive = data["iosettings"]["drive"]
slew = data["iosettings"]["slew"]
ibuf_param_str = ".IOSTANDARD(\"{}\")".format(iostandard)
obuf_param_str = str(ibuf_param_str)
if drive is not None:
obuf_param_str += ", .DRIVE({})".format(drive)
if slew is not None:
obuf_param_str += ", .SLEW(\"{}\")".format(slew)
keys = {
"region": data["region"],
"ibuf_0_loc": data["input"][0],
"ibuf_1_loc": data["input"][1],
"obuf_0_loc": data["output"][0],
"obuf_1_loc": data["output"][1],
"iobuf_loc": data["inout"][0],
"inp_0": 2 * i + 0,
"inp_1": 2 * i + 1,
"out_0": 2 * i + 0,
"out_1": 2 * i + 1,
"ino": i,
"ibuf_param_str": ibuf_param_str,
"obuf_param_str": obuf_param_str,
}
verilog += """
// {region}
wire inp_0_{region};
wire inp_1_{region};
wire out_0_{region};
wire out_1_{region};
wire ino_i_{region};
wire ino_o_{region};
wire ino_t_{region};
""".format(**keys)
verilog += """
(* KEEP, DONT_TOUCH, LOC="{ibuf_0_loc}" *)
IBUF # ({ibuf_param_str}) ibuf_0_{region} (
.I(inp[{inp_0}]),
.O(inp_0_{region})
);
(* KEEP, DONT_TOUCH, LOC="{ibuf_1_loc}" *)
IBUF # ({ibuf_param_str}) ibuf_1_{region} (
.I(inp[{inp_1}]),
.O(inp_1_{region})
);
(* KEEP, DONT_TOUCH, LOC="{obuf_0_loc}" *)
OBUF # ({obuf_param_str}) obuf_0_{region} (
.I(out_0_{region}),
.O(out[{out_0}])
);
(* KEEP, DONT_TOUCH, LOC="{obuf_1_loc}" *)
OBUF # ({obuf_param_str}) obuf_1_{region} (
.I(out_1_{region}),
.O(out[{out_1}])
);
""".format(**keys)
if use_ino:
verilog += """
(* KEEP, DONT_TOUCH, LOC="{iobuf_loc}" *)
IOBUF # ({obuf_param_str}) iobuf_{region} (
.I(ino_i_{region}),
.O(ino_o_{region}),
.T(ino_t_{region}),
.IO(ino[{ino}])
);
assign out_0_{region} = inp_0_{region};
assign out_1_{region} = ino_o_{region};
assign ino_i_{region} = inp_0_{region};
assign ino_t_{region} = inp_1_{region};
""".format(**keys)
else:
verilog += """
assign out_0_{region} = inp_0_{region};
assign out_1_{region} = inp_1_{region};
assign ino[{ino}] = 1'b0;
""".format(**keys)
verilog += "endmodule"
# Write verilog
fname = "design_{:03d}.v".format(design_index)
with open(fname, "w") as fp:
fp.write(verilog)
# Write JSON
fname = "design_{:03d}.json".format(design_index)
# Convert Vivado site names to fasm-style TILE.SITE names.
for data in region_data:
type_to_loc = {
"IOB33": "IOB_Y0",
"IOB33M": "IOB_Y0",
"IOB33S": "IOB_Y1"
}
site_to_loc = {
s["name"]: "{}.{}".format(s["tile"], type_to_loc[s["type"]])
for s in iob_sites[data["region"]]
}
for l in ["input", "output", "inout", "unused_sites"]:
data[l] = [site_to_loc[s] for s in data[l]]
# Write design settings to JSON
with open(fname, "w") as fp:
json.dump(region_data, fp, sort_keys=True, indent=1)
design_index += 1
if __name__ == "__main__":
run()

View File

@ -0,0 +1,199 @@
import os
import argparse
from collections import namedtuple
IOSettings = namedtuple("IOSettings", "iostandard, drive, slew")
# =============================================================================
def load_feature_data(fname):
"""
Load feature vs. IO settings correlation data from a CSV file.
"""
feature_data = {}
# Open the file
with open(fname, "r") as fp:
# Header
line = fp.readline()
features = line.strip().split(",")[1:]
# Data
for line in fp:
fields = line.strip().split(",")
# IOSettings
parts = fields[0].split(".")
ios = IOSettings(
parts[0],
int(parts[1][1:]) if parts[1][1:] != "_FIXED" else None,
parts[2])
# Feature activity
feature_data[ios] = {
f: fields[1 + i]
for i, f in enumerate(features)
}
return feature_data
def filter_feature_data(feature_data):
"""
Filters fasm features and leaves only those related to IO settings.
"""
for data in feature_data.values():
for feature in dict(data):
if "DISABLE" in feature:
del data[feature]
if "PULLTYPE" in feature:
del data[feature]
if "IN_TERM" in feature:
del data[feature]
if "LOW_PWR" in feature:
del data[feature]
return feature_data
def get_relevant_parameters(feature_data, iob_type):
"""
Gets features relevant to a specific IOB type and assign them verilog
parameter names.
"""
# Get all features and make parameter names
all_features = set(
[f for data in feature_data.values() for f in data.keys()])
parameters = []
# Check if a feature is relevant to particular IOB type
for feature in all_features:
for iosettings in feature_data:
if iob_type in feature_data[iosettings][feature]:
parameters.append((
feature,
feature.replace(".", "_"),
))
break
return parameters
# =============================================================================
def generate_parameter_defs(parameters):
"""
Generates verilog parameter definitions for the IOB cell model.
"""
verilog = []
for feature, parameter in sorted(parameters):
verilog.append("parameter [0:0] {} = 1'b0".format(parameter))
return ";\n".join(verilog)
def generate_parameter_map(parameters):
"""
Generates mapping of parameters for fasm features for architecture
definition.
"""
xml = []
xml.append("<meta name=\"fasm_params\">")
for feature, parameter in sorted(parameters):
xml.append(" {} = {}".format(feature, parameter))
xml.append("</meta>")
return "\n".join(xml)
def generate_parameter_assignments(parameters, feature_data, iob_type):
"""
Generates parameter assignmets to be used inside the techmap. For
each IOB cell parameter a set of conditions is generated based on
feature activity observation.
"""
verilog = []
for feature, parameter in sorted(parameters):
condition = []
for iosettings in feature_data:
# Feature is set
if iob_type in feature_data[iosettings][feature]:
cond = "IOSTANDARD == \"{}\"".format(
iosettings.iostandard.upper())
if iob_type in ["O", "T"]:
if iosettings.drive is not None and "DRIVE" in feature:
cond += " && DRIVE == {}".format(iosettings.drive)
if "SLEW" in feature:
cond += " && SLEW == \"{}\"".format(iosettings.slew)
condition.append(cond)
condition = sorted(list(set(condition)))
condition_str = " || \n".join([" ({})".format(c) for c in condition])
verilog.append(".{}(\n{}\n)".format(parameter, condition_str))
return ",\n".join(verilog)
# =============================================================================
def run():
"""
Main.
"""
# Parse arguments
parser = argparse.ArgumentParser()
parser.add_argument("features", type=str, help="Input CSV file")
args = parser.parse_args()
# Load iosettings vs. feature map
feature_data = load_feature_data(args.features)
# Reject non-iostandard related features
feature_data = filter_feature_data(feature_data)
# Make parameters
parameters = {}
for iob_type in ["I", "O", "T"]:
parameters[iob_type] = get_relevant_parameters(feature_data, iob_type)
# Parameter definition
with open("cells_sim.v", "w") as fp:
for iob_type, params in parameters.items():
fp.write("// {} {}\n\n".format(iob_type, "=" * 70))
verilog = generate_parameter_defs(params)
fp.write(verilog + "\n\n")
# Parameter assignments
with open("cells_map.v", "w") as fp:
for iob_type, params in parameters.items():
fp.write("// {} {}\n\n".format(iob_type, "=" * 70))
verilog = generate_parameter_assignments(
params, feature_data, iob_type)
fp.write(verilog + "\n\n")
# Parameter assignments
with open("arch.xml", "w") as fp:
for iob_type, params in parameters.items():
fp.write("<!-- {} -->\n\n".format(iob_type))
xml = generate_parameter_map(params)
fp.write(xml + "\n\n")
if __name__ == "__main__":
run()

View File

@ -0,0 +1,12 @@
create_project -force -name $env(PROJECT_NAME) -part $env(VIVADO_PART)
set_property SEVERITY {Warning} [get_drc_checks NSTD-1]
set_property SEVERITY {Warning} [get_drc_checks UCIO-1]
read_verilog ../$env(PROJECT_NAME).v
synth_design -top top
place_design
route_design
write_checkpoint -force ../$env(PROJECT_NAME).dcp
write_bitstream -force ../$env(PROJECT_NAME).bit