litex icon indicating copy to clipboard operation
litex copied to clipboard

Feature Request: Add FPGA temperature sensors

Open troibe opened this issue 2 years ago • 37 comments

Some FPGAs feature internal temperature sensors. It would be great to integrate these in LiteX and access them in Linux to read out the current operating temperature of the chip.

troibe avatar Mar 29 '22 15:03 troibe

Here it is: https://github.com/enjoy-digital/litex/blob/master/litex/soc/cores/xadc.py :) You can search for XADC on LiteX-Boards to see an example of integration. On my side I've only used it with custom software, but I think @antmicro developed a Linux driver for it.

enjoy-digital avatar Mar 29 '22 16:03 enjoy-digital

On a somewhat related note, I had played around with the XADC for audio input using a Xilinx reference design. I wish I had the time to extract it as a core for LiteX -- it would make a nice match for the sigma-delta DAC :)

The Xilinx tools wrap the XADC in some generator IP, but I assume the underlying primitive can be directly instantiated. I'm not sure of the state of support from open source tools.

tcal-x avatar Mar 29 '22 20:03 tcal-x

Does anyone have a bare minimum example C program the samples the XADC on a LiteX generated SoC?

JamesTimothyMeech avatar Jul 05 '23 14:07 JamesTimothyMeech

You can find some code here over LitePCIe: https://github.com/enjoy-digital/litepcie/blob/master/litepcie/software/user/litepcie_util.c#L67-L76

enjoy-digital avatar Jul 05 '23 14:07 enjoy-digital

Thanks!

JamesTimothyMeech avatar Jul 05 '23 19:07 JamesTimothyMeech

Does anyone have any clues on how to use LiteX to generate verilog which allows me to read from the differential of VAUXP[14] and VAUXN[14] on the XADC? I have VAUXN[14] grounded in my implementation so the differential of VAUXP[14] and anything grounded would also work.

I can only see CSRs for TEMPERATURE, VCCINT, VCCAUX, and VCCBRAM from this diagram https://docs.xilinx.com/r/en-US/ug480_7Series_XADC/XADC-Overview in my generated csr.h file. Can I get at VAUXP[14] and VAUXN[14] by just incrementing the CSR address I read from to the correct value or do I need different verilog than what LiteX is generating? If this functionality is not yet implemented in LiteX I happy to try and build it myself if we can converge to a plan on how to implement it!

JamesTimothyMeech avatar Jul 10 '23 17:07 JamesTimothyMeech

I've been fighting with Vivado for a week and haven't even been able to get a simple ADC example up and running! My plan was to use what I learnt from the Vivado example to get a design working in LiteX. Any tips about how to do this on LiteX could save me a lot of time! Do I need to do anything on the HDL side or do I just need to find the right address to run XilinxSystemMonitorChannel with?

JamesTimothyMeech avatar Jul 17 '23 10:07 JamesTimothyMeech

According to this: https://docs.xilinx.com/r/en-US/ug480_7Series_XADC/XADC-Register-Interface it looks like I need to do:

XilinxSystemMonitorChannel(name="vaux14",      addr=0x1E, bits=12, desc=[
        "Raw VAUX14 value from XADC.",
        "The external voltage I want to measure",
    ]),

I'll try it and post here if it works

JamesTimothyMeech avatar Jul 17 '23 10:07 JamesTimothyMeech

Hi @JamesTimothyMeech,

we've only implemented what was useful for us, but the structure should be in place to read the other XADC channels. I think you are in the right direction in your last message.

enjoy-digital avatar Jul 17 '23 14:07 enjoy-digital

Thanks for the pointer that I am heading in the right direction. I tried what I mentioned in the previous message and I can see the CSR is successfully generated and I am able to use it to write a C program which compiles and runs:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <generated/csr.h>

void 
print_double(double x) {
	int i = (int)floor(x);
	double remainder = x - (double) i;
	remainder = remainder * 1000000;
	printf("%i",i);
	printf(".");
	printf("%i",(int) remainder);
}

int main(int argc, char** argv) {
   printf("hello, world\n");
   volatile unsigned int *temperature = (volatile unsigned int *) CSR_XADC_TEMPERATURE_ADDR;
   double temperature_value = *temperature * (503.975/4096) -273.15;
   printf("FPGA Temperature = ");
   print_double(temperature_value);
   printf(" °C\n");
   
   volatile unsigned int *vccint = (volatile unsigned int *) CSR_XADC_VCCINT_ADDR;
   double vccint_value = (double) *vccint / 4096 * 3;
   printf("VCCINT = ");
   print_double(vccint_value);
   printf(" V\n");
   
   volatile unsigned int *vccaux = (volatile unsigned int *) CSR_XADC_VCCAUX_ADDR;
   double vccaux_value = (double) *vccaux / 4096.0 * 3.0;
   printf("VCCAUX = ");
   print_double(vccaux_value);
   printf(" V\n");
   
   volatile unsigned int *vccbram = (volatile unsigned int *) CSR_XADC_VCCBRAM_ADDR;
   double vccbram_value = (double) *vccbram / 4096.0 * 3;
   printf("VCCBRAM = ");
   print_double(vccbram_value);
   printf(" V\n");
   
   volatile unsigned int *vaux14 = (volatile unsigned int *) CSR_XADC_VAUX14_ADDR;
   double vaux14_value = (double) *vaux14 / 4096.0 ;
   printf("Random Sample = ");
   print_double(vaux14_value);
   printf(" V\n");

   return 0;
}

Unfortunately I get zero for the analog voltage which I am trying to measure:

liteOS> run adc_test.elf
hello, world
FPGA Temperature = 39.865722 °C
VCCINT = 1.4150 V
VCCAUX = 1.792968 V
VCCBRAM = 1.4150 V
Random Sample = 0.0 V

Is there anything obviously wrong with the setup I have posted here? I have wired my circuit with a voltage divider for approximately 0.3V and measured the voltage on the A10 pin to check that it is really there. I have wired my 0.3V signal to the A10 pin as the schematic says that this is connected to AD14_P which is the ADC channel I have selected in my program and verilog. I have grounded A11 as the schematic says that this is AD14_N: https://digilent.com/reference/_media/programmable-logic/arty-a7/arty-a7-e2-sch.pdf. I have attached a photo of my circuit: Image

JamesTimothyMeech avatar Jul 21 '23 12:07 JamesTimothyMeech

This is a slightly better photo of my wiring: Image (1)

JamesTimothyMeech avatar Jul 21 '23 12:07 JamesTimothyMeech

What is this line in the xadc.py file doing?

analog_layout = [("vauxp", 16), ("vauxn", 16), ("vp", 1), ("vn", 1)]

JamesTimothyMeech avatar Sep 08 '23 12:09 JamesTimothyMeech

I'm able to get a voltage reading using

XilinxSystemMonitorChannel(name="vaux0",      addr=0x10, bits=12, desc=[
        "Raw VAUX0 value from XADC.",
        "The external voltage I want to measure",
    ]),

on my hardware but as I change the voltage connected to the A5 pin on my board (the pin header connection which the documentation claims that channel 0 is connected to: https://digilent.com/reference/programmable-logic/arty-a7/reference-manual) the voltage is constant and unchanged:

liteOS> run adc_test.elf
hello, world
FPGA Temperature = 47.371209 °C
VCCINT = 1.4150 V
VCCAUX = 1.792968 V
VCCBRAM = 1.4882 V
Random Sample = 0.46630 V

JamesTimothyMeech avatar Sep 08 '23 13:09 JamesTimothyMeech

I observed a similar problem when using Vivado and Vitis to do the same thing and I was able to fix it by adding a pin constraints file for the Arty board and uncommenting the lines for VAUXN0 and VAUXP0. Is there any similar thing I can do in LiteX?

JamesTimothyMeech avatar Sep 11 '23 14:09 JamesTimothyMeech

I tried adding these lines to the digilent_arty.py file in litex-boards but I was unsure how to include them in the add_platform_command function so that they do not throw errors but still have their desired effect:

set_property -dict { PACKAGE_PIN C14   IOSTANDARD LVCMOS33 } [get_ports { vaux0_n  }]; #IO_L1N_T0_AD0N_15     Sch=ck_an_n[5] ChipKit pin=A5
set_property -dict { PACKAGE_PIN D14   IOSTANDARD LVCMOS33 } [get_ports { vaux0_p  }]; #IO_L1P_T0_AD0P_15     Sch=ck_an_p[5] ChipKit pin=A5

JamesTimothyMeech avatar Sep 11 '23 15:09 JamesTimothyMeech

My current design doesn't seem to generate any IO ports for the XADC. Is there any way I can force LiteX to do this? There are 8 IO ports but none are for the XADC Screenshot from 2023-10-06 18-22-32

JamesTimothyMeech avatar Oct 07 '23 05:10 JamesTimothyMeech

I recently made some progress on this with some help and advice from @bunnie. Now I need to figure out what to do with the analog.vbus_div, analog.usbdet_p, analog.usbdet_n, analog.vbus_div_n, analog.usbdet_p_n, and analog.usbdet_n_n signals here as they do not exist for the arty:

self.comb += analog_pads.vauxp.eq(Cat(dummy4,          # 0,1,2,3
                                             analog.noise1,        # 4
                                             dummy1,               # 5
                                             analog.vbus_div,      # 6
                                             dummy5,               # 7,8,9,10,11
                                             analog.noise0,        # 12
                                             dummy1,               # 13
                                             analog.usbdet_p,      # 14
                                             analog.usbdet_n,      # 15
                                        )),
            self.comb += analog_pads.vauxn.eq(Cat(dummy4,          # 0,1,2,3
                                             analog.noise1_n,      # 4
                                             dummy1,               # 5
                                             analog.vbus_div_n,    # 6
                                             dummy5,               # 7,8,9,10,11
                                             analog.noise0_n,      # 12
                                             dummy1,               # 13
                                             analog.usbdet_p_n,    # 14
                                             analog.usbdet_n_n,    # 15
                                        )),

https://github.com/betrusted-io/betrusted-soc/blob/6f8a181021be82ef0145f123c1650da3650edf07/betrusted_soc.py#L1404-L1482

JamesTimothyMeech avatar Jan 06 '24 17:01 JamesTimothyMeech

I can't just connect them all to dummy signals as this causes synthesis errors in Vivado:

        self.comb += analog_pads.vauxp.eq(Cat(dummy4,          # 0,1,2,3
                                             analog.ana_vp,    # 4
                                             dummy1,           # 5
                                             dummy1,           # 6
                                             dummy5,           # 7,8,9,10,11
                                             dummy1,           # 12
                                             dummy1,           # 13
                                             dummy1,           # 14
                                             dummy1,           # 15
                                        )),
        self.comb += analog_pads.vauxn.eq(Cat(dummy4,          # 0,1,2,3
                                             analog.ana_vn,    # 4
                                             dummy1,           # 5
                                             dummy1,           # 6
                                             dummy5,           # 7,8,9,10,11
                                             dummy1,           # 12
                                             dummy1,           # 13
                                             dummy1,           # 14
                                             dummy1,           # 15
                                        )),

Vivado errors:

---------------------------------------------------------------------------------
Finished Final Netlist Cleanup
---------------------------------------------------------------------------------
ERROR: [Synth 8-5535] port <analog_ana_vn> has illegal connections. It is illegal to have a port connected to an input buffer and other components. The following are the port connections :
Input Buffer:
	Port VN of instance XADC(XADC) in module <digilent_arty>
Other Components:
	Port VN of instance XADC(XADC) in module digilent_arty
	Port VAUXN[4] of instance XADC(XADC) in module digilent_arty

ERROR: [Synth 8-2918] Failing due to illegal port connections
ERROR: synthesis optimization failed, fatal insert_io failure.

JamesTimothyMeech avatar Jan 06 '24 17:01 JamesTimothyMeech

The error doesn't seem to be on a dummy signal -- it's complaining about VAUXN[4], which would be what is currently connected to analog.ana_vn.

The problem is more likely that analog.ana_vn doesn't map to the right pin. If you look at the Xilinx pin map. What pin is this mapped to, on what package?

bunnie avatar Jan 06 '24 17:01 bunnie

This looks like it might be the pin map for the Arty A7:

https://github.com/Digilent/digilent-xdc/blob/fa60017608b914b6765c8620e85a3b97a36179bf/Arty-A7-100-Master.xdc#L124-L135

You'll want to pick a pair of differential signals, for example:

#set_property -dict { PACKAGE_PIN C14   IOSTANDARD LVCMOS33 } [get_ports { vaux0_n  }]; #IO_L1N_T0_AD0N_15 	   Sch=ck_an_n[5] ChipKit pin=A5
#set_property -dict { PACKAGE_PIN D14   IOSTANDARD LVCMOS33 } [get_ports { vaux0_p  }]; #IO_L1P_T0_AD0P_15 	   Sch=ck_an_p[5] ChipKit pin=A5

Pins C14/D14 would be channel 0 +/- signals. If you picked that, you'd have to put the analog signal as the first in the Cat() list so it's mapped to analog 0, and then you'd need to make sure that on the Litex side you're mapped to the pins that correspond to that, i.e. these pins:

https://github.com/litex-hub/litex-boards/blob/52aeec00d78299fdb9270e24486035e997d93f68/litex_boards/platforms/digilent_arty.py#L228-L229

bunnie avatar Jan 06 '24 17:01 bunnie

Ah ok thanks that explains why I got past the Vivado errors when I changed my code to this:

        self.comb += analog_pads.vauxp.eq(Cat(dummy4,          # 0,1,2,3
                                             analog.vaux4_p,    # 4
                                             dummy1,           # 5
                                             dummy1,           # 6
                                             dummy5,           # 7,8,9,10,11
                                             dummy1,           # 12
                                             dummy1,           # 13
                                             dummy1,           # 14
                                             dummy1,           # 15
                                        )),
        self.comb += analog_pads.vauxn.eq(Cat(dummy4,          # 0,1,2,3
                                             analog.vaux4_n,    # 4
                                             dummy1,           # 5
                                             dummy1,           # 6
                                             dummy5,           # 7,8,9,10,11
                                             dummy1,           # 12
                                             dummy1,           # 13
                                             dummy1,           # 14
                                             dummy1,           # 15
                                        )),

I have this for the pin connections:

Subsignal("vaux4_n", Pins("C5"), IOStandard("LVCMOS33")),    # DVT
Subsignal("vaux4_p", Pins("C6"), IOStandard("LVCMOS33")),      # DVT

I'm just trying to open the .dcp file but my Vivado keeps crashing >.< I'll try on my Windows PC before I try to measure anything else using the hardware. digilent_arty_route.zip

JamesTimothyMeech avatar Jan 06 '24 18:01 JamesTimothyMeech

I still only see 8 IO ports in the .dcp file so my code must still be missing something despite me adding the pads to self.comb and the design running through Vivado without errors. Screenshot from 2024-01-07 01-35-19

JamesTimothyMeech avatar Jan 07 '24 01:01 JamesTimothyMeech

Ah there are 24 pages of schematics and I only checked one of them. The pins do look connected now! I will try to measure something on the hardware again tomorrow! Screenshot from 2024-01-07 01-56-29

JamesTimothyMeech avatar Jan 07 '24 01:01 JamesTimothyMeech

Despite successfully getting something that appears to be properly connected in Vivado I am unable to get voltage values other than zero printing out in my C program. In the attached screenshot in Vivado I see the MUXADDR on the XADC is unconnected which could explain why I cannot measure the signals on VAUX4. It does not explain why I cannot measure the signals on VP / VN as they do not go through the mux. I measured the voltages in my setup with a volt meter so I am sure they are there. Are there any obvious mistakes in my breadboard? Image A second better angle of the breadboard: Image

JamesTimothyMeech avatar Jan 07 '24 17:01 JamesTimothyMeech

Here is an image in Vivado showing that MUXADDR is not connected and many of the XADC signals are grounded as expected. Should I be worried about the IBUF instances on the analog lines? Screenshot from 2024-01-07 17-21-32

JamesTimothyMeech avatar Jan 07 '24 17:01 JamesTimothyMeech

I attach my xadc.py, digilent_arty.py board and target files, csr.h file and adc_test.c file in a zip file in case I made any obvious mistakes. I would happily make pull requests to integrate this with the public version of LiteX if we can get this working! This is the output of my adc_test.c program that shows the internal XADC channels working but the two external ones not working:

liteOS> run adc_test.elf
hello, world
FPGA Temperature = 43.187823 °C
VCCINT = 1.4882 V
VCCAUX = 1.792236 V
VCCBRAM = 1.4882 V
Random Sample = 0.0 V
Random Sample = 0.0 V

Files.zip

JamesTimothyMeech avatar Jan 07 '24 18:01 JamesTimothyMeech

image

this is what mine looks like. IBUF is what I'm using, and if you click on it you can confirm what pin it's mapped to.

bunnie avatar Jan 09 '24 08:01 bunnie

I attach my xadc.py, digilent_arty.py board and target files, csr.h file and adc_test.c file in a zip file in case I made any obvious mistakes. I would happily make pull requests to integrate this with the public version of LiteX if we can get this working! This is the output of my adc_test.c program that shows the internal XADC channels working but the two external ones not working:

liteOS> run adc_test.elf
hello, world
FPGA Temperature = 43.187823 °C
VCCINT = 1.4882 V
VCCAUX = 1.792236 V
VCCBRAM = 1.4882 V
Random Sample = 0.0 V
Random Sample = 0.0 V

Files.zip

can you share your C file in github please, I'd prefer not to download random ZIP attachments...

bunnie avatar Jan 09 '24 08:01 bunnie

I'll also note that your analog differential input is mapped to pins C5/C6 which is A0 on the input port of the header. So you could try plugging your test voltage into that pin and seeing if you get any voltage.

I'd also recommend, if you have a voltmeter, to sanity check the actual voltage you're seeing on the headers -- the jumpers you're using for your breadboard are notorious for having unreliable connections, and you're squishing them into the headers pretty tight, so you could also just be a victim of a wobbly connection. The schematics show a resistor divider to ground on all the analog inputs so they would read 0.0V correctly if left floating.

image

An alternative is to just take a jumper and plug it from an analog input to XVREF. Check first with a multimeter but that should just have a 1.25V output on it. You can wire that directly into any of the analog inputs and get a non-zero reading off of it, I think. You should also be able to plug any analog input into a 3.3V line and get effectively 1.0V, which is the full-scale input of the XADC. That would help eliminate wobbly wiring as a culprit as you debug the last mile. Seems like you're getting close!

bunnie avatar Jan 09 '24 08:01 bunnie

image

Just for reference that's also the pin mapping on the board, so, you can line that up against what you're seeing in the Vivado checkpoint viewer to make 100% sure you've got the right ports mapped. The port mapping is tricky, if you're reading the wrong one you'll be getting 0.0V out, at some point in the debug process I remember just reading every voltage out because I couldn't trust my port mappings.

bunnie avatar Jan 09 '24 08:01 bunnie