mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
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:
35 files changed
+1783
-762
No files matched your search
@@ -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"
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
*.rc
|
||||
*.aig
|
||||
*.so
|
||||
*.py[cod]
|
||||
__pycache__/
|
||||
.vscode
|
||||
pic
|
||||
video.h
|
||||
|
||||
@@ -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)
|
||||
@@ -3,3 +3,28 @@
|
||||
[](https://github.com/retroSoC/retroSoC/actions/workflows/style.yml)
|
||||
[](https://github.com/retroSoC/retroSoC/actions/workflows/lint.yml)
|
||||
[](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
@@ -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())
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
+13
-9
@@ -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
|
||||
+16
-10
@@ -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
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
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()
|
||||
|
||||
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 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())
|
||||
@@ -0,0 +1 @@
|
||||
"""Repository automation helpers."""
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -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())
|
||||
+20
-1
@@ -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
|
||||
+15
-7
@@ -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
@@ -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.
|
||||
INCLUDE_RE = re.compile(r'^\s*`include\s+"([^"]+)"')
|
||||
|
||||
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())
|
||||
|
||||
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 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:
|
||||
# Treat as a file path entry
|
||||
files.append(tok)
|
||||
return defines, incdirs, files
|
||||
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 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 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 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:
|
||||
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
|
||||
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
|
||||
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):
|
||||
def bundle_relative(path: Path) -> 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}')
|
||||
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())
|
||||
@@ -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)]} {
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user