Task 675: .SPF File Format
Task 675: .SPF File Format
- The .SPF file format refers to the Standard Parasitic Format, a specification for representing parasitic resistance and capacitance in electronic design automation (EDA) tools. Based on the available specifications, the properties intrinsic to this format include the following:
- Header section: Contains metadata such as the SPEF version, design name, creation date, vendor, program name, program version, design flow information, divider character, delimiter character, bus delimiter, time unit, capacitance unit, resistance unit, and inductance unit.
- Name map section: Provides mappings of long names (nets, instances, pins) to shortened integer references prefixed with an asterisk (e.g., *1 for a net name) to compress file size.
- Ports section: Lists top-level ports with their directions (I for input, O for output, B for bidirectional) and optional coordinates (x y).
- Parasitic description section: Includes *D_NET blocks for each net, specifying the net name or reference, total net capacitance, connections (*CONN), capacitances (*CAP), resistances (*RES), and an *END marker.
- Connections (*CONN): Details pins or ports connected to the net, including pin name or reference, direction, coordinates (x y), load capacitance (*L value), and driver cell type (*D cell_type).
- Capacitances (*CAP): Entries for lumped-to-ground capacitors (ID, node, value) or coupling capacitors (ID, node1, node2, value), with support for single values or min:typ:max ranges.
- Resistances (*RES): Entries for resistors (ID, node1, node2, value), forming the resistance network.
- Comments: Lines starting with // for annotations.
- Syntax rules: Keywords prefixed with *, optional name mapping, ASCII text format, support for complex RC networks, and negation of filters with -.
These properties define the structure and data representation in the file, ensuring interoperability between EDA tools for delay calculation and signal integrity analysis.
- Upon searching, direct download links for example .SPF files are limited due to their design-specific and proprietary nature in EDA workflows. However, the following two direct links provide sample files in the format:
- https://raw.githubusercontent.com/OpenTimer/Parser-SPEF/master/test/test.spef
- https://raw.githubusercontent.com/The-OpenROAD-Project/OpenROAD/master/test/gcd.spef
- The following is a standalone HTML page with embedded JavaScript for a Ghost blog-style interface. It allows users to drag and drop a .SPF file, parses the file content, extracts the properties from the list above, and displays them on the screen.
Drag and Drop .SPF File Parser
Drag and drop .SPF file here
- The following is a Python class for handling .SPF files. It can open, decode, read, write, and print the properties.
class SPFParser:
def __init__(self):
self.header = {}
self.name_map = {}
self.ports = []
self.nets = []
def open(self, filepath):
with open(filepath, 'r') as f:
content = f.read()
self.parse(content)
def parse(self, content):
lines = content.split('\n')
current_section = ''
current_net = None
for line in lines:
line = line.strip()
if line.startswith('//') or not line:
continue
parts = line.split()
if line.startswith('*SPEF'):
self.header['spef'] = line
elif line.startswith('*DESIGN'):
self.header['design'] = line
elif line.startswith('*DATE'):
self.header['date'] = line
elif line.startswith('*VENDOR'):
self.header['vendor'] = line
elif line.startswith('*PROGRAM'):
self.header['program'] = line
elif line.startswith('*VERSION'):
self.header['version'] = line
elif line.startswith('*DIVIDER'):
self.header['divider'] = line
elif line.startswith('*DELIMITER'):
self.header['delimiter'] = line
elif line.startswith('*BUS_DELIMITER'):
self.header['bus_delimiter'] = line
elif line.startswith('*T_UNIT'):
self.header['time_unit'] = line
elif line.startswith('*C_UNIT'):
self.header['cap_unit'] = line
elif line.startswith('*R_UNIT'):
self.header['res_unit'] = line
elif line.startswith('*L_UNIT'):
self.header['ind_unit'] = line
elif line.startswith('*NAME_MAP'):
current_section = 'name_map'
elif line.startsWith('*PORTS'):
current_section = 'ports'
elif line.startsWith('*D_NET'):
current_section = 'net'
current_net = {'net': line, 'conn': [], 'cap': [], 'res': []}
self.nets.append(current_net)
elif line.startsWith('*CONN'):
current_section = 'conn'
elif line.startsWith('*CAP'):
current_section = 'cap'
elif line.startsWith('*RES'):
current_section = 'res'
elif line.startsWith('*END'):
current_section = ''
else:
if current_section == 'name_map':
self.name_map[parts[0]] = ' '.join(parts[1:])
elif current_section == 'ports':
self.ports.append(line)
elif current_section == 'conn':
current_net['conn'].append(line)
elif current_section = 'cap':
current_net['cap'].append(line)
elif current_section == 'res':
current_net['res'].append(line)
def print_properties(self):
print('Header:')
for key, value in self.header.items():
print(f' {key}: {value}')
print('\nName Map:')
for key, value in self.name_map.items():
print(f' {key}: {value}')
print('\nPorts:')
for port in self.ports:
print(f' {port}')
print('\nNets:')
for net in self.nets:
print(f' {net["net"]}')
print(' Connections:')
for conn in net['conn']:
print(f' {conn}')
print(' Capacitances:')
for cap in net['cap']:
print(f' {cap}')
print(' Resistances:')
for res in net['res']:
print(f' {res}')
def write(self, filepath):
with open(filepath, 'w') as f:
for key, value in self.header.items():
f.write(f'{value}\n')
f.write('*NAME_MAP\n')
for key, value in self.name_map.items():
f.write(f'{key} {value}\n')
f.write('*PORTS\n')
for port in self.ports:
f.write(f'{port}\n')
for net in self.nets:
f.write(f'{net["net"]}\n')
f.write('*CONN\n')
for conn in net['conn']:
f.write(f'{conn}\n')
f.write('*CAP\n')
for cap in net['cap']:
f.write(f'{cap}\n')
f.write('*RES\n')
for res in net['res']:
f.write(f'{res}\n')
f.write('*END\n')
To use:
parser = SPFParser()
parser.open('example.spf')
parser.print_properties()
parser.write('output.spf')
- The following is a Java class for handling .SPF files. It can open, decode, read, write, and print the properties.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SPFParser {
private Map<String, String> header = new HashMap<>();
private Map<String, String> nameMap = new HashMap<>();
private List<String> ports = new ArrayList<>();
private List<Map<String, Object>> nets = new ArrayList<>();
public void open(String filepath) throws Exception {
BufferedReader reader = new BufferedReader(new FileReader(filepath));
String line;
String currentSection = "";
Map<String, Object> currentNet = null;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("//") || line.isEmpty()) continue;
if (line.startsWith("*SPEF")) header.put("spef", line);
else if (line.startsWith("*DESIGN")) header.put("design", line);
else if (line.startsWith("*DATE")) header.put("date", line);
else if (line.startsWith("*VENDOR")) header.put("vendor", line);
else if (line.startsWith("*PROGRAM")) header.put("program", line);
else if (line.startsWith("*VERSION")) header.put("version", line);
else if (line.startsWith("*DIVIDER")) header.put("divider", line);
else if (line.startsWith("*DELIMITER")) header.put("delimiter", line);
else if (line.startsWith("*BUS_DELIMITER")) header.put("busDelimiter", line);
else if (line.startsWith("*T_UNIT")) header.put("timeUnit", line);
else if (line.startsWith("*C_UNIT")) header.put("capUnit", line);
else if (line.startsWith("*R_UNIT")) header.put("resUnit", line);
else if (line.startsWith("*L_UNIT")) header.put("indUnit", line);
else if (line.startsWith("*NAME_MAP")) currentSection = "nameMap";
else if (line.startsWith("*PORTS")) currentSection = "ports";
else if (line.startsWith("*D_NET")) {
currentSection = "net";
currentNet = new HashMap<>();
currentNet.put("net", line);
currentNet.put("conn", new ArrayList<String>());
currentNet.put("cap", new ArrayList<String>());
currentNet.put("res", new ArrayList<String>());
nets.add(currentNet);
} else if (line.startsWith("*CONN")) currentSection = "conn";
else if (line.startsWith("*CAP")) currentSection = "cap";
else if (line.startsWith("*RES")) currentSection = "res";
else if (line.startsWith("*END")) currentSection = "";
else {
if ("nameMap".equals(currentSection)) {
String[] parts = line.split(" ", 2);
nameMap.put(parts[0], parts[1]);
} else if ("ports".equals(currentSection)) {
ports.add(line);
} else if ("conn".equals(currentSection)) {
((List<String>) currentNet.get("conn")).add(line);
} else if ("cap".equals(currentSection)) {
((List<String>) currentNet.get("cap")).add(line);
} else if ("res".equals(currentSection)) {
((List<String>) currentNet.get("res")).add(line);
}
}
}
reader.close();
}
public void printProperties() {
System.out.println("Header:");
header.forEach((key, value) -> System.out.println(" " + key + ": " + value));
System.out.println("\nName Map:");
nameMap.forEach((key, value) -> System.out.println(" " + key + ": " + value));
System.out.println("\nPorts:");
ports.forEach(port -> System.out.println(" " + port));
System.out.println("\nNets:");
for (Map<String, Object> net : nets) {
System.out.println(" " + net.get("net"));
System.out.println(" Connections:");
((List<String>) net.get("conn")).forEach(conn -> System.out.println(" " + conn));
System.out.println(" Capacitances:");
((List<String>) net.get("cap")).forEach(cap -> System.out.println(" " + cap));
System.out.println(" Resistances:");
((List<String>) net.get("res")).forEach(res -> System.out.println(" " + res));
}
}
public void write(String filepath) throws Exception {
PrintWriter writer = new PrintWriter(filepath);
header.values().forEach(writer::println);
writer.println("*NAME_MAP");
nameMap.forEach((key, value) -> writer.println(key + " " + value));
writer.println("*PORTS");
ports.forEach(writer::println);
for (Map<String, Object> net : nets) {
writer.println(net.get("net"));
writer.println("*CONN");
((List<String>) net.get("conn")).forEach(writer::println);
writer.println("*CAP");
((List<String>) net.get("cap")).forEach(writer::println);
writer.println("*RES");
((List<String>) net.get("res")).forEach(writer::println);
writer.println("*END");
}
writer.close();
}
}
To use:
SPFParser parser = new SPFParser();
parser.open("example.spf");
parser.printProperties();
parser.write("output.spf");
- The following is a JavaScript class for handling .SPF files. It can open (using FileReader), decode, read, write (using Blob), and print the properties to console.
class SPFParser {
constructor() {
this.header = {};
this.nameMap = {};
this.ports = [];
this.nets = [];
}
open(file, callback) {
const reader = new FileReader();
reader.onload = (event) => {
this.parse(event.target.result);
callback();
};
reader.readAsText(file);
}
parse(content) {
const lines = content.split('\n');
let currentSection = '';
let currentNet = null;
lines.forEach(line => {
line = line.trim();
if (line.startsWith('//') || !line) return;
if (line.startsWith('*SPEF')) this.header.spef = line;
else if (line.startsWith('*DESIGN')) this.header.design = line;
else if (line.startsWith('*DATE')) this.header.date = line;
else if (line.startsWith('*VENDOR')) this.header.vendor = line;
else if (line.startsWith('*PROGRAM')) this.header.program = line;
else if (line.startsWith('*VERSION')) this.header.version = line;
else if (line.startsWith('*DIVIDER')) this.header.divider = line;
else if (line.startsWith('*DELIMITER')) this.header.delimiter = line;
else if (line.startsWith('*BUS_DELIMITER')) this.header.busDelimiter = line;
else if (line.startsWith('*T_UNIT')) this.header.timeUnit = line;
else if (line.startsWith('*C_UNIT')) this.header.capUnit = line;
else if (line.startsWith('*R_UNIT')) this.header.resUnit = line;
else if (line.startsWith('*L_UNIT')) this.header.indUnit = line;
else if (line.startsWith('*NAME_MAP')) currentSection = 'nameMap';
else if (line.startsWith('*PORTS')) currentSection = 'ports';
else if (line.startsWith('*D_NET')) {
currentSection = 'net';
currentNet = { net: line, conn: [], cap: [], res: [] };
this.nets.push(currentNet);
} else if (line.startsWith('*CONN')) currentSection = 'conn';
else if (line.startsWith('*CAP')) currentSection = 'cap';
else if (line.startsWith('*RES')) currentSection = 'res';
else if (line.startsWith('*END')) currentSection = '';
else {
if (currentSection === 'nameMap') {
const parts = line.split(' ');
this.nameMap[parts[0]] = parts.slice(1).join(' ');
} else if (currentSection === 'ports') {
this.ports.push(line);
} else if (currentSection === 'conn') {
currentNet.conn.push(line);
} else if (currentSection === 'cap') {
currentNet.cap.push(line);
} else if (currentSection === 'res') {
currentNet.res.push(line);
}
}
});
}
printProperties() {
console.log('Header:');
for (let key in this.header) {
console.log(` ${key}: ${this.header[key]}`);
}
console.log('\nName Map:');
for (let key in this.nameMap) {
console.log(` ${key}: ${this.nameMap[key]}`);
}
console.log('\nPorts:');
this.ports.forEach(port => console.log(` ${port}`));
console.log('\nNets:');
this.nets.forEach(net => {
console.log(` ${net.net}`);
console.log(' Connections:');
net.conn.forEach(conn => console.log(` ${conn}`));
console.log(' Capacitances:');
net.cap.forEach(cap => console.log(` ${cap}`));
console.log(' Resistances:');
net.res.forEach(res => console.log(` ${res}`));
});
}
write() {
let content = '';
for (let key in this.header) {
content += this.header[key] + '\n';
}
content += '*NAME_MAP\n';
for (let key in this.nameMap) {
content += key + ' ' + this.nameMap[key] + '\n';
}
content += '*PORTS\n';
this.ports.forEach(port => content += port + '\n');
this.nets.forEach(net => {
content += net.net + '\n';
content += '*CONN\n';
net.conn.forEach(conn => content += conn + '\n');
content += '*CAP\n';
net.cap.forEach(cap => content += cap + '\n');
content += '*RES\n';
net.res.forEach(res => content += res + '\n');
content += '*END\n';
});
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'output.spf';
a.click();
URL.revokeObjectURL(url);
}
}
To use:
const parser = new SPFParser();
parser.open(fileObject, () => {
parser.printProperties();
parser.write();
});
- The following is a C++ class for handling .SPF files. It can open, decode, read, write, and print the properties to console.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
using std::string;
using std::vector;
using std::map;
using std::ifstream;
using std::ofstream;
using std::cout;
using std::endl;
class SPFParser {
private:
map<string, string> header;
map<string, string> nameMap;
vector<string> ports;
vector<map<string, vector<string>>> nets; // net -> conn, cap, res
public:
void open(const string& filepath) {
ifstream file(filepath);
string line;
string currentSection = "";
map<string, vector<string>> currentNet;
string currentNetName;
while (std::getline(file, line)) {
line = line.substr(0, line.find('//')); // Remove comments
if (line.empty()) continue;
if (line.find("*SPEF") == 0) header["spef"] = line;
else if (line.find("*DESIGN") == 0) header["design"] = line;
else if (line.find("*DATE") == 0) header["date"] = line;
else if (line.find("*VENDOR") == 0) header["vendor"] = line;
else if (line.find("*PROGRAM") == 0) header["program"] = line;
else if (line.find("*VERSION") == 0) header["version"] = line;
else if (line.find("*DIVIDER") == 0) header["divider"] = line;
else if (line.find("*DELIMITER") == 0) header["delimiter"] = line;
else if (line.find("*BUS_DELIMITER") == 0) header["busDelimiter"] = line;
else if (line.find("*T_UNIT") == 0) header["timeUnit"] = line;
else if (line.find("*C_UNIT") == 0) header["capUnit"] = line;
else if (line.find("*R_UNIT") == 0) header["resUnit"] = line;
else if (line.find("*L_UNIT") == 0) header["indUnit"] = line;
else if (line.find("*NAME_MAP") == 0) currentSection = "nameMap";
else if (line.find("*PORTS") == 0) currentSection = "ports";
else if (line.find("*D_NET") == 0) {
if (!currentNet.empty()) nets.push_back(currentNet);
currentNet.clear();
currentNetName = line;
currentSection = "net";
} else if (line.find("*CONN") == 0) currentSection = "conn";
else if (line.find("*CAP") == 0) currentSection = "cap";
else if (line.find("*RES") == 0) currentSection = "res";
else if (line.find("*END") == 0) {
if (!currentNet.empty()) nets.push_back(currentNet);
currentSection = "";
} else {
if (currentSection == "nameMap") {
size_t space = line.find(' ');
nameMap[line.substr(0, space)] = line.substr(space + 1);
} else if (currentSection == "ports") {
ports.push_back(line);
} else if (currentSection == "net") {
currentNet["net"].push_back(line);
} else if (currentSection == "conn") {
currentNet["conn"].push_back(line);
} else if (currentSection == "cap") {
currentNet["cap"].push_back(line);
} else if (currentSection == "res") {
currentNet["res"].push_back(line);
}
}
}
if (!currentNet.empty()) nets.push_back(currentNet);
file.close();
}
void printProperties() {
cout << "Header:" << endl;
for (auto& pair : header) {
cout << " " << pair.first << ": " << pair.second << endl;
}
cout << "\nName Map:" << endl;
for (auto& pair : nameMap) {
cout << " " << pair.first << ": " << pair.second << endl;
}
cout << "\nPorts:" << endl;
for (auto& port : ports) {
cout << " " << port << endl;
}
cout << "\nNets:" << endl;
for (auto& net : nets) {
cout << " Net: " << net["net"][0] << endl;
cout << " Connections:" << endl;
for (auto& conn : net["conn"]) {
cout << " " << conn << endl;
}
cout << " Capacitances:" << endl;
for (auto& cap : net["cap"]) {
cout << " " << cap << endl;
}
cout << " Resistances:" << endl;
for (auto& res : net["res"]) {
cout << " " << res << endl;
}
}
}
void write(const string& filepath) {
ofstream file(filepath);
for (auto& pair : header) {
file << pair.second << endl;
}
file << "*NAME_MAP" << endl;
for (auto& pair : nameMap) {
file << pair.first << " " << pair.second << endl;
}
file << "*PORTS" << endl;
for (auto& port : ports) {
file << port << endl;
}
for (auto& net : nets) {
file << net["net"][0] << endl;
file << "*CONN" << endl;
for (auto& conn : net["conn"]) {
file << conn << endl;
}
file << "*CAP" << endl;
for (auto& cap : net["cap"]) {
file << cap << endl;
}
file << "*RES" << endl;
for (auto& res : net["res"]) {
file << res << endl;
}
file << "*END" << endl;
}
file.close();
}
};
To use:
SPFParser parser;
parser.open("example.spf");
parser.printProperties();
parser.write("output.spf");