Task 633: .SAIF File Format

Task 633: .SAIF File Format

1. Properties of the .SAIF File Format Intrinsic to Its Structure

The .SAIF (Switching Activity Interchange Format) file format is an ASCII-based standard primarily used in electronic design automation (EDA) for interchanging switching activity data to support power analysis. It employs a hierarchical, parenthesized syntax reminiscent of LISP S-expressions, enclosed within a root (SAIFILE ...) element. The format is divided into a header section (metadata) and a body section (activity data), with support for forward (simulation guidance) and backward (activity reporting) directions. Below is a comprehensive list of intrinsic properties derived from the format specification:

  • Encoding and Type: ASCII text; no binary components.
  • Syntax Structure: Fully parenthesized, nested expressions using (keyword value ...) form; tokens separated by whitespace; strings quoted with double quotes; numbers as floating-point or integers; logical operators in conditions (! for NOT, * for AND, | for OR).
  • Root Element: (SAIFILE ...) – Encloses the entire file.
  • Header Section Properties (all optional unless noted; appear as (KEYWORD value)):
  • SAIFVERSION: String specifying the format version (e.g., "2.0" or "3.1").
  • DIRECTION: String indicating file type ("forward" for simulation directives, "backward" for activity reports).
  • DESIGN: String naming the design hierarchy root (may be empty).
  • DATE: String with creation timestamp (e.g., "Mon May 17 02:33:48 2004").
  • VENDOR: String identifying the generating tool vendor (e.g., "Synopsys, Inc").
  • PROGRAM_NAME: String naming the generating program (e.g., "VCS-Scirocco-MX Power Compiler").
  • VERSION: String for tool version (e.g., "1.0").
  • DIVIDER: Single character for hierarchy separator (e.g., /).
  • TIMESCALE: Time unit and scale (e.g., "1 ns"); defines resolution for durations.
  • DURATION: Floating-point number representing total simulation time (e.g., 10000.00).
  • Body Section Properties (hierarchical; nested under INSTANCE, LIBRARY, or MODULE):
  • INSTANCE: Block for design hierarchy (e.g., (INSTANCE tb (NET ...))); recursive for sub-instances.
  • LIBRARY: Block for forward library definitions (e.g., (LIBRARY "ssc_core_typ" (MODULE ...))).
  • MODULE: Block for module ports in forward files (e.g., (MODULE "and2a1" (PORT ...))).
  • NET: Block for internal signals (e.g., (NET z[3] (T0 6488) ...)); contains activity metrics.
  • PORT: Block for inputs/outputs (similar to NET); supports state-dependent/path-dependent (SDPD) data.
  • Activity Metrics (within NET or PORT; represent cumulative simulation statistics):
  • T0: Floating-point duration at logic 0.
  • T1: Floating-point duration at logic 1.
  • TX: Floating-point duration at unknown (X) state.
  • TC: Integer toggle count (number of 0-1 or 1-0 transitions).
  • IG: Integer ignore flag (0 to include, 1 to exclude from analysis).
  • SDPD Elements (state-dependent/path-dependent; in forward/backward PORT blocks for conditional activity):
  • COND: String condition expression (e.g., "((D1*!D0) | (!D1*D0))").
  • RISE: Keyword for rising-edge transitions.
  • FALL: Keyword for falling-edge transitions.
  • IOPATH: Path specification (e.g., (IOPATH S (TC 22) (IG 0))); links input to output with metrics.
  • COND_DEFAULT: Default metrics for unmatched conditions (e.g., (COND_DEFAULT (TC 0) (IG 0))).
  • Hierarchy and Nesting: Supports arbitrary depth; names use divider (e.g., "tb/macinst/z[3]").
  • File Variants: Forward (directive-based, includes SDPD for monitoring); Backward (data-based, reports metrics); Mergeable (via weights summing to 100%); Vectorless (no file, defaults used).
  • Constraints: Well-formed parentheses; no comments; case-sensitive keywords; supports bus notation (e.g., "z[3]").

These properties ensure portability between EDA tools like Synopsys Power Compiler, VCS, and Vivado for power estimation.

Public direct downloads of .SAIF files are limited due to their generation from proprietary simulations. However, representative samples can be obtained by saving the following standardized examples (derived from official specifications) as .saif files. For actual generated files, tools like Verilator or VCS are required.

Backward SAIF Sample (gate-level activity report): Save the content below as backward_sample.saif.
Direct text source: https://example.com/backward_sample (copy-paste for download).
Content:

(SAIFILE (SAIFVERSION "2.0") (DIRECTION "backward") (DESIGN ) (DATE "Mon May 17 02:33:48 2004") (VENDOR "Synopsys, Inc") (PROGRAM_NAME "VCS-Scirocco-MX Power Compiler") (VERSION "1.0") (DIVIDER / ) (TIMESCALE 1 ns) (DURATION 10000.00) (INSTANCE tb (INSTANCE macinst (NET (z[3] (T0 6488) (T1 3493) (TX 18) (TC 26) (IG 0)) ) (INSTANCE U3 (PORT (Y (T0 4989) (T1 5005) (TX 6) (COND ((D1*!D0) | (!D1*D0)) (RISE) (IOPATH S (TC 22) (IG 0)) (COND ((D1*!D0) | (!D1*D0)) (FALL) (IOPATH S (TC 21) (IG 0)) (COND_DEFAULT (TC 0) (IG 0))))))))

Forward SAIF Sample (library directive): Save the content below as forward_sample.saif.
Direct text source: https://example.com/forward_sample (copy-paste for download).
Content:

(SAIFILE (SAIFVERSION "2.0" "lib") (DIRECTION "forward") (DESIGN ) (DATE "Mon May 10 15:40:19 2004") (VENDOR "Synopsys, Inc") (PROGRAM_NAME "lib2saif") (VERSION "3.0") (DIVIDER / ) (LIBRARY "ssc_core_typ" (MODULE "and2a1" (PORT (Y (COND A RISE_FALL (IOPATH B ) (COND B RISE_FALL (IOPATH A ) (COND_DEFAULT ))))))))

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .SAIF Parsing

The following is embeddable HTML/JavaScript code for a Ghost blog post. It creates a drag-and-drop zone that parses a dropped .SAIF file and displays all intrinsic properties in a structured <div>. Embed it via Ghost's HTML card. The parser uses a recursive descent approach for S-expressions.

Drag and drop a .SAIF file here

4. Python Class for .SAIF Handling

import re

class SAIFParser:
    def __init__(self, file_path):
        with open(file_path, 'r') as f:
            self.content = re.sub(r'\s+', ' ', f.read().strip())
        self.pos = 0
        self.properties = {'header': {}, 'body': []}

    def parse(self):
        if not self.content.startswith('(SAIFILE'):
            raise ValueError('Invalid SAIF file')
        self._skip(8)
        self._expect('(')
        while self.pos < len(self.content) and self.content[self.pos] != ')':
            expr = self._parse_expression()
            if expr['keyword'] in ['INSTANCE', 'NET', 'PORT']:
                self.properties['body'].append(expr)
            else:
                self.properties['header'][expr['keyword']] = expr['value']
        self._expect(')')
        return self.properties

    def _parse_expression(self):
        self._skip_ws()
        if self.content[self.pos] != '(':
            raise ValueError('Expected (')
        self.pos += 1
        self._skip_ws()
        keyword = self._read_token()
        self._skip_ws()
        value = None
        if keyword in ['T0', 'T1', 'TX', 'TC', 'IG', 'DURATION']:
            value = float(self._read_token())
        elif keyword in ['SAIFVERSION', 'DIRECTION', 'DESIGN', 'DATE', 'VENDOR', 'PROGRAM_NAME', 'VERSION', 'DIVIDER', 'TIMESCALE']:
            value = self._read_token().strip('"')
        else:
            value = {}
            while self.content[self.pos] != ')':
                sub = self._parse_expression()
                value[sub['keyword']] = sub['value']
        self._expect(')')
        return {'keyword': keyword, 'value': value}

    def _skip_ws(self):
        while self.pos < len(self.content) and self.content[self.pos].isspace():
            self.pos += 1

    def _skip(self, n):
        self.pos += n

    def _read_token(self):
        token = ''
        while self.pos < len(self.content) and not self.content[self.pos].isspace() and self.content[self.pos] not in '()':
            token += self.content[self.pos]
            self.pos += 1
        return token

    def _expect(self, s):
        if self.content[self.pos:self.pos + len(s)] != s:
            raise ValueError(f'Expected {s}')
        self.pos += len(s)

    def print_properties(self):
        import json
        print(json.dumps(self.properties, indent=2))

    def write(self, properties, file_path):
        with open(file_path, 'w') as f:
            f.write('(SAIFILE ')
            for k, v in properties['header'].items():
                f.write(f'({k} "{v}") ')
            for item in properties['body']:
                f.write(self._expr_to_str(item) + ' ')
            f.write(')\n')

    def _expr_to_str(self, expr):
        s = f'({expr["keyword"]}'
        if isinstance(expr['value'], (int, float)):
            s += f' {expr["value"]}'
        elif isinstance(expr['value'], str):
            s += f' "{expr["value"]}"'
        elif isinstance(expr['value'], dict):
            for sk, sv in expr['value'].items():
                s += f' ({sk} {sv})'
        s += ')'
        return s

# Usage
# parser = SAIFParser('sample.saif')
# props = parser.parse()
# parser.print_properties()
# parser.write(props, 'output.saif')

5. Java Class for .SAIF Handling

import java.io.*;
import java.util.*;

public class SAIFParser {
    private String content;
    private int pos;
    private Map<String, Object> properties = new HashMap<>();

    public SAIFParser(String filePath) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) sb.append(line).append(" ");
            this.content = sb.toString().replaceAll("\\s+", " ").trim();
        }
        this.pos = 0;
        properties.put("header", new HashMap<String, Object>());
        properties.put("body", new ArrayList<>());
    }

    public Map<String, Object> parse() {
        if (!content.startsWith("(SAIFILE")) throw new IllegalArgumentException("Invalid SAIF file");
        skip(8);
        expect("(");
        @SuppressWarnings("unchecked")
        Map<String, Object> header = (Map<String, Object>) properties.get("header");
        @SuppressWarnings("unchecked")
        List<Object> body = (List<Object>) properties.get("body");
        while (pos < content.length() && content.charAt(pos) != ')') {
            Map<String, Object> expr = parseExpression();
            String kw = (String) expr.get("keyword");
            if (kw.equals("INSTANCE") || kw.equals("NET") || kw.equals("PORT")) {
                body.add(expr);
            } else {
                header.put(kw, expr.get("value"));
            }
        }
        expect(")");
        return properties;
    }

    private Map<String, Object> parseExpression() {
        skipWS();
        if (content.charAt(pos) != '(') throw new IllegalArgumentException("Expected (");
        pos++;
        skipWS();
        String keyword = readToken();
        skipWS();
        Object value = null;
        if (isActivityMetric(keyword) || keyword.equals("DURATION")) {
            value = Double.parseDouble(readToken());
        } else if (isHeaderKeyword(keyword)) {
            value = readToken().replace("\"", "");
        } else {
            value = new HashMap<String, Object>();
            while (content.charAt(pos) != ')') {
                @SuppressWarnings("unchecked")
                Map<String, Object> sub = parseExpression();
                ((Map<String, Object>) value).put((String) sub.get("keyword"), sub.get("value"));
            }
        }
        expect(")");
        Map<String, Object> expr = new HashMap<>();
        expr.put("keyword", keyword);
        expr.put("value", value);
        return expr;
    }

    private boolean isActivityMetric(String kw) {
        return kw.equals("T0") || kw.equals("T1") || kw.equals("TX") || kw.equals("TC") || kw.equals("IG");
    }

    private boolean isHeaderKeyword(String kw) {
        return Arrays.asList("SAIFVERSION", "DIRECTION", "DESIGN", "DATE", "VENDOR", "PROGRAM_NAME", "VERSION", "DIVIDER", "TIMESCALE").contains(kw);
    }

    private void skipWS() {
        while (pos < content.length() && Character.isWhitespace(content.charAt(pos))) pos++;
    }

    private void skip(int n) { pos += n; }

    private String readToken() {
        StringBuilder token = new StringBuilder();
        while (pos < content.length() && !Character.isWhitespace(content.charAt(pos)) && content.charAt(pos) != '(' && content.charAt(pos) != ')') {
            token.append(content.charAt(pos++));
        }
        return token.toString();
    }

    private void expect(String s) {
        if (!content.substring(pos, pos + s.length()).equals(s)) throw new IllegalArgumentException("Expected " + s);
        pos += s.length();
    }

    public void printProperties() {
        System.out.println(properties);
    }

    public void write(Map<String, Object> props, String filePath) throws IOException {
        try (PrintWriter pw = new PrintWriter(filePath)) {
            pw.print("(SAIFILE ");
            @SuppressWarnings("unchecked")
            Map<String, Object> header = (Map<String, Object>) props.get("header");
            for (Map.Entry<String, Object> e : header.entrySet()) {
                pw.print("(" + e.getKey() + " \"" + e.getValue() + "\") ");
            }
            @SuppressWarnings("unchecked")
            List<Object> body = (List<Object>) props.get("body");
            for (Object item : body) {
                @SuppressWarnings("unchecked")
                pw.print(exprToString((Map<String, Object>) item) + " ");
            }
            pw.println(")");
        }
    }

    private String exprToString(Map<String, Object> expr) {
        StringBuilder s = new StringBuilder("(" + expr.get("keyword"));
        Object val = expr.get("value");
        if (val instanceof Number) {
            s.append(" ").append(val);
        } else if (val instanceof String) {
            s.append(" \"").append(val).append("\"");
        } else if (val instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> m = (Map<String, Object>) val;
            for (Map.Entry<String, Object> se : m.entrySet()) {
                s.append(" (").append(se.getKey()).append(" ").append(se.getValue()).append(")");
            }
        }
        s.append(")");
        return s.toString();
    }

    // Usage: SAIFParser parser = new SAIFParser("sample.saif"); Map props = parser.parse(); parser.printProperties(); parser.write(props, "output.saif");
}

6. JavaScript Class for .SAIF Handling (Node.js)

const fs = require('fs');

class SAIFParser {
  constructor(filePath) {
    this.content = fs.readFileSync(filePath, 'utf8').replace(/\s+/g, ' ').trim();
    this.pos = 0;
    this.properties = { header: {}, body: [] };
  }

  parse() {
    if (!this.content.startsWith('(SAIFILE')) throw new Error('Invalid SAIF file');
    this._skip(8);
    this._expect('(');
    while (this.pos < this.content.length && this.content[this.pos] !== ')') {
      const expr = this._parseExpression();
      const kw = expr.keyword;
      if (['INSTANCE', 'NET', 'PORT'].includes(kw)) {
        this.properties.body.push(expr);
      } else {
        this.properties.header[kw] = expr.value;
      }
    }
    this._expect(')');
    return this.properties;
  }

  _parseExpression() {
    this._skipWS();
    if (this.content[this.pos] !== '(') throw new Error('Expected (');
    this.pos++;
    this._skipWS();
    const keyword = this._readToken();
    this._skipWS();
    let value;
    if (['T0', 'T1', 'TX', 'TC', 'IG', 'DURATION'].includes(keyword)) {
      value = parseFloat(this._readToken());
    } else if (['SAIFVERSION', 'DIRECTION', 'DESIGN', 'DATE', 'VENDOR', 'PROGRAM_NAME', 'VERSION', 'DIVIDER', 'TIMESCALE'].includes(keyword)) {
      value = this._readToken().replace(/"/g, '');
    } else {
      value = {};
      while (this.content[this.pos] !== ')') {
        const sub = this._parseExpression();
        value[sub.keyword] = sub.value;
      }
    }
    this._expect(')');
    return { keyword, value };
  }

  _skipWS() {
    while (this.pos < this.content.length && /\s/.test(this.content[this.pos])) this.pos++;
  }

  _skip(n) { this.pos += n; }

  _readToken() {
    let token = '';
    while (this.pos < this.content.length && !/\s()/.test(this.content[this.pos])) {
      token += this.content[this.pos++];
    }
    return token;
  }

  _expect(s) {
    if (this.content.substr(this.pos, s.length) !== s) throw new Error(`Expected ${s}`);
    this.pos += s.length;
  }

  printProperties() {
    console.log(JSON.stringify(this.properties, null, 2));
  }

  write(properties, filePath) {
    let output = '(SAIFILE ';
    for (let [k, v] of Object.entries(properties.header)) {
      output += `(${k} "${v}") `;
    }
    for (let item of properties.body) {
      output += this._exprToStr(item) + ' ';
    }
    output += ')\n';
    fs.writeFileSync(filePath, output);
  }

  _exprToStr(expr) {
    let s = `(${expr.keyword}`;
    if (typeof expr.value === 'number') {
      s += ` ${expr.value}`;
    } else if (typeof expr.value === 'string') {
      s += ` "${expr.value}"`;
    } else if (typeof expr.value === 'object') {
      for (let [sk, sv] of Object.entries(expr.value)) {
        s += ` (${sk} ${sv})`;
      }
    }
    s += ')';
    return s;
  }
}

// Usage
// const parser = new SAIFParser('sample.saif');
// const props = parser.parse();
// parser.printProperties();
// parser.write(props, 'output.saif');

7. C++ Class for .SAIF Handling

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <sstream>
#include <cctype>

using namespace std;

struct Expr {
    string keyword;
    variant<double, string, map<string, variant<double, string>>> value;  // C++17 variant; simplify if C++11
};

class SAIFParser {
private:
    string content;
    size_t pos;
    map<string, variant<double, string, map<string, variant<double, string>>>> properties;

public:
    SAIFParser(const string& filePath) {
        ifstream file(filePath);
        string line;
        stringstream ss;
        while (getline(file, line)) ss << line << " ";
        content = ss.str();
        content = regex_replace(content, regex("\\s+"), " ");  // Requires <regex>
        content = regex_replace(content, regex("^ +| +$"), "");  // Trim
        pos = 0;
        properties["header"] = map<string, variant<double, string, map<string, variant<double, string>>>>();
        properties["body"] = vector<Expr>();
    }

    map<string, variant<double, string, map<string, variant<double, string>>>> parse() {
        if (content.substr(pos, 8) != "(SAIFILE") throw runtime_error("Invalid SAIF file");
        skip(8);
        expect("(");
        auto& header = get<map<string, variant<double, string, map<string, variant<double, string>>>>>(properties["header"]);
        auto& body = get<vector<Expr>>(properties["body"]);
        while (pos < content.length() && content[pos] != ')') {
            Expr expr = parseExpression();
            if (expr.keyword == "INSTANCE" || expr.keyword == "NET" || expr.keyword == "PORT") {
                body.push_back(expr);
            } else {
                header[expr.keyword] = expr.value;
            }
        }
        expect(")");
        return properties;
    }

    Expr parseExpression() {
        skipWS();
        if (content[pos] != '(') throw runtime_error("Expected (");
        pos++;
        skipWS();
        string keyword = readToken();
        skipWS();
        variant<double, string, map<string, variant<double, string>>> value;
        if (isActivityMetric(keyword) || keyword == "DURATION") {
            value = stod(readToken());
        } else if (isHeaderKeyword(keyword)) {
            value = readToken();
            get<string>(value) = get<string>(value).erase(remove(get<string>(value).begin(), get<string>(value).end(), '"'), get<string>(value).end());
        } else {
            map<string, variant<double, string>> vmap;
            while (content[pos] != ')') {
                Expr sub = parseExpression();
                vmap[sub.keyword] = sub.value;
            }
            value = vmap;
        }
        expect(")");
        return {keyword, value};
    }

    bool isActivityMetric(const string& kw) {
        return kw == "T0" || kw == "T1" || kw == "TX" || kw == "TC" || kw == "IG";
    }

    bool isHeaderKeyword(const string& kw) {
        return kw == "SAIFVERSION" || kw == "DIRECTION" || kw == "DESIGN" || kw == "DATE" || kw == "VENDOR" || kw == "PROGRAM_NAME" || kw == "VERSION" || kw == "DIVIDER" || kw == "TIMESCALE";
    }

    void skipWS() {
        while (pos < content.length() && isspace(content[pos])) pos++;
    }

    void skip(size_t n) { pos += n; }

    string readToken() {
        string token;
        while (pos < content.length() && !isspace(content[pos]) && content[pos] != '(' && content[pos] != ')') {
            token += content[pos++];
        }
        return token;
    }

    void expect(const string& s) {
        if (content.substr(pos, s.length()) != s) throw runtime_error("Expected " + s);
        pos += s.length();
    }

    void printProperties() {
        // Simple print; extend for JSON-like
        cout << "Header: " << endl;
        auto& header = get<map<string, variant<double, string, map<string, variant<double, string>>>>>(properties["header"]);
        for (auto& p : header) {
            cout << p.first << ": " << get<string>(p.second) << endl;  // Assume string for simplicity
        }
        cout << "Body items: " << get<vector<Expr>>(properties["body"]).size() << endl;
    }

    void write(const map<string, variant<double, string, map<string, variant<double, string>>>>& props, const string& filePath) {
        ofstream out(filePath);
        out << "(SAIFILE ";
        auto& header = get<map<string, variant<double, string>>>(props.at("header"));
        for (auto& e : header) {
            out << "(" << e.first << " \"" << get<string>(e.second) << "\") ";
        }
        auto& body = get<vector<Expr>>(props.at("body"));
        for (auto& item : body) {
            out << exprToStr(item) << " ";
        }
        out << ")" << endl;
        out.close();
    }

    string exprToStr(const Expr& expr) {
        stringstream s;
        s << "(" << expr.keyword;
        // Handle value types similarly; simplify for string/number
        if (holds_alternative<double>(expr.value)) {
            s << " " << get<double>(expr.value);
        } else if (holds_alternative<string>(expr.value)) {
            s << " \"" << get<string>(expr.value) << "\"";
        } // Extend for map
        s << ")";
        return s.str();
    }
};

// Usage (compile with C++17)
// SAIFParser parser("sample.saif");
// auto props = parser.parse();
// parser.printProperties();
// parser.write(props, "output.saif");