You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

461 lines
14 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2016 CERN
  5. * @author Maciej Suminski <maciej.suminski@cern.ch>
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 3
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * https://www.gnu.org/licenses/gpl-3.0.html
  20. * or you may search the http://www.gnu.org website for the version 3 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include "dialog_sim_settings.h"
  25. #include <sim/netlist_exporter_pspice_sim.h>
  26. #include <confirm.h>
  27. #include <wx/tokenzr.h>
  28. /// @todo ngspice offers more types of analysis,
  29. //so there are a few tabs missing (e.g. pole-zero, distortion, sensitivity)
  30. // Helper function to shorten conditions
  31. static bool empty( const wxTextEntryBase* aCtrl )
  32. {
  33. return aCtrl->GetValue().IsEmpty();
  34. }
  35. DIALOG_SIM_SETTINGS::DIALOG_SIM_SETTINGS( wxWindow* aParent )
  36. : DIALOG_SIM_SETTINGS_BASE( aParent ), m_exporter( nullptr ), m_spiceEmptyValidator( true )
  37. {
  38. m_posIntValidator.SetMin( 1 );
  39. m_acPointsNumber->SetValidator( m_posIntValidator );
  40. m_acFreqStart->SetValidator( m_spiceValidator );
  41. m_acFreqStop->SetValidator( m_spiceValidator );
  42. m_dcStart1->SetValidator( m_spiceValidator );
  43. m_dcStop1->SetValidator( m_spiceValidator );
  44. m_dcIncr1->SetValidator( m_spiceValidator );
  45. m_dcStart2->SetValidator( m_spiceValidator );
  46. m_dcStop2->SetValidator( m_spiceValidator );
  47. m_dcIncr2->SetValidator( m_spiceValidator );
  48. m_noisePointsNumber->SetValidator( m_posIntValidator );
  49. m_noiseFreqStart->SetValidator( m_spiceValidator );
  50. m_noiseFreqStop->SetValidator( m_spiceValidator );
  51. m_transStep->SetValidator( m_spiceValidator );
  52. m_transFinal->SetValidator( m_spiceValidator );
  53. m_transInitial->SetValidator( m_spiceEmptyValidator );
  54. // Hide pages that aren't fully implemented yet
  55. // wxPanel::Hide() isn't enough on some platforms
  56. m_simPages->RemovePage( m_simPages->FindPage( m_pgDistortion ) );
  57. m_simPages->RemovePage( m_simPages->FindPage( m_pgNoise ) );
  58. m_simPages->RemovePage( m_simPages->FindPage( m_pgOP ) );
  59. m_simPages->RemovePage( m_simPages->FindPage( m_pgPoleZero ) );
  60. m_simPages->RemovePage( m_simPages->FindPage( m_pgSensitivity ) );
  61. m_simPages->RemovePage( m_simPages->FindPage( m_pgTransferFunction ) );
  62. m_sdbSizerOK->SetDefault();
  63. updateNetlistOpts();
  64. }
  65. bool DIALOG_SIM_SETTINGS::TransferDataFromWindow()
  66. {
  67. if( !wxDialog::TransferDataFromWindow() )
  68. return false;
  69. wxWindow* page = m_simPages->GetCurrentPage();
  70. // AC analysis
  71. if( page == m_pgAC )
  72. {
  73. if( !m_pgAC->Validate() )
  74. return false;
  75. m_simCommand = wxString::Format( ".ac %s %s %s %s",
  76. scaleToString( m_acScale->GetSelection() ),
  77. m_acPointsNumber->GetValue(),
  78. SPICE_VALUE( m_acFreqStart->GetValue() ).ToSpiceString(),
  79. SPICE_VALUE( m_acFreqStop->GetValue() ).ToSpiceString() );
  80. }
  81. // DC transfer analysis
  82. else if( page == m_pgDC )
  83. {
  84. // At least one source has to be enabled
  85. if( !m_dcEnable1->IsChecked() && !m_dcEnable2->IsChecked() )
  86. {
  87. DisplayError( this, _( "You need to enable at least one source" ) );
  88. return false;
  89. }
  90. wxString simCmd = wxString( ".dc " );
  91. if( m_dcEnable1->IsChecked() )
  92. {
  93. if( empty( m_dcSource1 ) )
  94. {
  95. DisplayError( this, _( "You need to select DC source (sweep 1)" ) );
  96. return false;
  97. }
  98. /// @todo for some reason it does not trigger the assigned SPICE_VALIDATOR,
  99. // hence try..catch below
  100. if( !m_dcStart1->Validate() || !m_dcStop1->Validate() || !m_dcIncr1->Validate() )
  101. return false;
  102. try
  103. {
  104. wxString dcSource = m_exporter->GetSpiceDevice( m_dcSource1->GetValue() );
  105. simCmd += wxString::Format( "%s %s %s %s",
  106. dcSource,
  107. SPICE_VALUE( m_dcStart1->GetValue() ).ToSpiceString(),
  108. SPICE_VALUE( m_dcStop1->GetValue() ).ToSpiceString(),
  109. SPICE_VALUE( m_dcIncr1->GetValue() ).ToSpiceString() );
  110. }
  111. catch( std::exception& e )
  112. {
  113. DisplayError( this, e.what() );
  114. return false;
  115. }
  116. catch( const KI_PARAM_ERROR& e )
  117. {
  118. DisplayError( this, e.What() );
  119. return false;
  120. }
  121. catch( ... )
  122. {
  123. return false;
  124. }
  125. }
  126. if( m_dcEnable2->IsChecked() )
  127. {
  128. if( empty( m_dcSource2 ) )
  129. {
  130. DisplayError( this, _( "You need to select DC source (sweep 2)" ) );
  131. return false;
  132. }
  133. if( m_dcEnable1->IsChecked() && m_dcSource1->GetValue() == m_dcSource2->GetValue() )
  134. {
  135. DisplayError( this, _( "Source 1 and Source 2 must be different" ) );
  136. return false;
  137. }
  138. /// @todo for some reason it does not trigger the assigned SPICE_VALIDATOR,
  139. // hence try..catch below
  140. if( !m_dcStart2->Validate() || !m_dcStop2->Validate() || !m_dcIncr2->Validate() )
  141. return false;
  142. try
  143. {
  144. wxString dcSource = m_exporter->GetSpiceDevice( m_dcSource2->GetValue() );
  145. if( m_dcEnable1->IsChecked() )
  146. simCmd += " ";
  147. simCmd += wxString::Format( "%s %s %s %s",
  148. dcSource,
  149. SPICE_VALUE( m_dcStart2->GetValue() ).ToSpiceString(),
  150. SPICE_VALUE( m_dcStop2->GetValue() ).ToSpiceString(),
  151. SPICE_VALUE( m_dcIncr2->GetValue() ).ToSpiceString() );
  152. }
  153. catch( std::exception& e )
  154. {
  155. DisplayError( this, e.what() );
  156. return false;
  157. }
  158. catch( const KI_PARAM_ERROR& e )
  159. {
  160. DisplayError( this, e.What() );
  161. return false;
  162. }
  163. catch( ... )
  164. {
  165. return false;
  166. }
  167. }
  168. m_simCommand = simCmd;
  169. }
  170. // Noise analysis
  171. else if( page == m_pgNoise )
  172. {
  173. const NETLIST_EXPORTER_PSPICE::NET_INDEX_MAP& netMap = m_exporter->GetNetIndexMap();
  174. if( empty( m_noiseMeas ) || empty( m_noiseSrc ) || empty( m_noisePointsNumber )
  175. || empty( m_noiseFreqStart ) || empty( m_noiseFreqStop ) )
  176. return false;
  177. wxString ref = empty( m_noiseRef )
  178. ? wxString() : wxString::Format( ", %d", netMap.at( m_noiseRef->GetValue() ) );
  179. wxString noiseSource = m_exporter->GetSpiceDevice( m_noiseSrc->GetValue() );
  180. // Add voltage source prefix if needed
  181. if( noiseSource[0] != 'v' && noiseSource[0] != 'V' )
  182. noiseSource += 'v' + noiseSource;
  183. m_simCommand = wxString::Format( ".noise v(%d%s) %s %s %s %s %s",
  184. netMap.at( m_noiseMeas->GetValue() ), ref,
  185. noiseSource, scaleToString( m_noiseScale->GetSelection() ),
  186. m_noisePointsNumber->GetValue(),
  187. SPICE_VALUE( m_noiseFreqStart->GetValue() ).ToSpiceString(),
  188. SPICE_VALUE( m_noiseFreqStop->GetValue() ).ToSpiceString() );
  189. }
  190. // DC operating point analysis
  191. else if( page == m_pgOP )
  192. {
  193. m_simCommand = wxString( ".op" );
  194. }
  195. // Transient analysis
  196. else if( page == m_pgTransient )
  197. {
  198. if( !m_pgTransient->Validate() )
  199. return false;
  200. wxString initial = empty( m_transInitial )
  201. ? "" : SPICE_VALUE( m_transInitial->GetValue() ).ToSpiceString();
  202. m_simCommand = wxString::Format( ".tran %s %s %s",
  203. SPICE_VALUE( m_transStep->GetValue() ).ToSpiceString(),
  204. SPICE_VALUE( m_transFinal->GetValue() ).ToSpiceString(),
  205. initial );
  206. }
  207. // Custom directives
  208. else if( page == m_pgCustom )
  209. {
  210. m_simCommand = m_customTxt->GetValue();
  211. }
  212. else
  213. {
  214. return false;
  215. }
  216. m_simCommand.Trim();
  217. updateNetlistOpts();
  218. return true;
  219. }
  220. bool DIALOG_SIM_SETTINGS::TransferDataToWindow()
  221. {
  222. /// @todo one day it could interpret the sim command and fill out appropriate fields..
  223. if( empty( m_customTxt ) )
  224. loadDirectives();
  225. if( m_simCommand.IsEmpty() && !empty( m_customTxt ) )
  226. return parseCommand( m_customTxt->GetValue() );
  227. return true;
  228. }
  229. int DIALOG_SIM_SETTINGS::ShowModal()
  230. {
  231. // Fill out comboboxes that allows one to select nets
  232. // Map comoboxes to their current values
  233. std::map<wxComboBox*, wxString> cmbNet = {
  234. { m_noiseMeas, m_noiseMeas->GetStringSelection() },
  235. { m_noiseRef, m_noiseRef->GetStringSelection() }
  236. };
  237. for( auto c : cmbNet )
  238. c.first->Clear();
  239. for( const auto& net : m_exporter->GetNetIndexMap() )
  240. {
  241. for( auto c : cmbNet )
  242. c.first->Append( net.first );
  243. }
  244. // Try to restore the previous selection, if possible
  245. for( auto c : cmbNet )
  246. {
  247. int idx = c.first->FindString( c.second );
  248. if( idx != wxNOT_FOUND )
  249. c.first->SetSelection( idx );
  250. }
  251. // Fill out comboboxes that allows one to select power sources
  252. std::map<wxComboBox*, wxString> cmbSrc = {
  253. { m_dcSource1, m_dcSource1->GetStringSelection() },
  254. { m_dcSource2, m_dcSource2->GetStringSelection() },
  255. { m_noiseSrc, m_noiseSrc->GetStringSelection() },
  256. };
  257. for( auto c : cmbSrc )
  258. c.first->Clear();
  259. for( const auto& item : m_exporter->GetSpiceItems() )
  260. {
  261. if( item.m_primitive == 'V' )
  262. {
  263. for( auto c : cmbSrc )
  264. c.first->Append( item.m_refName );
  265. }
  266. }
  267. // Try to restore the previous selection, if possible
  268. for( auto c : cmbSrc )
  269. {
  270. int idx = c.first->FindString( c.second );
  271. if( idx != wxNOT_FOUND )
  272. c.first->SetSelection( idx );
  273. }
  274. return DIALOG_SIM_SETTINGS_BASE::ShowModal();
  275. }
  276. bool DIALOG_SIM_SETTINGS::parseCommand( const wxString& aCommand )
  277. {
  278. if( aCommand.IsEmpty() )
  279. return false;
  280. wxStringTokenizer tokenizer( aCommand, " " );
  281. wxString tkn = tokenizer.GetNextToken().Lower();
  282. try {
  283. if( tkn == ".ac" )
  284. {
  285. m_simPages->SetSelection( m_simPages->FindPage( m_pgAC ) );
  286. tkn = tokenizer.GetNextToken().Lower();
  287. if( tkn == "dec" )
  288. m_acScale->SetSelection( 0 );
  289. if( tkn == "oct" )
  290. m_acScale->SetSelection( 1 );
  291. if( tkn == "lin" )
  292. m_acScale->SetSelection( 2 );
  293. else
  294. return false;
  295. // If the fields below are empty, it will be caught by the exception handler
  296. m_acPointsNumber->SetValue( tokenizer.GetNextToken() );
  297. m_acFreqStart->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
  298. m_acFreqStop->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
  299. }
  300. else if( tkn == ".dc" )
  301. {
  302. m_simPages->SetSelection( m_simPages->FindPage( m_pgDC ) );
  303. tkn = tokenizer.GetNextToken();
  304. if( !tkn.IsEmpty() )
  305. {
  306. m_dcSource1->SetValue( tkn );
  307. m_dcStart1->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
  308. m_dcStop1->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
  309. m_dcIncr1->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
  310. // Check the 'Enabled' field, if all values are filled
  311. m_dcEnable1->SetValue( !empty( m_dcSource1 ) && !empty( m_dcStart1 )
  312. && !empty( m_dcStop1 ) && !empty( m_dcIncr1 ) );
  313. }
  314. tkn = tokenizer.GetNextToken();
  315. if( !tkn.IsEmpty() )
  316. {
  317. m_dcSource2->SetValue( tkn );
  318. m_dcStart2->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
  319. m_dcStop2->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
  320. m_dcIncr2->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
  321. // Check the 'Enabled' field, if all values are filled
  322. m_dcEnable2->SetValue( !empty( m_dcSource2 ) && !empty( m_dcStart2 )
  323. && !empty( m_dcStop2 ) && !empty( m_dcIncr2 ) );
  324. }
  325. // Check if the directive is complete
  326. if( !m_dcEnable1->IsChecked() || !m_dcEnable2->IsChecked() )
  327. return false;
  328. }
  329. else if( tkn == ".tran" )
  330. {
  331. m_simPages->SetSelection( m_simPages->FindPage( m_pgTransient ) );
  332. // If the fields below are empty, it will be caught by the exception handler
  333. m_transStep->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
  334. m_transFinal->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
  335. // Initial time is an optional field
  336. tkn = tokenizer.GetNextToken();
  337. if( !tkn.IsEmpty() )
  338. m_transInitial->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
  339. }
  340. // Custom directives
  341. else if( !empty( m_customTxt ) )
  342. {
  343. m_simPages->SetSelection( m_simPages->FindPage( m_pgCustom ) );
  344. }
  345. }
  346. catch( ... )
  347. {
  348. // Nothing really bad has happened
  349. return false;
  350. }
  351. return true;
  352. }
  353. void DIALOG_SIM_SETTINGS::loadDirectives()
  354. {
  355. if( m_exporter )
  356. m_customTxt->SetValue( m_exporter->GetSheetSimCommand() );
  357. }
  358. void DIALOG_SIM_SETTINGS::updateNetlistOpts()
  359. {
  360. m_netlistOpts = NET_ALL_FLAGS;
  361. if( !m_fixPassiveVals->IsChecked() )
  362. m_netlistOpts &= ~NET_ADJUST_PASSIVE_VALS;
  363. if( !m_fixIncludePaths->IsChecked() )
  364. m_netlistOpts &= ~NET_ADJUST_INCLUDE_PATHS;
  365. }