Initial commit: Loongson FPGA SoC project for 2026 CIC-IEC

This commit is contained in:
2026-06-23 01:22:45 +08:00
commit fbc993b846
181 changed files with 51135 additions and 0 deletions

59
fpga/check_dsp.py Normal file
View 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
View 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
View 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
View 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
View 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