/*
 * ATPERF -- PC Tech Journal AT Hardware Performance Test
 *
 * Version 1.01
 * Originally written 05/17/86
 * Last modified 10/25/86
 * Changes:
 *      1. Correct to work with zero-wait state memory.
 *      2. Detects when not running on 80286 and issues
 *         a warning.
 *
 * Copyright (c) 1986, Ziff Communications Company
 * Program by: Ted Forgeron, Paul Pierce
 *
 * Measures clock rates and memory speeds
 * of AT compatible computers.
 */

/*
 * Measurements are accumulated in the acctime array.  Each
 * element is identified by one of these constants.
 *  IRW Instruction fetch, word
 *  MRB RAM read byte
 *  MWB RAM write byte
 *  RRB ROM read byte
 *  MRW RAM read word
 *  MWW RAM write word
 *  RRW ROM read word
 *  ERB EMM read byte
 *  EWB EMM write byte
 *  ERW EMM read word
 *  EWW EMM write word
 *  VWB Video write byte
 *  VWW Video write word
 */

#define IRW     0
#define MRB     1
#define MWB     2
#define RRB     3
#define MRW     4
#define MWW     5
#define RRW     6
#define ERB     7
#define EWB     8
#define ERW     9
#define EWW    10
#define VWB    11
#define VWW    12

#define VARS   13

/* Timer rate in MHz */
#define TIMER2_RATE 1.193180

/* Number of processor clocks in a multiply instruction */
#define MULCLKS 21

/* Overhead in the multiply test */
#define MULOVH ( 15 + 14*count/100 )

/* Overhead in the mov instruction test */
#define MOVOVH ( clktime * (15 + 14*count/100) )

/* Overhead in pusha instruction test */
#define WPOVH ( clktime * (15 + 9*count/200) )

/* Number of numeric processor clocks in a FP divide */
#define FPCLKS 203

/* Processor overhead in the FP divide test */
#define FPOVH ( clktime * 9 * FPCOUNT )

/* Count for most tests */
#define COUNT 1000

/* Count for the f. p. divide test */
#define FPCOUNT 100

/* Number of trials for each test */
#define TRIALS 100


int cpu;        /* CPU type: 0=86/88,1=80186,2=80286,3=80386 */
double clkrate; /* Processor clock rate, MHz */
double clktime; /* Processor clock period, usec */
double fprate;  /* FP processor clock rate, MHz */
double fpacc;   /* FP processor clock period accumulator */
int emmok;      /* Set if extended memory is present */
int ndpok;      /* Set if math coprocessor is present */



/*
 * Main program.
 */

main(argc, argv)
        int     argc;
        char    **argv;
{
        double raw, brw, wrw, rif;   /* Variables for raw data */
        double acctime[VARS];   /* Accumulators for speeds */
        int count;              /* Number of ops per trial */
        int trials;             /* Number of repetitions */
        register int i;

        count = COUNT;
        trials = TRIALS;

        /*
         * Find out the CPU type and set
         * parameters accordingly.
         */

        cpu = cpu_type();

        if (cpu != 2) {
                printf("\nThis version of ATPERF is for ");
                printf("80286-based machines only.\n");
                exit();
        }

        /*
         * Measure the clock rate by executing
         * multiply instructions.  Each multiply
         * takes a fixed number of clock cycles.
         */

        clktime = 0;
        for (i = 0; i < trials; i++) {

                /*
                 * Obtain the number of clock ticks for
                 * "count" multiplies.
                 */

                raw = multime(count);

                /*
                 * Accumulate the clock time in microseconds
                 * by adjusting for the timer rate,
                 * number of clocks per multiply,
                 * instruction count, and test overhead.
                 */

                clktime +=  raw /
                        (TIMER2_RATE * (MULCLKS*count + MULOVH));
        }

        /*
         * Calculate the average clock period by dividing by
         * the number of trials.  The clock rate is the
         * inverse of the clock period.
         */

        clktime /= trials;
        clkrate = 1.0/clktime;

        /*
         * Determine whether there a math coprocessor
         * in the system.
         */

        ndpok = ndp_present();

        /*
         * Determine whether there is extended memory in the
         * system and allocate a piece of it for testing.
         */

        emmok = (setup_emm() == 0);

        /*
         * Detect the type of video card and save the
         * information for the video measurements.
         */

        setup_video();

        /*
         * Clear all of the memory speed accumulators.
         */

        for (i = 0; i < VARS; i++)
                acctime[i] = 0;

        /*
         * Do the memory speed tests.
         */

        for (i = 0; i < trials; i++) {

                /*
                 * Obtain the number of timer ticks for
                 * "count" mov instructions, which are
                 * limited by memory fetch time.
                 */

                rif = wmovtime(count);

                /*
                 * Accumulate the number of microseconds
                 * per instruction fetch by adjusting for
                 * the timer rate, test overhead, and
                 * instruction count.
                 */

                acctime[IRW] +=
                        (rif / TIMER2_RATE - MOVOVH) / count;

                /*
                 * Measure byte read+write time
                 * measuring movs instructions.
                 */

                raw = bmvstime(count);
                acctime[MRB] += raw/(TIMER2_RATE*count);

                /*
                 * Calculate ROM read time by
                 * measuring movs from ROM to RAM.
                 */

                raw = bromtime(count);
                acctime[RRB] += raw/(TIMER2_RATE*count);

                /*
                 * Measure word write using the
                 * pusha instruction.
                 */

                wrw = wpshtime(count) - WPOVH;
                acctime[MWW] += wrw/(TIMER2_RATE*count);

                /*
                 * Measure movs (read+write) time.
                 */

                raw = wmvstime(count);
                acctime[MRW] += raw/(TIMER2_RATE*count);
                raw = wromtime(count);
                acctime[RRW] += raw/(TIMER2_RATE*count);

                /*
                 * If EMM is present, do measurements
                 * on it using the same techniques.
                 */

                if (emmok) {

                        /*
                         * Measure byte mov in EMM.
                         */

                        raw = bemmtime(count);
                        acctime[ERB] += raw/(TIMER2_RATE*count);

                        /*
                         * Measure word write,
                         * calculate word read.
                         */

                        wrw = wemptime(count) - WPOVH;
                        acctime[EWW] += wrw/(TIMER2_RATE*count);
                        raw = wemmtime(count);
                        acctime[ERW] += raw/(TIMER2_RATE*count);
                }

                /*
                 * Measure byte and word writes
                 * into video RAM.
                 */

                raw = bvidtime(count);
                acctime[VWB] += raw/(TIMER2_RATE*count);
                raw = wvidtime(count);
                acctime[VWW] += raw/(TIMER2_RATE*count);
        }

        /*
         * Calculate averages for all measurements.
         */

        for (i = 0; i < VARS; i++)
                acctime[i] /= trials;

        /*
         * Adjust word write times by subtracting
         * the instruction fetch time.
         */

        acctime[MWW] -= acctime[IRW]/16;
        if (emmok)
                acctime[EWW] -= acctime[IRW]/16;

        /*
         * Adjust for extra time per instruction when
         * measuring zero wait state memory.
         */

        if (acctime[MWW] < 3*clktime)
                acctime[MWW] -= clktime/8;
        if (emmok)
                if (acctime[EWW] < 3*clktime)
                        acctime[EWW] -= clktime/8;

        /*
         * Calculate byte write time by assuming the same
         * ratio between read and write as for word access.
         */

        acctime[MWB] = acctime[MRB] * acctime[MWW] /
                       acctime[MRW];
        if (emmok)
                acctime[EWB] = acctime[ERB] * acctime[EWW] /
                               acctime[ERW];

        /*
         * Calculate read times by subtracting write time from
         * mov (read+write) time.
         */

        acctime[MRB] = acctime[MRB] - acctime[MWB];
        acctime[MRW] = acctime[MRW] - acctime[MWW];
        acctime[RRB] = acctime[RRB] - acctime[MWB];
        acctime[RRW] = acctime[RRW] - acctime[MWW];
        if (emmok) {
                acctime[ERB] = acctime[ERB] - acctime[EWB];
                acctime[ERW] = acctime[ERW] - acctime[EWW];
        }

        /*
         * Release EMM memory page.
         */

        if (emmok)
                finish_emm();

        /*
         * Calculate numeric processor clock
         * rate using floating point divide
         * instructions, using the same
         * technique as was used to measure
         * the processor clock rate.
         */
        if (ndpok) {

                fprate = 0;
                for (i = 0; i < trials; i++) {
                        raw = fptime(FPCOUNT);
                        fpacc +=  (raw / TIMER2_RATE - FPOVH) /
                                FPCLKS / FPCOUNT;
                }
                fpacc /= trials;
                fprate = 1.0/fpacc;
        }

        /*
         * Display the basic measurement results and
         * performance index relative to a 8 MHz AT.
         */

        printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        printf("ATPERF -- PC Tech Journal AT Hardware ");
        printf("Performance Test\n");
        printf("Version 1.01, Copyright (c) 1986 Ziff ");
        printf("Communications Co.\n");
        printf("Written by Ted Forgeron and Paul Pierce\n");
        printf("IBM PC/AT model 339 (8 MHz) = 1.00 for relative ");
        printf("measurements.\n                            ");
        printf("       Byte             Word       Relative\n");

        printf("Average RAM instruction fetch:");
        printf("               ");
        printf("%#10.2g uS", acctime[IRW]);
        printf("%#10.2g\n", 0.403/acctime[IRW]);

        printf("Average RAM read time:      ");
        printf("%#10.2g uS    ", acctime[MRB]);
        printf("%#10.2g uS", acctime[MRW]);
        printf("%#10.2g\n", 0.401/acctime[MRW]);

        printf("Average RAM write time:     ");
        printf("%#10.2g uS    ", acctime[MWB]);
        printf("%#10.2g uS", acctime[MWW]);
        printf("%#10.2g\n", 0.401/acctime[MWW]);

        if (emmok) {
                printf("Average EMM read time:      ");
                printf("%#10.2g uS    ", acctime[ERB]);
                printf("%#10.2g uS", acctime[ERW]);
                printf("%#10.2g\n", 0.402/acctime[ERW]);

                printf("Average EMM write time:     ");
                printf("%#10.2g uS    ", acctime[EWB]);
                printf("%#10.2g uS", acctime[EWW]);
                printf("%#10.2g\n", 0.402/acctime[EWW]);
        }
        printf("Average ROM read time:      ");
        printf("%#10.2g uS    ", acctime[RRB]);
        printf("%#10.2g uS", acctime[RRW]);
        printf("%#10.2g\n", 0.401/acctime[RRW]);

        printf("Average Video write time:   ");
        printf("%#10.2g uS    ", acctime[VWB]);
        printf("%#10.2g uS", acctime[VWW]);
        printf("%#10.2g\n", 2.415/acctime[VWW]);

        printf("CPU clock rate:              ");
        printf("%#4.1g MHz", clkrate);
        printf("  Relative: %#4.2g\n", clkrate/8.0);

        if (ndpok) {
                printf("Math Coprocessor clock rate: ");
                printf("%#4.1g MHz", fprate);
                printf("  Relative: %#4.2g\n", fprate/5.33);
        }

        /*
         * Calculate refresh overhead from instruction
         * fetch time by assuming that each fetch takes
         * an exact multiple of the clock period.  The
         * difference between average time and the time
         * for an individual fetch is due to memory
         * refresh cycles.
         */

        raw = acctime[MRB] / clktime;
        printf("Refresh overhead:            %#2.1g%%\n",
                ( (raw - (int)raw) / (int)raw ) * 100);

        /*
         * Print information about the memory based
         * on the speed measurements.
         */

        printf("\nMemory   ");
        printf("       Access width          Wait states\n");
        analyze("RAM read", acctime[MRB], acctime[MRW]);
        analyze("RAM write", acctime[MWB], acctime[MWW]);
        if (emmok) {
                analyze("EMM read", acctime[ERB], acctime[ERW]);
                analyze("EMM write", acctime[EWB], acctime[EWW]);
        }
        analyze("ROM read", acctime[RRB], acctime[RRW]);
        analyze("Video write", acctime[VWB], acctime[VWW]);
}

/*
 * analyze
 *
 * This procedure deduces information about the memory based on
 * the measured times.
 * If byte (8 bits) and word (16 bits) times are different then
 * the memory is byte oriented since each word operation takes
 * two byte operations.  Otherwise, if the byte and word
 * times are about the same, the memory is word oriented and can
 * access either a word or a byte in a single memory cycle.
 *
 * Each memory access takes an exact number of processor clock
 * cycles.  The first two are required by the processor, but
 * any additional cycles are determined by the memory and are
 * called wait states (because the processor is waiting for
 * the memory.)
 */

analyze(name, btime, wtime)
        char    *name;
        double  btime;
        double  wtime;
{

        /*
         * Print the heading
         */

        printf("%-12s", name);

        /*
         * Determine whether the memory is byte
         * oriented, word oriented, or neither.
         * (If neither, the data are suspect.)
         */

        if (btime > wtime*0.75 &&
            btime < wtime*1.25)
                printf("        Word  ");
        else if (btime*2 > wtime*0.75 &&
                 btime*2 < wtime*1.25)
                printf("        Byte  ");
        else
                printf("       Strange");

        /*
         * Determine the number of wait states
         * by subtracting two processor clock times,
         * dividing by the clock period,
         * and rounding down to an integer.
         */

        printf("              %6.0f\n",
               (btime - 2*clktime) / clktime);
}
