diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..0564dc22 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,19 @@ +name: 'lint' + +on: + push: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: | + pip install pycodestyle + pip install pylint + pip install . + - name: Lint + run: | + pycodestyle fpga examples test + pylint fpga diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce081042..96b267ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,15 +17,15 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - pip install pycodestyle - pip install pylint pip install pytest pip install . - - name: Lint + - name: Pull container images run: | - pycodestyle fpga examples test - pylint fpga - git diff --check --cached + docker pull hdlc/prjtrellis + docker pull hdlc/ghdl:yosys + docker pull hdlc/icestorm + docker pull hdlc/nextpnr:ecp5 + docker pull hdlc/nextpnr:ice40 - name: Test run: | pytest diff --git a/fpga/project.py b/fpga/project.py index 5173d965..fa2bbf7d 100644 --- a/fpga/project.py +++ b/fpga/project.py @@ -237,7 +237,7 @@ def set_top(self, toplevel): toplevel = os.path.join(self._absdir, toplevel) toplevel = os.path.normpath(toplevel) if os.path.exists(toplevel): - with open(toplevel, 'r') as file: + with open(toplevel, 'r', encoding='utf-8') as file: hdl = file.read() # Removing comments, newlines and carriage-returns hdl = re.sub(r'--.*[$\n]|\/\/.*[$\n]', '', hdl) diff --git a/fpga/tool/__init__.py b/fpga/tool/__init__.py index 8ff2783f..1f82fc3e 100644 --- a/fpga/tool/__init__.py +++ b/fpga/tool/__init__.py @@ -1,6 +1,6 @@ # # Copyright (C) 2019-2020 INTI -# Copyright (C) 2019-2020 Rodrigo A. Melo +# Copyright (C) 2019-2021 Rodrigo A. Melo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -37,10 +37,8 @@ def check_value(value, values): """Check if VALUE is included in VALUES.""" if value not in values: - raise ValueError( - '{} is not a valid value [{}]' - .format(value, ", ".join(values)) - ) + joined_values = ", ".join(values) + raise ValueError(f'{value} is not a valid value [{joined_values}]') def run(command, capture): @@ -114,7 +112,7 @@ def _configure(self): filename = '.pyfpga.yml' self.configs = {} if os.path.exists(filename): - with open(filename, 'r') as file: + with open(filename, 'r', encoding='utf-8') as file: data = safe_load(file) if self._TOOL in data: self.configs = data[self._TOOL] @@ -163,30 +161,30 @@ def _create_gen_script(self, tasks): # Paths and files files = [] if self.presynth: - files.append(' fpga_file {}.edif'.format(self.project)) + files.append(f' fpga_file {self.project}.edif') else: for path in self.paths: - files.append(' fpga_include {}'.format(tcl_path(path))) + files.append(f' fpga_include {tcl_path(path)}') for file in self.files['verilog']: - files.append(' fpga_file {}'.format(tcl_path(file[0]))) + files.append(f' fpga_file {tcl_path(file[0])}') for file in self.files['vhdl']: if file[1] is None: - files.append(' fpga_file {}'.format(tcl_path(file[0]))) + files.append(f' fpga_file {tcl_path(file[0])}') else: - files.append(' fpga_file {} {}'.format( - tcl_path(file[0]), file[1] - )) + files.append( + f' fpga_file {tcl_path(file[0])} {file[1]}' + ) for file in self.files['design']: - files.append(' fpga_design {}'.format(tcl_path(file[0]))) + files.append(f' fpga_design {tcl_path(file[0])}') for file in self.files['constraint']: - files.append(' fpga_file {}'.format(tcl_path(file[0]))) + files.append(f' fpga_file {tcl_path(file[0])}') # Parameters params = [] for param in self.params: - params.append('{{ {} {} }}'.format(param[0], param[1])) + params.append(f'{{ {param[0]} {param[1]} }}') # Script creation template = os.path.join(os.path.dirname(__file__), 'template.tcl') - with open(template, 'r') as file: + with open(template, 'r', encoding='utf-8') as file: tcl = file.read() tcl = tcl.replace('#TOOL#', self._TOOL) tcl = tcl.replace('#PRESYNTH#', "True" if self.presynth else "False") @@ -206,7 +204,7 @@ def _create_gen_script(self, tasks): tcl = tcl.replace('#POSTSYN_CMDS#', '\n'.join(self.cmds['postsyn'])) tcl = tcl.replace('#POSTIMP_CMDS#', '\n'.join(self.cmds['postimp'])) tcl = tcl.replace('#POSTBIT_CMDS#', '\n'.join(self.cmds['postbit'])) - with open('%s.tcl' % self._TOOL, 'w') as file: + with open(f'{self._TOOL}.tcl', 'w', encoding='utf-8') as file: file.write(tcl) def generate(self, to_task, from_task, capture): @@ -217,15 +215,13 @@ def generate(self, to_task, from_task, capture): from_index = TASKS.index(from_task) if from_index > to_index: raise ValueError( - 'initial task "{}" cannot be later than the last task "{}"' - .format(from_task, to_task) + f'initial task "{from_task}" cannot be later than the ' + + f'last task "{to_task}"' ) tasks = " ".join(TASKS[from_index:to_index+1]) self._create_gen_script(tasks) if not which(self._GEN_PROGRAM): - raise RuntimeError( - 'program "{}" not found'.format(self._GEN_PROGRAM) - ) + raise RuntimeError(f'program "{self._GEN_PROGRAM}" not found') return run(self._GEN_COMMAND, capture) def set_bitstream(self, path): @@ -235,9 +231,7 @@ def set_bitstream(self, path): def transfer(self, devtype, position, part, width, capture): """Transfer a bitstream.""" if not which(self._TRF_PROGRAM): - raise RuntimeError( - 'program "{}" not found'.format(self._TRF_PROGRAM) - ) + raise RuntimeError(f'program "{self._TRF_PROGRAM}" not found') check_value(devtype, self._DEVTYPES) check_value(position, range(10)) isinstance(part, str) @@ -247,7 +241,7 @@ def transfer(self, devtype, position, part, width, capture): if not self.bitstream and devtype not in ['detect', 'unlock']: bitstream = [] for ext in self._BIT_EXT: - bitstream.extend(glob('**/*.{}'.format(ext), recursive=True)) + bitstream.extend(glob(f'**/*.{ext}', recursive=True)) if len(bitstream) == 0: raise FileNotFoundError('bitStream not found') self.bitstream = bitstream[0] diff --git a/fpga/tool/ise.py b/fpga/tool/ise.py index f162705d..c6f5e83d 100644 --- a/fpga/tool/ise.py +++ b/fpga/tool/ise.py @@ -1,6 +1,6 @@ # # Copyright (C) 2019-2020 INTI -# Copyright (C) 2019-2020 Rodrigo A. Melo +# Copyright (C) 2019-2021 Rodrigo A. Melo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -132,7 +132,7 @@ def set_part(self, part): device, speed, package = re.findall(r'(\w+)-(\w+)-(\w+)', part)[0] if len(speed) > len(package): speed, package = package, speed - part = "{}-{}-{}".format(device, speed, package) + part = f'{device}-{speed}-{package}' except IndexError: raise ValueError( 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE-SPEED' @@ -164,7 +164,7 @@ def transfer(self, devtype, position, part, width, capture): temp = temp.replace('#POSITION#', str(position)) temp = temp.replace('#NAME#', part) temp = temp.replace('#WIDTH#', str(width)) - with open('ise-prog.impact', 'w') as file: + with open('ise-prog.impact', 'w', encoding='utf-8') as file: file.write(temp) return run(self._TRF_COMMAND, capture) diff --git a/fpga/tool/libero.py b/fpga/tool/libero.py index 92f0cc44..ee463993 100644 --- a/fpga/tool/libero.py +++ b/fpga/tool/libero.py @@ -1,6 +1,6 @@ # # Copyright (C) 2019-2020 INTI -# Copyright (C) 2019-2020 Rodrigo A. Melo +# Copyright (C) 2019-2021 Rodrigo A. Melo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -64,7 +64,7 @@ def set_part(self, part): speed, package = package, speed if speed == '': speed = 'STD' - part = "{}-{}-{}".format(device, speed, package) + part = f'{device}-{speed}-{package}' except IndexError: raise ValueError( 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE' diff --git a/fpga/tool/openflow.py b/fpga/tool/openflow.py index 06ce76c0..0eb6b905 100644 --- a/fpga/tool/openflow.py +++ b/fpga/tool/openflow.py @@ -1,6 +1,6 @@ # # Copyright (C) 2020 INTI -# Copyright (C) 2020 Rodrigo A. Melo +# Copyright (C) 2020-2021 Rodrigo A. Melo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -60,7 +60,7 @@ def _configure(self): command = engine.get('command', 'docker') + ' run --rm' volumes = '-v ' + ('-v ').join(engine.get('volumes', ['$HOME:$HOME'])) work = '-w ' + engine.get('work', '$PWD') - self.oci_engine = '{} {} {}'.format(command, volumes, work) + self.oci_engine = f'{command} {volumes} {work}' # Containers defaults = { 'ghdl': 'ghdl/synth:beta', @@ -90,7 +90,7 @@ def set_part(self, part): self.part['device'] = aux[0] self.part['package'] = aux[1] elif len(aux) == 3: - self.part['device'] = '{}-{}'.format(aux[0], aux[1]) + self.part['device'] = f'{aux[0]}-{aux[1]}' self.part['package'] = aux[2] else: raise ValueError('Part must be DEVICE-PACKAGE') @@ -103,7 +103,7 @@ def _create_gen_script(self, tasks): # Verilog includes paths = [] for path in self.paths: - paths.append('verilog_defaults -add -I{}'.format(path)) + paths.append(f'verilog_defaults -add -I{path}') # Files constraints = [] verilogs = [] @@ -111,28 +111,24 @@ def _create_gen_script(self, tasks): for file in self.files['vhdl']: lib = '' if file[1] is not None: - lib = '--work={}'.format(file[1]) - vhdls.append('{} -a $FLAGS {} {}'.format( - self.tools['ghdl'], lib, file[0]) - ) + lib = f'--work={file[1]}' + vhdls.append(f'{self.tools["ghdl"]} -a $FLAGS {lib} {file[0]}') for file in self.files['verilog']: if file[0].endswith('.sv'): - verilogs.append('read_verilog -sv -defer {}'.format(file[0])) + verilogs.append(f'read_verilog -sv -defer {file[0]}') else: - verilogs.append('read_verilog -defer {}'.format(file[0])) + verilogs.append(f'read_verilog -defer {file[0]}') for file in self.files['constraint']: constraints.append(file[0]) if len(vhdls) > 0: - verilogs = ['ghdl $FLAGS {}'.format(self.top)] + verilogs = [f'ghdl $FLAGS {self.top}'] # Parameters params = [] for param in self.params: - params.append('chparam -set {} {} {}'.format( - param[0], param[1], self.top - )) + params.append(f'chparam -set {param[0]} {param[1]} {self.top}') # Script creation template = os.path.join(os.path.dirname(__file__), 'template.sh') - with open(template, 'r') as file: + with open(template, 'r', encoding='utf-8') as file: text = file.read() text = text.format( backend=self.backend, @@ -165,7 +161,7 @@ def _create_gen_script(self, tasks): tool_nextpnr_ecp5=self.tools['nextpnr-ecp5'], tool_ecppack=self.tools['ecppack'] ) - with open('%s.sh' % self._TOOL, 'w') as file: + with open(f'{self._TOOL}.sh', 'w', encoding='utf-8') as file: file.write(text) def generate(self, to_task, from_task, capture): @@ -177,7 +173,7 @@ def generate(self, to_task, from_task, capture): def transfer(self, devtype, position, part, width, capture): super().transfer(devtype, position, part, width, capture) template = os.path.join(os.path.dirname(__file__), 'openprog.sh') - with open(template, 'r') as file: + with open(template, 'r', encoding='utf-8') as file: text = file.read() text = text.format( family=self.part['family'], @@ -189,7 +185,7 @@ def transfer(self, devtype, position, part, width, capture): tool_iceprog=self.tools['iceprog'], tool_openocd=self.tools['openocd'] ) - with open('openprog.sh', 'w') as file: + with open('openprog.sh', 'w', encoding='utf-8') as file: file.write(text) return run(self._TRF_COMMAND, capture) diff --git a/fpga/tool/vivado.py b/fpga/tool/vivado.py index 86d1e62f..067644ed 100644 --- a/fpga/tool/vivado.py +++ b/fpga/tool/vivado.py @@ -92,6 +92,6 @@ def transfer(self, devtype, position, part, width, capture): temp = _TEMPLATES[devtype] if devtype != 'detect': temp = temp.replace('#BITSTREAM#', self.bitstream) - with open('vivado-prog.tcl', 'w') as file: + with open('vivado-prog.tcl', 'w', encoding='utf-8') as file: file.write(temp) return run(self._TRF_COMMAND, capture)