# SPDX-License-Identifier: BSD-2-Clause

from textwrap                           import dedent
from typing                             import Literal

from torii.build                        import Attrs, Clock, Connector, DiffPairs, Pins, PinsN, Resource, Subsignal
from torii.build.run                    import BuildPlan, BuildProducts
from torii.hdl.ir                       import Fragment
from torii.platform.resources.display   import VGAResource
from torii.platform.resources.interface import I2CResource, SPIResource, UARTResource, ULPIResource
from torii.platform.resources.memory    import SDCardResources
from torii.platform.resources.user      import ButtonResources, LEDResources, SwitchResources
from torii.platform.vendor.xilinx       import XilinxPlatform

__all__ = (
	'Genesys2Platform',
)

class Genesys2Platform(XilinxPlatform):
	'''Platform file for Diglient Genesys2 Kitex-7 board.
	https://digilent.com/reference/programmable-logic/genesys-2/start'''

	device  = 'xc7k325t'
	package = 'ffg900'
	speed   = '2'

	pretty_name = 'Gensys 2'
	description = 'Digilent Gensys 2 Xilinx Kintex7-325T Development Board'

	def __init__(self, JP6: Literal['1V2', '1V8', '2V5', '3V3'] = '2V5') -> None:
		super().__init__()

		if JP6 not in ('1V2', '1V8', '2V5', '3V3'):
			raise ValueError(f'JP6 must be one of \'1V2\', \'1V8\', \'2V5\', \'3V3\', not {JP6!r}')

		self._JP6 = JP6

	def bank15_16_17_iostandard(self) -> str:
		return 'LVCMOS' + self._JP6.replace('V', '')

	default_rst = 'rst'
	default_clk = 'clk'

	resources = [
		Resource('rst', 0, PinsN('R19', dir = 'i'), Attrs(IOSTANDARD = 'LVCMOS33')),
		Resource(
			'clk', 0, DiffPairs(p = 'AD12 ', n = 'AD11', dir = 'i'), Clock(200e6),
			Attrs(IOSTANDARD = 'LVDS')
		),
		*ButtonResources(pins = {
				0: 'E18',
				1: 'B19',
				2: 'C19',
				3: 'M19',
				4: 'M20'
			}, attrs = Attrs(IOSTANDARD = bank15_16_17_iostandard)
		),
		*SwitchResources(
			pins = 'G19 G25 H24 K19 N19 P19',
			attrs = Attrs(IOSTANDARD = bank15_16_17_iostandard)
		),
		*SwitchResources(pins = {
				6: 'P26',
				7: 'P27'
			}, attrs = Attrs(IOSTANDARD = 'LVCMOS33')
		),
		*LEDResources(
			pins = 'T28 V19 U30 U29 V20 V26 W24 W23',
			attrs = Attrs(IOSTANDARD = 'LVCMOS33')
		),
		Resource(
			'fan', 0,
			Subsignal('pwm', Pins('W19', dir = 'o')),
			Subsignal('tach', Pins('V21', dir = 'i')),
			Attrs(IOSTANDARD = 'LVCMOS33')
		),
		UARTResource(
			0,
			rx = 'Y20', tx = 'Y23',
			attrs = Attrs(IOSTANDARD = 'LVCMOS33')
		),
		I2CResource(
			0,
			scl = 'AE30', sda = 'AF30',
			attrs = Attrs(IOSTANDARD = 'LVCMOS33')
		),
		# TODO(aki): Replace with `DDR3Resource` when more granular attrs are possible
		Resource(
			'ddr3', 0,
			Subsignal('rst', PinsN('AG5', dir = 'o'), Attrs(IOSTANDARD = 'SSTL15')),
			Subsignal(
				'clk',
				DiffPairs(p = 'AB9', n = 'AC9', dir = 'o'), Attrs(IOSTANDARD = 'DIFF_SSTL15_DCI')
			),
			Subsignal('clk_en', Pins('AJ9', dir = 'o')),
			Subsignal('cs', PinsN('AH12', dir = 'o')),
			Subsignal('we', PinsN('AG13', dir = 'o')),
			Subsignal('ras', PinsN('AE11', dir = 'o')),
			Subsignal('cas', PinsN('AF11', dir = 'o')),
			Subsignal('a', Pins(
				'AC12 AE8 AD8 AC10 AD9 AA13 AA10 AA11 '
				'Y10 Y11 AB8 AA8 AB12 AA12 AH9 AG9', dir = 'o')
			),
			Subsignal('ba', Pins('AE9 AB10 AC11', dir = 'o')),
			Subsignal(
				'dqs',
				DiffPairs(p = 'AD2 AG4 AG2 AH7', n = 'AD1 AG3 AH1 AJ7', dir = 'io'),
				Attrs(IOSTANDARD = 'DIFF_SSTL15_DCI', ODT = 'RTT_40')
			),
			Subsignal(
				'dq',
				Pins(
					'AD3 AC2 AC1 AC5 AC4 AD6 AE6 AC7 '
					'AF2 AE1 AF1 AE4 AE3 AE5 AF5 AF6 '
					'AJ4 AH6 AH5 AH2 AJ2 AJ1 AK1 AJ3 '
					'AF7 AG7 AJ6 AK6 AJ8 AK8 AK5 AK4',
					dir = 'io'
				),
				Attrs(ODT = 'RTT_40')
			),
			Subsignal('dm', Pins('AD4 AF3 AH4 AF8', dir = 'o')),
			Subsignal('odt', Pins('AK9', dir = 'o')),
			Attrs(IOSTANDARD = 'SSTL15_DCI', SLEW = 'FAST', OUTPUT_IMPEDANCE = 'RDRV_40_40')
		),
		Resource(
			'audio_i2c', 0,  # ADAU1761 I2C
			Subsignal('scl', Pins('AE19', dir = 'io')),
			Subsignal('sda', Pins('AF18', dir = 'io')),
			Subsignal('adr', Pins('AD19 AG19', dir = 'o')),
			Attrs(IOSTANDARD = 'LVCMOS18')
		),
		Resource(
			'audio_i2s', 0,  # ADAU1761 ADC, I2S
			Subsignal('clk', Pins('AG18', dir = 'o')), # BCLK
			Subsignal('sd_adc', Pins('AH19', dir = 'o')), # ADC_SDATA
			Subsignal('sd_dac', Pins('AJ19', dir = 'i')),  # DAC_SDATA
			Subsignal('ws', Pins('AJ18', dir = 'o')), # LRCLK
			Attrs(IOSTANDARD = 'LVCMOS18')
		),
		Resource(
			'audio_clk', 0,  # ADAU1761 MCLK
			Pins('AK19', dir = 'o'), Attrs(IOSTANDARD = 'LVCMOS18')
		),
		SPIResource(
			0,  # OLED, SSD1306, 128 x 32
			cs_n = 'dummy-cs0', clk = 'AF17', copi = 'Y15',
			cipo = 'dummy-cipo0', reset = 'AB17',
			attrs = Attrs(IOSTANDARD = 'LVCMOS18')
		),
		Resource(
			'oled', 0,  # OLED, UG-2832HSWEG04
			Subsignal('dc', Pins('AC17', dir = 'o')),
			Subsignal('vdd_en', PinsN('AG17', dir = 'o')),
			Subsignal('vbat_en', PinsN('AB22', dir = 'o'), Attrs(IOSTANDARD = 'LVCMOS33')),
			Attrs(IOSTANDARD = 'LVCMOS18')
		),
		# TODO(aki): Replace with `HDMIResource` when merged + released in Torii
		Resource(
			'hdmi', 0,  # HDMI TX, connector J4
			Subsignal('scl', Pins('AF27', dir = 'io'), Attrs(IOSTANDARD = 'LVCMOS33')),
			Subsignal('sda', Pins('AF26', dir = 'io'), Attrs(IOSTANDARD = 'LVCMOS33')),
			Subsignal('clk', DiffPairs(p = 'AA20', n = 'AB20', dir = 'o')),
			Subsignal('d', DiffPairs(
				p = 'AC20 AA22 AB24',
				n = 'AC21 AA23 AC25',
				dir = 'o'
			)),
			Attrs(IOSTANDARD = 'TMDS_33')
		),
		# TODO(aki): Replace with `HDMIResource` when merged + released in Torii
		Resource(
			'hdmi', 1,  # HDMI RX, connector J5
			Subsignal('scl', Pins('AJ28', dir = 'io'), Attrs(IOSTANDARD = 'LVCMOS33')),
			Subsignal('sda', Pins('AJ29', dir = 'io'), Attrs(IOSTANDARD = 'LVCMOS33')),
			Subsignal('clk', DiffPairs(p = 'AE28', n = 'AF28', dir = 'i')),
			Subsignal('rx', DiffPairs(
				p = 'AJ26 AG27 AH26',
				n = 'AK26 AG28 AH27',
				dir = 'i'
			)),
			Attrs(IOSTANDARD = 'TMDS_33')
		),
		VGAResource(
			0,
			r = 'AK25 AG25 AH25 AK24 AJ24',
			g = 'AJ23 AJ22 AH22 AK21 AJ21 AK23',
			b = 'AH20 AG20 AF21 AK20 AG22',
			hs = 'AF20', vs = 'AG23', invert_sync = True,
			attrs = Attrs(IOSTANDARD = 'LVCMOS33')
		),
		*SDCardResources(
			0,
			clk = 'R28', cmd = 'R29', dat0 = 'R26', dat1 = 'R30',
			dat2 = 'P29', dat3 = 'T30', cd = 'P28',
			attrs = Attrs(IOSTANDARD = 'LVCMOS33')
		),
		Resource(
			'sd_card_rst', 0,
			Pins('AE24', dir = 'o'), Attrs(IOSTANDARD = 'LVCMOS33')
		),
		ULPIResource(
			0,
			data = 'AE14 AE15 AC15 AC16 AB15 AA15 AD14 AC14',
			rst = 'AB14', clk = 'AD18', dir = 'Y16', stp = 'AA17', nxt = 'AA16',
			clk_dir = 'i', rst_invert = True, attrs = Attrs(IOSTANDARD = 'LVCMOS18')
		),
		Resource(
			'vusb_oc', 0, PinsN('AF16', dir = 'i'), Attrs(IOSTANDARD = 'LVCMOS18')
		),
		# TODO(aki): Replace with `EthernetResource` when more granular attrs are possible
		Resource(
			'eth_rgmii', 0,
			Subsignal('rst', PinsN('AH24', dir = 'o'), Attrs(IOSTANDARD = 'LVCMOS33')),
			Subsignal('mdc', Pins('AF12', dir = 'o')),
			Subsignal('mdio', Pins('AG12', dir = 'io')),
			Subsignal('tx_clk', Pins('AE10', dir = 'o')),
			Subsignal('tx_ctl', Pins('AK14', dir = 'o')),
			Subsignal('tx_data', Pins('AJ12 AK11 AJ11 AK10', dir = 'o')),
			Subsignal('rx_clk', Pins('AG10', dir = 'i')),
			Subsignal('rx_ctl', Pins('AH11', dir = 'i')),
			Subsignal('rx_data', Pins('AJ14 AH14 AK13 AJ13', dir = 'i')),
			Attrs(IOSTANDARD = 'LVCMOS15')
		)
	]

	connectors = [
		Connector(
			'pmod', 0,  # JA
			'U27 U28 T26 T27 - - '
			'T22 T23 T20 T21 - - '
		),
		Connector(
			'pmod', 1,  # JB
			'V29 V30 V25 W26 - - '
			'T25 U25 U22 U23 - - '
		),
		Connector(
			'pmod', 2,  # JC
			'AC26 AJ27 AH30 AK29 - - '
			'AD26 AG30 AK30 AK28 - - '
		),
		Connector(
			'pmod', 3,  # JD
			'V27 Y30 V24 W22 - - '
			'U24 Y26 V22 W21 - - '
		),
		Connector(
			'pmod', 4,  # JXADC
			'J23 K23 L22 L21 - - '
			'J24 K24 L23 K21 - - '
		),
		Connector('hpc', 0, {
			'dp1_m2c_p': 'Y6',
			'dp1_m2c_n': 'Y5',
			'dp2_m2c_p': 'W4',
			'dp2_m2c_n': 'W3',
			'dp3_m2c_p': 'V6',
			'dp3_m2c_n': 'V5',
			'dp1_c2m_p': 'V2',
			'dp1_c2m_n': 'V1',
			'dp2_c2m_p': 'U4',
			'dp2_c2m_n': 'U3',
			'dp3_c2m_p': 'T2',
			'dp3_c2m_n': 'T1',
			'dp0_c2m_p': 'Y2',
			'dp0_c2m_n': 'Y1',
			'dp0_m2c_p': 'AA4',
			'dp0_m2c_n': 'AA3',
			'la06_p': 'D29',
			'la06_n': 'C30',
			'la10_p': 'B27',
			'la10_n': 'A27',
			'la14_p': 'C24',
			'la14_n': 'B24',
			'la18_cc_p': 'D17',
			'la18_cc_n': 'D18',
			'la27_p': 'A20',
			'la27_n': 'A21',
			'ha01_cc_p': 'M28',
			'ha01_cc_n': 'L28',
			'ha05_p': 'J29',
			'ha05_n': 'H29',
			'ha09_p': 'L30',
			'ha09_n': 'K30',
			'ha13_p': 'K26',
			'ha13_n': 'J26',
			'ha16_p': 'M22',
			'ha16_n': 'M23',
			'ha20_p': 'G27',
			'ha20_n': 'F27',
			'clk1_m2c_p': 'E28',
			'clk1_m2c_n': 'D28',
			'la00_cc_p': 'D27',
			'la00_cc_n': 'C27',
			'la03_p': 'E29',
			'la03_n': 'E30',
			'la08_p': 'C29',
			'la08_n': 'B29',
			'la12_p': 'F26',
			'la12_n': 'E26',
			'la16_p': 'E23',
			'la16_n': 'D23',
			'la20_p': 'G22',
			'la20_n': 'F22',
			'la22_p': 'J17',
			'la22_n': 'H17',
			'la25_p': 'D22',
			'la25_n': 'C22',
			'la29_p': 'B18',
			'la29_n': 'A18',
			'la31_p': 'C17',
			'la31_n': 'B17',
			'la33_p': 'D16',
			'la33_n': 'C16',
			'ha03_p': 'N25',
			'ha03_n': 'N26',
			'ha07_p': 'M29',
			'ha07_n': 'M30',
			'ha11_p': 'P23',
			'ha11_n': 'N24',
			'ha14_p': 'N27',
			'ha14_n': 'M27',
			'ha18_p': 'E19',
			'ha18_n': 'D19',
			'ha22_p': 'D21',
			'ha22_n': 'C21',
			'gbtclk1_m2c_p': 'N8',
			'gbtclk1_m2c_n': 'N7',
			'gbtclk0_m2c_p': 'L8',
			'gbtclk0_m2c_n': 'L7',
			'la01_cc_p': 'D26',
			'la01_cc_n': 'C26',
			'la05_p': 'B30',
			'la05_n': 'A30',
			'la09_p': 'B28',
			'la09_n': 'A28',
			'la13_p': 'E24',
			'la13_n': 'D24',
			'la17_cc_p': 'F21',
			'la17_cc_n': 'E21',
			'la23_p': 'G17',
			'la23_n': 'F17',
			'la26_p': 'B22',
			'la26_n': 'A22',
			'pg_m2c': 'AH21',
			'ha00_cc_p': 'K28',
			'ha00_cc_n': 'K29',
			'ha04_p': 'M24',
			'ha04_n': 'M25',
			'ha08_p': 'J27',
			'ha08_n': 'J28',
			'ha12_p': 'L26',
			'ha12_n': 'L27',
			'ha15_p': 'J21',
			'ha15_n': 'J22',
			'ha19_p': 'G29',
			'ha19_n': 'F30',
			'prsnt_m2c_b': 'AA21',
			'clk0_m2c_p': 'F20',
			'clk0_m2c_n': 'E20',
			'la02_p': 'H30',
			'la02_n': 'G30',
			'la04_p': 'H26',
			'la04_n': 'H27',
			'la07_p': 'F25',
			'la07_n': 'E25',
			'la11_p': 'A25',
			'la11_n': 'A26',
			'la15_p': 'B23',
			'la15_n': 'A23',
			'la19_p': 'H21',
			'la19_n': 'H22',
			'la21_p': 'L17',
			'la21_n': 'L18',
			'la24_p': 'H20',
			'la24_n': 'G20',
			'la28_p': 'J19',
			'la28_n': 'H19',
			'la30_p': 'A16',
			'la30_n': 'A17',
			'la32_p': 'K18',
			'la32_n': 'J18',
			'ha02_p': 'P21',
			'ha02_n': 'P22',
			'ha06_p': 'N29',
			'ha06_n': 'N30',
			'ha10_p': 'N21',
			'ha10_n': 'N22',
			'ha17_cc_p': 'C25',
			'ha17_cc_n': 'B25',
			'ha21_p': 'G28',
			'ha21_n': 'F28',
			'ha23_p': 'G18',
			'ha23_n': 'F18'
		})
	]

	def toolchain_prepare(self, fragment: Fragment, name: str, **kwargs) -> BuildPlan:
		overrides = {
			'script_after_read': 'auto_detect_xpm',
			'script_before_bitstream':
			'set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]',
			'add_constraints': dedent('''
			set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]
			set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]
			set_property BITSTREAM.CONFIG.SPI_32BIT_ADDR YES [current_design]
			set_property BITSTREAM.CONFIG.SPI_FALL_EDGE YES [current_design]
			set_property CFGBVS VCCO [current_design]
			set_property CONFIG_VOLTAGE 3.3 [current_design]
			''')
		}
		return super().toolchain_prepare(
			fragment, name, **overrides, **kwargs
		)

	@property
	def file_templates(self) -> dict[str, str]:
		return {
			**super().file_templates,
			'{{name}}-openocd.cfg': dedent(r'''
			source [find interface/ftdi/digilent-hs1.cfg]
			# fix channel
			ftdi_channel 1
			adapter_khz 25000
			transport select jtag
			source [find cpld/xilinx-xc7.cfg]
			source [find cpld/jtagspi.cfg]
			''')
		}

	def toolchain_program(self, products: BuildProducts, name: str) -> None:
		from os         import environ
		from subprocess import check_call

		openocd = environ.get('OPENOCD', 'openocd')
		with products.extract(f'{name}-openocd.cfg', f'{name}.bit') as files:
			(config_filename, bitstream_filename) = files

			check_call([
				openocd,
				'-f', config_filename,
				'-c', f'init; pld load 0 {bitstream_filename}; exit'
			])


if __name__ == '__main__':
	from ...test.blinky import Blinky
	Genesys2Platform().build(Blinky(), do_program = True)
