
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2019 Matthias Koefferlein

  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 2 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, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

#include "dbNetlistWriter.h"
#include "dbNetlistSpiceWriter.h"
#include "dbNetlist.h"
#include "dbNetlistDeviceClasses.h"
#include "dbLayoutToNetlist.h"

#include "tlUnitTest.h"
#include "tlStream.h"
#include "tlFileUtils.h"

static void compare_netlists (tl::TestBase *_this, const std::string &path, const std::string &au_path)
{
  tl::InputStream is (path);
  tl::InputStream is_au (au_path);

  std::string netlist = is.read_all ();
  std::string netlist_au = is_au.read_all ();

  //  normalize "1.0e-005" to "1.0e-05" for compatibility
  netlist = tl::replaced (netlist, "\r\n", "\n");   //  for Windows
  netlist = tl::replaced (netlist, "e-00", "e-0");
  netlist = tl::replaced (netlist, "e-0", "e-");
  netlist_au = tl::replaced (netlist_au, "\r\n", "\n");   //  for Windows
  netlist_au = tl::replaced (netlist_au, "e-00", "e-0");
  netlist_au = tl::replaced (netlist_au, "e-0", "e-");

  if (netlist != netlist_au) {
    _this->raise (tl::sprintf ("Compare failed - see\n  actual: %s\n  golden: %s",
                               tl::absolute_file_path (path),
                               tl::absolute_file_path (au_path)));
  }
}

TEST(1_WriterResistorDevices)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  rcls->set_name ("RCLS");

  nl.add_device_class (rcls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);

  db::Device *rdev1 = new db::Device (rcls);
  rdev1->set_parameter_value (db::DeviceClassResistor::param_id_R, 1.7);
  db::Device *rdev2 = new db::Device (rcls);
  rdev2->set_parameter_value (db::DeviceClassResistor::param_id_R, 42e-6);
  circuit1->add_device (rdev1);
  circuit1->add_device (rdev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);

  rdev1->connect_terminal (rdev1->device_class ()->terminal_id_for_name ("A"), n1);
  rdev1->connect_terminal (rdev1->device_class ()->terminal_id_for_name ("B"), n3);
  rdev2->connect_terminal (rdev2->device_class ()->terminal_id_for_name ("A"), n3);
  rdev2->connect_terminal (rdev2->device_class ()->terminal_id_for_name ("B"), n2);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter1.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter1_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(1_WriterResistorDevicesWithBulk)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistorWithBulk ();
  rcls->set_name ("RCLS");

  nl.add_device_class (rcls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);

  db::Device *rdev1 = new db::Device (rcls);
  rdev1->set_parameter_value (db::DeviceClassResistor::param_id_R, 1.7);
  db::Device *rdev2 = new db::Device (rcls);
  rdev2->set_parameter_value (db::DeviceClassResistor::param_id_R, 42e-6);
  circuit1->add_device (rdev1);
  circuit1->add_device (rdev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);

  rdev1->connect_terminal (rdev1->device_class ()->terminal_id_for_name ("A"), n1);
  rdev1->connect_terminal (rdev1->device_class ()->terminal_id_for_name ("B"), n3);
  rdev1->connect_terminal (rdev1->device_class ()->terminal_id_for_name ("W"), n3);
  rdev2->connect_terminal (rdev2->device_class ()->terminal_id_for_name ("A"), n3);
  rdev2->connect_terminal (rdev2->device_class ()->terminal_id_for_name ("B"), n2);
  rdev2->connect_terminal (rdev2->device_class ()->terminal_id_for_name ("W"), n3);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter1.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter1_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(2_WriterCapacitorDevices)
{
  db::Netlist nl;

  db::DeviceClass *ccls = new db::DeviceClassCapacitor ();

  ccls->set_name ("CCLS");

  nl.add_device_class (ccls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);

  db::Device *cdev1 = new db::Device (ccls);
  cdev1->set_parameter_value (db::DeviceClassCapacitor::param_id_C, 1.7e-12);
  db::Device *cdev2 = new db::Device (ccls);
  cdev2->set_parameter_value (db::DeviceClassCapacitor::param_id_C, 42e-15);
  circuit1->add_device (cdev1);
  circuit1->add_device (cdev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);

  cdev1->connect_terminal (cdev1->device_class ()->terminal_id_for_name ("A"), n1);
  cdev1->connect_terminal (cdev1->device_class ()->terminal_id_for_name ("B"), n3);
  cdev2->connect_terminal (cdev2->device_class ()->terminal_id_for_name ("A"), n3);
  cdev2->connect_terminal (cdev2->device_class ()->terminal_id_for_name ("B"), n2);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter2.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter2_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(2_WriterCapacitorDevicesWithBulk)
{
  db::Netlist nl;

  db::DeviceClass *ccls = new db::DeviceClassCapacitorWithBulk ();

  ccls->set_name ("CCLS");

  nl.add_device_class (ccls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);

  db::Device *cdev1 = new db::Device (ccls);
  cdev1->set_parameter_value (db::DeviceClassCapacitor::param_id_C, 1.7e-12);
  db::Device *cdev2 = new db::Device (ccls);
  cdev2->set_parameter_value (db::DeviceClassCapacitor::param_id_C, 42e-15);
  circuit1->add_device (cdev1);
  circuit1->add_device (cdev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);

  cdev1->connect_terminal (cdev1->device_class ()->terminal_id_for_name ("A"), n1);
  cdev1->connect_terminal (cdev1->device_class ()->terminal_id_for_name ("B"), n3);
  cdev1->connect_terminal (cdev1->device_class ()->terminal_id_for_name ("W"), n3);
  cdev2->connect_terminal (cdev2->device_class ()->terminal_id_for_name ("A"), n3);
  cdev2->connect_terminal (cdev2->device_class ()->terminal_id_for_name ("B"), n2);
  cdev2->connect_terminal (cdev2->device_class ()->terminal_id_for_name ("W"), n3);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter2.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter2_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(3_WriterInductorDevices)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  db::DeviceClass *ccls = new db::DeviceClassCapacitor ();
  db::DeviceClass *lcls = new db::DeviceClassInductor ();
  db::DeviceClass *dcls = new db::DeviceClassDiode ();
  db::DeviceClass *m3cls = new db::DeviceClassMOS3Transistor ();
  db::DeviceClass *m4cls = new db::DeviceClassMOS4Transistor ();

  rcls->set_name ("RCLS");
  lcls->set_name ("LCLS");
  ccls->set_name ("CCLS");
  dcls->set_name ("DCLS");
  m3cls->set_name ("M3CLS");
  m4cls->set_name ("M4CLS");

  nl.add_device_class (rcls);
  nl.add_device_class (lcls);
  nl.add_device_class (ccls);
  nl.add_device_class (dcls);
  nl.add_device_class (m3cls);
  nl.add_device_class (m4cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);

  db::Device *ldev1 = new db::Device (lcls);
  ldev1->set_parameter_value (db::DeviceClassInductor::param_id_L, 1.7e-10);
  db::Device *ldev2 = new db::Device (lcls);
  ldev2->set_parameter_value (db::DeviceClassInductor::param_id_L, 42e-9);
  circuit1->add_device (ldev1);
  circuit1->add_device (ldev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);

  ldev1->connect_terminal (ldev1->device_class ()->terminal_id_for_name ("A"), n1);
  ldev1->connect_terminal (ldev1->device_class ()->terminal_id_for_name ("B"), n3);
  ldev2->connect_terminal (ldev2->device_class ()->terminal_id_for_name ("A"), n3);
  ldev2->connect_terminal (ldev2->device_class ()->terminal_id_for_name ("B"), n2);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter3.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter3_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(4_WriterDiodeDevices)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  db::DeviceClass *ccls = new db::DeviceClassCapacitor ();
  db::DeviceClass *lcls = new db::DeviceClassInductor ();
  db::DeviceClass *dcls = new db::DeviceClassDiode ();
  db::DeviceClass *m3cls = new db::DeviceClassMOS3Transistor ();
  db::DeviceClass *m4cls = new db::DeviceClassMOS4Transistor ();

  rcls->set_name ("RCLS");
  lcls->set_name ("LCLS");
  ccls->set_name ("CCLS");
  dcls->set_name ("DCLS");
  m3cls->set_name ("M3CLS");
  m4cls->set_name ("M4CLS");

  nl.add_device_class (rcls);
  nl.add_device_class (lcls);
  nl.add_device_class (ccls);
  nl.add_device_class (dcls);
  nl.add_device_class (m3cls);
  nl.add_device_class (m4cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);

  db::Device *ddev1 = new db::Device (dcls);
  ddev1->set_parameter_value (db::DeviceClassDiode::param_id_A, 1.7);
  db::Device *ddev2 = new db::Device (dcls);
  ddev2->set_parameter_value (db::DeviceClassDiode::param_id_A, 0.42);
  circuit1->add_device (ddev1);
  circuit1->add_device (ddev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);

  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("A"), n1);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("C"), n3);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("A"), n3);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("C"), n2);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter4.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter4_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(5_WriterMOS3Devices)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  db::DeviceClass *ccls = new db::DeviceClassCapacitor ();
  db::DeviceClass *lcls = new db::DeviceClassInductor ();
  db::DeviceClass *dcls = new db::DeviceClassDiode ();
  db::DeviceClass *m3cls = new db::DeviceClassMOS3Transistor ();
  db::DeviceClass *m4cls = new db::DeviceClassMOS4Transistor ();

  rcls->set_name ("RCLS");
  lcls->set_name ("LCLS");
  ccls->set_name ("CCLS");
  dcls->set_name ("DCLS");
  m3cls->set_name ("M3CLS");
  m4cls->set_name ("M4CLS");

  nl.add_device_class (rcls);
  nl.add_device_class (lcls);
  nl.add_device_class (ccls);
  nl.add_device_class (dcls);
  nl.add_device_class (m3cls);
  nl.add_device_class (m4cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3, *n4;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);
  n4 = new db::Net ();
  n4->set_name ("n4");
  circuit1->add_net (n4);

  db::Device *ddev1 = new db::Device (m3cls);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 0.25);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.18);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AS, 1.2);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AD, 0.75);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PS, 2.2);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PD, 1.75);
  db::Device *ddev2 = new db::Device (m3cls);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 1.4);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.25);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AS, 1.3);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AD, 0.85);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PS, 2.3);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PD, 1.85);
  circuit1->add_device (ddev1);
  circuit1->add_device (ddev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();
  size_t pid3 = circuit1->add_pin ("p3").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);
  circuit1->connect_pin (pid3, n4);

  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("S"), n1);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("G"), n4);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("D"), n3);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("S"), n3);
  ddev2->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("G"), n4);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("D"), n2);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter5.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter5_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(6_WriterMOS4Devices)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  db::DeviceClass *ccls = new db::DeviceClassCapacitor ();
  db::DeviceClass *lcls = new db::DeviceClassInductor ();
  db::DeviceClass *dcls = new db::DeviceClassDiode ();
  db::DeviceClass *m3cls = new db::DeviceClassMOS3Transistor ();
  db::DeviceClass *m4cls = new db::DeviceClassMOS4Transistor ();

  rcls->set_name ("RCLS");
  lcls->set_name ("LCLS");
  ccls->set_name ("CCLS");
  dcls->set_name ("DCLS");
  m3cls->set_name ("M3CLS");
  m4cls->set_name ("M4CLS");

  nl.add_device_class (rcls);
  nl.add_device_class (lcls);
  nl.add_device_class (ccls);
  nl.add_device_class (dcls);
  nl.add_device_class (m3cls);
  nl.add_device_class (m4cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3, *n4, *n5;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);
  n4 = new db::Net ();
  n4->set_name ("n4");
  circuit1->add_net (n4);
  n5 = new db::Net ();
  n5->set_name ("n5");
  circuit1->add_net (n5);

  db::Device *ddev1 = new db::Device (m4cls);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 0.25);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.18);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AS, 1.2);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AD, 0.75);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PS, 2.2);
  ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PD, 1.75);
  db::Device *ddev2 = new db::Device (m4cls);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 1.4);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.25);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AS, 1.3);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AD, 0.85);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PS, 2.3);
  ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PD, 1.85);
  circuit1->add_device (ddev1);
  circuit1->add_device (ddev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();
  size_t pid3 = circuit1->add_pin ("p3").id ();
  size_t pid4 = circuit1->add_pin ("p4").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);
  circuit1->connect_pin (pid3, n4);
  circuit1->connect_pin (pid4, n5);

  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("S"), n1);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("G"), n4);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("D"), n3);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n5);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("S"), n3);
  ddev2->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("G"), n4);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("D"), n2);
  ddev2->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n5);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter6.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter6_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(7_WriterAnyDevices)
{
  db::Netlist nl;

  db::DeviceClass *cls = new db::DeviceClass ();
  cls->add_terminal_definition (db::DeviceTerminalDefinition ("A", "a"));
  cls->add_terminal_definition (db::DeviceTerminalDefinition ("B", "b"));
  cls->add_parameter_definition (db::DeviceParameterDefinition ("U", "u"));
  cls->add_parameter_definition (db::DeviceParameterDefinition ("V", "v"));
  cls->set_name ("XCLS");

  nl.add_device_class (cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);

  db::Device *ddev1 = new db::Device (cls);
  ddev1->set_parameter_value (0, -17);
  ddev1->set_parameter_value (1, 42);
  db::Device *ddev2 = new db::Device (cls);
  ddev2->set_parameter_value (0, 17);
  ddev2->set_parameter_value (1, -42);
  circuit1->add_device (ddev1);
  circuit1->add_device (ddev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);

  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("A"), n1);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n3);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("A"), n3);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("B"), n2);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter7.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter7_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(8_WriterSubcircuits)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  db::DeviceClass *ccls = new db::DeviceClassCapacitor ();
  db::DeviceClass *lcls = new db::DeviceClassInductor ();
  db::DeviceClass *dcls = new db::DeviceClassDiode ();
  db::DeviceClass *m3cls = new db::DeviceClassMOS3Transistor ();
  db::DeviceClass *m4cls = new db::DeviceClassMOS4Transistor ();

  rcls->set_name ("RCLS");
  lcls->set_name ("LCLS");
  ccls->set_name ("CCLS");
  dcls->set_name ("DCLS");
  m3cls->set_name ("M3CLS");
  m4cls->set_name ("M4CLS");

  nl.add_device_class (rcls);
  nl.add_device_class (lcls);
  nl.add_device_class (ccls);
  nl.add_device_class (dcls);
  nl.add_device_class (m3cls);
  nl.add_device_class (m4cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  {
    db::Net *n1, *n2, *n3, *n4, *n5;
    n1 = new db::Net ();
    n1->set_name ("n1");
    circuit1->add_net (n1);
    n2 = new db::Net ();
    n2->set_name ("n2");
    circuit1->add_net (n2);
    n3 = new db::Net ();
    n3->set_name ("n3");
    circuit1->add_net (n3);
    n4 = new db::Net ();
    n4->set_name ("n4");
    circuit1->add_net (n4);
    n5 = new db::Net ();
    n5->set_name ("n5");
    circuit1->add_net (n5);

    db::Device *ddev1 = new db::Device (m4cls);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 0.25);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.18);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AS, 1.2);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AD, 0.75);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PS, 2.2);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PD, 1.75);
    db::Device *ddev2 = new db::Device (m4cls);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 1.4);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.25);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AS, 1.3);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AD, 0.85);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PS, 2.3);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PD, 1.85);
    circuit1->add_device (ddev1);
    circuit1->add_device (ddev2);

    size_t pid1 = circuit1->add_pin ("p1").id ();
    size_t pid2 = circuit1->add_pin ("p2").id ();
    size_t pid3 = circuit1->add_pin ("p3").id ();
    size_t pid4 = circuit1->add_pin ("p4").id ();

    circuit1->connect_pin (pid1, n1);
    circuit1->connect_pin (pid2, n2);
    circuit1->connect_pin (pid3, n4);
    circuit1->connect_pin (pid4, n5);

    ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("S"), n1);
    ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("G"), n4);
    ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("D"), n3);
    ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n5);
    ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("S"), n3);
    ddev2->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("G"), n4);
    ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("D"), n2);
    ddev2->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n5);
  }

  db::Circuit *circuit2 = new db::Circuit ();
  circuit2->set_name ("C2");
  nl.add_circuit (circuit2);

  {
    db::Net *n1, *n2, *n3, *n4, *n5;
    n1 = new db::Net ();
    n1->set_name ("n1");
    circuit2->add_net (n1);
    n2 = new db::Net ();
    n2->set_name ("n2");
    circuit2->add_net (n2);
    n3 = new db::Net ();
    n3->set_name ("n3");
    circuit2->add_net (n3);
    n4 = new db::Net ();
    n4->set_name ("n4");
    circuit2->add_net (n4);
    n5 = new db::Net ();
    n5->set_name ("n5");
    circuit2->add_net (n5);

    db::SubCircuit *sc1 = new db::SubCircuit (circuit1, "SC1");
    circuit2->add_subcircuit (sc1);
    sc1->connect_pin (0, n1);
    sc1->connect_pin (1, n3);
    sc1->connect_pin (2, n4);
    sc1->connect_pin (3, n3);

    db::SubCircuit *sc2 = new db::SubCircuit (circuit1, "SC2");
    circuit2->add_subcircuit (sc2);
    sc2->connect_pin (0, n3);
    sc2->connect_pin (1, n2);
    sc2->connect_pin (2, n4);
    sc2->connect_pin (3, n3);

    size_t pid1 = circuit2->add_pin ("p1").id ();
    size_t pid2 = circuit2->add_pin ("p2").id ();
    size_t pid3 = circuit2->add_pin ("p3").id ();

    circuit2->connect_pin (pid1, n1);
    circuit2->connect_pin (pid2, n2);
    circuit2->connect_pin (pid3, n4);
  }

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter8.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter8_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(9_WriterNetNamesInsteadOfNumbers)
{
  db::Netlist nl;

  db::DeviceClass *cls = new db::DeviceClass ();
  cls->add_terminal_definition (db::DeviceTerminalDefinition ("A", "a"));
  cls->add_terminal_definition (db::DeviceTerminalDefinition ("B", "b"));
  cls->add_parameter_definition (db::DeviceParameterDefinition ("U", "u"));
  cls->add_parameter_definition (db::DeviceParameterDefinition ("V", "v"));
  cls->set_name ("XCLS");

  nl.add_device_class (cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  {
    db::Net *n1, *n2, *n3;
    n1 = new db::Net ();
    n1->set_name ("N1");
    circuit1->add_net (n1);
    n2 = new db::Net ();
    n2->set_name ("N 2");
    circuit1->add_net (n2);
    n3 = new db::Net ();
    n3->set_name ("n3");
    circuit1->add_net (n3);

    db::Device *ddev1 = new db::Device (cls);
    ddev1->set_parameter_value (0, -17);
    ddev1->set_parameter_value (1, 42);
    db::Device *ddev2 = new db::Device (cls);
    ddev2->set_parameter_value (0, 17);
    ddev2->set_parameter_value (1, -42);
    circuit1->add_device (ddev1);
    circuit1->add_device (ddev2);

    size_t pid1 = circuit1->add_pin ("p1").id ();
    size_t pid2 = circuit1->add_pin ("p2").id ();

    circuit1->connect_pin (pid1, n1);
    circuit1->connect_pin (pid2, n2);

    ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("A"), n1);
    ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n3);
    ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("A"), n3);
    ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("B"), n2);
  }

  db::Circuit *circuit2 = new db::Circuit ();
  circuit2->set_name ("C2");
  nl.add_circuit (circuit2);

  {
    db::Net *n1, *n2;
    n1 = new db::Net ();
    n1->set_name ("n1");
    circuit2->add_net (n1);
    n2 = new db::Net ();
    n2->set_name ("n2");
    circuit2->add_net (n2);

    db::SubCircuit *sc1 = new db::SubCircuit (circuit1, "SC1");
    circuit2->add_subcircuit (sc1);
    sc1->connect_pin (0, n1);
    sc1->connect_pin (1, n2);

    size_t pid1 = circuit2->add_pin ("p1").id ();
    size_t pid2 = circuit2->add_pin ("p2").id ();

    circuit2->connect_pin (pid1, n1);
    circuit2->connect_pin (pid2, n2);
  }

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter9.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.set_use_net_names (true);
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter9_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(10_WriterLongLines)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  rcls->set_name ("RCLS");
  nl.add_device_class (rcls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1withaverylongextensionthatgoesbeyondmultiplelinesunlessipasteeverythingtogetherwhichmakesithardtoreadbutexactlythatisthereasonwhyiwriteitthisway");
  nl.add_circuit (circuit1);

  db::Net *n0 = new db::Net ();
  n0->set_name ("n0");
  circuit1->add_net (n0);

  size_t pid0 = circuit1->add_pin ("p0").id ();
  circuit1->connect_pin (pid0, n0);

  for (int i = 0; i < 100; ++i) {

    db::Net *n = new db::Net ();
    n->set_name ("n" + tl::to_string (i + 1));
    circuit1->add_net (n);

    size_t pid = circuit1->add_pin ("p" + tl::to_string (i + 1)).id ();
    circuit1->connect_pin (pid, n);

    db::Device *ddev = new db::Device (rcls);
    circuit1->add_device (ddev);
    ddev->connect_terminal (db::DeviceClassResistor::terminal_id_A, n0);
    ddev->connect_terminal (db::DeviceClassResistor::terminal_id_B, n);

  }

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter10.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter10_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(11_WriterNonConnectedPins)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  db::DeviceClass *ccls = new db::DeviceClassCapacitor ();
  db::DeviceClass *lcls = new db::DeviceClassInductor ();
  db::DeviceClass *dcls = new db::DeviceClassDiode ();
  db::DeviceClass *m3cls = new db::DeviceClassMOS3Transistor ();
  db::DeviceClass *m4cls = new db::DeviceClassMOS4Transistor ();

  rcls->set_name ("RCLS");
  lcls->set_name ("LCLS");
  ccls->set_name ("CCLS");
  dcls->set_name ("DCLS");
  m3cls->set_name ("M3CLS");
  m4cls->set_name ("M4CLS");

  nl.add_device_class (rcls);
  nl.add_device_class (lcls);
  nl.add_device_class (ccls);
  nl.add_device_class (dcls);
  nl.add_device_class (m3cls);
  nl.add_device_class (m4cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  {
    db::Net *n1, *n2, *n3, *n4, *n5;
    n1 = new db::Net ();
    n1->set_name ("n1");
    circuit1->add_net (n1);
    n2 = new db::Net ();
    n2->set_name ("n2");
    circuit1->add_net (n2);
    n3 = new db::Net ();
    n3->set_name ("n3");
    circuit1->add_net (n3);
    n4 = new db::Net ();
    n4->set_name ("n4");
    circuit1->add_net (n4);
    n5 = new db::Net ();
    n5->set_name ("n5");
    circuit1->add_net (n5);

    db::Device *ddev1 = new db::Device (m4cls);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 0.25);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.18);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AS, 1.2);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AD, 0.75);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PS, 2.2);
    ddev1->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PD, 1.75);
    db::Device *ddev2 = new db::Device (m4cls);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 1.4);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.25);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AS, 1.3);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AD, 0.85);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PS, 2.3);
    ddev2->set_parameter_value (db::DeviceClassMOS3Transistor::param_id_PD, 1.85);
    circuit1->add_device (ddev1);
    circuit1->add_device (ddev2);

    size_t pid1 = circuit1->add_pin ("p1").id ();
    size_t pid2 = circuit1->add_pin ("p2").id ();
    size_t pid3 = circuit1->add_pin ("p3").id ();
    size_t pid4 = circuit1->add_pin ("p4").id ();

    circuit1->connect_pin (pid1, n1);
    circuit1->connect_pin (pid2, n2);
    circuit1->connect_pin (pid3, n4);
    circuit1->connect_pin (pid4, n5);

    ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("S"), n1);
    ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("G"), n4);
    ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("D"), n3);
    ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n5);
    ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("S"), n3);
    ddev2->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("G"), n4);
    ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("D"), n2);
    ddev2->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n5);
  }

  db::Circuit *circuit2 = new db::Circuit ();
  circuit2->set_name ("C2");
  nl.add_circuit (circuit2);

  {
    db::Net *n1, *n2, *n3, *n4, *n5;
    n1 = new db::Net ();
    //  this gives a clash with the auto-generated node names with non-connected subcircuit pins
    //  and terminals - we test proper generation of such names this way
    n1->set_name ("nc_10");
    circuit2->add_net (n1);
    n2 = new db::Net ();
    n2->set_name ("n2");
    circuit2->add_net (n2);
    n3 = new db::Net ();
    n3->set_name ("n3");
    circuit2->add_net (n3);
    n4 = new db::Net ();
    n4->set_name ("n4");
    circuit2->add_net (n4);
    n5 = new db::Net ();
    n5->set_name ("n5");
    circuit2->add_net (n5);

    db::SubCircuit *sc1 = new db::SubCircuit (circuit1, "SC1");
    circuit2->add_subcircuit (sc1);
    sc1->connect_pin (0, n1);
    sc1->connect_pin (1, n3);
    //  pin 2 unconnected
    sc1->connect_pin (3, n3);

    db::SubCircuit *sc2 = new db::SubCircuit (circuit1, "SC2");
    circuit2->add_subcircuit (sc2);
    sc2->connect_pin (0, n3);
    //  pin 1 unconnected
    sc2->connect_pin (2, n4);
    sc2->connect_pin (3, n3);

    size_t pid1 = circuit2->add_pin ("p1").id ();
    size_t pid2 = circuit2->add_pin ("p2").id ();
    size_t pid3 = circuit2->add_pin ("p3").id ();

    circuit2->connect_pin (pid1, n1);
    circuit2->connect_pin (pid2, n2);
    circuit2->connect_pin (pid3, n4);
  }

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter11.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter11_au.txt");

  compare_netlists (_this, path, au_path);

  path = tmp_file ("tmp_nwriter11b.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.set_use_net_names (true);
    writer.write (stream, nl, "written by unit test");
  }

  au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter11b_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(12_UniqueNetNames)
{
  db::LayoutToNetlist l2n;
  std::string l2n_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "same_net_names.l2n");
  l2n.load (l2n_path);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter12.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, *l2n.netlist (), "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter12_au.txt");

  compare_netlists (_this, path, au_path);

  path = tmp_file ("tmp_nwriter12b.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.set_use_net_names (true);
    writer.write (stream, *l2n.netlist (), "written by unit test");
  }

  au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter12b_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(13_WriterBJT3Devices)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  db::DeviceClass *ccls = new db::DeviceClassCapacitor ();
  db::DeviceClass *lcls = new db::DeviceClassInductor ();
  db::DeviceClass *dcls = new db::DeviceClassDiode ();
  db::DeviceClass *b3cls = new db::DeviceClassBJT3Transistor ();
  db::DeviceClass *b4cls = new db::DeviceClassBJT4Transistor ();

  rcls->set_name ("RCLS");
  lcls->set_name ("LCLS");
  ccls->set_name ("CCLS");
  dcls->set_name ("DCLS");
  b3cls->set_name ("B3CLS");
  b4cls->set_name ("B4CLS");

  nl.add_device_class (rcls);
  nl.add_device_class (lcls);
  nl.add_device_class (ccls);
  nl.add_device_class (dcls);
  nl.add_device_class (b3cls);
  nl.add_device_class (b4cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3, *n4;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);
  n4 = new db::Net ();
  n4->set_name ("n4");
  circuit1->add_net (n4);

  db::Device *ddev1 = new db::Device (b3cls);
  ddev1->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_AE, 0.25);
  ddev1->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_PE, 0.18);
  ddev1->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_AB, 1.2);
  ddev1->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_PB, 0.75);
  ddev1->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_AC, 1.0);
  ddev1->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_PC, 0.6);
  db::Device *ddev2 = new db::Device (b3cls);
  ddev2->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_AE, 1.2);
  ddev2->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_PE, 2.5);
  ddev2->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_AB, 1.4);
  ddev2->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_PB, 2.8);
  ddev2->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_AC, 1.5);
  ddev2->set_parameter_value (db::DeviceClassBJT3Transistor::param_id_PC, 3.0);
  circuit1->add_device (ddev1);
  circuit1->add_device (ddev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();
  size_t pid3 = circuit1->add_pin ("p3").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);
  circuit1->connect_pin (pid3, n4);

  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("E"), n1);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n4);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("C"), n3);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("E"), n3);
  ddev2->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n4);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("C"), n2);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter13.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter13_au.txt");

  compare_netlists (_this, path, au_path);
}

TEST(14_WriterBJT4Devices)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  db::DeviceClass *ccls = new db::DeviceClassCapacitor ();
  db::DeviceClass *lcls = new db::DeviceClassInductor ();
  db::DeviceClass *dcls = new db::DeviceClassDiode ();
  db::DeviceClass *b3cls = new db::DeviceClassBJT3Transistor ();
  db::DeviceClass *b4cls = new db::DeviceClassBJT4Transistor ();

  rcls->set_name ("RCLS");
  lcls->set_name ("LCLS");
  ccls->set_name ("CCLS");
  dcls->set_name ("DCLS");
  b3cls->set_name ("B3CLS");
  b4cls->set_name ("B4CLS");

  nl.add_device_class (rcls);
  nl.add_device_class (lcls);
  nl.add_device_class (ccls);
  nl.add_device_class (dcls);
  nl.add_device_class (b3cls);
  nl.add_device_class (b4cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3, *n4, *n5;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);
  n4 = new db::Net ();
  n4->set_name ("n4");
  circuit1->add_net (n4);
  n5 = new db::Net ();
  n5->set_name ("n5");
  circuit1->add_net (n5);

  db::Device *ddev1 = new db::Device (b4cls);
  ddev1->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_AE, 0.25);
  ddev1->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_PE, 0.18);
  ddev1->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_AB, 1.2);
  ddev1->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_PB, 0.75);
  ddev1->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_AC, 1.0);
  ddev1->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_PC, 0.6);
  db::Device *ddev2 = new db::Device (b4cls);
  ddev2->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_AE, 1.2);
  ddev2->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_PE, 2.5);
  ddev2->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_AB, 1.4);
  ddev2->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_PB, 2.8);
  ddev2->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_AC, 1.5);
  ddev2->set_parameter_value (db::DeviceClassBJT4Transistor::param_id_PC, 3.0);
  circuit1->add_device (ddev1);
  circuit1->add_device (ddev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();
  size_t pid3 = circuit1->add_pin ("p3").id ();
  size_t pid4 = circuit1->add_pin ("p4").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);
  circuit1->connect_pin (pid3, n4);
  circuit1->connect_pin (pid4, n5);

  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("E"), n1);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n4);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("C"), n3);
  ddev1->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("S"), n5);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("E"), n3);
  ddev2->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("B"), n4);
  ddev2->connect_terminal (ddev2->device_class ()->terminal_id_for_name ("C"), n2);
  ddev2->connect_terminal (ddev1->device_class ()->terminal_id_for_name ("S"), n5);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter14.txt");
  {
    tl::OutputStream stream (path);
    db::NetlistSpiceWriter writer;
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter14_au.txt");

  compare_netlists (_this, path, au_path);
}


namespace {

class MyDelegate
  : public db::NetlistSpiceWriterDelegate
{
public:
  MyDelegate ()
    : db::NetlistSpiceWriterDelegate ()
  { }

  void write_header () const
  {
    emit_line ("*** My special header");
  }

  void write_device_intro (const db::DeviceClass &cls) const
  {
    emit_line ("*** My intro for class " + cls.name ());
  }

  void write_device (const db::Device &dev) const
  {
    emit_line ("*** Before device " + dev.expanded_name ());
    db::NetlistSpiceWriterDelegate::write_device (dev);
    emit_line ("*** After device " + dev.expanded_name ());
  }
};

}

TEST(20_Delegate)
{
  db::Netlist nl;

  db::DeviceClass *rcls = new db::DeviceClassResistor ();
  db::DeviceClass *ccls = new db::DeviceClassCapacitor ();
  db::DeviceClass *lcls = new db::DeviceClassInductor ();
  db::DeviceClass *dcls = new db::DeviceClassDiode ();
  db::DeviceClass *m3cls = new db::DeviceClassMOS3Transistor ();
  db::DeviceClass *m4cls = new db::DeviceClassMOS4Transistor ();

  rcls->set_name ("RCLS");
  lcls->set_name ("LCLS");
  ccls->set_name ("CCLS");
  dcls->set_name ("DCLS");
  m3cls->set_name ("M3CLS");
  m4cls->set_name ("M4CLS");

  nl.add_device_class (rcls);
  nl.add_device_class (lcls);
  nl.add_device_class (ccls);
  nl.add_device_class (dcls);
  nl.add_device_class (m3cls);
  nl.add_device_class (m4cls);

  db::Circuit *circuit1 = new db::Circuit ();
  circuit1->set_name ("C1");
  nl.add_circuit (circuit1);

  db::Net *n1, *n2, *n3;
  n1 = new db::Net ();
  n1->set_name ("n1");
  circuit1->add_net (n1);
  n2 = new db::Net ();
  n2->set_name ("n2");
  circuit1->add_net (n2);
  n3 = new db::Net ();
  n3->set_name ("n3");
  circuit1->add_net (n3);

  db::Device *rdev1 = new db::Device (rcls);
  rdev1->set_parameter_value (db::DeviceClassResistor::param_id_R, 1.7);
  db::Device *rdev2 = new db::Device (rcls);
  rdev2->set_parameter_value (db::DeviceClassResistor::param_id_R, 42e-6);
  circuit1->add_device (rdev1);
  circuit1->add_device (rdev2);

  size_t pid1 = circuit1->add_pin ("p1").id ();
  size_t pid2 = circuit1->add_pin ("p2").id ();

  circuit1->connect_pin (pid1, n1);
  circuit1->connect_pin (pid2, n2);

  rdev1->connect_terminal (rdev1->device_class ()->terminal_id_for_name ("A"), n1);
  rdev1->connect_terminal (rdev1->device_class ()->terminal_id_for_name ("B"), n3);
  rdev2->connect_terminal (rdev2->device_class ()->terminal_id_for_name ("A"), n3);
  rdev2->connect_terminal (rdev2->device_class ()->terminal_id_for_name ("B"), n2);

  //  verify against the input

  std::string path = tmp_file ("tmp_nwriter20.txt");
  {
    tl::OutputStream stream (path);
    MyDelegate delegate;
    db::NetlistSpiceWriter writer (&delegate);
    writer.write (stream, nl, "written by unit test");
  }

  std::string au_path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nwriter20_au.txt");

  compare_netlists (_this, path, au_path);
}
