Imported Upstream version 0.0.4

parents
This diff is collapsed.
#
# Rudimentary makefile for "gwave" analog waveform viewer
# This makefile requires gmake.
#
#
# a few configuration items
# someday real soon we'll use autoconf
# If you have a working POSIX regcomp/regexec(3), define HAVE_POSIX_REGEXP
# and comment out REGEXP_OBJS
DFLAGS=-DHAVE_POSIX_REGEXP
#REGEXP_OBJS=regexp.o regerror.o
HW_OS=$(shell hw_os)
INSTDIR=/usr/msl/vlsi/bin/bin-$(HW_OS)
NAME=gwave-0.0.4
#############################################################################
GTKLIBS = $(shell gtk-config --libs)
GTKCFLAGS = $(shell gtk-config --cflags)
THISDIR=$(notdir $(shell pwd))
CFLAGS = -g -Wall $(GTKCFLAGS) $(DFLAGS)
LDLIBS = $(GTKLIBS)
CC=gcc
LDCC=gcc
all: gwave test_read
GWAVE_OBJS=gwave.o wavelist.o pixmaps.o cmd.o draw.o event.o wavewin.o \
reader.o rd_cazm.o rd_hspice.o gtkmisc.o $(REGEXP_OBJS)
gwave: $(GWAVE_OBJS)
$(LDCC) -o $@ $(GWAVE_OBJS) $(LDLIBS)
test_read: test_read.o reader.o rd_cazm.o rd_hspice.o $(REGEXP_OBJS)
$(LDCC) -o $@ $^ $(LDLIBS)
regexp.o: regexp.c
gcc -traditional $(CFLAGS) -c $*.c
clean:
rm -f core *.o
distclean: clean
rm -f gwave test_read *~
dist: distclean
cd .. ; \
ln -s $(THISDIR) $(NAME) ; \
gtar czvf $(NAME).tar.gz --exclude=RCS --dereference $(NAME); \
rm $(NAME)
install: gwave
cp gwave $(INSTDIR)
# manual dependencies
gwave.o cmd.o draw.o event.o wavewin.o wavelist.o: gwave.h reader.h
gtkmisc.o: gtkmisc.h
gwave.o: reader.h gwave.h
wavelist.o: gtkmisc.h
reader.o: reader.h
rd_cazm.o: reader.h
rd_hspice.o: reader.h
gwave - a viewer for the output of spice-like simulators
and other viewing of analog data.
by:
Steve Tell
Microelectronic Systems Laboratory
University of North Carolina at Chapel Hill
CB 3175 Sitterson Hall
Chapel Hill, NC 27599-3175
tell@cs.unc.edu
Installation:
Edit the top of the makefile if necessary
Compile with GNU make.
test gwave on some files in the examples directory
Copy the "gwave" binary to your favorite bin
Usage:
wave [-t filetype] [-p number-of-panels] your-waveform-file
Number-of-panels defaults to 2. It understands CAzM transient output
(.N/.B/.W) files, and the binary and ascii outputs from HSPICE (.tr0,
.sw0, and .ac0) files.
If the file type is unspecified, it tries to guess
based on filename, and then tries all file formats until one succedes.
Use the View->Variable List menu to see a list of a list of signals in
your simulation files, then drag-and-drop signals into waveform
panels.
To delete waves, select one or more of them using the buttons down the
left side of the main window, and then press the "delete" button.
Click on the waveform panels with the left and center mouse buttons to set two
cursors for measuring.
New in 0.0.3:
- Load multiple files at once, either from the command line or
interactively.
- Some operations are moved to pulldown menus.
New in 0.0.4:
- Fixed bug and/or warning when first waveform is added to display
- Ability to drag vertical bar cursors around, in addition to click-to-place
- Added zoom-to-cursor function; if both cursors have been placed,
zooms so that the time between the cursors fills the display.
- Added zoom-to-window function. Select this menu option, then
press/drag/release mouse button 1 to indicate a region of the X
axis that then zooms to fill the display.
Known problems:
- HSPICE data files generated without ".options probe" may not be read
correctly. workaround: try using ".options probe". This mostly happens with
AC sweeps.
This tool is far from complete. Partial contents of the To Do list:
Interactively changing the number of panels instead of using a lame
command-line option
Send feedback and error messages from loading of files to a popup window
or status line instead of to stdout.
Drag-and-drop to move waveform from one panel to another
Logarithmic X and Y axes
Speed up redraws and minimize flashing, especialy flashing
of waveform name/value labels when moving cursors.
Generate a postscript representation for printing or documentation.
Override of wave and panel paramters, like min/max Y value, color, etc.
Saving and restoring waveform/panel configuration
Drawing a labeled graticule in the waveform area
Seperate lists for selecting voltage/current/etc. types of variables
Feedback is welcome - tell@cs.unc.edu
/*
* cmd.c, part of the gwave waveform viewer tool
*
* Functions in this file implement basic user-interface functionality.
* Later they can become callable from the extension language
* when we add one (most will will require some glue).
*
* Copyright (C) 1998 University of North Carolina at Chapel Hill
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Log: cmd.c,v $
* Revision 1.3 1998/09/30 21:54:39 tell
* Restructure zoom commands, creating cmd_zoom_absolute and cmd_zoom_relative
* to factor out common code. Add cmd_zoom_cursor and cmd_zoom_window.
* Add supress_redraw hook for use in update_wavetable to keep things from
* getting redrawn before we're ready when adding first wave.
*
* Revision 1.2 1998/09/17 18:31:58 tell
* Fixed longstanding bug deleting multiple waves.
* update scrollbar if min_xval/max_xval change.
*
* Revision 1.1 1998/09/01 21:28:02 tell
* Initial revision
*
*/
#include <ctype.h>
#include <math.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <gtk/gtk.h>
#include "reader.h"
#include "gwave.h"
gint cmd_zoom_absolute(double start, double end)
{
if(start <= end) {
wtable->start_xval = start;
wtable->end_xval = end;
} else {
wtable->start_xval = end;
wtable->end_xval = start;
}
if(wtable->start_xval < wtable->min_xval)
wtable->start_xval = wtable->min_xval;
if(wtable->end_xval > wtable->max_xval)
wtable->end_xval = wtable->max_xval;
win_hsadj->page_size = fabs(wtable->end_xval - wtable->start_xval);
win_hsadj->page_increment = win_hsadj->page_size/2;
win_hsadj->step_increment = win_hsadj->page_size/100;
win_hsadj->value = wtable->start_xval;
win_hsadj->lower = wtable->min_xval;
win_hsadj->upper = wtable->max_xval;
gtk_signal_emit_by_name(GTK_OBJECT(win_hsadj), "changed");
gtk_signal_emit_by_name(GTK_OBJECT(win_hsadj), "value_changed");
return 0;
}
gint cmd_zoom_relative(double factor)
{
double ocenter, owidth;
g_assert(factor > DBL_EPSILON);
ocenter = (wtable->start_xval + wtable->end_xval)/2;
owidth = wtable->end_xval - wtable->start_xval;
return cmd_zoom_absolute(ocenter - owidth / (factor * 2),
ocenter + owidth / (factor * 2));
}
gint cmd_zoom_in(GtkWidget *widget)
{
return cmd_zoom_relative(2);
}
gint cmd_zoom_out(GtkWidget *widget)
{
return cmd_zoom_relative(0.5);
}
gint cmd_zoom_full(GtkWidget *widget)
{
return cmd_zoom_absolute(wtable->min_xval, wtable->max_xval);
}
gint cmd_zoom_cursors(GtkWidget *widget)
{
if(!wtable->cursor[0]->shown)
return 0;
if(!wtable->cursor[1]->shown)
return 0;
return cmd_zoom_absolute(wtable->cursor[0]->xval,
wtable->cursor[1]->xval);
}
static void
zoom_window_finish(WavePanel *wp, int x1, int x2, gpointer data)
{
double nstart, nend;
nstart = x2val(wp, x1);
nend = x2val(wp, x2);
cmd_zoom_absolute(nstart, nend);
}
gint cmd_zoom_window(GtkWidget *widget)
{
select_x_range(zoom_window_finish, NULL);
return 0;
}
typedef struct {
WavePanel *wp;
VisibleWave *vw;
} VWListItem;
static GList *vw_delete_list;
static void
vw_wp_list_if_selected(gpointer p, gpointer d)
{
VisibleWave *vw = (VisibleWave *)p;
WavePanel *wp = (WavePanel *)d;
GtkToggleButton *btn = GTK_TOGGLE_BUTTON(vw->button);
if(btn->active) {
VWListItem *vdi = g_new(VWListItem, 1);
vdi->wp = wp;
vdi->vw = vw;
vw_delete_list = g_list_append(vw_delete_list, vdi);
}
}
/*
* cmd_delete_selected_waves
*/
gint cmd_delete_selected_waves(GtkWidget *widget)
{
int i;
VWListItem *vdi;
/*
* Have to build a special list while traversing, and then do the
* deletes, because removing elements from the wp->vwlist
* during its g_list_foreach is apparently a no-no.
*/
for(i = 0; i < wtable->npanels; i++) {
WavePanel *wp = &wtable->panels[i];
g_list_foreach(wp->vwlist, vw_wp_list_if_selected, wp);
}
while((vdi = g_list_nth_data(vw_delete_list, 0)) != NULL) {
remove_wave_from_panel(vdi->wp, vdi->vw);
vw_delete_list = g_list_remove(vw_delete_list, vdi);
g_free(vdi);
}
return 0;
}
/*
* remove waveform from panel
* Somthing bad will probably happen if the waveform isn't actually
* in the indicated panel.
*/
void
remove_wave_from_panel(WavePanel *wp, VisibleWave *vw)
{
wp->vwlist = g_list_remove(wp->vwlist, vw);
gtk_widget_destroy(vw->button);
gdk_gc_destroy(vw->gc);
g_free(vw);
wavepanel_update_data(wp);
wavetable_update_data();
}
/*
* Add a waveform to a WavePanel
*/
void
add_var_to_panel(WavePanel *wp, DVar *dv)
{
VisibleWave *vw;
vw = g_new0(VisibleWave, 1);
vw->var = dv;
vw->colorn = wp->nextcolor;
wp->nextcolor = (wp->nextcolor + 1)%NWColors;
wp->vwlist = g_list_append(wp->vwlist, vw);
wavepanel_update_data(wp);
wavetable_update_data();
if(wp->lvbox) /* add button to Y-label box */
vw_wp_create_button(vw, wp);
if(wp->drawing) {
/* vw_wp_setup_gc(vw, wp); */
vw_wp_visit_draw(vw, wp);
}
}
/*
* called with g_list_foreach to update a WavePanel from all of its
* VisibleWaves.
*/
static void
vw_wp_visit_update_data(gpointer p, gpointer d)
{
VisibleWave *vw = (VisibleWave *)p;
WavePanel *wp = (WavePanel *)d;
if(vw->var->iv->d.min < wp->min_xval)
wp->min_xval = vw->var->iv->d.min;
if(vw->var->iv->d.max > wp->max_xval)
wp->max_xval = vw->var->iv->d.max;
if(vw->var->d.min < wp->min_yval)
wp->min_yval = vw->var->d.min;
if(vw->var->d.max > wp->max_yval)
wp->max_yval = vw->var->d.max;
}
/*
* wavepanel_update_data
* update wavepanel values that sumarize things over all of the
* VisibleWaves in the panel.
*/
void
wavepanel_update_data(WavePanel *wp)
{
char lbuf[128];
wp->min_xval = G_MAXDOUBLE;
wp->max_xval = G_MINDOUBLE;
wp->min_yval = G_MAXDOUBLE;
wp->max_yval = G_MINDOUBLE;
g_list_foreach(wp->vwlist, vw_wp_visit_update_data, (gpointer)wp);
/* set to something reasonable if they didn't change,
* like if the panel was empty
*/
if(wp->min_xval == G_MAXDOUBLE)
wp->min_xval = wtable->min_xval;
if(wp->max_xval == G_MINDOUBLE)
wp->max_xval = wtable->max_xval;
if(wp->min_yval == G_MAXDOUBLE)
wp->min_yval = 0.0;
if(wp->max_yval == G_MINDOUBLE)
wp->max_yval = 3.3;
/* zero height? set to +- 0.1% so a line is visible in the center */
if((wp->max_yval - wp->min_yval) < DBL_EPSILON) {
wp->max_yval *= 1.001;
wp->min_yval *= 0.999;
}
/* if start & end were the same, try updating them
* -- this probably isn't quite right.
*/
if(fabs(wp->end_xval - wp->start_xval) < DBL_EPSILON) {
wp->start_xval = wp->min_xval;
wp->end_xval = wp->max_xval;
}
/* Update y-axis labels */
if(wp->lab_min) {
sprintf(lbuf, "%.3f", wp->min_yval);
gtk_label_set(GTK_LABEL(wp->lab_min), lbuf);
}
if(wp->lab_max) {
sprintf(lbuf, "%.3f", wp->max_yval);
gtk_label_set(GTK_LABEL(wp->lab_max), lbuf);
}
}
/* Update parameters in wavetable that depend on all panels */
void
wavetable_update_data()
{
int i;
WavePanel *wp;
double old_min_x, old_max_x;
old_min_x = wtable->min_xval;
old_max_x = wtable->max_xval;
wtable->min_xval = G_MAXDOUBLE;
wtable->max_xval = G_MINDOUBLE;
for(i = 0; i < wtable->npanels; i++) {
wp = &wtable->panels[i];
if(wp->vwlist == NULL)
continue; /* no waves? min/max for panel are bogus */
if(wp->min_xval < wtable->min_xval)
wtable->min_xval = wp->min_xval;
if(wp->max_xval > wtable->max_xval)
wtable->max_xval = wp->max_xval;
}
/* still nothing? set back to zero */
if(wtable->min_xval == G_MAXDOUBLE)
wtable->min_xval = 0;
if(wtable->max_xval == G_MINDOUBLE)
wtable->max_xval = 0;
/* if start & end were the same, try updating them so we can
* see somthing.
*/
if(fabs(wtable->end_xval - wtable->start_xval) < DBL_EPSILON &&
win_hsadj != NULL) {
wtable->suppress_redraw = 1;
cmd_zoom_full(NULL);
wtable->suppress_redraw = 0;
} else if((wtable->min_xval != old_min_x
|| wtable->max_xval != old_max_x) && win_hsadj) {
/* min/max changed, might have added first (or removed last)
* wave from a file with different range.
* Keep start/end the same, but update scrollbar.
*/
win_hsadj->lower = wtable->min_xval;
win_hsadj->upper = wtable->max_xval;
wtable->suppress_redraw = 1;
gtk_signal_emit_by_name(GTK_OBJECT(win_hsadj), "value_changed");
wtable->suppress_redraw = 0;
}
}
Notes on output files from hspice (.tr0, .sw0, .ac0)
Header includes an integer that seems to indicate the type of a variable.
It doesn't seem to mean much for an independent variable.
Values seen:
3 voltage as independent var in sweep
1 voltage as dependent var in sweep or transient
2 voltage as dependent var in AC analysis
15 current
hspice file format "9601" is the default in hspice 98.2 (9007 was
previous). Difference is in how nodenames 16 characters and longer
are handled. As written, our filereader always does 9601 correctly,
and does 9007 correctly if nodnames are 15 characters or shorter.
I don't think the 9007/exactly-16 case is worth fixing.
-----------------------------------------------------------------------------
About Binary Hspice files:
generate with ".options POST=1"
Appear to have 16-byte binary header, then an ascii header similar
(identical?) to that from an ascii hspice file, followed by binary
data.
output from test.hsp:
ascii has 292 floating-point values - 97 rows of three, plus one extra.
binary has 1192 bytes in the binary data section - could be 149 doubles or
298 floats, or maybe some is some sort of header/trailer/control info.
binary header is:
00 00 00 04 00 00 00 2b 00 00 00 04 00 00 01 58 .......+.......X
guess: 4 32-bit ints,
fourth one is length in bytes of the ascii header portion. In this case,
binary data starts at offset 0x0168.
confirmed by adding another output item to test1.hsp - start of binary data
moves to offset 0x0180, 4th integer in header is 0x170.
With four plotted items, the pattern in the binary data is real obvious.
Looks like there are 5 more 32-bit longwords, then binary floats.
test1.tr0.binary:
000170 20 20 20 20 20 20 20 20 24 26 25 23 20 20 20 20 $&%#
000180 00 00 01 70 00 00 00 04 00 00 01 85 00 00 00 04 ...p............
000190 00 00 06 14 00 00 00 00 00 00 00 00 00 00 00 00 ................
0001a0 80 00 00 00 2f 09 70 5f 00 00 00 00 00 00 00 00 ..../.p_........
0001b0 80 00 00 00 2f 89 70 5f 00 00 00 00 00 00 00 00 ..../.p_........
floating-point data appears to start at offset 0x194 here.
could be that the value 0x195 (word at 0x188) is related to this offset.
the value 0x614 at offset 0x190 appears related to the size of the floating-point
data. At the end of the file we have:
000780 af c5 1b fa 34 54 d0 cd 00 00 00 00 00 00 00 00 ....4T..........
000790 af 6c 87 f9 34 56 bf 95 00 00 00 00 00 00 00 00 .l..4V..........
0007a0 af 24 5e 7d 71 49 f2 ca 00 00 06 14 .$^}qI......
Data block size is 0x7a8-0x194, or 0x614.
Last word is 0x614 - matches word at 0x190. Clearly is block-size.
Pattern for both the ascii header and the floating point data appears to be:
4-word header, last word indicating data size, followed by data, then a 1-word
trailer consisting of the size-word.
last question: what are the 32-bit floating-point values?
single-precision IEEE? single-precision native floating point? On HP,
these are the same so we really can't tell. We haven't got any hspice
binaries for other architectures, so lets blow this off for now.
-----------------------------------------------------------------------------
More complications in both ascii and binary files without ".options probe".
First bit of quickAC.ac0:
00050004000000009601 a simple ac run
09/17/9816:07:48 Copyright (C) 1985-1997 by Avant! Corporation.
0
2 1 1 1 8 2 2
8 8 HERTZ 0 1 2
I(v1 v(1 v(2 i(r2 i(c1
$&%# v(1 v(2 i(r2 i(c1
.10000E+04 .00000E+00 .00000E+00 .10000E+01 .00000E+00 .50000E+00-.15708E-02
-.50000E-03-.15708E-05 .10000E+01 .50000E+00 .50000E-03 .31416E-05 .12589E+04
.00000E+00 .00000E+00 .10000E+01 .00000E+00 .49999E+00-.19775E-02-.50001E-03
-.19775E-05 .10000E+01 .50000E+00 .50000E-03 .39550E-05 .15849E+04 .00000E+00
.00000E+00 .10000E+01 .00000E+00 .49999E+00-.24895E-02-.50001E-03-.24895E-05
.10000E+01 .49999E+00 .49999E-03 .49790E-05 .19953E+04 .00000E+00 .00000E+00
.10000E+01 .00000E+00 .49998E+00-.31340E-02-.50002E-03-.31340E-05 .10000E+01
For the first time, the first number in the header line is not 1. The
sum of those two numbers, less 1 (for the independent var), seems to
be the number of dependent variables that we have column headings for.
New variable type - 8 - seems to be another current.
Problem is that there are 13 columns, as determined by looking for the
repeating pattern of the frequency column.
Hypothesis:
first number in header is number of things put in automaticly.
independent variable counts for 1. The rest of them (four, in this case)
are complex, responsible for two columns.
Works for quickAC.ac0, chokes on quickINV.tr0:
00070002000000009601 inverter circuit
09/17/9818:22:46 Copyright (C) 1985-1997 by Avant! Corporation.
0
1 1 1 1 1 8 8
1 1 TIME 0 in out
vcc I(vcc I(vin v(in v(out
$&%# I(vcc I(vin v(in v(out
.00000E+00 .00000E+00 .20000E+00 .49989E+01 .50000E+01-.90576E-06 .00000E+00
.20000E+00 .49989E+01 .50000E-10 .00000E+00 .20000E+00 .49989E+01 .50000E+01
-.90576E-06-.54210E-19 .20000E+00 .49989E+01 .10000E-09 .00000E+00 .20000E+00
.49989E+01 .50000E+01-.90576E-06-.54210E-19 .20000E+00 .49989E+01 .30000E-09
.00000E+00 .20000E+00 .49989E+01 .50000E+01-.90576E-06-.54210E-19 .20000E+00
.49989E+01 .80000E-09 .00000E+00 .20000E+00 .49989E+01 .50000E+01-.90576E-06
.00000E+00 .20000E+00 .49989E+01 .13000E-08 .00000E+00 .20000E+00 .49989E+01
.50000E+01-.90576E-06 .00000E+00 .20000E+00 .49989E+01 .20000E-08 .00000E+00
.20000E+00 .49989E+01 .50000E+01-.90576E-06-.13553E-19 .20000E+00 .49989E+01
.21000E-08 .00000E+00 .66000E+00 .49940E+01 .50000E+01 .11557E-04-.54467E-04
Header indicates 8 dependent variables, column headings for:
TIME 0 in out
vcc I(vcc I(vin v(in v(out
Only 9 columns of data - looks like none of them are complex.
I've got no idea how to differentiate these cases from this header info.
Notes on output files from Berkeley Spice 3F5.
Ran spice3 -b <deck> -r <rawfile>
Rawfile contains:
Title: Basic RC circuit
Date: Tue Sep 15 11:35:58 1998
Plotname: Transient Analysis
Flags: real
No. Variables: 4
No. Points: 66
Command: version 3f5
Variables:
0 time time
1 V(1) voltage
2 V(2) voltage
3 vin#branch current
Binary:
<binary data - 2112 bytes>
Looks like the data is native-format doubles, No.varibles * No.points of them.
This doesn't quite seem to match any of the descriptions of file formats in
the sconvert(1) man page.
It appears to be what's written by the routines in
spice3f5/src/lib/fte/rawfile.c, however. This example is pretty
simple, but there are a bunch of options in the "flags:" line that can
affect what's in the data portion. In particular, values can be
complex instead of real, and there's an "unpadded" option that means not to
put 0's at the end of columns that contain fewer values than the others.
If things are unpadded, there is apparently some additional
per-variable indication of length.
The full generality of this appears tied up in all of nutmeg's
functionality, but the basic spice-output case may be pretty simple.
/*
* draw.c, part of the gwave waveform viewer tool
*
* Functions for drawing waveforms
*
* Copyright (C) 1998 University of North Carolina at Chapel Hill
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Log: draw.c,v $
* Revision 1.3 1998/09/30 21:52:32 tell
* various fixes to clean up lingering bug - error messages on first few redraws
* a few things for the range-select function
*
* Revision 1.2 1998/09/17 18:33:39 tell
* Only draw portion of wave for which there is actually data - with multiple
* files loaded, can scroll off the end of one.
*
* Revision 1.1 1998/09/01 21:28:09 tell
* Initial revision
*
*/
#include <ctype.h>
#include <math.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <gtk/gtk.h>
#include "reader.h"
#include "gwave.h"
/* convert value to pixmap y coord */
int val2y(double val, double top, double bot, int height)
{
return height - ((height-2) * ((val - bot ) / (top - bot))) - 1;
}
double x2val(WavePanel *wp, int x)
{
int w = wp->drawing->allocation.width;
return ((double)x/(double)w) * (wp->end_xval