chipfoundry-cli 1.2.9__tar.gz → 1.2.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {chipfoundry_cli-1.2.9 → chipfoundry_cli-1.2.10}/PKG-INFO +1 -1
- {chipfoundry_cli-1.2.9 → chipfoundry_cli-1.2.10}/chipfoundry_cli/main.py +140 -29
- {chipfoundry_cli-1.2.9 → chipfoundry_cli-1.2.10}/pyproject.toml +2 -2
- {chipfoundry_cli-1.2.9 → chipfoundry_cli-1.2.10}/LICENSE +0 -0
- {chipfoundry_cli-1.2.9 → chipfoundry_cli-1.2.10}/README.md +0 -0
- {chipfoundry_cli-1.2.9 → chipfoundry_cli-1.2.10}/chipfoundry_cli/__init__.py +0 -0
- {chipfoundry_cli-1.2.9 → chipfoundry_cli-1.2.10}/chipfoundry_cli/utils.py +0 -0
|
@@ -7,7 +7,7 @@ from chipfoundry_cli.utils import (
|
|
|
7
7
|
open_html_in_browser, download_with_progress, update_repo_files,
|
|
8
8
|
fetch_versions_from_upstream, parse_user_defines_v, update_user_defines_v,
|
|
9
9
|
get_gpio_config_from_project_json, save_gpio_config_to_project_json,
|
|
10
|
-
GPIO_MODES, GPIO_MODE_DESCRIPTIONS
|
|
10
|
+
GPIO_MODES, GPIO_MODE_DESCRIPTIONS, GPIO_HEX_TO_MODE
|
|
11
11
|
)
|
|
12
12
|
import os
|
|
13
13
|
from pathlib import Path
|
|
@@ -527,7 +527,7 @@ def gpio_config(project_root):
|
|
|
527
527
|
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
528
528
|
table.add_column("Mode", style="cyan", width=24)
|
|
529
529
|
table.add_column("Count", justify="right", width=6)
|
|
530
|
-
table.add_column("GPIOs"
|
|
530
|
+
table.add_column("GPIOs")
|
|
531
531
|
|
|
532
532
|
# Sort by count (most common first)
|
|
533
533
|
for mode_key in sorted(mode_groups.keys(), key=lambda k: -len(mode_groups[k])):
|
|
@@ -576,7 +576,7 @@ def gpio_config(project_root):
|
|
|
576
576
|
if not mode_key:
|
|
577
577
|
return "red"
|
|
578
578
|
elif "output" in mode_key:
|
|
579
|
-
return "
|
|
579
|
+
return "ansi_bright_blue"
|
|
580
580
|
elif "input" in mode_key:
|
|
581
581
|
return "cyan"
|
|
582
582
|
elif "bidirectional" in mode_key:
|
|
@@ -838,7 +838,7 @@ def gpio_config(project_root):
|
|
|
838
838
|
class GPIOGridApp(App):
|
|
839
839
|
"""Textual app for GPIO grid configuration."""
|
|
840
840
|
|
|
841
|
-
GRID_COLS =
|
|
841
|
+
GRID_COLS = 6 # Number of columns in the grid
|
|
842
842
|
|
|
843
843
|
CSS = """
|
|
844
844
|
Screen {
|
|
@@ -864,21 +864,21 @@ def gpio_config(project_root):
|
|
|
864
864
|
}
|
|
865
865
|
|
|
866
866
|
#gpio-grid {
|
|
867
|
-
grid-size:
|
|
868
|
-
grid-gutter:
|
|
869
|
-
padding:
|
|
867
|
+
grid-size: 6;
|
|
868
|
+
grid-gutter: 0;
|
|
869
|
+
padding: 0;
|
|
870
870
|
height: auto;
|
|
871
871
|
width: 100%;
|
|
872
872
|
}
|
|
873
873
|
|
|
874
874
|
GPIOButton {
|
|
875
875
|
width: 1fr;
|
|
876
|
-
min-width:
|
|
876
|
+
min-width: 20;
|
|
877
877
|
height: 4;
|
|
878
878
|
text-align: center;
|
|
879
|
-
border: solid
|
|
879
|
+
border: solid grey;
|
|
880
880
|
content-align: center middle;
|
|
881
|
-
padding: 0
|
|
881
|
+
padding: 0;
|
|
882
882
|
}
|
|
883
883
|
|
|
884
884
|
GPIOButton.current {
|
|
@@ -1089,10 +1089,9 @@ def gpio_config(project_root):
|
|
|
1089
1089
|
"""Open mode selection for selected GPIOs."""
|
|
1090
1090
|
selected = self._get_selected_gpios()
|
|
1091
1091
|
if not selected:
|
|
1092
|
-
# If nothing selected, use the
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
selected = [focused.gpio_num]
|
|
1092
|
+
# If nothing selected, use the current one (where cursor is)
|
|
1093
|
+
if 0 <= self.current_index < len(self.gpio_list):
|
|
1094
|
+
selected = [self.gpio_list[self.current_index]]
|
|
1096
1095
|
|
|
1097
1096
|
if selected:
|
|
1098
1097
|
self.push_screen(ModeSelectScreen(selected), self._apply_mode)
|
|
@@ -1102,9 +1101,9 @@ def gpio_config(project_root):
|
|
|
1102
1101
|
if mode_key:
|
|
1103
1102
|
selected = self._get_selected_gpios()
|
|
1104
1103
|
if not selected:
|
|
1105
|
-
|
|
1106
|
-
if
|
|
1107
|
-
selected = [
|
|
1104
|
+
# Use current one if nothing selected
|
|
1105
|
+
if 0 <= self.current_index < len(self.gpio_list):
|
|
1106
|
+
selected = [self.gpio_list[self.current_index]]
|
|
1108
1107
|
|
|
1109
1108
|
for gpio_num in selected:
|
|
1110
1109
|
real_gpio = self.user_to_real_map[gpio_num]
|
|
@@ -1198,6 +1197,119 @@ def gpio_config(project_root):
|
|
|
1198
1197
|
except Exception as e:
|
|
1199
1198
|
console.print(f"[red]Error updating user_defines.v: {e}[/red]")
|
|
1200
1199
|
|
|
1200
|
+
|
|
1201
|
+
@main.command('gpio-status')
|
|
1202
|
+
@click.option('--project-root', required=False, type=click.Path(exists=True, file_okay=False), help='Path to the project directory (defaults to current directory).')
|
|
1203
|
+
def gpio_status(project_root):
|
|
1204
|
+
"""Display current GPIO configuration summary."""
|
|
1205
|
+
if not project_root:
|
|
1206
|
+
project_root = os.getcwd()
|
|
1207
|
+
|
|
1208
|
+
project_root = Path(project_root)
|
|
1209
|
+
|
|
1210
|
+
# Load project config
|
|
1211
|
+
project_json_path = project_root / '.cf' / 'project.json'
|
|
1212
|
+
if not project_json_path.exists():
|
|
1213
|
+
console.print("[red]Error: No project configuration found.[/red]")
|
|
1214
|
+
console.print("[dim]Run 'cf gpio-config' first to configure GPIOs.[/dim]")
|
|
1215
|
+
return
|
|
1216
|
+
|
|
1217
|
+
with open(project_json_path, 'r') as f:
|
|
1218
|
+
project_config = json.load(f)
|
|
1219
|
+
|
|
1220
|
+
# GPIO config is stored under project.gpio_config
|
|
1221
|
+
project_data = project_config.get('project', {})
|
|
1222
|
+
gpio_configs = project_data.get('gpio_config', {})
|
|
1223
|
+
if not gpio_configs:
|
|
1224
|
+
console.print("[yellow]No GPIO configuration found in project.json[/yellow]")
|
|
1225
|
+
console.print("[dim]Run 'cf gpio-config' to configure GPIOs.[/dim]")
|
|
1226
|
+
return
|
|
1227
|
+
|
|
1228
|
+
# Detect project type
|
|
1229
|
+
project_type = project_data.get('type')
|
|
1230
|
+
if not project_type:
|
|
1231
|
+
project_type = detect_project_type(project_root)
|
|
1232
|
+
|
|
1233
|
+
# Determine GPIO range based on project type
|
|
1234
|
+
if project_type == 'caravan':
|
|
1235
|
+
user_gpio_range = list(range(0, 32))
|
|
1236
|
+
gpio_label = "Caravan"
|
|
1237
|
+
else:
|
|
1238
|
+
user_gpio_range = list(range(5, 38))
|
|
1239
|
+
gpio_label = "Caravel"
|
|
1240
|
+
|
|
1241
|
+
user_to_real_map = {g: g for g in user_gpio_range}
|
|
1242
|
+
|
|
1243
|
+
# Convert string keys back to int if needed
|
|
1244
|
+
gpio_configs_int = {}
|
|
1245
|
+
for k, v in gpio_configs.items():
|
|
1246
|
+
try:
|
|
1247
|
+
gpio_configs_int[int(k)] = v
|
|
1248
|
+
except (ValueError, TypeError):
|
|
1249
|
+
gpio_configs_int[k] = v
|
|
1250
|
+
|
|
1251
|
+
def find_mode_key(mode_value):
|
|
1252
|
+
"""Find the mode key for a given mode value (handles hex values)."""
|
|
1253
|
+
if not mode_value:
|
|
1254
|
+
return None
|
|
1255
|
+
# First, try to convert hex to mode constant name
|
|
1256
|
+
mode_name = GPIO_HEX_TO_MODE.get(mode_value, mode_value)
|
|
1257
|
+
# Then find the short key for that mode constant
|
|
1258
|
+
for key, name in GPIO_MODES.items():
|
|
1259
|
+
if name == mode_name and key != "invalid":
|
|
1260
|
+
return key
|
|
1261
|
+
return None
|
|
1262
|
+
|
|
1263
|
+
def format_gpio_ranges(gpio_list):
|
|
1264
|
+
"""Convert [5,6,7,10,11,15] to '5-7, 10-11, 15'."""
|
|
1265
|
+
if not gpio_list:
|
|
1266
|
+
return "-"
|
|
1267
|
+
gpio_list = sorted(gpio_list)
|
|
1268
|
+
ranges = []
|
|
1269
|
+
start = gpio_list[0]
|
|
1270
|
+
end = start
|
|
1271
|
+
for g in gpio_list[1:]:
|
|
1272
|
+
if g == end + 1:
|
|
1273
|
+
end = g
|
|
1274
|
+
else:
|
|
1275
|
+
ranges.append(f"{start}-{end}" if start != end else str(start))
|
|
1276
|
+
start = end = g
|
|
1277
|
+
ranges.append(f"{start}-{end}" if start != end else str(start))
|
|
1278
|
+
return ", ".join(ranges)
|
|
1279
|
+
|
|
1280
|
+
# Group by mode
|
|
1281
|
+
mode_groups = {}
|
|
1282
|
+
for user_gpio, real_gpio in user_to_real_map.items():
|
|
1283
|
+
mode_value = gpio_configs_int.get(real_gpio)
|
|
1284
|
+
mode_key = find_mode_key(mode_value) if mode_value else None
|
|
1285
|
+
if mode_key not in mode_groups:
|
|
1286
|
+
mode_groups[mode_key] = []
|
|
1287
|
+
mode_groups[mode_key].append(user_gpio)
|
|
1288
|
+
|
|
1289
|
+
console.print(f"\n[bold cyan]GPIO Configuration ({gpio_label})[/bold cyan]\n")
|
|
1290
|
+
|
|
1291
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
1292
|
+
table.add_column("Mode", style="cyan", width=24)
|
|
1293
|
+
table.add_column("Count", justify="right", width=6)
|
|
1294
|
+
table.add_column("GPIOs")
|
|
1295
|
+
|
|
1296
|
+
# Sort by count (most common first)
|
|
1297
|
+
for mode_key in sorted(mode_groups.keys(), key=lambda k: -len(mode_groups[k])):
|
|
1298
|
+
gpios = mode_groups[mode_key]
|
|
1299
|
+
display_name = mode_key if mode_key else "[red]unconfigured[/red]"
|
|
1300
|
+
style = ""
|
|
1301
|
+
if mode_key:
|
|
1302
|
+
if "output" in mode_key: style = "[green]"
|
|
1303
|
+
elif "input" in mode_key: style = "[cyan]"
|
|
1304
|
+
elif "bidirectional" in mode_key: style = "[yellow]"
|
|
1305
|
+
elif "analog" in mode_key: style = "[magenta]"
|
|
1306
|
+
display_name = f"{style}{mode_key}[/]"
|
|
1307
|
+
table.add_row(display_name, str(len(gpios)), format_gpio_ranges(gpios))
|
|
1308
|
+
|
|
1309
|
+
console.print(table)
|
|
1310
|
+
console.print()
|
|
1311
|
+
|
|
1312
|
+
|
|
1201
1313
|
@main.command('push')
|
|
1202
1314
|
@click.option('--project-root', required=False, type=click.Path(exists=True, file_okay=False), help='Path to the local ChipFoundry project directory (defaults to current directory if .cf/project.json exists).')
|
|
1203
1315
|
@click.option('--sftp-host', default=DEFAULT_SFTP_HOST, show_default=True, help='SFTP server hostname.')
|
|
@@ -2496,10 +2608,12 @@ def setup(project_root, repo_owner, repo_name, branch, pdk, caravel_lite,
|
|
|
2496
2608
|
def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry_run):
|
|
2497
2609
|
"""Harden a macro using LibreLane (OpenLane 2).
|
|
2498
2610
|
|
|
2611
|
+
If no macro is specified, lists all available macros.
|
|
2612
|
+
|
|
2499
2613
|
Examples:
|
|
2500
|
-
cf harden
|
|
2614
|
+
cf harden # List available macros
|
|
2615
|
+
cf harden user_proj_example # Harden a specific macro
|
|
2501
2616
|
cf harden user_project_wrapper
|
|
2502
|
-
cf harden --list
|
|
2503
2617
|
"""
|
|
2504
2618
|
from datetime import datetime
|
|
2505
2619
|
|
|
@@ -2512,8 +2626,8 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
|
|
|
2512
2626
|
|
|
2513
2627
|
project_root_path = Path(project_root)
|
|
2514
2628
|
|
|
2515
|
-
# Check if project is initialized (skip check for --list, allow graceful return)
|
|
2516
|
-
if not list_designs:
|
|
2629
|
+
# Check if project is initialized (skip check for --list or when no macro specified, allow graceful return)
|
|
2630
|
+
if not list_designs and macro:
|
|
2517
2631
|
if not check_project_initialized(project_root_path, 'harden', dry_run=dry_run, allow_graceful=True):
|
|
2518
2632
|
console.print(f"[red]✗[/red] Project not initialized. Please run 'cf init' first.")
|
|
2519
2633
|
console.print("[yellow]Run 'cf setup' first to install OpenLane[/yellow]")
|
|
@@ -2527,7 +2641,11 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
|
|
|
2527
2641
|
console.print("[yellow]Run 'cf setup' first to install OpenLane[/yellow]")
|
|
2528
2642
|
return
|
|
2529
2643
|
|
|
2530
|
-
#
|
|
2644
|
+
# If no macro specified, default to listing available macros
|
|
2645
|
+
if not macro:
|
|
2646
|
+
list_designs = True
|
|
2647
|
+
|
|
2648
|
+
# List designs if requested (or if no macro specified)
|
|
2531
2649
|
if list_designs:
|
|
2532
2650
|
console.print("[bold cyan]Available macros:[/bold cyan]")
|
|
2533
2651
|
designs = [d.name for d in openlane_dir.iterdir() if d.is_dir() and ((d / 'config.json').exists() or (d / 'config.yaml').exists() or (d / 'config.tcl').exists())]
|
|
@@ -2544,13 +2662,6 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
|
|
|
2544
2662
|
console.print("[yellow]No macros found in openlane/[/yellow]")
|
|
2545
2663
|
return
|
|
2546
2664
|
|
|
2547
|
-
# Macro is required if not listing
|
|
2548
|
-
if not macro:
|
|
2549
|
-
console.print("[red]✗[/red] Error: MACRO argument is required")
|
|
2550
|
-
console.print("[yellow]Usage:[/yellow] cf harden <macro>")
|
|
2551
|
-
console.print("[yellow] [/yellow] cf harden --list")
|
|
2552
|
-
return
|
|
2553
|
-
|
|
2554
2665
|
# Check if macro exists
|
|
2555
2666
|
macro_dir = openlane_dir / macro
|
|
2556
2667
|
if not macro_dir.exists():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "chipfoundry-cli"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.10"
|
|
4
4
|
description = "CLI tool to automate ChipFoundry project submission to SFTP server"
|
|
5
5
|
authors = ["ChipFoundry <marwan.abbas@chipfoundry.io>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -18,7 +18,7 @@ toml = ">=0.10,<1.0"
|
|
|
18
18
|
httpx = ">=0.24.0,<1.0"
|
|
19
19
|
textual = ">=0.40.0,<1"
|
|
20
20
|
|
|
21
|
-
[tool.poetry.dev
|
|
21
|
+
[tool.poetry.group.dev.dependencies]
|
|
22
22
|
wheel = "*"
|
|
23
23
|
black = ">=24.4.0,<25"
|
|
24
24
|
flake8 = ">=4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|