Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jl/basys2project #1

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,56 @@
A tool to program the FPGA on Basys 2 boards.
adepttool
=======================

A set of tools to program the FPGA on Basys 2 boards.

Installation
----
Run::

./install.sh


Programmer
----
Usage: install Python 3 and libusb1, make sure you have the access rights
to the USB device (use the attached udev rule if you must), then run::

./basys2_prog.py file.bit
basys2_prog file.bit


Project builder
----
Creates handy `Makefile` for code synth and chip programming.
Usage::

basys2_prj [-vvv]

Flow:

1. Create project dir ``X``.
2. Create main file ``X.v`` with top module named ``X`` inside.
3. Write other modules.
4. Run ``basys2_prj`` and generate builder.
5. ``make``
6. ``make prog``

EEP
----
Simple communication with Basys2 board via EPP. [If you implemented such module, of course.]

Get register::

basys2_epp -g ADDR

Put register::

basys2_epp -p ADDR VAL

``ADDR`` and ``VAL`` should be hexstring, eg. ``ff``, ``0xa1``.

List devices
----
Usage::

basys2_list

40 changes: 40 additions & 0 deletions basys2_epp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env python3

import argparse
import usb1
from adepttool.device import get_devices
import sys

parser = argparse.ArgumentParser(description='Super simple EPP communication with the FPGA on Basys 2.')
parser.add_argument('--device', type=int, help='Device index (Default: 0)', default=0)
parser.add_argument('--port', type=int, help='Port index (Default: 0)', default=0)
parser.add_argument('-p', nargs=2, metavar=("ADDR", "VAL"), help='Put VAL into ADDR. (eg. -p 0e 0x10) ')
parser.add_argument('-g', metavar="ADDR", help='Get value from ADDR. (eg. -g 0a)')

args = parser.parse_args()

with usb1.USBContext() as ctx:
devs = get_devices(ctx)
if args.device >= len(devs):
if not devs:
print('No devices found.')
else:
print('Invalid device index (max is {})'.format(len(devs)-1))
sys.exit(1)
dev = devs[args.device]
dev.start()
port = dev.depp_ports[args.port]
port.enable()

if args.g is not None:
addr = int(args.g, 16)
print("%x" % port.get_reg(addr, 1)[0])
elif args.p is not None:
addr, val = args.p
addr, val = int(addr, 16), int(val, 16)
port.put_reg(addr, [val])
else:
print('DEPP PORT {port.idx}: {port.caps:08x}'.format(port=port))

port.disable()

29 changes: 29 additions & 0 deletions basys2_prj.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env python3

from project import Pins, Project
import os
import logging
from argparse import ArgumentParser


if __name__ == '__main__':
parser = ArgumentParser(description='Basys2 project builder builder')
parser.add_argument('-v', action='count', default=0)
parser.add_argument('--pins', action="store_true", help='List available pins');
args = parser.parse_args()

if args.v < 1:
lvl = logging.ERROR
elif args.v < 2:
lvl = logging.WARN
else:
lvl = logging.INFO
logging.basicConfig(level=lvl)

if args.pins:
pins = Pins()
pins.print_all()
else:
project = Project(root_dir=os.getcwd())
project.generate_files()

12 changes: 12 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash

set -x

pip3 install --user -r requirements.txt

ln -s ${PWD}/basys2_prj.py ~/.local/bin/basys2_prj
ln -s ${PWD}/basys2_prog.py ~/.local/bin/basys2_prog
ln -s ${PWD}/basys2_epp.py ~/.local/bin/basys2_epp
ln -s ${PWD}/list.py ~/.local/bin/basys2_list

echo 'Remember about exporting ${HOME}/.local/bin in PATH'
2 changes: 2 additions & 0 deletions project/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .project import Project
from .pins import Pins
30 changes: 30 additions & 0 deletions project/contents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

MAKEFILE = """

BUILD=out
TOP={top:}
TAR=$(BUILD)/$(TOP)_par.bit

all: synth

FILES={deps:}

prog: $(TAR)
basys2_prog $(TAR)

synth: $(BUILD) $(FILES)
cd $(BUILD) && xst -ifn ../$(TOP).xst && ngdbuild $(TOP) -uc ../$(TOP).ucf && map $(TOP) && par -w $(TOP).ncd $(TOP)_par.ncd && bitgen -w $(TOP)_par.ncd -g StartupClk:JTAGCLK

$(BUILD):
mkdir -p $@

clean:
rm -rf $(BUILD)


.PHONY: clean synth

"""

XST_CMD = "run -ifn ../{prj:} -p xc3s100e-4-cp132 -top {top:} -ofn {top:}"

109 changes: 109 additions & 0 deletions project/pins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# This file is a general .ucf for Basys2 rev C board
# To use it in a project:
# - remove or comment the lines corresponding to unused pins
# - rename the used signals according to the project

import logging


class Pins:
def __init__(self):
self.log = logging.getLogger(name='Pins')
loc = {}
extra = {}

#=== CLOCKS
loc['mclk'] = ["B8"]
extra['mclk'] = {"CLOCK_DEDICATED_ROUTE": "FALSE"}

loc['uclk'] = ["M6"]
extra['uclk'] = {"CLOCK_DEDICATED_ROUTE": "FALSE"}

#=== SWITCHES
loc['sw'] = ["P11", "L3", "K3", "B4", "G3", "F3", "E2", "N3"]

#=== BUTTONS
loc['btn'] = ["G12", "C11", "M4", "A7"]

#=== LEDS
loc['Led'] = ["M5", "M11", "P7", "P6", "N5", "N4", "P4", "G1"]

#=== 7-segment display
loc['seg'] = (["M12", "L13", "P12", "N11", "N14", "H12", "L14"])[::-1]
loc['an'] = ["F12", "J12", "M13", "K14"]
loc['dp'] = ["N13"]

#=== EPP ext pins
loc['EppAstb'] = ["F2"]
loc['EppDstb'] = ["F1"]
loc['EppWR'] = ["C2"]
loc['EppWait'] = ["D2"]
loc['EppDB'] = ["N2", "M2", "M1", "L1", "L2", "H2", "H1", "H3"]

#=== PS2
loc['PS2C'] = ['B1']
loc['PS2D'] = ['C3']
extra['PS2C'] = {'DRIVE': 2, 'PULLUP': None}
extra['PS2D'] = {'DRIVE': 2, 'PULLUP': None}

#=== VGA
loc['HSYNC'] = ['J14']
loc['VSYNC'] = ['K13']
loc['OutRed'] = ['C14', 'D13', 'F13']
loc['OutGreen'] = ['F14', 'G13', 'G14']
loc['OutBlue'] = ['H13', 'J13']
extra['HSYNC'] = {'DRIVE': 2, 'PULLUP': None}
extra['VSYNC'] = {'DRIVE': 2, 'PULLUP': None}
extra['OutRed'] = {'DRIVE': 2, 'PULLUP': None}
extra['OutGreen'] = {'DRIVE': 2, 'PULLUP': None}
extra['OutBlue'] = {'DRIVE': 2, 'PULLUP': None}


self.extra = extra
self.loc = loc

def get_ucf(self, ios):

def get_extras(extras, name):
l = []
if name in extras:
for k, v in extras[name].items():
if v is None:
_s = k
else:
_s = '%s = %s' % (k, v)
l.append(_s)
return " | ".join(l) if l else None

self.log.info("Generating constraint file...")

ucf_content = ""
for n, r in ios:
if n not in self.loc:
self.log.error("`%s` external pin not found! Use --pins to see available external pins." % n)
exit(1)

s = ""
extra_opts = get_extras(self.extra, n)
if r is None:
l = self.loc[n][0]
s += 'NET "%s" LOC = "%s";\n' % (n, l)
if extra_opts is not None:
s += 'NET "%s" ' % n + extra_opts + ";\n"
else:
a, b = r
for idx, l in enumerate(self.loc[n][a:b+1]):
s += 'NET "%s<%d>" LOC = "%s";\n' % (n, idx, l)
if extra_opts is not None:
s += 'NET "%s<%d>" ' % (n, idx) + extra_opts + ";\n"
self.log.info('Wire `%s` entries: \n' % n + s)
ucf_content += s
return ucf_content

def print_all(self):
l = [(k,len(v)) for k, v in self.loc.items()]
print("{:^12s} {:^6s}".format("NET NAME", "WIDTH"))
for k, w in l:
print("{:>12s} {:^6d}".format(k, w))


73 changes: 73 additions & 0 deletions project/project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import os
import glob
import logging
import re

from .pins import Pins
from .contents import MAKEFILE, XST_CMD


class Project:

def __init__(self, root_dir):
self.log = logging.getLogger(name='Builder')

self.root_dir = root_dir
self.log.info("Root dir: {:s}".format(root_dir))

self.top_module = os.path.basename(root_dir)
self.log.info("Top module: {:s}".format(self.top_module))

self.files = glob.glob(root_dir + '/*.v')
self.log.info("Verilog files found: ")
for fn in self.files:
self.log.info(" - " + fn)

def _ext_io(self):
with open(self.top_module + '.v', 'r') as f:
content = f.read()
res = re.match(".*module (?P<name>\w+)\s*\((?P<args>[\[\]\s\w_,:]+)\);.*", content, re.M | re.S).groupdict()
if self.top_module != res['name']:
self.log.error("Wrong top module name!")
exit()

self.log.info('Found external IOs in `%s`:' % self.top_module)
ios = []
for arg in res['args'].split(','):
res = re.match("(?P<T>input|output|inout)\s+(wire)?\s*\[(?P<a>.+):(?P<b>.+)\]\s*(?P<name>\w+)", arg.strip())
if res is not None:
d = res.groupdict()
a, b = int(d['a']), int(d['b'])
x, y = min(a,b), max(a, b)
ios += [(d['name'], (x, y))]
self.log.info(' - {T:} [{a:}:{b:}] {name:}'.format(**d))
else:
d = re.match("(?P<T>input|output|inout)\s+(wire)?\s*(?P<name>\w+)", arg.strip()).groupdict()
ios += [(d['name'], None)]
self.log.info(' - {T:} {name:}'.format(**d))
return ios


def generate_files(self):
ext_ios = self._ext_io()

# Generate UCF constraints file
with open(self.top_module + ".ucf", "w") as f:
ucf_str = Pins().get_ucf(ext_ios)
f.write(ucf_str)

# Generate *.xst file
with open(self.top_module + ".xst", "w") as f:
f.write(XST_CMD.format(prj=self.top_module + ".prj", top=self.top_module))

# Generate *.prj file
with open(self.top_module + ".prj", "w") as f:
for fpath in self.files:
fn = os.path.basename(fpath)
f.write('verilog work %s\n' % fn)

deps = [os.path.basename(fpath) for fpath in self.files]
deps = " ".join(deps)
with open("Makefile", "w") as f:
f.write(MAKEFILE.format(top=self.top_module, deps=deps))