#!/usr/bin/env python

import struct
import fcntl
import os
import sys
import time

########################################################################
"""
Object for interfacing with LPT port on FreeBSD.
"""

class HwPort_BSD_LPT(object):
    PPIGSTATUS = 0x4001500b
    PPISDATA = 0x80015010
    PPISCTRL = 0x80015012

    def __init__(self):
        self.f = open("/dev/ppi0", "rw")
    def sctrl(self, v):
        """ Write byte v to the CONTROL byte """
        fcntl.ioctl(self.f, self.PPISCTRL, struct.pack("I", v))
    def sdata(self, v):
        """ Write byte v to the DATA byte """
        fcntl.ioctl(self.f, self.PPISDATA, struct.pack("I", v ^ 3))
    def status(self):
        """ Read STATUS byte """
        (s,) = struct.unpack("I", (fcntl.ioctl(self.f, self.PPIGSTATUS, struct.pack("I", 0))))
        return s

########################################################################
"""
JTAG Class
Subclassers provide 'operate' and 'sample' methods
"""

class Jtag(object):
    verbose = False

    st = 0
    states = [
        ( "Test-Logic-Reset", 1,  0,  ), #  0
        ( "Run-Test/Idle",    1,  2,  ), #  1
        ( "Select-DR-Scan",   3,  9,  ), #  2
        ( "Capture-DR",       4,  5,  ), #  3
        ( "Shift-DR",         4,  5,  ), #  4
        ( "Exit1-DR",         6,  8,  ), #  5
        ( "Pause-DR",         6,  7,  ), #  6
        ( "Exit2-DR",         4,  8,  ), #  7
        ( "Update-DR",        1,  2,  ), #  8
        ( "Select-IR-Scan",   10, 0,  ), #  9
        ( "Capture-IR",       11, 12, ), #  10
        ( "Shift-IR",         11, 12, ), #  11
        ( "Exit1-IR",         13, 15, ), #  12
        ( "Pause-IR",         13, 14, ), #  13
        ( "Exit2-IR",         11, 15, ), #  14
        ( "Update-IR",        1,  2,  ), #  15
    ]
    def debug_tms(self, tms):
        self.st = self.states[self.st][1 + tms]
    def state(self):
        return self.states[self.st][0]
    def __repr__(self):
        return "<JTAG object state=%s>" % self.state()

    def debug(self, tms, tdi):
        if self.verbose:
            print "%18s TDI=%d TMS=%d" % (self.state(), tdi, tms)

    def go_state(self, tms):
        self.debug_tms(tms)
        self.debug(tms = tms, tdi = 0)
        self.operate(tck = 0, tms = tms, tdi = 0)
        self.operate(tck = 1, tms = tms, tdi = 0)

    def go_states(self, *ss):
        for s in ss:
            self.go_state(s)

    def do_bit(self, tms, tdi):
        self.debug_tms(tms)
        sample = self.sample()
        self.debug(tms = tms, tdi = tdi)
        self.operate(tck = 0, tms = tms, tdi = tdi)
        self.operate(tck = 1, tms = tms, tdi = tdi)
        return sample

    def do_nbit(self, n, data):
        r = 0
        for i in range(n):
            assert(self.state().startswith("Shift-"))
            mask = 1 << i
            is_lastbit = (i == (n - 1))
            tdo = self.do_bit(tms = is_lastbit, tdi = (data & mask) != 0)
            if tdo:
                r |= mask
        assert(self.state().startswith("Exit1-"))
        return r

    def do_nbit_cycle(self, n, data):
        r = 0
        for i in range(n):
            mask = 1 << i
            tdo = self.do_bit(tms = 0, tdi = (data & mask) != 0)
            if tdo:
                r |= mask
        return r

    def goTLR(self):
        for i in range(10):
            self.go_state(1)

    def goSelectDRScan(self):
        for i in range(10):
            self.go_state(1)
        self.go_state(0)
        self.go_state(1)
        assert(jt.state() == "Select-DR-Scan")

    def initTAP(self):
        for i in range(10):
            self.go_state(1)
        self.go_state(0)

        self.go_state(1)
        self.go_state(1)
        self.go_state(0)
        self.go_state(0)

########################################################################
"""
CPLDJtag is a kind of Jtag that talks over a hardware port, e.g.
HwPort_BSD_LPT.
"""

class CPLDJtag(Jtag):
    def __init__(self, hwport):
        self.hwport = hwport

    def operate(self, tck, tms, tdi):
        mask = 0
        if tck:
            mask |= 0x02
        if tms:
            mask |= 0x04
        if not tdi:
            mask |= 0x08
        self.hwport.sctrl(mask)

    def sample(self):
        time.sleep(.000002)
        status = self.hwport.status()
        tdo = 1 ^ (1 & (status >> 7))
        return tdo

########################################################################
"""
XC9572XL is a kind of CPLDJtag that has methods:
getcode() for fetching 32-bit codes, both IDCODE and USERCODE
load_svf() for loading an SVF into the CPLD via JTAG

see Xilinx appnotes:
XAPP503: "SVF and XSVF File Formats for Xilinx Devices"
XAPP067: "Using Serial Vector Format Files to Program XC9500/XL/XV
          Devices In-System"

"""

class XC9572XL(CPLDJtag):
    def getcode(self, opcode):
        self.initTAP()
        assert(self.state() == "Shift-IR")
        self.do_nbit(8, opcode);          # EXIT1_IR
        self.go_state(1)                  # UPDATE_IR
        self.go_state(1)                  # SELECT_DR_SCAN
        self.go_state(0)                  # CAPTURE_DR
        self.go_state(0)                  # SHIFT_DR
        return self.do_nbit(32, 0)

    def load_svf(self, svf_filename):
        print "Loading CPLD with", svf_filename
        def unparen(v):
            return long(v[1:-1], 16)

        def sir_sdr(fe, vals):
            bitcount = int(fe[1])
            vals.update(dict(zip(fe[2::2],
                                 [unparen(v) for v in fe[3::2]])))
            tdo = self.do_nbit(bitcount, vals['TDI'])
            if 'TDO' in vals:
                if (tdo & vals['MASK']) != (vals['MASK'] & vals['TDO']):
                    raise Exception, "MISMATCH actual=%x expected=%x" % (tdo,vals['TDO'])
                del vals['TDO']

        sir = {}
        sdr = {}
        self.goTLR()
        self.go_states(0)
        svf = open(svf_filename, "r")
        for l in svf:
            print l.strip()
            assert(self.state() == "Run-Test/Idle")
            fe = l.split()
            if l.startswith("SIR"):
                self.go_states(0, 1, 1, 0, 0)
                assert(self.state() == "Shift-IR")
                sir_sdr(fe, sir)
                self.go_states(1, 0)
            if l.startswith("SDR"):
                self.go_states(0, 1, 0, 0)
                assert(self.state() == "Shift-DR")
                sir_sdr(fe, sdr)
                self.go_states(1, 0)
            if l.startswith('RUNTEST'):
                time.sleep(2 * int(fe[1]) / 1e6)

def reverse(x):
    """ Reverse bits in byte x """
    r = 0
    for i in range(8):
        r <<= 1
        r |= (x & 1)
        x >>= 1
    return r

########################################################################

def main(bitfile):
    hwport = HwPort_BSD_LPT()
    jt = XC9572XL(hwport)

    if (0xfffffff & jt.getcode(0xfe)) != 0x09604093:
        raise Exception, "CPLD ID not recognized"

    DWNLDPAR_USERID = 0x3c343e21 # the CPLD program dwnldpar.svf

    if jt.getcode(0xfd) != DWNLDPAR_USERID:
        jt.load_svf("dwnldpar.svf")

    if jt.getcode(0xfd) != DWNLDPAR_USERID:
        raise Exception, "CPLD load failed"

    print "Talking to XSA Board OK!"

    bit = open(bitfile, "r")

    def getH(fi):
        return struct.unpack(">H", bit.read(2))[0]
    def getI(fi):
        return struct.unpack(">I", bit.read(4))[0]

    bit.seek(getH(bit), os.SEEK_CUR)
    assert getH(bit) == 1

    # Search for the data section in the .bit file...
    while True:
        ty = ord(bit.read(1))
        if ty == 0x65:
            break
        length = getH(bit)
        bit.seek(length, os.SEEK_CUR)
    fieldLength = getI(bit)
    print "bitfile %s loaded, %d bytes" % (bitfile, fieldLength)

    mask = 0
    hwport.sdata(mask)
    mask = 0x80
    hwport.sdata(mask)
    time.sleep(0.030)

    def reportDONE():
        print "DONE = %d" % (1 ^ (1 & (hwport.status() >> 6)))

    def sendbyte(x):
        x = reverse(x)
        mask = 0x80 | ((x >> 4) << 2)
        hwport.sdata(mask)
        mask |= 1
        hwport.sdata(mask)
        mask = 0x80 | (((x & 0xf) << 2) | 1)
        hwport.sdata(mask)
        mask -= 1
        hwport.sdata(mask)

    reportDONE()
    for i in range(fieldLength):
        if (i % 10000) == 0:
            print "%d / %d" % (i, fieldLength)
        sendbyte(ord(bit.read(1)))
    for i in range(8):
        mask = 0x80 | 0
        hwport.sdata(mask)
        mask = 0x80 | 1
        hwport.sdata(mask)
    mask = 0x80
    hwport.sdata(mask)
    reportDONE()
    time.sleep(0.010)

if __name__ == "__main__":
    print "xsload, see http://excamera.com/sphinx/fpga-xess-python.html for details"
    if len(sys.argv) != 2:
        print "usage: xsload <bitfile>"
        sys.exit(1)
    main(sys.argv[1])
