Commit dde94af2 authored by Andrey Filippov's avatar Andrey Filippov

Working on PLL/clocks configuration

parent 0876f70c
This diff is collapsed.
This diff is collapsed.
......@@ -41,22 +41,6 @@ class EzynqDDR:
self.DDRC_DEFS= ezynq_ddrc_defs.DDRC_DEFS
self.DDRIOB_DEFS=ezynq_ddriob_def.DDRIOB_DEFS
self.DDR_CFG_DEFS=ezynq_ddrcfg_defs.DDR_CFG_DEFS
# self.ddrc_register_sets= {'PRE': ezynq_registers.EzynqRegisters(self.DDRC_DEFS,0,regs_masked,permit_undefined_bits), # all now start from the same registers
# 'MAIN':ezynq_registers.EzynqRegisters(self.DDRC_DEFS,0,regs_masked,permit_undefined_bits),
# 'POST':ezynq_registers.EzynqRegisters(self.DDRC_DEFS,0,regs_masked,permit_undefined_bits)}
# self.ddriob_register_sets= {'PRE': ezynq_registers.EzynqRegisters(self.DDRIOB_DEFS,0,regs_masked,permit_undefined_bits), # all now start from the same registers
# 'MAIN':ezynq_registers.EzynqRegisters(self.DDRIOB_DEFS,0,regs_masked,permit_undefined_bits),
# 'POST':ezynq_registers.EzynqRegisters(self.DDRIOB_DEFS,0,regs_masked,permit_undefined_bits)}
# self.set_ddrc_attribs=(
# {'NAME':'PRE','POSTFIX':'_PRE','PREFIX':'CONFIG_EZYNQ_DDR_SETREG_','TITLE':"DDR Controller Register Pre-Set"},
# {'NAME':'MAIN','POSTFIX':'','PREFIX':'CONFIG_EZYNQ_DDR_SETREG_','TITLE':"DDR Controller Register Set"},
# {'NAME':'POST','POSTFIX':'_POST','PREFIX':'CONFIG_EZYNQ_DDR_SETREG_','TITLE':"DDR Controller Register Post-Set"})
# self.set_ddiob_attribs=(
# {'NAME':'PRE','POSTFIX':'_PRE','PREFIX':'CONFIG_EZYNQ_DDRIOB_SETREG_','TITLE':"DDR I/O Buffer Register Pre-Set"},
# {'NAME':'MAIN','POSTFIX':'','PREFIX':'CONFIG_EZYNQ_DDRIOB_SETREG_','TITLE':"DDR I/O Buffer Register Set"},
# {'NAME':'POST','POSTFIX':'_POST','PREFIX':'CONFIG_EZYNQ_DDRIOB_SETREG_','TITLE':"DDR I/O Buffer Register Post-Set"})
# self.postfixes=[attrib['POSTFIX'] for attrib in self.set_ddrc_attribs]
self.features=ezynq_feature_config.EzynqFeatures(self.DDR_CFG_DEFS,0) #DDR_CFG_DEFS
self.ddrc_register_set= ezynq_registers.EzynqRegisters(self.DDRC_DEFS,0,regs_masked,permit_undefined_bits)
self.ddriob_register_set=ezynq_registers.EzynqRegisters(self.DDRIOB_DEFS,0,regs_masked,permit_undefined_bits)
......@@ -71,8 +55,11 @@ class EzynqDDR:
return
html_file.write('<h2>DDR memory configuration parameters</h2>\n')
self.features.html_list_features(html_file)
def calculate_dependent_pars(self):
def calculate_dependent_pars(self,ddr_mhz):
#TODO: just testing on a few pars, migrate more of them
if not self.features.is_known('FREQ_MHZ'):
self.features.set_calculated_value('FREQ_MHZ',ddr_mhz,True)
try:
tCK=1000.0/self.features.get_par_value('FREQ_MHZ')
# print 'FREQ_MHZ=',self.features.get_par_value('FREQ_MHZ')
......@@ -258,6 +245,9 @@ class EzynqDDR:
def get_new_register_sets(self):
# return self.ddrc_register_sets['MAIN'].get_register_sets(True,True)
return self.ddrc_register_set.get_register_sets(True,True)
def get_ddr_type(self):
return self.features.get_par_value('MEMORY_TYPE')
def ddr_init_memory(self,current_reg_sets,force=False,warn=False):
# print 'ddr_init_memory, len(current_reg_sets)=',len(current_reg_sets),'\n'
......
......@@ -25,10 +25,8 @@ __status__ = "Development"
DDR_CFG_DEFS=[
{'NAME':'ENABLE', 'CONF_NAME':'CONFIG_EZYNQ_DDR_ENABLE','TYPE':'B','MANDATORY':True,'DERIVED':False,'DEFAULT':True,
'DESCRIPTION':'Enable DDR memory'},
{'NAME':'TARGET_FREQ_MHZ', 'CONF_NAME':'CONFIG_EZYNQ_DDR_TARGET_FREQ_MHZ','TYPE':'F','MANDATORY':True,'DERIVED':True,'DEFAULT':533.333333,
'DESCRIPTION':'Target DDR clock frequency in MHz (actual frequency will depend on the clock/clock muxes)'},
{'NAME':'FREQ_MHZ', 'CONF_NAME':'CONFIG_EZYNQ_DDR_FREQ_MHZ','TYPE':'F','MANDATORY':True,'DERIVED':True,'DEFAULT':533.333333,
'DESCRIPTION':'Actual DDR clock frequency in MHz, may be derived form CONFIG_EZYNQ_DDR_TARGET_FREQ_MHZ and clock multiplexer settings'},
'DESCRIPTION':'DDR clock frequency in MHz, this value overwrites the one calculated by the PLL/clock setup'},
{'NAME':'BANK_ADDR_MAP', 'CONF_NAME':'CONFIG_EZYNQ_DDR_BANK_ADDR_MAP','TYPE':'I','MANDATORY':True,'DERIVED':False,'DEFAULT':10,
'DESCRIPTION':'DRAM address mapping: number of combined column and row addresses lower than BA0'},
{'NAME':'ARB_PAGE_BANK', 'CONF_NAME':'CONFIG_EZYNQ_DDR_ARB_PAGE_BANK','TYPE':'B','MANDATORY':False,'DERIVED':False,'DEFAULT':False,
......@@ -210,7 +208,8 @@ DDR_CFG_DEFS=[
#TODO make some of (possibly) derived, leave '_T_' for ns only!
# CONFIG_EZYNQ_DDR_FREQ_MHZ = 533.333333 *
# CONFIG_EZYNQ_DDR_FREQ_MHZ = 533.333333 # DDR clock frequency in MHz, this value overwrites the one calculated by the PLL/clock setup
# CONFIG_EZYNQ_DDR_CL = 7 *
# CONFIG_EZYNQ_DDR_CWL = 6 *
# CONFIG_EZYNQ_DDR_DS_RCD = 7 (was CONFIG_EZYNQ_DDR_DS_T_RCD = 7) *
......
......@@ -74,6 +74,7 @@ class EzynqFeatures:
self.defs[feature['NAME']]['INDEX']=i
self.channel=channel
self.pars={}
self.target={} # target values (as specified)
self.config_names={}
self.defined=set()
self.calculated=set()
......@@ -130,6 +131,7 @@ class EzynqFeatures:
pass #keep string value
self.pars[name]=value
self.defined.add(name)
self.target[name]=value
#check after calculating derivative parameters
def check_missing_features(self):
all_set=True
......@@ -173,6 +175,32 @@ class EzynqFeatures:
except:
raise Exception (name+' not found in self.defs') # should not happen with wrong data, program bug
raise Exception (config_name+' is not defined, nor calculated')
def get_par_value_or_default(self,name):
try:
return self.pars[name]
except:
# print 'name=',name
# print self.pars
try:
config_name=self.defs[name]['CONF_NAME']
except:
raise Exception (name+' not found in self.defs') # should not happen with wrong data, program bug
try:
return self.defs[name]['DEFAULT']
except:
raise Exception (config_name+' is not defined, nor calculated and no default is provided')
def get_par_target(self,name):
try:
return self.target[name]
except:
try:
config_name=self.defs[name]['CONF_NAME']
except:
raise Exception (name+' not found in self.defs') # should not happen with wrong data, program bug
raise Exception ('Target value for '+config_name+' is not defined')
def is_known(self,name): # specified or calculated
return (name in self.defined) or (name in self.calculated)
......@@ -209,7 +237,8 @@ class EzynqFeatures:
if not html_file:
return
html_file.write('<table border="1">\n')
html_file.write('<tr><th>Configuration name</th><th>Value</th><th>Type/<br/>Choices</th><th>Mandatory</th><th>Origin</th><th>Default</th><th>Description</th></tr>\n')
# html_file.write('<tr><th>Configuration name</th><th>Value<br/>(Target)</th><th>Type/<br/>Choices</th><th>Mandatory</th><th>Origin</th><th>Default</th><th>Description</th></tr>\n')
html_file.write('<tr><th>Configuration name</th><th>Value (Target)</th><th>Type/<br/>Choices</th><th>Mandatory</th><th>Origin</th><th>Default</th><th>Description</th></tr>\n')
# print self.get_par_names()
# for name in self.pars:
for name in self.get_par_names():
......@@ -221,13 +250,24 @@ class EzynqFeatures:
value=hex(value)
else:
value=str(value)
try:
target_value=self.get_par_target(name)
if isinstance(target_value,int):
if (feature['TYPE']=='H'):
target_value=hex(target_value)
else:
target_value=str(target_value)
if value != target_value: # Do not show target_value if it is the same as actual
# value+='<br/>('+str(target_value)+')'
value='<b>'+str(value)+'</b> ('+str(target_value)+')'
except: # target_value is not set
pass
if name in self.defined:
origin="Defined"
elif name in self.calculated:
origin="Calculated"
else:
origin="Default"
if isinstance (feature['TYPE'],tuple):
par_type='<select>\n'
for t in feature['TYPE']:
......
......@@ -22,11 +22,12 @@ __maintainer__ = "Andrey Filippov"
__email__ = "andrey@elphel.com"
__status__ = "Development"
import struct
import argparse # http://docs.python.org/2/howto/argparse.html
import ezynq_ddr
import ezynq_registers
import ezynq_mio
# http://docs.python.org/2/howto/argparse.html
import argparse
import ezynq_clk
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbosity', action='count', help='increase output verbosity')
parser.add_argument('-c', '--configs', help='Configuration file (such as autoconf.mk)')
......@@ -289,6 +290,18 @@ mio_regs.process_mio(raw_configs,WARN) # does not use regs_masked
ddr=ezynq_ddr.EzynqDDR([],permit_undefined_bits, force, warn_notfit) #regs_masked are just []
ddr.parse_parameters(raw_configs)
ddr_type=ddr.get_ddr_type()
#clk=ezynq_clk.EzynqClk(regs_masked,ddr_type,permit_undefined_bits=False,force=False,warn=False)
clk=ezynq_clk.EzynqClk([],ddr_type,permit_undefined_bits,force,warn_notfit) # will it verify memory type is set?
clk.parse_parameters(raw_configs)
clk.calculate_dependent_pars() # will calculate DDR clock, needed for ddr.calculate_dependent_pars()
clk.check_missing_features() #and apply default values
clk.check_ds_compliance()
clk.setup_clocks()
ddr_mhz=clk.get_ddr_mhz()
if MIO_HTML:
f=open(MIO_HTML,'w')
......@@ -302,12 +315,15 @@ mio_regs.output_mio(f,MIO_HTML_MASK)
# setregs_mio(self,current_reg_sets,force=True):
#output_mio(registers,f,mio,MIO_HTML_MASK)
ddr.calculate_dependent_pars()
ddr.calculate_dependent_pars(ddr_mhz)
ddr.pre_validate() # before applying default values (some timings should be undefined, not defaults)
ddr.check_missing_features() #and apply default values
ddr.html_list_features(f) #verify /fix values after defaults are applied
#ddr.ddr_init_memory(current_reg_sets,force=False,warn=False): # will program to sequence 'MAIN'
#clk.calculate_dependent_pars()
clk.html_list_features(f)
reg_sets=[]
reg_sets=mio_regs.setregs_mio(reg_sets,force) # reg Sets include now MIO
num_mio_regs=len(reg_sets)
......
......@@ -109,11 +109,10 @@ CONFIG_EZYNQ_DDR_RAM_HIGHADDR = 0x3FFFFFFF
##### DDR independent ######
CONFIG_EZYNQ_DDR_ENABLE = Y # Enable DDR memory'},
CONFIG_EZYNQ_DDR_TARGET_FREQ_MHZ = 533.3333 # Target DDR clock frequency in MHz (actual frequency will depend on the clock/clock muxes)
#CONFIG_EZYNQ_DDR_FREQ_MHZ = 545.0 # Actual DDR clock frequency in MHz, may be derived form CONFIG_EZYNQ_DDR_TARGET_FREQ_MHZ and clock multiplexer settings. Causes tWR to go higher
#CONFIG_EZYNQ_DDR_FREQ_MHZ = 533.333374 # Actual DDR clock frequency in MHz, may be derived form CONFIG_EZYNQ_DDR_TARGET_FREQ_MHZ and clock multiplexer settings. Causes tWR to go higher
CONFIG_EZYNQ_DDR_FREQ_MHZ = 533.3333 # Actual DDR clock frequency in MHz, may be derived form CONFIG_EZYNQ_DDR_TARGET_FREQ_MHZ and clock multiplexer settings
CONFIG_EZYNQ_DDR_ENABLE = Y # Enable DDR memory'},
# CONFIG_EZYNQ_DDR_FREQ_MHZ = 533.333374 # DDR clock frequency in MHz, this value overwrites the one calculated by the PLL/clock setup
# CONFIG_EZYNQ_DDR_FREQ_MHZ = 533.333333 # DDR clock frequency in MHz, this value overwrites the one calculated by the PLL/clock setup
CONFIG_EZYNQ_DDR_BANK_ADDR_MAP = 10 # DRAM address mapping: number of combined column and row addresses lower than BA0
CONFIG_EZYNQ_DDR_ARB_PAGE_BANK = N # Enable Arbiter prioritization based on page/bank match
CONFIG_EZYNQ_DDR_ECC = Disabled # Enable ECC for the DDR memory
......@@ -143,7 +142,8 @@ CONFIG_EZYNQ_DDR_BIDIR_DRIVE_NEG = 12 # Drive strength negative for drivi
CONFIG_EZYNQ_DDR_BIDIR_DRIVE_POS = 28 # Slew rate positive for driving DDR DQ/DQS signals
###### DDR Datasheet (can be in include file) #######
CONFIG_EZYNQ_DDR_DS_PARTNO = MT41K256M16RE125 # Memory part number (currently not used - derive some parameters later)
CONFIG_EZYNQ_DDR_DS_MEMORY_TYPE = DDR3L # DDR memory type: DDR3 (1.5V), DDR3L (1.35V), DDR2 (1.8V), LPDDR2 (1.2V)
#CONFIG_EZYNQ_DDR_DS_MEMORY_TYPE = DDR3L # DDR memory type: DDR3 (1.5V), DDR3L (1.35V), DDR2 (1.8V), LPDDR2 (1.2V)
CONFIG_EZYNQ_DDR_DS_BANK_ADDR_COUNT = 3 # Number of DDR banks
CONFIG_EZYNQ_DDR_DS_ROW_ADDR_COUNT = 15 # Number of DDR Row Address bits
CONFIG_EZYNQ_DDR_DS_COL_ADDR_COUNT = 10 # Number of DDR Column address bits
......@@ -239,3 +239,68 @@ CONFIG_EZYNQ_DDR_CLOCK_0_PROPOGATION_DELAY = 160
CONFIG_EZYNQ_DDR_CLOCK_1_PROPOGATION_DELAY = 160
CONFIG_EZYNQ_DDR_CLOCK_2_PROPOGATION_DELAY = 160
CONFIG_EZYNQ_DDR_CLOCK_3_PROPOGATION_DELAY = 160
############# Main clock settings #############
CONFIG_EZYNQ_CLK_PS_MHZ = 33.333333 # PS_CLK System clock input frequency (MHz)
CONFIG_EZYNQ_CLK_DDR_MHZ = 533.333333 # DDR clock frequency - DDR_3X (MHz)
CONFIG_EZYNQ_CLK_ARM_MHZ = 640#667 # ARM CPU clock frequency cpu_6x4x (MHz)
CONFIG_EZYNQ_CLK_CPU_MODE = 6_2_1 # CPU clocks set 6:2:1 (6:3:2:1) or 4:2:1 (4:2:2:1)
############# Normally do not need to be modified #############
CONFIG_EZYNQ_CLK_DDR_DCI_MHZ = 10.0 # DDR DCI clock frequency (MHz). Normally 10 Mhz'},
CONFIG_EZYNQ_CLK_DDR2X_MHZ = 355.556 # DDR2X clock frequency (MHz). Does not need to be exactly 2/3 of DDR3X clock'},
CONFIG_EZYNQ_CLK_DDR_DCI_MHZ= 10.0 # DDR DCI clock frequency (MHz). Normally 10Mhz
CONFIG_EZYNQ_CLK_SMC_MHZ = 100.0 # Static memory controller clock frequency (MHz). Normally 100 Mhz
CONFIG_EZYNQ_CLK_QSPI_MHZ = 200.0 # Quad SPI memory controller clock frequency (MHz). Normally 200 Mhz
CONFIG_EZYNQ_CLK_GIGE_MHZ = 125.0 # GigE Ethernet controller reference clock frequency (MHz). Normally 125 Mhz
CONFIG_EZYNQ_CLK_SDIO_MHZ = 100.0 # SDIO controller reference clock frequency (MHz). Normally 100 Mhz
CONFIG_EZYNQ_CLK_UART_MHZ = 25.0 # UART controller reference clock frequency (MHz). Normally 25 Mhz
CONFIG_EZYNQ_CLK_SPI_MHZ = 200.0 # SPI controller reference clock frequency (MHz). Normally 200 Mhz
CONFIG_EZYNQ_CLK_CAN_MHZ = 100.0 # CAN controller reference clock frequency (MHz). Normally 100 Mhz
CONFIG_EZYNQ_CLK_PCAP_MHZ = 200.0 # PCAP clock frequency (MHz). Normally 200 Mhz
CONFIG_EZYNQ_CLK_TRACE_MHZ = 100.0 # Trace Port clock frequency (MHz). Normally 100 Mhz
CONFIG_EZYNQ_CLK_PLL_FCLK_MHZ = 50.0 # PLL DCLK clock frequency (MHz). Normally 50 Mhz
CONFIG_EZYNQ_CLK_ARM_SRC = ARM # ARM CPU clock source (normally ARM PLL)'},
CONFIG_EZYNQ_CLK_DDR_SRC = DDR # DDR (DDR2x, DDR3x) clock source (normally DDR PLL)'},
CONFIG_EZYNQ_CLK_DCI_SRC = DDR # DDR DCI clock source (normally DDR PLL)'},
CONFIG_EZYNQ_CLK_SMC_SRC = IO # Static memory controller clock source (normally IO PLL)'},
CONFIG_EZYNQ_CLK_QSPI_SRC = IO # Quad SPI memory controller clock source (normally IO PLL)'},
CONFIG_EZYNQ_CLK_GIGE_SRC = IO # GigE Ethernet controller clock source (normally IO PLL)'},
CONFIG_EZYNQ_CLK_SDIO_SRC = IO # SDIO controller clock source (normally IO PLL)'},
CONFIG_EZYNQ_CLK_UART_SRC = IO # UART controller clock source (normally IO PLL)'},
CONFIG_EZYNQ_CLK_SPI_SRC = IO # SPI controller clock source (normally IO PLL)'},
CONFIG_EZYNQ_CLK_CAN_SRC = IO # CAN controller clock source (normally IO PLL)'},
CONFIG_EZYNQ_CLK_PCAP_SRC = IO # PCAP controller clock source (normally IO PLL)'},
CONFIG_EZYNQ_CLK_TRACE_SRC = IO # Trace Port clock source (normally IO PLL)'},
CONFIG_EZYNQ_CLK_PLL_FCLK_SRC = IO # PLL FCLK clock source (normally IO PLL)'},
CONFIG_EZYNQ_DDR_DS_MEMORY_TYPE = DDR3 # DDR memory type: DDR3 (1.5V), DDR3L (1.35V), DDR2 (1.8V), LPDDR2 (1.2V)
##### performance data, final values (overwrite calculated) #####
CONFIG_EZYNQ_CLK_SPEED_GRADE = 3 # Device speed grade
#CONFIG_EZYNQ_CLK_PLL_MAX_MHZ = 1800.0 # Maximal PLL clock frequency, MHz. Overwrites default for selected speed grade: (Speed grade -1:1600, -2:1800, -3:2000)'},
#CONFIG_EZYNQ_CLK_PLL_MIN_MHZ = 780.0 # Minimal PLL clock frequency, all speed grades (MHz)'},
#CONFIG_EZYNQ_CLK_ARM621_MAX_MHZ = 733.0 # Maximal ARM clk_6x4x in 621 mode, MHz. Overwrites default for selected speed grade: (Speed grade -1:667, -2:733, -3:1000)'},
#CONFIG_EZYNQ_CLK_ARM421_MAX_MHZ = 600.0 # Maximal ARM clk_6x4x in 421 mode, MHz. Overwrites default for selected speed grade: (Speed grade -1:533, -2:600, -3:710)'},
#CONFIG_EZYNQ_CLK_DDR_3X_MAX_MHZ = 533.0 # Maximal DDR clk_3x clock frequency (MHz). Overwrites DDR-type/speed grade specific'},
#CONFIG_EZYNQ_CLK_DDR_2X_MAX_MHZ = 408.0 # Maximal DDR_2X clock frequency (MHz). Overwrites speed grade specific'},
##### datasheet data for specific speed grades #####
CONFIG_EZYNQ_CLK_DS_PLL_MAX_1_MHZ = 1600.0 # Maximal PLL clock frequency for speed grade 1 (MHz)'},
CONFIG_EZYNQ_CLK_DS_PLL_MAX_2_MHZ = 1800.0 # Maximal PLL clock frequency for speed grade 2 (MHz)'},
CONFIG_EZYNQ_CLK_DS_PLL_MAX_3_MHZ = 2000.0 # Maximal PLL clock frequency for speed grade 3 (MHz)'},
CONFIG_EZYNQ_CLK_DS_ARM621_MAX_1_MHZ = 667.0 # Maximal ARM clk_6x4x in 621 mode for speed grade 1, MHz'},
CONFIG_EZYNQ_CLK_DS_ARM621_MAX_2_MHZ = 733.0 #Maximal ARM clk_6x4x in 621 mode for speed grade 2, MHz'},
CONFIG_EZYNQ_CLK_DS_ARM621_MAX_3_MHZ =1000.0 #Maximal ARM clk_6x4x in 621 mode for speed grade 3, MHz'},
CONFIG_EZYNQ_CLK_DS_ARM421_MAX_1_MHZ = 533.0 # Maximal ARM clk_6x4x in 421 mode for speed grade 1, MHz'},
CONFIG_EZYNQ_CLK_DS_ARM421_MAX_2_MHZ = 600.0 # Maximal ARM clk_6x4x in 421 mode for speed grade 2, MHz'},
CONFIG_EZYNQ_CLK_DS_ARM421_MAX_3_MHZ = 710.0 # Maximal ARM clk_6x4x in 421 mode for speed grade 3, MHz'},
CONFIG_EZYNQ_CLK_DS_DDR3_MAX_1_MBPS = 1066.0 # Maximal DDR3 performance in Mb/s - twice clock frequency (MHz). Speed grade 1'},
CONFIG_EZYNQ_CLK_DS_DDR3_MAX_2_MBPS = 1066.0 # Maximal DDR3 performance in Mb/s - twice clock frequency (MHz). Speed grade 2'},
CONFIG_EZYNQ_CLK_DS_DDR3_MAX_3_MBPS = 1333.0 # Maximal DDR3 performance in Mb/s - twice clock frequency (MHz). Speed grade 3'},
CONFIG_EZYNQ_CLK_DS_DDRX_MAX_X_MBPS = 800.0 # Maximal DDR3L, DDR2, LPDDR2 performance in Mb/s - twice clock frequency (MHz). All speed grades'},
CONFIG_EZYNQ_CLK_DS_DDR_2X_MAX_1_MHZ = 355.0 # Maximal DDR_2X clock frequency (MHz) for speed grade 1'},
CONFIG_EZYNQ_CLK_DS_DDR_2X_MAX_2_MHZ = 408.0 # Maximal DDR_2X clock frequency (MHz) for speed grade 2'},
CONFIG_EZYNQ_CLK_DS_DDR_2X_MAX_3_MHZ = 444.0 # Maximal DDR_2X clock frequency (MHz) for speed grade 3'},
CONFIG_EZYNQ_CLK_COMPLIANCE_PERCENT = 5.0 # Allow exceeding maximal limits by this margin (percent'},
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment