Annotation of nono/vm/mpu88xx0.cpp, revision 1.1.1.10

1.1       root        1: //
                      2: // nono
1.1.1.2   root        3: // Copyright (C) 2020 nono project
                      4: // Licensed under nono-license.txt
1.1       root        5: //
                      6: 
                      7: // MPU (m88xx0)
                      8: 
                      9: #include "mpu88xx0.h"
                     10: #include "config.h"
1.1.1.2   root       11: #include "sysctlr.h"
                     12: 
1.1       root       13: // コンストラクタ
1.1.1.7   root       14: MPU88xx0Device::MPU88xx0Device()
1.1.1.8   root       15:        : inherited("MPU(88xx0)")
1.1       root       16: {
1.1.1.7   root       17:        cpu.reset(new m88kcpu());
1.1.1.4   root       18: 
1.1.1.2   root       19:        // LUNA88K では CMMU の ID は次のように割り振ってあるようだ。
                     20:        // OpenBSD の sys/arch/luna68k/include/board.h より。
                     21:        //
                     22:        //       Inst Data
                     23:        // CPU#0 ID=7 ID=6
                     24:        // CPU#1 ID=5 ID=4
                     25:        // CPU#2 ID=3 ID=2
                     26:        // CPU#3 ID=1 ID=0
                     27:        //
                     28:        // 厳密にはこれは LUNA88K のハードウェアがどういうコンフィグレーションで
                     29:        // m88200 を使うかなので、ここではなくて vm_luna あたりのほうがいいかも
                     30:        // しれんけど、あまり遠いのも面倒なので、とりあえずこの辺に。
                     31:        // あとマルチプロセッサになったらまたその時考える。
                     32:        cpu->cmmu[0].SetID(7);
                     33:        cpu->cmmu[1].SetID(6);
                     34: 
1.1.1.8   root       35:        // レジスタモニター
                     36:        monitor.func = (MonitorCallback_t)&MPU88xx0Device::MonitorUpdate;
1.1.1.10! root       37:        monitor.SetSize(79, 28);
1.1.1.8   root       38:        monitor.Regist(ID_MONITOR_MPUREG);
1.1       root       39: }
                     40: 
                     41: // デストラクタ
                     42: MPU88xx0Device::~MPU88xx0Device()
                     43: {
                     44: }
                     45: 
1.1.1.5   root       46: bool
                     47: MPU88xx0Device::Init()
                     48: {
                     49:        // 親クラス
                     50:        if (inherited::Init() == false) {
                     51:                return false;
                     52:        }
                     53:        // MPU クロックは親 Init() で読み込んで、ここで cpu にセットする
                     54:        cpu->SetClockSpeed(clock_khz);
                     55: 
1.1.1.8   root       56:        const ConfigItem& item = gConfig->Find("mpu-pseudo-stop");
                     57:        cpu->pseudo_stop_enable = (bool)item.AsInt();
                     58: 
1.1.1.5   root       59:        return true;
                     60: }
                     61: 
1.1.1.7   root       62: bool
                     63: MPU88xx0Device::PowerOn()
                     64: {
                     65:        cpu->PowerOn();
                     66:        return true;
                     67: }
                     68: 
1.1       root       69: void
1.1.1.7   root       70: MPU88xx0Device::ResetHard()
1.1       root       71: {
1.1.1.5   root       72:        cpu->RequestReset();
1.1       root       73: }
                     74: 
1.1.1.6   root       75: // アクセス中の論理アドレスを取得
                     76: uint32
                     77: MPU88xx0Device::GetLaddr() const
                     78: {
                     79:        m88200 *cmmu = m88200::GetBusMaster();
                     80:        assert(cmmu);
                     81:        return cmmu->GetLaddr();
                     82: }
                     83: 
                     84: // アクセス中の物理アドレスを取得
                     85: uint32
                     86: MPU88xx0Device::GetPaddr() const
                     87: {
                     88:        m88200 *cmmu = m88200::GetBusMaster();
                     89:        assert(cmmu);
                     90:        return cmmu->GetPaddr();
                     91: }
                     92: 
1.1.1.4   root       93: // アクセスウェイトを加算
                     94: void
1.1.1.5   root       95: MPU88xx0Device::AddCycle(int32 cycle)
1.1.1.4   root       96: {
                     97:        // 今アクセスしてきている(= バスマスターの) CMMU に対して加算する
                     98:        m88200 *cmmu = m88200::GetBusMaster();
                     99:        assert(cmmu);
                    100:        cmmu->AddCycle(cycle);
                    101: }
                    102: 
1.1.1.2   root      103: // Interrupt
                    104: void
1.1.1.5   root      105: MPU88xx0Device::Interrupt(int level)
1.1.1.2   root      106: {
1.1.1.5   root      107:        cpu->Interrupt(level);
1.1.1.2   root      108: }
                    109: 
1.1.1.9   root      110: // DOS call エミュレーションのコールバックを指定
                    111: void
                    112: MPU88xx0Device::SetFLineCallback(bool (*callback)(m88kcpu *, void *), void *arg)
                    113: {
                    114:        cpu->SetFLineCallback(callback, arg);
                    115: }
                    116: 
                    117: // MPU からのアクセスをエミュレート
                    118: uint64
                    119: MPU88xx0Device::Read8(uint32 addr)
                    120: {
                    121:        return cpu->cmmu[1].load_8(addr);
                    122: }
                    123: 
                    124: uint64
                    125: MPU88xx0Device::Read16(uint32 addr)
                    126: {
                    127:        if ((addr & 1) == 0) {
                    128:                return cpu->cmmu[1].load_16(addr);
                    129:        } else {
                    130:                uint64 h, l;
                    131:                h = Read8(addr);
                    132:                if ((int64)h < 0) {
                    133:                        return h;
                    134:                }
                    135:                l = Read8(addr + 1);
                    136:                if ((int64)l < 0) {
                    137:                        return l;
                    138:                }
                    139:                return (h << 8) | l;
                    140:        }
                    141: }
                    142: 
                    143: uint64
                    144: MPU88xx0Device::Read32(uint32 addr)
                    145: {
                    146:        if ((addr & 3) == 0) {
                    147:                return cpu->cmmu[1].load_32(addr);
                    148:        } else if ((addr & 1) == 0) {
                    149:                uint64 h, l;
                    150:                h = Read16(addr);
                    151:                if ((int64)h < 0) {
                    152:                        return h;
                    153:                }
                    154:                l = Read16(addr + 2);
                    155:                if ((int64)l < 0) {
                    156:                        return l;
                    157:                }
                    158:                return (h << 16) | l;
                    159:        } else {
                    160:                uint64 h, m, l;
                    161:                h = Read8(addr);
                    162:                if ((int64)h < 0) {
                    163:                        return h;
                    164:                }
                    165:                m = Read16(addr + 1);
                    166:                if ((int64)m < 0) {
                    167:                        return m;
                    168:                }
                    169:                l = Read8(addr + 3);
                    170:                if ((int64)l < 0) {
                    171:                        return l;
                    172:                }
                    173:                return (h << 24) | (m << 8) | l;
                    174:        }
                    175: }
                    176: 
                    177: uint64
                    178: MPU88xx0Device::Write8(uint32 addr, uint32 data)
                    179: {
                    180:        return cpu->cmmu[1].store_8(addr, data & 0xff);
                    181: }
                    182: 
                    183: uint64
                    184: MPU88xx0Device::Write16(uint32 addr, uint32 data)
                    185: {
                    186:        if ((addr & 1) == 0) {
                    187:                return cpu->cmmu[1].store_16(addr, data & 0xffff);
                    188:        } else {
                    189:                uint64 r;
                    190:                r = Write8(addr, data >> 8);
                    191:                if ((int64)r < 0) {
                    192:                        return r;
                    193:                }
                    194:                return Write8(addr + 1, data);
                    195:        }
                    196: }
                    197: 
                    198: uint64
                    199: MPU88xx0Device::Write32(uint32 addr, uint32 data)
                    200: {
                    201:        if ((addr & 3) == 0) {
                    202:                return cpu->cmmu[1].store_32(addr, data);
                    203:        } else if ((addr & 1) == 0) {
                    204:                uint64 r;
                    205:                r = Write16(addr, data >> 16);
                    206:                if ((int64)r < 0) {
                    207:                        return r;
                    208:                }
                    209:                return Write16(addr + 2, data);
                    210:        } else {
                    211:                uint64 r;
                    212:                r = Write8(addr, data >> 24);
                    213:                if ((int64)r < 0) {
                    214:                        return r;
                    215:                }
                    216:                r = Write16(addr + 1, data >> 8);
                    217:                if ((int64)r < 0) {
                    218:                        return r;
                    219:                }
                    220:                return Write8(addr + 3, data);
                    221:        }
                    222: }
                    223: 
1.1.1.10! root      224: #define FA(val, bit)   TA::OnOff((val) & m88100reg::bit)
        !           225: 
1.1       root      226: // モニター更新
1.1.1.3   root      227: void
1.1.1.8   root      228: MPU88xx0Device::MonitorUpdate(Monitor *, TextScreen& screen)
1.1       root      229: {
                    230:        m88100reg reg;
                    231:        int x;
                    232:        int y;
                    233: 
1.1.1.8   root      234:        screen.Clear();
1.1       root      235: 
                    236:        // ローカルにコピー
                    237:        reg = *cpu;
                    238: 
                    239:        //
                    240:        for (int i = 0; i < countof(reg.r); i++) {
1.1.1.8   root      241:                screen.Print((i / 8) * 14, i % 8, "r%-2d:%08x", i, reg.r[i]);
1.1       root      242:        }
                    243: 
                    244:        // 制御レジスタ
                    245:        x = 0;
                    246:        y = 9;
1.1.1.8   root      247:        screen.Print(x, y++,  "pid (cr0):%08x(Arch=$%02x Ver=$%02x MC=%s)",
1.1       root      248:                reg.pid,
                    249:                (reg.pid >> 8) & 0xff,
1.1.1.2   root      250:                (reg.pid >> 1) & 0x7f,
                    251:                (reg.pid & 1) ? "Master" : "Checker");
1.1.1.8   root      252:        screen.Print(x, y, "psr (cr1):%08x(", reg.psr);
1.1.1.10! root      253:        screen.Puts(x + 19, y, FA(reg.psr,  PSR_SUPER), "S");
1.1.1.8   root      254:        screen.Puts(x + 21, y,
1.1.1.2   root      255:                ((reg.psr & m88100reg::PSR_BO_LE) ? "BO=LE" : "BO=BE"));
1.1.1.10! root      256:        screen.Puts(x + 27, y, FA(reg.psr,  PSR_SER),  "SER");
        !           257:        screen.Puts(x + 31, y, FA(reg.psr,  PSR_C),    "Cy");
        !           258:        screen.Puts(x + 34, y, FA(reg.psr,  PSR_SFD1), "SFD1");
        !           259:        screen.Puts(x + 39, y, FA(reg.psr,  PSR_MXM),  "MXM");
        !           260:        screen.Puts(x + 43, y, FA(reg.psr,  PSR_IND),  "IND");
        !           261:        screen.Puts(x + 47, y, FA(reg.psr,  PSR_SFRZ), "SFRZ");
1.1.1.8   root      262:        screen.Puts(x + 51, y, ")");
1.1.1.2   root      263:        y++;
1.1.1.8   root      264:        screen.Print(x, y, "epsr(cr2):%08x(", reg.epsr);
1.1.1.10! root      265:        screen.Puts(x + 19, y, FA(reg.epsr, PSR_SUPER), "S");
1.1.1.8   root      266:        screen.Puts(x + 21, y,
1.1.1.2   root      267:                ((reg.epsr& m88100reg::PSR_BO_LE) ? "BO=LE" : "BO=BE"));
1.1.1.10! root      268:        screen.Puts(x + 27, y, FA(reg.epsr, PSR_SER),  "SER");
        !           269:        screen.Puts(x + 31, y, FA(reg.epsr, PSR_C),    "Cy");
        !           270:        screen.Puts(x + 34, y, FA(reg.epsr, PSR_SFD1), "SFD1");
        !           271:        screen.Puts(x + 39, y, FA(reg.epsr, PSR_MXM),  "MXM");
        !           272:        screen.Puts(x + 43, y, FA(reg.epsr, PSR_IND),  "IND");
        !           273:        screen.Puts(x + 47, y, FA(reg.epsr, PSR_SFRZ), "SFRZ");
1.1.1.8   root      274:        screen.Puts(x + 51, y, ")");
1.1       root      275: 
1.1.1.2   root      276:        // 制御レジスタ(左中; *IP)
1.1       root      277:        x = 0;
                    278:        y = 12;
1.1.1.8   root      279:        screen.Print(x, y + 0, "xip:%08x  opX:%08x(%c%c)",
1.1       root      280:                reg.xip, (uint32)reg.opX,
                    281:                cpu->OpIsBusErr(reg.opX) ? 'B' : '-',
                    282:                cpu->OpIsDelay(reg.opX)  ? 'D' : '-');
1.1.1.8   root      283:        screen.Print(x, y + 1, "nip:%08x  opF:%08x(%c%c)",
1.1       root      284:                reg.nip, (uint32)reg.opF,
                    285:                cpu->OpIsBusErr(reg.opF) ? 'B' : '-',
                    286:                cpu->OpIsDelay(reg.opF)  ? 'D' : '-');
1.1.1.8   root      287:        screen.Print(x, y + 2, "fip:%08x", reg.fip);
1.1       root      288: 
                    289:        // 制御レジスタ (S*IP)
                    290:        x = 32;
                    291:        for (int i = 0; i < 3; i++) {
1.1.1.8   root      292:                screen.Print(x, y + i, "%s(cr%d):%08x:%c%c",
1.1       root      293:                        m88100reg::sipname[i], 4 + i,
                    294:                        (reg.cr[4 + i] & m88100reg::SIP_MASK),
                    295:                        (reg.cr[4 + i] & m88100reg::SIP_V) ? 'V' : '-',
                    296:                        (reg.cr[4 + i] & m88100reg::SIP_E) ? 'E' : '-');
                    297:        }
                    298: 
1.1.1.2   root      299:        // 制御レジスタ(右中)
1.1       root      300:        x = 55;
                    301:        y = 9;
                    302:        for (int i = 0; i < 4; i++) {
                    303:                int rn = 17 + i;
1.1.1.8   root      304:                screen.Print(x, y++, "sr%d(cr%d):%08x", i, rn, reg.cr[rn]);
1.1       root      305:        }
1.1.1.8   root      306:        screen.Print(x, y++, "ssbr(cr3):%08x", reg.ssbr);
                    307:        screen.Print(x, y++, "vbr (cr7):%08x", reg.vbr);
1.1       root      308: 
1.1.1.10! root      309:        // MMU レジスタ(次段)
1.1.1.2   root      310:        y = 15;
                    311:        for (int i = 0; i <= 2; i++) {
                    312:                int dt =  8 + i * 3;
                    313:                int dd =  9 + i * 3;
                    314:                int da = 10 + i * 3;
                    315: 
1.1.1.8   root      316:                screen.Print(0, y, "dmt%d(cr%-2d):%04x",
1.1.1.2   root      317:                        i, dt, (reg.cr[dt] & 0xffff));
1.1.1.8   root      318:                screen.Puts(15, y, "(b,s,d,l,rx, s,en  ,w,v)");
                    319:                screen.Puts(16, y, (reg.cr[dt] & m88100reg::DM_BO)     ? "B" : "-");
                    320:                screen.Puts(18, y, (reg.cr[dt] & m88100reg::DM_DAS)    ? "S" : "U");
                    321:                screen.Puts(20, y, (reg.cr[dt] & m88100reg::DM_DOUB1)  ? "D" : "-");
                    322:                screen.Puts(22, y, (reg.cr[dt] & m88100reg::DM_LOCK)   ? "L" : "-");
1.1.1.2   root      323:                // カンマが自然に見えるようにここだけカンマも含めて前詰めで出力
1.1.1.8   root      324:                screen.Print(25, y, "%d,",
1.1.1.2   root      325:                        (reg.cr[dt] & m88100reg::DM_DREG_MASK) >> 7);
1.1.1.8   root      326:                screen.Puts(28, y, (reg.cr[dt] & m88100reg::DM_SIGNED) ? "S" : "-");
1.1.1.2   root      327:                uint32 en = (reg.cr[dt] & m88100reg::DM_EN_MASK) >> 2;
1.1.1.8   root      328:                screen.Puts(30, y, m88100reg::dmt_en_str[en]);
                    329:                screen.Puts(35, y, (reg.cr[dt] & m88100reg::DM_WRITE)  ? "W" : "-");
                    330:                screen.Puts(37, y, (reg.cr[dt] & m88100reg::DM_VALID)  ? "V" : "-");
1.1.1.2   root      331: 
1.1.1.8   root      332:                screen.Print(40, y, "dmd%d(cr%-2d):%08x", i, dd, reg.cr[dd]);
                    333:                screen.Print(60, y, "dma%d(cr%2d):%08x",  i, da, reg.cr[da]);
1.1.1.2   root      334:                y++;
                    335:        }
1.1.1.10! root      336:        y++;
        !           337: 
        !           338:        // FPU レジスタ (下段)
        !           339:        screen.Print(0, y, "fpecr(fcr0):%08x(", reg.fpecr);
        !           340:        x = 21;
        !           341:        screen.Puts(x +  0, y, FA(reg.fpecr, FPECR_FIOV),       "FIOV");
        !           342:        screen.Puts(x +  5, y, FA(reg.fpecr, FPECR_FUNIMP),     "FUNIMP");
        !           343:        screen.Puts(x + 12, y, FA(reg.fpecr, FPECR_FPRV),       "FPRV");
        !           344:        screen.Puts(x + 17, y, FA(reg.fpecr, FPECR_FROP),       "FROP");
        !           345:        screen.Puts(x + 22, y, FA(reg.fpecr, FPECR_FDVZ),       "FDVZ");
        !           346:        screen.Puts(x + 27, y, FA(reg.fpecr, FPECR_FUNF),       "FUNF");
        !           347:        screen.Puts(x + 32, y, FA(reg.fpecr, FPECR_FOVF),       "FOVF");
        !           348:        screen.Puts(x + 37, y, FA(reg.fpecr, FPECR_FINX),       "FINX");
        !           349:        screen.Puts(x + 41, y, ")");
        !           350:        y++;
        !           351: 
        !           352:        screen.Print(0, y++,
        !           353:                "fppt(fcr5): %08x(OpCode=%%%c%c%c%c%c T1=%d T2=%d TD=%d Dest=r%d)",
        !           354:                reg.fppt,
        !           355:                ((reg.fppt >> 15) & 1) + '0',
        !           356:                ((reg.fppt >> 14) & 1) + '0',
        !           357:                ((reg.fppt >> 13) & 1) + '0',
        !           358:                ((reg.fppt >> 12) & 1) + '0',
        !           359:                ((reg.fppt >> 11) & 1) + '0',
        !           360:                ((reg.fppt >>  9) & 3),
        !           361:                ((reg.fppt >>  7) & 3),
        !           362:                ((reg.fppt >>  5) & 3),
        !           363:                (reg.fppt & m88100reg::FPPT_DEST));
        !           364: 
        !           365:        screen.Print(0, y++, "fphs1(fcr1)/fpls1(fcr2): %08x'%08x",
        !           366:                reg.fphs1, reg.fpls1);
        !           367:        screen.Print(0, y++, "fphs2(fcr3)/fpls2(fcr4): %08x'%08x",
        !           368:                reg.fphs2, reg.fpls2);
        !           369: 
        !           370:        screen.Print(0, y, "fprh(fcr6) /fprl(fcr7):  %08x'%08x(",
        !           371:                reg.fprh, reg.fprl);
        !           372:        x = 43;
        !           373:        screen.Puts(x + 0, y, FA(reg.fprh, FPRH_ADDONE), "ADDONE");
        !           374:        screen.Print(x + 7, y, "RM=%s)", rmstr[(reg.fprh >> 29) & 3]);
        !           375:        y++;
        !           376: 
        !           377:        x = 21;
        !           378:        screen.Print(0, y, "fpit(fcr8): %08x(OpCode=%%%c%c%c%c%c TD=%d",
        !           379:                reg.fpit,
        !           380:                ((reg.fpit >> 15) & 1) + '0',
        !           381:                ((reg.fpit >> 14) & 1) + '0',
        !           382:                ((reg.fpit >> 13) & 1) + '0',
        !           383:                ((reg.fpit >> 12) & 1) + '0',
        !           384:                ((reg.fpit >> 11) & 1) + '0',
        !           385:                ((reg.fpit >> 10) & 1));
        !           386:        screen.Puts(x + 19, y, FA(reg.fpit, FPIT_EFINV), "EINV");
        !           387:        screen.Puts(x + 24, y, FA(reg.fpit, FPIT_EFDVZ), "EDVZ");
        !           388:        screen.Puts(x + 29, y, FA(reg.fpit, FPIT_EFUNF), "EUNF");
        !           389:        screen.Puts(x + 34, y, FA(reg.fpit, FPIT_EFOVF), "EOVF");
        !           390:        screen.Puts(x + 39, y, FA(reg.fpit, FPIT_EFINX), "EINX");
        !           391:        screen.Print(x + 44, y, "Dest=r%d)", reg.fpit & m88100reg::FPIT_DEST);
        !           392:        y++;
        !           393: 
        !           394:        uint32 sign = (reg.fprh & m88100reg::FPRH_SIGN);
        !           395:        uint32 exp  = (int32)reg.fpit >> 20;
        !           396:        uint32 mant_h = (reg.fprh & 0x000fffff);
        !           397:        uint32 mant_l = reg.fprl;
        !           398:        screen.Print(12, y, "result: %c%1x.%05x'%08x'grs e%+d",
        !           399:                (sign ? '-' : '+'),
        !           400:                ((reg.fprh >> 20) & 1),
        !           401:                mant_h,
        !           402:                mant_l,
        !           403:                exp - 0x3ff);
        !           404:        screen.Puts(38, y, FA(reg.fprh, FPRH_GUARD),  "G");
        !           405:        screen.Puts(39, y, FA(reg.fprh, FPRH_ROUND),  "R");
        !           406:        screen.Puts(40, y, FA(reg.fprh, FPRH_STICKY), "S");
        !           407:        screen.Print(48, y, "($%03x)", exp & 0x7ff);
        !           408:        // Inf と NAN はこの形式だけからでは分かりづらいので別途追加で表示。
        !           409:        // exp は符号拡張しているので指数部 $7ff は exp == -1。
        !           410:        if (exp == 0xffffffff) {
        !           411:                x = 55;
        !           412:                if ((mant_h | mant_l) == 0) {
        !           413:                        if (sign == 0) {
        !           414:                                screen.Puts(x, y, "= +Inf");
        !           415:                        } else {
        !           416:                                screen.Puts(x, y, "= -Inf");
        !           417:                        }
        !           418:                } else {
        !           419:                        if (sign == 0) {
        !           420:                                screen.Puts(x, y, "= +NAN");
        !           421:                        } else {
        !           422:                                screen.Puts(x, y, "= -NAN");
        !           423:                        }
        !           424:                }
        !           425:        }
        !           426:        y++;
        !           427: 
        !           428:        screen.Print(0, y, "fpsr(fcr62):%08x(", reg.fpsr);
        !           429:        x = 21;
        !           430:        screen.Puts(x +  0, y, FA(reg.fpsr, FPSR_AFINV), "AFINV");
        !           431:        screen.Puts(x +  6, y, FA(reg.fpsr, FPSR_AFDVZ), "AFDVZ");
        !           432:        screen.Puts(x + 12, y, FA(reg.fpsr, FPSR_AFUNF), "AFUNF");
        !           433:        screen.Puts(x + 18, y, FA(reg.fpsr, FPSR_AFOVF), "AFOVF");
        !           434:        screen.Puts(x + 24, y, FA(reg.fpsr, FPSR_AFINX), "AFINX");
        !           435:        screen.Puts(x + 29, y, ")");
        !           436:        y++;
        !           437: 
        !           438:        screen.Print(0, y, "fpcr(fcr63):%08x(", reg.fpcr);
        !           439:        screen.Puts(x +  0, y, FA(reg.fpcr, FPCR_EFINV), "EFINV");
        !           440:        screen.Puts(x +  6, y, FA(reg.fpcr, FPCR_EFDVZ), "EFDVZ");
        !           441:        screen.Puts(x + 12, y, FA(reg.fpcr, FPCR_EFUNF), "EFUNF");
        !           442:        screen.Puts(x + 18, y, FA(reg.fpcr, FPCR_EFOVF), "EFOVF");
        !           443:        screen.Puts(x + 24, y, FA(reg.fpcr, FPCR_EFINX), "EFINX");
        !           444:        screen.Print(x + 30, y, "RM=%s)", rmstr[(reg.fpcr >> 14) & 3]);
1.1       root      445: }

unix.superglobalmegacorp.com

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