#!/usr/local/bin/perl # $Id: bitgen.pl,v 1.23 2000/10/16 18:25:30 awglaser Exp $ require 5.000; # Also, due to Tk-GBARR, Perl/Tk 402.002 or later is required. use lib qw(/home/mcsg/misc/Tk-GBARR-2.06/lib/perl5/5.8.0 /home/mcsg/misc/Tk-GBARR-2.06/lib/perl5/site_perl/5.8.0 /home/ramirtha/Html/EEC216/W06/Tk-GBARR-2.06/lib/perl5/5.8.0 /home/ramirtha/Html/EEC216/W06/Tk-GBARR-2.06/lib/perl5/site_perl/5.8.0); use strict "subs"; use strict "vars"; use Tk; # Perl/Tk distribution use Tk::Balloon; # Perl/Tk distribution use Tk::Pane; # Tk-GBARR or Perl/Tk distribution 800.015 (I think) use Tk::TFrame; # Tk-GBARR use vars qw( $opt_b $opt_h ); use Getopt::Std; sub doExit; sub doPlot(); sub makeSigviewHeader; sub doMessageBox; sub doNewSource; sub dupSrc; sub setBalloonHelp; sub createNewSourceWidgets; sub resetSrc; sub deleteSrc; sub doExport; sub doSave; sub doOpen; sub doBitWave; sub doBusWave; sub doClockWave; sub doDCWave; sub printBitWave; sub getParameters; sub getOutfileHandle; sub expandDataStream; sub showSourceListWindow; sub addToSourceListWindow; sub setBallonHelpState; sub setMainWindowTitle; sub createOptionsWindow; sub createHelpWindow; sub createAboutWindow; sub setupGUI; sub busyCursor; sub assignElement; sub get; # waveform characteristics' defaults my $RT_DEFAULT = "1n"; # rise time (seconds) my $FT_DEFAULT = "1n"; # fall time (seconds) my $RT_DEFAULT_CLK = "10%"; # rise time (percentage of period, for clock) my $FT_DEFAULT_CLK = "10%"; # fall time (percentage of period, for clock) my $PW_DEFAULT = "10n"; # pulse width (seconds) my $ON_DEFAULT = "3.3"; # logic "1" (volts) my $OFF_DEFAULT = "0"; # logic "0" (volts) my $DELAY_DEFAULT = "0"; # initial delay (seconds) my $NEGNODE_DEFAULT = "0"; # default negative node for voltage source (text string) my $FREQ_DEFAULT = "50e6"; # default frequency for clock sources my $DUTYCYCLE_DEFAULT = "50%"; # default duty cycle for clock sources my $DC_DEFAULT = 3.3; # default DC voltage # version string my $BitGen_Version = "2.0b"; # first line of file to mark native BitGen format my $BitGen_Signature = "@@@ BitGen input file"; # file types for opening/saving BitGen files my $BitGen_Filetypes = [ ['BitGen Files', '.bitgen'], ['All Files', '*' ] ]; # parameters for waveforms my( $rt, $ft, $pw, $freq, $dutyCycle, $on, $off, $delay, $initVal, $dc ); # default entry fields my( $rtDefaultEntry, $ftDefaultEntry, $rtClkDefaultEntry, $ftClkDefaultEntry ); my( $pwDefaultEntry, $onDefaultEntry, $offDefaultEntry ); my( $delayDefaultEntry, $negnodeDefaultEntry, $freqDefaultEntry ); my( $dutycycleDefaultEntry, $dcDefaultEntry ); # use 'x' for meg so we can use single character for all suffixes my %suffixes = ( 't' => 1e12, 'g' => 1e9, 'x' => 1e6, 'k' => 1e3, 'm' => 1e-3, 'u' => 1e-6, 'n' => 1e-9, 'p' => 1e-12, 'f' => 1e-15, 'a' => 1e-18 ); my( %srcFrames, %srcLabels, %srcEntries ); my( %descFrames, %descLabels, %descEntries ); my( %vecCompsFrames, %vecCompsLabels, %vecCompsEntries ); my( %posNodeFrames, %posNodeLabels, %posNodeEntries ); my( %negNodeFrames, %negNodeLabels, %negNodeEntries ); my( %dataFrames, %dataLabels, %dataEntries ); my( %dcFrames, %dcLabels, %dcEntries ); my( %rtFrames, %rtLabels, %rtEntries ); my( %ftFrames, %ftLabels, %ftEntries ); my( %pwFrames, %pwLabels, %pwEntries ); my( %freqFrames, %freqLabels, %freqEntries ); my( %dutyCycleFrames, %dutyCycleLabels, %dutyCycleEntries ); my( %onFrames, %onLabels, %onEntries ); my( %offFrames, %offLabels, %offEntries ); my( %delayFrames, %delayLabels, %delayEntries ); my( %initValFrames, %initValLabels, %initValEntries ); my( %resetSrcButtons, %deleteSrcButtons, %dupSrcButtons ); my( %sourceEnclosures, %sourceTypes ); my( %sourceListFrames, %sourceListButtons, %sourceListExpCheckB ); my( %isExportable ); # for keeping all the data before printing out my @sigviewData; my $maxTime = 0; my $sigDataCount = 0; my $sigviewCommand = "/afs/eos/dist/cad/bin/sigview"; my $makePlotFile; # boolean flag my $plotFile = "/tmp/sigviewfile_$<"; # currently selected netlist language my $netlistLang = "CDS"; # comment characters my %commentChar = ( "CDS" => "*", "SPICE" => "*", "Spectre" => "*", "2-Column" => "*" ); # types of sources that can be added my $newSourceTypeList = [qw( single bus clock DC )]; # currently selected source type my $newSourceType; # balloon help is initially on my $doBalloonHelp = 1; # source list window my $showSourceList = 1; # to see if user has changed anything my $needSaveFlag = 0; # start numbering sources at ++$sourceNum my $sourceNum = "0"; # keep track of how many sources there are my $numSources = 0; # if true, when duplicating a (single) source, invert all the bits # (useful for making a source's complement) my $invertDupSrc = 0; # for use when selecting "Save" and "Export" (as opposed to "Save as" and # "Export as") actions my $currentFilename = undef; my $currentNetlistname = undef; # bitmap file for icon my $iconBitmap = "/afs/eos/dist/cadence/local/bin/bitgen.xbm"; my( $main, $sourceFrame, $sourcePane, $helpWin, $aboutWin, $optionsWin ); my( $fileMenu, $settingsMenu, $helpMenu ); my( $srcTypeMenu, $sourceListWindow, $sourceListPane, $sourceListBalloon, $mainBalloon ); my( $addSrcButton, $saveButton, $exportButton, $plotButton, $exitButton ); my $tty = 0; my $dataFile = ""; getopt( 'b' ); if( defined( $opt_h ) ) { print "Usage: $0 [-h] [-b filename|filename]\n"; print " -h : print this message and exit\n"; print " -b filename : convert \"filename\" to SPICE format (no GUI)\n"; print " filename : open \"filename\" with GUI\n"; exit( 0 ); } if( defined( $opt_b ) ) { $tty = 1; $dataFile = $opt_b; } elsif( $#ARGV == 0 ) { $dataFile = $ARGV[0]; } setupGUI() unless $tty; if( $dataFile ne "" ) { $main->update() unless $tty; doOpen( $dataFile ); } else { setMainWindowTitle(); } if( $tty ) { exit( 0 ); } else { widgetStatus(); MainLoop; } ############################################################### sub setupGUI{ # main window $main = new MainWindow; $main->protocol( "WM_DELETE_WINDOW", \&doExit ); # key bindings $main->bind( 'all', '' => sub { doExit(); } ); $main->bind( 'all', '' => sub { doPlot(); } ); $main->bind( 'all', '' => sub { doExport( "Export" ); } ); $main->bind( 'all', '' => sub { doSave( "Save" ); } ); $main->bind( 'all', '' => sub { doOpen(); } ); $main->bind( 'all', '' => sub { doNewSource( $newSourceType ); } ); my $menubar = $main->Frame( -relief => "raised", -borderwidth => 2 ) ->pack( -fill => "x" ); # "File" menu $fileMenu = $menubar->Menubutton( -text => "File", -underline => 0, -menuitems => [ [Button => "Open...", -command => sub { doOpen(); }, -underline => 0], [Button => "Save", -command => sub { doSave( "Save" ); }, -underline => 0], [Button => "Save as...", -command => sub { doSave( "SaveAs" ); }, -underline => 5], [Separator => ""], [Button => "Export", -command => sub { doExport( "Export" ); }, -underline => 0], [Button => "Export as...",-command => sub { doExport( "ExportAs" ); }], [Separator => ""], [Button => "Exit", -command => sub { doExit(); }, -underline => 1 ] ] ) ->pack( -side => "left" ); # "Settings" menu $settingsMenu = $menubar->Menubutton( -text => "Settings", -underline => 0, -menuitems => [ [Cascade => 'Netlist language', -menuitems => [ map( [Radiobutton => "$_", -variable => \$netlistLang], (qw(CDS SPICE Spectre 2-Column))) ] ], [Checkbutton => "Balloon help", -command => sub { setBallonHelpState(); }, -variable => \$doBalloonHelp], [Checkbutton => "Show source list", -command => sub {showSourceListWindow();}, -variable => \$showSourceList], [Separator => ''], [Button => "Source defaults...", -command => sub {$optionsWin->deiconify();}] ] ) ->pack( -side => "left" ); # "Help" menu $helpMenu = $menubar->Menubutton( -text => "Help", -underline => 0, -menuitems => [ [Button => "Help...", -command => sub { $helpWin->deiconify(); }], [Button => "About...", -command => sub { $aboutWin->deiconify(); }]]) ->pack( -side => "right" ); # window with list of sources, initially unmapped $sourceListWindow = new MainWindow; $sourceListWindow->withdraw(); $sourceListWindow->configure( -title => "Source List" ); $sourceListWindow->protocol( "WM_DELETE_WINDOW", \sub { $showSourceList = 0; showSourceListWindow(); } ); $sourceListPane = $sourceListWindow->Scrolled( "Pane", Name => "sourceListPane", -scrollbars => "w", -sticky => "ew" ) ->pack( -expand => 1, -fill => "both" ); # $multiFrame is to hold the next widgets; need this frame so when we start # adding sources, they will go beneath these here widgets, instead of beside # them my $multiFrame = $main->Frame()->pack( -fill => "x" ); my $sourceOpsFrame = $multiFrame->TFrame(-label=>"Source Operations") ->pack( -pady => 10, -ipady => 5, -padx => 10, -expand => 1, -anchor => "w", -side => "left" ); $addSrcButton = $sourceOpsFrame->Button( -text => "Add source", -command=>sub{doNewSource($newSourceType);} ) ->pack( -padx => 10, -side =>"left" ); $srcTypeMenu = $sourceOpsFrame->Optionmenu( -options => $newSourceTypeList, -variable => \$newSourceType ) ->pack( -padx => 15, -side => "left"); $exitButton = $multiFrame->Button( -text => "Exit", -command => sub { doExit(); } ) ->pack( -padx => 10, -ipady => 5, -side => "right"); $plotButton = $multiFrame->Button( -text => "Plot", -command => sub { doPlot(); } ) ->pack( -padx => 10, -ipady => 5, -side => "right"); $saveButton = $multiFrame->Button( -text => "Save", -command => sub { doSave( "Save" ); } ) ->pack( -padx => 10, -ipady => 5, -side => "right"); $exportButton = $multiFrame->Button( -text => "Export" ) ->pack( -padx => 10, -ipady => 5, -side => "right"); $exportButton->bind( "", sub { doExport( "Export" ); } ); $exportButton->bind( "", sub { doExport( "Export", "ttyOut" ); } ); # for balloon help $mainBalloon = $main->Balloon(); $sourceListBalloon = $sourceListWindow->Balloon(); setBalloonHelp( "initial" ); createOptionsWindow(); createHelpWindow(); createAboutWindow(); # catch ^C $SIG{'INT'} = \&doExit; # make icon if( -r $iconBitmap ) { $main->iconbitmap( "\@$iconBitmap" ) } } # create "about" window sub createAboutWindow { my $msg = "BitGen $BitGen_Version by Toby Schaffer (toby_schaffer\@ieee.org) BitGen is a program for converting digital bitstreams to analog voltage sources suitable for circuit simulation in programs such as SPICE. Select the \"Help->Help...\" menu entry for usage instructions and examples. Please let me know if you have any suggestions, comments, or questions!"; $aboutWin = $main->Toplevel(); $aboutWin->configure( -title => "About BitGen" ); $aboutWin->protocol( "WM_DELETE_WINDOW", sub {$aboutWin->withdraw();} ); my $text = $aboutWin->Scrolled( "Text", -height => 30, -width => 55, -scrollbars => "e" ) ->pack( -expand => 1, -fill => "both" ); $text->insert( "0.0", $msg ); $text->configure( -state => "disabled" ); $aboutWin->Button( -pady => 10, -text => "Ok", -command => sub{ $aboutWin->withdraw(); } ) ->pack( -side => "bottom" ); $aboutWin->withdraw(); } # create help window sub createHelpWindow { my $msg = '----------------------- Adding a Voltage Source ----------------------- There are two methods to add a new source: - Select the desired type in the source type pop-up menu and click "Add source" or hit ctrl-a. - Click the "Duplicate source" button of an existing source to make a copy of that source. *Shortcut*: if you hold the control key while clicking the "Duplicate source" button, the new source is the *inverse* of the original. For a "single" source, all bits are inverted. For a "clock", the new source is 180 degrees out-of-phase. This shortcut has no effect for "bus" or "DC" sources. ----------------------- Voltage Source Types ----------------------- BitGen understands four different digital source types: single, bus, clock and DC. "Single" is one bit. "Bus" is a vector of bits with a common name and a numeric suffix, e.g., "data<3:0>". "Clock" is a single periodic bit with a user-defined duty cycle. "DC" is a DC (time-invariant) voltage. (See the end of this help for examples of data patterns.) "Single" and "bus" waveforms are exported as piecewise linear (PWL) sources, and "clock" waveforms are exported as pulse sources. ---------------------- The Source List Window ---------------------- When a source is added the source list window is updated with a button for the new source. Clicking the button scrolls the main window to make the corresponding source visible. The button in the source list window uses the text in the source\'s "Description" field as a label, which is dynamically updated. If the "Description" field is empty, the source type ("single", "bus", "clock", or "dc" ) is used instead. If the source list window is unmapped when a source is added, it is unmapped if the "Settings->Show source list" menu item is selected. ---------------------- Source Characteristics ---------------------- Associated with each source type is a: description (optional, saved as a comment in the netlist) source name (defines the name of the source in the netlist) vector (defines the bus name and components for bus sources) + node (defines the positive node; the source name is used if this field is left blank) - node (defines the negative node) Additionally, there is a checkbutton which controls whether or not the source is exported or plotted when those actions occur. See the "Data Examples" section below for examples of the data field format. Waveform characteristics of the single and bus sources are: rt (rise time) Time in seconds for 0-100% transition ft (fall time) Time in seconds for 100%-0 transition pw (pulse width) Width in seconds of a "pulse" (a 0 or 1) (includes rt/ft) on (on voltage) Voltage in volts for a logic 1 off (off voltage) Voltage in volts for a logic 0 delay (initial delay) Time in seconds before waveform begins data (digital bitstream to be converted to analog voltage sources) You can also specify an initial value (0, 1 or X) for a single source. This can be useful to create an inital ramp that does not have a full bit duration. Waveform characteristics of the clock source are: rt (rise time) Time, in seconds or as a percentage of the period, for 0-100% transition ft (fall time) Time, in seconds or as a percentage of the period, for 100%-0 transition freq (frequency) Frequency in hertz duty cycle Ratio of high/low time on (on voltage) Voltage in volts for a logic 1 off (off voltage) Voltage in volts for a logic 0 delay (initial delay) Time in seconds before waveform begins Characteristics of the DC source are: DC Voltage in volts Numeric input can be in scientific notation (eg, 10e6, 5e-9) or can use standard suffixes (eg, 10u, 5.5n) (NOTE: use "m" for milli-, "meg" for mega-.) The characteristics rt, ft, pw, on, off and delay can be assigned text strings as well as numeric values. Using HSPICE\'s ".param" statement, you then assign values to those strings in your netlist. Click the "Reset source" button to reset the source values to their defaults. You can change the default values through the "Settings->Source defaults..." menu entry. ------------------- Exporting a Netlist ------------------- To export a netlist, first select the desired netlist format from the "Settings->Netlist format" menu entry, then either click "Export" or select the "File->Export" menu entry. The available netlist formats are: CDS (for spectreS/hspiceS inside Analog Artist or Cadence cdsspice) SPICE (for HSPICE and other SPICE variants) Spectre (for Cadence Spectre) 2-Column (time/voltage pairs) If you control-click the Export button, the netlist will be written to stdout (i.e., the terminal window), otherwise you will be prompted for a file name if you have not previously exported a netlist. BitGen does fairly little error-checking, so if you get unexpected output, double-check the input. --------------------- Plotting Waveforms --------------------- All waveforms are extended out in time to be of equal duration for plotting purposes. A source must be exportable to be plotted, since the purpose of the plot is to indicate what you get when you export a netlist. --------------------- Handling BitGen Files --------------------- BitGen files are saved by clicking "Save" or selecting the "File->Save" menu entry. BitGen files are opened by selecting the "File->Open" menu entry or hitting ctrl-o. You can also specify a filename as a command-line argument when invoking BitGen. ------------- Data Examples ------------- Below are some examples of the "Data" field. * Four different ways to make three 0110 nibbles in a "single" source: 011001100110 enumerated 0\'s and 1\'s 0110 0110 0110 whitespace is ok 3(0 2(1) 0) repeat factor, can be nested 3(0x6) "0x" signifies hex format * Note that before a repeat factor whitespace is significant: 1 2(1) three 1\'s 12(1) twelve 1\'s * Two ways to assign a "marching one" pattern to a four-bit bus, MSB high first: 1000 0100 0010 0001 group bus bits together in binary 0x8 0x4 0x2 0x1 or in hex '; $helpWin = $main->Toplevel(); $helpWin->configure( -title => "BitGen Help" ); $helpWin->protocol( "WM_DELETE_WINDOW", sub { $helpWin->withdraw(); } ); my $text = $helpWin->Scrolled( "Text", -height => 35, -width => 75, -scrollbars => "e" ) ->pack( -expand => 1, -fill => "both" ); $text->insert( "0.0", $msg ); $text->configure( -state => "disabled" ); $helpWin->Button( -pady => 10, -text => "Ok", -command => sub { $helpWin->withdraw(); } ) ->pack( -side => "bottom" ); $helpWin->withdraw(); } sub closeOptionsWin { my( $mode ) = @_; my $tmp; if( $mode eq "ok" or $mode eq "apply" ) { $tmp = $rtDefaultEntry->get(); $RT_DEFAULT = $tmp unless $tmp =~ /^\s*$/; $tmp = $ftDefaultEntry->get(); $FT_DEFAULT = $tmp unless $tmp =~ /^\s*$/; $tmp = $rtClkDefaultEntry->get(); $RT_DEFAULT_CLK = $tmp unless $tmp =~ /^\s*$/; $tmp = $ftClkDefaultEntry->get(); $FT_DEFAULT_CLK = $tmp unless $tmp =~ /^\s*$/; $tmp = $pwDefaultEntry->get(); $PW_DEFAULT = $tmp unless $tmp =~ /^\s*$/; $tmp = $onDefaultEntry->get(); $ON_DEFAULT = $tmp unless $tmp =~ /^\s*$/; $tmp = $offDefaultEntry->get(); $OFF_DEFAULT = $tmp unless $tmp =~ /^\s*$/; $tmp = $delayDefaultEntry->get(); $DELAY_DEFAULT = $tmp unless $tmp =~ /^\s*$/; $tmp = $negnodeDefaultEntry->get(); $NEGNODE_DEFAULT = $tmp unless $tmp =~ /^\s*$/; $tmp = $freqDefaultEntry->get(); $FREQ_DEFAULT = $tmp unless $tmp =~ /^\s*$/; $tmp = $dutycycleDefaultEntry->get(); $DUTYCYCLE_DEFAULT = $tmp unless $tmp =~ /^\s*$/; $tmp = $dcDefaultEntry->get(); $DC_DEFAULT = $tmp unless $tmp =~ /^\s*$/; } elsif( $mode eq "cancel" ) { setEntryString( $rtDefaultEntry, $RT_DEFAULT ); setEntryString( $ftDefaultEntry, $FT_DEFAULT ); setEntryString( $rtClkDefaultEntry, $RT_DEFAULT_CLK ); setEntryString( $ftClkDefaultEntry, $FT_DEFAULT_CLK ); setEntryString( $pwDefaultEntry, $PW_DEFAULT ); setEntryString( $onDefaultEntry, $ON_DEFAULT ); setEntryString( $offDefaultEntry, $OFF_DEFAULT ); setEntryString( $delayDefaultEntry, $DELAY_DEFAULT ); setEntryString( $negnodeDefaultEntry, $NEGNODE_DEFAULT ); setEntryString( $freqDefaultEntry, $FREQ_DEFAULT ); setEntryString( $dutycycleDefaultEntry, $DUTYCYCLE_DEFAULT ); setEntryString( $dcDefaultEntry, $DC_DEFAULT ); } unless( $mode eq "apply" ) { $optionsWin->withdraw(); } } # create and show options window sub createOptionsWindow { $optionsWin = $main->Toplevel(); $optionsWin->configure( -title => "Source Defaults" ); $optionsWin->transient( $main ); $optionsWin->protocol( "WM_DELETE_WINDOW", [\&closeOptionsWin, "cancel"] ); my $defaultsFrame = $optionsWin->Frame() ->pack( -fill => "x", -padx => 5, -pady => 5 ); my $buttonsContainer = $optionsWin->Frame()->pack( -fill => "x" ); my $rtDefaultContainer = $defaultsFrame->Frame()->pack( -fill => "x" ); my $ftDefaultContainer = $defaultsFrame->Frame()->pack( -fill => "x" ); my $rtClkDefaultContainer = $defaultsFrame->Frame()->pack( -fill => "x" ); my $ftClkDefaultContainer = $defaultsFrame->Frame()->pack( -fill => "x" ); my $pwDefaultContainer = $defaultsFrame->Frame()->pack( -fill => "x" ); my $onDefaultContainer = $defaultsFrame->Frame()->pack( -fill => "x" ); my $offDefaultContainer = $defaultsFrame->Frame()->pack( -fill => "x" ); my $freqDefaultContainer = $defaultsFrame->Frame()->pack( -fill => "x" ); my $delayDefaultContainer = $defaultsFrame->Frame()->pack( -fill => "x" ); my $negnodeDefaultContainer = $defaultsFrame->Frame()->pack( -fill => "x" ); my $dutycycleDefaultContainer= $defaultsFrame->Frame()->pack( -fill => "x" ); my $dcDefaultContainer= $defaultsFrame->Frame()->pack( -fill => "x" ); my $rtDefaultLabel = $rtDefaultContainer->Label( -text => "Rise time (s)" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $rtDefaultEntry = $rtDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $ftDefaultLabel = $ftDefaultContainer->Label( -text => "Fall time (s)" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $ftDefaultEntry = $ftDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $rtClkDefaultLabel = $rtClkDefaultContainer->Label( -text => "Clock rise time" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $rtClkDefaultEntry = $rtClkDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $ftClkDefaultLabel = $ftClkDefaultContainer->Label( -text => "Clock fall time" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $ftClkDefaultEntry = $ftClkDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $pwDefaultLabel = $pwDefaultContainer->Label( -text => "Pulse width (s)" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $pwDefaultEntry = $pwDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $onDefaultLabel = $onDefaultContainer->Label( -text => "On voltage (V)" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $onDefaultEntry = $onDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $offDefaultLabel = $offDefaultContainer->Label( -text => "Off voltage (V)" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $offDefaultEntry = $offDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $delayDefaultLabel = $delayDefaultContainer->Label( -text => "Initial delay (sec)" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $delayDefaultEntry = $delayDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $negnodeDefaultLabel = $negnodeDefaultContainer->Label( -text => "Negnode name" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $negnodeDefaultEntry = $negnodeDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $freqDefaultLabel = $freqDefaultContainer->Label( -text => "Frequency (Hz)" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $freqDefaultEntry = $freqDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $dutycycleDefaultLabel = $dutycycleDefaultContainer->Label( -text => "Duty cycle" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $dutycycleDefaultEntry = $dutycycleDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); my $dcDefaultLabel = $dcDefaultContainer->Label( -text => "DC voltage (V)" ) ->pack( -padx => 2, -pady => 2, -side => "left" ); $dcDefaultEntry = $dcDefaultContainer->Entry( -highlightthickness => 0, -width => 6 ) ->pack( -padx => 2, -pady => 2, -side => "right" ); setEntryString( $rtDefaultEntry, $RT_DEFAULT ); setEntryString( $ftDefaultEntry, $FT_DEFAULT ); setEntryString( $rtClkDefaultEntry, $RT_DEFAULT_CLK ); setEntryString( $ftClkDefaultEntry, $FT_DEFAULT_CLK ); setEntryString( $pwDefaultEntry, $PW_DEFAULT ); setEntryString( $onDefaultEntry, $ON_DEFAULT ); setEntryString( $offDefaultEntry, $OFF_DEFAULT ); setEntryString( $delayDefaultEntry, $DELAY_DEFAULT ); setEntryString( $negnodeDefaultEntry, $NEGNODE_DEFAULT ); setEntryString( $freqDefaultEntry, $FREQ_DEFAULT ); setEntryString( $dutycycleDefaultEntry, $DUTYCYCLE_DEFAULT ); setEntryString( $dcDefaultEntry, $DC_DEFAULT ); $buttonsContainer->Button( -text => "Ok", -command => sub { closeOptionsWin( "ok" ); } ) ->pack( -padx => 10, -ipady => 5, -side => "left" ); $buttonsContainer->Button( -text => "Apply", -command => sub { closeOptionsWin( "apply" ); } ) ->pack( -padx => 10, -ipady => 5, -side => "left" ); $buttonsContainer->Button( -text => "Cancel", -command => sub { closeOptionsWin( "cancel" ); } ) ->pack( -padx => 10, -ipady => 5, -side => "right" ); $optionsWin->withdraw(); } # show or hide source list window depending on... sub showSourceListWindow{ if( $showSourceList && $numSources > 0 ) { $sourceListWindow->deiconify(); } if( $showSourceList == 0 || $numSources == 0 ) { $sourceListWindow->withdraw(); } } # open native BitGen file sub doOpen { my $inFileName = $_[0]; my( $line, $name, $posNode, $negNode, $dataStream ); my( $rt, $ft, $on, $off, $delay, $initVal, $freq, $dutyCycle, $dc, $export ); my( $continue, $doneInitialDeletion, $comps, $sourceType, $desc ); my( $tmpCount ); # when calling bitgen with filename to be opened, the filename gets passed # as an argument here. if no argument was passed here, get filename regular # ways. if( !defined( $inFileName ) ) { # pop up the browser $inFileName = $main->getOpenFile( -title => "Open BitGen File", -filetypes => $BitGen_Filetypes ); if( !defined( $inFileName ) ) { return; } } unless( open( IN, $inFileName ) ) { doMessageBox( "File Error", "Can't open file \"$inFileName\"", "Error" ); return; } # make sure it's a valid bitgen file! $_ = ; unless( /^$BitGen_Signature/ ) { doMessageBox( "File Error", "File \"$inFileName\" isn't a BitGen file!", "Error" ); return; } $line++; if( $needSaveFlag ) { my $save = doMessageBox( "Save needed", "Save current data before proceeding?", "question", "YesNoCancel" ); if( $save =~ /^y/i ) { close IN, return if doSave( "Save" ) < 0; } elsif( $save =~ /^c/i ) { close IN; return; } } $currentFilename = $inFileName; setMainWindowTitle(); busyCursor( 1 ); $doneInitialDeletion = 0; $tmpCount = 0 if $tty; while( ) { $line++; next if /(^\s*$|^\s*\*)/; # get (optional) description if( /^\s*#\s*(.+)$/ ) { $desc = $1; $_ = ; } else { $desc = undef; } if( /^\s*bit\s+/ ) { $sourceType = "single"; } elsif( /^\s*bus\s+/ ) { $sourceType = "bus"; } elsif( /^\s*clock\s+/ ) { $sourceType = "clock"; } elsif( /^\s*dc\s+/ ) { $sourceType = "dc"; } else { $continue = doMessageBox( "Syntax error", "Syntax error, line $line. Continue reading input file?", "question", "YesNo" ); last if $continue =~ /^n/i; next; } if( $sourceType =~ /single/i ) { /^\s*bit \s+([\w-]+) (?# source name) \s+([\w-!\\]+) (?# pos node) \s+([\w-!\\]+) (?# neg node) \s+{ ([\s\d\(\)xa-f]+) (?# datastream) }/xi; $name = $1; $posNode = $2; $negNode = $3; $dataStream = $4; } elsif( $sourceType =~ /bus/i ) { /^\s*bus \s+"([\w-!]+[\[<][:\d]+[\]>][\w-!]*)" (?# components; use [] or <>) \s+([\w-!\\]+) (?# neg node) \s+{ ([\s\d\(\)xa-f]+) (?# datastream) }/xi; $comps = $1; $negNode = $2; $dataStream = $3; } elsif( $sourceType =~ /clock/i ) { /^\s*clock \s+([\w-]+) (?# source name) \s+([\w-!\\]+) (?# pos node) \s+([\w-!\\]+) (?# neg node) /xi; $name = $1; $posNode = $2; $negNode = $3; } elsif( $sourceType =~ /dc/i ) { /^\s*dc \s+([\w-]+) (?# source name) \s+([\w-!\\]+) (?# pos node) \s+([\w-!\\]+) (?# neg node) /xi; $name = $1; $posNode = $2; $negNode = $3; } unless( $doneInitialDeletion ) { foreach my $index (sort keys %descLabels) { # delete each source but don't resize main window # since we're just going to be opening more sources deleteSrc( $index, 0 ); } $doneInitialDeletion = 1; $sourceNum = "0"; } if( /\brt\s*=\s*([\w-\.\%]+)/ ) { $rt = $1; } else { $rt = $sourceType eq "clock" ? $RT_DEFAULT_CLK : $RT_DEFAULT; } if( /\bft\s*=\s*([\w-\.\%]+)/ ) { $ft = $1; } else { $ft = $sourceType eq "clock" ? $FT_DEFAULT_CLK : $FT_DEFAULT; } if( /\bpw\s*=\s*([\w-\.]+)/ ) { $pw = $1; } else { $pw = $PW_DEFAULT; } if( /\bfreq\s*=\s*([\w-\.]+)/ ) { $freq = $1; } else { $freq = $FREQ_DEFAULT; } if( /\bdutycycle\s*=\s*(\d+)%?/ ) { $dutyCycle = $1; } else { $dutyCycle = $DUTYCYCLE_DEFAULT; } if( /\bon\s*=\s*([\w-\.]+)/ ) { $on = $1; } else { $on = $ON_DEFAULT; } if( /\boff\s*=\s*([\w-\.]+)/ ) { $off = $1; } else { $off = $OFF_DEFAULT; } if( /\bdelay\s*=\s*([\w-\.]+)/ ) { $delay = $1; } else { $delay = $DELAY_DEFAULT; } if( /\binitval\s*=\s*([\w-\.]+)/ ) { $initVal = $1; } if( /\bdc\s*=\s*([\w-\.]+)/ ) { $dc = $1; } else { $dc = $DC_DEFAULT; } if( /\bexport\s*=\s*(\w+)/ ) { $export = $1; } else { $export = "on"; } if( $tty ) { $sourceNum = "TTY"; $sourceTypes{"TTY"} = $sourceType; } else { doNewSource( $sourceType, 1 ); } assignElement( $desc, \$descEntries{$sourceNum} ); assignElement( $negNode, \$negNodeEntries{$sourceNum} ); if( $sourceType eq "single" or $sourceType eq "clock" or $sourceType eq "dc" ) { assignElement( $name, \$srcEntries{$sourceNum} ); assignElement( $posNode, \$posNodeEntries{$sourceNum} ); } elsif( $sourceType eq "bus" ) { assignElement( $comps, \$vecCompsEntries{$sourceNum} ); } if( $sourceType eq "single" or $sourceType eq "bus" ) { assignElement( $dataStream, \$dataEntries{$sourceNum} ); assignElement( $pw, \$pwEntries{$sourceNum} ); } elsif( $sourceType eq "clock" ) { assignElement( $freq, \$freqEntries{$sourceNum} ); assignElement( $dutyCycle, \$dutyCycleEntries{$sourceNum} ); } if( $sourceType eq "dc" ) { assignElement( $dc, \$dcEntries{$sourceNum} ); } elsif( $sourceType eq "single" or $sourceType eq "bus" or $sourceType eq "clock" ) { assignElement( $rt, \$rtEntries{$sourceNum} ); assignElement( $ft, \$ftEntries{$sourceNum} ); assignElement( $on, \$onEntries{$sourceNum} ); assignElement( $off, \$offEntries{$sourceNum} ); assignElement( $delay, \$delayEntries{$sourceNum} ); } if( $sourceType eq "single" ) { assignElement( $initVal, \$initValEntries{$sourceNum} ); } $isExportable{$sourceNum} = $export; # update source list buttons labels setSourceListButton( $sourceNum ) unless $tty; # if we're in tty mode, go ahead and export sources as they are read in $tmpCount++; if( $tty && $isExportable{"TTY"} ) { my $desc = $descEntries{"TTY"}; my $fh = "STDOUT"; print $fh "$commentChar{$netlistLang} $desc\n" if $desc; if( $sourceType =~ /single/i ) { doBitWave( "TTY", $tmpCount, $fh ); } elsif( $sourceType =~ /bus/i ) { doBusWave( "TTY", $tmpCount, $fh ); } elsif( $sourceType =~ /clock/i ) { doClockWave( "TTY", $tmpCount, $fh ); } elsif( $sourceType =~ /dc/i ) { doDCWave( "TTY", $tmpCount, $fh ); } } } close( IN ); $needSaveFlag = 0; busyCursor( 0 ); } sub assignElement { my( $data, $element ) = @_; if( $tty ) { $$element = $data; } else { setEntryString( $$element, $data ); } } sub busyCursor { my( $busy ) = @_; return if $tty; if( $busy ) { $main->Busy; } else { $main->Unbusy; } } # save in native BitGen format sub doSave { my( $mode ) = @_; my( $fileName, $fh, $source, $sourceType, $comps ); my( $desc, $name, $posNode, $negNode, $data ); my( $rt, $ft, $pw, $freq, $on, $off, $delay, $initVal, $dutyCycle, $dc, $export ); my( $rtDefault, $ftDefault ); return 0 if $numSources == 0; if( !defined( $currentFilename ) or $mode eq "SaveAs" ) { $currentFilename = $main->getSaveFile( -title => "Save BitGen File", -filetypes => $BitGen_Filetypes ); return -1 if !defined( $currentFilename ); } $fh = getOutfileHandle( $currentFilename ); return -2 if $fh eq "Error"; setMainWindowTitle(); print $fh "$BitGen_Signature\n"; # since $sourceNum gets all screwed up when you start deleting sources, # just keep count here and use this count for any warning messages about # skipping sources. my $tmpCount = 0; foreach $source (sort keys %descLabels) { $tmpCount++; $sourceType = $sourceTypes{$source}; unless( $sourceType =~ /^bus/i ) { $name = $srcEntries{$source}->get(); if( $name =~ /^\s*$/ ) { doMessageBox( "Missing name", "Skipping source #$tmpCount: unnamed", "warning" ); next; } } $negNode = $negNodeEntries{$source}->get(); if( $negNode =~ /^\s*$/ ) { doMessageBox( "Missing node", "Skipping source #$tmpCount: no - node", "warning" ); next; } if( $sourceType =~ /^single/i or $sourceType =~ /^bus/i ) { $data = $dataEntries{$source}->get(); if( $data =~ /^\s*$/ ) { doMessageBox( "Missing data", "Skipping source #$tmpCount: empty datastream", "warning" ); next; } } if( $sourceType =~ /^bus/i ) { $comps = $vecCompsEntries{$source}->get(); if( $comps =~ /^\s*$/ ) { doMessageBox( "Missing components", "Skipping source #$source: no vector", "warning" ); next; } } # use the source name for the pos node if they didn't enter one if( $sourceType =~ /^single/i or $sourceType =~ /^clock/i or $sourceType =~ /^dc/i ) { $posNode = $posNodeEntries{$source}->get(); $posNode = $name if $posNode =~ /^\s*$/; } if( $sourceType =~ /^dc/i ) { $dc = $dcEntries{$source}->get(); $dc = $DC_DEFAULT if $dc =~ /^\s*$/; } else { if( $sourceType =~ /^clock/i ) { $rtDefault = $RT_DEFAULT_CLK; $ftDefault = $FT_DEFAULT_CLK; } else { $rtDefault = $RT_DEFAULT; $ftDefault = $FT_DEFAULT; } $rt = $rtEntries{$source}->get(); $rt = $rtDefault if $rt =~ /^\s*$/; $ft = $ftEntries{$source}->get(); $ft = $ftDefault if $ft =~ /^\s*$/; if( $sourceType =~ /^clock/i ) { $freq = $freqEntries{$source}->get(); $freq = $FREQ_DEFAULT if $freq =~ /^\s*$/; $dutyCycle = $dutyCycleEntries{$source}->get(); $dutyCycle = $DUTYCYCLE_DEFAULT if $dutyCycle =~ /^\s*$/; } else { $pw = $pwEntries{$source}->get(); $pw = $PW_DEFAULT if $pw =~ /^\s*$/; } $on = $onEntries{$source}->get(); $on = $ON_DEFAULT if $on =~ /^\s*$/; $off = $offEntries{$source}->get(); $off = $OFF_DEFAULT if $off =~ /^\s*$/; $delay = $delayEntries{$source}->get(); $delay = $DELAY_DEFAULT if $delay =~ /^\s*$/; if( $sourceType =~ /^single/i ) { $initVal = $initValEntries{$source}->get(); } } $export = $isExportable{$source}; $desc = $descEntries{$source}->get(); print $fh "# $desc\n" if $desc; if( $sourceType =~ /^single/i ) { printf $fh "bit %s %s %s {%s} rt=%s ft=%s pw=%s on=%s off=%s delay=%s initval=%s export=%s\n", $name, $posNode, $negNode, $data, $rt, $ft, $pw, $on, $off, $delay, $initVal, $export; } elsif( $sourceType =~ /^bus/i ) { printf $fh "bus \"%s\" %s {%s} rt=%s ft=%s pw=%s on=%s off=%s delay=%s export=%s\n", $comps, $negNode, $data, $rt, $ft, $pw, $on, $off, $delay, $export; } elsif( $sourceType =~ /^clock/i ) { printf $fh "clock %s %s %s rt=%s ft=%s freq=%s dutycycle=%s on=%s off=%s delay=%s export=%s\n", $name, $posNode, $negNode, $rt, $ft, $freq, $dutyCycle, $on, $off, $delay, $export; } elsif( $sourceType =~ /^dc/i ) { printf $fh "dc %s %s %s dc=%s export=%s\n", $name, $posNode, $negNode, $dc, $export; } } $needSaveFlag = 0; close( $fh ) if $fh eq "OUT"; return 0; } sub getOutfileHandle { my( $outFileName ) = @_; # print to stdout if there's no file name specified if( $outFileName eq "" or $outFileName =~ /^\s*$/ ) { return( "STDOUT" ); } else { unless( open( OUT, ">$outFileName" ) ) { doMessageBox( "Write Error", "Can't write to \"$outFileName\"!", "Error", "OK" ); return( "Error" ); } return( "OUT" ); } } # export netlist sub doExport { my( $mode, $ttyOut ) = @_; my( $source, $fh, $desc ); my $types = [ ['CDS', '.cds' ], ['SPICE', ['.sp', '.hsp'] ], ['Spectre', '.spectre' ], ['2-column', '.2col' ], ['All Files', '*' ] ]; return 0 if $numSources == 0; if( !$ttyOut ) { if( !defined( $currentNetlistname ) or $mode eq "ExportAs" ) { $currentNetlistname = $main->getSaveFile( -title => "Export Netlist", -filetypes => $types ); return -1 if !defined( $currentNetlistname ); } $fh = getOutfileHandle( $currentNetlistname ); return -2 if $fh eq "Error"; setMainWindowTitle(); } else { $fh = "STDOUT"; } # since $sourceNum gets all screwed up when you start deleting sources, # just keep count here and use this count for any warning messages about # skipping sources. my $tmpCount = 0; foreach $source (sort keys %descLabels) { $tmpCount++; next if $isExportable{$source} =~ /^off/; $desc = $descEntries{$source}->get(); print $fh "$commentChar{$netlistLang} $desc\n" if $desc; if( $sourceTypes{$source} =~ /single/i ) { doBitWave( $source, $tmpCount, $fh ); } elsif( $sourceTypes{$source} =~ /bus/i ) { doBusWave( $source, $tmpCount, $fh ); } elsif( $sourceTypes{$source} =~ /clock/i ) { doClockWave( $source, $tmpCount, $fh ); } elsif( $sourceTypes{$source} =~ /dc/i ) { doDCWave( $source, $tmpCount, $fh ); } } close( $fh ) if $fh eq "OUT"; return 0; } sub doDCWave { my( $source, $tmpCount, $OUT ) = @_; my( $name, $res, $pattern, $period, $highTime, $posNode, $negNode ); $name = get( $srcEntries{$source} ); if( $name =~ /^\s*$/ ) { doMessageBox( "Missing name", "Skipping source #$tmpCount: unnamed", "warning" ); return -1; } if( get( $negNodeEntries{$source} ) =~ /^\s*$/ ) { doMessageBox( "Missing node", "Skipping source #$tmpCount: no - node", "warning" ); return -1; } $res = getParameters( $source, "dc" ); if( $res != 0 ) { doMessageBox( "Syntax Error", "Source #$tmpCount: " . $res, "Error" ); return -1; } $posNode = get( $posNodeEntries{$source} ); # use the source name for the pos node if they didn't enter one $posNode = $name if( $posNode =~ /^\s*$/ ); $negNode = get( $negNodeEntries{$source} ); if( $netlistLang eq "SPICE" or $netlistLang eq "CDS" ) { if( $makePlotFile ) { my $plotStr = makeSigviewHeader( $name ); $plotStr .= "0 0\n " . $dc . "\n1 MAXTIME\n " . $dc . "\n"; $plotStr =~ s/XXX/2/; $sigviewData[$sigDataCount++] = $plotStr; } else { print $OUT "V$name $posNode $negNode DC $dc\n"; } } elsif( $netlistLang eq "Spectre" ) { print $OUT "V$name $posNode $negNode vsource type=dc dc=$dc\n"; } return 0; } sub doClockWave { my( $source, $tmpCount, $OUT ) = @_; my( $name, $res, $pattern, $period, $highTime, $posNode, $negNode ); $name = get( $srcEntries{$source} ); if( $name =~ /^\s*$/ ) { doMessageBox( "Missing name", "Skipping source #$tmpCount: unnamed", "warning" ); return -1; } if( get( $negNodeEntries{$source} ) =~ /^\s*$/ ) { doMessageBox( "Missing node", "Skipping source #$tmpCount: no - node", "warning" ); return -1; } $res = getParameters( $source, "clock" ); if( $res != 0 ) { doMessageBox( "Syntax Error", "Source #$tmpCount: " . $res, "Error" ); return -1; } # rudimentary error checking if( $freq <= 0 ) { doMessageBox( "Illegal parameter value", "Skipping source #$tmpCount: frequency must be positive", "warning" ); return -1; } if( $dutyCycle <= 0 or $dutyCycle >= 100 ) { doMessageBox( "Illegal parameter value", "Skipping source #$tmpCount: duty cycle must be between 0-100", "warning" ); return -1; } $dutyCycle /= 100; # "2-Column" format doesn't make much sense with a clock source, # since we don't know how long to make it run (of course, we could # find out, but it's not worth the effort; also, in Composer, it's # just as easy to use a pulse source as it is to use a pwlf) if( $netlistLang eq "2-Column" ) { doMessageBox( "Illegal netlist format", "Skipping source #$tmpCount: clock source not supported in 2-Column format", "warning" ); return -1; } $period = 1 / $freq; $highTime = ($dutyCycle * $period) - $rt; if( $highTime <= 0 ) { doMessageBox( "Illegal parameter values", "Skipping source #$tmpCount: duty cycle, freq and rt result in negative pulse width", "warning" ); return -1; } $posNode = get( $posNodeEntries{$source} ); # use the source name for the pos node if they didn't enter one $posNode = $name if( $posNode =~ /^\s*$/ ); $negNode = get( $negNodeEntries{$source} ); # take care of plotting clocks in doPlot() instead of here so we can know what $maxTime is # i.e., so we know for how long to plot the clock if( $makePlotFile ) { $sigviewData[$sigDataCount++] = "clock"." ".$off." ".$on." ".$delay." ".$rt." ".$ft." ".$highTime." ".$period; } else { if( $netlistLang eq "SPICE" or $netlistLang eq "CDS" ) { print $OUT "V$name $posNode $negNode pulse("; print $OUT "$off $on $delay $rt $ft $highTime $period )\n"; } elsif( $netlistLang eq "Spectre" ) { print $OUT "V$name $posNode $negNode vsource type=pulse "; print $OUT "val0=$off val1=$on delay=$delay rise=$rt fall=$ft width=$highTime period=$period\n"; } } return 0; } sub doBusWave { my( $source, $tmpCount, $OUT ) = @_; my( $name1, $name2, $start, $stop, $incr, $data, $res ); my( $pattern, $key, $components, $idx, %bitStreams ); $pattern = get( $dataEntries{$source} ); $components = get( $vecCompsEntries{$source} ); if( $pattern =~ /^\s*$/ ) { doMessageBox( "Missing data", "Skipping source #$tmpCount: empty datastream", "warning" ); return -1; } if( get( $negNodeEntries{$source} ) =~ /^\s*$/ ) { doMessageBox( "Missing node", "Skipping source #$tmpCount: no - node", "warning" ); return -1; } if( $components =~ /^\s*$/ ) { doMessageBox( "Missing components", "Skipping source #$tmpCount: no components", "warning" ); return -1; } # expand notation if( $components =~ /(\w+) (?# name) [<\[](\d+): (?# start index) (\d+) (?# stop index) :?(\d*) (?# increment, optional) [>\]](\w*)/x ) { # if name is of form x[a:b]y, $name1="x" $name2="y" $name1 = $1; $name2 = $5; $start = $2; $stop = $3; $incr = $4; $incr = 1 if !$incr; $components = ""; if( $start > $stop ) { # descending for( $idx=$start; $idx>=$stop; $idx -= $incr ) { $components .= $name1 . $idx . $name2 . " "; } } else { # ascending for( $idx=$start; $idx<=$stop; $idx += $incr ) { $components .= $name1 . $idx . $name2 . " "; } } } # expand multiplied patterns while( $pattern =~ /\s*((\d+) (?# $2, the multiplier) \(\s*([a-fx\d ]+)\) (?# $3, vec to be multiplied) )\s*/ix ) { substr($pattern, index($pattern, $1), length($1)) = join("", " ", ($3 . " ") x $2, " " ); } $pattern =~ s/^\s+//; foreach my $datum (split( /\s+/, $pattern )) { if( $datum =~ /^0x/ ) { # hex string $datum = oct( $datum ); } else { # string of 1's and 0's # pad w/0's to make sure we have an integral # of bytes $datum = "0" . $datum while( length( $datum ) % 8 != 0 ); $datum = oct( "0x" . unpack( "H*", pack( "B*", $datum ) ) ); } # - for each signal "name" in the vector, update the # corresponding bitStream array # - reverse() is needed so the vector components are assigned # in the order in which they were defined foreach my $comp (reverse split( /[\s,;]+/, $components )) { $bitStreams{$comp} .= ($datum & 1) ? "1" : "0"; $datum >>= 1; } } $res = getParameters( $source, "bus" ); if( $res != 0 ) { doMessageBox( "Syntax Error", "Source #$tmpCount: " . $res, "Error" ); return -1; } # rudimentary error checking # if( $rt > $pw or $ft > $pw ) { # doMessageBox( "Illegal parameter value", # "Skipping source #$tmpCount: rt/ft must be <= pw", "warning" ); # return; # } # sort the keys so voltage sources are printed in order foreach $key (sort keys %bitStreams) { printBitWave( $key, $bitStreams{$key}, $source, $OUT ); } return 0; } sub get { my( $element ) = @_; if( $tty ) { return $element; } else { return $element->get(); } } sub doBitWave { my( $source, $tmpCount, $OUT) = @_; my( $name, $pattern, $bits, $res ); $name = get( $srcEntries{$source} ); if( $name =~ /^\s*$/ ) { doMessageBox( "Missing name", "Skipping source #$tmpCount: unnamed", "warning" ); return -1; } $pattern = get( $dataEntries{$source} ); if( $pattern =~ /^\s*$/ ) { doMessageBox( "Missing data", "Skipping source #$tmpCount: empty datastream", "warning" ); return -1; } if( get($negNodeEntries{$source}) =~ /^\s*$/ ) { doMessageBox( "Missing node", "Skipping source #$tmpCount: no - node", "warning" ); return -1; } # expand hex numbers $pattern = expandDataStream( $pattern ); if( $pattern =~ /[^01]/ ) { doMessageBox( "Error", "Illegal character in datastream #$tmpCount", "Error" ); return -1; } $res = getParameters( $source, "single" ); if( $res != 0 ) { doMessageBox( "Syntax Error", "Source #$tmpCount: " . $res, "Error" ); return -1; } # rudimentary error checking # if( $rt > $pw or $ft > $pw ) { # doMessageBox( "Illegal parameter value", # "Skipping source #$tmpCount: rt/ft must be <= pw", "warning" ); # return; # } # now, binary vector is in $pattern printBitWave( $name, $pattern, $source, $OUT ); return 0; } sub doPlot() { my $count = 0; my $plottableSourceFlag = 0; return if $numSources == 0; unless( open( SIGVIEWFILE, ">$plotFile" ) ) { doMessageBox( "File Error", "Can't open file \"$plotFile\"", "Error" ); return; } $makePlotFile = 1; foreach my $source (sort keys %descLabels) { $count++; next if $isExportable{$source} =~ /^off/; if( $sourceTypes{$source} =~ /single/i ) { unless( doBitWave( $source, $count, 0 ) < 0 ) { $plottableSourceFlag++; } } elsif( $sourceTypes{$source} =~ /bus/i ) { unless( doBusWave( $source, $count, 0 ) < 0 ) { $plottableSourceFlag++; } } elsif( $sourceTypes{$source} =~ /clock/i ) { unless( doClockWave( $source, $count, 0 ) < 0 ) { $plottableSourceFlag++; } } elsif( $sourceTypes{$source} =~ /dc/i ) { unless( doDCWave( $source, $count, 0 ) < 0 ) { $plottableSourceFlag++; } } } unless( $plottableSourceFlag ) { doMessageBox( "Plot", "No plottable sources found", "warning" ); return; } # make sure each signal extends as far as $maxTime foreach my $sig (@sigviewData) { if( $sig =~ /^clock/ ) { $sig =~ s/^clock\s*//; my $dataPtCount = 1; my ($off,$on,$delay,$rt,$ft,$highTime,$period) = split( /\s+/, $sig); my $lowTime = $period - $highTime - $rt - $ft; my $numPeriods = ($maxTime - $delay) / $period; $sig = makeSigviewHeader( "clock" ); $sig .= "0 0\n $off\n"; for( my $i=0; $i<$numPeriods; $i++ ) { my $t = eval "$delay + $lowTime + $period*$i"; $sig .= "$dataPtCount $t\n $off\n"; $dataPtCount++; $t = eval "$delay + ($lowTime + $rt) + $period*$i"; $sig .= "$dataPtCount $t\n $on\n"; $dataPtCount++; $t = eval "$delay + ($lowTime + $rt + $highTime) + $period*$i"; $sig .= "$dataPtCount $t\n $on\n"; $dataPtCount++; $t = eval "$delay + ($lowTime + $rt + $highTime + $ft) + $period*$i"; $sig .= "$dataPtCount $t\n $off\n"; $dataPtCount++; } $sig =~ s/XXX/$dataPtCount/; } $sig =~ s/MAXTIME/$maxTime/; print SIGVIEWFILE $sig; } my $pid = fork(); if( defined( $pid ) && $pid == 0 ) { # child # when running in rxvt, sigview spews out some bizarro escape sequences # so send those to the bit bucket open STDOUT, ">/dev/null"; open STDERR, ">/dev/null"; exec "$sigviewCommand $plotFile"; doMessageBox( "Exec Error", "Exec failed ($!) - exiting", "Error" ); exit 0; } $makePlotFile = 0; $sigDataCount = 0; $maxTime = 0; @sigviewData = undef; close SIGVIEWFILE; } sub makeSigviewHeader { my( $name ) = @_; my @info = getpwuid $<; my $plotStr = "Title: Voltage source $name by BitGen\n"; $plotStr .= "Date: " . scalar( localtime ) . "\n"; $plotStr .= "Name: " . shift( @info ) . "\n"; $plotStr .= "Flags:\n"; $plotStr .= "No. Variables: 2\n"; $plotStr .= "No. Points: XXX\n"; $plotStr .= "Command\nVariables:\n 0 time time\n 1 $name voltage\n"; $plotStr .= "Values:\n"; return $plotStr; } sub isNumber { my $str = $_[0]; if( $str =~ /^(-?\d+\.?\d*(e[+-]?\d+)?)([afpnumkxgt]?)$/ ) { if( defined( $suffixes{$3} )) { return( $1 * $suffixes{$3} ); } else { return( $1 ); } } else { return( -1 ); } } sub printBitWave { my( $name, $pattern, $source, $OUT ) = @_; my( $pwCount, $count, $posNode, $negNode, $twoColBreak, $patternBit1 ); my( $plotStr, $dataPtCount, $t1, $t2 ); if( $sourceTypes{$source} eq "single" or $sourceTypes{$source} eq "clock") { $posNode = get( $posNodeEntries{$source} ); # use the source name for the pos node if they didn't enter one $posNode = $name if( $posNode =~ /^\s*$/ ); } elsif( $sourceTypes{$source} eq "bus" ) { $posNode = $name; } $negNode = get( $negNodeEntries{$source} ); $pattern =~ /^(.)/; $patternBit1 = $1; unless( $initVal =~ /[01]/ ) { $initVal = $patternBit1; } if( $makePlotFile ) { $dataPtCount = 1; $plotStr = makeSigviewHeader( $name ); $plotStr .= "0 0\n " . ($initVal == '1' ? $on : $off) . "\n"; } else { if( $netlistLang eq "SPICE" ) { print $OUT "V$name $posNode $negNode pwl(0 ", $initVal == '1' ? $on : $off,"\n+ "; } elsif( $netlistLang eq "Spectre" ) { print $OUT "V$name $posNode $negNode vsource type=pwl "; print $OUT "wave=[0 ", $initVal == '1' ? $on : $off,"\n+ "; } elsif( $netlistLang eq "CDS" ) { print $OUT "V$name $posNode $negNode pwl(0 ", $initVal == '1' ? $on : $off," &\n"; } elsif( $netlistLang eq "2-Column" ) { print $OUT "0 ", $initVal == '1' ? $on : $off,"\n"; } if( $netlistLang eq "2-Column" ) { $twoColBreak = "\n"; } else { $twoColBreak = " "; } } if( $initVal ne $patternBit1 ) { if( $initVal eq '1' ) { # falling edge if( $makePlotFile ) { $plotStr .= "$dataPtCount $delay\n $on\n"; $dataPtCount++; $t1 = eval "$ft+$delay"; $plotStr .= "$dataPtCount $t1\n $off\n"; $dataPtCount++; } else { if( ($t1=isNumber( $delay ))>=0 && ($t2=isNumber( $ft ))>=0) { print $OUT eval("$t1")," $on, " if $delay ne '0'; print $OUT eval("$t2+$t1")," $off, "; } else { print $OUT "\'$delay\' $on, " if $delay ne '0'; print $OUT "\'$ft+$delay\' $off, "; } } } else { # rising edge if( $makePlotFile ) { $plotStr .= "$dataPtCount $delay\n $off\n"; $dataPtCount++; $t1 = eval "$rt+$delay"; $plotStr .= "$dataPtCount $t1\n $on\n"; $dataPtCount++; } else { print $OUT "\'$delay\' $off, " if $delay ne '0'; print $OUT "\'$rt+$delay\' $on, "; } } } $pwCount = 1; while( $pattern =~ s/^(.)(.)/$2/ ) { if( $2 eq $1 ) { # no edge, do only increment time $pwCount++; } else { if( $1 eq '1' ) { # falling edge if( $makePlotFile ) { $t1 = eval( "$pwCount*$pw+$delay" ); $t2 = eval( "$pwCount*$pw+$ft+$delay" ); $plotStr .= "$dataPtCount $t1\n $on\n"; $dataPtCount++; $plotStr .= "$dataPtCount $t2\n $off\n"; $dataPtCount++; } else { print $OUT "\'$pwCount*$pw+$delay\' $on,$twoColBreak"; print $OUT "\'$pwCount*$pw+$ft+$delay\' $off, "; } } else { # rising edge if( $makePlotFile ) { my $t1 = eval( "$pwCount*$pw+$delay" ); my $t2 = eval( "$pwCount*$pw+$rt+$delay" ); $plotStr .= "$dataPtCount $t1\n $off\n"; $dataPtCount++; $plotStr .= "$dataPtCount $t2\n $on\n"; $dataPtCount++; } else { print $OUT "\'$pwCount*$pw+$delay\' $off,$twoColBreak"; print $OUT "\'$rt+$pwCount*$pw+$delay\' $on, "; } } $pwCount++; if( ($netlistLang eq "2-Column" || ++$count == 2) && !$makePlotFile) { $count = 0; print $OUT "&" if $netlistLang eq "CDS"; print $OUT "\n"; print $OUT "+ " unless( $netlistLang eq "2-Column" || $netlistLang eq "CDS" ); } } } if( $makePlotFile ) { my $t1 = eval( "$pwCount*$pw+$delay" ); $plotStr .= "$dataPtCount $t1\n " . ($pattern eq '1' ? $on : $off) . "\n"; $dataPtCount++; $plotStr .= "$dataPtCount MAXTIME\n " . ($pattern eq '1' ? $on : $off) . "\n"; $dataPtCount++; $plotStr =~ s/XXX/$dataPtCount/; $maxTime = $t1 if $t1 > $maxTime; } else { print $OUT "\'$pwCount*$pw+$delay\' ", $pattern eq '1' ? $on : $off; if( $netlistLang eq "SPICE" || $netlistLang eq "CDS" ) { print $OUT ")\n"; } elsif( $netlistLang eq "Spectre" ) { print $OUT "]\n"; } elsif( $netlistLang eq "2-Column" ) { print $OUT "\n"; } } $sigviewData[$sigDataCount++] = $plotStr; } # get digital parameters: pw, on, off, rt, ft, delay, initVal # this applies to both single sources and busses # clocks need to get freq and duty cycle sub getParameters { my( $source, $type ) = @_; my( $parameters ); if( $type eq "dc" ) { $parameters = "dc=" . get( $dcEntries{$source} ); if( $parameters =~ /dc=(\d+(?:\.\d+)?)%?/i ) { $dc = sprintf( "%f", $1 ); } elsif( $parameters =~ /dc=\s+/ ) { $dc = $DC_DEFAULT; } elsif( $parameters =~ /dc=([a-z]\w*)/i ) { $dc = $1; } else { return( "dc" ); } return( 0 ); } if( $type eq "clock" ) { $parameters = "freq=" . get( $freqEntries{$source} ); $parameters .= " dutycycle=" . get( $dutyCycleEntries{$source} ); } else { $parameters = "pw=" . get( $pwEntries{$source} ); } $parameters .= " rt=" . get( $rtEntries{$source} ); $parameters .= " ft=" . get( $ftEntries{$source} ); $parameters .= " delay=" . get( $delayEntries{$source} ); $parameters .= " initval=" . get( $initValEntries{$source} ) if $type =~ /single/i; $parameters .= " on=" . get( $onEntries{$source} ); $parameters .= " off=" . get( $offEntries{$source} ); # change "meg" to the one-character symbol for 1e6 $parameters =~ s/meg/x/gi; if( $type eq "clock" ) { # frequency if( $parameters =~ /freq=(\d+(?:\.\d+)?e[-\+]?\d+)/i ) { $freq = sprintf( "%e", $1 ); } elsif( $parameters =~ /freq=(\d+(?:\.\d+)?)([afpnumkxgt]?)/i ) { $freq = $1; $freq *= $suffixes{$2} if $2; } elsif( $parameters =~ /freq=\s+/ ) { $freq = $FREQ_DEFAULT; } else { return( "freq" ); } # duty cycle if( $parameters =~ /dutycycle=(\d+(?:\.\d+)?)%?/i ) { $dutyCycle = sprintf( "%f", $1 ); } elsif( $parameters =~ /dutyCycle=\s+/ ) { $dutyCycle = $DUTYCYCLE_DEFAULT; } else { return( "duty cycle" ); } } else { # pulse width if( $parameters =~ /pw=(\d+(?:\.\d+)?e\[-\+]?\d+)/i ) { $pw = sprintf( "%e", $1 ); } elsif( $parameters =~ /pw=(\d+(?:\.\d+)?)([afpnumkxgt]?)/i ) { $pw = $1; $pw *= $suffixes{$2} if $2; } elsif( $parameters =~ /pw=([a-z]\w*)/i ) { $pw = $1; } elsif( $parameters =~ /pw=\s+/ ) { $pw = $PW_DEFAULT; } else { return( "pw" ); } } # rise time if( $parameters =~ /rt=(\d+(?:\.\d+)?e[-\+]?\d+)/i ) { $rt = sprintf( "%e", $1 ); } elsif( $parameters =~ /rt=(\d+(?:\.\d+)?)([afpnumkxgt%]?)/i ) { if( $2 ) { if( defined( $suffixes{$2} ) ) { $rt = $1 * $suffixes{$2}; } elsif( $type eq "clock" and $2 eq "%" ) { $rt = 1/$freq * $1/100; # percentage of period } else { return( "rt" ); } } else { $rt = $1; } } elsif( $parameters =~ /rt=([a-z]\w*)/i ) { $rt = $1; } else { $rt = $type eq "clock" ? $RT_DEFAULT_CLK : $RT_DEFAULT; } # fall time if( $parameters =~ /ft=(\d+(?:\.\d+)?e[-\+]?\d+)/i ) { $ft = sprintf( "%e", $1 ); } elsif( $parameters =~ /ft=(\d+(?:\.\d+)?)([afpnumkxgt%]?)/i ) { if( $2 ) { if( defined( $suffixes{$2} ) ) { $ft = $1 * $suffixes{$2}; } elsif( $type eq "clock" and $2 eq "%" ) { $ft = 1/$freq * $1/100; # percentage of period } else { return( "ft" ); } } else { $ft = $1; } } elsif( $parameters =~ /ft=([a-z]\w*)/i ) { $ft = $1; } else { $ft = $type eq "clock" ? $FT_DEFAULT_CLK : $FT_DEFAULT; } # initial delay if( $parameters =~ /delay=(\d+(?:\.\d+)?e[-\+]?\d+)/i ) { $delay = sprintf( "%e", $1 ); } elsif( $parameters =~ /delay=(\d+(?:\.\d+)?)([afpnumkxgt]?)/i ) { $delay = $1; $delay *= $suffixes{$2} if $2; } elsif( $parameters =~ /delay=([a-z]\w*)/i ) { $delay = $1; } elsif( $parameters =~ /delay=\s+/ ) { $delay = $DELAY_DEFAULT; } else { return( "delay" ); } # initial value if( $parameters =~ /initval=([01X])/i ) { $initVal = $1; } else { $initVal = "X"; } # on voltage if( $parameters =~ /on=(-?\d+(?:\.\d+)?e[-\+]?\d+)/i ) { $on = sprintf( "%e", $1 ); } elsif( $parameters =~ /on=(-?\d+(?:\.\d+)?)([afpnumkxgt]?)/i ) { $on = $1; $on *= $suffixes{$2} if $2; } elsif( $parameters =~ /on=([a-z]\w*)/i ) { $on = $1; } elsif( $parameters =~ /on=\s+/ ) { $on = $ON_DEFAULT; } else { return( "on" ); } # off voltage if( $parameters =~ /off=(-?\d+(?:\.\d+)?e[-\+]?\d+)/i ) { $off = sprintf( "%e", $1 ); } elsif( $parameters =~ /off=(-?\d+(?:\.\d+)?)([afpnumkxgt]?)/i ) { $off = $1; $off *= $suffixes{$2} if $2; } elsif( $parameters =~ /off=\s+/ ) { $off = $OFF_DEFAULT; } elsif( $parameters =~ /off=([a-z]\w*)/i ) { $off = $1; } else { return( "off" ); } return( 0 ); } sub widgetStatus() { # make sure the save, export, etc. buttons and menu entries have the # correct enabled/disabled state my $state = $numSources > 0 ? "normal" : "disabled"; $saveButton->configure( -state => $state ); my $m = $fileMenu->cget( "menu" ); $m->entryconfigure( 2, -state => $state ); $m->entryconfigure( 3, -state => $state ); $state = "disabled"; foreach my $source (sort keys %descLabels) { if( $isExportable{$source} eq "on" ) { $state = "normal"; last; } } $exportButton->configure( -state => $state ); $plotButton->configure( -state => $state ); $m->entryconfigure( 5, -state => $state ); $m->entryconfigure( 6, -state => $state ); } # create the widgets for another source # side effect: sourceNum is incremented sub doNewSource { my( $type, $noBusyCursor ) = @_; busyCursor( 1 ) unless $noBusyCursor; $sourceNum++; # this is an index into the widget arrays, is never decremented createNewSourceWidgets( $sourceNum, $type ); $numSources++; # this keeps track of the current number of sources addToSourceListWindow( $sourceNum ); showSourceListWindow(); setBalloonHelp( $sourceNum ); busyCursor( 0 ) unless $noBusyCursor; widgetStatus(); } # add the new source to the "source list" window sub addToSourceListWindow { my $sourceIdx = $_[0]; $sourceListFrames{$sourceIdx} = $sourceListPane->Frame() ->pack( -pady => 5, -expand => 1, -fill => "x" ); $sourceListButtons{$sourceIdx} = $sourceListFrames{$sourceIdx}->Button(); # use tmp variable for attempt at readability my $tmp = $sourceListButtons{$sourceIdx}; $tmp->configure( -text => "$sourceTypes{$sourceIdx}", # when button is clicked, see() widgets at both top and bottom # of the source so we get (almost the) whole thing visible -command => sub { $sourcePane->see($resetSrcButtons{$sourceIdx}); $sourcePane->see($descEntries{$sourceIdx}); $descEntries{$sourceIdx}->focus(); $descEntries{$sourceIdx}->selectionRange( 0,'end' ); }); $tmp->pack( -expand => 1, -fill => "both", -padx => 5, -pady => 1, -side => "left" ); } # make button text the same as the "description" field of the corresponding source sub setSourceListButton{ my $sourceIdx = $_[0]; my $text = $descEntries{$sourceIdx}->get(); if( $text =~ /^$/ ) { $text = $sourceTypes{$sourceIdx}; } my $font = $sourceListButtons{$sourceIdx}->cget( "font"); my $len = $sourceListButtons{$sourceIdx}->fontMeasure( $font, $text ); $sourceListButtons{$sourceIdx}->geometry =~ /^(\d+)/; $sourceListButtons{$sourceIdx}->configure( -text => "$text" ) ; $sourceListWindow->update(); # resize source list window if needed $sourceListWindow->geometry() =~ /^(\d+)x(\d+)/; my $w = $1; my $h = $2; # $sourceListButtons{$sourceIdx}->geometry =~ /^(\d+)/; my $minW = $len + 50; if( $w < $minW ) { $sourceListWindow->geometry( sprintf( "=%dx%d", $minW, $h ) ); } } # make a copy of a source sub dupSrc { my $sourceIdx = $_[0]; my $destIdx; my $type; $type = $sourceTypes{$sourceIdx}; doNewSource( $type ); # $sourceIdx is the index of the source source (sigh); # $sourceNum (from doNewSource()) is index of source we just created, i.e, # the dest src $destIdx = $sourceNum; setEntryString( $descEntries{$destIdx}, $descEntries{$sourceIdx}->get() ); setEntryString( $negNodeEntries{$destIdx}, $negNodeEntries{$sourceIdx}->get() ); setEntryString( $rtEntries{$destIdx}, $rtEntries{$sourceIdx}->get() ); setEntryString( $ftEntries{$destIdx}, $ftEntries{$sourceIdx}->get() ); if( $type eq "single" or $type eq "clock" or $type eq "dc" ) { setEntryString( $srcEntries{$destIdx}, $srcEntries{$sourceIdx}->get() ); if( $invertDupSrc ) { setEntryString( $posNodeEntries{$destIdx}, "_".$posNodeEntries{$sourceIdx}->get() ); } else { setEntryString( $posNodeEntries{$destIdx}, $posNodeEntries{$sourceIdx}->get() ); } } if( $type eq "bus" ) { setEntryString( $vecCompsEntries{$destIdx}, $vecCompsEntries{$sourceIdx}->get() ); } if( $type eq "single" or $type eq "bus" ) { if( $invertDupSrc ) { # since data can be in various formats (eg: 5(1), 3( 11 3(0)), 0x8) # just write out the complemented form in 1's and 0's my $data = expandDataStream( $dataEntries{$sourceIdx}->get() ); # there has to be a better way to do this... $data =~ s/0/a/g; $data =~ s/1/0/g; $data =~ s/a/1/g; setEntryString( $dataEntries{$destIdx}, $data ); # prepend "_" to source name if we're inverting my $str = $srcEntries{$destIdx}->get(); setEntryString( $srcEntries{$destIdx}, "_" . $str ) unless $str =~ /^\s*$/; $str = $descEntries{$destIdx}->get(); setEntryString( $descEntries{$destIdx}, "_" . $str ) unless $str =~ /^\s*$/; } else { setEntryString( $dataEntries{$destIdx}, $dataEntries{$sourceIdx}->get() ); } setEntryString( $pwEntries{$destIdx}, $pwEntries{$sourceIdx}->get() ); } if( $type eq "clock" ) { setEntryString( $freqEntries{$destIdx}, $freqEntries{$sourceIdx}->get() ); setEntryString( $dutyCycleEntries{$destIdx}, $dutyCycleEntries{$sourceIdx}->get() ); if( $invertDupSrc ) { # prepend "_" to source name if we're inverting setEntryString( $srcEntries{$destIdx}, "_".$srcEntries{$destIdx}->get() ); setEntryString( $descEntries{$destIdx}, "_".$descEntries{$destIdx}->get() ); setEntryString( $onEntries{$destIdx}, $offEntries{$sourceIdx}->get() ); setEntryString( $offEntries{$destIdx}, $onEntries{$sourceIdx}->get() ); } else { setEntryString( $onEntries{$destIdx}, $onEntries{$sourceIdx}->get() ); setEntryString( $offEntries{$destIdx}, $offEntries{$sourceIdx}->get() ); } } elsif( $type eq "single" or $type eq "bus" ) { setEntryString( $onEntries{$destIdx}, $onEntries{$sourceIdx}->get() ); setEntryString( $offEntries{$destIdx}, $offEntries{$sourceIdx}->get() ); } elsif( $type eq "dc" ) { setEntryString( $dcEntries{$destIdx}, $dcEntries{$sourceIdx}->get() ); } setEntryString( $delayEntries{$destIdx}, $delayEntries{$sourceIdx}->get() ); setEntryString( $initValEntries{$destIdx}, $initValEntries{$sourceIdx}->get() ); setSourceListButton( $destIdx ); $invertDupSrc = 0; } sub expandDataStream { my $data = $_[0]; my $bits; # expand hex numbers while( $data =~ /\s*(0x[a-f\d]+)\s*/i ) { # use substr to avoid the leading 0x $bits = unpack( "B*", pack( "H*", substr( $1, 2 ) ) ); # unpack fills in a blank nybble if the number of hex digits is odd, # eg, unpack(B*", pack("H*","0x5")) produces 01010000. so, we need to # delete these 4 extra zeroes. if( length( $1 ) % 2 == 1 ) { $bits = substr( $bits, 0, -4 ); } # now substitute $bits back into the $data vector where $1 used to # be. since we're using $1 here it's very important not to do any regex # searches between here and the "while" statement above substr( $data, index($data,$1), length($1) ) = join( "", " ", $bits, " "); } # expand multiplied datas while( $data =~ /\s*((\d+) (?# $2, the multiplier) \(([01 ]+)\) (?# $3, the vector to be multiplied) )\s*/x ) { # mult/vec pair is $1 # pad with spaces so we don't get, eg: 4(111)1(1) -> 1111111111111(1) substr($data, index($data, $1), length($1)) = join("", " ", $3 x $2, " "); } # remove whitespace; anything left other than 0 or 1 is illegal $data =~ s/[ \{]//g; # } dummy brace to fix syn hilite in vim return $data; } # initialize an entry widget with a text string sub setEntryString { my( $entry, $str ) = @_; $entry->delete( 0, "end" ); $entry->insert( 0, $str ); } # initialize source sub resetSrc { my $index = $_[0]; my $type = $sourceTypes{$index}; if( $type eq "single" or $type eq "clock" ) { setEntryString( $srcEntries{$index}, "" ); setEntryString( $posNodeEntries{$index}, "" ); } if( $type eq "bus" ) { setEntryString( $vecCompsEntries{$index}, "" ); } if( $type eq "single" or $type eq "bus" ) { setEntryString( $dataEntries{$index}, "" ); setEntryString( $pwEntries{$index}, $PW_DEFAULT ); setEntryString( $rtEntries{$index}, $RT_DEFAULT ); setEntryString( $ftEntries{$index}, $FT_DEFAULT ); } if( $type eq "clock" ) { setEntryString( $freqEntries{$index}, $FREQ_DEFAULT ); setEntryString( $dutyCycleEntries{$index}, $DUTYCYCLE_DEFAULT); setEntryString( $rtEntries{$index}, $RT_DEFAULT_CLK ); setEntryString( $ftEntries{$index}, $FT_DEFAULT_CLK ); } if( $type =~ /dc/i ) { setEntryString( $dcEntries{$index}, $DC_DEFAULT ); } else { setEntryString( $onEntries{$index}, $ON_DEFAULT ); setEntryString( $offEntries{$index}, $OFF_DEFAULT ); setEntryString( $delayEntries{$index}, $DELAY_DEFAULT ); } if( $type =~ /single/i ) { setEntryString( $initValEntries{$index}, "" ); } setEntryString( $descEntries{$index}, "" ); setEntryString( $negNodeEntries{$index}, $NEGNODE_DEFAULT ); } sub setBallonHelpState { setBalloonHelp( "initial" ); foreach my $w (keys %descLabels){ setBalloonHelp( $w ); } } # toggle balloon help sub setBalloonHelp { my $index = $_[0]; # items that are always present if( $index eq "initial" ) { my %helpMsgs1 = ( "add new source (ctrl-a)" => $addSrcButton, "type of source to add" => $srcTypeMenu, "save BitGen file (ctrl-s)" => $saveButton, "plot with Sigview (ctrl-p)" => $plotButton, "export netlist (ctrl-e)" => $exportButton, "exit (ctrl-q)" => $exitButton, ); foreach my $w (keys %helpMsgs1) { if( $doBalloonHelp ) { $mainBalloon->attach( $helpMsgs1{$w}, -msg => $w ); } else { $mainBalloon->detach( $helpMsgs1{$w} ); } } return; } # source items in main window, not all are always present my %helpMsgs2 = ( "rise time (seconds or % of period)" => $rtLabels{$index}, "fall time (seconds or % of period)" => $ftLabels{$index}, "pulse width (seconds)" => $pwLabels{$index}, "logic \"1\" voltage (volts)" => $onLabels{$index}, "logic \"0\" voltage (volts)" => $offLabels{$index}, "initial delay (seconds)" => $delayLabels{$index}, "initial value (0, 1 or x)" => $initValLabels{$index}, "restore defaults" => $resetSrcButtons{$index}, "delete this source" => $deleteSrcButtons{$index}, "duplicate this source\n(ctrl-click to invert)" => $dupSrcButtons{$index}, "name of source" => $srcLabels{$index}, "source's positive node (source\nname is used if this is left blank)" => $posNodeLabels{$index}, "source's negative node" => $negNodeLabels{$index}, "digital bitstream" => $dataLabels{$index}, "vector name and components" => $vecCompsLabels{$index}, "description of source;\nexported as comment" => $descLabels{$index}, "frequency (Hz)" => $freqLabels{$index}, "duty cycle" => $dutyCycleLabels{$index}, "DC voltage (volts)" => $dcLabels{$index}, ); foreach my $w (keys %helpMsgs2) { if( $doBalloonHelp ) { $mainBalloon->attach( $helpMsgs2{$w}, -msg => $w ) if defined $helpMsgs2{$w}; } else { $mainBalloon->detach( $helpMsgs2{$w} ) if defined $helpMsgs2{$w}; } } # items in source list window my %helpMsgs3 = ( "find source" => $sourceListButtons{$index}, "let source be exported/plotted" => $sourceListExpCheckB{$index} ); foreach my $w (keys %helpMsgs3) { if( $doBalloonHelp ) { $sourceListBalloon->attach( $helpMsgs3{$w}, -msg => $w ); } else { $sourceListBalloon->detach( $helpMsgs3{$w} ); } } } # create widgets for a new source sub createNewSourceWidgets { my( $index, $sourceType ) = @_; if( $numSources == 0 ) { $main->geometry() =~ /^(\d+)x(\d+)/; my $width = $1; my $height = $2; # don't have window size flash back and forth... $main->packPropagate( 0 ); $sourceFrame = $main->TFrame( -label => "Sources" ) ->pack( -padx => 10, -expand => 1, -side => "bottom", -fill => "both" ); $sourcePane = $sourceFrame->Scrolled( "Pane", Name => "sourcePane", -scrollbars => "se", -sticky => "ew" ) ->pack( -expand => 1, -fill => "both" ); # resize to show (most of) first source $height = 375 if $height < 375; $width = 725 if $width < 725; $main->geometry( sprintf( "=%dx%d", $width, $height ) ); } $sourceTypes{$index} = $sourceType; if( $sourceType =~ /dc/i ) { $sourceType = "DC"; } else { $sourceType =~ s/^(.)/\u$1/; } $sourceEnclosures{$index} = $sourcePane->TFrame( -label => "$sourceType" ) ->pack( -expand => 1, -fill => "both", -padx => 5, -pady => 1 ); my $descContainer = $sourceEnclosures{$index}->Frame() ->pack( -pady => 1, -expand => 1, -fill => "x" ); my $srcContainer = $sourceEnclosures{$index}->Frame() ->pack( -pady => 1, -expand => 1, -fill => "x" ); my $dataContainer = $sourceEnclosures{$index}->Frame() ->pack( -pady => 1, -expand => 1, -fill => "x" ); my $waveParamsContainer = $sourceEnclosures{$index}->Frame() ->pack( -pady => 1, -expand => 1, -fill => "x" ); $descFrames{$index} = $descContainer->Frame( -borderwidth => 1, -relief => "raised" ) ->pack( -padx => 10, -pady => 3, -side => "left" ); $descLabels{$index} = $descContainer->Label( -anchor => "w", -text => "Description" ) ->pack( -in => $descFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $descEntries{$index} = $descContainer->Entry( -highlightthickness => 0, -width => 50 ) ->pack( -in => $descFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $descEntries{$index}->bind( '' => sub{ setSourceListButton( $index ); $needSaveFlag=1; } ); $sourceListExpCheckB{$index} = $descContainer->Checkbutton( -text => "Exportable"); my $tmp = $sourceListExpCheckB{$index}; $tmp->configure( -variable => \$isExportable{$index}, -command => sub { widgetStatus(); }, -onvalue => "on", -offvalue => "off" ); $tmp->select; # exportable by default $tmp->pack( -padx => 5, -side => "right" ); if( $sourceType =~ /bus/i ) { $vecCompsFrames{$index} = $srcContainer->Frame( -borderwidth => 1, -relief => "raised" ) ->pack( -padx=>10, -side=>"left" ); $vecCompsLabels{$index} = $srcContainer->Label( -anchor => "w", -text => "Vector" ) ->pack( -in => $vecCompsFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $vecCompsEntries{$index} = $srcContainer->Entry( -highlightthickness => 0, -width => 20 ) ->pack( -in => $vecCompsFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $vecCompsEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); $negNodeFrames{$index} = $srcContainer->Frame( -borderwidth => 1, -relief => "raised" ) ->pack( -padx => 10, -side => "left" ); $negNodeLabels{$index} = $srcContainer->Label( -anchor => "w", -text => "- node" ) ->pack( -in => $negNodeFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $negNodeEntries{$index} = $srcContainer->Entry( -highlightthickness => 0, -width => 10 ) ->pack( -in => $negNodeFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); } else { $srcFrames{$index} = $srcContainer->Frame( -borderwidth => 1, -relief => "raised" ) ->pack( -padx=>10, -side=>"left" ); $srcLabels{$index} = $srcContainer->Label( -anchor => "w", -text => "Source name" ) ->pack( -in => $srcFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $srcEntries{$index} = $srcContainer->Entry( -highlightthickness => 0, -width=>16 ) ->pack( -in => $srcFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $srcEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); $posNodeFrames{$index} = $srcContainer->Frame( -borderwidth => 1, -relief => "raised" ); $posNodeLabels{$index} = $srcContainer->Label( -anchor => "w", -text => "+ node" ); $posNodeEntries{$index} = $srcContainer->Entry( -highlightthickness => 0, -width=>10 ); $posNodeFrames{$index}->pack(-padx=>10,-side=>"left"); $posNodeLabels{$index}->pack( -in => $posNodeFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $posNodeEntries{$index}->pack( -in => $posNodeFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $posNodeEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); $negNodeFrames{$index} = $srcContainer->Frame( -borderwidth => 1, -relief => "raised" ); $negNodeLabels{$index} = $srcContainer->Label( -anchor => "w", -text => "- node" ); $negNodeEntries{$index} = $srcContainer->Entry( -highlightthickness => 0, -width=>10 ); $negNodeFrames{$index}->pack( -padx=>10, -side=>"left" ); $negNodeLabels{$index}->pack( -in => $negNodeFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $negNodeEntries{$index}->pack( -in => $negNodeFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); } $negNodeEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); if( $sourceType =~ /dc/i ) { $dcFrames{$index} = $waveParamsContainer->Frame( -borderwidth => 1, -relief => "raised" ); $dcLabels{$index} = $waveParamsContainer->Label( -anchor => "w", -text => "DC" ); $dcEntries{$index} = $waveParamsContainer->Entry( -highlightthickness => 0, -justify => "right", -width => 5 ); $dcFrames{$index}->pack( -padx => 10, -side => "left" ); $dcLabels{$index}->pack( -in => $dcFrames{$index}, -expand => 0, -fill => "none", -padx => 2, -pady => 3, -side => "left" ); $dcEntries{$index}->pack( -in => $dcFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $dcEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); } else { if( $sourceType =~ /bus/i or $sourceType =~ /single/i ) { $dataFrames{$index} = $dataContainer->Frame( -borderwidth => 1, -relief => "raised" ) ->pack( -padx => 10, -pady => 3, -side => "left", -expand => 1, -fill => "x" ); $dataLabels{$index} = $dataContainer->Label( -anchor => "w", -text => "Data" ) ->pack( -in => $dataFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $dataEntries{$index} = $dataContainer->Entry( -highlightthickness => 0, -width => 50 ) ->pack( -in => $dataFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $dataEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); } $rtFrames{$index} = $waveParamsContainer->Frame( -borderwidth => 1, -relief => "raised" ); $rtLabels{$index} = $waveParamsContainer->Label( -anchor => "w", -text => "rt" ); $rtEntries{$index} = $waveParamsContainer->Entry( -highlightthickness => 0, -justify => "right", -width => 5 ); $rtFrames{$index}->pack( -padx => 10, -side => "left" ); $rtLabels{$index}->pack( -in => $rtFrames{$index}, -expand => 0, -fill => "none", -padx => 2, -pady => 3, -side => "left" ); $rtEntries{$index}->pack( -in => $rtFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $rtEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); $ftFrames{$index} = $waveParamsContainer->Frame( -borderwidth => 1, -relief => "raised" ); $ftLabels{$index} = $waveParamsContainer->Label( -anchor => "w", -text => "ft" ); $ftEntries{$index} = $waveParamsContainer->Entry( -highlightthickness => 0, -justify => "right" , -width => 5); $ftFrames{$index}->pack( -padx => 10, -side => "left" ); $ftLabels{$index}->pack( -in => $ftFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $ftEntries{$index}->pack( -in => $ftFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $ftEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); if( $sourceType =~ /clock/i ) { $freqFrames{$index} = $waveParamsContainer->Frame( -borderwidth => 1, -relief => "raised" ) ->pack( -padx => 10, -side => "left" ); $freqLabels{$index} = $waveParamsContainer->Label( -anchor => "w", -text => "freq" ) ->pack( -in => $freqFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $freqEntries{$index} = $waveParamsContainer->Entry( -highlightthickness => 0, -justify => "right", -width => 5 ) ->pack( -in => $freqFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $freqEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); $dutyCycleFrames{$index} = $waveParamsContainer->Frame( -borderwidth => 1, -relief => "raised" ) ->pack( -padx => 10, -side => "left" ); $dutyCycleLabels{$index} = $waveParamsContainer->Label( -anchor => "w", -text => "duty cycle" ) ->pack( -in=>$dutyCycleFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $dutyCycleEntries{$index} = $waveParamsContainer->Entry( -highlightthickness => 0, -justify => "right", -width => 5 ) ->pack( -in=>$dutyCycleFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $dutyCycleEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); } else { $pwFrames{$index} = $waveParamsContainer->Frame( -borderwidth => 1, -relief => "raised" ) ->pack( -padx => 10, -side => "left" ); $pwLabels{$index} = $waveParamsContainer->Label( -anchor => "w", -text => "pw" ) ->pack( -in => $pwFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $pwEntries{$index} = $waveParamsContainer->Entry( -highlightthickness => 0, -justify => "right", -width => 5 ) ->pack( -in => $pwFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $pwEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); } $onFrames{$index} = $waveParamsContainer->Frame( -borderwidth => 1, -relief => "raised" ); $onLabels{$index} = $waveParamsContainer->Label( -anchor => "w", -text => "on" ); $onEntries{$index} = $waveParamsContainer->Entry( -highlightthickness => 0, -justify => "right", -width => 5 ); $onFrames{$index}->pack( -padx => 10, -side => "left" ); $onLabels{$index}->pack( -in => $onFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $onEntries{$index}->pack( -in => $onFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $onEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); $offFrames{$index} = $waveParamsContainer->Frame( -borderwidth => 1, -relief => "raised" ); $offLabels{$index} = $waveParamsContainer->Label( -anchor => "w", -text => "off" ); $offEntries{$index} = $waveParamsContainer->Entry( -highlightthickness => 0, -justify => "right", -width => 5 ); $offFrames{$index}->pack( -padx => 10, -side => "left" ); $offLabels{$index}->pack( -in => $offFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $offEntries{$index}->pack( -in => $offFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $offEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); $delayFrames{$index} = $waveParamsContainer->Frame( -borderwidth => 1, -relief => "raised" ); $delayLabels{$index} = $waveParamsContainer->Label( -anchor => "w", -text => "delay" ); $delayEntries{$index} = $waveParamsContainer->Entry( -highlightthickness => 0, -justify => "right", -width => 5 ); $delayFrames{$index}->pack( -padx => 10, -side => "left" ); $delayLabels{$index}->pack( -in => $delayFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $delayEntries{$index}->pack( -in => $delayFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $delayEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); } if( $sourceType =~ /single/i ) { $initValFrames{$index} = $waveParamsContainer->Frame( -borderwidth => 1, -relief => "raised" ); $initValLabels{$index} = $waveParamsContainer->Label( -anchor => "w", -text => "initVal" ); $initValEntries{$index} = $waveParamsContainer->Entry( -highlightthickness => 0, -justify => "right", -width => 1 ); $initValFrames{$index}->pack( -padx => 10, -side => "left" ); $initValLabels{$index}->pack( -in => $initValFrames{$index}, -padx => 2, -pady => 3, -side => "left" ); $initValEntries{$index}->pack( -in => $initValFrames{$index}, -expand => 1, -fill => "x", -padx => 2, -pady => 3, -side => "right" ); $initValEntries{$index}->bind( '' => sub{ $needSaveFlag=1; } ); } $resetSrcButtons{$index} = $sourceEnclosures{$index}->Button( -text => "Reset source", -command => sub { resetSrc( $index ); } ); $resetSrcButtons{$index}->pack( -padx => 10, -pady => 5, -side => "left" ); $deleteSrcButtons{$index} = $sourceEnclosures{$index}->Button( -text => "Delete source", -command => sub { deleteSrc( $index, 1 ); } ); $deleteSrcButtons{$index}->pack( -padx => 10, -pady => 5, -side => "left" ); $dupSrcButtons{$index} = $sourceEnclosures{$index}->Button( -text => "Duplicate source", -command => sub { dupSrc( $index ); } ); $dupSrcButtons{$index}->bind( '' => sub{ $invertDupSrc = 1; } ); $dupSrcButtons{$index}->pack( -padx => 10, -pady => 5, -side => "left" ); # initialize entry widgets with defaults resetSrc( $index ); # scroll pane down to see most recently added widgets # have to "update" to draw widgets before we can "see" them $main->update(); $sourcePane->see( $resetSrcButtons{$index} ); } # delete a source sub deleteSrc { my( $source, $doResize) = @_; $sourceEnclosures{$source}->destroy(); $sourceListFrames{$source}->destroy(); delete $descFrames{$source}; delete $descLabels{$source}; delete $descEntries{$source}; delete $srcFrames{$source}; delete $srcLabels{$source}; delete $srcEntries{$source}; delete $vecCompsFrames{$source}; delete $vecCompsLabels{$source}; delete $vecCompsEntries{$source}; delete $posNodeFrames{$source}; delete $posNodeLabels{$source}; delete $posNodeEntries{$source}; delete $negNodeFrames{$source}; delete $negNodeLabels{$source}; delete $negNodeEntries{$source}; delete $dataFrames{$source}; delete $dataLabels{$source}; delete $dataEntries{$source}; delete $dcFrames{$source}; delete $dcLabels{$source}; delete $dcEntries{$source}; delete $rtFrames{$source}; delete $rtLabels{$source}; delete $rtEntries{$source}; delete $ftFrames{$source}; delete $ftLabels{$source}; delete $ftEntries{$source}; delete $pwFrames{$source}; delete $pwLabels{$source}; delete $pwEntries{$source}; delete $freqFrames{$source}; delete $freqLabels{$source}; delete $freqEntries{$source}; delete $dutyCycleFrames{$source}; delete $dutyCycleLabels{$source}; delete $dutyCycleEntries{$source}; delete $onFrames{$source}; delete $onLabels{$source}; delete $onEntries{$source}; delete $offFrames{$source}; delete $offLabels{$source}; delete $offEntries{$source}; delete $delayFrames{$source}; delete $delayLabels{$source}; delete $delayEntries{$source}; delete $initValFrames{$source}; delete $initValLabels{$source}; delete $initValEntries{$source}; delete $resetSrcButtons{$source}; delete $deleteSrcButtons{$source}; delete $sourceEnclosures{$source}; delete $sourceTypes{$source}; delete $sourceListButtons{$source}; delete $sourceListExpCheckB{$source}; delete $sourceListFrames{$source}; # did we delete them all? if( --$numSources == 0 ) { $sourceFrame->destroy(); # resize height to only show top if( $doResize ) { $main->geometry( "=580x120" ); } # nothing to save! $needSaveFlag = 0; # close source list window showSourceListWindow(); } else { $needSaveFlag = 1; } widgetStatus(); } sub setMainWindowTitle { return if $tty; my $title = "BitGen $BitGen_Version: "; if( defined( $currentFilename ) ) { $title .= "$currentFilename "; } else { $title .= "(no BitGen file) "; } if( defined( $currentNetlistname ) ) { $title .= "($currentNetlistname)"; } else { $title .= "(no netlist file)"; } $main->configure( -title => $title ); $main->iconname( $title ); } sub doMessageBox { my( $title, $msg, $icon, $type ) = @_; $type = "OK" if !defined( $type ); if( $tty ) { print STDERR "$type: $msg\n"; } else { $main->bell() if $icon =~ /error/i; return $main->messageBox( -icon => $icon, -type => $type, -title => $title, -message => $msg ); } } sub doExit { my $save = "no"; if( $needSaveFlag and $numSources > 0 ) { $save = doMessageBox( "Save needed", "Save BitGen file before exit?", "question", "YesNoCancel" ); } doSave( "Save" ) if $save =~ /^y/i; exit( 0 ) unless $save =~ /^c/i; } # vim:ts=4:columns=90:tw=70: