feat: implement setup helpers and refactor setup script

- Added  to manage Git repository dependencies and file downloads.
- Refactored  to utilize the new helper functions for cloning the MPW generator.
- Enhanced OpenSTA Makefile to validate input files and configurations before execution.
- Updated OpenSTA TCL script to use environment variables for file paths.
- Refactored source export script to improve file handling and path resolution.
- Added tests for atomic writing, filelist parsing, and dependency management.
- Improved Yosys synthesis scripts to include configuration recording.
This commit is contained in:
maksyuki committed 2026-07-10 13:13:59 +08:00
1 parent 2fc59c8cc5
commit 7d064ce782
35 files changed
+1792 -771

No files matched your search

+6 -1
View File
@@ -14,6 +14,11 @@ jobs:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Test build scripts
run: |
python3 -m pip install pytest
python3 -m pytest -q tests
- name: Download and Extract Verilator Artifact - name: Download and Extract Verilator Artifact
run: | run: |
verilator_path="artifact-verilator" verilator_path="artifact-verilator"
@@ -147,4 +152,4 @@ jobs:
# retention-days: 90 # Retain for 90 days # retention-days: 90 # Retain for 90 days
- name: Success Notification - name: Success Notification
run: echo "RTL simulation completed successfully" run: echo "RTL simulation completed successfully"
+3 -1
View File
@@ -20,6 +20,8 @@
*.rc *.rc
*.aig *.aig
*.so *.so
*.py[cod]
__pycache__/
.vscode .vscode
pic pic
video.h video.h
@@ -67,4 +69,4 @@ coremark-main
*wave *wave
picorv32_ver.v picorv32_ver.v
socver.* socver.*
3rd-party 3rd-party
+113 -52
View File
@@ -1,3 +1,7 @@
SHELL := /bin/bash
.DEFAULT_GOAL := help
.DELETE_ON_ERROR:
SOC ?= MINI SOC ?= MINI
SIMU ?= VCS SIMU ?= VCS
SYNTH ?= NONE SYNTH ?= NONE
@@ -16,8 +20,8 @@ RTL_SIM_PLLCFG ?= NONE
RTL_SIM_CORESEL ?= 0 RTL_SIM_CORESEL ?= 0
RTL_SIM_TIMEOUT ?= -1 RTL_SIM_TIMEOUT ?= -1
ROOT_PATH ?= $(CURDIR) ROOT_PATH ?= $(abspath $(dir $(firstword $(MAKEFILE_LIST))))
RTL_PATH ?= NONE RTL_PATH := $(ROOT_PATH)/rtl/mini
CORE ?= HAZARD3 CORE ?= HAZARD3
IP ?= NONE IP ?= NONE
@@ -30,40 +34,50 @@ FIRMWARE_NAME ?= retrosoc_fw
PROG_TYPE ?= FULL PROG_TYPE ?= FULL
LINK_TYPE ?= ld2_sram LINK_TYPE ?= ld2_sram
$(info ============== BASE INFO ==================================) VALID_SOC := MINI
$(info retroSoC: <https://github.com/retroSoC/retroSoC>) VALID_CORE := PICORV32 HAZARD3 MDD
$(info author: Yuchi Miao <https://github.com/maksyuki>) VALID_IP := NONE MDD
$(info license: MulanPSL-2.0 license) VALID_SIMU := VCS VERILATOR IVERILOG
$(info ROOT_PATH: $(CURDIR)) VALID_SYNTH := NONE YOSYS
$(info SHELL: $(SHELL)) VALID_STA := NONE OPENSTA
$(info MAKE VERSION: $(MAKE_VERSION)) VALID_PDK := ICS55 IHP130 SKY130 GF180
$(info MAKE APP: $(MAKE)) VALID_BOOL := YES NO
$(info MAKE CMDGOAL: $(MAKECMDGOALS)) VALID_ISA := RV32E RV32I RV32IM
$(info MAKE FILELIST: $(MAKEFILE_LIST)) VALID_PROG_TYPE := BASE FULL
$(info ============== HW CONFIG INFO ===================================) VALID_LINK_TYPE := xip ld2_sram ld2_psram ld2_sdram
$(info SOC [TINY, MINI]: $(SOC))
$(info CORE [PICORV32, HAZARD3, MDD]: $(CORE)) define validate_value
$(info IP [NONE, MDD]: $(IP)) $(if $(filter $($(1)),$(2)),,$(error Invalid $(1)='$($(1))'; expected one of: $(2)))
$(info SIMU [VCS, VERILATOR, IVERILOG]: $(SIMU)) endef
$(info SYNTH [NONE, YOSYS, DC]: $(SYNTH))
$(info STA [NONE, OPENSTA, ISTA]: $(STA)) $(call validate_value,SOC,$(VALID_SOC))
$(info PDK [ICS55, IHP130, SKY130, GF180]: $(PDK)) $(call validate_value,CORE,$(VALID_CORE))
$(info HAVE_PLL [YES, NO]: $(HAVE_PLL)) $(call validate_value,IP,$(VALID_IP))
$(info HAVE_SRAM_IF [YES, NO]: $(HAVE_SRAM_IF)) $(call validate_value,SIMU,$(VALID_SIMU))
$(info HAVE_SRAM_MACRO [YES, NO]: $(HAVE_SRAM_MACRO)) $(call validate_value,SYNTH,$(VALID_SYNTH))
$(info HAVE_SVA [YES, NO]: $(HAVE_SVA)) $(call validate_value,STA,$(VALID_STA))
$(info WAVE: [YES, NO]: $(WAVE)) $(call validate_value,PDK,$(VALID_PDK))
$(info RTL_SIM_PLLEN: $(RTL_SIM_PLLEN)) $(call validate_value,HAVE_PLL,$(VALID_BOOL))
$(info RTL_SIM_PLLCFG: $(RTL_SIM_PLLCFG)) $(call validate_value,HAVE_SRAM_IF,$(VALID_BOOL))
$(info RTL_SIM_CORESEL: $(RTL_SIM_CORESEL)) $(call validate_value,HAVE_SRAM_MACRO,$(VALID_BOOL))
$(info RTL_SIM_TIMEOUT: $(RTL_SIM_TIMEOUT)) $(call validate_value,HAVE_SVA,$(VALID_BOOL))
$(info ============== SW CONFIG INFO ===================================) $(call validate_value,WAVE,$(VALID_BOOL))
$(info ISA [RV32E RV32I RV32IM]: $(ISA)) $(call validate_value,ISA,$(VALID_ISA))
$(info HAVE_CSR [YES, NO]: $(HAVE_CSR)) $(call validate_value,HAVE_CSR,$(VALID_BOOL))
$(info FIRMWARE_NAME [retrosoc_fw]: $(FIRMWARE_NAME)) $(call validate_value,PROG_TYPE,$(VALID_PROG_TYPE))
$(info PROG_TYPE [BASE, FULL]: $(PROG_TYPE)) $(call validate_value,LINK_TYPE,$(VALID_LINK_TYPE))
$(info LINK_TYPE [xip, ld2_sram, ld2_psram, ld2_sdram]: $(LINK_TYPE))
$(info =================================================================) ifeq ($(SYNTH),YOSYS)
ifeq ($(filter $(PDK),IHP130 ICS55),)
$(error Yosys synthesis supports PDK=IHP130 or PDK=ICS55, not PDK=$(PDK))
endif
endif
ifeq ($(STA),OPENSTA)
ifneq ($(PDK),IHP130)
$(error OpenSTA currently supports PDK=IHP130 only)
endif
endif
DEF_LIST ?= +define+PDK_$(PDK) DEF_LIST ?= +define+PDK_$(PDK)
DEF_LIST += +define+CORE_$(CORE) DEF_LIST += +define+CORE_$(CORE)
@@ -86,27 +100,74 @@ ifeq ($(HAVE_SVA), NO)
DEF_LIST += +define+SV_ASSRT_DISABLE DEF_LIST += +define+SV_ASSRT_DISABLE
endif endif
ifeq ($(SOC), MINI) ifeq ($(SYNTH), YOSYS)
RTL_PATH = $(ROOT_PATH)/rtl/mini DEF_LIST += +define+SYNTHESIS
$(info DEF_LIST: $(DEF_LIST))
$(shell mkdir -p $(RTL_PATH)/.generated_fl)
$(file > $(RTL_PATH)/.generated_fl/def.fl, $(DEF_LIST))
include rtl/mini/Makefile
endif endif
include rtl/mini/Makefile
ifeq ($(SYNTH), YOSYS) ifeq ($(SYNTH), YOSYS)
#HACK: for some core include syn/yosys/yosys.mk
DEF_LIST += +define+SYNTHESIS
$(info SYNTH DEF_LIST: $(DEF_LIST))
$(shell mkdir -p $(RTL_PATH)/.generated_fl)
$(file > $(RTL_PATH)/.generated_fl/def.fl, $(DEF_LIST))
include syn/yosys/yosys.mk
else ifeq ($(SYNTH), DC)
include syn/dc.mk
endif endif
ifeq ($(STA), OPENSTA) ifeq ($(STA), OPENSTA)
include sta/opensta/opensta.mk include sta/opensta/opensta.mk
endif endif
.PHONY: help config doctor setup setup-mpw setup-core setup-clusterip setup-ip setup-pdk setup-app clean-all
.NOTPARALLEL: setup
help:
@printf '%s\n' \
'retroSoC build targets:' \
' firmware | asm build firmware' \
' comp | sim behavioral simulation' \
' netcomp | netsim synthesized-netlist simulation' \
' postcomp | postsim post-layout simulation' \
' synth | sta synthesis and timing analysis' \
' setup install pinned external dependencies' \
' doctor check tools, paths, and selected configuration' \
' config print selected configuration' \
' clean | clean-all clean current backend or all generated output' \
'' \
'Key variables: SOC CORE IP SIMU SYNTH STA PDK ISA PROG_TYPE LINK_TYPE'
config:
@printf '%-18s %s\n' \
ROOT_PATH '$(ROOT_PATH)' SOC '$(SOC)' CORE '$(CORE)' IP '$(IP)' \
SIMU '$(SIMU)' SYNTH '$(SYNTH)' STA '$(STA)' PDK '$(PDK)' \
HAVE_PLL '$(HAVE_PLL)' HAVE_SRAM_IF '$(HAVE_SRAM_IF)' \
HAVE_SRAM_MACRO '$(HAVE_SRAM_MACRO)' HAVE_SVA '$(HAVE_SVA)' \
ISA '$(ISA)' HAVE_CSR '$(HAVE_CSR)' PROG_TYPE '$(PROG_TYPE)' \
LINK_TYPE '$(LINK_TYPE)'
doctor:
@python3 $(ROOT_PATH)/scripts/doctor.py \
--root $(ROOT_PATH) --simu $(SIMU) --synth $(SYNTH) --sta $(STA) \
--pdk $(PDK) --core $(CORE) --ip $(IP)
setup: setup-mpw setup-core setup-clusterip setup-ip setup-pdk setup-app
setup-mpw:
python3 $(ROOT_PATH)/setup.py
python3 $(ROOT_PATH)/scripts/prepare_mpw.py
@mkdir -p $(dir $(MPW_STAMP))
@touch $(MPW_STAMP)
setup-core: setup-mpw
python3 $(ROOT_PATH)/rtl/mini/core/setup.py
setup-clusterip:
python3 $(ROOT_PATH)/rtl/clusterip/setup.py
setup-ip:
python3 $(ROOT_PATH)/rtl/ip/setup.py
setup-pdk:
python3 $(ROOT_PATH)/pdk/setup.py
setup-app:
python3 $(ROOT_PATH)/app/setup.py
clean-all:
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH)
+26 -1
View File
@@ -2,4 +2,29 @@
[![style](https://github.com/retroSoC/retroSoC/actions/workflows/style.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/style.yml) [![style](https://github.com/retroSoC/retroSoC/actions/workflows/style.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/style.yml)
[![lint](https://github.com/retroSoC/retroSoC/actions/workflows/lint.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/lint.yml) [![lint](https://github.com/retroSoC/retroSoC/actions/workflows/lint.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/lint.yml)
[![test](https://github.com/retroSoC/retroSoC/actions/workflows/test.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/test.yml) [![test](https://github.com/retroSoC/retroSoC/actions/workflows/test.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/test.yml)
## Build automation
Run `make help` for the supported build targets and `make config` for the active
configuration. `make doctor` checks the selected simulator, synthesis/STA tools,
RISC-V compiler, PDK, and external source trees before a long build starts.
External dependencies are pinned to known commits. Install them with `make setup`;
individual targets such as `setup-pdk`, `setup-ip`, and `setup-app` are also
available. Setup scripts are idempotent and accept `--update` when an existing
checkout must be moved to the pinned revision.
Typical open-source flows are:
```sh
make PROG_TYPE=BASE firmware
make SIMU=VERILATOR sim
make SIMU=IVERILOG sim
make SYNTH=YOSYS synth
make STA=OPENSTA sta
```
Generated outputs are isolated under ignored build directories. `make clean`
cleans the selected simulator backend and `make clean-all` removes all generated
flow output while leaving dependency checkouts intact.
+72 -40
View File
@@ -1,47 +1,79 @@
#!/bin/python #!/usr/bin/env python3
import os import argparse
import shutil
# Always use the script directory as the base to avoid failures in different working directories import sys
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) from pathlib import Path
APP_DIR = SCRIPT_DIR from zipfile import ZipFile
FATFS_DIR = os.path.join(APP_DIR, 'fatfs')
COREMARK_DIR = os.path.join(APP_DIR, 'coremark')
LVGL_DIR = os.path.join(APP_DIR, 'lvgl')
LVGL_NAME = 'v9.4.0.tar.gz'
# print(APP_DIR)
# print(FATFS_DIR)
# print(COREMARK_DIR)
def prepend_line(filename, line): ROOT = Path(__file__).resolve().parents[1]
with open(filename, 'r') as f: sys.path.insert(0, str(ROOT))
content = f.read() from scripts.setup_helpers import atomic_write, download_file, ensure_git_repo # noqa: E402
with open(filename, 'w') as f:
f.write(line + '\n' + content)
def replace_line(filename, old, new):
lines = ''
with open(filename, 'r', encoding='utf-8') as fp:
for v in fp:
if old in v:
lines += v.replace(old, new)
else:
lines += v
with open(filename, 'w', encoding='utf-8') as fp:
fp.writelines(lines)
os.system(f'mkdir -p "{FATFS_DIR}"/ff16 && cd "{FATFS_DIR}"/ff16 && if [ -d source ]; then echo "[fatfs] already exists, skip"; else wget https://elm-chan.org/fsw/ff/arc/ff16.zip && unzip -q ff16.zip && echo "[fatfs] downloaded and extracted"; fi') APP_DIR = Path(__file__).resolve().parent
os.system(f'cd "{FATFS_DIR}" && cp -rf ffconf.h diskio.c ff16/source/') FATFS_DIR = APP_DIR / "fatfs"
FATFS_ARCHIVE = FATFS_DIR / "ff16/ff16.zip"
os.system(f'git clone https://github.com/eembc/coremark.git {COREMARK_DIR}/coremark-main') FATFS_URL = "https://elm-chan.org/fsw/ff/arc/ff16.zip"
os.system(f'cd {COREMARK_DIR}/coremark-main && git checkout 1f483d5b8316753a742cbf5590caf5bd0a4e4777') FATFS_SHA256 = "41d98115f72b090c2d0c269a001c5c0216efd78fbd84bb6427be808a76315a5a"
os.chdir(f'{COREMARK_DIR}/coremark-main') COREMARK_DIR = APP_DIR / "coremark/coremark-main"
prepend_line('coremark.h', '#include <tinyprintf.h>') COREMARK_REVISION = "1f483d5b8316753a742cbf5590caf5bd0a4e4777"
replace_line('core_main.c', 'main', 'core_main')
# os.system(f'cd "{LVGL_DIR}" && if ls -1d lvgl-* >/dev/null 2>&1; then echo "[lvgl] already exists, skip"; else wget https://github.com/lvgl/lvgl/archive/refs/tags/{LVGL_NAME} && tar -xf {LVGL_NAME} && echo "[lvgl] downloaded and extracted"; fi') def patch_coremark() -> None:
header = COREMARK_DIR / "coremark.h"
content = header.read_text(encoding="utf-8")
include = "#include <tinyprintf.h>"
if include not in content:
marker = '#include "core_portme.h"'
if marker not in content:
raise RuntimeError(f"CoreMark patch marker missing in {header}")
content = content.replace(marker, f"{marker}\n{include}", 1)
atomic_write(header, content)
source = COREMARK_DIR / "core_main.c"
content = source.read_text(encoding="utf-8")
if "core_main(" not in content:
if "main(" not in content:
raise RuntimeError(f"CoreMark entry point not found in {source}")
content = content.replace("/* Function: main", "/* Function: core_main", 1)
content = content.replace("\nmain(", "\ncore_main(")
atomic_write(source, content)
def main() -> int:
parser = argparse.ArgumentParser(description="Install pinned application dependencies")
parser.add_argument("--update", action="store_true")
args = parser.parse_args()
download_file(
FATFS_URL,
FATFS_ARCHIVE,
FATFS_SHA256,
update=args.update,
)
fatfs_source = FATFS_ARCHIVE.parent / "source"
if not fatfs_source.is_dir():
with ZipFile(FATFS_ARCHIVE) as archive:
archive.extractall(FATFS_ARCHIVE.parent)
for name in ("ffconf.h", "diskio.c"):
source = FATFS_DIR / name
if not source.is_file():
raise FileNotFoundError(f"FatFs configuration file missing: {source}")
shutil.copy2(source, fatfs_source / name)
ensure_git_repo(
"https://github.com/eembc/coremark.git",
COREMARK_DIR,
COREMARK_REVISION,
update=args.update,
allow_dirty=True,
)
patch_coremark()
print("application dependencies are ready")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+1
View File
@@ -11,6 +11,7 @@ void main() {
uart0_init(CPU_FREQ, UART_BPS); uart0_init(CPU_FREQ, UART_BPS);
#ifdef SW_BASE #ifdef SW_BASE
app_info(); app_info();
ip_archinfo_test(0, NULL);
#else #else
tinybooter(); tinybooter();
i2c0_init((uint8_t)(CPU_FREQ / 2 - 1)); i2c0_init((uint8_t)(CPU_FREQ / 2 - 1));
+35 -33
View File
@@ -1,43 +1,45 @@
#!/bin/python #!/usr/bin/env python3
import os import argparse
import subprocess import subprocess
import sys
from pathlib import Path
ROOT_PATH = os.getcwd()
def get_git_info(): ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import atomic_write # noqa: E402
def git_value(root: Path, *args: str, fallback: str = "unknown") -> str:
try: try:
branch = subprocess.check_output( return subprocess.check_output(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'], ["git", "-C", str(root), *args],
stderr=subprocess.STDOUT text=True,
).decode('utf-8').strip() stderr=subprocess.DEVNULL,
).strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return fallback
# get full commit hash
commit = subprocess.check_output(
['git', 'rev-parse', 'HEAD'],
stderr=subprocess.STDOUT
).decode('utf-8').strip()[0:6]
return branch, commit def main() -> int:
except subprocess.CalledProcessError: parser = argparse.ArgumentParser(description="Generate the firmware version header")
return None, None parser.add_argument("--root", type=Path, default=ROOT)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
root = args.root.resolve()
output = args.output or root / "crt/inc/socver.h"
branch, commit = get_git_info() branch = git_value(root, "rev-parse", "--abbrev-ref", "HEAD")
print(f"current branch: {branch}") commit = git_value(root, "rev-parse", "--short=6", "HEAD")
print(f"current commit: {commit}") template = (root / "crt/ver.tmpl").read_text(encoding="utf-8")
print(ROOT_PATH) content = template.replace("SOC_DEFAULT_BRANCH", branch).replace(
"SOC_DEFAULT_COMMIT", commit
)
changed = atomic_write(output, content)
print(f"version {branch}@{commit}: {'updated' if changed else 'unchanged'}")
return 0
res = ''
with open(f'{ROOT_PATH}/crt/ver.tmpl', 'r', encoding='utf-8') as fp:
file = fp.readlines()
for line in file: if __name__ == "__main__":
if 'SOC_DEFAULT_BRANCH' in line: raise SystemExit(main())
res += line.replace('SOC_DEFAULT_BRANCH', branch)
elif 'SOC_DEFAULT_COMMIT' in line:
res += line.replace('SOC_DEFAULT_COMMIT', commit)
else:
res += line
with open(f'{ROOT_PATH}/crt/inc/socver.h', 'w', encoding='utf-8') as fp:
fp.writelines(res)
+41 -7
View File
@@ -1,10 +1,44 @@
#!/bin/python #!/usr/bin/env python3
import os import argparse
import sys
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) from pathlib import Path
os.system(f'git clone https://github.com/IHP-GmbH/IHP-Open-PDK.git {SCRIPT_DIR}/IHP-Open-PDK') ROOT = Path(__file__).resolve().parents[1]
os.system(f'cd {SCRIPT_DIR}/IHP-Open-PDK && git checkout 68eebafcd9b2f5e92c69d37a8d3d90eb266550f5 && git submodule init') sys.path.insert(0, str(ROOT))
os.system(f'git clone --recursive --depth 1 https://github.com/openecos-projects/icsprout55-pdk.git {SCRIPT_DIR}/icsprout55-pdk') from scripts.setup_helpers import ensure_git_repo # noqa: E402
DEPENDENCIES = (
(
"https://github.com/IHP-GmbH/IHP-Open-PDK.git",
"IHP-Open-PDK",
"68eebafcd9b2f5e92c69d37a8d3d90eb266550f5",
),
(
"https://github.com/openecos-projects/icsprout55-pdk.git",
"icsprout55-pdk",
"e696e093129ca2212487aa169af74d06ebd86eb6",
),
)
def main() -> int:
parser = argparse.ArgumentParser(description="Install pinned open PDKs")
parser.add_argument("--update", action="store_true")
args = parser.parse_args()
pdk_dir = Path(__file__).resolve().parent
for url, name, revision in DEPENDENCIES:
ensure_git_repo(
url,
pdk_dir / name,
revision,
recursive=True,
update=args.update,
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+44 -6
View File
@@ -1,10 +1,48 @@
#!/bin/python #!/usr/bin/env python3
import os import argparse
import sys
from pathlib import Path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ip = ['archinfo', 'clint', 'common', 'crc', 'i2c', 'i2s', 'plic', 'ps2', 'pwm', 'rng', 'rtc', 'spi', 'timer', 'uart', 'wdg'] ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import ensure_git_repo # noqa: E402
for v in ip:
os.system(f'git clone https://github.com/retroSoC/{v}.git {SCRIPT_DIR}/{v}') REVISIONS = {
"archinfo": "d65dd13c83a1176668bf5178004814a1551bf6ba",
"clint": "ce49f1a5501c0a9f18c0e3f358417648f3f2fb18",
"common": "d9921eb05160be873aebc46bb2f820bee2c7eebc",
"crc": "4654a63d06d487b5b9fe22af228c36c736b90bad",
"i2c": "73b024547c5cd70fb8bf801cc7757db2536c604f",
"i2s": "bd0978e8b45e4cf4c1335a1fa7311385d2a4e805",
"plic": "c851d93829d2e75b224003a058e718925855c0cc",
"ps2": "60921e0f72d6113f8615d03ce54567388fc2410f",
"pwm": "0777ef085c661d2b103f2fa0f8519f97eea3c950",
"rng": "7374e1e7f25bfd1ff9954ea99f19b77a583def29",
"rtc": "69963b8424cb00e04c8be70157b8ca738464e342",
"spi": "515cb408806584525605b4747db5e890dbdf1928",
"timer": "0c44b0d8b742c6a477fda15a812cf22962b54dd8",
"uart": "eca12213ba06f42e0a6dd0f27907fd4c447e2774",
"wdg": "b2603c1faeba96603392f0fe9e692e645d7e061f",
}
def main() -> int:
parser = argparse.ArgumentParser(description="Install pinned cluster IP repositories")
parser.add_argument("--update", action="store_true")
args = parser.parse_args()
destination = Path(__file__).resolve().parent
for name, revision in REVISIONS.items():
ensure_git_repo(
f"https://github.com/retroSoC/{name}.git",
destination / name,
revision,
update=args.update,
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+27 -4
View File
@@ -1,7 +1,30 @@
#!/bin/python #!/usr/bin/env python3
import os import argparse
import sys
from pathlib import Path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
os.system(f'git clone https://github.com/retroSoC/3rd-party.git {SCRIPT_DIR}/3rd-party') ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import ensure_git_repo # noqa: E402
REVISION = "d991ec9f7bf696e7e5d840758487068b11bd4121"
def main() -> int:
parser = argparse.ArgumentParser(description="Install pinned third-party IP")
parser.add_argument("--update", action="store_true")
args = parser.parse_args()
ensure_git_repo(
"https://github.com/retroSoC/3rd-party.git",
Path(__file__).resolve().parent / "3rd-party",
REVISION,
update=args.update,
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+16 -5
View File
@@ -1,7 +1,16 @@
$(info RTL_PATH: $(RTL_PATH)) GENERATED_FL_DIR := $(RTL_PATH)/.generated_fl
MPW_STAMP := $(RTL_PATH)/mpw/.build/.prepared
gen_mpw_code: $(MPW_STAMP):
python3 $(RTL_PATH)/mpw/setup.py @test -d $(RTL_PATH)/mpw/.git || { \
echo "missing $(RTL_PATH)/mpw; run 'make setup-mpw' first" >&2; \
exit 1; \
}
python3 $(ROOT_PATH)/scripts/prepare_mpw.py
@mkdir -p $(@D)
@touch $@
gen_mpw_code: $(MPW_STAMP)
ifeq ($(IP), MDD) ifeq ($(IP), MDD)
@echo "[mpw] generating user ip ..." @echo "[mpw] generating user ip ..."
python3 $(RTL_PATH)/mpw/ip.py python3 $(RTL_PATH)/mpw/ip.py
@@ -18,7 +27,9 @@ else
endif endif
generate_filelist: generate_filelist:
python3 $(RTL_PATH)/script/generate_filelist.py python3 $(RTL_PATH)/script/generate_filelist.py \
--output-dir $(GENERATED_FL_DIR) \
$(foreach define,$(DEF_LIST),--define $(define))
# software # software
include $(RTL_PATH)/mk/software.mk include $(RTL_PATH)/mk/software.mk
@@ -67,4 +78,4 @@ else ifeq ($(SIMU), IVERILOG)
include $(RTL_PATH)/mk/iverilog.mk include $(RTL_PATH)/mk/iverilog.mk
endif endif
.PHONY: clean firmware .PHONY: gen_mpw_code generate_filelist firmware
+21 -15
View File
@@ -1,19 +1,25 @@
#!/bin/python #!/usr/bin/env python3
import os import sys
from pathlib import Path
ROOT_PATH = os.getcwd()
lines = '' ROOT = Path(__file__).resolve().parents[3]
# remove parallel case statement sys.path.insert(0, str(ROOT))
with open(f'{ROOT_PATH}/rtl/mini/core/picorv32.v', 'r', encoding='utf-8') as fp: from scripts.setup_helpers import atomic_write # noqa: E402
for v in fp:
if '// synopsys parallel_case' in v:
lines += v.replace('// synopsys parallel_case', '')
elif '// synopsys full_case parallel_case' in v:
lines += v.replace('// synopsys full_case parallel_case', '')
else:
lines += v
with open(f'{ROOT_PATH}/rtl/mini/core/picorv32_ver.v', 'w', encoding='utf-8') as fp:
fp.writelines(lines) def main() -> int:
core_dir = Path(__file__).resolve().parent
source = core_dir / "picorv32.v"
destination = core_dir / "picorv32_ver.v"
content = source.read_text(encoding="utf-8")
content = content.replace("// synopsys full_case parallel_case", "")
content = content.replace("// synopsys parallel_case", "")
changed = atomic_write(destination, content)
print(f"{'updated' if changed else 'unchanged'}: {destination}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+76 -58
View File
@@ -1,76 +1,94 @@
# tools & paths IVERILOG ?= iverilog
SIM_TOOL := iverilog VVP ?= vvp
SIM_BINY := vvp simv -fst GTKWAVE ?= gtkwave
GTKWAVE_TOOL := gtkwave
COMP_LOG := compile.log
SIM_LOG := sim.log
# netlist file path
NETLIST_PATH := $(ROOT_PATH)/syn/yosys/.synth_build/out/retrosoc_asic_yosys.v
POST_PATH := $(ROOT_PATH)/pd/sdf/retrosoc_asic.v
SDF_PATH := $(ROOT_PATH)/pd/sdf/retrosoc_asic_CTS_MIN.sdf.gz
# testbench filelist
TB_FLIST := -f $(RTL_PATH)/.generate_verilogd_fl/tb.fl
NETLIST_PATH ?= $(ROOT_PATH)/syn/yosys/.synth_build/out/retrosoc_asic_yosys.v
POST_PATH ?= $(ROOT_PATH)/pd/sdf/retrosoc_asic.v
SDF_PATH ?= $(ROOT_PATH)/pd/sdf/retrosoc_asic_CTS_MIN.sdf.gz
SDF_SCOPE ?= $(RTL_TOP).u_retrosoc_asic
# --- Compilation Flags --- IVERILOG_ROOT := $(RTL_PATH)/.iverilog_build
COMMON_OPTS := -g2012 IVERILOG_BEHV_DIR := $(IVERILOG_ROOT)/behv
IVERILOG_NETL_DIR := $(IVERILOG_ROOT)/netl
IVERILOG_POST_DIR := $(IVERILOG_ROOT)/post
IVERILOG_BEHV_FLIST := $(IVERILOG_BEHV_DIR)/iverilog.fl
IVERILOG_NETL_FLIST := $(IVERILOG_NETL_DIR)/iverilog.fl
IVERILOG_POST_FLIST := $(IVERILOG_POST_DIR)/iverilog.fl
CONVERTED_SOC := $(IVERILOG_BEHV_DIR)/converted_soc.v
POST_OPTS := -ghello IVERILOG_COMMON_OPTS := -g2012
IVERILOG_TIME_OPTS := -gno-specify
IVERILOG_POST_OPTS := -gspecify -Tmin
IVERILOG_SIM_OPTS := +$(RTL_SIM_PLLEN) +$(RTL_SIM_PLLCFG) \
+core_sel=$(RTL_SIM_CORESEL) +sim_timeout=$(RTL_SIM_TIMEOUT) +wave_$(WAVE)
TIME_OPTS := -gno-specify $(CONVERTED_SOC): gen_mpw_code generate_filelist
SIM_OPTS := +$(RTL_SIM_PLLEN) +$(RTL_SIM_PLLCFG) +core_sel=$(RTL_SIM_CORESEL) +sim_timeout=$(RTL_SIM_TIMEOUT) \ @mkdir -p $(@D)
+wave_$(WAVE) python3 $(RTL_PATH)/script/convt_sv2v.py $(RTL_FLIST) --output $@
$(IVERILOG_BEHV_FLIST): generate_filelist $(CONVERTED_SOC)
@mkdir -p $(@D)
python3 $(RTL_PATH)/script/gen_iverilog_filelist.py \
--mode behv --pdk $(PDK) --output $@ --converted $(CONVERTED_SOC)
comp: DIR := .iverilog_build/behv $(IVERILOG_NETL_FLIST): generate_filelist
comp: FLIST := -f $(RTL_PATH)/.generated_fl/iverilog.fl @mkdir -p $(@D)
comp: OPTS := $(TIME_OPTS) python3 $(RTL_PATH)/script/gen_iverilog_filelist.py \
--mode netl --pdk $(PDK) --output $@ --netlist $(NETLIST_PATH)
netcomp: DIR := .iverilog_build/netl $(IVERILOG_POST_FLIST): generate_filelist
netcomp: FLIST := -f $(RTL_PATH)/.generated_fl/iverilog.fl @mkdir -p $(@D)
netcomp: OPTS := $(TIME_OPTS) python3 $(RTL_PATH)/script/gen_iverilog_filelist.py \
--mode post --pdk $(PDK) --output $@ --netlist $(POST_PATH) --sdf $(SDF_PATH) \
--sdf-scope $(SDF_SCOPE)
postcomp: DIR := .iverilog_build/post convt_sv2v: $(CONVERTED_SOC)
postcomp: FLIST := $(NET_FLIST) $(TB_FLIST) gen_iverilog_filelist: $(IVERILOG_BEHV_FLIST)
postcomp: OPTS := $(POST_OPTS)
sim: DIR := .iverilog_build/behv comp: $(IVERILOG_BEHV_FLIST)
netsim: DIR := .iverilog_build/netl @mkdir -p $(IVERILOG_BEHV_DIR)
postsim: DIR := .iverilog_build/post @cd $(IVERILOG_BEHV_DIR) && set -o pipefail && \
$(IVERILOG) $(IVERILOG_COMMON_OPTS) $(IVERILOG_TIME_OPTS) -f $< \
-o simv -s $(RTL_TOP) 2>&1 | tee compile.log
wave: DIR := .iverilog_build/behv netcomp: $(IVERILOG_NETL_FLIST)
netwave: DIR := .iverilog_build/netl @mkdir -p $(IVERILOG_NETL_DIR)
postwave: DIR := .iverilog_build/post @cd $(IVERILOG_NETL_DIR) && set -o pipefail && \
$(IVERILOG) $(IVERILOG_COMMON_OPTS) $(IVERILOG_TIME_OPTS) -f $< \
-o simv -s $(RTL_TOP) 2>&1 | tee compile.log
postcomp: $(IVERILOG_POST_FLIST)
@mkdir -p $(IVERILOG_POST_DIR)
@cd $(IVERILOG_POST_DIR) && set -o pipefail && \
$(IVERILOG) $(IVERILOG_COMMON_OPTS) $(IVERILOG_POST_OPTS) -f $< \
-o simv -s $(RTL_TOP) -s retrosoc_sdf_annotator 2>&1 | tee compile.log
sim: comp prepare_norflash sim: comp
netsim: netcomp prepare_norflash python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_BEHV_DIR) \
postsim: postcomp prepare_norflash --firmware $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).hex
@cd $(IVERILOG_BEHV_DIR) && set -o pipefail && \
stdbuf -oL -eL $(VVP) simv -fst $(IVERILOG_SIM_OPTS) 2>&1 | tee sim.log
convt_sv2v: generate_filelist netsim: netcomp
@mkdir -p $(RTL_PATH)/$(DIR) python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_NETL_DIR) \
python3 $(RTL_PATH)/script/convt_sv2v.py $(RTL_FLIST) --firmware $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).hex
@cd $(IVERILOG_NETL_DIR) && set -o pipefail && \
stdbuf -oL -eL $(VVP) simv -fst $(IVERILOG_SIM_OPTS) 2>&1 | tee sim.log
gen_iverilog_filelist: postsim: postcomp
@mkdir -p $(RTL_PATH)/$(DIR) python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_POST_DIR) \
python3 $(RTL_PATH)/script/gen_iverilog_filelist.py $(MAKECMDGOALS) $(PDK) $(NETLIST_PATH) --firmware $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).hex
@cd $(IVERILOG_POST_DIR) && set -o pipefail && \
stdbuf -oL -eL $(VVP) simv -fst $(IVERILOG_SIM_OPTS) 2>&1 | tee sim.log
prepare_norflash: wave:
@mkdir -p $(RTL_PATH)/$(DIR) cd $(IVERILOG_BEHV_DIR) && $(GTKWAVE) $(RTL_TOP).fst &
python3 $(RTL_PATH)/script/prepare_norflash.py $(MAKECMDGOALS) netwave:
cd $(IVERILOG_NETL_DIR) && $(GTKWAVE) $(RTL_TOP).fst &
comp netcomp postcomp: convt_sv2v gen_iverilog_filelist postwave:
@mkdir -p $(RTL_PATH)/$(DIR) cd $(IVERILOG_POST_DIR) && $(GTKWAVE) $(RTL_TOP).fst &
cd $(RTL_PATH)/$(DIR) && ($(SIM_TOOL) $(COMMON_OPTS) $(OPTS) $(FLIST) -o simv -s $(RTL_TOP)) 2>&1 | tee $(COMP_LOG)
sim netsim postsim:
cd $(RTL_PATH)/$(DIR) && (stdbuf -oL -eL $(SIM_BINY) $(SIM_OPTS)) 2>&1 | tee $(SIM_LOG)
wave netwave postwave:
cd $(RTL_PATH)/$(DIR) && ($(GTKWAVE_TOOL) $(RTL_TOP).fst &)
clean: clean:
rm -rf .iverilog_build/behv $(RTL_PATH)/.iverilog_build/netl $(RTL_PATH)/.iverilog_build/post rm -rf $(IVERILOG_ROOT)
.PHONY: comp netcomp postcomp sim netsim postsim wave clean .PHONY: convt_sv2v gen_iverilog_filelist comp netcomp postcomp sim netsim postsim wave netwave postwave clean
+16 -15
View File
@@ -1,5 +1,5 @@
# software # software
CROSS=riscv32-unknown-elf- CROSS ?= riscv32-unknown-elf-
CP = $(CROSS)cpp CP = $(CROSS)cpp
CC = $(CROSS)gcc CC = $(CROSS)gcc
@@ -108,7 +108,7 @@ INC_PATH := -I$(ROOT_PATH)/crt/inc \
ifneq ($(findstring MDD,$(CORE) $(IP)),) ifneq ($(findstring MDD,$(CORE) $(IP)),)
INC_PATH += -I$(ROOT_PATH)/rtl/min/mpw/.build INC_PATH += -I$(ROOT_PATH)/rtl/mini/mpw/.build
endif endif
@@ -129,22 +129,23 @@ ifneq ($(filter RV32E RV32I,$(ISA)),)
SRC_PATH += $(ROOT_PATH)/crt/libgcc/mulsi3.c SRC_PATH += $(ROOT_PATH)/crt/libgcc/mulsi3.c
endif endif
LDS_PATH := $(ROOT_PATH)/crt/./lds/$(LINK_TYPE).lds LDS_PATH := $(ROOT_PATH)/crt/lds/$(LINK_TYPE).lds
upd_ver_info: upd_ver_info:
python3 crt/ver.py python3 $(ROOT_PATH)/crt/ver.py --root $(ROOT_PATH)
asm: gen_mpw_code asm: gen_mpw_code
cd app/asm && make @mkdir -p $(ROOT_PATH)/.sw_build
cp -rf app/asm/hello-asm.flash .sw_build/retrosoc_fw.hex $(MAKE) -C $(ROOT_PATH)/app/asm
cp -rf app/asm/hello-asm.bin .sw_build/retrosoc_fw.bin cp $(ROOT_PATH)/app/asm/hello-asm.flash $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).hex
cp -rf app/asm/hello-asm.txt .sw_build/retrosoc_fw_all.txt cp $(ROOT_PATH)/app/asm/hello-asm.bin $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).bin
cp $(ROOT_PATH)/app/asm/hello-asm.txt $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME)_all.txt
firmware: gen_mpw_code upd_ver_info firmware: gen_mpw_code upd_ver_info
@mkdir -p .sw_build @mkdir -p $(ROOT_PATH)/.sw_build
cd .sw_build && ($(CP) -P -o $(LINK_TYPE).lds $(LDS_PATH)) cd $(ROOT_PATH)/.sw_build && $(CP) -P -o $(LINK_TYPE).lds $(LDS_PATH)
cd .sw_build && ($(CC) $(CFLAGS) $(INC_PATH) -o $@ $(SRC_PATH)) cd $(ROOT_PATH)/.sw_build && $(CC) $(CFLAGS) $(INC_PATH) -o $@ $(SRC_PATH)
cd .sw_build && ($(OBJC) -O verilog $@ $(FIRMWARE_NAME).hex) cd $(ROOT_PATH)/.sw_build && $(OBJC) -O verilog $@ $(FIRMWARE_NAME).hex
cd .sw_build && ($(OBJC) -O binary $@ $(FIRMWARE_NAME).bin) cd $(ROOT_PATH)/.sw_build && $(OBJC) -O binary $@ $(FIRMWARE_NAME).bin
cd .sw_build && ($(DUMP) -d $@ > $(FIRMWARE_NAME).txt) cd $(ROOT_PATH)/.sw_build && $(DUMP) -d $@ > $(FIRMWARE_NAME).txt
cd .sw_build && ($(DUMP) -D $@ > $(FIRMWARE_NAME)_all.txt) cd $(ROOT_PATH)/.sw_build && $(DUMP) -D $@ > $(FIRMWARE_NAME)_all.txt
+14 -10
View File
@@ -1,9 +1,12 @@
# tools & paths # tools & paths
NOVAS := /nfs/tools/synopsys/verdi/V-2023.12-SP1-1/share/PLI/VCS/LINUX64 NOVAS ?= /nfs/tools/synopsys/verdi/V-2023.12-SP1-1/share/PLI/VCS/LINUX64
EXTRA := -P $(NOVAS)/novas.tab $(NOVAS)/pli.a VCS_RUNNER ?= bsub -Is
SIM_TOOL := bsub -Is vcs VCS ?= vcs
SIM_BINY := bsub -Is ./simv VERDI ?= verdi
VERDI_TOOL := bsub -Is verdi EXTRA ?= -P $(NOVAS)/novas.tab $(NOVAS)/pli.a
SIM_TOOL := $(VCS_RUNNER) $(VCS)
SIM_BINY := $(VCS_RUNNER) ./simv
VERDI_TOOL := $(VCS_RUNNER) $(VERDI)
COMP_LOG := -l compile.log COMP_LOG := -l compile.log
SIM_LOG := -l sim.log SIM_LOG := -l sim.log
# netlist file path # netlist file path
@@ -56,7 +59,7 @@ netcomp: FLIST := $(NETLIST_PATH) $(NET_FLIST) $(TB_FLIST)
netcomp: OPTS := $(TIME_OPTION) netcomp: OPTS := $(TIME_OPTION)
postcomp: DIR := .vcs_build/post postcomp: DIR := .vcs_build/post
postcomp: FLIST := $(NET_FLIST) $(TB_FLIST) postcomp: FLIST := $(POST_PATH) $(NET_FLIST) $(TB_FLIST)
postcomp: OPTS := $(POST_OPTS) postcomp: OPTS := $(POST_OPTS)
sim: DIR := .vcs_build/behv sim: DIR := .vcs_build/behv
@@ -72,18 +75,19 @@ sim: comp
netsim: netcomp netsim: netcomp
postsim: postcomp postsim: postcomp
comp netcomp postcomp: gen_mpw_code generate_filelist
comp netcomp postcomp: comp netcomp postcomp:
@mkdir -p $(RTL_PATH)/$(DIR) @mkdir -p $(RTL_PATH)/$(DIR)
cd $(RTL_PATH)/$(DIR) && ($(SIM_TOOL) $(COMMON_OPTS) $(OPTS) $(FLIST) -top $(RTL_TOP) $(COMP_LOG)) cd $(RTL_PATH)/$(DIR) && $(SIM_TOOL) $(COMMON_OPTS) $(OPTS) $(FLIST) -top $(RTL_TOP) $(COMP_LOG)
sim netsim postsim: sim netsim postsim:
cd $(RTL_PATH)/$(DIR) && ($(SIM_BINY) $(SIM_OPTS) $(if $(filter netsim postsim,$@),+bus_conflict_off) $(SIM_LOG)) cd $(RTL_PATH)/$(DIR) && $(SIM_BINY) $(SIM_OPTS) $(if $(filter netsim postsim,$@),+bus_conflict_off) $(SIM_LOG)
wave netwave postwave: wave netwave postwave:
cd $(RTL_PATH)/$(DIR) && ($(VERDI_TOOL) -ssf $(RTL_TOP).fsdb -nologo &) cd $(RTL_PATH)/$(DIR) && ($(VERDI_TOOL) -ssf $(RTL_TOP).fsdb -nologo &)
clean: clean:
rm -rf .vcs_build/behv $(RTL_PATH)/.vcs_build/netl $(RTL_PATH)/.vcs_build/post rm -rf $(RTL_PATH)/.vcs_build
.PHONY: comp netcomp postcomp sim netsim postsim wave clean .PHONY: comp netcomp postcomp sim netsim postsim wave clean
+16 -10
View File
@@ -1,7 +1,7 @@
BUILD_DIR := $(RTL_PATH)/.verilator_build BUILD_DIR := $(RTL_PATH)/.verilator_build
SOC_CSRC_HOME += $(RTL_PATH)/csrc SOC_CSRC_HOME += $(RTL_PATH)/csrc
SOC_CSRC_LIB_HOME += $(RTL_PATH)/csrc SOC_CSRC_LIB_HOME += $(RTL_PATH)/csrc
SOC_CXXFILES += $(shell find $(SOC_CSRC_HOME) -name "*.cpp") SOC_CXXFILES += $(sort $(wildcard $(SOC_CSRC_HOME)/*.cpp))
SOC_CSRC_INCLPATH += -I$(SOC_CSRC_HOME) SOC_CSRC_INCLPATH += -I$(SOC_CSRC_HOME)
SOC_CSRC_INCLPATH += $(foreach val, $(SOC_CSRC_LIB_HOME), -I$(val)) SOC_CSRC_INCLPATH += $(foreach val, $(SOC_CSRC_LIB_HOME), -I$(val))
@@ -18,7 +18,8 @@ SOC_VXXFILES += $(RTL_PATH)/vsrc/QSPIFlash.sv
SOC_VXXFILES += $(RTL_PATH)/vsrc/retrosoc_top.sv SOC_VXXFILES += $(RTL_PATH)/vsrc/retrosoc_top.sv
SOC_VSRC_INCLPATH += -I$(SOC_VSRC_HOME) SOC_VSRC_INCLPATH += -I$(SOC_VSRC_HOME)
VERILATOR_CXXFLAGS += -std=c++17 -static -Wall $(SOC_CSRC_INCLPATH) -DDUMP_WAVE_FST VERILATOR ?= verilator
VERILATOR_CXXFLAGS += -std=c++17 -Wall $(SOC_CSRC_INCLPATH) -DDUMP_WAVE_FST
VERILATOR_FLAGS += --cc --exe --no-timing --top-module $(SOC_VSRC_TOP) VERILATOR_FLAGS += --cc --exe --no-timing --top-module $(SOC_VSRC_TOP)
VERILATOR_FLAGS += --x-assign unique -O3 -CFLAGS "$(VERILATOR_CXXFLAGS)" VERILATOR_FLAGS += --x-assign unique -O3 -CFLAGS "$(VERILATOR_CXXFLAGS)"
VERILATOR_FLAGS += --trace-fst --assert --stats-vars --output-split 30000 --output-split-cfuncs 30000 VERILATOR_FLAGS += --trace-fst --assert --stats-vars --output-split 30000 --output-split-cfuncs 30000
@@ -27,28 +28,33 @@ VERILATOR_FLAGS += -o $(BUILD_DIR)/emu
VERILATOR_FLAGS += -Mdir $(SOC_COMPILE_HOME) VERILATOR_FLAGS += -Mdir $(SOC_COMPILE_HOME)
VERILATOR_FLAGS += $(SOC_VSRC_INCLPATH) $(SOC_CXXFILES) $(SOC_VXXFILES) VERILATOR_FLAGS += $(SOC_VSRC_INCLPATH) $(SOC_CXXFILES) $(SOC_VXXFILES)
SOC_SIM_TIME ?= -1 SOC_SIM_TIME ?= 40
CCACHE := $(if $(shell which ccache),ccache,) CCACHE := $(shell command -v ccache 2>/dev/null)
ifneq ($(CCACHE),) ifneq ($(CCACHE),)
export OBJCACHE = ccache export OBJCACHE = ccache
export CCACHE_DIR = $(BUILD_DIR)/ccache
export CCACHE_TEMPDIR = $(BUILD_DIR)/ccache/tmp
endif endif
lint: gen_mpw_code generate_filelist lint: gen_mpw_code generate_filelist
@mkdir -p $(BUILD_DIR) @mkdir -p $(BUILD_DIR)
@mkdir -p $(BUILD_DIR)/ccache/tmp
comp: lint comp: lint
# verilator $(VERILATOR_FLAGS) $(VERILATOR) $(VERILATOR_FLAGS) > $(BUILD_DIR)/verilating.log 2>&1
verilator $(VERILATOR_FLAGS) > $(BUILD_DIR)/verilating.log 2>&1 +$(MAKE) VM_PARALLEL_BUILDS=1 OPT_FAST="-O3" -C $(SOC_COMPILE_HOME) \
$(MAKE) VM_PARALLEL_BUILDS=1 OPT_FAST="-O3" -C $(SOC_COMPILE_HOME) -f V$(SOC_VSRC_TOP).mk -j$(nproc) > $(BUILD_DIR)/compile.log 2>&1 -f V$(SOC_VSRC_TOP).mk > $(BUILD_DIR)/compile.log 2>&1
sim: comp sim: comp
$(BUILD_DIR)/emu -i .sw_build/retrosoc_fw.bin -s $(RTL_SIM_CORESEL) -t 40 @test -f $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).bin || { \
echo "firmware image missing; run 'make firmware' first" >&2; exit 1; }
$(BUILD_DIR)/emu -i $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).bin \
-s $(RTL_SIM_CORESEL) -t $(SOC_SIM_TIME)
wave: wave:
clean: clean:
rm -rf .verilator_build rm -rf $(BUILD_DIR)
.PHONY: comp sim clean .PHONY: comp sim clean
+26 -17
View File
@@ -1,24 +1,33 @@
#!/bin/python #!/usr/bin/env python3
import sys from __future__ import annotations
import os
import argparse
from pathlib import Path from pathlib import Path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) from filelist import parse_filelists, write_filelist
MINI_DIR = os.path.abspath(f'{SCRIPT_DIR}/..')
GEN_DIR = os.path.abspath(f'{MINI_DIR}/.generated_fl')
res = f'{GEN_DIR}/yosys.fl'
is_first = True
for v in sys.argv[1:]: DEFAULT_OUTPUT = Path(__file__).resolve().parent.parent / ".generated_fl" / "yosys.fl"
if '-f' not in v:
if 'pdk' in v: continue
print(f'gen code: {v}')
if is_first: def parse_args() -> argparse.Namespace:
os.system(f'cat {v} > {res}') parser = argparse.ArgumentParser(description="Combine synthesis filelists")
is_first = False parser.add_argument("-f", "--filelist", action="append", type=Path, default=[])
else: parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
os.system(f'cat {v} >> {res}') return parser.parse_args()
def main() -> int:
args = parse_args()
selected = [path for path in args.filelist if not path.name.startswith("pdk_")]
if not selected:
raise SystemExit("at least one non-PDK -f/--filelist is required")
combined = parse_filelists(selected)
write_filelist(args.output.resolve(), combined)
print(f"generated synthesis filelist: {args.output.resolve()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+31 -31
View File
@@ -1,38 +1,38 @@
#!/bin/python #!/usr/bin/env python3
import os from __future__ import annotations
import sys
import argparse
import subprocess import subprocess
from pathlib import Path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) from filelist import parse_filelists
MINI_DIR = os.path.abspath(f'{SCRIPT_DIR}/..')
GEN_DIR = os.path.abspath(f'{MINI_DIR}/.iverilog_build/behv')
sv2v_cmd = 'sv2v' def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Convert the behavioral RTL with sv2v")
for v in sys.argv[1:]: parser.add_argument("-f", "--filelist", action="append", type=Path, default=[])
if '-f' not in v: parser.add_argument("--output", type=Path, required=True)
if 'pdk' in v: continue parser.add_argument("--sv2v", default="sv2v", help="sv2v executable")
# print(f'filelist: {v}') return parser.parse_args()
try:
result = subprocess.run(
f"sed -e 's/+incdir+/-I/g' -e 's/+define+/-D/g' -e 's/^-v/-y/g' {v}",
shell=True,
capture_output=True,
text=True,
check=True
)
sed_output = result.stdout
# print(sed_output.split('\n'))
lines = sed_output.split('\n')
for v in lines:
if v != '':
sv2v_cmd = f'{sv2v_cmd} {v}'
except Exception as e:
print(f"fail:{str(e)}")
sv2v_cmd = f'{sv2v_cmd} --write {GEN_DIR}/converted_soc.v' def main() -> int:
# print(f'sv2v_cmd: {sv2v_cmd}') args = parse_args()
os.system(sv2v_cmd) selected = [path for path in args.filelist if not path.name.startswith("pdk_")]
if not selected:
raise SystemExit("at least one non-PDK -f/--filelist is required")
filelist = parse_filelists(selected)
args.output.parent.mkdir(parents=True, exist_ok=True)
command = [args.sv2v]
command.extend(f"-D{item[len('+define+'):]}" for item in filelist.defines)
command.extend(f"-I{path}" for path in filelist.incdirs)
command.extend(str(path) for path in filelist.files)
command.extend(("--write", str(args.output.resolve())))
subprocess.run(command, check=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
from __future__ import annotations
import os
import shlex
import tempfile
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterable
@dataclass
class FileList:
defines: list[str] = field(default_factory=list)
incdirs: list[Path] = field(default_factory=list)
files: list[Path] = field(default_factory=list)
library_files: list[Path] = field(default_factory=list)
options: list[str] = field(default_factory=list)
def extend(self, other: "FileList") -> None:
self.defines.extend(other.defines)
self.incdirs.extend(other.incdirs)
self.files.extend(other.files)
self.library_files.extend(other.library_files)
self.options.extend(other.options)
def deduplicate(self) -> "FileList":
self.defines = _unique(self.defines)
self.incdirs = _unique(self.incdirs)
self.files = _unique(self.files)
self.library_files = _unique(self.library_files)
self.options = _unique(self.options)
return self
def as_tokens(self) -> list[str]:
return [
*self.defines,
*(_quote(f"+incdir+{path}") for path in self.incdirs),
*(item for path in self.library_files for item in ("-v", _quote(str(path)))),
*self.options,
*(_quote(str(path)) for path in self.files),
]
def _unique(values: Iterable):
return list(dict.fromkeys(values))
def _quote(value: str) -> str:
if not any(character.isspace() for character in value):
return value
return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
def atomic_write(path: Path, content: str) -> bool:
"""Write content atomically and preserve mtime when content is unchanged."""
path = Path(path)
if path.exists() and path.read_text(encoding="utf-8") == content:
return False
path.parent.mkdir(parents=True, exist_ok=True)
fd, temp_name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
with os.fdopen(fd, "w", encoding="utf-8", newline="") as stream:
stream.write(content)
os.replace(temp_name, path)
except Exception:
Path(temp_name).unlink(missing_ok=True)
raise
return True
def tokenize_filelist(path: Path) -> list[str]:
tokens: list[str] = []
for line_number, raw_line in enumerate(
path.read_text(encoding="utf-8").splitlines(), start=1
):
stripped = raw_line.strip()
if not stripped or stripped.startswith("//"):
continue
try:
tokens.extend(shlex.split(raw_line, comments=True, posix=True))
except ValueError as error:
raise ValueError(f"{path}:{line_number}: {error}") from error
return tokens
def parse_filelists(paths: Iterable[Path], *, require_files: bool = True) -> FileList:
result = FileList()
visited: set[Path] = set()
for path in paths:
result.extend(
_parse_filelist(Path(path).resolve(), visited, require_files=require_files)
)
return result.deduplicate()
def _parse_filelist(
path: Path, visited: set[Path], *, require_files: bool
) -> FileList:
if path in visited:
return FileList()
if not path.is_file():
raise FileNotFoundError(f"filelist not found: {path}")
visited.add(path)
result = FileList()
tokens = tokenize_filelist(path)
index = 0
while index < len(tokens):
token = tokens[index]
if token == "-f":
index += 1
if index >= len(tokens):
raise ValueError(f"{path}: -f requires a filelist path")
nested = _resolve(tokens[index], path.parent)
result.extend(
_parse_filelist(nested, visited, require_files=require_files)
)
elif token.startswith("-f") and len(token) > 2:
nested = _resolve(token[2:], path.parent)
result.extend(
_parse_filelist(nested, visited, require_files=require_files)
)
elif token.startswith("+define+"):
result.defines.append(token)
elif token.startswith("+incdir+"):
incdir = _resolve(token[len("+incdir+") :], path.parent)
if require_files and not incdir.is_dir():
raise FileNotFoundError(f"include directory not found: {incdir}")
result.incdirs.append(incdir)
elif token == "-v":
index += 1
if index >= len(tokens):
raise ValueError(f"{path}: -v requires a source path")
library_file = _resolve(tokens[index], path.parent)
_require_source(library_file, require_files)
result.library_files.append(library_file)
elif token.startswith("-") or token.startswith("+"):
result.options.append(token)
else:
source = _resolve(token, path.parent)
_require_source(source, require_files)
result.files.append(source)
index += 1
return result
def _resolve(value: str, base: Path) -> Path:
path = Path(value).expanduser()
return path.resolve() if path.is_absolute() else (base / path).resolve()
def _require_source(path: Path, required: bool) -> None:
if required and not path.is_file():
raise FileNotFoundError(f"source file not found: {path}")
def write_filelist(path: Path, filelist: FileList) -> bool:
content = "\n".join(filelist.as_tokens()) + "\n"
return atomic_write(path, content)
+73 -33
View File
@@ -1,44 +1,84 @@
#!/bin/python #!/usr/bin/env python3
import sys from __future__ import annotations
import os
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) import argparse
MINI_DIR = os.path.abspath(f'{SCRIPT_DIR}/..') import gzip
GEN_DIR = os.path.abspath(f'{MINI_DIR}/.generated_fl') from pathlib import Path
cmd = ['+timescale+1ns/1ps\n'] from filelist import FileList, atomic_write, parse_filelists, write_filelist
filelist = ['def', 'sys_def', 'inc', 'tb']
for file in filelist:
with open(f'{GEN_DIR}/{file}.fl', 'r', encoding='utf-8') as fp:
tmp = fp.readlines()
cmd += tmp
tgt = sys.argv[1] SCRIPT_DIR = Path(__file__).resolve().parent
pdk = sys.argv[2] MINI_DIR = SCRIPT_DIR.parent
netl = sys.argv[3] DEFAULT_GENERATED_DIR = MINI_DIR / ".generated_fl"
print(f'tgt: {tgt}')
print(f'pdk: {pdk}')
print(f'netl: {netl}')
if tgt == 'sim':
cmd += [f'{MINI_DIR}/.iverilog_build/behv/converted_soc.v\n']
elif tgt == 'netsim': def parse_args() -> argparse.Namespace:
with open(f'{GEN_DIR}/commonip.fl', 'r', encoding='utf-8') as fp: parser = argparse.ArgumentParser(description="Generate an Icarus Verilog filelist")
tmp = fp.readlines() parser.add_argument("--mode", required=True, choices=("behv", "netl", "post"))
cmd += tmp parser.add_argument("--pdk", required=True)
cmd += f'{netl}\n' parser.add_argument("--generated-dir", type=Path, default=DEFAULT_GENERATED_DIR)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--converted", type=Path)
parser.add_argument("--netlist", type=Path)
parser.add_argument("--sdf", type=Path)
parser.add_argument("--sdf-scope", default="retrosoc_tb.u_retrosoc_asic")
return parser.parse_args()
elif tgt == 'postsim':
pass
with open(f'{GEN_DIR}/pdk_{pdk.lower()}.fl', 'r', encoding='utf-8') as fp: def _require(path: Path | None, description: str) -> Path:
tmp = fp.readlines() if path is None or not path.is_file():
cmd += tmp raise FileNotFoundError(f"{description} not found: {path}")
return path.resolve()
with open(f'{GEN_DIR}/iverilog.fl', 'w', encoding='utf-8') as fp:
fp.writelines(cmd)
def _prepare_sdf(source: Path, output_dir: Path, scope: str) -> Path:
sdf_output = output_dir / "timing.sdf"
if source.suffix == ".gz":
content = gzip.open(source, "rt", encoding="utf-8", errors="replace").read()
else:
content = source.read_text(encoding="utf-8", errors="replace")
atomic_write(sdf_output, content)
annotator = output_dir / "sdf_annotator.sv"
escaped_sdf = str(sdf_output.resolve()).replace("\\", "\\\\").replace('"', '\\"')
atomic_write(
annotator,
"module retrosoc_sdf_annotator;\n"
" initial begin\n"
f' $sdf_annotate("{escaped_sdf}", {scope}, , "sdf.log", "MINIMUM");\n'
" end\n"
"endmodule\n",
)
return annotator
def main() -> int:
args = parse_args()
generated_dir = args.generated_dir.resolve()
base_names = ["def.fl", "sys_def.fl", "inc.fl", "tb.fl"]
base = parse_filelists(generated_dir / name for name in base_names)
base.options.insert(0, "+timescale+1ns/1ps")
if args.mode == "behv":
base.files.append(_require(args.converted, "converted behavioral RTL"))
else:
common = parse_filelists([generated_dir / "commonip.fl"])
base.extend(common)
base.files.append(_require(args.netlist, f"{args.mode} netlist"))
if args.mode == "post":
sdf = _require(args.sdf, "post-layout SDF")
base.files.append(_prepare_sdf(sdf, args.output.resolve().parent, args.sdf_scope))
pdk_filelist = generated_dir / f"pdk_{args.pdk.lower()}.fl"
base.extend(parse_filelists([pdk_filelist]))
base.deduplicate()
write_filelist(args.output.resolve(), base)
print(f"generated {args.mode} filelist: {args.output.resolve()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+107 -103
View File
@@ -1,114 +1,118 @@
#!/bin/python #!/usr/bin/env python3
import os from __future__ import annotations
import glob
import argparse
import re import re
from pathlib import Path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) from filelist import atomic_write
ROOT_DIR = os.path.abspath(f'{SCRIPT_DIR}/../../..')
RTL_DIR = os.path.abspath(f'{SCRIPT_DIR}/../..')
MINI_DIR = os.path.abspath(f'{SCRIPT_DIR}/..')
GEN_DIR = os.path.abspath(f'{MINI_DIR}/.generated_fl')
def process_fl_files(folder_path): SCRIPT_DIR = Path(__file__).resolve().parent
# source files MINI_DIR = SCRIPT_DIR.parent
pdk_patn = re.compile(r'^(/(pdk))') RTL_DIR = MINI_DIR.parent
rtl_patn = re.compile(r'^(/(clusterip|ip|tech))') ROOT_DIR = RTL_DIR.parent
mini_patn = re.compile(r'^(/(core|mpw|tb|top))') DEFAULT_OUTPUT_DIR = MINI_DIR / ".generated_fl"
# incdir
inc_pdk_patn = re.compile(r'^(\+incdir\+)(/(pdk))')
inc_rtl_patn = re.compile(r'^(\+incdir\+)(/(clusterip|ip|tech))')
inc_mini_patn = re.compile(r'^(\+incdir\+)(/(core|mpw|tb|top))')
msg_files = glob.glob(os.path.join(folder_path, "*.fl")) PATH_PREFIXES = {
if not msg_files: "pdk": ROOT_DIR / "pdk",
print("no files found") "clusterip": RTL_DIR / "clusterip",
return "ip": RTL_DIR / "ip",
"tech": RTL_DIR / "tech",
for msg_file in msg_files: "core": MINI_DIR / "core",
try: "mpw": MINI_DIR / "mpw",
with open(msg_file, 'r', encoding='utf-8') as f: "tb": MINI_DIR / "tb",
lines = f.readlines() "top": MINI_DIR / "top",
}
processed_lines = []
for line in lines:
pdk_repl = f'{ROOT_DIR}' + r'\1'
rtl_repl = f'{RTL_DIR}' + r'\1'
mini_repl = f'{MINI_DIR}' + r'\1'
inc_pdk_repl = r'\1' + f'{ROOT_DIR}' + r'\2'
inc_rtl_repl = r'\1' + f'{RTL_DIR}' + r'\2'
inc_mini_repl = r'\1' + f'{MINI_DIR}' + r'\2'
new_line = pdk_patn.sub(pdk_repl, line)
new_line = rtl_patn.sub(rtl_repl, new_line)
new_line = mini_patn.sub(mini_repl, new_line)
new_line = inc_pdk_patn.sub(inc_pdk_repl, new_line)
new_line = inc_rtl_patn.sub(inc_rtl_repl, new_line)
new_line = inc_mini_patn.sub(inc_mini_repl, new_line)
processed_lines.append(new_line)
with open(msg_file, 'w', encoding='utf-8') as f:
f.writelines(processed_lines)
except FileNotFoundError:
print(f"ERROR: file {msg_file} is not found")
except PermissionError:
print(f"ERROR: file {msg_file} is not permission")
except Exception as e:
print(f"ERROR: file {msg_file} unknown {str(e)}")
def process_lint_files(folder_path):
# source files
pdk_patn = re.compile(r'(\,)(/(pdk))')
rtl_patn = re.compile(r'(\,)(/(clusterip|ip|tech))')
mini_patn = re.compile(r'(\,)(/(core|mpw|tb|top))')
first_patn = re.compile(r'(\=)(/(core|mpw|tb|top))')
msg_files = glob.glob(os.path.join(folder_path, "*.msg")) def expand_filelist(content: str) -> str:
if not msg_files: output: list[str] = []
print("no files found") for line in content.splitlines():
return expanded = line
for prefix, directory in PATH_PREFIXES.items():
for msg_file in msg_files: expanded = re.sub(
try: rf"(?<![^\s+])/{re.escape(prefix)}(?=/|\s|$)",
with open(msg_file, 'r', encoding='utf-8') as f: str(directory),
lines = f.readlines() expanded,
)
processed_lines = [] output.append(expanded)
for line in lines: return "\n".join(output) + "\n"
pdk_repl = r'\1' + f'{ROOT_DIR}' + r'\2'
rtl_repl = r'\1' + f'{RTL_DIR}' + r'\2'
mini_repl = r'\1' + f'{MINI_DIR}' + r'\2'
first_repl = r'\1' + f'{MINI_DIR}' + r'\2'
# seq is important!
new_line = pdk_patn.sub(pdk_repl, line)
new_line = rtl_patn.sub(rtl_repl, new_line)
new_line = mini_patn.sub(mini_repl, new_line)
new_line = first_patn.sub(first_repl, new_line)
processed_lines.append(new_line)
with open(msg_file, 'w', encoding='utf-8') as f:
f.writelines(processed_lines)
except FileNotFoundError:
print(f'ERROR: file {msg_file} is not found')
except PermissionError:
print(f'ERROR: file {msg_file} has not permission')
except Exception as e:
print(f'ERROR: file {msg_file} unknown {str(e)}')
print('generate filelists') def expand_lint_config(content: str) -> str:
os.system(f'mkdir -p {GEN_DIR}') expanded = content
os.system(f'cp -rf {RTL_DIR}/filelist/pdk_*.fl {GEN_DIR}') for prefix, directory in PATH_PREFIXES.items():
os.system(f'cp -rf {RTL_DIR}/mini/filelist/*.fl {GEN_DIR}') expanded = expanded.replace(f",/{prefix}", f",{directory}")
os.system(f'cp -rf {RTL_DIR}/mini/lint.msg {GEN_DIR}') expanded = expanded.replace(f"=/{prefix}", f"={directory}")
return expanded
process_fl_files(GEN_DIR)
process_lint_files(GEN_DIR) def generate_all(output_dir: Path, defines: list[str]) -> list[Path]:
output_dir.mkdir(parents=True, exist_ok=True)
generated: list[Path] = []
templates = sorted((RTL_DIR / "filelist").glob("pdk_*.fl"))
templates.extend(
MINI_DIR / "filelist" / name
for name in (
"clusterip.fl",
"commonip.fl",
"core_hazard3.fl",
"core_picorv32.fl",
"inc.fl",
"ip.fl",
"sys_def.fl",
"tb.fl",
"tech.fl",
"top.fl",
)
)
for source in templates:
if not source.is_file():
raise FileNotFoundError(f"canonical filelist not found: {source}")
destination = output_dir / source.name
atomic_write(
destination,
expand_filelist(source.read_text(encoding="utf-8")),
)
generated.append(destination)
lint_source = MINI_DIR / "lint.msg"
lint_destination = output_dir / lint_source.name
atomic_write(
lint_destination,
expand_lint_config(lint_source.read_text(encoding="utf-8")),
)
generated.append(lint_destination)
def_file = output_dir / "def.fl"
atomic_write(def_file, " ".join(defines) + "\n")
generated.append(def_file)
return generated
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate absolute RTL filelists")
parser.add_argument(
"--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR, help="output directory"
)
parser.add_argument(
"--define", action="append", default=[], help="filelist +define+ token"
)
return parser.parse_args()
def main() -> int:
args = parse_args()
invalid = [item for item in args.define if not item.startswith("+define+")]
if invalid:
raise SystemExit(f"invalid define token(s): {' '.join(invalid)}")
generated = generate_all(args.output_dir.resolve(), args.define)
print(f"generated {len(generated)} files in {args.output_dir.resolve()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+50 -34
View File
@@ -1,39 +1,55 @@
#!/bin/python #!/usr/bin/env python3
import sys from __future__ import annotations
import os
sim_dir = { 'sim': 'behv', 'netsim': 'netl', 'postsim': 'post' } import argparse
tgt = sys.argv[1] import shutil
from pathlib import Path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
MINI_DIR = os.path.abspath(f'{SCRIPT_DIR}/..')
ROOT_DIR = os.path.abspath(f'{MINI_DIR}/../..')
NORLASH_DIR = os.path.abspath(f'{MINI_DIR}/../ip/3rd-party/norflash')
SIM_DIR = os.path.abspath(f'{MINI_DIR}/.iverilog_build/{sim_dir[tgt]}')
# check SCRIPT_DIR = Path(__file__).resolve().parent
check_files = ['SECSI', 'SFDP', 'SREG', 'MEM'] MINI_DIR = SCRIPT_DIR.parent
ROOT_DIR = MINI_DIR.parent.parent
DEFAULT_MODELS = MINI_DIR.parent / "ip" / "3rd-party" / "norflash"
DEFAULT_FIRMWARE = ROOT_DIR / ".sw_build" / "retrosoc_fw.hex"
try:
dir_file_list = [ def parse_args() -> argparse.Namespace:
fname for fname in os.listdir(SIM_DIR) parser = argparse.ArgumentParser(description="Prepare NOR Flash simulation files")
if os.path.isfile(os.path.join(SIM_DIR, fname)) parser.add_argument("--sim-dir", type=Path, required=True)
] parser.add_argument("--models-dir", type=Path, default=DEFAULT_MODELS)
parser.add_argument("--firmware", type=Path, default=DEFAULT_FIRMWARE)
missing_files = [] return parser.parse_args()
for fname in check_files:
if f'{fname}.TXT' not in dir_file_list:
missing_files.append(fname) def main() -> int:
args = parse_args()
if not missing_files: sim_dir = args.sim_dir.resolve()
print(f'contains all files') models_dir = args.models_dir.resolve()
else: firmware = args.firmware.resolve()
os.system(f'cp -rf {NORLASH_DIR}/*.TXT {SIM_DIR}') sim_dir.mkdir(parents=True, exist_ok=True)
os.system(f'ln -sf {ROOT_DIR}/.sw_build/retrosoc_fw.hex {SIM_DIR}/MEM.TXT')
model_names = ("SECSI.TXT", "SFDP.TXT", "SREG.TXT")
except PermissionError: missing_models = [name for name in model_names if not (models_dir / name).is_file()]
print(f"ERROR: {SIM_DIR} has not permission") if missing_models:
except Exception as e: raise FileNotFoundError(
print(f"ERROR: unknown {str(e)}") f"NOR Flash model files missing in {models_dir}: {', '.join(missing_models)}"
)
if not firmware.is_file():
raise FileNotFoundError(
f"firmware image not found: {firmware}; run 'make firmware' first"
)
for name in model_names:
shutil.copy2(models_dir / name, sim_dir / name)
memory = sim_dir / "MEM.TXT"
if memory.is_symlink() or memory.exists():
memory.unlink()
memory.symlink_to(firmware)
print(f"prepared NOR Flash files in {sim_dir}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+1
View File
@@ -0,0 +1 @@
"""Repository automation helpers."""
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import shutil
from pathlib import Path
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Remove retroSoC generated output")
parser.add_argument("--root", type=Path, required=True)
return parser.parse_args()
def remove(root: Path, relative: str) -> None:
target = (root / relative).resolve()
target.relative_to(root)
if target.is_dir() and not target.is_symlink():
shutil.rmtree(target)
elif target.exists() or target.is_symlink():
target.unlink()
print(f"cleaned {target}")
def main() -> int:
root = parse_args().root.resolve()
if not (root / "Makefile").is_file() or not (root / ".git").exists():
raise SystemExit(f"not a retroSoC repository root: {root}")
generated = (
".sw_build",
"rtl/mini/.generated_fl",
"rtl/mini/.iverilog_build",
"rtl/mini/.verilator_build",
"rtl/mini/.vcs_build",
"syn/yosys/.synth_build",
"sta/opensta/retrosoc_sta.log",
"export/rtl",
)
for relative in generated:
remove(root, relative)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import shutil
from pathlib import Path
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Check the retroSoC build environment")
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--simu", required=True)
parser.add_argument("--synth", required=True)
parser.add_argument("--sta", required=True)
parser.add_argument("--pdk", required=True)
parser.add_argument("--core", required=True)
parser.add_argument("--ip", required=True)
return parser.parse_args()
def main() -> int:
args = parse_args()
root = args.root.resolve()
tools = ["python3", "make", "git", "riscv32-unknown-elf-gcc"]
if args.simu == "IVERILOG":
tools.extend(("iverilog", "vvp", "sv2v"))
elif args.simu == "VERILATOR":
tools.extend(("verilator", "c++"))
elif args.simu == "VCS":
tools.extend(("bsub", "vcs"))
if args.synth == "YOSYS":
tools.extend(("yosys", "gawk"))
if args.sta == "OPENSTA":
tools.append("sta")
paths = {
"MPW generator": root / "rtl/mini/mpw/.git",
"cluster IP": root / "rtl/clusterip/common",
"third-party models": root / "rtl/ip/3rd-party",
}
pdk_paths = {
"IHP130": root / "pdk/IHP-Open-PDK",
"ICS55": root / "pdk/icsprout55-pdk",
"SKY130": root / "pdk/skywater-sdk",
"GF180": root / "pdk/gf180mcu-pdk",
}
paths[f"PDK {args.pdk}"] = pdk_paths[args.pdk]
failures = 0
print("Tools:")
for tool in dict.fromkeys(tools):
location = shutil.which(tool)
status = location or "MISSING"
print(f" {tool:<28} {status}")
failures += location is None
print("Paths:")
for name, path in paths.items():
exists = path.exists()
print(f" {name:<28} {path} {'[ok]' if exists else '[MISSING]'}")
failures += not exists
if failures:
print(f"doctor found {failures} missing requirement(s)")
return 1
print("doctor: all selected requirements are available")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import shutil
import sys
import tarfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import download_file, ensure_git_repo # noqa: E402
MPW_DIR = ROOT / "rtl/mini/mpw"
HAZARD3_REVISION = "5b3a34f6955e50e03bdcb964201c91462f7078c3"
IBEX_REVISION = "0c233f54361d769f370889223acc456f2ac19d46"
SERV_ARCHIVE = MPW_DIR / "serv-1.4.0.tar.gz"
SERV_URL = "https://github.com/olofk/serv/archive/refs/tags/1.4.0.tar.gz"
SERV_SHA256 = "f81c37b9f9d548c658e23bb7eb90fbed8b54e2e9e51fa42b8cd2f34cecc472ab"
def tree_digest(root: Path) -> str:
digest = hashlib.sha256()
for path in sorted(item for item in root.rglob("*") if item.is_file()):
digest.update(path.relative_to(root).as_posix().encode("utf-8"))
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def sync_tree(source: Path, destination: Path) -> None:
if destination.is_dir() and tree_digest(source) == tree_digest(destination):
print(f"[dependency] source tree unchanged: {destination}")
return
if destination.exists():
shutil.rmtree(destination)
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(source, destination)
def extract_serv() -> Path:
extracted = MPW_DIR / "serv-1.4.0"
if extracted.is_dir():
return extracted
with tarfile.open(SERV_ARCHIVE, "r:gz") as archive:
for member in archive.getmembers():
(MPW_DIR / member.name).resolve().relative_to(MPW_DIR.resolve())
archive.extractall(MPW_DIR)
if not extracted.is_dir():
raise RuntimeError(f"SERV archive did not create {extracted}")
return extracted
def main() -> int:
parser = argparse.ArgumentParser(description="Prepare pinned MPW core sources")
parser.add_argument("--update", action="store_true")
args = parser.parse_args()
if not (MPW_DIR / ".git").is_dir():
raise SystemExit("MPW generator is missing; run 'python3 setup.py' first")
ensure_git_repo(
"https://github.com/Wren6991/Hazard3.git",
MPW_DIR / "Hazard3",
HAZARD3_REVISION,
update=args.update,
)
ensure_git_repo(
"https://github.com/lowRISC/ibex.git",
MPW_DIR / "ibex",
IBEX_REVISION,
update=args.update,
)
download_file(
SERV_URL,
SERV_ARCHIVE,
SERV_SHA256,
update=args.update,
)
serv = extract_serv()
sync_tree(MPW_DIR / "Hazard3/hdl", MPW_DIR / "core/username3/Hazard3")
sync_tree(serv / "rtl", MPW_DIR / "core/username4/serv")
sync_tree(MPW_DIR / "ibex/rtl", MPW_DIR / "core/username7/ibex")
(MPW_DIR / ".build").mkdir(exist_ok=True)
print("pinned MPW core sources are ready")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
from __future__ import annotations
import hashlib
import os
import subprocess
import tempfile
import time
import urllib.request
from pathlib import Path
from typing import Iterable
def run(command: Iterable[str], *, cwd: Path | None = None) -> None:
printable = " ".join(str(item) for item in command)
print(f"+ {printable}")
subprocess.run(
[str(item) for item in command],
cwd=cwd,
check=True,
)
def git_output(repo: Path, *args: str) -> str:
return subprocess.check_output(
["git", "-C", str(repo), *args], text=True, stderr=subprocess.STDOUT
).strip()
def ensure_git_repo(
url: str,
destination: Path,
revision: str,
*,
recursive: bool = False,
update: bool = False,
allow_dirty: bool = False,
) -> None:
destination = destination.resolve()
created = False
if not destination.exists():
destination.parent.mkdir(parents=True, exist_ok=True)
command = ["git", "clone"]
if recursive:
command.append("--recursive")
command.extend((url, str(destination)))
run(command)
created = True
elif not (destination / ".git").exists():
raise RuntimeError(f"dependency path is not a Git repository: {destination}")
current = git_output(destination, "rev-parse", "HEAD")
dirty = git_output(destination, "status", "--porcelain")
if current != revision:
if not created and not update:
raise RuntimeError(
f"{destination} is at {current}, expected {revision}; rerun with --update"
)
if not created and dirty:
raise RuntimeError(f"refusing to update a dirty dependency: {destination}")
try:
git_output(destination, "cat-file", "-e", f"{revision}^{{commit}}")
except subprocess.CalledProcessError:
run(("git", "fetch", "origin", revision), cwd=destination)
run(("git", "checkout", "--detach", revision), cwd=destination)
elif dirty and not allow_dirty:
raise RuntimeError(f"pinned dependency has local changes: {destination}")
if recursive:
run(("git", "submodule", "update", "--init", "--recursive"), cwd=destination)
print(f"[dependency] {destination.name}: {revision}")
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def download_file(
url: str,
destination: Path,
expected_sha256: str,
*,
update: bool = False,
retries: int = 3,
timeout: int = 30,
) -> None:
destination = destination.resolve()
if destination.is_file():
actual = sha256(destination)
if actual == expected_sha256:
print(f"[dependency] archive verified: {destination}")
return
if not update:
raise RuntimeError(
f"checksum mismatch for {destination}: {actual}; rerun with --update"
)
destination.parent.mkdir(parents=True, exist_ok=True)
last_error: Exception | None = None
for attempt in range(1, retries + 1):
fd, temporary_name = tempfile.mkstemp(
prefix=f".{destination.name}.", dir=destination.parent
)
os.close(fd)
temporary = Path(temporary_name)
try:
request = urllib.request.Request(
url, headers={"User-Agent": "retroSoC-setup/1"}
)
with urllib.request.urlopen(request, timeout=timeout) as response:
with temporary.open("wb") as output:
while chunk := response.read(1024 * 1024):
output.write(chunk)
actual = sha256(temporary)
if actual != expected_sha256:
raise RuntimeError(
f"checksum mismatch for {url}: {actual}, expected {expected_sha256}"
)
os.replace(temporary, destination)
print(f"[dependency] downloaded: {destination}")
return
except Exception as error:
last_error = error
temporary.unlink(missing_ok=True)
if attempt < retries:
time.sleep(attempt)
raise RuntimeError(f"failed to download {url}: {last_error}")
def atomic_write(path: Path, content: str) -> bool:
path = path.resolve()
if path.exists() and path.read_text(encoding="utf-8") == content:
return False
path.parent.mkdir(parents=True, exist_ok=True)
fd, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
with os.fdopen(fd, "w", encoding="utf-8") as stream:
stream.write(content)
os.replace(temporary_name, path)
except Exception:
Path(temporary_name).unlink(missing_ok=True)
raise
return True
+24 -5
View File
@@ -1,8 +1,27 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import argparse
import os from pathlib import Path
project_path = os.getcwd() from scripts.setup_helpers import ensure_git_repo
os.chdir(project_path)
os.system("git clone https://github.com/retroSoC/mini-ver-mpw.git rtl/mini/mpw")
ROOT = Path(__file__).resolve().parent
MPW_REVISION = "30503eb8a7cf33a0c955913faba5014ebd00186f"
def main() -> int:
parser = argparse.ArgumentParser(description="Install the pinned MPW generator")
parser.add_argument("--update", action="store_true")
args = parser.parse_args()
ensure_git_repo(
"https://github.com/retroSoC/mini-ver-mpw.git",
ROOT / "rtl/mini/mpw",
MPW_REVISION,
update=args.update,
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+21 -2
View File
@@ -1,5 +1,24 @@
OPENSTA ?= sta
OPENSTA_NETLIST ?= $(ROOT_PATH)/syn/yosys/.synth_build/out/retrosoc_asic_yosys.v
OPENSTA_LIBERTY ?= $(ROOT_PATH)/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/lib/sg13g2_stdcell_typ_1p20V_25C.lib
OPENSTA_IO_LIB ?= $(ROOT_PATH)/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_io/lib/sg13g2_io_typ_1p2V_3p3V_25C.lib
OPENSTA_SRAM_LIBS ?= $(wildcard $(ROOT_PATH)/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/lib/*_typ_1p20V_25C.lib)
OPENSTA_SDC ?= $(ROOT_PATH)/sta/opensta/gen2.sdc
OPENSTA_REPORT ?= $(ROOT_PATH)/sta/opensta/retrosoc_sta.log
OPENSTA_CONFIG ?= $(ROOT_PATH)/syn/yosys/.synth_build/out/retrosoc_asic_yosys.config
sta: sta:
sta $(RTL_PATH)/../../sta/opensta/opensta.tcl -threads max @for input in $(OPENSTA_NETLIST) $(OPENSTA_LIBERTY) $(OPENSTA_IO_LIB) $(OPENSTA_SRAM_LIBS) $(OPENSTA_SDC) $(OPENSTA_CONFIG); do \
test -f "$$input" || { echo "OpenSTA input missing: $$input" >&2; exit 1; }; \
done
@grep -qx 'PDK=$(PDK)' $(OPENSTA_CONFIG) || { \
echo "OpenSTA netlist configuration does not match PDK=$(PDK); rerun synthesis" >&2; \
exit 1; \
}
OPENSTA_NETLIST=$(OPENSTA_NETLIST) OPENSTA_LIBERTY=$(OPENSTA_LIBERTY) \
OPENSTA_IO_LIB=$(OPENSTA_IO_LIB) OPENSTA_SRAM_LIBS="$(OPENSTA_SRAM_LIBS)" OPENSTA_SDC=$(OPENSTA_SDC) \
OPENSTA_REPORT=$(OPENSTA_REPORT) \
$(OPENSTA) $(ROOT_PATH)/sta/opensta/opensta.tcl -threads max
.PHONY: sta .PHONY: sta
+15 -7
View File
@@ -1,11 +1,19 @@
# report_timing foreach variable {OPENSTA_NETLIST OPENSTA_LIBERTY OPENSTA_IO_LIB OPENSTA_SRAM_LIBS OPENSTA_SDC OPENSTA_REPORT} {
read_liberty pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_io/lib/sg13g2_io_typ_1p2V_3p3V_25C.lib if {![info exists ::env($variable)] || $::env($variable) eq ""} {
read_liberty pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/lib/sg13g2_stdcell_typ_1p20V_25C.lib error "required environment variable is missing: $variable"
read_verilog syn/yosys/.synth_build/out/retrosoc_asic_yosys.v }
link_design retrosoc_asic }
read_sdc sta/opensta/gen2.sdc
report_checks -path_delay min_max -path_group {clk_ext clk_aud } -sort_by_slack -slack_max 0.0 -group_path_count 1000 -endpoint_path_count 1000 > sta/opensta/retrosoc_sta.log read_liberty $::env(OPENSTA_IO_LIB)
read_liberty $::env(OPENSTA_LIBERTY)
foreach liberty $::env(OPENSTA_SRAM_LIBS) {
read_liberty $liberty
}
read_verilog $::env(OPENSTA_NETLIST)
link_design retrosoc_asic
read_sdc $::env(OPENSTA_SDC)
report_checks -path_delay min_max -path_group {clk_ext clk_aud } -sort_by_slack -slack_max 0.0 -group_path_count 1000 -endpoint_path_count 1000 > $::env(OPENSTA_REPORT)
# set_propagated_clock clk # set_propagated_clock clk
# read_spef gcd_sky130hd.spef # read_spef gcd_sky130hd.spef
+177 -260
View File
@@ -1,290 +1,207 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Export retroSoC sources (tar.gz or single .sv). """Export the configured retroSoC RTL as a tarball or a single SV file."""
# Rebuild filelists to absolute paths; inline `include` in .sv.
# Usage: python3 syn/tools/export_soc_sources.py [sv|tar] --soc MINI|TINY from __future__ import annotations
# Outputs: sv -> export/retrosoc_asic_sources.sv; tar -> export/retrosoc_<soc>_sources.tar.gz
import argparse import argparse
import os import re
import tarfile
import shutil import shutil
import sys import sys
import tarfile
import tempfile
from pathlib import Path from pathlib import Path
import re
REPO_ROOT = Path(__file__).resolve().parents[2] REPO_ROOT = Path(__file__).resolve().parents[2]
SCRIPT_DIR = REPO_ROOT / "rtl/mini/script"
sys.path.insert(0, str(SCRIPT_DIR))
from filelist import FileList, atomic_write, parse_filelists # noqa: E402
from generate_filelist import generate_all # noqa: E402
def read_flist(flist: Path): INCLUDE_RE = re.compile(r'^\s*`include\s+"([^"]+)"')
"""Parse a filelist where entries may be space-separated on a single line.
Robustly handles lines like: "+define+FOO +define+BAR +incdir+dir path/to/file.sv ..."
by splitting into tokens and classifying each token.
"""
defines = []
incdirs = []
files = []
if not flist.exists():
return defines, incdirs, files
for raw in flist.read_text().splitlines():
# strip comments (# or //) and split by whitespace
line = raw.split('//', 1)[0].split('#', 1)[0].strip()
if not line:
continue
tokens = line.split()
for tok in tokens:
if not tok:
continue
if tok.startswith('+define+'):
defines.append(tok)
elif tok.startswith('+incdir+'):
# Everything after +incdir+ is the directory path
incdirs.append(tok[len('+incdir+'):].strip())
else:
# Treat as a file path entry
files.append(tok)
return defines, incdirs, files
def build_flist_if_missing(soc_dir: Path): def build_defines(args: argparse.Namespace) -> list[str]:
"""(Re)build yosys.fl and yosys-abspath.fl from canonical filelists. defines = [
f"+define+PDK_{args.pdk}",
Always reconstruct them to avoid stale or inconsistent content. f"+define+CORE_{args.core}",
""" f"+define+IP_{args.ip}",
fl_dir = soc_dir / 'filelist' f"+define+SIMU_{args.simu}",
yosys_fl = fl_dir / 'yosys.fl' "+define+SYNTHESIS",
yosys_abs = fl_dir / 'yosys-abspath.fl' ]
# Compose in-order lists (always rebuild to avoid historical issues) if args.have_pll:
def_fl = fl_dir / 'def.fl' defines.append("+define+HAVE_PLL")
inc_fl = fl_dir / 'inc.fl' if args.have_sram_if:
ip_fl = fl_dir / 'ip.fl' defines.append("+define+HAVE_SRAM_IF")
tech_fl = fl_dir / 'tech.fl' if args.have_sram_macro:
top_fl = fl_dir / 'top.fl' defines.append("+define+HAVE_SRAM_MACRO")
# Guess core from def.fl if not args.have_sva:
core_name = None defines.append("+define+SV_ASSRT_DISABLE")
if def_fl.exists(): return defines
text = def_fl.read_text()
m = re.search(r"\+define\+CORE_([A-Z0-9_]+)", text)
if m:
core_name = m.group(1).lower()
if core_name is None:
core_name = 'picorv32'
core_fl = fl_dir / f'core_{core_name}.fl'
rel_lines = []
if def_fl.exists():
rel_lines.extend(def_fl.read_text().splitlines())
if inc_fl.exists():
rel_lines.extend(inc_fl.read_text().splitlines())
if ip_fl.exists():
rel_lines.extend(ip_fl.read_text().splitlines())
if tech_fl.exists():
rel_lines.extend(tech_fl.read_text().splitlines())
if core_fl.exists():
rel_lines.extend(core_fl.read_text().splitlines())
if top_fl.exists():
rel_lines.extend(top_fl.read_text().splitlines())
# Write yosys.fl
yosys_fl.write_text('\n'.join(rel_lines) + '\n')
# Convert to abspath list
defines, incdirs, files = read_flist(yosys_fl)
abs_lines = []
abs_lines.extend(defines)
for d in incdirs:
p = Path(d)
if not p.is_absolute():
p = (fl_dir / p).resolve()
abs_lines.append(f'+incdir+{p}')
for f in files:
p = Path(f)
if not p.is_absolute():
p = (fl_dir / p).resolve()
abs_lines.append(str(p))
yosys_abs.write_text('\n'.join(abs_lines) + '\n')
return yosys_fl, yosys_abs
def copy_sources_to_export(files, export_root: Path): def configured_filelist(args: argparse.Namespace, generated_dir: Path) -> FileList:
for f in files: names = [
src = Path(f) "def.fl",
# compute path relative to repo root if possible "sys_def.fl",
try: "commonip.fl",
rel = src.relative_to(REPO_ROOT) "inc.fl",
except ValueError: "clusterip.fl",
# fall back to absolute path mirroring under export "ip.fl",
rel = Path('_abs') / src.as_posix().lstrip('/') "tech.fl",
dst = export_root / rel ]
dst.parent.mkdir(parents=True, exist_ok=True) if args.core in ("HAZARD3", "PICORV32"):
shutil.copy2(src, dst) names.append(f"core_{args.core.lower()}.fl")
else:
dynamic_core = REPO_ROOT / "rtl/mini/mpw/.build/core/core.fl"
if not dynamic_core.is_file():
raise FileNotFoundError("MDD core is not generated; run 'make CORE=MDD gen_mpw_code'")
names.append(str(dynamic_core))
if args.ip == "MDD":
dynamic_ip = REPO_ROOT / "rtl/mini/mpw/.build/ip/ip.fl"
if not dynamic_ip.is_file():
raise FileNotFoundError("MDD IP is not generated; run 'make IP=MDD gen_mpw_code'")
names.append(str(dynamic_ip))
names.append("top.fl")
paths = [Path(name) if Path(name).is_absolute() else generated_dir / name for name in names]
return parse_filelists(paths)
def collect_includes(files, incdirs): def resolve_include(name: str, incdirs: list[Path], current_file: Path) -> Path | None:
"""Collect all directly and transitively included headers for a set of files.""" candidates = [current_file.parent / name, *(directory / name for directory in incdirs)]
headers = set() # store resolved absolute paths as strings for candidate in candidates:
if candidate.is_file():
def add_and_recurse(hpath: Path): return candidate.resolve()
sp = str(hpath.resolve())
if sp in headers:
return
headers.add(sp)
# recurse into header to discover nested includes
try:
text = hpath.read_text()
except Exception:
return
for line in text.splitlines():
m = INCLUDE_RE.search(line)
if m:
nested = resolve_include(m.group(1), incdirs, hpath)
if nested:
add_and_recurse(nested)
for f in files:
fp = Path(f)
if not fp.exists():
continue
try:
text = fp.read_text()
except Exception:
continue
for line in text.splitlines():
m = INCLUDE_RE.search(line)
if m:
name = m.group(1)
cand = resolve_include(name, incdirs, fp)
if cand:
add_and_recurse(cand)
return sorted(headers)
def defines_to_sv(defines):
sv_lines = []
for d in defines:
# +define+FOO or +define+FOO=BAR
if '=' in d:
name = d[len('+define+'):].split('=')[0]
val = d[len('+define+'):].split('=')[1]
sv_lines.append(f'`define {name} {val}')
else:
name = d[len('+define+'):]
sv_lines.append(f'`define {name}')
return sv_lines
INCLUDE_RE = re.compile(r"`include\s+\"([^\"]+)\"")
def resolve_include(name: str, incdirs, current_file: Path):
# search order: incdirs then alongside current working file dir
for d in incdirs:
cand = Path(d) / name
if cand.exists():
return cand
# fallback: relative to current file directory
cand = current_file.parent / name
if cand.exists():
return cand
return None return None
def inline_file(path: Path, incdirs, visited_headers): def collect_includes(files: list[Path], incdirs: list[Path]) -> list[Path]:
out = [] headers: set[Path] = set()
if not path.exists():
return out def visit(path: Path) -> None:
text = path.read_text() for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
for line in text.splitlines(): match = INCLUDE_RE.search(line)
m = INCLUDE_RE.search(line) if not match:
if m: continue
hdr = m.group(1) header = resolve_include(match.group(1), incdirs, path)
hdr_path = resolve_include(hdr, incdirs, path) if header is None:
if hdr_path: continue
rp = str(hdr_path.resolve()) if header not in headers:
if rp in visited_headers: headers.add(header)
continue visit(header)
visited_headers.add(rp)
out.append(f'// `include "{hdr}" inlined from {hdr_path}') for source in files:
out.extend(inline_file(hdr_path, incdirs, visited_headers)) visit(source)
out.append('// end inline') return sorted(headers)
else:
# keep original include if not found
out.append(line)
else:
out.append(line)
return out
def build_single_sv(defines, incdirs, files, out_path: Path): def inline_file(path: Path, incdirs: list[Path], stack: tuple[Path, ...] = ()) -> list[str]:
lines = [] resolved = path.resolve()
# header if resolved in stack:
lines.append('// retroSoC single-file export') cycle = " -> ".join(str(item) for item in (*stack, resolved))
lines.append(f'// Generated from {REPO_ROOT}') raise RuntimeError(f"cyclic `include detected: {cycle}")
# translate defines
lines.extend(defines_to_sv(defines)) output: list[str] = []
lines.append('') for line in resolved.read_text(encoding="utf-8", errors="replace").splitlines():
visited_headers = set() match = INCLUDE_RE.search(line)
for f in files: if not match:
fp = Path(f) output.append(line)
lines.append(f'// ====== file: {fp} ======') continue
lines.extend(inline_file(fp, incdirs, visited_headers)) header = resolve_include(match.group(1), incdirs, resolved)
lines.append('') if header is None:
out_path.parent.mkdir(parents=True, exist_ok=True) output.append(line)
out_path.write_text('\n'.join(lines) + '\n') continue
output.append(f'// begin `include "{match.group(1)}"')
output.extend(inline_file(header, incdirs, (*stack, resolved)))
output.append(f'// end `include "{match.group(1)}"')
return output
def main(): def defines_to_sv(defines: list[str]) -> list[str]:
ap = argparse.ArgumentParser(description='Export retroSoC sources') output = []
ap.add_argument('mode', choices=['tar', 'sv'], help='export as tar.gz or single .sv') for define in defines:
ap.add_argument('--soc', default='MINI', help='SOC: MINI or TINY') body = define[len("+define+") :]
args = ap.parse_args() name, separator, value = body.partition("=")
output.append(f"`define {name}{' ' + value if separator else ''}")
return output
soc = args.soc.lower()
soc_dir = REPO_ROOT / 'rtl' / soc
if not soc_dir.exists():
print(f'Error: SOC directory not found: {soc_dir}', file=sys.stderr)
sys.exit(1)
# ensure flists are present or build them def write_single_sv(filelist: FileList, output: Path) -> None:
_, yosys_abs = build_flist_if_missing(soc_dir) lines = ["// Generated by syn/tools/export_soc_sources.py", *defines_to_sv(filelist.defines), ""]
defines, incdirs, files = read_flist(yosys_abs) for source in [*filelist.library_files, *filelist.files]:
lines.append(f"// ===== file: {source.relative_to(REPO_ROOT)} =====")
lines.extend(inline_file(source, filelist.incdirs))
lines.append("")
atomic_write(output, "\n".join(lines) + "\n")
export_dir = REPO_ROOT / 'export'
export_dir.mkdir(exist_ok=True)
if args.mode == 'tar': def bundle_relative(path: Path) -> Path:
bundle_root = export_dir / 'rtl' try:
if bundle_root.exists(): relative = path.resolve().relative_to(REPO_ROOT)
shutil.rmtree(bundle_root) except ValueError:
bundle_root.mkdir(parents=True) return Path("_abs") / path.as_posix().lstrip("/")
# copy sources if relative.parts and relative.parts[0] == "rtl":
copy_sources_to_export(files, bundle_root) return Path(*relative.parts[1:])
# copy headers referenced by sources return Path("repo") / relative
hdrs = collect_includes(files, incdirs)
copy_sources_to_export(hdrs, bundle_root)
# write manifests (absolute and relative-to-bundle) def write_tar(filelist: FileList, export_dir: Path, soc: str) -> Path:
abs_manifest = '\n'.join([*defines, *[f'+incdir+{d}' for d in incdirs], *files]) + '\n' bundle = export_dir / "rtl"
(bundle_root / 'filelist_abspath.fl').write_text(abs_manifest) if bundle.exists():
# relative manifest for portability shutil.rmtree(bundle)
def to_bundle_rel(p: Path): bundle.mkdir(parents=True)
try:
rel = p.resolve().relative_to(REPO_ROOT) sources = [*filelist.library_files, *filelist.files]
except Exception: for source in [*sources, *collect_includes(sources, filelist.incdirs)]:
rel = Path('_abs') / p.as_posix().lstrip('/') destination = bundle / bundle_relative(source)
return rel destination.parent.mkdir(parents=True, exist_ok=True)
rel_incdirs = [f'+incdir+{to_bundle_rel(Path(d))}' for d in incdirs] shutil.copy2(source, destination)
rel_files = [str(to_bundle_rel(Path(f))) for f in files]
(bundle_root / 'filelist_rel.fl').write_text('\n'.join([*defines, *rel_incdirs, *rel_files]) + '\n') manifest = [*filelist.defines]
# create tar.gz manifest.extend(f"+incdir+{bundle_relative(path)}" for path in filelist.incdirs)
tar_path = export_dir / f'retrosoc_{soc}_sources.tar.gz' manifest.extend(str(bundle_relative(path)) for path in sources)
with tarfile.open(tar_path, 'w:gz') as tf: atomic_write(bundle / "filelist.fl", "\n".join(manifest) + "\n")
tf.add(bundle_root, arcname='rtl')
print(f'Generated: {tar_path}') tar_path = export_dir / f"retrosoc_{soc.lower()}_sources.tar.gz"
with tarfile.open(tar_path, "w:gz") as archive:
archive.add(bundle, arcname="rtl")
return tar_path
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Export configured retroSoC RTL sources")
parser.add_argument("mode", choices=("tar", "sv"))
parser.add_argument("--soc", default="MINI", choices=("MINI",))
parser.add_argument("--pdk", default="IHP130", choices=("IHP130", "ICS55", "SKY130", "GF180"))
parser.add_argument("--core", default="HAZARD3", choices=("HAZARD3", "PICORV32", "MDD"))
parser.add_argument("--ip", default="NONE", choices=("NONE", "MDD"))
parser.add_argument("--simu", default="VCS", choices=("VCS", "VERILATOR", "IVERILOG"))
parser.add_argument("--have-pll", action="store_true")
parser.add_argument("--have-sram-if", action="store_true")
parser.add_argument("--have-sram-macro", action="store_true")
parser.add_argument("--have-sva", action="store_true")
parser.add_argument("--output-dir", type=Path, default=REPO_ROOT / "export")
return parser.parse_args()
def main() -> int:
args = parse_args()
with tempfile.TemporaryDirectory(prefix="retrosoc-filelists-") as temporary:
generated_dir = Path(temporary)
generate_all(generated_dir, build_defines(args))
filelist = configured_filelist(args, generated_dir)
export_dir = args.output_dir.resolve()
export_dir.mkdir(parents=True, exist_ok=True)
if args.mode == "sv":
output = export_dir / "retrosoc_asic_sources.sv"
write_single_sv(filelist, output)
else: else:
sv_path = export_dir / f'retrosoc_asic_sources.sv' output = write_tar(filelist, export_dir, args.soc)
build_single_sv(defines, incdirs, files, sv_path) print(f"generated: {output}")
print(f'Generated: {sv_path}') return 0
if __name__ == '__main__': if __name__ == "__main__":
main() raise SystemExit(main())
+8 -1
View File
@@ -12,6 +12,9 @@ set variables {
vlog_files { VLOG_FILES "" } vlog_files { VLOG_FILES "" }
sv_flist { SV_FLIST "" } sv_flist { SV_FLIST "" }
pdk { PDK "" } pdk { PDK "" }
soc { SOC "" }
core { CORE "" }
ip { IP "" }
top_design { TOP_DESIGN "" } top_design { TOP_DESIGN "" }
hier_depth { HIER_DEPTH 0 } hier_depth { HIER_DEPTH 0 }
period_ps { YOSYS_TARGET_PERIOD_PS 5000 } period_ps { YOSYS_TARGET_PERIOD_PS 5000 }
@@ -20,6 +23,7 @@ set variables {
work_dir { WORK "[set dir [pwd]]/WORK" } work_dir { WORK "[set dir [pwd]]/WORK" }
report_dir { REPORTS "[set dir [pwd]]/report" } report_dir { REPORTS "[set dir [pwd]]/report" }
netlist { NETLIST "" } netlist { NETLIST "" }
config { CONFIG "" }
} }
# either use env-var or default to fallback # either use env-var or default to fallback
@@ -40,6 +44,9 @@ foreach var [dict keys $variables] {
if {[string eq $netlist ""]} { if {[string eq $netlist ""]} {
set netlist ${build_dir}/${top_design}_netlist.v set netlist ${build_dir}/${top_design}_netlist.v
} }
if {[string eq $config ""]} {
set config ${netlist}.config
}
proc envVarValid {var_name} { proc envVarValid {var_name} {
if { [info exists ::env($var_name)]} { if { [info exists ::env($var_name)]} {
@@ -64,4 +71,4 @@ proc processAbcScript {abc_script} {
flush $abc_out flush $abc_out
close $abc_out close $abc_out
return $abc_out_path return $abc_out_path
} }
+10
View File
@@ -176,3 +176,13 @@ yosys tee -q -o "${report_dir}/${proj_name}_area_logic.rpt" stat -top $top_desig
# final netlist # final netlist
yosys write_verilog -noattr -noexpr -nohex -nodec $netlist yosys write_verilog -noattr -noexpr -nohex -nodec $netlist
# Record the configuration only after the netlist has been written successfully.
set config_tmp "${config}.tmp"
set config_file [open $config_tmp "w"]
puts $config_file "PDK=$pdk"
puts $config_file "SOC=$soc"
puts $config_file "CORE=$core"
puts $config_file "IP=$ip"
puts $config_file "TOP_DESIGN=$top_design"
close $config_file
file rename -force $config_tmp $config
+17 -20
View File
@@ -29,41 +29,38 @@ include $(YOSYS_DIR)/synth_config.mk
TOP_DESIGN ?= retrosoc_asic TOP_DESIGN ?= retrosoc_asic
RTL_NAME ?= retrosoc_asic RTL_NAME ?= retrosoc_asic
SV_FLIST := $(YOSYS_DIR)/../../rtl/$(shell echo $(SOC) | tr A-Z a-z )/.generated_fl/yosys.fl SV_FLIST := $(RTL_PATH)/.generated_fl/yosys.fl
$(info SYNTH SV_FLIST PATH: $(SV_FLIST))
NETLIST := $(YOSYS_OUT)/$(RTL_NAME)_yosys.v NETLIST := $(YOSYS_OUT)/$(RTL_NAME)_yosys.v
NETLIST_DEBUG := $(YOSYS_OUT)/$(RTL_NAME)_debug_yosys.v NETLIST_DEBUG := $(YOSYS_OUT)/$(RTL_NAME)_yosys_debug.v
NETLIST_CONFIG := $(YOSYS_OUT)/$(RTL_NAME)_yosys.config
gen_synth_filelist: gen_mpw_code generate_filelist gen_synth_filelist: gen_mpw_code generate_filelist
$(info SYNTH RTL_FLIST: $(RTL_FLIST)) @python3 $(RTL_PATH)/script/comb.py $(RTL_FLIST) --output $(SV_FLIST)
@python3 $(RTL_PATH)/script/comb.py $(RTL_FLIST)
## Synthesize netlist using Yosys ## Synthesize netlist using Yosys
synth: gen_synth_filelist $(NETLIST) synth: $(NETLIST)
$(NETLIST) $(NETLIST_DEBUG): $(NETLIST): gen_synth_filelist
@mkdir -p $(YOSYS_OUT) @mkdir -p $(YOSYS_OUT)
@mkdir -p $(YOSYS_TMP) @mkdir -p $(YOSYS_TMP)
@mkdir -p $(YOSYS_RPT) @mkdir -p $(YOSYS_RPT)
PDK="$(PDK)" \ export PDK="$(PDK)" SOC="$(SOC)" CORE="$(CORE)" IP="$(IP)" \
SV_FLIST="$(SV_FLIST)" \ SV_FLIST="$(SV_FLIST)" TOP_DESIGN="$(TOP_DESIGN)" CONFIG="$(NETLIST_CONFIG)" \
TOP_DESIGN="$(TOP_DESIGN)" \ PROJ_NAME="$(RTL_NAME)" WORK="$(YOSYS_TMP)" BUILD="$(YOSYS_OUT)" \
PROJ_NAME="$(RTL_NAME)" \ REPORTS="$(YOSYS_RPT)" NETLIST="$(NETLIST)"; \
WORK="$(YOSYS_TMP)" \ set -o pipefail; yosys -c $(YOSYS_DIR)/script/synth.tcl \
BUILD="$(YOSYS_OUT)" \
REPORTS="$(YOSYS_RPT)" \
NETLIST="$(NETLIST)" \
yosys -c $(YOSYS_DIR)/script/synth.tcl \
2>&1 | TZ=UTC-8 gawk '{ print strftime("[%Y-%m-%d %H:%M %Z]"), $$0 }' \ 2>&1 | TZ=UTC-8 gawk '{ print strftime("[%Y-%m-%d %H:%M %Z]"), $$0 }' \
| tee "$(YOSYS_BUILD)/$(RTL_NAME).log" \ | tee "$(YOSYS_BUILD)/$(RTL_NAME).log" \
| gawk -f $(YOSYS_DIR)/script/filter_output.awk; | gawk -f $(YOSYS_DIR)/script/filter_output.awk
$(NETLIST_DEBUG): $(NETLIST)
@test -f $@
synth_clean: synth_clean:
rm -rf $(YOSYS_OUT) rm -rf $(YOSYS_OUT)
rm -rf $(YOSYS_TMP) rm -rf $(YOSYS_TMP)
rm -rf $(YOSYS_RPT) rm -rf $(YOSYS_RPT)
rm -f $(YOSYS_DIR)/$(RTL_NAME).log rm -f $(YOSYS_BUILD)/$(RTL_NAME).log
.PHONY: synth_clean yosys synth $(NETLIST) $(NETLIST_DEBUG) .PHONY: gen_synth_filelist synth_clean synth
+183
View File
@@ -0,0 +1,183 @@
from __future__ import annotations
import hashlib
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FILELIST_SCRIPT_DIR = ROOT / "rtl/mini/script"
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(FILELIST_SCRIPT_DIR))
from filelist import FileList, atomic_write, parse_filelists, write_filelist # noqa: E402
from generate_filelist import generate_all # noqa: E402
from scripts.setup_helpers import download_file, ensure_git_repo # noqa: E402
def run(*command: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
return subprocess.run(
command,
cwd=cwd or ROOT,
text=True,
capture_output=True,
check=True,
)
def test_atomic_write_preserves_unchanged_mtime(tmp_path: Path) -> None:
output = tmp_path / "output.fl"
assert atomic_write(output, "same\n") is True
first_mtime = output.stat().st_mtime_ns
assert atomic_write(output, "same\n") is False
assert output.stat().st_mtime_ns == first_mtime
def test_nested_filelist_and_space_path_round_trip(tmp_path: Path) -> None:
include_dir = tmp_path / "include files"
include_dir.mkdir()
source = tmp_path / "source file.sv"
library = tmp_path / "library file.v"
source.write_text("module source_file; endmodule\n", encoding="utf-8")
library.write_text("module library_file; endmodule\n", encoding="utf-8")
nested = tmp_path / "nested.fl"
nested.write_text(
f'"{source}"\n-v "{library}"\n',
encoding="utf-8",
)
top = tmp_path / "top.fl"
top.write_text(
f'+define+TEST=1\n"+incdir+{include_dir}"\n-f "{nested}"\n',
encoding="utf-8",
)
parsed = parse_filelists([top])
assert parsed.defines == ["+define+TEST=1"]
assert parsed.incdirs == [include_dir.resolve()]
assert parsed.files == [source.resolve()]
assert parsed.library_files == [library.resolve()]
output = tmp_path / "round trip.fl"
write_filelist(output, parsed)
assert parse_filelists([output]) == parsed
def test_generate_all_is_stable_and_expands_paths(tmp_path: Path) -> None:
defines = ["+define+PDK_IHP130", "+define+CORE_HAZARD3"]
generated = generate_all(tmp_path, defines)
mtimes = {path: path.stat().st_mtime_ns for path in generated}
generate_all(tmp_path, defines)
assert {path: path.stat().st_mtime_ns for path in generated} == mtimes
assert (tmp_path / "def.fl").read_text(encoding="utf-8") == " ".join(defines) + "\n"
cluster = (tmp_path / "clusterip.fl").read_text(encoding="utf-8")
assert str(ROOT / "rtl/clusterip") in cluster
def test_prepare_norflash_and_missing_firmware(tmp_path: Path) -> None:
models = tmp_path / "models"
models.mkdir()
for name in ("SECSI.TXT", "SFDP.TXT", "SREG.TXT"):
(models / name).write_text(name, encoding="utf-8")
firmware = tmp_path / "firmware.hex"
firmware.write_text("00\n", encoding="utf-8")
sim_dir = tmp_path / "sim"
script = ROOT / "rtl/mini/script/prepare_norflash.py"
run(
sys.executable,
str(script),
"--sim-dir",
str(sim_dir),
"--models-dir",
str(models),
"--firmware",
str(firmware),
)
assert (sim_dir / "MEM.TXT").resolve() == firmware.resolve()
assert (sim_dir / "SFDP.TXT").read_text(encoding="utf-8") == "SFDP.TXT"
firmware.unlink()
result = subprocess.run(
[
sys.executable,
str(script),
"--sim-dir",
str(sim_dir),
"--models-dir",
str(models),
"--firmware",
str(firmware),
],
text=True,
capture_output=True,
)
assert result.returncode != 0
assert "firmware image not found" in result.stderr
def test_dependency_helpers_are_idempotent(tmp_path: Path) -> None:
source = tmp_path / "source"
run("git", "init", str(source))
run("git", "config", "user.email", "test@example.com", cwd=source)
run("git", "config", "user.name", "Test", cwd=source)
run("git", "config", "commit.gpgsign", "false", cwd=source)
(source / "dependency.txt").write_text("pinned\n", encoding="utf-8")
run("git", "add", "dependency.txt", cwd=source)
run("git", "commit", "-m", "pinned", cwd=source)
revision = run("git", "rev-parse", "HEAD", cwd=source).stdout.strip()
destination = tmp_path / "checkout"
ensure_git_repo(str(source), destination, revision)
first_head = run("git", "rev-parse", "HEAD", cwd=destination).stdout.strip()
ensure_git_repo(str(source), destination, revision)
assert run("git", "rev-parse", "HEAD", cwd=destination).stdout.strip() == first_head
payload = tmp_path / "payload.bin"
payload.write_bytes(b"verified")
digest = hashlib.sha256(payload.read_bytes()).hexdigest()
downloaded = tmp_path / "downloaded.bin"
download_file(payload.as_uri(), downloaded, digest)
first_mtime = downloaded.stat().st_mtime_ns
download_file(payload.as_uri(), downloaded, digest)
assert downloaded.stat().st_mtime_ns == first_mtime
def test_make_dry_run_and_validation_do_not_write_filelists() -> None:
generated_def = ROOT / "rtl/mini/.generated_fl/def.fl"
before = generated_def.stat().st_mtime_ns if generated_def.exists() else None
run("make", "-n", "help")
run("make", "-n", "SIMU=IVERILOG", "comp")
after = generated_def.stat().st_mtime_ns if generated_def.exists() else None
assert after == before
invalid = subprocess.run(
["make", "SIMU=UNKNOWN", "help"],
cwd=ROOT,
text=True,
capture_output=True,
)
assert invalid.returncode != 0
assert "Invalid SIMU='UNKNOWN'" in invalid.stderr
def test_clean_all_stays_within_repository(tmp_path: Path) -> None:
fake_root = tmp_path / "repository"
(fake_root / ".git").mkdir(parents=True)
(fake_root / "Makefile").write_text("all:\n", encoding="utf-8")
generated = fake_root / "rtl/mini/.iverilog_build"
generated.mkdir(parents=True)
(generated / "simv").write_text("generated", encoding="utf-8")
dependency = fake_root / "rtl/ip/3rd-party"
dependency.mkdir(parents=True)
(dependency / "model.v").write_text("dependency", encoding="utf-8")
run(
sys.executable,
str(ROOT / "scripts/clean.py"),
"--root",
str(fake_root),
)
assert not generated.exists()
assert (dependency / "model.v").is_file()