--- /dev/null
+# Xilinx XADC support for 7 Series FPGAs
+#
+# The 7 Series FPGAs contain an on-chip 12 bit ADC that can probe die
+# temperature, internal power supply rail voltages as well as external
+# voltages. The XADC is available both from fabric as well as through the
+# JTAG TAP.
+#
+# This code implements access throught the JTAG TAP.
+#
+# https://www.xilinx.com/support/documentation/user_guides/ug480_7Series_XADC.pdf
+
+# build a 32 bit DRP command for the XADC DR
+proc xadc_cmd {cmd addr data} {
+ array set cmds {
+ NOP 0x00
+ READ 0x01
+ WRITE 0x02
+ }
+ return [expr ($cmds($cmd) << 26) | ($addr << 16) | ($data << 0)]
+}
+
+# XADC register addresses
+# Some addresses (status registers 0-3) have special function when written to.
+proc XADC {key} {
+ array set addrs {
+ TEMP 0x00
+ LOCK 0x00
+ VCCINT 0x01
+ VCCAUX 0x02
+ VAUXEN 0x02
+ VPVN 0x03
+ RESET 0x03
+ VREFP 0x04
+ VREFN 0x05
+ VCCBRAM 0x06
+ SUPAOFFS 0x08
+ ADCAOFFS 0x09
+ ADCAGAIN 0x0a
+ VCCPINT 0x0d
+ VCCPAUX 0x0e
+ VCCODDR 0x0f
+ VAUX0 0x10
+ VAUX1 0x11
+ VAUX2 0x12
+ VAUX3 0x13
+ VAUX4 0x14
+ VAUX5 0x15
+ VAUX6 0x16
+ VAUX7 0x17
+ VAUX8 0x18
+ VAUX9 0x19
+ VAUX10 0x1a
+ VAUX11 0x1b
+ VAUX12 0x1c
+ VAUX13 0x1d
+ VAUX14 0x1e
+ VAUX15 0x1f
+ SUPBOFFS 0x30
+ ADCBOFFS 0x31
+ ADCBGAIN 0x32
+ FLAG 0x3f
+ CFG0 0x40
+ CFG1 0x41
+ CFG2 0x42
+ SEQ0 0x48
+ SEQ1 0x49
+ SEQ2 0x4a
+ SEQ3 0x4b
+ SEQ4 0x4c
+ SEQ5 0x4d
+ SEQ6 0x4e
+ SEQ7 0x4f
+ ALARM0 0x50
+ ALARM1 0x51
+ ALARM2 0x52
+ ALARM3 0x53
+ ALARM4 0x54
+ ALARM5 0x55
+ ALARM6 0x56
+ ALARM7 0x57
+ ALARM8 0x58
+ ALARM9 0x59
+ ALARM10 0x5a
+ ALARM11 0x5b
+ ALARM12 0x5c
+ ALARM13 0x5d
+ ALARM14 0x5e
+ ALARM15 0x5f
+ }
+ return $addrs($key)
+}
+
+# Select the XADC DR
+proc xadc_select {tap} {
+ set XADC_IR 0x37
+ irscan $tap $XADC_IR
+ runtest 10
+}
+
+# XADC transfer
+proc xadc_xfer {tap cmd addr data} {
+ set ret [drscan $tap 32 [xadc_cmd $cmd $addr $data]]
+ runtest 10
+ return [expr 0x$ret]
+}
+
+# XADC register write
+proc xadc_write {tap addr data} {
+ xadc_xfer $tap WRITE $addr $data
+}
+
+# XADC register read, non-pipelined
+proc xadc_read {tap addr} {
+ xadc_xfer $tap READ $addr 0
+ return [xadc_xfer $tap NOP 0 0]
+}
+
+# convert 16 bit register code from ADC measurement on
+# external voltages (VAUX) to Volt
+proc xadc_volt {code} {
+ return [expr $code * 1./(1 << 16)]
+}
+
+# convert 16 bit temperature measurement to Celsius
+proc xadc_temp {code} {
+ return [expr $code * 503.975/(1 << 16) - 273.15]
+}
+
+# convert 16 bit suppply voltage measurement to Volt
+proc xadc_sup {code} {
+ return [expr $code * 3./(1 << 16)]
+}
+
+# perform a single channel measurement using default settings
+proc xadc_single {tap ch} {
+ set cfg0 [xadc_read $tap [XADC CFG0]]
+ set cfg1 [xadc_read $tap [XADC CFG1]]
+ # set channel
+ xadc_write $tap [XADC CFG0] $cfg0
+ # single channel, disable the sequencer
+ xadc_write $tap [XADC CFG1] 0x3000
+ # leave some time for the conversion
+ runtest 100
+ set ret [xadc_read $tap [XADC $ch]]
+ # restore CFG0/1
+ xadc_write $tap [XADC CFG0] $cfg0
+ xadc_write $tap [XADC CFG1] $cfg1
+ return $ret
+}
+
+# measure all internal voltages
+proc xadc_report {tap} {
+ xadc_select $tap
+ echo "TEMP [format %.2f [xadc_temp [xadc_single $tap TEMP]]] C"
+ foreach ch [list VCCINT VCCAUX VCCBRAM VPVN VREFP VREFN \
+ VCCPINT VCCPAUX VCCODDR] {
+ echo "$ch [format %.3f [xadc_sup [xadc_single $tap $ch]]] V"
+ }
+}