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
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
run: |
verilator_path="artifact-verilator"
@@ -147,4 +152,4 @@ jobs:
# retention-days: 90 # Retain for 90 days
- name: Success Notification
run: echo "RTL simulation completed successfully"
run: echo "RTL simulation completed successfully"
+3 -1
View File
@@ -20,6 +20,8 @@
*.rc
*.aig
*.so
*.py[cod]
__pycache__/
.vscode
pic
video.h
@@ -67,4 +69,4 @@ coremark-main
*wave
picorv32_ver.v
socver.*
3rd-party
3rd-party
+113 -52
View File
@@ -1,3 +1,7 @@
SHELL := /bin/bash
.DEFAULT_GOAL := help
.DELETE_ON_ERROR:
SOC ?= MINI
SIMU ?= VCS
SYNTH ?= NONE
@@ -16,8 +20,8 @@ RTL_SIM_PLLCFG ?= NONE
RTL_SIM_CORESEL ?= 0
RTL_SIM_TIMEOUT ?= -1
ROOT_PATH ?= $(CURDIR)
RTL_PATH ?= NONE
ROOT_PATH ?= $(abspath $(dir $(firstword $(MAKEFILE_LIST))))
RTL_PATH := $(ROOT_PATH)/rtl/mini
CORE ?= HAZARD3
IP ?= NONE
@@ -30,40 +34,50 @@ FIRMWARE_NAME ?= retrosoc_fw
PROG_TYPE ?= FULL
LINK_TYPE ?= ld2_sram
$(info ============== BASE INFO ==================================)
$(info retroSoC: <https://github.com/retroSoC/retroSoC>)
$(info author: Yuchi Miao <https://github.com/maksyuki>)
$(info license: MulanPSL-2.0 license)
$(info ROOT_PATH: $(CURDIR))
$(info SHELL: $(SHELL))
$(info MAKE VERSION: $(MAKE_VERSION))
$(info MAKE APP: $(MAKE))
$(info MAKE CMDGOAL: $(MAKECMDGOALS))
$(info MAKE FILELIST: $(MAKEFILE_LIST))
$(info ============== HW CONFIG INFO ===================================)
$(info SOC [TINY, MINI]: $(SOC))
$(info CORE [PICORV32, HAZARD3, MDD]: $(CORE))
$(info IP [NONE, MDD]: $(IP))
$(info SIMU [VCS, VERILATOR, IVERILOG]: $(SIMU))
$(info SYNTH [NONE, YOSYS, DC]: $(SYNTH))
$(info STA [NONE, OPENSTA, ISTA]: $(STA))
$(info PDK [ICS55, IHP130, SKY130, GF180]: $(PDK))
$(info HAVE_PLL [YES, NO]: $(HAVE_PLL))
$(info HAVE_SRAM_IF [YES, NO]: $(HAVE_SRAM_IF))
$(info HAVE_SRAM_MACRO [YES, NO]: $(HAVE_SRAM_MACRO))
$(info HAVE_SVA [YES, NO]: $(HAVE_SVA))
$(info WAVE: [YES, NO]: $(WAVE))
$(info RTL_SIM_PLLEN: $(RTL_SIM_PLLEN))
$(info RTL_SIM_PLLCFG: $(RTL_SIM_PLLCFG))
$(info RTL_SIM_CORESEL: $(RTL_SIM_CORESEL))
$(info RTL_SIM_TIMEOUT: $(RTL_SIM_TIMEOUT))
$(info ============== SW CONFIG INFO ===================================)
$(info ISA [RV32E RV32I RV32IM]: $(ISA))
$(info HAVE_CSR [YES, NO]: $(HAVE_CSR))
$(info FIRMWARE_NAME [retrosoc_fw]: $(FIRMWARE_NAME))
$(info PROG_TYPE [BASE, FULL]: $(PROG_TYPE))
$(info LINK_TYPE [xip, ld2_sram, ld2_psram, ld2_sdram]: $(LINK_TYPE))
$(info =================================================================)
VALID_SOC := MINI
VALID_CORE := PICORV32 HAZARD3 MDD
VALID_IP := NONE MDD
VALID_SIMU := VCS VERILATOR IVERILOG
VALID_SYNTH := NONE YOSYS
VALID_STA := NONE OPENSTA
VALID_PDK := ICS55 IHP130 SKY130 GF180
VALID_BOOL := YES NO
VALID_ISA := RV32E RV32I RV32IM
VALID_PROG_TYPE := BASE FULL
VALID_LINK_TYPE := xip ld2_sram ld2_psram ld2_sdram
define validate_value
$(if $(filter $($(1)),$(2)),,$(error Invalid $(1)='$($(1))'; expected one of: $(2)))
endef
$(call validate_value,SOC,$(VALID_SOC))
$(call validate_value,CORE,$(VALID_CORE))
$(call validate_value,IP,$(VALID_IP))
$(call validate_value,SIMU,$(VALID_SIMU))
$(call validate_value,SYNTH,$(VALID_SYNTH))
$(call validate_value,STA,$(VALID_STA))
$(call validate_value,PDK,$(VALID_PDK))
$(call validate_value,HAVE_PLL,$(VALID_BOOL))
$(call validate_value,HAVE_SRAM_IF,$(VALID_BOOL))
$(call validate_value,HAVE_SRAM_MACRO,$(VALID_BOOL))
$(call validate_value,HAVE_SVA,$(VALID_BOOL))
$(call validate_value,WAVE,$(VALID_BOOL))
$(call validate_value,ISA,$(VALID_ISA))
$(call validate_value,HAVE_CSR,$(VALID_BOOL))
$(call validate_value,PROG_TYPE,$(VALID_PROG_TYPE))
$(call validate_value,LINK_TYPE,$(VALID_LINK_TYPE))
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+CORE_$(CORE)
@@ -86,27 +100,74 @@ ifeq ($(HAVE_SVA), NO)
DEF_LIST += +define+SV_ASSRT_DISABLE
endif
ifeq ($(SOC), MINI)
RTL_PATH = $(ROOT_PATH)/rtl/mini
$(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
ifeq ($(SYNTH), YOSYS)
DEF_LIST += +define+SYNTHESIS
endif
include rtl/mini/Makefile
ifeq ($(SYNTH), YOSYS)
#HACK: for some core
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
include syn/yosys/yosys.mk
endif
ifeq ($(STA), OPENSTA)
include sta/opensta/opensta.mk
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)
[![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
# Always use the script directory as the base to avoid failures in different working directories
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
APP_DIR = SCRIPT_DIR
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)
import argparse
import shutil
import sys
from pathlib import Path
from zipfile import ZipFile
def prepend_line(filename, line):
with open(filename, 'r') as f:
content = f.read()
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)
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import atomic_write, download_file, ensure_git_repo # noqa: E402
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')
os.system(f'cd "{FATFS_DIR}" && cp -rf ffconf.h diskio.c ff16/source/')
os.system(f'git clone https://github.com/eembc/coremark.git {COREMARK_DIR}/coremark-main')
os.system(f'cd {COREMARK_DIR}/coremark-main && git checkout 1f483d5b8316753a742cbf5590caf5bd0a4e4777')
os.chdir(f'{COREMARK_DIR}/coremark-main')
prepend_line('coremark.h', '#include <tinyprintf.h>')
replace_line('core_main.c', 'main', 'core_main')
APP_DIR = Path(__file__).resolve().parent
FATFS_DIR = APP_DIR / "fatfs"
FATFS_ARCHIVE = FATFS_DIR / "ff16/ff16.zip"
FATFS_URL = "https://elm-chan.org/fsw/ff/arc/ff16.zip"
FATFS_SHA256 = "41d98115f72b090c2d0c269a001c5c0216efd78fbd84bb6427be808a76315a5a"
COREMARK_DIR = APP_DIR / "coremark/coremark-main"
COREMARK_REVISION = "1f483d5b8316753a742cbf5590caf5bd0a4e4777"
# 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);
#ifdef SW_BASE
app_info();
ip_archinfo_test(0, NULL);
#else
tinybooter();
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 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:
branch = subprocess.check_output(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
stderr=subprocess.STDOUT
).decode('utf-8').strip()
return subprocess.check_output(
["git", "-C", str(root), *args],
text=True,
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
except subprocess.CalledProcessError:
return None, None
def main() -> int:
parser = argparse.ArgumentParser(description="Generate the firmware version header")
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()
print(f"current branch: {branch}")
print(f"current commit: {commit}")
print(ROOT_PATH)
branch = git_value(root, "rev-parse", "--abbrev-ref", "HEAD")
commit = git_value(root, "rev-parse", "--short=6", "HEAD")
template = (root / "crt/ver.tmpl").read_text(encoding="utf-8")
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 'SOC_DEFAULT_BRANCH' in line:
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)
if __name__ == "__main__":
raise SystemExit(main())
+41 -7
View File
@@ -1,10 +1,44 @@
#!/bin/python
#!/usr/bin/env python3
import os
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
import argparse
import sys
from pathlib import Path
os.system(f'git clone https://github.com/IHP-GmbH/IHP-Open-PDK.git {SCRIPT_DIR}/IHP-Open-PDK')
os.system(f'cd {SCRIPT_DIR}/IHP-Open-PDK && git checkout 68eebafcd9b2f5e92c69d37a8d3d90eb266550f5 && git submodule init')
os.system(f'git clone --recursive --depth 1 https://github.com/openecos-projects/icsprout55-pdk.git {SCRIPT_DIR}/icsprout55-pdk')
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
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:
python3 $(RTL_PATH)/mpw/setup.py
$(MPW_STAMP):
@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)
@echo "[mpw] generating user ip ..."
python3 $(RTL_PATH)/mpw/ip.py
@@ -18,7 +27,9 @@ else
endif
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
include $(RTL_PATH)/mk/software.mk
@@ -67,4 +78,4 @@ else ifeq ($(SIMU), IVERILOG)
include $(RTL_PATH)/mk/iverilog.mk
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 = ''
# remove parallel case statement
with open(f'{ROOT_PATH}/rtl/mini/core/picorv32.v', 'r', encoding='utf-8') as fp:
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
ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import atomic_write # noqa: E402
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
SIM_TOOL := iverilog
SIM_BINY := vvp simv -fst
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
IVERILOG ?= iverilog
VVP ?= vvp
GTKWAVE ?= gtkwave
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 ---
COMMON_OPTS := -g2012
IVERILOG_ROOT := $(RTL_PATH)/.iverilog_build
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
SIM_OPTS := +$(RTL_SIM_PLLEN) +$(RTL_SIM_PLLCFG) +core_sel=$(RTL_SIM_CORESEL) +sim_timeout=$(RTL_SIM_TIMEOUT) \
+wave_$(WAVE)
$(CONVERTED_SOC): gen_mpw_code generate_filelist
@mkdir -p $(@D)
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
comp: FLIST := -f $(RTL_PATH)/.generated_fl/iverilog.fl
comp: OPTS := $(TIME_OPTS)
$(IVERILOG_NETL_FLIST): generate_filelist
@mkdir -p $(@D)
python3 $(RTL_PATH)/script/gen_iverilog_filelist.py \
--mode netl --pdk $(PDK) --output $@ --netlist $(NETLIST_PATH)
netcomp: DIR := .iverilog_build/netl
netcomp: FLIST := -f $(RTL_PATH)/.generated_fl/iverilog.fl
netcomp: OPTS := $(TIME_OPTS)
$(IVERILOG_POST_FLIST): generate_filelist
@mkdir -p $(@D)
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
postcomp: FLIST := $(NET_FLIST) $(TB_FLIST)
postcomp: OPTS := $(POST_OPTS)
convt_sv2v: $(CONVERTED_SOC)
gen_iverilog_filelist: $(IVERILOG_BEHV_FLIST)
sim: DIR := .iverilog_build/behv
netsim: DIR := .iverilog_build/netl
postsim: DIR := .iverilog_build/post
comp: $(IVERILOG_BEHV_FLIST)
@mkdir -p $(IVERILOG_BEHV_DIR)
@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
netwave: DIR := .iverilog_build/netl
postwave: DIR := .iverilog_build/post
netcomp: $(IVERILOG_NETL_FLIST)
@mkdir -p $(IVERILOG_NETL_DIR)
@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
netsim: netcomp prepare_norflash
postsim: postcomp prepare_norflash
sim: comp
python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_BEHV_DIR) \
--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
@mkdir -p $(RTL_PATH)/$(DIR)
python3 $(RTL_PATH)/script/convt_sv2v.py $(RTL_FLIST)
netsim: netcomp
python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_NETL_DIR) \
--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:
@mkdir -p $(RTL_PATH)/$(DIR)
python3 $(RTL_PATH)/script/gen_iverilog_filelist.py $(MAKECMDGOALS) $(PDK) $(NETLIST_PATH)
postsim: postcomp
python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_POST_DIR) \
--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:
@mkdir -p $(RTL_PATH)/$(DIR)
python3 $(RTL_PATH)/script/prepare_norflash.py $(MAKECMDGOALS)
comp netcomp postcomp: convt_sv2v gen_iverilog_filelist
@mkdir -p $(RTL_PATH)/$(DIR)
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 &)
wave:
cd $(IVERILOG_BEHV_DIR) && $(GTKWAVE) $(RTL_TOP).fst &
netwave:
cd $(IVERILOG_NETL_DIR) && $(GTKWAVE) $(RTL_TOP).fst &
postwave:
cd $(IVERILOG_POST_DIR) && $(GTKWAVE) $(RTL_TOP).fst &
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
CROSS=riscv32-unknown-elf-
CROSS ?= riscv32-unknown-elf-
CP = $(CROSS)cpp
CC = $(CROSS)gcc
@@ -108,7 +108,7 @@ INC_PATH := -I$(ROOT_PATH)/crt/inc \
ifneq ($(findstring MDD,$(CORE) $(IP)),)
INC_PATH += -I$(ROOT_PATH)/rtl/min/mpw/.build
INC_PATH += -I$(ROOT_PATH)/rtl/mini/mpw/.build
endif
@@ -129,22 +129,23 @@ ifneq ($(filter RV32E RV32I,$(ISA)),)
SRC_PATH += $(ROOT_PATH)/crt/libgcc/mulsi3.c
endif
LDS_PATH := $(ROOT_PATH)/crt/./lds/$(LINK_TYPE).lds
LDS_PATH := $(ROOT_PATH)/crt/lds/$(LINK_TYPE).lds
upd_ver_info:
python3 crt/ver.py
python3 $(ROOT_PATH)/crt/ver.py --root $(ROOT_PATH)
asm: gen_mpw_code
cd app/asm && make
cp -rf app/asm/hello-asm.flash .sw_build/retrosoc_fw.hex
cp -rf app/asm/hello-asm.bin .sw_build/retrosoc_fw.bin
cp -rf app/asm/hello-asm.txt .sw_build/retrosoc_fw_all.txt
@mkdir -p $(ROOT_PATH)/.sw_build
$(MAKE) -C $(ROOT_PATH)/app/asm
cp $(ROOT_PATH)/app/asm/hello-asm.flash $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).hex
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
@mkdir -p .sw_build
cd .sw_build && ($(CP) -P -o $(LINK_TYPE).lds $(LDS_PATH))
cd .sw_build && ($(CC) $(CFLAGS) $(INC_PATH) -o $@ $(SRC_PATH))
cd .sw_build && ($(OBJC) -O verilog $@ $(FIRMWARE_NAME).hex)
cd .sw_build && ($(OBJC) -O binary $@ $(FIRMWARE_NAME).bin)
cd .sw_build && ($(DUMP) -d $@ > $(FIRMWARE_NAME).txt)
cd .sw_build && ($(DUMP) -D $@ > $(FIRMWARE_NAME)_all.txt)
@mkdir -p $(ROOT_PATH)/.sw_build
cd $(ROOT_PATH)/.sw_build && $(CP) -P -o $(LINK_TYPE).lds $(LDS_PATH)
cd $(ROOT_PATH)/.sw_build && $(CC) $(CFLAGS) $(INC_PATH) -o $@ $(SRC_PATH)
cd $(ROOT_PATH)/.sw_build && $(OBJC) -O verilog $@ $(FIRMWARE_NAME).hex
cd $(ROOT_PATH)/.sw_build && $(OBJC) -O binary $@ $(FIRMWARE_NAME).bin
cd $(ROOT_PATH)/.sw_build && $(DUMP) -d $@ > $(FIRMWARE_NAME).txt
cd $(ROOT_PATH)/.sw_build && $(DUMP) -D $@ > $(FIRMWARE_NAME)_all.txt
+14 -10
View File
@@ -1,9 +1,12 @@
# tools & paths
NOVAS := /nfs/tools/synopsys/verdi/V-2023.12-SP1-1/share/PLI/VCS/LINUX64
EXTRA := -P $(NOVAS)/novas.tab $(NOVAS)/pli.a
SIM_TOOL := bsub -Is vcs
SIM_BINY := bsub -Is ./simv
VERDI_TOOL := bsub -Is verdi
NOVAS ?= /nfs/tools/synopsys/verdi/V-2023.12-SP1-1/share/PLI/VCS/LINUX64
VCS_RUNNER ?= bsub -Is
VCS ?= vcs
VERDI ?= 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
SIM_LOG := -l sim.log
# netlist file path
@@ -56,7 +59,7 @@ netcomp: FLIST := $(NETLIST_PATH) $(NET_FLIST) $(TB_FLIST)
netcomp: OPTS := $(TIME_OPTION)
postcomp: DIR := .vcs_build/post
postcomp: FLIST := $(NET_FLIST) $(TB_FLIST)
postcomp: FLIST := $(POST_PATH) $(NET_FLIST) $(TB_FLIST)
postcomp: OPTS := $(POST_OPTS)
sim: DIR := .vcs_build/behv
@@ -72,18 +75,19 @@ sim: comp
netsim: netcomp
postsim: postcomp
comp netcomp postcomp: gen_mpw_code generate_filelist
comp netcomp postcomp:
@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:
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:
cd $(RTL_PATH)/$(DIR) && ($(VERDI_TOOL) -ssf $(RTL_TOP).fsdb -nologo &)
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
SOC_CSRC_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 += $(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_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 += --x-assign unique -O3 -CFLAGS "$(VERILATOR_CXXFLAGS)"
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 += $(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),)
export OBJCACHE = ccache
export CCACHE_DIR = $(BUILD_DIR)/ccache
export CCACHE_TEMPDIR = $(BUILD_DIR)/ccache/tmp
endif
lint: gen_mpw_code generate_filelist
@mkdir -p $(BUILD_DIR)
@mkdir -p $(BUILD_DIR)/ccache/tmp
comp: lint
# verilator $(VERILATOR_FLAGS)
verilator $(VERILATOR_FLAGS) > $(BUILD_DIR)/verilating.log 2>&1
$(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
$(VERILATOR) $(VERILATOR_FLAGS) > $(BUILD_DIR)/verilating.log 2>&1
+$(MAKE) VM_PARALLEL_BUILDS=1 OPT_FAST="-O3" -C $(SOC_COMPILE_HOME) \
-f V$(SOC_VSRC_TOP).mk > $(BUILD_DIR)/compile.log 2>&1
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:
clean:
rm -rf .verilator_build
rm -rf $(BUILD_DIR)
.PHONY: comp sim clean
+26 -17
View File
@@ -1,24 +1,33 @@
#!/bin/python
#!/usr/bin/env python3
import sys
import os
from __future__ import annotations
import argparse
from pathlib import Path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
MINI_DIR = os.path.abspath(f'{SCRIPT_DIR}/..')
GEN_DIR = os.path.abspath(f'{MINI_DIR}/.generated_fl')
from filelist import parse_filelists, write_filelist
res = f'{GEN_DIR}/yosys.fl'
is_first = True
for v in sys.argv[1:]:
if '-f' not in v:
if 'pdk' in v: continue
DEFAULT_OUTPUT = Path(__file__).resolve().parent.parent / ".generated_fl" / "yosys.fl"
print(f'gen code: {v}')
if is_first:
os.system(f'cat {v} > {res}')
is_first = False
else:
os.system(f'cat {v} >> {res}')
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Combine synthesis filelists")
parser.add_argument("-f", "--filelist", action="append", type=Path, default=[])
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
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
import sys
from __future__ import annotations
import argparse
import subprocess
from pathlib import Path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
MINI_DIR = os.path.abspath(f'{SCRIPT_DIR}/..')
GEN_DIR = os.path.abspath(f'{MINI_DIR}/.iverilog_build/behv')
from filelist import parse_filelists
sv2v_cmd = 'sv2v'
for v in sys.argv[1:]:
if '-f' not in v:
if 'pdk' in v: continue
# print(f'filelist: {v}')
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)}")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Convert the behavioral RTL with sv2v")
parser.add_argument("-f", "--filelist", action="append", type=Path, default=[])
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--sv2v", default="sv2v", help="sv2v executable")
return parser.parse_args()
sv2v_cmd = f'{sv2v_cmd} --write {GEN_DIR}/converted_soc.v'
# print(f'sv2v_cmd: {sv2v_cmd}')
os.system(sv2v_cmd)
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")
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
import os
from __future__ import annotations
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
MINI_DIR = os.path.abspath(f'{SCRIPT_DIR}/..')
GEN_DIR = os.path.abspath(f'{MINI_DIR}/.generated_fl')
import argparse
import gzip
from pathlib import Path
cmd = ['+timescale+1ns/1ps\n']
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
from filelist import FileList, atomic_write, parse_filelists, write_filelist
tgt = sys.argv[1]
pdk = sys.argv[2]
netl = sys.argv[3]
print(f'tgt: {tgt}')
print(f'pdk: {pdk}')
print(f'netl: {netl}')
SCRIPT_DIR = Path(__file__).resolve().parent
MINI_DIR = SCRIPT_DIR.parent
DEFAULT_GENERATED_DIR = MINI_DIR / ".generated_fl"
if tgt == 'sim':
cmd += [f'{MINI_DIR}/.iverilog_build/behv/converted_soc.v\n']
elif tgt == 'netsim':
with open(f'{GEN_DIR}/commonip.fl', 'r', encoding='utf-8') as fp:
tmp = fp.readlines()
cmd += tmp
cmd += f'{netl}\n'
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate an Icarus Verilog filelist")
parser.add_argument("--mode", required=True, choices=("behv", "netl", "post"))
parser.add_argument("--pdk", required=True)
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:
tmp = fp.readlines()
cmd += tmp
def _require(path: Path | None, description: str) -> Path:
if path is None or not path.is_file():
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
import glob
from __future__ import annotations
import argparse
import re
from pathlib import Path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
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')
from filelist import atomic_write
def process_fl_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))')
# 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))')
SCRIPT_DIR = Path(__file__).resolve().parent
MINI_DIR = SCRIPT_DIR.parent
RTL_DIR = MINI_DIR.parent
ROOT_DIR = RTL_DIR.parent
DEFAULT_OUTPUT_DIR = MINI_DIR / ".generated_fl"
msg_files = glob.glob(os.path.join(folder_path, "*.fl"))
if not msg_files:
print("no files found")
return
for msg_file in msg_files:
try:
with open(msg_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
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))')
PATH_PREFIXES = {
"pdk": ROOT_DIR / "pdk",
"clusterip": RTL_DIR / "clusterip",
"ip": RTL_DIR / "ip",
"tech": RTL_DIR / "tech",
"core": MINI_DIR / "core",
"mpw": MINI_DIR / "mpw",
"tb": MINI_DIR / "tb",
"top": MINI_DIR / "top",
}
msg_files = glob.glob(os.path.join(folder_path, "*.msg"))
if not msg_files:
print("no files found")
return
for msg_file in msg_files:
try:
with open(msg_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
processed_lines = []
for line in lines:
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)}')
def expand_filelist(content: str) -> str:
output: list[str] = []
for line in content.splitlines():
expanded = line
for prefix, directory in PATH_PREFIXES.items():
expanded = re.sub(
rf"(?<![^\s+])/{re.escape(prefix)}(?=/|\s|$)",
str(directory),
expanded,
)
output.append(expanded)
return "\n".join(output) + "\n"
print('generate filelists')
os.system(f'mkdir -p {GEN_DIR}')
os.system(f'cp -rf {RTL_DIR}/filelist/pdk_*.fl {GEN_DIR}')
os.system(f'cp -rf {RTL_DIR}/mini/filelist/*.fl {GEN_DIR}')
os.system(f'cp -rf {RTL_DIR}/mini/lint.msg {GEN_DIR}')
def expand_lint_config(content: str) -> str:
expanded = content
for prefix, directory in PATH_PREFIXES.items():
expanded = expanded.replace(f",/{prefix}", f",{directory}")
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
import os
from __future__ import annotations
sim_dir = { 'sim': 'behv', 'netsim': 'netl', 'postsim': 'post' }
tgt = sys.argv[1]
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]}')
import argparse
import shutil
from pathlib import Path
# check
check_files = ['SECSI', 'SFDP', 'SREG', 'MEM']
SCRIPT_DIR = Path(__file__).resolve().parent
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 = [
fname for fname in os.listdir(SIM_DIR)
if os.path.isfile(os.path.join(SIM_DIR, fname))
]
missing_files = []
for fname in check_files:
if f'{fname}.TXT' not in dir_file_list:
missing_files.append(fname)
if not missing_files:
print(f'contains all files')
else:
os.system(f'cp -rf {NORLASH_DIR}/*.TXT {SIM_DIR}')
os.system(f'ln -sf {ROOT_DIR}/.sw_build/retrosoc_fw.hex {SIM_DIR}/MEM.TXT')
except PermissionError:
print(f"ERROR: {SIM_DIR} has not permission")
except Exception as e:
print(f"ERROR: unknown {str(e)}")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Prepare NOR Flash simulation files")
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)
return parser.parse_args()
def main() -> int:
args = parse_args()
sim_dir = args.sim_dir.resolve()
models_dir = args.models_dir.resolve()
firmware = args.firmware.resolve()
sim_dir.mkdir(parents=True, exist_ok=True)
model_names = ("SECSI.TXT", "SFDP.TXT", "SREG.TXT")
missing_models = [name for name in model_names if not (models_dir / name).is_file()]
if missing_models:
raise FileNotFoundError(
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
import sys
import os
import argparse
from pathlib import Path
project_path = os.getcwd()
os.chdir(project_path)
os.system("git clone https://github.com/retroSoC/mini-ver-mpw.git rtl/mini/mpw")
from scripts.setup_helpers import ensure_git_repo
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 $(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
read_liberty pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_io/lib/sg13g2_io_typ_1p2V_3p3V_25C.lib
read_liberty pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/lib/sg13g2_stdcell_typ_1p20V_25C.lib
read_verilog syn/yosys/.synth_build/out/retrosoc_asic_yosys.v
link_design retrosoc_asic
read_sdc sta/opensta/gen2.sdc
foreach variable {OPENSTA_NETLIST OPENSTA_LIBERTY OPENSTA_IO_LIB OPENSTA_SRAM_LIBS OPENSTA_SDC OPENSTA_REPORT} {
if {![info exists ::env($variable)] || $::env($variable) eq ""} {
error "required environment variable is missing: $variable"
}
}
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
# read_spef gcd_sky130hd.spef
+177 -260
View File
@@ -1,290 +1,207 @@
#!/usr/bin/env python3
# Export retroSoC sources (tar.gz or single .sv).
# Rebuild filelists to absolute paths; inline `include` in .sv.
# Usage: python3 syn/tools/export_soc_sources.py [sv|tar] --soc MINI|TINY
# Outputs: sv -> export/retrosoc_asic_sources.sv; tar -> export/retrosoc_<soc>_sources.tar.gz
"""Export the configured retroSoC RTL as a tarball or a single SV file."""
from __future__ import annotations
import argparse
import os
import tarfile
import re
import shutil
import sys
import tarfile
import tempfile
from pathlib import Path
import re
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):
"""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
INCLUDE_RE = re.compile(r'^\s*`include\s+"([^"]+)"')
def build_flist_if_missing(soc_dir: Path):
"""(Re)build yosys.fl and yosys-abspath.fl from canonical filelists.
Always reconstruct them to avoid stale or inconsistent content.
"""
fl_dir = soc_dir / 'filelist'
yosys_fl = fl_dir / 'yosys.fl'
yosys_abs = fl_dir / 'yosys-abspath.fl'
# Compose in-order lists (always rebuild to avoid historical issues)
def_fl = fl_dir / 'def.fl'
inc_fl = fl_dir / 'inc.fl'
ip_fl = fl_dir / 'ip.fl'
tech_fl = fl_dir / 'tech.fl'
top_fl = fl_dir / 'top.fl'
# Guess core from def.fl
core_name = None
if def_fl.exists():
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 build_defines(args: argparse.Namespace) -> list[str]:
defines = [
f"+define+PDK_{args.pdk}",
f"+define+CORE_{args.core}",
f"+define+IP_{args.ip}",
f"+define+SIMU_{args.simu}",
"+define+SYNTHESIS",
]
if args.have_pll:
defines.append("+define+HAVE_PLL")
if args.have_sram_if:
defines.append("+define+HAVE_SRAM_IF")
if args.have_sram_macro:
defines.append("+define+HAVE_SRAM_MACRO")
if not args.have_sva:
defines.append("+define+SV_ASSRT_DISABLE")
return defines
def copy_sources_to_export(files, export_root: Path):
for f in files:
src = Path(f)
# compute path relative to repo root if possible
try:
rel = src.relative_to(REPO_ROOT)
except ValueError:
# fall back to absolute path mirroring under export
rel = Path('_abs') / src.as_posix().lstrip('/')
dst = export_root / rel
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
def configured_filelist(args: argparse.Namespace, generated_dir: Path) -> FileList:
names = [
"def.fl",
"sys_def.fl",
"commonip.fl",
"inc.fl",
"clusterip.fl",
"ip.fl",
"tech.fl",
]
if args.core in ("HAZARD3", "PICORV32"):
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):
"""Collect all directly and transitively included headers for a set of files."""
headers = set() # store resolved absolute paths as strings
def add_and_recurse(hpath: Path):
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
def resolve_include(name: str, incdirs: list[Path], current_file: Path) -> Path | None:
candidates = [current_file.parent / name, *(directory / name for directory in incdirs)]
for candidate in candidates:
if candidate.is_file():
return candidate.resolve()
return None
def inline_file(path: Path, incdirs, visited_headers):
out = []
if not path.exists():
return out
text = path.read_text()
for line in text.splitlines():
m = INCLUDE_RE.search(line)
if m:
hdr = m.group(1)
hdr_path = resolve_include(hdr, incdirs, path)
if hdr_path:
rp = str(hdr_path.resolve())
if rp in visited_headers:
continue
visited_headers.add(rp)
out.append(f'// `include "{hdr}" inlined from {hdr_path}')
out.extend(inline_file(hdr_path, incdirs, visited_headers))
out.append('// end inline')
else:
# keep original include if not found
out.append(line)
else:
out.append(line)
return out
def collect_includes(files: list[Path], incdirs: list[Path]) -> list[Path]:
headers: set[Path] = set()
def visit(path: Path) -> None:
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
match = INCLUDE_RE.search(line)
if not match:
continue
header = resolve_include(match.group(1), incdirs, path)
if header is None:
continue
if header not in headers:
headers.add(header)
visit(header)
for source in files:
visit(source)
return sorted(headers)
def build_single_sv(defines, incdirs, files, out_path: Path):
lines = []
# header
lines.append('// retroSoC single-file export')
lines.append(f'// Generated from {REPO_ROOT}')
# translate defines
lines.extend(defines_to_sv(defines))
lines.append('')
visited_headers = set()
for f in files:
fp = Path(f)
lines.append(f'// ====== file: {fp} ======')
lines.extend(inline_file(fp, incdirs, visited_headers))
lines.append('')
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text('\n'.join(lines) + '\n')
def inline_file(path: Path, incdirs: list[Path], stack: tuple[Path, ...] = ()) -> list[str]:
resolved = path.resolve()
if resolved in stack:
cycle = " -> ".join(str(item) for item in (*stack, resolved))
raise RuntimeError(f"cyclic `include detected: {cycle}")
output: list[str] = []
for line in resolved.read_text(encoding="utf-8", errors="replace").splitlines():
match = INCLUDE_RE.search(line)
if not match:
output.append(line)
continue
header = resolve_include(match.group(1), incdirs, resolved)
if header is None:
output.append(line)
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():
ap = argparse.ArgumentParser(description='Export retroSoC sources')
ap.add_argument('mode', choices=['tar', 'sv'], help='export as tar.gz or single .sv')
ap.add_argument('--soc', default='MINI', help='SOC: MINI or TINY')
args = ap.parse_args()
def defines_to_sv(defines: list[str]) -> list[str]:
output = []
for define in defines:
body = define[len("+define+") :]
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
_, yosys_abs = build_flist_if_missing(soc_dir)
defines, incdirs, files = read_flist(yosys_abs)
def write_single_sv(filelist: FileList, output: Path) -> None:
lines = ["// Generated by syn/tools/export_soc_sources.py", *defines_to_sv(filelist.defines), ""]
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':
bundle_root = export_dir / 'rtl'
if bundle_root.exists():
shutil.rmtree(bundle_root)
bundle_root.mkdir(parents=True)
# copy sources
copy_sources_to_export(files, bundle_root)
# copy headers referenced by sources
hdrs = collect_includes(files, incdirs)
copy_sources_to_export(hdrs, bundle_root)
# write manifests (absolute and relative-to-bundle)
abs_manifest = '\n'.join([*defines, *[f'+incdir+{d}' for d in incdirs], *files]) + '\n'
(bundle_root / 'filelist_abspath.fl').write_text(abs_manifest)
# relative manifest for portability
def to_bundle_rel(p: Path):
try:
rel = p.resolve().relative_to(REPO_ROOT)
except Exception:
rel = Path('_abs') / p.as_posix().lstrip('/')
return rel
rel_incdirs = [f'+incdir+{to_bundle_rel(Path(d))}' for d in incdirs]
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')
# create tar.gz
tar_path = export_dir / f'retrosoc_{soc}_sources.tar.gz'
with tarfile.open(tar_path, 'w:gz') as tf:
tf.add(bundle_root, arcname='rtl')
print(f'Generated: {tar_path}')
def bundle_relative(path: Path) -> Path:
try:
relative = path.resolve().relative_to(REPO_ROOT)
except ValueError:
return Path("_abs") / path.as_posix().lstrip("/")
if relative.parts and relative.parts[0] == "rtl":
return Path(*relative.parts[1:])
return Path("repo") / relative
def write_tar(filelist: FileList, export_dir: Path, soc: str) -> Path:
bundle = export_dir / "rtl"
if bundle.exists():
shutil.rmtree(bundle)
bundle.mkdir(parents=True)
sources = [*filelist.library_files, *filelist.files]
for source in [*sources, *collect_includes(sources, filelist.incdirs)]:
destination = bundle / bundle_relative(source)
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
manifest = [*filelist.defines]
manifest.extend(f"+incdir+{bundle_relative(path)}" for path in filelist.incdirs)
manifest.extend(str(bundle_relative(path)) for path in sources)
atomic_write(bundle / "filelist.fl", "\n".join(manifest) + "\n")
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:
sv_path = export_dir / f'retrosoc_asic_sources.sv'
build_single_sv(defines, incdirs, files, sv_path)
print(f'Generated: {sv_path}')
output = write_tar(filelist, export_dir, args.soc)
print(f"generated: {output}")
return 0
if __name__ == '__main__':
main()
if __name__ == "__main__":
raise SystemExit(main())
+8 -1
View File
@@ -12,6 +12,9 @@ set variables {
vlog_files { VLOG_FILES "" }
sv_flist { SV_FLIST "" }
pdk { PDK "" }
soc { SOC "" }
core { CORE "" }
ip { IP "" }
top_design { TOP_DESIGN "" }
hier_depth { HIER_DEPTH 0 }
period_ps { YOSYS_TARGET_PERIOD_PS 5000 }
@@ -20,6 +23,7 @@ set variables {
work_dir { WORK "[set dir [pwd]]/WORK" }
report_dir { REPORTS "[set dir [pwd]]/report" }
netlist { NETLIST "" }
config { CONFIG "" }
}
# either use env-var or default to fallback
@@ -40,6 +44,9 @@ foreach var [dict keys $variables] {
if {[string eq $netlist ""]} {
set netlist ${build_dir}/${top_design}_netlist.v
}
if {[string eq $config ""]} {
set config ${netlist}.config
}
proc envVarValid {var_name} {
if { [info exists ::env($var_name)]} {
@@ -64,4 +71,4 @@ proc processAbcScript {abc_script} {
flush $abc_out
close $abc_out
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
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
RTL_NAME ?= retrosoc_asic
SV_FLIST := $(YOSYS_DIR)/../../rtl/$(shell echo $(SOC) | tr A-Z a-z )/.generated_fl/yosys.fl
$(info SYNTH SV_FLIST PATH: $(SV_FLIST))
SV_FLIST := $(RTL_PATH)/.generated_fl/yosys.fl
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
$(info SYNTH RTL_FLIST: $(RTL_FLIST))
@python3 $(RTL_PATH)/script/comb.py $(RTL_FLIST)
@python3 $(RTL_PATH)/script/comb.py $(RTL_FLIST) --output $(SV_FLIST)
## 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_TMP)
@mkdir -p $(YOSYS_RPT)
PDK="$(PDK)" \
SV_FLIST="$(SV_FLIST)" \
TOP_DESIGN="$(TOP_DESIGN)" \
PROJ_NAME="$(RTL_NAME)" \
WORK="$(YOSYS_TMP)" \
BUILD="$(YOSYS_OUT)" \
REPORTS="$(YOSYS_RPT)" \
NETLIST="$(NETLIST)" \
yosys -c $(YOSYS_DIR)/script/synth.tcl \
export PDK="$(PDK)" SOC="$(SOC)" CORE="$(CORE)" IP="$(IP)" \
SV_FLIST="$(SV_FLIST)" TOP_DESIGN="$(TOP_DESIGN)" CONFIG="$(NETLIST_CONFIG)" \
PROJ_NAME="$(RTL_NAME)" WORK="$(YOSYS_TMP)" BUILD="$(YOSYS_OUT)" \
REPORTS="$(YOSYS_RPT)" NETLIST="$(NETLIST)"; \
set -o pipefail; yosys -c $(YOSYS_DIR)/script/synth.tcl \
2>&1 | TZ=UTC-8 gawk '{ print strftime("[%Y-%m-%d %H:%M %Z]"), $$0 }' \
| 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:
rm -rf $(YOSYS_OUT)
rm -rf $(YOSYS_TMP)
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()