Annotation of hatari/src/includes/m68000.h, revision 1.1.1.18

1.1       root        1: /*
1.1.1.5   root        2:   Hatari - m68000.h
                      3: 
1.1.1.16  root        4:   This file is distributed under the GNU General Public License, version 2
                      5:   or at your option any later version. Read the file gpl.txt for details.
1.1       root        6: */
                      7: 
1.1.1.10  root        8: /* 2007/11/10  [NP]    Add pairing for lsr / dbcc (and all variants    */
                      9: /*                     working on register, not on memory).            */
                     10: /* 2008/01/07  [NP]    Use PairingArray to store all valid pairing     */
                     11: /*                     combinations (in m68000.c)                      */
1.1.1.14  root       12: /* 2010/04/05  [NP]    Rework the pairing code to take BusCyclePenalty */
                     13: /*                     into account when using d8(an,ix).              */
                     14: /* 2010/05/07  [NP]    Add BusCyclePenalty to LastInstrCycles to detect*/
                     15: /*                     a possible pairing between add.l (a5,d1.w),d0   */
                     16: /*                     and move.b 7(a5,d1.w),d5.                       */
                     17: 
1.1.1.10  root       18: 
1.1.1.5   root       19: #ifndef HATARI_M68000_H
                     20: #define HATARI_M68000_H
                     21: 
1.1.1.9   root       22: #include "cycles.h"     /* for nCyclesMainCounter */
1.1.1.6   root       23: #include "sysdeps.h"
                     24: #include "memory.h"
1.1.1.9   root       25: #include "newcpu.h"     /* for regs */
1.1.1.14  root       26: #include "cycInt.h"
1.1.1.11  root       27: #include "log.h"
                     28: 
                     29: 
                     30: /* 68000 Register defines */
                     31: enum {
                     32:   REG_D0,    /* D0.. */
                     33:   REG_D1,
                     34:   REG_D2,
                     35:   REG_D3,
                     36:   REG_D4,
                     37:   REG_D5,
                     38:   REG_D6,
                     39:   REG_D7,    /* ..D7 */
                     40:   REG_A0,    /* A0.. */
                     41:   REG_A1,
                     42:   REG_A2,
                     43:   REG_A3,
                     44:   REG_A4,
                     45:   REG_A5,
                     46:   REG_A6,
1.1.1.16  root       47:   REG_A7    /* ..A7 (also SP) */
1.1.1.11  root       48: };
                     49: 
                     50: /* 68000 Condition code's */
                     51: #define SR_AUX              0x0010
                     52: #define SR_NEG              0x0008
                     53: #define SR_ZERO             0x0004
                     54: #define SR_OVERFLOW         0x0002
                     55: #define SR_CARRY            0x0001
                     56: 
                     57: #define SR_CLEAR_AUX        0xffef
                     58: #define SR_CLEAR_NEG        0xfff7
                     59: #define SR_CLEAR_ZERO       0xfffb
                     60: #define SR_CLEAR_OVERFLOW   0xfffd
                     61: #define SR_CLEAR_CARRY      0xfffe
                     62: 
                     63: #define SR_CCODE_MASK       (SR_AUX|SR_NEG|SR_ZERO|SR_OVERFLOW|SR_CARRY)
                     64: #define SR_MASK             0xFFE0
                     65: 
                     66: #define SR_TRACEMODE        0x8000
                     67: #define SR_SUPERMODE        0x2000
                     68: #define SR_IPL              0x0700
                     69: 
                     70: #define SR_CLEAR_IPL        0xf8ff
                     71: #define SR_CLEAR_TRACEMODE  0x7fff
                     72: #define SR_CLEAR_SUPERMODE  0xdfff
                     73: 
1.1.1.17  root       74: /* Exception numbers most commonly used in ST */
                     75: #define  EXCEPTION_NR_BUSERROR         2
                     76: #define  EXCEPTION_NR_ADDRERROR                3
                     77: #define  EXCEPTION_NR_ILLEGALINS       4
                     78: #define  EXCEPTION_NR_DIVZERO          5
                     79: #define  EXCEPTION_NR_CHK              6
                     80: #define  EXCEPTION_NR_TRAPV            7
                     81: #define  EXCEPTION_NR_TRACE            9
                     82: #define  EXCEPTION_NR_LINE_A           10
                     83: #define  EXCEPTION_NR_LINE_F           11
                     84: #define  EXCEPTION_NR_HBLANK           26              /* Level 2 interrupt */
                     85: #define  EXCEPTION_NR_VBLANK           28              /* Level 4 interrupt */
                     86: #define  EXCEPTION_NR_MFP_DSP          30              /* Level 6 interrupt */
                     87: #define  EXCEPTION_NR_TRAP0            32
                     88: #define  EXCEPTION_NR_TRAP1            33
                     89: #define  EXCEPTION_NR_TRAP2            34
                     90: #define  EXCEPTION_NR_TRAP13           45
                     91: #define  EXCEPTION_NR_TRAP14           46
1.1.1.11  root       92: 
                     93: 
                     94: /* Size of 68000 instructions */
                     95: #define MAX_68000_INSTRUCTION_SIZE  10  /* Longest 68000 instruction is 10 bytes(6+4) */
                     96: #define MIN_68000_INSTRUCTION_SIZE  2   /* Smallest 68000 instruction is 2 bytes(ie NOP) */
                     97: 
                     98: /* Illegal Opcode used to help emulation. eg. free entries are 8 to 15 inc' */
                     99: #define  GEMDOS_OPCODE        8  /* Free op-code to intercept GemDOS trap */
                    100: #define  SYSINIT_OPCODE      10  /* Free op-code to initialize system (connected drives etc.) */
                    101: #define  VDI_OPCODE          12  /* Free op-code to call VDI handlers AFTER Trap#2 */
                    102: 
1.1.1.16  root      103: /* Illegal opcodes used for Native Features emulation:
                    104:  * http://wiki.aranym.org/natfeats/proposal#special_opcodes
                    105:  */
                    106: #define  NATFEAT_ID_OPCODE   0x7300
                    107: #define  NATFEAT_CALL_OPCODE 0x7301
1.1.1.10  root      108: 
                    109: 
                    110: /* Ugly hacks to adapt the main code to the different CPU cores: */
                    111: 
                    112: #define Regs regs.regs
                    113: 
                    114: 
                    115: # define M68000_GetPC()     m68k_getpc()
                    116: # define M68000_SetPC(val)  m68k_setpc(val)
                    117: 
1.1.1.17  root      118: # define M68000_InstrPC                regs.instruction_pc
                    119: # define M68000_CurrentOpcode  regs.opcode
                    120: 
                    121: 
1.1.1.10  root      122: static inline Uint16 M68000_GetSR(void)
                    123: {
                    124:        MakeSR();
                    125:        return regs.sr;
                    126: }
                    127: static inline void M68000_SetSR(Uint16 v)
                    128: {
                    129:        regs.sr = v;
                    130:        MakeFromSR();
                    131: }
                    132: 
                    133: # define M68000_SetSpecial(flags)   set_special(flags)
                    134: # define M68000_UnsetSpecial(flags) unset_special(flags)
                    135: 
                    136: 
1.1.1.17  root      137: /* Some define's for bus error (see newcpu.c) */
                    138: /* Bus error read/write mode */
                    139: #define BUS_ERROR_WRITE                0
                    140: #define BUS_ERROR_READ         1
                    141: /* Bus error access size */
                    142: #define BUS_ERROR_SIZE_BYTE    1
                    143: #define BUS_ERROR_SIZE_WORD    2
                    144: #define BUS_ERROR_SIZE_LONG    4
                    145: /* Bus error access type */
                    146: #define BUS_ERROR_ACCESS_INSTR 0
                    147: #define BUS_ERROR_ACCESS_DATA  1
1.1.1.9   root      148: 
1.1.1.10  root      149: 
1.1.1.17  root      150: /* Bus access mode */
1.1.1.12  root      151: #define        BUS_MODE_CPU            0                       /* bus is owned by the cpu */
                    152: #define        BUS_MODE_BLITTER        1                       /* bus is owned by the blitter */
                    153: 
                    154: 
1.1.1.17  root      155: /* [NP] Notes on IACK :
1.1.1.16  root      156:  * When an interrupt happens, it's possible a similar interrupt happens again
                    157:  * between the start of the exception and the IACK sequence. In that case, we
                    158:  * might have to set pending bit twice and change the interrupt vector.
1.1.1.17  root      159:  *
1.1.1.18! root      160:  * From the 68000's doc, IACK starts after 10 cycles (12 cycles on STF due to 2 cycle
        !           161:  * bus penalty) and is supposed to take 4 cycles if the interrupt takes a total of 44 cycles.
1.1.1.17  root      162:  *
                    163:  * On Atari STF, interrupts take 56 cycles instead of 44, which means it takes
                    164:  * 12 extra cycles to fetch the vector number and to handle non-aligned memory accesses.
                    165:  * From WinUAE's CE mode, we have 2 non-aligned memory accesses to wait for (ie 2+2 cycles),
                    166:  * which leaves a total of 12 cycles to fetch the vector.
1.1.1.18! root      167:  *
        !           168:  * As seen wth a custom program on STF that measures HBL's jitter, we get the same results with Hatari
        !           169:  * in CE mode if we use 10 cycles to fetch the vector (step 3), which will also add 2 cycle penalty (step 4b)
        !           170:  * This means we have at max 12+10=22 cycles after the start of the exception where some
1.1.1.17  root      171:  * changes can happen (maybe it's a little less, depending on when the interrupt
                    172:  * vector is written on the bus).
                    173:  *
1.1.1.18! root      174:  * Additionally, auto vectored interrupts (HBL and VBL) require to be in sync with E-clock,
        !           175:  * which can add 0 to 8 cycles (step 3a). In that case we have between 22+0 and 22+8 cycles
        !           176:  * to get another interrupt before vector is written on the bus.
        !           177:  *
        !           178:  * The values we use were not entirely measured on real ST hardware, they were guessed/adjusted
        !           179:  * to get the correct behaviour in some games/demos relying on this.
        !           180:  * These values are when running in CE mode (2 cycle precision) ; when CPU runs in prefetch
        !           181:  * mode, values need to be rounded to 4).
        !           182:  *
        !           183:  * Interrupt steps + WinUAE cycles (measured on real A500 HW) + ST specific values :
        !           184:  *
        !           185:  * 1   6       idle cycles
        !           186:  * 1b  2(*)    ST bus access penalty (if necessary)
        !           187:  * 2   4       write PC low word
        !           188:  * 3a  0-8(*)  wait for E-clock for auto vectored interrupt
        !           189:  * 3   10(*)   read exception number
        !           190:  * 4   4       idle cycles
        !           191:  * 4b  2(*)    ST bus access penalty
        !           192:  * 5   4       write SR
        !           193:  * 6   4       write PC high word
        !           194:  * 7   4       read exception address high word
        !           195:  * 8   4       read exception address low word
        !           196:  * 9   4       prefetch
        !           197:  * 10  2       idle cycles
        !           198:  * 10b 2(*)    ST bus access penalty
        !           199:  * 11  4       prefetch
        !           200:  *  TOTAL = 56
1.1.1.17  root      201:  *
                    202:  *   (*) ST specific timings
1.1.1.16  root      203:  */
                    204: 
1.1.1.18! root      205: /* Values for IACK sequence when running in cycle exact mode */
        !           206: #define CPU_IACK_CYCLES_MFP_CE         12              /* vector sent by the MFP (TODO value not measured on real STF) */
        !           207: #define CPU_IACK_CYCLES_VIDEO_CE       10              /* auto vectored for HBL/VBL (value measured on real STF) */
        !           208: 
        !           209: /* Values for IACK sequence when running in normal/prefetch mode or when using old UAE CPU */
        !           210: #define CPU_IACK_CYCLES_START          12              /* number of cycles before starting the IACK when not using CE mode */
        !           211:                                                        /* (this should be a multiple of 4, else it will be rounded by M68000_AddCycles) */
        !           212: #define CPU_IACK_CYCLES_MFP            12              /* vector sent by the MFP */
        !           213: #define CPU_IACK_CYCLES_VIDEO          12              /* auto vectored for HBL/VBL */
1.1.1.16  root      214: 
1.1.1.17  root      215: /* Informations about current CPU instruction */
1.1.1.16  root      216: typedef struct {
1.1.1.17  root      217:        /* These are provided only by WinUAE CPU core */
                    218:        int     I_Cache_miss;                           /* Instruction cache for 68020/30/40/60 */
                    219:        int     I_Cache_hit;
                    220:        int     D_Cache_miss;                           /* Data cache for 68030/40/60 */
                    221:        int     D_Cache_hit;
1.1.1.16  root      222: 
                    223:        /* TODO: move other instruction specific Hatari variables here */
                    224: } cpu_instruction_t;
                    225: 
                    226: extern cpu_instruction_t CpuInstruction;
                    227: 
1.1.1.7   root      228: extern Uint32 BusErrorAddress;
1.1.1.11  root      229: extern bool bBusErrorReadWrite;
1.1.1.7   root      230: extern int nCpuFreqShift;
1.1.1.18! root      231: extern int WaitStateCycles;
1.1.1.12  root      232: extern int BusMode;
1.1.1.16  root      233: extern bool    CPU_IACK;
1.1.1.6   root      234: 
1.1.1.10  root      235: extern int     LastOpcodeFamily;
                    236: extern int     LastInstrCycles;
                    237: extern int     Pairing;
                    238: extern char    PairingArray[ MAX_OPCODE_FAMILY ][ MAX_OPCODE_FAMILY ];
                    239: extern const char *OpcodeName[];
                    240: 
1.1.1.6   root      241: 
                    242: /*-----------------------------------------------------------------------*/
1.1.1.10  root      243: /**
                    244:  * Add CPU cycles.
                    245:  * NOTE: All times are rounded up to nearest 4 cycles.
                    246:  */
1.1.1.6   root      247: static inline void M68000_AddCycles(int cycles)
                    248: {
1.1.1.10  root      249:        cycles = (cycles + 3) & ~3;
1.1.1.18! root      250: #ifdef OLD_CPU_SHIFT
1.1.1.10  root      251:        cycles = cycles >> nCpuFreqShift;
                    252: 
1.1.1.18! root      253:        PendingInterruptCount -= INT_CONVERT_TO_INTERNAL ( cycles , INT_CPU_CYCLE );
        !           254: #else
        !           255: //     PendingInterruptCount -= INT_CONVERT_TO_INTERNAL_NO_FREQSHIFT ( cycles , INT_CPU_CYCLE );
        !           256:        PendingInterruptCount -= INT_CONVERT_TO_INTERNAL ( cycles , INT_CPU_CYCLE );
        !           257: #endif
1.1.1.9   root      258:        nCyclesMainCounter += cycles;
1.1.1.16  root      259:        CyclesGlobalClockCounter += cycles;
1.1.1.6   root      260: }
1.1.1.5   root      261: 
1.1.1.10  root      262: 
                    263: /*-----------------------------------------------------------------------*/
                    264: /**
1.1.1.14  root      265:  * Add CPU cycles, take cycles pairing into account. Pairing will make
                    266:  * some specific instructions take 4 cycles less when run one after the other.
                    267:  * Pairing happens when the 2 instructions are "aligned" on different bus accesses.
                    268:  * Candidates are :
                    269:  *  - 2 instructions taking 4n+2 cycles
                    270:  *  - 1 instruction taking 4n+2 cycles, followed by 1 instruction using d8(an,ix)
                    271:  *
                    272:  * Not all the candidate instructions can pair, only the opcodes listed in PairingArray.
                    273:  * On ST, when using d8(an,ix), we get an extra 2 cycle penalty for misaligned bus access.
                    274:  * The only instruction that can generate BusCyclePenalty=4 is move d8(an,ix),d8(an,ix)
                    275:  * and although it takes 4n cycles (24 for .b/.w or 32 for .l) it can pair with
                    276:  * a previous 4n+2 instruction (but it will still have 1 misaligned bus access in the end).
                    277:  *
                    278:  * Verified pairing on an STF :
                    279:  *  - lsl.w #4,d1 + move.w 0(a4,d2.w),d1               motorola=14+14=28  stf=28
                    280:  *  - lsl.w #4,d1 + move.w 0(a4,d2.w),(a4)             motorola=14+18=32  stf=32
                    281:  *  - lsl.w #4,d1 + move.w 0(a4,d2.w),0(a4,d2.w)       motorola=14+24=38  stf=40
                    282:  *  - add.l (a5,d1.w),d0 + move.b 7(a5,d1.w),d5)       motorola=20+14=34  stf=36
                    283:  *
                    284:  * d8(an,ix) timings without pairing (2 cycles penalty) :
                    285:  *  - add.l   0(a4,d2.w),a1                            motorola=20  stf=24
                    286:  *  - move.w  0(a4,d2.w),d1                            motorola=14  stf=16
                    287:  *  - move.w  0(a4,d2.w),(a4)                          motorola=18  stf=20
                    288:  *  - move.w  0(a4,d2.w),0(a4,d2.w)                    motorola=24  stf=28
                    289:  *
1.1.1.10  root      290:  * NOTE: All times are rounded up to nearest 4 cycles.
                    291:  */
                    292: static inline void M68000_AddCyclesWithPairing(int cycles)
                    293: {
                    294:        Pairing = 0;
                    295:        /* Check if number of cycles for current instr and for */
                    296:        /* the previous one is of the form 4+2n */
                    297:        /* If so, a pairing could be possible depending on the opcode */
1.1.1.14  root      298:        /* A pairing is also possible if current instr is 4n but with BusCyclePenalty > 0 */
1.1.1.10  root      299:        if ( ( PairingArray[ LastOpcodeFamily ][ OpcodeFamily ] == 1 )
1.1.1.14  root      300:            && ( ( LastInstrCycles & 3 ) == 2 )
                    301:            && ( ( ( cycles & 3 ) == 2 ) || ( BusCyclePenalty > 0 ) ) )
1.1.1.10  root      302:        {
                    303:                Pairing = 1;
1.1.1.13  root      304:                LOG_TRACE(TRACE_CPU_PAIRING,
                    305:                          "cpu pairing detected pc=%x family %s/%s cycles %d/%d\n",
                    306:                          m68k_getpc(), OpcodeName[LastOpcodeFamily],
                    307:                          OpcodeName[OpcodeFamily], LastInstrCycles, cycles);
1.1.1.10  root      308:        }
                    309: 
1.1.1.11  root      310:        /* [NP] This part is only needed to track possible pairing instructions, */
                    311:        /* we can keep it disabled most of the time */
                    312: #if 0
                    313:        if ( (LastOpcodeFamily!=OpcodeFamily) && ( Pairing == 0 )
                    314:                && ( ( cycles & 3 ) == 2 ) && ( ( LastInstrCycles & 3 ) == 2 ) )
                    315:        {
1.1.1.13  root      316:                LOG_TRACE(TRACE_CPU_PAIRING,
                    317:                          "cpu could pair pc=%x family %s/%s cycles %d/%d\n",
                    318:                          m68k_getpc(), OpcodeName[LastOpcodeFamily],
                    319:                          OpcodeName[OpcodeFamily], LastInstrCycles, cycles);
1.1.1.11  root      320:        }
                    321: #endif
                    322: 
1.1.1.10  root      323:        /* Store current instr (not rounded) to check next time */
1.1.1.14  root      324:        LastInstrCycles = cycles + BusCyclePenalty;
1.1.1.10  root      325:        LastOpcodeFamily = OpcodeFamily;
                    326: 
1.1.1.16  root      327:        /* If pairing is true, we need to subtract 2 cycles for the     */
1.1.1.10  root      328:        /* previous instr which was rounded to 4 cycles while it wasn't */
                    329:        /* needed (and we don't round the current one)                  */
                    330:        /* -> both instr will take 4 cycles less on the ST than if ran  */
                    331:        /* separately.                                                  */
                    332:        if (Pairing == 1)
1.1.1.14  root      333:        {
                    334:                if ( ( cycles & 3 ) == 2 )              /* pairing between 4n+2 and 4n+2 instructions */
                    335:                        cycles -= 2;                    /* if we have a pairing, we should not count the misaligned bus access */
                    336: 
                    337:                else                                    /* this is the case of move d8(an,ix),d8(an,ix) where BusCyclePenalty=4 */
                    338:                        /*do nothing */;                /* we gain 2 cycles for the pairing with 1st d8(an,ix) */
                    339:                                                        /* and we have 1 remaining misaligned access for the 2nd d8(an,ix). So in the end, we keep */
                    340:                                                        /* cycles unmodified as 4n cycles (eg lsl.w #4,d1 + move.w 0(a4,d2.w),0(a4,d2.w) takes 40 cycles) */
                    341:        }
1.1.1.10  root      342:        else
1.1.1.14  root      343:        {
                    344:                cycles += BusCyclePenalty;              /* >0 if d8(an,ix) was used */
                    345:                cycles = (cycles + 3) & ~3;             /* no pairing, round current instr to 4 cycles */
                    346:        }
1.1.1.10  root      347: 
1.1.1.18! root      348: #ifdef OLD_CPU_SHIFT
1.1.1.10  root      349:        cycles = cycles >> nCpuFreqShift;
                    350: 
                    351:        PendingInterruptCount -= INT_CONVERT_TO_INTERNAL ( cycles , INT_CPU_CYCLE );
1.1.1.18! root      352: #else
        !           353: //     PendingInterruptCount -= INT_CONVERT_TO_INTERNAL_NO_FREQSHIFT ( cycles , INT_CPU_CYCLE );
        !           354:        PendingInterruptCount -= INT_CONVERT_TO_INTERNAL ( cycles , INT_CPU_CYCLE );
        !           355: #endif
1.1.1.10  root      356: 
                    357:        nCyclesMainCounter += cycles;
1.1.1.16  root      358:        CyclesGlobalClockCounter += cycles;
1.1.1.14  root      359:        BusCyclePenalty = 0;
1.1.1.10  root      360: }
                    361: 
                    362: 
1.1.1.18! root      363: /*-----------------------------------------------------------------------*/
        !           364: /**
        !           365:  * Add CPU cycles when using WinUAE CPU 'cycle exact' mode.
        !           366:  * In this mode, we should not round cycles to the next nearest 4 cycles
        !           367:  * because all memory accesses will already be aligned to 4 cycles when
        !           368:  * using CE mode.
        !           369:  * The CE mode will also give the correct 'instruction pairing' for all
        !           370:  * opcodes/addressing mode, without requiring tables/heuristics (in the
        !           371:  * same way that it's done in real hardware)
        !           372:  */
        !           373: static inline void M68000_AddCycles_CE(int cycles)
        !           374: {
        !           375: #ifdef OLD_CPU_SHIFT
        !           376:        cycles = cycles >> nCpuFreqShift;
        !           377: 
        !           378:        PendingInterruptCount -= INT_CONVERT_TO_INTERNAL(cycles, INT_CPU_CYCLE);
        !           379: #else
        !           380: //     PendingInterruptCount -= INT_CONVERT_TO_INTERNAL_NO_FREQSHIFT ( cycles , INT_CPU_CYCLE );
        !           381:        PendingInterruptCount -= INT_CONVERT_TO_INTERNAL ( cycles , INT_CPU_CYCLE );
        !           382: #endif
        !           383: 
        !           384:        nCyclesMainCounter += cycles;
        !           385:        CyclesGlobalClockCounter += cycles;
        !           386: }
        !           387: 
        !           388: 
        !           389: 
1.1.1.14  root      390: extern void M68000_Init(void);
1.1.1.11  root      391: extern void M68000_Reset(bool bCold);
1.1.1.10  root      392: extern void M68000_Start(void);
1.1.1.15  root      393: extern void M68000_CheckCpuSettings(void);
1.1.1.11  root      394: extern void M68000_MemorySnapShot_Capture(bool bSave);
1.1.1.17  root      395: extern void M68000_BusError ( Uint32 addr , int ReadWrite , int Size , int AccessType );
                    396: extern void M68000_Exception(Uint32 ExceptionNr , int ExceptionSource);
                    397: extern void M68000_Update_intlev ( void );
1.1.1.18! root      398: extern void M68000_WaitState(int WaitCycles);
1.1.1.16  root      399: extern int M68000_WaitEClock ( void );
1.1.1.18! root      400: extern void M68000_SyncCpuBus_OnReadAccess ( void );
        !           401: extern void M68000_SyncCpuBus_OnWriteAccess ( void );
1.1.1.17  root      402: extern void M68000_Flush_Instr_Cache ( uaecptr addr , int size );
                    403: extern void M68000_Flush_Data_Cache ( uaecptr addr , int size );
                    404: extern void M68000_Flush_All_Caches ( uaecptr addr , int size );
1.1.1.18! root      405: extern int DMA_MaskAddressHigh ( void );
        !           406: extern void M68000_ChangeCpuFreq ( void );
1.1.1.5   root      407: 
                    408: #endif

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.