#!/usr/bin/env python
# encoding: utf-8
# waf example, builds a Xilinx FPGA bitstream

__copyright__  = '(c) Jérôme Carretero <cJ-waf@zougloub.eu> 2012'

"""
This script builds an FPGA bitstream in an automated fashion.
The Xilinx ISE IDE does the same thing, but needs mouse interaction.

Notes:

- this is quite sad, but the Xilinx toolchain tools want to operate
  in the source folder, so top==out

- Xilinx toolchain tools generate file with timestamps,
  so an unsignificant change can still trigger domino cascade
  of compilations.

- a "xilinx" wrapper is used; this file performs set up of the
  PATH for Xilinx tools (not done yet by the wscript)

TODO:

- make a tool
- remove hard-coded .xst / .ut data (ISE generates that from the .xise)
- CPLD toolchain (only works for FPGA)

"""

top = out = "." # mandatory

import os
import shutil
import waflib
from lxml import etree

def options(opt):
	pass

def configure(cfg):
	pass

def build(bld):

	if not os.path.exists("xst/projnav.tmp"):
		os.makedirs("xst/projnav.tmp")
	
	nsmap={"pn": "http://www.xilinx.com/XMLSchema"}

	xise = "waf_demo.xise"
	fn = "waf_demo"
	xml = etree.parse(xise)

	def get(txt):
		try: return xml.xpath('//pn:property[@pn:name = "%s"]/@pn:value' % txt, namespaces=nsmap)[0]
		except: pass

	device = get("Device") # or "xc3s1500"
	package = get("Package") # or "fg456"
	speed = get("Speed Grade") # or "-4"

	# Set .prj file contents and collect HDL sources
	hdl = []
	prj = []
	for x in xml.xpath('//pn:files/pn:file[@pn:type = "FILE_VHDL"]/@pn:name', namespaces=nsmap):
		prj.append('vhdl work "%s"' % x)
		hdl.append(x)
	for x in xml.xpath('//pn:files/pn:file[@pn:type = "FILE_VERILOG"]/@pn:name', namespaces=nsmap):
		prj.append('verilog work "%s"' % x)
		hdl.append(x)
	
	ucf = xml.xpath('//pn:files/pn:file[@pn:type = "FILE_UCF"]/@pn:name', namespaces=nsmap)[0] or "src/pci_7seg.ucf"

	def make_prj(self):
		self.outputs[0].write("\n".join(prj))
	
	def make_xst(self):
		self.outputs[0].write("""
set -tmpdir "xst/projnav.tmp"
set -xsthdpdir "xst"
run
-ifn %(fn)s.prj
-ifmt mixed
-ofn %(fn)s
-ofmt NGC
-p %(device)s%(speed)s-%(package)s
-top %(fn)s
-opt_mode Speed
-opt_level 1
-iuc NO
-keep_hierarchy No
-netlist_hierarchy As_Optimized
-rtlview Yes
-glob_opt AllClockNets
-read_cores YES
-write_timing_constraints NO
-cross_clock_analysis NO
-hierarchy_separator /
-bus_delimiter <>
-case Maintain
-slice_utilization_ratio 100
-bram_utilization_ratio 100
-verilog2001 YES
-fsm_extract YES -fsm_encoding Auto
-safe_implementation No
-fsm_style LUT
-ram_extract Yes
-ram_style Auto
-rom_extract Yes
-mux_style Auto
-decoder_extract YES
-priority_extract Yes
-shreg_extract YES
-shift_extract YES
-xor_collapse YES
-rom_style Auto
-auto_bram_packing NO
-mux_extract Yes
-resource_sharing YES
-async_to_sync NO
-mult_style Auto
-iobuf YES
-max_fanout 500
-bufg 8
-register_duplication YES
-register_balancing No
-slice_packing YES
-optimize_primitives NO
-use_clock_enable Yes
-use_sync_set Yes
-use_sync_reset Yes
-iob Auto
-equivalent_register_removal YES
-slice_utilization_ratio_maxmargin 5
""" % locals())



	def make_ut(self):
		self.outputs[0].write("""
-w
-g DebugBitstream:No
-g Binary:no
-g CRC:Enable
-g ConfigRate:6
-g CclkPin:PullUp
-g M0Pin:PullUp
-g M1Pin:PullUp
-g M2Pin:PullUp
-g ProgPin:PullUp
-g DonePin:PullUp
-g HswapenPin:PullUp
-g TckPin:PullUp
-g TdiPin:PullUp
-g TdoPin:PullUp
-g TmsPin:PullUp
-g UnusedPin:PullDown
-g UserID:0xFFFFFFFF
-g DCMShutdown:Disable
-g DCIUpdateMode:AsRequired
-g StartUpClk:CClk
-g DONE_cycle:4
-g GTS_cycle:5
-g GWE_cycle:6
-g LCK_cycle:NoWait
-g Match_cycle:Auto
-g Security:None
-g DonePipe:No
-g DriveDone:No""")


	bld(
	 name='prj',
	 target="%s.prj" % fn,
	 rule=make_prj,
	 source=[xise],
	)

	bld(
	 name='xst',
	 target='%s.xst' % fn,
	 rule=make_xst,
	 source=[xise],
	)
	bld(
	 name='ut',
	 target='%s.ut' % fn,
	 rule=make_ut,
	 source=[xise],
	)

	bld(
	 name='synth',
	 target=['%s%s' % (fn, ext) for ext in ('.syr', '.ngc', '.ngr', '.lso', '_xst.xrpt') ],
	 rule='xilinx xst -intstyle ise -ifn ${SRC[0].abspath()} -ofn ${TGT[0].abspath()}; true',
	 source=['%s.xst' % fn, '%s.prj' % fn] + hdl,
	)

	bld(
	 name='ngdbuild',
	 target=['%s%s' % (fn, ext) for ext in ('.ngd', '_ngdbuild.xrpt') ],
	 rule='xilinx ngdbuild -intstyle ise -dd _ngo -nt timestamp -uc ${SRC[1].abspath()} -p %(device)s-%(package)s%(speed)s ${SRC[0].bldpath()} ${TGT[0].bldpath()}' % locals(),
	 source=['%s.ngc' % fn, ucf],
	)

	bld(
	 name='map',
	 target=['%s%s' % (fn, ext) for ext in ('_map.ncd', '.pcf', '_map.map', '_map.mrp', '_map.ngm', '_map.xrpt', '.bld') ],
	 rule='xilinx map -intstyle ise -p %(device)s-%(package)s%(speed)s -cm area -ir off -pr b -c 100 -o ${TGT[0].bldpath()} ${SRC[0].bldpath()} ${TGT[1].bldpath()}' % locals(),
	 source=['%s.ngd' % fn],
	)

	bld(
	 name='par',
	 target=['%s%s' % (fn, ext) for ext in ('.ncd', '.pad', '.par', '.ptwx', '.unroutes', '.xpi', '_pad.csv', '_pad.txt', '_par.xrpt') ],
	 rule='xilinx par -w -intstyle ise -ol high -t 1 ${SRC[0].bldpath()} ${TGT[0].bldpath()} ${SRC[1].bldpath()}',
	 source=['%s_map.ncd' % fn, '%s.pcf' % fn],
	)

	bld(
	 name='trce',
	 target=['%s%s' % (fn, ext) for ext in ('.twx', '.twr') ],
	 rule='xilinx trce -intstyle ise -e 3 -s 4 -n 3 -xml ${TGT[0].bldpath()} ${SRC[0].bldpath()} -o ${TGT[1].bldpath()} ${SRC[1].bldpath()}; true',
	 source=['%s_map.ncd' % fn, '%s.pcf' % fn],
	)

	bld(
	 name='bitgen',
	 target=['%s%s' % (fn, ext) for ext in ('.bit', '.bgn', '.drc', '_bitgen.xwbt', '_summary.xml', '_usage.xml') ],
	 rule='xilinx bitgen -intstyle ise -f ${SRC[1].bldpath()} ${SRC[0].bldpath()} ${TGT[0].bldpath()}',
	 source=['%s.ncd' % fn, '%s.ut' % fn],
	)

	if bld.cmd == 'clean':
		for tgen in bld.get_all_task_gen():
			for tgt in waflib.Utils.to_list(tgen.target):
				if os.path.exists(tgt):
					os.remove(tgt)
		for x in (
		 'usage_statistics_webtalk.html',
		 'webtalk_pn.xml',
		 'webtalk.log',
		 ):
			if os.path.exists(x):
				os.remove(x)

		for x in (
		 '_ngo',
		 '_xmsgs',
		 'iseconfig',
		 'xlnx_auto_0_xdb',
		 'xst',
		 ):
			try:
				shutil.rmtree(x)
			except:
				pass

def distclean(ctx):
	import os, shutil
	from waflib import Context

	for fn in os.listdir('.'):
		if fn.startswith(('.conf_check_', ".lock-w")) \
		 or fn in (Context.DBFILE, 'config.log') \
		 or fn == 'c4che':
			if os.path.isdir(fn):
				shutil.rmtree(fn)
			else:
				os.remove(fn)


