2022-02-21 03:58:31 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2022 Mikolaj Wielgus
|
|
|
|
* Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 3
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, you may find one here:
|
|
|
|
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
* or you may search the http://www.gnu.org website for the version 3 license,
|
|
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sim/sim_model_ngspice.h>
|
|
|
|
|
2022-09-22 05:38:45 +00:00
|
|
|
#include <boost/algorithm/string/case_conv.hpp>
|
|
|
|
#include <fmt/core.h>
|
2022-02-21 03:58:31 +00:00
|
|
|
|
2022-04-12 14:37:06 +00:00
|
|
|
|
2022-10-10 23:23:00 +00:00
|
|
|
std::vector<std::string> SPICE_GENERATOR_NGSPICE::CurrentNames( const SPICE_ITEM& aItem ) const
|
2022-09-22 05:38:45 +00:00
|
|
|
{
|
2022-09-14 07:19:25 +00:00
|
|
|
switch( m_model.GetTypeInfo().deviceType )
|
2022-04-12 14:37:06 +00:00
|
|
|
{
|
2022-12-06 14:59:49 +00:00
|
|
|
case SIM_MODEL::DEVICE_T::NPN:
|
|
|
|
case SIM_MODEL::DEVICE_T::PNP:
|
2022-10-10 23:23:00 +00:00
|
|
|
return { fmt::format( "I({}:c)", aItem.refName ),
|
|
|
|
fmt::format( "I({}:b)", aItem.refName ),
|
|
|
|
fmt::format( "I({}:e)", aItem.refName ) };
|
2022-04-12 14:37:06 +00:00
|
|
|
|
2022-12-06 14:59:49 +00:00
|
|
|
case SIM_MODEL::DEVICE_T::NJFET:
|
|
|
|
case SIM_MODEL::DEVICE_T::PJFET:
|
|
|
|
case SIM_MODEL::DEVICE_T::NMES:
|
|
|
|
case SIM_MODEL::DEVICE_T::PMES:
|
|
|
|
case SIM_MODEL::DEVICE_T::NMOS:
|
|
|
|
case SIM_MODEL::DEVICE_T::PMOS:
|
2022-10-10 23:23:00 +00:00
|
|
|
return { fmt::format( "I({}:d)", aItem.refName ),
|
|
|
|
fmt::format( "I({}:g)", aItem.refName ),
|
|
|
|
fmt::format( "I({}:s)", aItem.refName ) };
|
2022-04-12 14:37:06 +00:00
|
|
|
|
2022-12-06 14:59:49 +00:00
|
|
|
case SIM_MODEL::DEVICE_T::R:
|
|
|
|
case SIM_MODEL::DEVICE_T::C:
|
|
|
|
case SIM_MODEL::DEVICE_T::L:
|
|
|
|
case SIM_MODEL::DEVICE_T::D:
|
2022-10-30 10:01:59 +00:00
|
|
|
return SPICE_GENERATOR::CurrentNames( aItem );
|
|
|
|
|
2022-04-12 14:37:06 +00:00
|
|
|
default:
|
2022-09-03 12:55:47 +00:00
|
|
|
wxFAIL_MSG( "Unhandled model device type in SIM_MODEL_NGSPICE" );
|
2022-04-12 14:37:06 +00:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-09-18 19:22:59 +00:00
|
|
|
SIM_MODEL_NGSPICE::SIM_MODEL_NGSPICE( TYPE aType ) :
|
2022-10-30 10:01:59 +00:00
|
|
|
SIM_MODEL_SPICE( aType, std::make_unique<SPICE_GENERATOR_NGSPICE>( *this ) )
|
2022-09-14 07:19:25 +00:00
|
|
|
{
|
2022-10-30 10:01:59 +00:00
|
|
|
const MODEL_INFO& modelInfo = ModelInfo( getModelType() );
|
|
|
|
|
|
|
|
for( const SIM_MODEL::PARAM::INFO& paramInfo : modelInfo.instanceParams )
|
|
|
|
{
|
|
|
|
// For now, only the geometry parameters.
|
|
|
|
if( paramInfo.category == SIM_MODEL::PARAM::CATEGORY::PRINCIPAL
|
|
|
|
|| paramInfo.category == SIM_MODEL::PARAM::CATEGORY::GEOMETRY )
|
|
|
|
{
|
|
|
|
AddParam( paramInfo, getIsOtherVariant() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for( const SIM_MODEL::PARAM::INFO& paramInfo : modelInfo.modelParams )
|
|
|
|
AddParam( paramInfo, getIsOtherVariant() );
|
2022-09-14 07:19:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-10-12 02:26:16 +00:00
|
|
|
void SIM_MODEL_NGSPICE::SetParamFromSpiceCode( const std::string& aParamName,
|
|
|
|
const std::string& aParamValue,
|
2022-05-08 09:54:35 +00:00
|
|
|
SIM_VALUE_GRAMMAR::NOTATION aNotation )
|
|
|
|
{
|
2022-10-04 20:41:19 +00:00
|
|
|
std::string paramName = boost::to_lower_copy( aParamName );
|
|
|
|
|
2022-05-31 02:55:48 +00:00
|
|
|
// "level" and "version" are not really parameters - they're part of the type - so silently
|
|
|
|
// ignore them.
|
2022-10-04 20:41:19 +00:00
|
|
|
if( paramName == "level" || paramName == "version" )
|
2022-10-12 02:26:16 +00:00
|
|
|
return;
|
2022-09-18 02:14:31 +00:00
|
|
|
|
2022-06-21 02:22:52 +00:00
|
|
|
// First we try to use the name as is. Note that you can't set instance parameters from this
|
2022-10-20 00:41:42 +00:00
|
|
|
// function, it's for ".model" cards, not for instantiations.
|
2022-05-08 09:54:35 +00:00
|
|
|
|
|
|
|
std::vector<std::reference_wrapper<const PARAM>> params = GetParams();
|
|
|
|
|
|
|
|
auto paramIt = std::find_if( params.begin(), params.end(),
|
2022-10-04 20:41:19 +00:00
|
|
|
[paramName]( const PARAM& param )
|
2022-05-08 09:54:35 +00:00
|
|
|
{
|
2022-06-21 02:22:52 +00:00
|
|
|
return !param.info.isSpiceInstanceParam
|
|
|
|
&& param.info.category != PARAM::CATEGORY::SUPERFLUOUS
|
2022-10-04 20:41:19 +00:00
|
|
|
&& ( param.info.name == boost::to_lower_copy( paramName )
|
|
|
|
|| param.info.name == boost::to_lower_copy( paramName ) + "_" );
|
2022-05-08 09:54:35 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
if( paramIt != params.end() )
|
2022-10-12 02:26:16 +00:00
|
|
|
{
|
|
|
|
SIM_MODEL::SetParamValue( static_cast<int>( paramIt - params.begin() ), aParamValue, aNotation );
|
|
|
|
return;
|
|
|
|
}
|
2022-06-21 02:22:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
// One Spice param can have multiple names, we need to take this into account.
|
2022-08-05 15:37:20 +00:00
|
|
|
|
|
|
|
std::vector<PARAM::INFO> ngspiceParams = ModelInfo( getModelType() ).modelParams;
|
2022-05-08 09:54:35 +00:00
|
|
|
|
|
|
|
auto ngspiceParamIt = std::find_if( ngspiceParams.begin(), ngspiceParams.end(),
|
2022-10-04 20:41:19 +00:00
|
|
|
[paramName]( const PARAM& param )
|
2022-05-08 09:54:35 +00:00
|
|
|
{
|
2022-10-20 00:41:42 +00:00
|
|
|
// Now we search without excluding Spice instance
|
|
|
|
// parameters and superfluous parameters.
|
2022-10-04 20:41:19 +00:00
|
|
|
return param.info.name == boost::to_lower_copy( paramName );
|
2022-05-08 09:54:35 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
if( ngspiceParamIt == ngspiceParams.end() )
|
2022-10-12 02:26:16 +00:00
|
|
|
{
|
2022-10-20 00:41:42 +00:00
|
|
|
if( canSilentlyIgnoreParam( paramName ) )
|
|
|
|
return;
|
|
|
|
|
2022-10-12 02:26:16 +00:00
|
|
|
THROW_IO_ERROR( wxString::Format( "Failed to set parameter '%s' to value '%s'",
|
|
|
|
aParamName,
|
|
|
|
aParamValue ) );
|
|
|
|
}
|
2022-05-08 09:54:35 +00:00
|
|
|
|
2022-06-21 02:22:52 +00:00
|
|
|
|
2022-10-20 00:41:42 +00:00
|
|
|
// We obtain the id of the Ngspice param that is to be set. We use this id to address the
|
|
|
|
// parameter to be set here because a superfluous parameter may be an alias: this will
|
|
|
|
// dereference it.
|
2022-05-08 09:54:35 +00:00
|
|
|
unsigned id = ngspiceParamIt->id;
|
|
|
|
|
|
|
|
// Find an actual parameter with the same id.
|
|
|
|
paramIt = std::find_if( params.begin(), params.end(),
|
|
|
|
[id]( const PARAM& param )
|
|
|
|
{
|
2022-10-20 00:41:42 +00:00
|
|
|
// Look for any non-superfluous parameter with the same id.
|
|
|
|
return param.info.id == id
|
|
|
|
&& param.info.category != PARAM::CATEGORY::SUPERFLUOUS;
|
2022-05-08 09:54:35 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
if( paramIt == params.end() )
|
2022-10-12 02:26:16 +00:00
|
|
|
{
|
|
|
|
THROW_IO_ERROR( wxString::Format( "Failed to set parameter '%s' to value '%s'",
|
|
|
|
aParamName,
|
|
|
|
aParamValue ) );
|
|
|
|
}
|
2022-05-08 09:54:35 +00:00
|
|
|
|
2022-10-12 02:26:16 +00:00
|
|
|
SIM_MODEL::SetParamValue( static_cast<int>( paramIt - params.begin() ), aParamValue, aNotation );
|
2022-05-08 09:54:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-10-20 00:41:42 +00:00
|
|
|
bool SIM_MODEL_NGSPICE::canSilentlyIgnoreParam( const std::string& aParamName )
|
|
|
|
{
|
|
|
|
// Ignore the purely informative LTspice-specific parameters "mfg" and "type".
|
|
|
|
if( aParamName == "mfg" || aParamName == "type" )
|
|
|
|
return true;
|
|
|
|
|
2022-12-06 14:59:49 +00:00
|
|
|
if( GetDeviceType() == DEVICE_T::D )
|
2022-10-20 00:41:42 +00:00
|
|
|
{
|
|
|
|
if( aParamName == "perim"
|
|
|
|
|| aParamName == "isw"
|
|
|
|
|| aParamName == "ns"
|
|
|
|
|| aParamName == "rsw"
|
|
|
|
|| aParamName == "cjsw"
|
|
|
|
|| aParamName == "vjsw"
|
|
|
|
|| aParamName == "mjsw"
|
|
|
|
|| aParamName == "fcs" )
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-06 14:59:49 +00:00
|
|
|
if( GetDeviceType() == DEVICE_T::NPN || GetDeviceType() == DEVICE_T::PNP )
|
2022-10-20 00:41:42 +00:00
|
|
|
{
|
|
|
|
// Ignore the purely informative LTspice-specific parameters "icrating" and "vceo".
|
|
|
|
if( aParamName == "icrating" || aParamName == "vceo" )
|
|
|
|
return true;
|
2022-10-20 00:56:28 +00:00
|
|
|
}
|
2022-10-20 00:41:42 +00:00
|
|
|
|
2022-10-20 00:56:28 +00:00
|
|
|
if( GetType() == TYPE::NPN_GUMMELPOON || GetType() == TYPE::PNP_GUMMELPOON )
|
|
|
|
{
|
2022-10-20 00:41:42 +00:00
|
|
|
// Ignore unused parameters.
|
|
|
|
if( aParamName == "bvcbo"
|
|
|
|
|| aParamName == "nbvcbo"
|
|
|
|
|| aParamName == "tbvcbo1"
|
|
|
|
|| aParamName == "tbvcbo2"
|
|
|
|
|| aParamName == "bvbe"
|
|
|
|
|| aParamName == "ibvbe"
|
|
|
|
|| aParamName == "nbvbe" )
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-09-22 05:38:45 +00:00
|
|
|
std::vector<std::string> SIM_MODEL_NGSPICE::getPinNames() const
|
2022-03-09 01:40:59 +00:00
|
|
|
{
|
2022-08-05 15:37:20 +00:00
|
|
|
return ModelInfo( getModelType() ).pinNames;
|
2022-03-09 01:40:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-08-05 15:37:20 +00:00
|
|
|
SIM_MODEL_NGSPICE::MODEL_TYPE SIM_MODEL_NGSPICE::getModelType() const
|
2022-02-21 03:58:31 +00:00
|
|
|
{
|
|
|
|
switch( GetType() )
|
|
|
|
{
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::NONE: return MODEL_TYPE::NONE;
|
|
|
|
case TYPE::D: return MODEL_TYPE::DIODE;
|
2022-02-21 03:58:31 +00:00
|
|
|
|
|
|
|
case TYPE::NPN_VBIC:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PNP_VBIC: return MODEL_TYPE::VBIC;
|
2022-11-29 09:10:33 +00:00
|
|
|
case TYPE::NPN_GUMMELPOON:
|
|
|
|
case TYPE::PNP_GUMMELPOON: return MODEL_TYPE::BJT;
|
2022-06-21 02:22:52 +00:00
|
|
|
case TYPE::NPN_HICUM2:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PNP_HICUM2: return MODEL_TYPE::HICUM2;
|
2022-02-21 03:58:31 +00:00
|
|
|
|
2022-04-12 14:37:06 +00:00
|
|
|
case TYPE::NJFET_SHICHMANHODGES:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PJFET_SHICHMANHODGES: return MODEL_TYPE::JFET;
|
2022-04-12 14:37:06 +00:00
|
|
|
case TYPE::NJFET_PARKERSKELLERN:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PJFET_PARKERSKELLERN: return MODEL_TYPE::JFET2;
|
2022-02-21 03:58:31 +00:00
|
|
|
|
|
|
|
case TYPE::NMES_STATZ:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMES_STATZ: return MODEL_TYPE::MES;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMES_YTTERDAL:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMES_YTTERDAL: return MODEL_TYPE::MESA;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMES_HFET1:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMES_HFET1: return MODEL_TYPE::HFET1;
|
2022-05-31 02:55:48 +00:00
|
|
|
case TYPE::NMES_HFET2:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMES_HFET2: return MODEL_TYPE::HFET2;
|
2022-02-21 03:58:31 +00:00
|
|
|
|
2022-11-28 06:59:24 +00:00
|
|
|
case TYPE::NMOS_VDMOS:
|
|
|
|
case TYPE::PMOS_VDMOS: return MODEL_TYPE::VDMOS;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_MOS1:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_MOS1: return MODEL_TYPE::MOS1;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_MOS2:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_MOS2: return MODEL_TYPE::MOS2;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_MOS3:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_MOS3: return MODEL_TYPE::MOS3;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_BSIM1:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_BSIM1: return MODEL_TYPE::BSIM1;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_BSIM2:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_BSIM2: return MODEL_TYPE::BSIM2;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_MOS6:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_MOS6: return MODEL_TYPE::MOS6;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_BSIM3:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_BSIM3: return MODEL_TYPE::BSIM3;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_MOS9:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_MOS9: return MODEL_TYPE::MOS9;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_B4SOI:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_B4SOI: return MODEL_TYPE::B4SOI;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_BSIM4:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_BSIM4: return MODEL_TYPE::BSIM4;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_B3SOIFD:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_B3SOIFD: return MODEL_TYPE::B3SOIFD;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_B3SOIDD:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_B3SOIDD: return MODEL_TYPE::B3SOIDD;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_B3SOIPD:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_B3SOIPD: return MODEL_TYPE::B3SOIPD;
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::NMOS_HISIM2:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_HISIM2: return MODEL_TYPE::HISIM2;
|
2022-04-12 14:37:06 +00:00
|
|
|
case TYPE::NMOS_HISIMHV1:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_HISIMHV1: return MODEL_TYPE::HISIMHV1;
|
2022-04-12 14:37:06 +00:00
|
|
|
case TYPE::NMOS_HISIMHV2:
|
2022-08-05 15:37:20 +00:00
|
|
|
case TYPE::PMOS_HISIMHV2: return MODEL_TYPE::HISIMHV2;
|
2022-02-21 03:58:31 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
wxFAIL_MSG( "Unhandled SIM_MODEL type in SIM_MODEL_NGSPICE" );
|
2022-08-05 15:37:20 +00:00
|
|
|
return MODEL_TYPE::NONE;
|
2022-02-21 03:58:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SIM_MODEL_NGSPICE::getIsOtherVariant()
|
|
|
|
{
|
|
|
|
switch( GetType() )
|
|
|
|
{
|
2022-04-12 14:37:06 +00:00
|
|
|
case TYPE::PNP_GUMMELPOON:
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::PNP_VBIC:
|
2022-06-21 02:22:52 +00:00
|
|
|
case TYPE::PNP_HICUM2:
|
2022-04-12 14:37:06 +00:00
|
|
|
case TYPE::PJFET_SHICHMANHODGES:
|
|
|
|
case TYPE::PJFET_PARKERSKELLERN:
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::PMES_STATZ:
|
|
|
|
case TYPE::PMES_YTTERDAL:
|
|
|
|
case TYPE::PMES_HFET1:
|
|
|
|
case TYPE::PMES_HFET2:
|
2022-11-28 06:59:24 +00:00
|
|
|
case TYPE::PMOS_VDMOS:
|
2022-02-21 03:58:31 +00:00
|
|
|
case TYPE::PMOS_MOS1:
|
|
|
|
case TYPE::PMOS_MOS2:
|
|
|
|
case TYPE::PMOS_MOS3:
|
|
|
|
case TYPE::PMOS_BSIM1:
|
|
|
|
case TYPE::PMOS_BSIM2:
|
|
|
|
case TYPE::PMOS_MOS6:
|
|
|
|
case TYPE::PMOS_BSIM3:
|
|
|
|
case TYPE::PMOS_MOS9:
|
|
|
|
case TYPE::PMOS_B4SOI:
|
|
|
|
case TYPE::PMOS_BSIM4:
|
|
|
|
case TYPE::PMOS_B3SOIFD:
|
|
|
|
case TYPE::PMOS_B3SOIDD:
|
|
|
|
case TYPE::PMOS_B3SOIPD:
|
|
|
|
case TYPE::PMOS_HISIM2:
|
2022-04-12 14:37:06 +00:00
|
|
|
case TYPE::PMOS_HISIMHV1:
|
|
|
|
case TYPE::PMOS_HISIMHV2:
|
2022-02-21 03:58:31 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|