juanmard/icestudio

View on GitHub
ICETool/__init__.py

Summary

Maintainability
B
5 hrs
Test Coverage
#!/usr/bin/env python3

from sys import (
    argv as sys_argv,
    exit as sys_exit,
    stdout as sys_stdout,
    stderr as sys_stderr
)
from os import getenv
from shutil import which
from pathlib import Path
from subprocess import check_call
import click
import re
import json

from ICETool.constraints.constraints import getBoardsInfo


def getBoard(board):
    """
    Get board info from hdl/constraints.
    """
    ConvertBoardNameFromIcestudioToConstraints = {
        "icezum": "IceZumAlhambra",
        "alhambra-ii": "IceZumAlhambraII",
        "icestick": "IceStick",
        "upduino": "UPDuino-v1.0",
        "upduino2": "UPDuino-v2.0",
        "upduino21": "UPDuino-v2.1",
        "upduino3": "UPDuino-v3.0"
    }
    boardInfo = getBoardsInfo(verbose=False)[(
        ConvertBoardNameFromIcestudioToConstraints[board]
        if board in ConvertBoardNameFromIcestudioToConstraints else
        board
    )]
    print(boardInfo)
    return boardInfo


def execCommand(cmd, cwd):
    """
    Print a command, execute it, and flush stdout and stderr.
    """
    print(cmd)
    check_call(cmd, cwd=cwd)
    sys_stdout.flush()
    sys_stderr.flush()


@click.command("verify")
@click.pass_context
@click.option("-p", "--project-dir", type=str, metavar="path", help="Set the target directory for the project.")
@click.option("-b", "--board", type=str, metavar="board", help="Set the board.")
@click.option("-v", "--verbose", is_flag=True, help="Show the entire output of the command.")
def VerifyCommand(ctx, board, project_dir, verbose):
    """
    Verify verilog sources through Icarus Verilog.
    """

    YOSYS_SHARE_PATH = Path(which('yosys')).parent.parent / 'share/yosys'

    boardInfo = getBoard(board)

    device = boardInfo.Device.split('-')[0].lower()

    sources = " ".join([item.name for item in Path(project_dir).glob("*.v")])

    opts = '-D NO_ICE40_DEFAULT_ASSIGNMENTS' if device == 'ice40' else '-D NO_INCLUDES'

    execCommand(
        f'iverilog -D VCD_OUTPUT=sim.vcd {opts} {YOSYS_SHARE_PATH!s}/{device}/cells_sim.v {sources}',
        project_dir
    )

    print("verbose:", verbose)

    ctx.exit(0)


@click.command("build")
@click.pass_context
@click.option("-b", "--board", type=str, metavar="board", help="Set the board.")
@click.option("--device", type=str, metavar="device", help="Set the FPGA device.")
@click.option("--package", type=str, metavar="package", help="Set the FPGA package.")
@click.option("-p", "--project-dir", type=str, metavar="path", help="Set the target directory for the project.")
@click.option("-v", "--verbose", is_flag=True, help="Show the entire output of the command.")
@click.option("--verbose-yosys", is_flag=True, help="Show the yosys output of the command.")
@click.option("--verbose-pnr", is_flag=True, help="Show the pnr output of the command.")
def BuildCommand(ctx, board, device, package, project_dir, verbose, verbose_yosys, verbose_pnr):
    """
    Generate bitstream through Yosys and Nextpnr.
    """

    sources = " ".join([item.name for item in Path(project_dir).glob("*.v")])
    opts = '' if verbose or verbose_yosys else '-q'

    execCommand(
        f'yosys {opts} -p "proc; read_verilog {sources}; synth_ice40 -top main -json synth.json"',
        project_dir
    )

    boardInfo = getBoard(board)
    device = boardInfo.Device.split('-')[1].lower() if device is None else device
    package = boardInfo.Package.lower() if package is None else package
    pcf = [item.name for item in Path(project_dir).glob("*.pcf")][0]
    opts = '' if verbose or verbose_pnr else '-q'

    execCommand(
        f'nextpnr-ice40 {opts} --{device} --package {package} --pcf {pcf} --json synth.json --asc pnr.asc',
        project_dir
    )

    execCommand(
        'icepack pnr.asc design.bin',
        project_dir
    )

    ctx.exit(0)


@click.command("upload")
@click.pass_context
@click.option("-b", "--board", type=str, metavar="board", help="Set the board.")
@click.option("--serial-port", type=str, metavar="serial-port", help="Set the serial port.")
@click.option("--ftdi-id", type=str, metavar="ftdi-id", help="Set the FTDI id.")
@click.option("-s", "--sram", is_flag=True, help="Perform SRAM programming.")
@click.option("-f", "--flash", is_flag=True, help="Perform FLASH programming.")
@click.option("-p", "--project-dir", type=str, metavar="path", help="Set the target directory for the project.")
@click.option("-v", "--verbose", is_flag=True, help="Show the entire output of the command.")
@click.option("--verbose-yosys", is_flag=True, help="Show the yosys output of the command.")
@click.option("--verbose-pnr", is_flag=True, help="Show the pnr output of the command.")
def UploadCommand(ctx, board, serial_port, ftdi_id, sram, flash, project_dir, verbose, verbose_yosys, verbose_pnr):
    """
    Upload bitstream to the board through openFPGALoader.
    """

    print("board:", board)
    print("serial_port:", serial_port)
    print("ftdi_id:", ftdi_id)
    print("sram:", sram)
    print("flash:", flash)
    print("verbose:", verbose)
    print("verbose_yosys:", verbose_yosys)
    print("verbose_pnr:", verbose_pnr)

    ctx.exit(0)


@click.command("regenerate-pinouts")
@click.pass_context
@click.option("-d", "--rdir", type=str, metavar="rdir", help="Resources directory.")
def RegeneratePinoutsCommand(ctx, rdir):
    p = Path(rdir)

    for item in list(p.glob('*')):
        if item.is_dir() and item.name[0] != '_':
            path = item
            cfile = path / 'pinout.pcf'
            if not cfile.exists():
                cfile = path / 'pinout.lpf'
                if not cfile.exists():
                    raise Exception('No known constraints file found in %s', str(path))

            print('ยท Processing %s file %s' % (item.name, cfile.name))

            pattern = 'set_io\s+(--warn-no-port|-nowarn)?\s*(.*?)\s+(.*?)\s+(#+\s+)?' if cfile.suffix == '.pcf' else r'LOCATE\s*?COMP\s*?"(.*)"\s*?SITE\s*?"(.*)";\s*?#?\s*?'
            pinout = re.findall(pattern, cfile.read_text())

            if len(pinout) == 0:
                print('  !!! Something went wrong; empty pinout list')
                continue

            info = json.loads((path / 'info.json').read_text())

            info['pinout'] = { item[1]: item[2] for item in sorted(pinout, key=lambda pinout: pinout[1],reverse=True) } if cfile.suffix == '.pcf' else { item[0]: item[1] for item in pinout }

            (path / 'info.json').write_text(json.dumps(info, indent=2) + "\n")


@click.group()
def cli():
    pass

cli.add_command(VerifyCommand)
cli.add_command(BuildCommand)
cli.add_command(UploadCommand)
cli.add_command(RegeneratePinoutsCommand)


if __name__ == '__main__':
    cli()