"I am a person who works hard and plays hard."

Yuan Wei
Second Year Graduate Student Department of Computer Science
University of Virginia Charlottesville, VA 22903
Email: yw3f@cs.virginia.edu


Source Code Analysis

Main Page   Compound List   File List   Compound Members   File Members  

sim-bpred.c

Go to the documentation of this file.
00001 /*
00002  * sim-bpred.c - sample branch predictor simulator implementation
00003  *
00004  * This file is a part of the SimpleScalar tool suite written by
00005  * Todd M. Austin as a part of the Multiscalar Research Project.
00006  *  
00007  * The tool suite is currently maintained by Doug Burger and Todd M. Austin.
00008  * 
00009  * Copyright (C) 1994, 1995, 1996, 1997, 1998 by Todd M. Austin
00010  *
00011  * This source file is distributed "as is" in the hope that it will be
00012  * useful.  The tool set comes with no warranty, and no author or
00013  * distributor accepts any responsibility for the consequences of its
00014  * use. 
00015  * 
00016  * Everyone is granted permission to copy, modify and redistribute
00017  * this tool set under the following conditions:
00018  * 
00019  *    This source code is distributed for non-commercial use only. 
00020  *    Please contact the maintainer for restrictions applying to 
00021  *    commercial use.
00022  *
00023  *    Permission is granted to anyone to make or distribute copies
00024  *    of this source code, either as received or modified, in any
00025  *    medium, provided that all copyright notices, permission and
00026  *    nonwarranty notices are preserved, and that the distributor
00027  *    grants the recipient permission for further redistribution as
00028  *    permitted by this document.
00029  *
00030  *    Permission is granted to distribute this file in compiled
00031  *    or executable form under the same conditions that apply for
00032  *    source code, provided that either:
00033  *
00034  *    A. it is accompanied by the corresponding machine-readable
00035  *       source code,
00036  *    B. it is accompanied by a written offer, with no time limit,
00037  *       to give anyone a machine-readable copy of the corresponding
00038  *       source code in return for reimbursement of the cost of
00039  *       distribution.  This written offer must permit verbatim
00040  *       duplication by anyone, or
00041  *    C. it is distributed by someone who received only the
00042  *       executable form, and is accompanied by a copy of the
00043  *       written offer of source code that they received concurrently.
00044  *
00045  * In other words, you are welcome to use, share and improve this
00046  * source file.  You are forbidden to forbid anyone else to use, share
00047  * and improve what you give them.
00048  *
00049  * INTERNET: dburger@cs.wisc.edu
00050  * US Mail:  1210 W. Dayton Street, Madison, WI 53706
00051  *
00052  * $Id: sim-bpred.c,v 1.1.1.1 2000/05/26 15:19:03 taustin Exp $
00053  *
00054  * $Log: sim-bpred.c,v $
00055  * Revision 1.1.1.1  2000/05/26 15:19:03  taustin
00056  * SimpleScalar Tool Set
00057  *
00058  *
00059  * Revision 1.3  1999/12/31 18:46:55  taustin
00060  * quad_t naming conflicts removed
00061  *
00062  * Revision 1.2  1999/12/13 18:45:29  taustin
00063  * cross endian execution support added
00064  *
00065  * Revision 1.1  1998/08/27 15:54:35  taustin
00066  * Initial revision
00067  *
00068  *
00069  */
00070 
00071 #include <stdio.h>
00072 #include <stdlib.h>
00073 #include <math.h>
00074 
00075 #include "host.h"
00076 #include "misc.h"
00077 #include "machine.h"
00078 #include "regs.h"
00079 #include "memory.h"
00080 #include "loader.h"
00081 #include "syscall.h"
00082 #include "dlite.h"
00083 #include "options.h"
00084 #include "stats.h"
00085 #include "bpred.h"
00086 #include "sim.h"
00087 
00088 /*
00089  * This file implements a branch predictor analyzer.
00090  */
00091 
00092 /* simulated registers */
00093 static struct regs_t regs;
00094 
00095 /* simulated memory */
00096 static struct mem_t *mem = NULL;
00097 
00098 /* maximum number of inst's to execute */
00099 static unsigned int max_insts;
00100 
00101 /* branch predictor type {nottaken|taken|perfect|bimod|2lev} */
00102 static char *pred_type;
00103 
00104 /* bimodal predictor config (<table_size>) */
00105 static int bimod_nelt = 1;
00106 static int bimod_config[1] =
00107   { /* bimod tbl size */2048 };
00108 
00109 /* 2-level predictor config (<l1size> <l2size> <hist_size> <xor>) */
00110 static int twolev_nelt = 4;
00111 static int twolev_config[4] =
00112   { /* l1size */1, /* l2size */1024, /* hist */8, /* xor */FALSE};
00113 
00114 /* combining predictor config (<meta_table_size> */
00115 static int comb_nelt = 1;
00116 static int comb_config[1] =
00117   { /* meta_table_size */1024 };
00118 
00119 /* return address stack (RAS) size */
00120 static int ras_size = 8;
00121 
00122 /* BTB predictor config (<num_sets> <associativity>) */
00123 static int btb_nelt = 2;
00124 static int btb_config[2] =
00125   { /* nsets */512, /* assoc */4 };
00126 
00127 /* branch predictor */
00128 static struct bpred_t *pred;
00129 
00130 /* track number of insn and refs */
00131 static counter_t sim_num_refs = 0;
00132 
00133 /* total number of branches executed */
00134 static counter_t sim_num_branches = 0;
00135 
00136 
00137 /* register simulator-specific options */
00138 void
00139 sim_reg_options(struct opt_odb_t *odb)
00140 {
00141   opt_reg_header(odb, 
00142 "sim-bpred: This simulator implements a branch predictor analyzer.\n"
00143                  );
00144 
00145   /* branch predictor options */
00146   opt_reg_note(odb,
00147 "  Branch predictor configuration examples for 2-level predictor:\n"
00148 "    Configurations:   N, M, W, X\n"
00149 "      N   # entries in first level (# of shift register(s))\n"
00150 "      W   width of shift register(s)\n"
00151 "      M   # entries in 2nd level (# of counters, or other FSM)\n"
00152 "      X   (yes-1/no-0) xor history and address for 2nd level index\n"
00153 "    Sample predictors:\n"
00154 "      GAg     : 1, W, 2^W, 0\n"
00155 "      GAp     : 1, W, M (M > 2^W), 0\n"
00156 "      PAg     : N, W, 2^W, 0\n"
00157 "      PAp     : N, W, M (M == 2^(N+W)), 0\n"
00158 "      gshare  : 1, W, 2^W, 1\n"
00159 "  Predictor `comb' combines a bimodal and a 2-level predictor.\n"
00160                );
00161 
00162   /* instruction limit */
00163   opt_reg_uint(odb, "-max:inst", "maximum number of inst's to execute",
00164                &max_insts, /* default */0,
00165                /* print */TRUE, /* format */NULL);
00166 
00167   opt_reg_string(odb, "-bpred",
00168                  "branch predictor type {nottaken|taken|bimod|2lev|comb}",
00169                  &pred_type, /* default */"bimod",
00170                  /* print */TRUE, /* format */NULL);
00171 
00172   opt_reg_int_list(odb, "-bpred:bimod",
00173                    "bimodal predictor config (<table size>)",
00174                    bimod_config, bimod_nelt, &bimod_nelt,
00175                    /* default */bimod_config,
00176                    /* print */TRUE, /* format */NULL, /* !accrue */FALSE);
00177 
00178   opt_reg_int_list(odb, "-bpred:2lev",
00179                    "2-level predictor config "
00180                    "(<l1size> <l2size> <hist_size> <xor>)",
00181                    twolev_config, twolev_nelt, &twolev_nelt,
00182                    /* default */twolev_config,
00183                    /* print */TRUE, /* format */NULL, /* !accrue */FALSE);
00184 
00185   opt_reg_int_list(odb, "-bpred:comb",
00186                    "combining predictor config (<meta_table_size>)",
00187                    comb_config, comb_nelt, &comb_nelt,
00188                    /* default */comb_config,
00189                    /* print */TRUE, /* format */NULL, /* !accrue */FALSE);
00190 
00191   opt_reg_int(odb, "-bpred:ras",
00192               "return address stack size (0 for no return stack)",
00193               &ras_size, /* default */ras_size,
00194               /* print */TRUE, /* format */NULL);
00195 
00196   opt_reg_int_list(odb, "-bpred:btb",
00197                    "BTB config (<num_sets> <associativity>)",
00198                    btb_config, btb_nelt, &btb_nelt,
00199                    /* default */btb_config,
00200                    /* print */TRUE, /* format */NULL, /* !accrue */FALSE);
00201 }
00202 
00203 /* check simulator-specific option values */
00204 void
00205 sim_check_options(struct opt_odb_t *odb, int argc, char **argv)
00206 {
00207   if (!mystricmp(pred_type, "taken"))
00208     {
00209       /* static predictor, not taken */
00210       pred = bpred_create(BPredTaken, 0, 0, 0, 0, 0, 0, 0, 0, 0);
00211     }
00212   else if (!mystricmp(pred_type, "nottaken"))
00213     {
00214       /* static predictor, taken */
00215       pred = bpred_create(BPredNotTaken, 0, 0, 0, 0, 0, 0, 0, 0, 0);
00216     }
00217   else if (!mystricmp(pred_type, "bimod"))
00218     {
00219       if (bimod_nelt != 1)
00220         fatal("bad bimod predictor config (<table_size>)");
00221       if (btb_nelt != 2)
00222         fatal("bad btb config (<num_sets> <associativity>)");
00223 
00224       /* bimodal predictor, bpred_create() checks BTB_SIZE */
00225       pred = bpred_create(BPred2bit,
00226                           /* bimod table size */bimod_config[0],
00227                           /* 2lev l1 size */0,
00228                           /* 2lev l2 size */0,
00229                           /* meta table size */0,
00230                           /* history reg size */0,
00231                           /* history xor address */0,
00232                           /* btb sets */btb_config[0],
00233                           /* btb assoc */btb_config[1],
00234                           /* ret-addr stack size */ras_size);
00235     }
00236   else if (!mystricmp(pred_type, "2lev"))
00237     {
00238       /* 2-level adaptive predictor, bpred_create() checks args */
00239       if (twolev_nelt != 4)
00240         fatal("bad 2-level pred config (<l1size> <l2size> <hist_size> <xor>)");
00241       if (btb_nelt != 2)
00242         fatal("bad btb config (<num_sets> <associativity>)");
00243 
00244       pred = bpred_create(BPred2Level,
00245                           /* bimod table size */0,
00246                           /* 2lev l1 size */twolev_config[0],
00247                           /* 2lev l2 size */twolev_config[1],
00248                           /* meta table size */0,
00249                           /* history reg size */twolev_config[2],
00250                           /* history xor address */twolev_config[3],
00251                           /* btb sets */btb_config[0],
00252                           /* btb assoc */btb_config[1],
00253                           /* ret-addr stack size */ras_size);
00254     }
00255   else if (!mystricmp(pred_type, "comb"))
00256     {
00257       /* combining predictor, bpred_create() checks args */
00258       if (twolev_nelt != 4)
00259         fatal("bad 2-level pred config (<l1size> <l2size> <hist_size> <xor>)");
00260       if (bimod_nelt != 1)
00261         fatal("bad bimod predictor config (<table_size>)");
00262       if (comb_nelt != 1)
00263         fatal("bad combining predictor config (<meta_table_size>)");
00264       if (btb_nelt != 2)
00265         fatal("bad btb config (<num_sets> <associativity>)");
00266 
00267       pred = bpred_create(BPredComb,
00268                           /* bimod table size */bimod_config[0],
00269                           /* l1 size */twolev_config[0],
00270                           /* l2 size */twolev_config[1],
00271                           /* meta table size */comb_config[0],
00272                           /* history reg size */twolev_config[2],
00273                           /* history xor address */twolev_config[3],
00274                           /* btb sets */btb_config[0],
00275                           /* btb assoc */btb_config[1],
00276                           /* ret-addr stack size */ras_size);
00277     }
00278   else
00279     fatal("cannot parse predictor type `%s'", pred_type);
00280 }
00281 
00282 /* register simulator-specific statistics */
00283 void
00284 sim_reg_stats(struct stat_sdb_t *sdb)
00285 {
00286   stat_reg_counter(sdb, "sim_num_insn",
00287                    "total number of instructions executed",
00288                    &sim_num_insn, sim_num_insn, NULL);
00289   stat_reg_counter(sdb, "sim_num_refs",
00290                    "total number of loads and stores executed",
00291                    &sim_num_refs, 0, NULL);
00292   stat_reg_int(sdb, "sim_elapsed_time",
00293                "total simulation time in seconds",
00294                &sim_elapsed_time, 0, NULL);
00295   stat_reg_formula(sdb, "sim_inst_rate",
00296                    "simulation speed (in insts/sec)",
00297                    "sim_num_insn / sim_elapsed_time", NULL);
00298 
00299   stat_reg_counter(sdb, "sim_num_branches",
00300                    "total number of branches executed",
00301                    &sim_num_branches, /* initial value */0, /* format */NULL);
00302   stat_reg_formula(sdb, "sim_IPB",
00303                    "instruction per branch",
00304                    "sim_num_insn / sim_num_branches", /* format */NULL);
00305 
00306   /* register predictor stats */
00307   if (pred)
00308     bpred_reg_stats(pred, sdb);
00309 }
00310 
00311 /* initialize the simulator */
00312 void
00313 sim_init(void)
00314 {
00315   sim_num_refs = 0;
00316 
00317   /* allocate and initialize register file */
00318   regs_init(&regs);
00319 
00320   /* allocate and initialize memory space */
00321   mem = mem_create("mem");
00322   mem_init(mem);
00323 }
00324 
00325 /* local machine state accessor */
00326 static char *                                   /* err str, NULL for no err */
00327 bpred_mstate_obj(FILE *stream,                  /* output stream */
00328                  char *cmd,                     /* optional command string */
00329                  struct regs_t *regs,           /* register to access */
00330                  struct mem_t *mem)             /* memory to access */
00331 {
00332   /* just dump intermediate stats */
00333   sim_print_stats(stream);
00334 
00335   /* no error */
00336   return NULL;
00337 }
00338 
00339 /* load program into simulated state */
00340 void
00341 sim_load_prog(char *fname,              /* program to load */
00342               int argc, char **argv,    /* program arguments */
00343               char **envp)              /* program environment */
00344 {
00345   /* load program text and data, set up environment, memory, and regs */
00346   ld_load_prog(fname, argc, argv, envp, &regs, mem, TRUE);
00347 
00348   /* initialize the DLite debugger */
00349   dlite_init(md_reg_obj, dlite_mem_obj, bpred_mstate_obj);
00350 }
00351 
00352 /* print simulator-specific configuration information */
00353 void
00354 sim_aux_config(FILE *stream)            /* output stream */
00355 {
00356   /* nothing currently */
00357 }
00358 
00359 /* dump simulator-specific auxiliary simulator statistics */
00360 void
00361 sim_aux_stats(FILE *stream)             /* output stream */
00362 {
00363   /* nada */
00364 }
00365 
00366 /* un-initialize simulator-specific state */
00367 void
00368 sim_uninit(void)
00369 {
00370   /* nada */
00371 }
00372 
00373 
00374 /*
00375  * configure the execution engine
00376  */
00377 
00378 /*
00379  * precise architected register accessors
00380  */
00381 
00382 /* next program counter */
00383 #define SET_NPC(EXPR)           (regs.regs_NPC = (EXPR))
00384 
00385 /* target program counter */
00386 #undef  SET_TPC
00387 #define SET_TPC(EXPR)           (target_PC = (EXPR))
00388 
00389 /* current program counter */
00390 #define CPC                     (regs.regs_PC)
00391 
00392 /* general purpose registers */
00393 #define GPR(N)                  (regs.regs_R[N])
00394 #define SET_GPR(N,EXPR)         (regs.regs_R[N] = (EXPR))
00395 
00396 #if defined(TARGET_PISA)
00397 
00398 /* floating point registers, L->word, F->single-prec, D->double-prec */
00399 #define FPR_L(N)                (regs.regs_F.l[(N)])
00400 #define SET_FPR_L(N,EXPR)       (regs.regs_F.l[(N)] = (EXPR))
00401 #define FPR_F(N)                (regs.regs_F.f[(N)])
00402 #define SET_FPR_F(N,EXPR)       (regs.regs_F.f[(N)] = (EXPR))
00403 #define FPR_D(N)                (regs.regs_F.d[(N) >> 1])
00404 #define SET_FPR_D(N,EXPR)       (regs.regs_F.d[(N) >> 1] = (EXPR))
00405 
00406 /* miscellaneous register accessors */
00407 #define SET_HI(EXPR)            (regs.regs_C.hi = (EXPR))
00408 #define HI                      (regs.regs_C.hi)
00409 #define SET_LO(EXPR)            (regs.regs_C.lo = (EXPR))
00410 #define LO                      (regs.regs_C.lo)
00411 #define FCC                     (regs.regs_C.fcc)
00412 #define SET_FCC(EXPR)           (regs.regs_C.fcc = (EXPR))
00413 
00414 #elif defined(TARGET_ALPHA)
00415 
00416 /* floating point registers, L->word, F->single-prec, D->double-prec */
00417 #define FPR_Q(N)                (regs.regs_F.q[N])
00418 #define SET_FPR_Q(N,EXPR)       (regs.regs_F.q[N] = (EXPR))
00419 #define FPR(N)                  (regs.regs_F.d[N])
00420 #define SET_FPR(N,EXPR)         (regs.regs_F.d[N] = (EXPR))
00421 
00422 /* miscellaneous register accessors */
00423 #define FPCR                    (regs.regs_C.fpcr)
00424 #define SET_FPCR(EXPR)          (regs.regs_C.fpcr = (EXPR))
00425 #define UNIQ                    (regs.regs_C.uniq)
00426 #define SET_UNIQ(EXPR)          (regs.regs_C.uniq = (EXPR))
00427 
00428 #else
00429 #error No ISA target defined...
00430 #endif
00431 
00432 /* precise architected memory state help functions */
00433 #define READ_BYTE(SRC, FAULT)                                           \
00434   ((FAULT) = md_fault_none, MEM_READ_BYTE(mem, addr = (SRC)))
00435 #define READ_HALF(SRC, FAULT)                                           \
00436   ((FAULT) = md_fault_none, MEM_READ_HALF(mem, addr = (SRC)))
00437 #define READ_WORD(SRC, FAULT)                                           \
00438   ((FAULT) = md_fault_none, MEM_READ_WORD(mem, addr = (SRC)))
00439 #ifdef HOST_HAS_QWORD
00440 #define READ_QWORD(SRC, FAULT)                                          \
00441   ((FAULT) = md_fault_none, MEM_READ_QWORD(mem, addr = (SRC)))
00442 #endif /* HOST_HAS_QWORD */
00443 
00444 #define WRITE_BYTE(SRC, DST, FAULT)                                     \
00445   ((FAULT) = md_fault_none, MEM_WRITE_BYTE(mem, addr = (DST), (SRC)))
00446 #define WRITE_HALF(SRC, DST, FAULT)                                     \
00447   ((FAULT) = md_fault_none, MEM_WRITE_HALF(mem, addr = (DST), (SRC)))
00448 #define WRITE_WORD(SRC, DST, FAULT)                                     \
00449   ((FAULT) = md_fault_none, MEM_WRITE_WORD(mem, addr = (DST), (SRC)))
00450 #ifdef HOST_HAS_QWORD
00451 #define WRITE_QWORD(SRC, DST, FAULT)                                    \
00452   ((FAULT) = md_fault_none, MEM_WRITE_QWORD(mem, addr = (DST), (SRC)))
00453 #endif /* HOST_HAS_QWORD */
00454 
00455 /* system call handler macro */
00456 #define SYSCALL(INST)   sys_syscall(&regs, mem_access, mem, INST, TRUE)
00457 
00458 /* start simulation, program loaded, processor precise state initialized */
00459 void
00460 sim_main(void)
00461 {
00462   md_inst_t inst;
00463   register md_addr_t addr, target_PC;
00464   enum md_opcode op;
00465   register int is_write;
00466   int stack_idx;
00467   enum md_fault_type fault;
00468 
00469   fprintf(stderr, "sim: ** starting functional simulation w/ predictors **\n");
00470 
00471   /* set up initial default next PC */
00472   regs.regs_NPC = regs.regs_PC + sizeof(md_inst_t);
00473 
00474   /* check for DLite debugger entry condition */
00475   if (dlite_check_break(regs.regs_PC, /* no access */0, /* addr */0, 0, 0))
00476     dlite_main(regs.regs_PC - sizeof(md_inst_t), regs.regs_PC,
00477                sim_num_insn, &regs, mem);
00478 
00479   while (TRUE)
00480     {
00481       /* maintain $r0 semantics */
00482       regs.regs_R[MD_REG_ZERO] = 0;
00483 #ifdef TARGET_ALPHA
00484       regs.regs_F.d[MD_REG_ZERO] = 0.0;
00485 #endif /* TARGET_ALPHA */
00486 
00487       /* get the next instruction to execute */
00488       MD_FETCH_INST(inst, mem, regs.regs_PC);
00489 
00490       /* keep an instruction count */
00491       sim_num_insn++;
00492 
00493       /* set default reference address and access mode */
00494       addr = 0; is_write = FALSE;
00495 
00496       /* set default fault - none */
00497       fault = md_fault_none;
00498 
00499       /* decode the instruction */
00500       MD_SET_OPCODE(op, inst);
00501 
00502       /* execute the instruction */
00503       switch (op)
00504         {
00505 #define DEFINST(OP,MSK,NAME,OPFORM,RES,FLAGS,O1,O2,I1,I2,I3)            \
00506         case OP:                                                        \
00507           SYMCAT(OP,_IMPL);                                             \
00508           break;
00509 #define DEFLINK(OP,MSK,NAME,MASK,SHIFT)                                 \
00510         case OP:                                                        \
00511           panic("attempted to execute a linking opcode");
00512 #define CONNECT(OP)
00513 #define DECLARE_FAULT(FAULT)                                            \
00514           { fault = (FAULT); break; }
00515 #include "machine.def"
00516         default:
00517           panic("attempted to execute a bogus opcode");
00518       }
00519 
00520       if (fault != md_fault_none)
00521         fatal("fault (%d) detected @ 0x%08p", fault, regs.regs_PC);
00522 
00523       if (MD_OP_FLAGS(op) & F_MEM)
00524         {
00525           sim_num_refs++;
00526           if (MD_OP_FLAGS(op) & F_STORE)
00527             is_write = TRUE;
00528         }
00529 
00530       if (MD_OP_FLAGS(op) & F_CTRL)
00531         {
00532           md_addr_t pred_PC;
00533           struct bpred_update_t update_rec;
00534 
00535           sim_num_branches++;
00536 
00537           if (pred)
00538             {
00539               /* get the next predicted fetch address */
00540               pred_PC = bpred_lookup(pred,
00541                                      /* branch addr */regs.regs_PC,
00542                                      /* target */target_PC,
00543                                      /* inst opcode */op,
00544                                      /* call? */MD_IS_CALL(op),
00545                                      /* return? */MD_IS_RETURN(op),
00546                                      /* stash an update ptr */&update_rec,
00547                                      /* stash return stack ptr */&stack_idx);
00548 
00549               /* valid address returned from branch predictor? */
00550               if (!pred_PC)
00551                 {
00552                   /* no predicted taken target, attempt not taken target */
00553                   pred_PC = regs.regs_PC + sizeof(md_inst_t);
00554                 }
00555 
00556               bpred_update(pred,
00557                            /* branch addr */regs.regs_PC,
00558                            /* resolved branch target */regs.regs_NPC,
00559                            /* taken? */regs.regs_NPC != (regs.regs_PC +
00560                                                          sizeof(md_inst_t)),
00561                            /* pred taken? */pred_PC != (regs.regs_PC +
00562                                                         sizeof(md_inst_t)),
00563                            /* correct pred? */pred_PC == regs.regs_NPC,
00564                            /* opcode */op,
00565                            /* predictor update pointer */&update_rec);
00566             }
00567         }
00568 
00569       /* check for DLite debugger entry condition */
00570       if (dlite_check_break(regs.regs_NPC,
00571                             is_write ? ACCESS_WRITE : ACCESS_READ,
00572                             addr, sim_num_insn, sim_num_insn))
00573         dlite_main(regs.regs_PC, regs.regs_NPC, sim_num_insn, &regs, mem);
00574 
00575       /* go to the next instruction */
00576       regs.regs_PC = regs.regs_NPC;
00577       regs.regs_NPC += sizeof(md_inst_t);
00578 
00579       /* finish early? */
00580       if (max_insts && sim_num_insn >= max_insts)
00581         return;
00582     }
00583 }


UVa CS Department of Computer Science
School of Engineering, University of Virginia
151 Engineer's Way, P.O. Box 400740
Charlottesville, Virginia 22904-4740

(434) 982-2200  Fax: (434) 982-2214