From 56ccaeebf0ee153b68fb717dcb94d45ce35274ae Mon Sep 17 00:00:00 2001 From: bunnie Date: Sat, 4 Jan 2020 00:25:09 +0800 Subject: [PATCH 1/5] add support for DRP on XADC The design is backward-compatible in functionality for users who don't want to use DRP. That is, on power on, the XADC will scan the supply and temperature and store them in CSRs. If drp_enable is set, the scanning stops, and the XADC is now controlled by the DRP bus. Wher drp_enable is reset, the XADC may return to an auto-sample mode, but only if the internal registers are configured to do this. If you return to drp_enable without, for example, turning on the continuous sequence and setting which channels to check, the results will be unpredictable (mostly either it'll scan just once and stop, or it'll not scan all the channels, depending on the register settings). At this point, the backward compatibility was confirmed in testing, the DRP API is still a work in progress as the application this is being developed for needs to support fun stuff like real time sampling of signals to a buffer. Down the road, this block may have to be modified again to support an output FIFO, so we're not railing the CPU trying to do real time sampling of ADC data. This will probably be added as a True/False flag of some sort in the parameter list, because the FIFO will be expensive as far as BRAM goes to implement and applications that don't need the FIFO buffer can probably use that BRAM for better things. --- litex/soc/cores/xadc.py | 55 +++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/litex/soc/cores/xadc.py b/litex/soc/cores/xadc.py index 0cea38bb7..b54db0fea 100644 --- a/litex/soc/cores/xadc.py +++ b/litex/soc/cores/xadc.py @@ -7,12 +7,42 @@ from litex.soc.interconnect.csr import * # XADC --------------------------------------------------------------------------------------------- +analog_layout = [("vauxp", 16), ("vauxn", 16), ("vp", 1), ("vn", 1)] + class XADC(Module, AutoCSR): - def __init__(self): + def __init__(self, analog=None): + # add a CSR bank for controlling the XADC DRP. Adds bloat to the gateware + # if you're not using this feature, but makes the code more elegant. + self.drp_enable = CSRStatus() # must set this to 1 to use DRP, otherwise auto-sample + self.drp_read = CSR() + self.drp_write = CSR() + self.drp_drdy = CSRStatus() + self.drp_adr = CSRStorage(7) + self.drp_dat_w = CSRStorage(16) + self.drp_dat_r = CSRStatus(16) + drp_drdy = Signal() + + if analog == None: + analog = Record(analog_layout) + self.comb += [ + analog.vauxp.eq(0), + analog.vauxn.eq(0), + analog.vp.eq(0), + analog.vn.eq(0), + ] + + self.sync += [ + If(self.drp_read.re | self.drp_write.re, + self.drp_drdy.status.eq(0) + ).Elif(drp_drdy, + self.drp_drdy.status.eq(1) + ) + ] + # Temperature(°C) = adc_value*503.975/4096 - 273.15 self.temperature = CSRStatus(12) - # Voltage(V) = adc_value*)/4096*3 + # Voltage(V) = as uadc_value*)/4096*3 self.vccint = CSRStatus(12) self.vccaux = CSRStatus(12) self.vccbram = CSRStatus(12) @@ -28,8 +58,17 @@ class XADC(Module, AutoCSR): eoc = Signal() eos = Signal() data = Signal(16) - drdy = Signal() + auto = Signal() + self.comb += auto.eq(~self.drp_enable.status) + adr = Signal(7) + self.comb += [ + If(auto, + adr.eq(channel), + ).Else( + adr.eq(self.drp_adr.storage) + ) + ] self.specials += Instance("XADC", # from ug480 p_INIT_40=0x9000, p_INIT_41=0x2ef0, p_INIT_42=0x0400, @@ -44,12 +83,14 @@ class XADC(Module, AutoCSR): p_INIT_58=0x5999, p_INIT_5C=0x5111, o_ALM=self.alarm, o_OT=self.ot, o_BUSY=busy, o_CHANNEL=channel, o_EOC=eoc, o_EOS=eos, - i_VAUXN=0, i_VAUXP=1, i_VN=0, i_VP=1, + i_VAUXN=analog.vauxn, i_VAUXP=analog.vauxp, i_VN=analog.vn, i_VP=analog.vp, i_CONVST=0, i_CONVSTCLK=0, i_RESET=ResetSignal(), - o_DO=data, o_DRDY=drdy, i_DADDR=channel, i_DCLK=ClockSignal(), - i_DEN=eoc, i_DI=0, i_DWE=0, + o_DO=data, o_DRDY=drp_drdy, i_DADDR=adr, i_DCLK=ClockSignal(), + i_DEN=(auto & eoc) | (~auto & (self.drp_read.re | self.drp_write.re)), + i_DI=self.drp_dat_w.storage, i_DWE=self.drp_write.re, # o_JTAGBUSY=, o_JTAGLOCKED=, o_JTAGMODIFIED=, o_MUXADDR=, ) + self.comb += self.drp_dat_r.status.eq(data) channels = { 0: self.temperature, @@ -59,7 +100,7 @@ class XADC(Module, AutoCSR): } self.sync += [ - If(drdy, + If(drp_drdy & auto, Case(channel, dict( (k, v.status.eq(data >> 4)) for k, v in channels.items())) From 5eec7432b8807205c289b512be2bccfbf128786a Mon Sep 17 00:00:00 2001 From: bunnie Date: Sat, 4 Jan 2020 03:03:59 +0800 Subject: [PATCH 2/5] fix a couple bugs in the DRP readout path I'm now getting data out via DRP. Still some TODOs, but progress. --- litex/soc/cores/xadc.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/litex/soc/cores/xadc.py b/litex/soc/cores/xadc.py index b54db0fea..5244b8fe0 100644 --- a/litex/soc/cores/xadc.py +++ b/litex/soc/cores/xadc.py @@ -13,13 +13,19 @@ class XADC(Module, AutoCSR): def __init__(self, analog=None): # add a CSR bank for controlling the XADC DRP. Adds bloat to the gateware # if you're not using this feature, but makes the code more elegant. - self.drp_enable = CSRStatus() # must set this to 1 to use DRP, otherwise auto-sample + self.drp_enable = CSRStorage() # must set this to 1 to use DRP, otherwise auto-sample self.drp_read = CSR() self.drp_write = CSR() self.drp_drdy = CSRStatus() self.drp_adr = CSRStorage(7) self.drp_dat_w = CSRStorage(16) self.drp_dat_r = CSRStatus(16) + + # monitor EOC/EOS so we can poll if the ADC has been updated + self.eoc = CSRStatus() + self.eos = CSRStatus() + # TODO: hook up the alarm as interrupt + drp_drdy = Signal() if analog == None: @@ -60,7 +66,7 @@ class XADC(Module, AutoCSR): data = Signal(16) auto = Signal() - self.comb += auto.eq(~self.drp_enable.status) + self.comb += auto.eq(~self.drp_enable.storage) adr = Signal(7) self.comb += [ If(auto, @@ -90,7 +96,18 @@ class XADC(Module, AutoCSR): i_DI=self.drp_dat_w.storage, i_DWE=self.drp_write.re, # o_JTAGBUSY=, o_JTAGLOCKED=, o_JTAGMODIFIED=, o_MUXADDR=, ) - self.comb += self.drp_dat_r.status.eq(data) + self.sync += [ + If(drp_drdy, + self.drp_dat_r.status.eq(data), + ).Else( + self.drp_dat_r.status.eq(self.drp_dat_r.status), + ) + ] + + self.sync += [ + self.eoc.status.eq((~self.eoc.we & self.eoc.status) | eoc), + self.eos.status.eq((~self.eos.we & self.eos.status) | eos), + ] channels = { 0: self.temperature, From 4dc0a614281968cae3fa579d608d3ef05656e5fd Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Sat, 4 Jan 2020 23:59:04 +0100 Subject: [PATCH 3/5] soc/core/xadc: cleanup, simplify and add expose_drp method - keep CSR ordering with older version, requested for software compatibility. - always enable analog capability (user will just not use it if not needed). - add expose_drp method (similar to clock.py) for cases where DRP is needed. --- litex/soc/cores/xadc.py | 155 +++++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 73 deletions(-) diff --git a/litex/soc/cores/xadc.py b/litex/soc/cores/xadc.py index 5244b8fe0..551738ac6 100644 --- a/litex/soc/cores/xadc.py +++ b/litex/soc/cores/xadc.py @@ -1,4 +1,6 @@ -# Copyright 2014-2015 Robert Jordens +# This file is Copyright (c) 2014-2015 Robert Jordens +# This file is Copyright (c) 2019 bunnie +# This file is Copyright (c) 2019 Florent Kermarrec # License: BSD from migen import * @@ -7,76 +9,45 @@ from litex.soc.interconnect.csr import * # XADC --------------------------------------------------------------------------------------------- -analog_layout = [("vauxp", 16), ("vauxn", 16), ("vp", 1), ("vn", 1)] - class XADC(Module, AutoCSR): - def __init__(self, analog=None): - # add a CSR bank for controlling the XADC DRP. Adds bloat to the gateware - # if you're not using this feature, but makes the code more elegant. - self.drp_enable = CSRStorage() # must set this to 1 to use DRP, otherwise auto-sample - self.drp_read = CSR() - self.drp_write = CSR() - self.drp_drdy = CSRStatus() - self.drp_adr = CSRStorage(7) - self.drp_dat_w = CSRStorage(16) - self.drp_dat_r = CSRStatus(16) - - # monitor EOC/EOS so we can poll if the ADC has been updated - self.eoc = CSRStatus() - self.eos = CSRStatus() - # TODO: hook up the alarm as interrupt - - drp_drdy = Signal() - - if analog == None: - analog = Record(analog_layout) - self.comb += [ - analog.vauxp.eq(0), - analog.vauxn.eq(0), - analog.vp.eq(0), - analog.vn.eq(0), - ] - - self.sync += [ - If(self.drp_read.re | self.drp_write.re, - self.drp_drdy.status.eq(0) - ).Elif(drp_drdy, - self.drp_drdy.status.eq(1) - ) - ] - + def __init__(self): # Temperature(°C) = adc_value*503.975/4096 - 273.15 self.temperature = CSRStatus(12) - # Voltage(V) = as uadc_value*)/4096*3 + # Voltage(V) = adc_value*)/4096*3 self.vccint = CSRStatus(12) self.vccaux = CSRStatus(12) self.vccbram = CSRStatus(12) + # End of Convertion/Sequence + self.eoc = CSRStatus() + self.eos = CSRStatus() + # Alarms self.alarm = Signal(8) self.ot = Signal() + # Analog + self.analog = Record([("vauxp", 16), ("vauxn", 16), ("vp", 1), ("vn", 1)]) + self.analog.vauxp.reset = 1 + self.analog.vp.reset = 1 + # # # busy = Signal() channel = Signal(7) eoc = Signal() eos = Signal() - data = Signal(16) - auto = Signal() - self.comb += auto.eq(~self.drp_enable.storage) - adr = Signal(7) - self.comb += [ - If(auto, - adr.eq(channel), - ).Else( - adr.eq(self.drp_adr.storage) - ) - ] + # XADC instance ---------------------------------------------------------------------------- + self.dwe = Signal() + self.den = Signal() + self.drdy = Signal() + self.dadr = Signal(7) + self.di = Signal(16) + self.do = Signal(16) self.specials += Instance("XADC", - # from ug480 + # From ug480 p_INIT_40=0x9000, p_INIT_41=0x2ef0, p_INIT_42=0x0400, p_INIT_48=0x4701, p_INIT_49=0x000f, p_INIT_4A=0x4700, p_INIT_4B=0x0000, @@ -87,39 +58,77 @@ class XADC(Module, AutoCSR): p_INIT_54=0xa93a, p_INIT_55=0x5111, p_INIT_56=0x91eb, p_INIT_57=0xae4e, p_INIT_58=0x5999, p_INIT_5C=0x5111, - o_ALM=self.alarm, o_OT=self.ot, - o_BUSY=busy, o_CHANNEL=channel, o_EOC=eoc, o_EOS=eos, - i_VAUXN=analog.vauxn, i_VAUXP=analog.vauxp, i_VN=analog.vn, i_VP=analog.vp, - i_CONVST=0, i_CONVSTCLK=0, i_RESET=ResetSignal(), - o_DO=data, o_DRDY=drp_drdy, i_DADDR=adr, i_DCLK=ClockSignal(), - i_DEN=(auto & eoc) | (~auto & (self.drp_read.re | self.drp_write.re)), - i_DI=self.drp_dat_w.storage, i_DWE=self.drp_write.re, - # o_JTAGBUSY=, o_JTAGLOCKED=, o_JTAGMODIFIED=, o_MUXADDR=, + o_ALM = self.alarm, + o_OT = self.ot, + o_BUSY = busy, + o_CHANNEL = channel, + o_EOC = eoc, + o_EOS = eos, + i_VAUXN = self.analog.vauxn, + i_VAUXP = self.analog.vauxp, + i_VN = self.analog.vn, + i_VP = self.analog.vp, + i_CONVST = 0, + i_CONVSTCLK = 0, + i_RESET = ResetSignal(), + i_DCLK = ClockSignal(), + i_DWE = self.dwe, + i_DEN = self.den, + o_DRDY = self.drdy, + i_DADDR = self.dadr, + i_DI = self.di, + o_DO = self.do ) - self.sync += [ - If(drp_drdy, - self.drp_dat_r.status.eq(data), - ).Else( - self.drp_dat_r.status.eq(self.drp_dat_r.status), - ) - ] - - self.sync += [ - self.eoc.status.eq((~self.eoc.we & self.eoc.status) | eoc), - self.eos.status.eq((~self.eos.we & self.eos.status) | eos), + self.comb += [ + self.den.eq(eoc), + self.dadr.eq(channel), ] + # Channels update -------------------------------------------------------------------------- channels = { 0: self.temperature, 1: self.vccint, 2: self.vccaux, 6: self.vccbram } - self.sync += [ - If(drp_drdy & auto, + If(self.drdy, Case(channel, dict( - (k, v.status.eq(data >> 4)) + (k, v.status.eq(self.do >> 4)) for k, v in channels.items())) ) ] + + # End of Convertion/Sequence update -------------------------------------------------------- + self.sync += [ + self.eoc.status.eq((self.eoc.status & ~self.eoc.we) | eoc), + self.eos.status.eq((self.eos.status & ~self.eos.we) | eos), + ] + + def expose_drp(self): + self.drp_enable = CSRStorage() # Set to 1 to use DRP and disable auto-sampling + self.drp_read = CSR() + self.drp_write = CSR() + self.drp_drdy = CSRStatus() + self.drp_adr = CSRStorage(7) + self.drp_dat_w = CSRStorage(16) + self.drp_dat_r = CSRStatus(16) + + # # # + + self.comb += [ + self.dwe.eq(self.drp_write.re), + self.di.eq(self.drp_dat_w.storage), + self.drp_dat_r.status.eq(self.do), + If(self.drp_enable.storage, + self.den.eq(self.drp_read.re | self.drp_write.re), + self.dadr.eq(self.drp_adr.storage), + ), + ] + self.sync += [ + If(self.drp_read.re | self.drp_write.re, + self.drp_drdy.status.eq(0) + ).Elif(self.drdy, + self.drp_drdy.status.eq(1) + ) + ] From 87d456cae2e1b0abac82d8a88f325e93aa7c3da6 Mon Sep 17 00:00:00 2001 From: bunnie Date: Mon, 6 Jan 2020 21:47:58 +0800 Subject: [PATCH 4/5] bring back analog_pads specifier, remove reset conditions on VP For the "P" side of the analog channels, actually, connecting a digital line to them has "no meaning". The docs say that either you connect an analog pin to a pad, or vivado "ties it off appropriately". I wish it were the case that tying a pin to 0 or 1 would actually connect it to a power or ground, because it means that even in unipolar mode you have to burn two pins to break out the signal of interest *and* the ground reference analog pad (I thought I could just connect it to "0" and the pin would be grounded, but that doesn't happen -- it's just ignored if it's not wired to a pad). For the pad specifier, is it OK to leave it with an optional argument of analog_pads=None? I tried assigning to the self.analog property after instantiation, but this doesn't seem to work, the default values are preferred. It looks like if you don't want to do the analog_pads= optional argument the other way to do it would be to add code on the instiating module that tampers with the properties of the instance directly, but I think that's sort of ugly. Also, I noticed you stripped out the layout specifier for the analog_pads. I thought it would be nice to provide that in the file, so the caller doesn't have to infer what the pad layout is by reading the code...what's the motivation for removing that? --- litex/soc/cores/xadc.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/litex/soc/cores/xadc.py b/litex/soc/cores/xadc.py index 551738ac6..49977e3fe 100644 --- a/litex/soc/cores/xadc.py +++ b/litex/soc/cores/xadc.py @@ -10,7 +10,7 @@ from litex.soc.interconnect.csr import * # XADC --------------------------------------------------------------------------------------------- class XADC(Module, AutoCSR): - def __init__(self): + def __init__(self, analog_pads=None): # Temperature(°C) = adc_value*503.975/4096 - 273.15 self.temperature = CSRStatus(12) @@ -28,9 +28,10 @@ class XADC(Module, AutoCSR): self.ot = Signal() # Analog - self.analog = Record([("vauxp", 16), ("vauxn", 16), ("vp", 1), ("vn", 1)]) - self.analog.vauxp.reset = 1 - self.analog.vp.reset = 1 + if analog_pads == None: + self.analog = Record([("vauxp", 16), ("vauxn", 16), ("vp", 1), ("vn", 1)]) + else: + self.analog = analog_pads # # # From 378722a7efe8c539466da0618adbc4215882d5c7 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Jan 2020 16:28:48 +0100 Subject: [PATCH 5/5] soc/cores/xadc: define analog_layout and simplify analog_pads connections --- litex/soc/cores/xadc.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/litex/soc/cores/xadc.py b/litex/soc/cores/xadc.py index 49977e3fe..178313b78 100644 --- a/litex/soc/cores/xadc.py +++ b/litex/soc/cores/xadc.py @@ -9,6 +9,8 @@ from litex.soc.interconnect.csr import * # XADC --------------------------------------------------------------------------------------------- +analog_layout = [("vauxp", 16), ("vauxn", 16), ("vp", 1), ("vn", 1)] + class XADC(Module, AutoCSR): def __init__(self, analog_pads=None): # Temperature(°C) = adc_value*503.975/4096 - 273.15 @@ -27,12 +29,6 @@ class XADC(Module, AutoCSR): self.alarm = Signal(8) self.ot = Signal() - # Analog - if analog_pads == None: - self.analog = Record([("vauxp", 16), ("vauxn", 16), ("vp", 1), ("vn", 1)]) - else: - self.analog = analog_pads - # # # busy = Signal() @@ -65,10 +61,10 @@ class XADC(Module, AutoCSR): o_CHANNEL = channel, o_EOC = eoc, o_EOS = eos, - i_VAUXN = self.analog.vauxn, - i_VAUXP = self.analog.vauxp, - i_VN = self.analog.vn, - i_VP = self.analog.vp, + i_VAUXP = 0 if analog_pads is None else analog_pads.vauxp, + i_VAUXN = 0 if analog_pads is None else analog_pads.vauxn, + i_VP = 0 if analog_pads is None else analog_pads.vp, + i_VN = 0 if analog_pads is None else analog_pads.vn, i_CONVST = 0, i_CONVSTCLK = 0, i_RESET = ResetSignal(),