From 80340c607cf8bede6a4aa539f5f2c02141d5e53e Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Mon, 3 Jul 2023 15:45:58 +0100 Subject: [PATCH] ADDED noise simulation GUI. --- eeschema/dialogs/dialog_sim_command.cpp | 356 +++++++++---------- eeschema/dialogs/dialog_sim_command.h | 17 +- eeschema/dialogs/dialog_sim_command_base.cpp | 65 ++-- eeschema/dialogs/dialog_sim_command_base.fbp | 123 ++----- eeschema/dialogs/dialog_sim_command_base.h | 7 +- eeschema/sim/ngspice_circuit_model.cpp | 71 +++- eeschema/sim/ngspice_circuit_model.h | 4 + eeschema/sim/sim_model.cpp | 29 +- eeschema/sim/sim_model_serializer.cpp | 2 +- eeschema/sim/spice_value.cpp | 30 +- 10 files changed, 340 insertions(+), 364 deletions(-) diff --git a/eeschema/dialogs/dialog_sim_command.cpp b/eeschema/dialogs/dialog_sim_command.cpp index 49414b40e9..a68f38782c 100644 --- a/eeschema/dialogs/dialog_sim_command.cpp +++ b/eeschema/dialogs/dialog_sim_command.cpp @@ -57,8 +57,8 @@ static wxString getStringSelection( const wxChoice* aCtrl ) DIALOG_SIM_COMMAND::DIALOG_SIM_COMMAND( wxWindow* aParent, - std::shared_ptr aCircuitModel, - std::shared_ptr& aSettings ) : + std::shared_ptr aCircuitModel, + std::shared_ptr& aSettings ) : DIALOG_SIM_COMMAND_BASE( aParent ), m_circuitModel( aCircuitModel ), m_settings( aSettings ), @@ -87,12 +87,32 @@ DIALOG_SIM_COMMAND::DIALOG_SIM_COMMAND( wxWindow* aParent, m_transInitial->SetValidator( m_spiceEmptyValidator ); m_transMaxStep->SetValidator( m_spiceEmptyValidator ); + wxChar type1 = getStringSelection( m_dcSourceType1 ).Upper().GetChar( 0 ); + updateDCSources( type1, m_dcSource1 ); + + wxChar type2 = getStringSelection( m_dcSourceType2 ).Upper().GetChar( 0 ); + updateDCSources( type2, m_dcSource2 ); + + // NoiseRef is optional + m_noiseRef->Append( wxEmptyString ); + + for( const std::string& net : m_circuitModel->GetNets() ) + { + m_noiseMeas->Append( net ); + m_noiseRef->Append( net ); + } + + for( const SPICE_ITEM& item : m_circuitModel->GetItems() ) + { + if( item.model->GetDeviceType() == SIM_MODEL::DEVICE_T::V ) + m_noiseSrc->Append( item.refName ); + } + refreshUIControls(); // Hide pages that aren't fully implemented yet // wxPanel::Hide() isn't enough on some platforms m_simPages->RemovePage( m_simPages->FindPage( m_pgDistortion ) ); - m_simPages->RemovePage( m_simPages->FindPage( m_pgNoise ) ); m_simPages->RemovePage( m_simPages->FindPage( m_pgPoleZero ) ); m_simPages->RemovePage( m_simPages->FindPage( m_pgSensitivity ) ); m_simPages->RemovePage( m_simPages->FindPage( m_pgTransferFunction ) ); @@ -104,7 +124,7 @@ DIALOG_SIM_COMMAND::DIALOG_SIM_COMMAND( wxWindow* aParent, } wxString DIALOG_SIM_COMMAND::evaluateDCControls( wxChoice* aDcSource, wxTextCtrl* aDcStart, - wxTextCtrl* aDcStop, wxTextCtrl* aDcIncr ) + wxTextCtrl* aDcStop, wxTextCtrl* aDcIncr ) { wxString dcSource; wxWindow* ctrlWithError = nullptr; @@ -114,11 +134,9 @@ wxString DIALOG_SIM_COMMAND::evaluateDCControls( wxChoice* aDcSource, wxTextCtrl if( dcSource.IsEmpty() ) { - DisplayError( this, _( "You need to select DC source" ) ); + DisplayError( this, _( "A DC source must be specified." ) ); ctrlWithError = aDcSource; } - /// @todo for some reason it does not trigger the assigned SPICE_VALIDATOR, - // hence try..catch below else if( !aDcStart->Validate() ) ctrlWithError = aDcStart; else if( !aDcStop->Validate() ) @@ -132,31 +150,14 @@ wxString DIALOG_SIM_COMMAND::evaluateDCControls( wxChoice* aDcSource, wxTextCtrl return wxEmptyString; } - try - { - // pick device name from exporter when something different than temperature is selected - if( dcSource.Cmp( "TEMP" ) ) - dcSource = m_circuitModel->GetItemName( std::string( dcSource.ToUTF8() ) ); + // pick device name from exporter when something different than temperature is selected + if( dcSource.Cmp( "TEMP" ) ) + dcSource = m_circuitModel->GetItemName( std::string( dcSource.ToUTF8() ) ); - return wxString::Format( "%s %s %s %s", dcSource, - SPICE_VALUE( aDcStart->GetValue() ).ToSpiceString(), - SPICE_VALUE( aDcStop->GetValue() ).ToSpiceString(), - SPICE_VALUE( aDcIncr->GetValue() ).ToSpiceString() ); - } - catch( std::exception& e ) - { - DisplayError( this, e.what() ); - return wxEmptyString; - } - catch( const KI_PARAM_ERROR& e ) - { - DisplayError( this, e.What() ); - return wxEmptyString; - } - catch( ... ) - { - return wxEmptyString; - } + return wxString::Format( "%s %s %s %s", dcSource, + SPICE_VALUE( aDcStart->GetValue() ).ToSpiceString(), + SPICE_VALUE( aDcStop->GetValue() ).ToSpiceString(), + SPICE_VALUE( aDcIncr->GetValue() ).ToSpiceString() ); } @@ -218,7 +219,7 @@ bool DIALOG_SIM_COMMAND::TransferDataFromWindow() if( m_dcSource1->GetStringSelection() == m_dcSource2->GetStringSelection() ) { - DisplayError( this, _( "Source 1 and Source 2 must be different" ) ); + DisplayError( this, _( "Source 1 and Source 2 must be different." ) ); return false; } } @@ -227,31 +228,27 @@ bool DIALOG_SIM_COMMAND::TransferDataFromWindow() } else if( page == m_pgNoise ) // Noise analysis { - /*const std::map& netMap = m_circuitModel->GetNetIndexMap(); + wxString output = m_noiseMeas->GetStringSelection(); + wxString ref = m_noiseRef->GetStringSelection(); + wxString noiseSource = m_noiseSrc->GetStringSelection(); - if( empty( m_noiseMeas ) || empty( m_noiseSrc ) || empty( m_noisePointsNumber ) - || empty( m_noiseFreqStart ) || empty( m_noiseFreqStop ) ) + if( m_noiseFreqStart->IsEmpty() || m_noiseFreqStop->IsEmpty() ) { + DisplayError( this, _( "A frequency range must be specified." ) ); return false; } - wxString ref; + if( !ref.IsEmpty() ) + ref = wxS( "," ) + m_circuitModel->GetItemName( std::string( ref.ToUTF8() ) ); - if( !empty( m_noiseRef ) ) - ref = wxString::Format( ", %d", netMap.at( m_noiseRef->GetValue() ) ); - - wxString noiseSource = m_circuitModel->GetSpiceDevice( m_noiseSrc->GetValue() ); - - // Add voltage source prefix if needed - if( noiseSource[0] != 'v' && noiseSource[0] != 'V' ) - noiseSource += 'v' + noiseSource; - - m_simCommand.Printf( ".noise v(%d%s) %s %s %s %s %s", - netMap.at( m_noiseMeas->GetValue() ), ref, - noiseSource, scaleToString( m_noiseScale->GetSelection() ), + m_simCommand.Printf( ".noise v(%s%s) %s %s %s %s %s", + output, + ref, + noiseSource, + scaleToString( m_noiseScale->GetSelection() ), m_noisePointsNumber->GetValue(), SPICE_VALUE( m_noiseFreqStart->GetValue() ).ToSpiceString(), - SPICE_VALUE( m_noiseFreqStop->GetValue() ).ToSpiceString() );*/ + SPICE_VALUE( m_noiseFreqStop->GetValue() ).ToSpiceString() ); } else if( page == m_pgOP ) // DC operating point analysis { @@ -277,7 +274,9 @@ bool DIALOG_SIM_COMMAND::TransferDataFromWindow() optionals = wxS( "uic" ); if( !empty( m_transMaxStep ) ) + { optionals = SPICE_VALUE( m_transMaxStep->GetValue() ).ToSpiceString() + spc + optionals; + } else if( !optionals.IsEmpty() ) { SPICE_VALUE maxStep = ( finalTime - startTime ) / 50.0; @@ -359,56 +358,13 @@ bool DIALOG_SIM_COMMAND::TransferDataToWindow() } } - if( !m_dcSource1->GetCount() ) - { - wxChar type1 = getStringSelection( m_dcSourceType1 ).Upper().GetChar( 0 ); - updateDCSources( type1, m_dcSource1 ); - } - - if( !m_dcSource2->GetCount() ) - { - wxChar type2 = getStringSelection( m_dcSourceType2 ).Upper().GetChar( 0 ); - updateDCSources( type2, m_dcSource2 ); - } - if( m_simCommand.IsEmpty() && !empty( m_customTxt ) ) - return parseCommand( m_customTxt->GetValue() ); + parseCommand( m_customTxt->GetValue() ); return true; } -int DIALOG_SIM_COMMAND::ShowModal() -{ - // Fill out comboboxes that allows one to select nets - // Map comoboxes to their current values - std::map cmbNet = { - { m_noiseMeas, m_noiseMeas->GetStringSelection() }, - { m_noiseRef, m_noiseRef->GetStringSelection() } - }; - - for( const std::pair& c : cmbNet ) - c.first->Clear(); - - for( const std::string& net : m_circuitModel->GetNets() ) - { - for( const std::pair& c : cmbNet ) - c.first->Append( net ); - } - - // Try to restore the previous selection, if possible - for( const std::pair& c : cmbNet ) - { - int idx = c.first->FindString( c.second ); - - if( idx != wxNOT_FOUND ) - c.first->SetSelection( idx ); - } - - return DIALOG_SIM_COMMAND_BASE::ShowModal(); -} - - void DIALOG_SIM_COMMAND::updateDCSources( wxChar aType, wxChoice* aSource ) { wxString prevSelection; @@ -453,117 +409,137 @@ void DIALOG_SIM_COMMAND::updateDCSources( wxChar aType, wxChoice* aSource ) } -bool DIALOG_SIM_COMMAND::parseCommand( const wxString& aCommand ) +void DIALOG_SIM_COMMAND::parseCommand( const wxString& aCommand ) { if( aCommand.IsEmpty() ) - return false; + return; wxStringTokenizer tokenizer( aCommand, " " ); - wxString tkn = tokenizer.GetNextToken().Lower(); + wxString token = tokenizer.GetNextToken().Lower(); - try + if( token == ".ac" ) { - if( tkn == ".ac" ) + m_simPages->SetSelection( m_simPages->FindPage( m_pgAC ) ); + + token = tokenizer.GetNextToken().Lower(); + + for( SCALE_TYPE candidate : { DECADE, OCTAVE, LINEAR } ) { - m_simPages->SetSelection( m_simPages->FindPage( m_pgAC ) ); - - tkn = tokenizer.GetNextToken().Lower(); - - if( tkn == "dec" ) - m_acScale->SetSelection( 0 ); - else if( tkn == "oct" ) - m_acScale->SetSelection( 1 ); - else if( tkn == "lin" ) - m_acScale->SetSelection( 2 ); - else - return false; - - // If the fields below are empty, it will be caught by the exception handler - m_acPointsNumber->SetValue( tokenizer.GetNextToken() ); - m_acFreqStart->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); - m_acFreqStop->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); - } - else if( tkn == ".dc" ) - { - m_simPages->SetSelection( m_simPages->FindPage( m_pgDC ) ); - - SPICE_DC_PARAMS src1, src2; - src2.m_vincrement = SPICE_VALUE( -1 ); - - if( !m_circuitModel->ParseDCCommand( aCommand, &src1, &src2 ) ) - return false; - - if( src1.m_source.IsSameAs( wxT( "TEMP" ), false ) ) - setStringSelection( m_dcSourceType1, wxT( "TEMP" ) ); - else - setStringSelection( m_dcSourceType1, src1.m_source.GetChar( 0 ) ); - - updateDCSources( src1.m_source.GetChar( 0 ), m_dcSource1 ); - m_dcSource1->SetStringSelection( src1.m_source ); - m_dcStart1->SetValue( src1.m_vstart.ToSpiceString() ); - m_dcStop1->SetValue( src1.m_vend.ToSpiceString() ); - m_dcIncr1->SetValue( src1.m_vincrement.ToSpiceString() ); - - if( src2.m_vincrement.ToDouble() != -1 ) + if( scaleToString( candidate ) == token ) { - if( src2.m_source.IsSameAs( wxT( "TEMP" ), false ) ) - setStringSelection( m_dcSourceType2, wxT( "TEMP" ) ); - else - setStringSelection( m_dcSourceType2, src2.m_source.GetChar( 0 ) ); - - updateDCSources( src2.m_source.GetChar( 0 ), m_dcSource2 ); - m_dcSource2->SetStringSelection( src2.m_source ); - m_dcStart2->SetValue( src2.m_vstart.ToSpiceString() ); - m_dcStop2->SetValue( src2.m_vend.ToSpiceString() ); - m_dcIncr2->SetValue( src2.m_vincrement.ToSpiceString() ); - - m_dcEnable2->SetValue( true ); + m_acScale->SetSelection( candidate ); + break; } - - refreshUIControls(); } - else if( tkn == ".tran" ) - { - m_simPages->SetSelection( m_simPages->FindPage( m_pgTransient ) ); - // If the fields below are empty, it will be caught by the exception handler - m_transStep->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); - m_transFinal->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); - - // Initial time is an optional field - tkn = tokenizer.GetNextToken(); - - if( !tkn.IsEmpty() ) - m_transInitial->SetValue( SPICE_VALUE( tkn ).ToSpiceString() ); - - // Max step is an optional field - tkn = tokenizer.GetNextToken(); - - if( !tkn.IsEmpty() ) - m_transMaxStep->SetValue( SPICE_VALUE( tkn ).ToSpiceString() ); - - // uic is an optional field - tkn = tokenizer.GetNextToken(); - - if( tkn.IsSameAs( wxS( "uic" ) ) ) - m_useInitialConditions->SetValue( true ); - } - else if( tkn == ".op" ) - { - m_simPages->SetSelection( m_simPages->FindPage( m_pgOP ) ); - } - else if( !empty( m_customTxt ) ) // Custom directives - { - m_simPages->SetSelection( m_simPages->FindPage( m_pgCustom ) ); - } + m_acPointsNumber->SetValue( tokenizer.GetNextToken() ); + m_acFreqStart->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); + m_acFreqStop->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); } - catch( ... ) + else if( token == ".dc" ) { - // Nothing really bad has happened - return false; - } + m_simPages->SetSelection( m_simPages->FindPage( m_pgDC ) ); - return true; + SPICE_DC_PARAMS src1, src2; + src2.m_vincrement = SPICE_VALUE( -1 ); + + m_circuitModel->ParseDCCommand( aCommand, &src1, &src2 ); + + if( src1.m_source.IsSameAs( wxT( "TEMP" ), false ) ) + setStringSelection( m_dcSourceType1, wxT( "TEMP" ) ); + else + setStringSelection( m_dcSourceType1, src1.m_source.GetChar( 0 ) ); + + updateDCSources( src1.m_source.GetChar( 0 ), m_dcSource1 ); + m_dcSource1->SetStringSelection( src1.m_source ); + m_dcStart1->SetValue( src1.m_vstart.ToSpiceString() ); + m_dcStop1->SetValue( src1.m_vend.ToSpiceString() ); + m_dcIncr1->SetValue( src1.m_vincrement.ToSpiceString() ); + + if( src2.m_vincrement.ToDouble() != -1 ) + { + if( src2.m_source.IsSameAs( wxT( "TEMP" ), false ) ) + setStringSelection( m_dcSourceType2, wxT( "TEMP" ) ); + else + setStringSelection( m_dcSourceType2, src2.m_source.GetChar( 0 ) ); + + updateDCSources( src2.m_source.GetChar( 0 ), m_dcSource2 ); + m_dcSource2->SetStringSelection( src2.m_source ); + m_dcStart2->SetValue( src2.m_vstart.ToSpiceString() ); + m_dcStop2->SetValue( src2.m_vend.ToSpiceString() ); + m_dcIncr2->SetValue( src2.m_vincrement.ToSpiceString() ); + + m_dcEnable2->SetValue( true ); + } + + refreshUIControls(); + } + else if( token == ".noise" ) + { + m_simPages->SetSelection( m_simPages->FindPage( m_pgNoise ) ); + + wxString output; + wxString ref; + wxString source; + wxString scale; + SPICE_VALUE pts; + SPICE_VALUE fStart; + SPICE_VALUE fStop; + + m_circuitModel->ParseNoiseCommand( aCommand, &output, &ref, &source, &scale, &pts, + &fStart, &fStop ); + + m_noiseMeas->SetStringSelection( output ); + m_noiseRef->SetStringSelection( ref ); + m_noiseSrc->SetStringSelection( source ); + + for( SCALE_TYPE candidate : { DECADE, OCTAVE, LINEAR } ) + { + if( scaleToString( candidate ) == scale ) + { + m_noiseScale->SetSelection( candidate ); + break; + } + } + + m_noisePointsNumber->SetValue( pts.ToSpiceString() ); + m_noiseFreqStart->SetValue( fStart.ToSpiceString() ); + m_noiseFreqStop->SetValue( fStop.ToSpiceString() ); + } + else if( token == ".tran" ) + { + m_simPages->SetSelection( m_simPages->FindPage( m_pgTransient ) ); + + // If the fields below are empty, it will be caught by the exception handler + m_transStep->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); + m_transFinal->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); + + // Initial time is an optional field + token = tokenizer.GetNextToken(); + + if( !token.IsEmpty() ) + m_transInitial->SetValue( SPICE_VALUE( token ).ToSpiceString() ); + + // Max step is an optional field + token = tokenizer.GetNextToken(); + + if( !token.IsEmpty() ) + m_transMaxStep->SetValue( SPICE_VALUE( token ).ToSpiceString() ); + + // uic is an optional field + token = tokenizer.GetNextToken(); + + if( token.IsSameAs( wxS( "uic" ) ) ) + m_useInitialConditions->SetValue( true ); + } + else if( token == ".op" ) + { + m_simPages->SetSelection( m_simPages->FindPage( m_pgOP ) ); + } + else if( !empty( m_customTxt ) ) // Custom directives + { + m_simPages->SetSelection( m_simPages->FindPage( m_pgCustom ) ); + } } diff --git a/eeschema/dialogs/dialog_sim_command.h b/eeschema/dialogs/dialog_sim_command.h index b0168986d7..dee7b272cd 100644 --- a/eeschema/dialogs/dialog_sim_command.h +++ b/eeschema/dialogs/dialog_sim_command.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016-2022 CERN - * Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors. * * @author Maciej Suminski * @@ -48,14 +48,10 @@ public: return m_simCommand; } - bool SetSimCommand( const wxString& aCommand ) + void SetSimCommand( const wxString& aCommand ) { - bool res = parseCommand( aCommand ); - - if( res ) - m_simCommand = aCommand; - - return res; + parseCommand( aCommand ); + m_simCommand = aCommand; } int GetSimOptions() const @@ -97,8 +93,6 @@ public: return true; } - int ShowModal() override; - private: enum SCALE_TYPE { @@ -147,9 +141,8 @@ private: * Parse a Spice directive. * * @param aCommand is the directive to be parsed (e.g. ".tran 10n 1000n"). - * @return true if the directive was parsed correctly. */ - bool parseCommand( const wxString& aCommand ); + void parseCommand( const wxString& aCommand ); void onLoadDirectives( wxCommandEvent& event ) override { diff --git a/eeschema/dialogs/dialog_sim_command_base.cpp b/eeschema/dialogs/dialog_sim_command_base.cpp index 60be8cdd20..8d77f5d170 100644 --- a/eeschema/dialogs/dialog_sim_command_base.cpp +++ b/eeschema/dialogs/dialog_sim_command_base.cpp @@ -69,10 +69,7 @@ DIALOG_SIM_COMMAND_BASE::DIALOG_SIM_COMMAND_BASE( wxWindow* parent, wxWindowID i fgSizer1->Add( m_staticText110, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT, 5 ); - bSizer3->Add( fgSizer1, 0, wxEXPAND|wxTOP|wxBOTTOM, 5 ); - - - bSizer3->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer3->Add( fgSizer1, 0, wxEXPAND|wxALL, 5 ); m_pgAC->SetSizer( bSizer3 ); @@ -194,15 +191,15 @@ DIALOG_SIM_COMMAND_BASE::DIALOG_SIM_COMMAND_BASE( wxWindow* parent, wxWindowID i gbSizer1->Add( m_src2DCStepUnit, wxGBPosition( 5, 4 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - bSizer82->Add( gbSizer1, 0, wxTOP|wxBOTTOM, 5 ); + bSizer82->Add( gbSizer1, 0, wxALL, 5 ); - bSizer82->Add( 0, 5, 0, wxEXPAND|wxTOP|wxBOTTOM, 5 ); + bSizer82->Add( 0, 10, 0, wxEXPAND, 5 ); m_swapDCSources = new wxButton( m_pgDC, wxID_ANY, _("Swap sources"), wxDefaultPosition, wxDefaultSize, 0 ); m_swapDCSources->SetMinSize( wxSize( 132,-1 ) ); - bSizer82->Add( m_swapDCSources, 0, wxALL, 5 ); + bSizer82->Add( m_swapDCSources, 0, wxALL, 10 ); m_pgDC->SetSizer( bSizer82 ); @@ -212,25 +209,22 @@ DIALOG_SIM_COMMAND_BASE::DIALOG_SIM_COMMAND_BASE( wxWindow* parent, wxWindowID i m_pgDistortion = new wxPanel( m_simPages, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_simPages->AddPage( m_pgDistortion, _("Distortion"), false ); m_pgNoise = new wxPanel( m_simPages, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - m_pgNoise->Hide(); - wxBoxSizer* bSizer15; bSizer15 = new wxBoxSizer( wxVERTICAL ); - - bSizer15->Add( 0, 0, 1, wxEXPAND, 5 ); - wxFlexGridSizer* fgSizer7; - fgSizer7 = new wxFlexGridSizer( 0, 3, 2, 0 ); + fgSizer7 = new wxFlexGridSizer( 0, 3, 5, 0 ); fgSizer7->SetFlexibleDirection( wxBOTH ); fgSizer7->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); m_staticText14 = new wxStaticText( m_pgNoise, wxID_ANY, _("Measured node:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText14->Wrap( -1 ); - fgSizer7->Add( m_staticText14, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxRIGHT|wxLEFT, 5 ); + fgSizer7->Add( m_staticText14, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); - m_noiseMeas = new wxComboBox( m_pgNoise, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); - fgSizer7->Add( m_noiseMeas, 1, wxTOP|wxALIGN_CENTER_VERTICAL, 5 ); + wxArrayString m_noiseMeasChoices; + m_noiseMeas = new wxChoice( m_pgNoise, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_noiseMeasChoices, 0 ); + m_noiseMeas->SetSelection( 0 ); + fgSizer7->Add( m_noiseMeas, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT|wxEXPAND, 5 ); fgSizer7->Add( 0, 0, 1, wxEXPAND, 5 ); @@ -239,8 +233,10 @@ DIALOG_SIM_COMMAND_BASE::DIALOG_SIM_COMMAND_BASE( wxWindow* parent, wxWindowID i m_staticText15->Wrap( -1 ); fgSizer7->Add( m_staticText15, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); - m_noiseRef = new wxComboBox( m_pgNoise, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); - fgSizer7->Add( m_noiseRef, 1, wxALIGN_CENTER_VERTICAL, 5 ); + wxArrayString m_noiseRefChoices; + m_noiseRef = new wxChoice( m_pgNoise, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_noiseRefChoices, 0 ); + m_noiseRef->SetSelection( 0 ); + fgSizer7->Add( m_noiseRef, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT|wxEXPAND, 5 ); m_staticText23 = new wxStaticText( m_pgNoise, wxID_ANY, _("(optional; default GND)"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText23->Wrap( -1 ); @@ -248,19 +244,18 @@ DIALOG_SIM_COMMAND_BASE::DIALOG_SIM_COMMAND_BASE( wxWindow* parent, wxWindowID i m_staticText16 = new wxStaticText( m_pgNoise, wxID_ANY, _("Noise source:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText16->Wrap( -1 ); - fgSizer7->Add( m_staticText16, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + fgSizer7->Add( m_staticText16, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); - m_noiseSrc = new wxComboBox( m_pgNoise, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); - fgSizer7->Add( m_noiseSrc, 1, wxBOTTOM|wxALIGN_CENTER_VERTICAL, 5 ); + wxArrayString m_noiseSrcChoices; + m_noiseSrc = new wxChoice( m_pgNoise, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_noiseSrcChoices, 0 ); + m_noiseSrc->SetSelection( 0 ); + fgSizer7->Add( m_noiseSrc, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT|wxEXPAND, 5 ); fgSizer7->Add( 0, 0, 1, wxEXPAND, 5 ); - bSizer15->Add( fgSizer7, 0, 0, 5 ); - - - bSizer15->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer15->Add( fgSizer7, 0, wxALL, 5 ); wxBoxSizer* bSizer10; bSizer10 = new wxBoxSizer( wxHORIZONTAL ); @@ -284,6 +279,8 @@ DIALOG_SIM_COMMAND_BASE::DIALOG_SIM_COMMAND_BASE( wxWindow* parent, wxWindowID i fgSizer11->Add( m_staticText11, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxRIGHT|wxLEFT, 5 ); m_noisePointsNumber = new wxTextCtrl( m_pgNoise, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + m_noisePointsNumber->SetMinSize( wxSize( 80,-1 ) ); + fgSizer11->Add( m_noisePointsNumber, 1, wxALIGN_CENTER_VERTICAL|wxTOP, 5 ); @@ -294,6 +291,8 @@ DIALOG_SIM_COMMAND_BASE::DIALOG_SIM_COMMAND_BASE( wxWindow* parent, wxWindowID i fgSizer11->Add( m_staticText21, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); m_noiseFreqStart = new wxTextCtrl( m_pgNoise, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + m_noiseFreqStart->SetMinSize( wxSize( 80,-1 ) ); + fgSizer11->Add( m_noiseFreqStart, 1, wxALIGN_CENTER_VERTICAL, 5 ); m_noiseFreqStartUnits = new wxStaticText( m_pgNoise, wxID_ANY, _("Hz"), wxDefaultPosition, wxDefaultSize, 0 ); @@ -305,6 +304,8 @@ DIALOG_SIM_COMMAND_BASE::DIALOG_SIM_COMMAND_BASE( wxWindow* parent, wxWindowID i fgSizer11->Add( m_staticText31, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); m_noiseFreqStop = new wxTextCtrl( m_pgNoise, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + m_noiseFreqStop->SetMinSize( wxSize( 80,-1 ) ); + fgSizer11->Add( m_noiseFreqStop, 1, wxALIGN_CENTER_VERTICAL|wxBOTTOM, 5 ); m_noiseFreqStopUnits = new wxStaticText( m_pgNoise, wxID_ANY, _("Hz"), wxDefaultPosition, wxDefaultSize, 0 ); @@ -315,19 +316,13 @@ DIALOG_SIM_COMMAND_BASE::DIALOG_SIM_COMMAND_BASE( wxWindow* parent, wxWindowID i bSizer10->Add( fgSizer11, 0, wxALIGN_BOTTOM|wxBOTTOM|wxLEFT, 4 ); - bSizer15->Add( bSizer10, 0, wxEXPAND, 5 ); - - - bSizer15->Add( 0, 0, 1, wxEXPAND, 5 ); - - - bSizer15->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer15->Add( bSizer10, 0, wxEXPAND|wxTOP, 5 ); m_pgNoise->SetSizer( bSizer15 ); m_pgNoise->Layout(); bSizer15->Fit( m_pgNoise ); - m_simPages->AddPage( m_pgNoise, _("Noise"), false ); + m_simPages->AddPage( m_pgNoise, _("Noise"), true ); m_pgOP = new wxPanel( m_simPages, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); wxBoxSizer* bSizer8; bSizer8 = new wxBoxSizer( wxVERTICAL ); @@ -430,13 +425,13 @@ DIALOG_SIM_COMMAND_BASE::DIALOG_SIM_COMMAND_BASE( wxWindow* parent, wxWindowID i gbSizer2->Add( m_useInitialConditionsHelp, wxGBPosition( 4, 3 ), wxGBSpan( 1, 1 ), wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - bSizer81->Add( gbSizer2, 1, wxEXPAND, 5 ); + bSizer81->Add( gbSizer2, 1, wxEXPAND|wxRIGHT|wxLEFT, 5 ); m_pgTransient->SetSizer( bSizer81 ); m_pgTransient->Layout(); bSizer81->Fit( m_pgTransient ); - m_simPages->AddPage( m_pgTransient, _("Transient"), true ); + m_simPages->AddPage( m_pgTransient, _("Transient"), false ); m_pgCustom = new wxPanel( m_simPages, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); wxBoxSizer* bSizer2; bSizer2 = new wxBoxSizer( wxVERTICAL ); diff --git a/eeschema/dialogs/dialog_sim_command_base.fbp b/eeschema/dialogs/dialog_sim_command_base.fbp index 500d34e60f..11f6b9a328 100644 --- a/eeschema/dialogs/dialog_sim_command_base.fbp +++ b/eeschema/dialogs/dialog_sim_command_base.fbp @@ -247,7 +247,7 @@ 5 - wxEXPAND|wxTOP|wxBOTTOM + wxEXPAND|wxALL 0 3 @@ -770,16 +770,6 @@ - - 5 - wxEXPAND - 1 - - 0 - protected - 0 - - @@ -845,7 +835,7 @@ none 5 - wxTOP|wxBOTTOM + wxALL 0 @@ -2370,16 +2360,16 @@ 5 - wxEXPAND|wxTOP|wxBOTTOM + wxEXPAND 0 - 5 + 10 protected 0 - 5 + 10 wxALL 0 @@ -2515,7 +2505,7 @@ Noise - 0 + 1 1 1 @@ -2542,7 +2532,7 @@ 1 0 - 1 + 0 wxID_ANY 0 @@ -2572,19 +2562,9 @@ bSizer15 wxVERTICAL none - - 5 - wxEXPAND - 1 - - 0 - protected - 0 - - 5 - + wxALL 0 3 @@ -2597,10 +2577,10 @@ wxFLEX_GROWMODE_SPECIFIED none 0 - 2 + 5 5 - wxALIGN_CENTER_VERTICAL|wxTOP|wxRIGHT|wxLEFT + wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT 0 1 @@ -2659,11 +2639,11 @@ -1 - + 5 - wxTOP|wxALIGN_CENTER_VERTICAL - 1 - + wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT|wxEXPAND + 0 + 1 1 1 @@ -2707,18 +2687,17 @@ 1 Resizable - -1 + 0 1 - + ; ; forward_declare 0 wxFILTER_NONE wxDefaultValidator - @@ -2795,11 +2774,11 @@ -1 - + 5 - wxALIGN_CENTER_VERTICAL - 1 - + wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT|wxEXPAND + 0 + 1 1 1 @@ -2843,18 +2822,17 @@ 1 Resizable - -1 + 0 1 - + ; ; forward_declare 0 wxFILTER_NONE wxDefaultValidator - @@ -2923,7 +2901,7 @@ 5 - wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT + wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT 0 1 @@ -2982,11 +2960,11 @@ -1 - + 5 - wxBOTTOM|wxALIGN_CENTER_VERTICAL - 1 - + wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT|wxEXPAND + 0 + 1 1 1 @@ -3030,18 +3008,17 @@ 1 Resizable - -1 + 0 1 - + ; ; forward_declare 0 wxFILTER_NONE wxDefaultValidator - @@ -3059,19 +3036,9 @@ - - 5 - wxEXPAND - 1 - - 0 - protected - 0 - - 5 - wxEXPAND + wxEXPAND|wxTOP 0 @@ -3269,7 +3236,7 @@ 0 0 - + 80,-1 1 m_noisePointsNumber 1 @@ -3404,7 +3371,7 @@ 0 0 - + 80,-1 1 m_noiseFreqStart 1 @@ -3590,7 +3557,7 @@ 0 0 - + 80,-1 1 m_noiseFreqStop 1 @@ -3681,26 +3648,6 @@ - - 5 - wxEXPAND - 1 - - 0 - protected - 0 - - - - 5 - wxEXPAND - 1 - - 0 - protected - 0 - - @@ -4022,7 +3969,7 @@ Transient - 1 + 0 1 1 @@ -4081,7 +4028,7 @@ none 5 - wxEXPAND + wxEXPAND|wxRIGHT|wxLEFT 1 diff --git a/eeschema/dialogs/dialog_sim_command_base.h b/eeschema/dialogs/dialog_sim_command_base.h index 669bc3b1da..1e683cea1a 100644 --- a/eeschema/dialogs/dialog_sim_command_base.h +++ b/eeschema/dialogs/dialog_sim_command_base.h @@ -29,7 +29,6 @@ #include #include #include -#include #include #include @@ -83,12 +82,12 @@ class DIALOG_SIM_COMMAND_BASE : public DIALOG_SHIM wxPanel* m_pgDistortion; wxPanel* m_pgNoise; wxStaticText* m_staticText14; - wxComboBox* m_noiseMeas; + wxChoice* m_noiseMeas; wxStaticText* m_staticText15; - wxComboBox* m_noiseRef; + wxChoice* m_noiseRef; wxStaticText* m_staticText23; wxStaticText* m_staticText16; - wxComboBox* m_noiseSrc; + wxChoice* m_noiseSrc; wxRadioBox* m_noiseScale; wxStaticText* m_staticText11; wxTextCtrl* m_noisePointsNumber; diff --git a/eeschema/sim/ngspice_circuit_model.cpp b/eeschema/sim/ngspice_circuit_model.cpp index 0d0eb5ea7a..b1f91d2112 100644 --- a/eeschema/sim/ngspice_circuit_model.cpp +++ b/eeschema/sim/ngspice_circuit_model.cpp @@ -130,24 +130,75 @@ bool NGSPICE_CIRCUIT_MODEL::ParseDCCommand( const wxString& aCmd, SPICE_DC_PARAM wxString cmd = aCmd.Mid( 3 ); wxStringTokenizer tokens( cmd, wxS( " \t" ), wxTOKEN_STRTOK ); - size_t num = tokens.CountTokens(); - - if( num != 4 && num != 8 ) - return false; - aSource1->m_source = tokens.GetNextToken(); aSource1->m_vstart = SPICE_VALUE( tokens.GetNextToken() ); aSource1->m_vend = SPICE_VALUE( tokens.GetNextToken() ); aSource1->m_vincrement = SPICE_VALUE( tokens.GetNextToken() ); - if( num == 8 ) + aSource2->m_source = tokens.GetNextToken(); + aSource2->m_vstart = SPICE_VALUE( tokens.GetNextToken() ); + aSource2->m_vend = SPICE_VALUE( tokens.GetNextToken() ); + aSource2->m_vincrement = SPICE_VALUE( tokens.GetNextToken() ); + + return true; +} + + +bool NGSPICE_CIRCUIT_MODEL::ParseNoiseCommand( const wxString& aCmd, wxString* aOutput, + wxString* aRef, wxString* aSource, wxString* aScale, + SPICE_VALUE* aPts, SPICE_VALUE* aFStart, + SPICE_VALUE* aFStop ) +{ + if( !aCmd.Lower().StartsWith( wxS( ".noise" ) ) ) + return false; + + wxString cmd = aCmd.Mid( 6 ); + + cmd.Trim( false ); + + if( !cmd.Lower().StartsWith( wxS( "v(" ) ) ) + return false; + + cmd = cmd.Mid( 2 ); + + wxString function = cmd.Before( ')' ); + wxString params = cmd.After( ')' ); + + wxStringTokenizer func_tokens( function, wxS( " ,\t" ), wxTOKEN_STRTOK ); + + *aOutput = func_tokens.GetNextToken(); + *aRef = func_tokens.GetNextToken(); + + wxStringTokenizer tokens( params, wxS( " \t" ), wxTOKEN_STRTOK ); + wxString token = tokens.GetNextToken(); + + if( !token.IsEmpty() ) { - aSource2->m_source = tokens.GetNextToken(); - aSource2->m_vstart = SPICE_VALUE( tokens.GetNextToken() ); - aSource2->m_vend = SPICE_VALUE( tokens.GetNextToken() ); - aSource2->m_vincrement = SPICE_VALUE( tokens.GetNextToken() ); + *aSource = token; + token = tokens.GetNextToken(); } + if( token.Lower() == "dec" || token.Lower() == "oct" || token.Lower() == "lin" ) + { + *aScale = token; + token = tokens.GetNextToken(); + } + + if( !token.IsEmpty() ) + { + *aPts = token; + token = tokens.GetNextToken(); + } + + if( !token.IsEmpty() ) + { + *aFStart = SPICE_VALUE( token ); + token = tokens.GetNextToken(); + } + + if( !token.IsEmpty() ) + *aFStop = SPICE_VALUE( token ); + return true; } diff --git a/eeschema/sim/ngspice_circuit_model.h b/eeschema/sim/ngspice_circuit_model.h index f5532ac229..a58d1e1ada 100644 --- a/eeschema/sim/ngspice_circuit_model.h +++ b/eeschema/sim/ngspice_circuit_model.h @@ -127,6 +127,10 @@ public: bool ParseDCCommand( const wxString& aCmd, SPICE_DC_PARAMS* aSource1, SPICE_DC_PARAMS* aSource2 ); + bool ParseNoiseCommand( const wxString& aCmd, wxString* aOutput, wxString* aRef, + wxString* aSource, wxString* aScale, SPICE_VALUE* aPts, + SPICE_VALUE* aFStart, SPICE_VALUE* aFStop ); + /** * Determine if a directive is a simulation command. */ diff --git a/eeschema/sim/sim_model.cpp b/eeschema/sim/sim_model.cpp index 6e4cbc2fc5..62e68a1463 100644 --- a/eeschema/sim/sim_model.cpp +++ b/eeschema/sim/sim_model.cpp @@ -1351,8 +1351,17 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector* aFields, { if( aModelParams->IsEmpty() && !value.IsEmpty() ) { + wxString param = "dc"; + if( value.StartsWith( wxT( "DC " ) ) ) + { value = value.Right( value.Length() - 3 ); + } + else if( value.StartsWith( wxT( "AC " ) ) ) + { + value = value.Right( value.Length() - 3 ); + param = "ac"; + } wxRegEx sourceVal( wxT( "^" "([0-9\\,\\. ]+)" @@ -1373,13 +1382,15 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector* aFields, if( valueMantissa.Contains( wxT( "." ) ) || valueFraction.IsEmpty() ) { - aModelParams->Printf( wxT( "dc=\"%s%s\"" ), + aModelParams->Printf( wxT( "%s=\"%s%s\"" ), + param, valueMantissa, convertNotation( valueExponent ) ); } else { - aModelParams->Printf( wxT( "dc=\"%s.%s%s\"" ), + aModelParams->Printf( wxT( "%s=\"%s.%s%s\"" ), + param, valueMantissa, valueFraction, convertNotation( valueExponent ) ); @@ -1387,7 +1398,7 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector* aFields, } else { - aModelParams->Printf( wxT( "dc=\"%s\"" ), value ); + aModelParams->Printf( wxT( "%s=\"%s\"" ), param, value ); } } @@ -1719,16 +1730,20 @@ void SIM_MODEL::MigrateSimModel( T_symbol& aSymbol, const PROJECT* aProject ) pinMapInfo.m_Text = generateDefaultPinMapFromSymbol( sourcePins ); } } - else if( ( spiceDeviceType == "R" || spiceDeviceType == "L" || spiceDeviceType == "C" ) + else if( ( spiceDeviceType == wxS( "R" ) + || spiceDeviceType == wxS( "L" ) + || spiceDeviceType == wxS( "C" ) + || spiceDeviceType == wxS( "V" ) + || spiceDeviceType == wxS( "I" ) ) && prefix.StartsWith( spiceDeviceType ) && modelFromValueField ) { inferredModel = true; } - else + else if( spiceDeviceType == wxS( "V" ) || spiceDeviceType == wxS( "I" ) ) { - // See if we have a SPICE model such as "sin(0 1 60)" or "sin 0 1 60" that can be handled - // by a built-in SIM_MODEL. + // See if we have a SPICE time-dependent function such as "sin(0 1 60)" or "sin 0 1 60" + // that can be handled by a built-in SIM_MODEL_SOURCE. wxStringTokenizer tokenizer( spiceModel, wxT( "() " ), wxTOKEN_STRTOK ); diff --git a/eeschema/sim/sim_model_serializer.cpp b/eeschema/sim/sim_model_serializer.cpp index c51b2b53bc..0a93d3e195 100644 --- a/eeschema/sim/sim_model_serializer.cpp +++ b/eeschema/sim/sim_model_serializer.cpp @@ -252,7 +252,7 @@ bool SIM_MODEL_SERIALIZER::ParseParams( const std::string& aParams ) } } - return !m_model.HasPrimaryValue() || isPrimaryValueSet; + return !m_model.HasPrimaryValue() || m_model.HasAutofill() || isPrimaryValueSet; } diff --git a/eeschema/sim/spice_value.cpp b/eeschema/sim/spice_value.cpp index 7b6e4279a5..6dcac378b8 100644 --- a/eeschema/sim/spice_value.cpp +++ b/eeschema/sim/spice_value.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include @@ -64,38 +63,37 @@ void SPICE_VALUE_FORMAT::UpdateUnits( const wxString& aUnits ) } -SPICE_VALUE::SPICE_VALUE( const wxString& aString ) +SPICE_VALUE::SPICE_VALUE( const wxString& aString ) : + m_base( 0.0 ), + m_prefix( PFX_NONE ), + m_spiceStr( false ) { - char buf[8] = { 0, }; - if( aString.IsEmpty() ) - throw KI_PARAM_ERROR( _( "Spice value cannot be empty" ) ); + return; - LOCALE_IO dummy; // All numeric values should be in "C" locale(decimal separator = .) + char units[8] = { 0, }; + LOCALE_IO dummy; // Numeric values must be in "C" locale ('.' decimal separator) - if( sscanf( (const char*) aString.c_str(), "%lf%7s", &m_base, buf ) == 0 ) - throw KI_PARAM_ERROR( _( "Invalid Spice value string" ) ); + sscanf( (const char*) aString.c_str(), "%lf%7s", &m_base, units ); - if( *buf == 0 ) + if( *units == 0 ) { - m_prefix = PFX_NONE; - m_spiceStr = false; Normalize(); return; } m_spiceStr = true; - for( char* bufPtr = buf; *bufPtr; ++bufPtr ) + for( char* bufPtr = units; *bufPtr; ++bufPtr ) *bufPtr = tolower( *bufPtr ); - if( !strcmp( buf, "meg" ) ) + if( strcmp( units, "meg" ) == 0 ) { m_prefix = PFX_MEGA; } else { - switch( buf[0] ) + switch( units[0] ) { case 'f': m_prefix = PFX_FEMTO; break; case 'p': m_prefix = PFX_PICO; break; @@ -105,9 +103,7 @@ SPICE_VALUE::SPICE_VALUE( const wxString& aString ) case 'k': m_prefix = PFX_KILO; break; case 'g': m_prefix = PFX_GIGA; break; case 't': m_prefix = PFX_TERA; break; - - default: - throw KI_PARAM_ERROR( _( "Invalid unit prefix" ) ); + default: m_prefix = PFX_NONE; break; } }