Initial commit: Loongson FPGA SoC project for 2026 CIC-IEC
This commit is contained in:
59
fpga/check_dsp.py
Normal file
59
fpga/check_dsp.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
|
||||
def usage() -> None:
|
||||
print("Usage: python3 check_dsp.py <dsp-utilization-report> [max-dsp]", file=sys.stderr)
|
||||
|
||||
|
||||
def extract_dsp_used(report: Path) -> int:
|
||||
if not report.is_file():
|
||||
raise FileNotFoundError(f"DSP utilization report not found: {report}")
|
||||
|
||||
used_values = []
|
||||
for line in report.read_text(errors="replace").splitlines():
|
||||
stripped = line.strip()
|
||||
if not stripped.startswith("|") or "DSP" not in stripped.upper():
|
||||
continue
|
||||
|
||||
columns = [col.strip() for col in stripped.strip("|").split("|")]
|
||||
if len(columns) < 2:
|
||||
continue
|
||||
if not re.fullmatch(r"DSPs?", columns[0], re.IGNORECASE):
|
||||
continue
|
||||
|
||||
match = re.fullmatch(r"\d+", columns[1])
|
||||
if match:
|
||||
used_values.append(int(match.group(0)))
|
||||
|
||||
if not used_values:
|
||||
raise ValueError(f"No DSP utilization row found in {report}")
|
||||
|
||||
return max(used_values)
|
||||
|
||||
|
||||
def main(argv: List[str]) -> int:
|
||||
if len(argv) not in (2, 3):
|
||||
usage()
|
||||
return 2
|
||||
|
||||
report = Path(argv[1])
|
||||
max_allowed = int(argv[2]) if len(argv) == 3 else int(os.environ.get("DSP_MAX", "0"))
|
||||
used = extract_dsp_used(report)
|
||||
print(f"DSP used: {used}, allowed: {max_allowed}")
|
||||
if used > max_allowed:
|
||||
print(f"ERROR: DSP usage {used} exceeds allowed limit {max_allowed}", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main(sys.argv))
|
||||
except Exception as exc:
|
||||
print(f"ERROR: {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
24
fpga/create_project.tcl
Normal file
24
fpga/create_project.tcl
Normal file
@@ -0,0 +1,24 @@
|
||||
# SET PROJECT NAME
|
||||
set project_name Loongson_Soc
|
||||
set project_path ./project
|
||||
set project_part xc7a200tfbg676-1
|
||||
# CLEAR
|
||||
file delete -force $project_path
|
||||
|
||||
create_project -force $project_name $project_path -part $project_part
|
||||
|
||||
# Add conventional sources
|
||||
add_files -scan_for_includes ../rtl
|
||||
|
||||
# Add IPs
|
||||
add_files -norecurse -scan_for_includes ../rtl/ip/PLL_2019_2/clk_pll.xci
|
||||
|
||||
# Add simulation files
|
||||
add_files -fileset sim_1 ../sim/
|
||||
|
||||
# Add constraints
|
||||
add_files -fileset constrs_1 -quiet ./constraints
|
||||
|
||||
set_property top soc_top [current_fileset]
|
||||
set_property -name "top" -value "tb_top" -objects [get_filesets sim_1]
|
||||
set_property -name {xsim.simulate.log_all_signals} -value {true} -objects [get_filesets sim_1]
|
||||
26
fpga/generate_bit.tcl
Normal file
26
fpga/generate_bit.tcl
Normal file
@@ -0,0 +1,26 @@
|
||||
# open project
|
||||
open_project project/Loongson_Soc.xpr
|
||||
|
||||
# generate Bitstream
|
||||
launch_runs impl_1 -to_step write_bitstream
|
||||
wait_on_run impl_1
|
||||
|
||||
set impl_run [get_runs impl_1]
|
||||
set impl_progress [get_property PROGRESS $impl_run]
|
||||
set impl_status [get_property STATUS $impl_run]
|
||||
puts "impl_1 progress: $impl_progress"
|
||||
puts "impl_1 status: $impl_status"
|
||||
|
||||
set bit_files [glob -nocomplain project/Loongson_Soc.runs/impl_1/*.bit]
|
||||
if {[llength $bit_files] == 0} {
|
||||
puts "ERROR: Bitstream file was not generated!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
puts "Generated bitstream files:"
|
||||
foreach bit_file $bit_files {
|
||||
puts " $bit_file"
|
||||
}
|
||||
|
||||
close_project
|
||||
exit 0
|
||||
288
fpga/run-linter.py
Normal file
288
fpga/run-linter.py
Normal file
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env python3
|
||||
from lxml import etree
|
||||
import chardet
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Tuple, List, Set
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import traceback
|
||||
import re
|
||||
|
||||
def usage():
|
||||
print("Usage: python3 run-linter.py <path-to-vivado-xpr>", file=sys.stderr)
|
||||
|
||||
def is_header(f: Path):
|
||||
with f.open('rb') as fd:
|
||||
return -1 == fd.read().find(b'endmodule')
|
||||
|
||||
def transcoding(src: Path, target: Path):
|
||||
with src.open("rb") as fd:
|
||||
rawdata = fd.read()
|
||||
res = chardet.detect(rawdata)
|
||||
detected = res['encoding'] if res['confidence'] > 0.5 else None
|
||||
encodings = ["utf-8", "gb18030"]
|
||||
if detected and detected.lower() not in ("utf-8", "gb18030"):
|
||||
encodings.append(detected)
|
||||
|
||||
for encoding in encodings:
|
||||
try:
|
||||
s = rawdata.decode(encoding)
|
||||
break
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
else:
|
||||
print("Transcoding", src, "with replacement characters")
|
||||
s = rawdata.decode("utf-8", "replace")
|
||||
with target.open("wb") as wfd:
|
||||
wfd.write(s.encode('utf-8'))
|
||||
|
||||
def parse_project(xpr: Path) -> Tuple[str, Set[str], Set[str], Set[str]]:
|
||||
prjname = os.path.splitext(xpr.name)[0]
|
||||
prjdir = xpr.parent.resolve()
|
||||
repo_root = prjdir.parent.parent
|
||||
srcdir = prjdir / (prjname + ".srcs")
|
||||
topname = ''
|
||||
target = prjdir / '.lint'
|
||||
|
||||
tree = etree.parse(str(xpr))
|
||||
srclist = set()
|
||||
headerlist = set()
|
||||
inclist = set()
|
||||
skipped_ip_count = 0
|
||||
for fileset in tree.xpath("/Project/FileSets/FileSet"):
|
||||
if fileset.attrib['Type'] != 'DesignSrcs' and \
|
||||
fileset.attrib['Type'] != 'BlockSrcs':
|
||||
continue
|
||||
for child in fileset:
|
||||
if child.tag == 'File':
|
||||
tmp = child.attrib['Path']
|
||||
if tmp.endswith((".xci", ".xcix")):
|
||||
skipped_ip_count += 1
|
||||
continue
|
||||
tmp = tmp.replace('$PSRCDIR', str(srcdir))
|
||||
tmp = tmp.replace('$PPRDIR', str(prjdir))
|
||||
vlog = Path(tmp).resolve()
|
||||
if not vlog.is_file():
|
||||
print("Source file", vlog, "does not exist")
|
||||
continue
|
||||
vlog_target = target / vlog.relative_to(repo_root)
|
||||
vlog_target.parent.mkdir(exist_ok=True, parents=True)
|
||||
# shutil.copy(vlog, vlog_target)
|
||||
transcoding(vlog, vlog_target)
|
||||
inclist.add(str(vlog_target.parent))
|
||||
if is_header(vlog_target):
|
||||
headerlist.add(str(vlog_target))
|
||||
else:
|
||||
srclist.add(str(vlog_target))
|
||||
elif child.tag == 'Config' and fileset.attrib['Type'] == 'DesignSrcs':
|
||||
topname = child.xpath("./Option[@Name='TopModule']")[0].attrib['Val']
|
||||
|
||||
return (topname, headerlist, srclist, inclist)
|
||||
|
||||
def verilator_supports_warning(verilator: str, env, warning: str) -> bool:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
probe = Path(tmpdir) / "verilator_warning_probe.v"
|
||||
probe.write_text("module verilator_warning_probe; endmodule\n", encoding="utf-8")
|
||||
res = subprocess.run(
|
||||
[verilator, "--lint-only", "-Wno-" + warning, str(probe)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
universal_newlines=True,
|
||||
env=env,
|
||||
)
|
||||
return "Unknown warning specified" not in res.stdout
|
||||
|
||||
def strip_verilog_comments(text: str) -> str:
|
||||
text = re.sub(r"/\*.*?\*/", "", text, flags=re.S)
|
||||
return re.sub(r"//.*", "", text)
|
||||
|
||||
def find_module_body(text: str, module_name: str) -> str:
|
||||
pattern = re.compile(r"\bmodule\s+" + re.escape(module_name) + r"\b.*?\bendmodule\b", re.S)
|
||||
m = pattern.search(text)
|
||||
return m.group(0) if m else ""
|
||||
|
||||
def find_top_source(topname: str, srclist: Set[str]) -> Tuple[Path, str]:
|
||||
module_re = re.compile(r"\bmodule\s+" + re.escape(topname) + r"\b")
|
||||
for src in sorted(srclist):
|
||||
path = Path(src)
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
if module_re.search(strip_verilog_comments(text)):
|
||||
return path, text
|
||||
return Path(), ""
|
||||
|
||||
def module_is_instantiated(module_name: str, text: str) -> bool:
|
||||
code = strip_verilog_comments(text)
|
||||
pattern = re.compile(
|
||||
r"(?<!\bmodule\s)\b"
|
||||
+ re.escape(module_name)
|
||||
+ r"\s*(?:#\s*\(.*?\)\s*)?[A-Za-z_][A-Za-z0-9_$]*\s*\(",
|
||||
re.S,
|
||||
)
|
||||
return bool(pattern.search(code))
|
||||
|
||||
def extract_module_definitions(sources: Set[str]) -> Tuple[dict, dict]:
|
||||
module_sources = {}
|
||||
module_bodies = {}
|
||||
for src in sorted(sources):
|
||||
path = Path(src)
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
code = strip_verilog_comments(text)
|
||||
for m in re.finditer(r"\bmodule\s+([A-Za-z_][A-Za-z0-9_$]*)\b.*?\bendmodule\b", code, re.S):
|
||||
name = m.group(1)
|
||||
module_sources.setdefault(name, path)
|
||||
module_bodies.setdefault(name, m.group(0))
|
||||
return module_sources, module_bodies
|
||||
|
||||
def find_instantiated_modules(text: str, module_names: Set[str]) -> Set[str]:
|
||||
return {
|
||||
module
|
||||
for module in module_names
|
||||
if module_is_instantiated(module, text)
|
||||
}
|
||||
|
||||
def find_reachable_modules(topname: str, module_bodies: dict) -> Set[str]:
|
||||
reachable = set()
|
||||
pending = [topname]
|
||||
module_names = set(module_bodies)
|
||||
while pending:
|
||||
module = pending.pop()
|
||||
if module in reachable:
|
||||
continue
|
||||
reachable.add(module)
|
||||
body = module_bodies.get(module, "")
|
||||
for child in find_instantiated_modules(body, module_names - reachable):
|
||||
pending.append(child)
|
||||
return reachable
|
||||
|
||||
def run_design_sanity_checks(prjdir: Path, topname: str, srclist: Set[str]):
|
||||
issues = []
|
||||
top_source, top_text = find_top_source(topname, srclist)
|
||||
if not top_text:
|
||||
issues.append("Top module '{}' was not found in lint source set.".format(topname))
|
||||
else:
|
||||
for lineno, line in enumerate(top_text.splitlines(), start=1):
|
||||
if "add your code" in line.lower():
|
||||
issues.append(
|
||||
"{}:{}: template placeholder remains in top module; the SoC integration is likely incomplete.".format(
|
||||
top_source, lineno
|
||||
)
|
||||
)
|
||||
|
||||
module_sources, module_bodies = extract_module_definitions(srclist)
|
||||
reachable_modules = find_reachable_modules(topname, module_bodies)
|
||||
unreachable_modules = sorted(set(module_bodies) - reachable_modules)
|
||||
if len(module_bodies) >= 20 and len(unreachable_modules) > len(module_bodies) // 2:
|
||||
shown_modules = ", ".join(unreachable_modules[:12])
|
||||
if len(unreachable_modules) > 12:
|
||||
shown_modules += ", ..."
|
||||
issues.append(
|
||||
"{}: only {} of {} project modules are reachable from top '{}'; unreachable modules include: {}.".format(
|
||||
top_source,
|
||||
len(reachable_modules & set(module_bodies)),
|
||||
len(module_bodies),
|
||||
topname,
|
||||
shown_modules,
|
||||
)
|
||||
)
|
||||
|
||||
if issues:
|
||||
sanity_log = prjdir / "linter-sanity.log"
|
||||
sanity_log.write_text("\n".join(issues) + "\n", encoding="utf-8")
|
||||
print("HDL sanity check failed.")
|
||||
for issue in issues:
|
||||
print(issue)
|
||||
print("Full log:", sanity_log)
|
||||
sys.exit(1)
|
||||
|
||||
def run_linter(prjdir: Path, topname: str, headerlist: Set[str], srclist: Set[str], inclist: Set[str]):
|
||||
linter_log = prjdir / "linter.log"
|
||||
linter_args = prjdir / "linter.args"
|
||||
verilator = os.environ.get("VERILATOR") or shutil.which("verilator") or shutil.which("verilator_bin.exe")
|
||||
if not verilator:
|
||||
print("ERROR: verilator was not found in PATH.", file=sys.stderr)
|
||||
print("Install verilator in the CI image, set VERILATOR, or remove the HDL lint step from .gitlab-ci.yml.", file=sys.stderr)
|
||||
sys.exit(127)
|
||||
|
||||
env = os.environ.copy()
|
||||
if os.name == "nt" and "VERILATOR_ROOT" not in env:
|
||||
verilator_root = Path(verilator).resolve().parent.parent / "share" / "verilator"
|
||||
if verilator_root.is_dir():
|
||||
env["VERILATOR_ROOT"] = str(verilator_root)
|
||||
|
||||
timescale_warning = None
|
||||
for warning in ("TIMESCALEMOD", "TIMESCALE"):
|
||||
if verilator_supports_warning(verilator, env, warning):
|
||||
timescale_warning = warning
|
||||
break
|
||||
|
||||
args = [
|
||||
verilator,
|
||||
"--lint-only",
|
||||
"-Wall",
|
||||
"-Wno-fatal",
|
||||
"-Wno-DECLFILENAME",
|
||||
"-Wno-PINCONNECTEMPTY",
|
||||
"-Wno-UNUSED",
|
||||
"-GSIMULATION=1",
|
||||
]
|
||||
if timescale_warning:
|
||||
args.append("-Wno-" + timescale_warning)
|
||||
args += ['--top-module', topname]
|
||||
incargs = [ '-I' + i for i in inclist]
|
||||
args += incargs
|
||||
args += sorted(headerlist)
|
||||
args += srclist
|
||||
|
||||
if os.name == "nt":
|
||||
response_args = [str(arg).replace("\\", "/") for arg in args[1:]]
|
||||
linter_args.write_text("\n".join(response_args) + "\n", encoding="utf-8")
|
||||
run_args = [verilator, "-f", str(linter_args)]
|
||||
else:
|
||||
run_args = args
|
||||
|
||||
with linter_log.open("w", encoding="utf-8") as log:
|
||||
res = subprocess.run(run_args, stdout=log, stderr=subprocess.STDOUT, env=env)
|
||||
if res.returncode != 0:
|
||||
log_text = linter_log.read_text(encoding="utf-8", errors="replace")
|
||||
error_lines = [
|
||||
line for line in log_text.splitlines()
|
||||
if "%Error" in line or "Error:" in line or "ERROR:" in line
|
||||
]
|
||||
print("HDL lint failed.")
|
||||
if error_lines:
|
||||
print("Key errors:")
|
||||
for line in error_lines[:20]:
|
||||
print(line)
|
||||
if len(error_lines) > 20:
|
||||
print("... {} more error lines omitted".format(len(error_lines) - 20))
|
||||
else:
|
||||
print("No explicit error line found in Verilator output.")
|
||||
print("Return code:", res.returncode)
|
||||
print("Full log:", linter_log)
|
||||
sys.exit(res.returncode)
|
||||
print("HDL lint passed.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
if len(sys.argv) != 2:
|
||||
usage()
|
||||
sys.exit(2)
|
||||
xpr = Path(sys.argv[1])
|
||||
if not xpr.is_file():
|
||||
print("Vivado project file does not exist:", xpr, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
topname, headerlist, srclist, inclist = parse_project(xpr)
|
||||
if not topname:
|
||||
print("Top module was not found in project:", xpr, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not srclist:
|
||||
print("No Verilog source files were found in project:", xpr, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
run_design_sanity_checks(xpr.parent, topname, srclist)
|
||||
run_linter(xpr.parent, topname, headerlist, srclist, inclist)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
32
fpga/run_impl.tcl
Normal file
32
fpga/run_impl.tcl
Normal file
@@ -0,0 +1,32 @@
|
||||
# bit.tcl
|
||||
open_project project/Loongson_Soc.xpr
|
||||
|
||||
# Add myCPU
|
||||
# add_files -scan_for_includes mysrc
|
||||
|
||||
# Add xilinx_ip in myCPU
|
||||
# add_files -quiet [glob -nocomplain mysrc/xilinx_ip/*/*.xci]
|
||||
# add_files -quiet [glob -nocomplain mysrc/xilinx_ip/*/*.xcix]
|
||||
|
||||
# Upgrade IPs
|
||||
upgrade_ip -quiet [get_ips]
|
||||
|
||||
# run_impl
|
||||
launch_runs impl_1
|
||||
wait_on_run impl_1
|
||||
|
||||
# check impl status
|
||||
if {[get_property PROGRESS [get_runs impl_1]] != "100%"} {
|
||||
puts "ERROR: Implementation failed!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# open_run impl_1
|
||||
open_run impl_1
|
||||
report_utilization -file project/dsp_utilization.rpt -pb project/dsp_utilization.pb
|
||||
|
||||
# report_timing_summary -delay_type min_max -report_unconstrained \
|
||||
# -file lab/lab1.runs/impl_1/timing_summary.rpt
|
||||
|
||||
close_project
|
||||
exit 0
|
||||
Reference in New Issue
Block a user