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