diff --git a/openfpga/src/fpga_spice/spice_subckt_writer.cpp b/openfpga/src/fpga_spice/spice_subckt_writer.cpp new file mode 100644 index 000000000..c90c0b5ac --- /dev/null +++ b/openfpga/src/fpga_spice/spice_subckt_writer.cpp @@ -0,0 +1,449 @@ +/******************************************************************** + * This file includes functions to write a SPICE module + * based on its definition in Module Manager + * + * Note that SPICE writer functions are just an outputter for the + * module definition. + * You should NOT modify any content of the module manager + * Please use const keyword to restrict this! + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "openfpga_naming.h" + +#include "module_manager_utils.h" +#include "spice_writer_utils.h" +#include "spice_subckt_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Generate the name of a local wire for a undriven port inside SPICE + * module + *******************************************************************/ +static +std::string generate_spice_undriven_local_wire_name(const ModuleManager& module_manager, + const ModuleId& parent, + const ModuleId& child, + const size_t& instance_id, + const ModulePortId& child_port_id) { + std::string wire_name; + if (!module_manager.instance_name(parent, child, instance_id).empty()) { + wire_name = module_manager.instance_name(parent, child, instance_id); + } else { + wire_name = module_manager.module_name(parent) + std::string("_") + std::to_string(instance_id); + wire_name += std::string("_"); + } + + wire_name += std::string("_undriven_"); + wire_name += module_manager.module_port(child, child_port_id).get_name(); + + return wire_name; +} + + +/******************************************************************** + * Name a net for a local wire for a SPICE subckt + * 1. If this is a local wire, name it after the __ + * 2. If this is not a local wire, name it after the port name of parent module + * + * In addition, it will assign the pin index as well + * + * Restriction: this function requires each net has single driver + * which is definitely always true in circuits. + *******************************************************************/ +static +BasicPort generate_spice_port_for_module_net(const ModuleManager& module_manager, + const ModuleId& module_id, + const ModuleNetId& module_net) { + /* Check all the sink modules of the net, + * if we have a source module is the current module, this is not local wire + */ + for (ModuleNetSrcId src_id : module_manager.module_net_sources(module_id, module_net)) { + if (module_id == module_manager.net_source_modules(module_id, module_net)[src_id]) { + /* Here, this is not a local wire, return the port name of the src_port */ + ModulePortId net_src_port = module_manager.net_source_ports(module_id, module_net)[src_id]; + size_t src_pin_index = module_manager.net_source_pins(module_id, module_net)[src_id]; + return BasicPort(module_manager.module_port(module_id, net_src_port).get_name(), src_pin_index, src_pin_index); + } + } + + /* Check all the sink modules of the net */ + for (ModuleNetSinkId sink_id : module_manager.module_net_sinks(module_id, module_net)) { + if (module_id == module_manager.net_sink_modules(module_id, module_net)[sink_id]) { + /* Here, this is not a local wire, return the port name of the sink_port */ + ModulePortId net_sink_port = module_manager.net_sink_ports(module_id, module_net)[sink_id]; + size_t sink_pin_index = module_manager.net_sink_pins(module_id, module_net)[sink_id]; + return BasicPort(module_manager.module_port(module_id, net_sink_port).get_name(), sink_pin_index, sink_pin_index); + } + } + + /* Reach here, this is a local wire */ + std::string net_name; + + /* Each net must only one 1 source */ + VTR_ASSERT(1 == module_manager.net_source_modules(module_id, module_net).size()); + + /* Get the source module */ + ModuleId net_src_module = module_manager.net_source_modules(module_id, module_net)[ModuleNetSrcId(0)]; + /* Get the instance id */ + size_t net_src_instance = module_manager.net_source_instances(module_id, module_net)[ModuleNetSrcId(0)]; + /* Get the port id */ + ModulePortId net_src_port = module_manager.net_source_ports(module_id, module_net)[ModuleNetSrcId(0)]; + /* Get the pin id */ + size_t net_src_pin = module_manager.net_source_pins(module_id, module_net)[ModuleNetSrcId(0)]; + + /* Load user-defined name if we have it */ + if (false == module_manager.net_name(module_id, module_net).empty()) { + net_name = module_manager.net_name(module_id, module_net); + } else { + net_name = module_manager.module_name(net_src_module); + net_name += std::string("_") + std::to_string(net_src_instance) + std::string("_"); + net_name += module_manager.module_port(net_src_module, net_src_port).get_name(); + } + + return BasicPort(net_name, net_src_pin, net_src_pin); +} + +/******************************************************************** + * Print a SPICE wire connection + * We search all the sinks of the net, + * if we find a module output, we try to find the next module output + * among the sinks of the net + * For each module output (except the first one), we print a wire connection + *******************************************************************/ +static +void print_spice_subckt_output_short_connection(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id, + const ModuleNetId& module_net) { + /* Ensure a valid file stream */ + VTR_ASSERT(true == valid_file_stream(fp)); + + bool first_port = true; + BasicPort src_port; + + /* We have found a module input, now check all the sink modules of the net */ + for (ModuleNetSinkId net_sink : module_manager.module_net_sinks(module_id, module_net)) { + ModuleId sink_module = module_manager.net_sink_modules(module_id, module_net)[net_sink]; + if (module_id != sink_module) { + continue; + } + + /* Find the sink port and pin information */ + ModulePortId sink_port_id = module_manager.net_sink_ports(module_id, module_net)[net_sink]; + size_t sink_pin = module_manager.net_sink_pins(module_id, module_net)[net_sink]; + BasicPort sink_port(module_manager.module_port(module_id, sink_port_id).get_name(), sink_pin, sink_pin); + + /* For the first module output, this is the source port, we do nothing and go to the next */ + if (true == first_port) { + src_port = sink_port; + /* Flip the flag */ + first_port = false; + continue; + } + + /* We need to print a wire connection here */ + VTR_ASSERT(src_port.get_width() == sink_port.get_width()); + for (size_t ipin = 0; ipin < src_port.pins().size(); ++ipin) { + BasicPort src_spice_pin(src_port.get_name(), src_port.pins()[ipin], src_port.pins()[ipin]); + BasicPort sink_spice_pin(sink_port.get_name(), sink_port.pins()[ipin], sink_port.pins()[ipin]); + print_spice_short_connection(fp, + generate_spice_port(src_spice_pin), + generate_spice_port(sink_spice_pin)); + } + } +} + + +/******************************************************************** + * Print a SPICE wire connection + * We search all the sources of the net, + * if we find a module input, we try to find a module output + * among the sinks of the net + * If we find such a pair, we print a wire connection + *******************************************************************/ +static +void print_spice_subckt_local_short_connection(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id, + const ModuleNetId& module_net) { + /* Ensure a valid file stream */ + VTR_ASSERT(true == valid_file_stream(fp)); + + for (ModuleNetSrcId net_src : module_manager.module_net_sources(module_id, module_net)) { + ModuleId src_module = module_manager.net_source_modules(module_id, module_net)[net_src]; + if (module_id != src_module) { + continue; + } + /* Find the source port and pin information */ + print_spice_comment(fp, std::string("Net source id " + std::to_string(size_t(net_src)))); + ModulePortId src_port_id = module_manager.net_source_ports(module_id, module_net)[net_src]; + size_t src_pin = module_manager.net_source_pins(module_id, module_net)[net_src]; + BasicPort src_port(module_manager.module_port(module_id, src_port_id).get_name(), src_pin, src_pin); + + /* We have found a module input, now check all the sink modules of the net */ + for (ModuleNetSinkId net_sink : module_manager.module_net_sinks(module_id, module_net)) { + ModuleId sink_module = module_manager.net_sink_modules(module_id, module_net)[net_sink]; + if (module_id != sink_module) { + continue; + } + + /* Find the sink port and pin information */ + print_spice_comment(fp, std::string("Net sink id " + std::to_string(size_t(net_sink)))); + ModulePortId sink_port_id = module_manager.net_sink_ports(module_id, module_net)[net_sink]; + size_t sink_pin = module_manager.net_sink_pins(module_id, module_net)[net_sink]; + BasicPort sink_port(module_manager.module_port(module_id, sink_port_id).get_name(), sink_pin, sink_pin); + + /* We need to print a wire connection here */ + VTR_ASSERT(src_port.get_width() == sink_port.get_width()); + for (size_t ipin = 0; ipin < src_port.pins().size(); ++ipin) { + BasicPort src_spice_pin(src_port.get_name(), src_port.pins()[ipin], src_port.pins()[ipin]); + BasicPort sink_spice_pin(sink_port.get_name(), sink_port.pins()[ipin], sink_port.pins()[ipin]); + print_spice_short_connection(fp, + generate_spice_port(src_spice_pin), + generate_spice_port(sink_spice_pin)); + } + } + } +} + +/******************************************************************** + * Print short connections inside a SPICE module + * The short connection is defined as the direct connection + * between an input port of the module and an output port of the module + * This type of connection is not covered when printing SPICE instances + * Therefore, they are covered in this function + * + * module + * +-----------------------------+ + * | | + * inputA--->|---------------------------->|--->outputB + * | | + * | | + * | | + * +-----------------------------+ + *******************************************************************/ +static +void print_spice_subckt_local_short_connections(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id) { + /* Local wires come from the child modules */ + for (ModuleNetId module_net : module_manager.module_nets(module_id)) { + /* We only care the nets that indicate short connections */ + if (false == module_net_include_local_short_connection(module_manager, module_id, module_net)) { + continue; + } + print_spice_comment(fp, std::string("Local connection due to Wire " + std::to_string(size_t(module_net)))); + print_spice_subckt_local_short_connection(fp, module_manager, module_id, module_net); + } +} + +/******************************************************************** + * Print output short connections inside a SPICE module + * The output short connection is defined as the direct connection + * between two output ports of the module + * This type of connection is not covered when printing SPICE instances + * Therefore, they are covered in this function + * + * module + * +-----------------------------+ + * | + * src------>+--------------->|--->outputA + * | | + * | | + * +--------------->|--->outputB + * +-----------------------------+ + *******************************************************************/ +static +void print_spice_subckt_output_short_connections(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id) { + /* Local wires come from the child modules */ + for (ModuleNetId module_net : module_manager.module_nets(module_id)) { + /* We only care the nets that indicate short connections */ + if (false == module_net_include_output_short_connection(module_manager, module_id, module_net)) { + continue; + } + print_spice_subckt_output_short_connection(fp, module_manager, module_id, module_net); + } +} + +/******************************************************************** + * Write a SPICE instance to a file + * This function will name the input and output connections to + * the inputs/output or local wires available in the parent module + * + * Parent_module + * +-----------------------------+ + * | | + * | +--------------+ | + * | | | | + * | | child_module | | + * | | [instance] | | + * | +--------------+ | + * | | + * +-----------------------------+ + * + *******************************************************************/ +static +void write_spice_instance_to_file(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const ModuleId& child_module, + const size_t& instance_id) { + /* Ensure a valid file stream */ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Print instance name: + * if we have an instance name, use it; + * if not, we use a default name _ + */ + std::string instance_head_line = "X "; + if (true == module_manager.instance_name(parent_module, child_module, instance_id).empty()) { + instance_head_line += generate_instance_name(module_manager.module_name(child_module), instance_id); + } else { + instance_head_line += module_manager.instance_name(parent_module, child_module, instance_id); + } + instance_head_line += " "; + fp << instance_head_line; + + /* Port sequence: global, inout, input, output and clock ports, */ + bool fit_one_line = true; + bool new_line = false; + size_t pin_cnt = 0; + for (int port_type = ModuleManager::MODULE_GLOBAL_PORT; + port_type < ModuleManager::NUM_MODULE_PORT_TYPES; + ++port_type) { + for (const auto& child_port_id : module_manager.module_port_ids_by_type(child_module, static_cast(port_type))) { + + BasicPort child_port = module_manager.module_port(child_module, child_port_id); + + /* Create the port name and width to be used by the instance */ + std::vector instance_ports; + for (size_t child_pin : child_port.pins()) { + /* Find the net linked to the pin */ + ModuleNetId net = module_manager.module_instance_port_net(parent_module, child_module, instance_id, + child_port_id, child_pin); + BasicPort instance_port; + if (ModuleNetId::INVALID() == net) { + /* We give the same port name as child module, this case happens to global ports */ + instance_port.set_name(generate_spice_undriven_local_wire_name(module_manager, parent_module, child_module, instance_id, child_port_id)); + instance_port.set_width(child_pin, child_pin); + } else { + /* Find the name for this child port */ + instance_port = generate_spice_port_for_module_net(module_manager, parent_module, net); + } + + if (true == new_line) { + std::string port_whitespace(instance_head_line.length() - 2, ' '); + fp << "+ " << port_whitespace; + } + + if (0 != pin_cnt) { + write_space_to_file(fp, 1); + } + + VTR_ASSERT(1 == instance_port.get_width()); + + /* For single-bit port, + * we can print the port name directly + */ + bool omit_pin_zero = false; + if ((1 == instance_port.pins().size()) + && (0 == instance_port.get_lsb())) { + omit_pin_zero = true; + } + + fp << generate_spice_port(instance_port, omit_pin_zero); + + /* Increase the counter */ + pin_cnt++; + + /* Currently we limit 10 ports per line to keep a clean netlist */ + new_line = false; + if (10 == pin_cnt) { + pin_cnt = 0; + fp << std::endl; + new_line = true; + fit_one_line = false; + } + } + } + } + + /* Print module name: + * if port print cannot fit one line, we create a new line for the module for a clean format + */ + if (false == fit_one_line) { + fp << std::endl; + fp << "+"; + } + write_space_to_file(fp, 1); + fp << module_manager.module_name(child_module); + + /* Print an end to the instance */ + fp << std::endl; +} + +/******************************************************************** + * Write a SPICE sub-circuit to a file + * This is a key function, maybe most frequently called in our SPICE writer + * Note that file stream must be valid + *******************************************************************/ +void write_spice_subckt_to_file(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id) { + + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Ensure we have a valid module_id */ + VTR_ASSERT(module_manager.valid_module_id(module_id)); + + /* Print module declaration */ + print_spice_subckt_definition(fp, module_manager, module_id); + + /* Print an empty line as splitter */ + fp << std::endl; + + /* Print an empty line as splitter */ + fp << std::endl; + + /* Print local connection (from module inputs to output! */ + print_spice_comment(fp, std::string("BEGIN Local short connections")); + print_spice_subckt_local_short_connections(fp, module_manager, module_id); + print_spice_comment(fp, std::string("END Local short connections")); + + print_spice_comment(fp, std::string("BEGIN Local output short connections")); + print_spice_subckt_output_short_connections(fp, module_manager, module_id); + + print_spice_comment(fp, std::string("END Local output short connections")); + /* Print an empty line as splitter */ + fp << std::endl; + + /* Print instances */ + for (ModuleId child_module : module_manager.child_modules(module_id)) { + for (size_t instance : module_manager.child_module_instances(module_id, child_module)) { + /* Print an instance */ + write_spice_instance_to_file(fp, module_manager, module_id, child_module, instance); + /* Print an empty line as splitter */ + fp << std::endl; + } + } + + /* Print an end for the module */ + print_spice_subckt_end(fp, module_manager.module_name(module_id)); + + /* Print an empty line as splitter */ + fp << std::endl; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_spice/spice_subckt_writer.h b/openfpga/src/fpga_spice/spice_subckt_writer.h new file mode 100644 index 000000000..f47e3915f --- /dev/null +++ b/openfpga/src/fpga_spice/spice_subckt_writer.h @@ -0,0 +1,23 @@ +#ifndef SPICE_SUBCKT_WRITER_H +#define SPICE_SUBCKT_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void write_spice_subckt_to_file(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id); + +} /* end namespace openfpga */ + +#endif