/*
 * besm6_mmu.c: BESM-6 fast write cache and TLB registers
 *（стойка БРУС)
 *
 * Copyright (c) 2009-2017, Leonid Broukhis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * SERGE VAKULENKO OR LEONID BROUKHIS BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
 * OR OTHER DEALINGS IN THE SOFTWARE.

 * Except as contained in this notice, the name of Leonid Broukhis or
 * Serge Vakulenko shall not be used in advertising or otherwise to promote
 * the sale, use or other dealings in this Software without prior written
 * authorization from Leonid Broukhis and Serge Vakulenko.
 */
#include "besm6_defs.h"

/*
 * MMU data structures
 *
 * mmu_dev      MMU device descriptor
 * mmu_unit     MMU unit descriptor
 * mmu_reg      MMU register list
 */
UNIT mmu_unit = {
    UDATA (NULL, UNIT_FIX, 8)
};

t_value BRZ[8];
uint32 BAZ[8], TABST, RZ, OLDEST, FLUSH;

t_value BRS[4];
uint32 BAS[4];
uint32 BRSLRU;

/*
 * 64-битные регистры RP0-RP7 - для отображения регистров приписки,
 * группами по 4 ради компактности, 12 бит на страницу.
 * TLB0-TLB31 - постраничные регистры приписки, копии RPi.
 * Обращение к памяти должно вестись через TLBi.
 */
t_value RP[8];
uint32 TLB[32];

uint32 iintr_data;    /* protected page number or parity check location */

/* There were several hardwired configurations of registers
 * corresponding to up to 7 first words of the memory space, selected by
 * a packet switch. Here selection 0 corresponds to settable switch registers,
 * the others are hardwired.
 * The configuration is selected with "SET CPU PULT=N" where 0 <= N <= 10
 * is the configuration number.
 */
unsigned pult_packet_switch;

/* Location 0 of each configuration is the bitset of its hardwired locations */
t_value pult[11][8] = {
/* Switch registers */
    { 0 },
/* Hardwired program 1, a simple CU test */
    { 0376,
      SET_PARITY(01240000007100002LL, PARITY_INSN),   /* 1: vtm (2), vjm 2(1) */
      SET_PARITY(00657777712577777LL, PARITY_INSN),   /* 2: utm -1(1), utm -1(2) */
      SET_PARITY(00444000317400007LL, PARITY_INSN),   /* 3: mtj 3(1), vzm 7(3) */
      SET_PARITY(01045000317500007LL, PARITY_INSN),   /* 4: j+m 3(2), v1m 7(3)*/
      SET_PARITY(00650000107700002LL, PARITY_INSN),   /* 5: utm 1(1), vlm 2(1) */
      SET_PARITY(01257777713400001LL, PARITY_INSN),   /* 6: utm -1(2), vzm 1(2) */
      SET_PARITY(00330000003000001LL, PARITY_INSN)    /* 7: stop, uj 1 */
    },
/* Hardwired program 2, RAM write test. The "arx" insn (cyclic add)
 * in word 3 could be changed to "atx" insn (load) to use a constant
 * bit pattern with a "constant/variable code" front panel switch (TODO).
 * The bit pattern to use is taken from switch register 7.
 */
    { 0176,
      SET_PARITY(00770000306400012LL, PARITY_INSN), /* 1: vlm 3(1), vtm 12(1) */
      SET_PARITY(00010000000000010LL, PARITY_INSN), /* 2: xta 0, atx 10 */
      SET_PARITY(00010001000130007LL, PARITY_INSN), /* 3: xta 10, arx 7 */
      SET_PARITY(00500777700000010LL, PARITY_INSN), /* 4: atx -1(1), atx 10 */
      SET_PARITY(00512777702600001LL, PARITY_INSN), /* 5: aex -1(1), uza 1 */
      SET_PARITY(00737777703000001LL, PARITY_INSN)  /* 6: stop -1(1), uj 1 */
    },
/* Hardwired program 3, RAM read test to use after program 2, arx/atx applies */
    { 0176,
      SET_PARITY(00770000306400012LL, PARITY_INSN), /* 1: vlm 3(1), vtm 12(1) */
      SET_PARITY(00010000000000010LL, PARITY_INSN), /* 2: xta 0, atx 10 */
      SET_PARITY(00010001000130007LL, PARITY_INSN), /* 3: xta 10, arx 7 */
      SET_PARITY(00000000000000010LL, PARITY_INSN), /* 4: atx 0, atx 10 */
      SET_PARITY(00512777702600001LL, PARITY_INSN), /* 5: aex -1(1), uza 1 */
      SET_PARITY(00737777703000001LL, PARITY_INSN)  /* 6: stop -1(1), uj 1 */
    },
/* Hardwired program 4, RAM write-read test to use after program 2, arx/atx applies */
    { 0176,
      SET_PARITY(00640001200100011LL, PARITY_INSN), /* 1: vtm 12(1), xta 11 */
      SET_PARITY(00000001005127777LL, PARITY_INSN), /* 2: atx 10, aex -1(1) */
      SET_PARITY(00260000407377777LL, PARITY_INSN), /* 3: uza 4, stop -1(1) */
      SET_PARITY(00010001000130007LL, PARITY_INSN), /* 4: xta 10, arx 7 */
      SET_PARITY(00500777707700002LL, PARITY_INSN), /* 5: atx -1(1), vlm 2(1) */
      SET_PARITY(00300000100000000LL, PARITY_INSN)  /* 6: uj 1 */
    },
/* Hardwired program 5, ALU test; switch reg 7 should contain a
   normalized f. p. value, e.g. 1.0 = 4050 0000 0000 0000 */
    { 0176,
      SET_PARITY(00004000700000011LL, PARITY_INSN), /* 1: a+x 7, atx 11 */
      SET_PARITY(00025001100000010LL, PARITY_INSN), /* 2: e-x 11, atx 10 */
      SET_PARITY(00017001000160010LL, PARITY_INSN), /* 3: a*x 10, a/x 10 */
      SET_PARITY(00005001000340145LL, PARITY_INSN), /* 4: a-x 10, e+n 145 */
      SET_PARITY(00270000603300000LL, PARITY_INSN), /* 5: u1a 6, stop */
      SET_PARITY(00010001103000001LL, PARITY_INSN)  /* 6: xta 11, uj 1*/
    },
/* Hardwired program 6, reading from punch tape (originally) or a disk (rework);
 * various bit groups not hardwired, marked [] (TODO). Disk operation is encoded.
 */
    { 0376,
      SET_PARITY(00640000300100006LL, PARITY_INSN), /* 1: vtm [3](1), xta 6 */
      SET_PARITY(00433002004330020LL, PARITY_INSN), /* 2: ext 20(1), ext 20(1) */
      SET_PARITY(00036015204330020LL, PARITY_INSN), /* 3: asn 152, ext 20(1) */
      SET_PARITY(00010000704330000LL, PARITY_INSN), /* 4: xta 7, ext (1) */
      SET_PARITY(00036014404330020LL, PARITY_INSN), /* 5: asn 144, ext 20(1) */
      SET_PARITY(00330000000002401LL, PARITY_INSN), /* 6: stop, =24[01] */
      SET_PARITY(04000000001400000LL, PARITY_NUMBER) /* 7: bits 37-47 not hardwired */
    },
/* Hardwired program 7, RAM peek/poke, bits 1-15 of word 1 not hardwired (TODO) */
    { 0176,
    },
/* Hardwired program 8, reading the test program from a fixed drum location */
    { 0036,
    },
/* Hardwired program 9, drum I/O */
    { 0176,
      SET_PARITY(00647774100100007LL, PARITY_INSN), /* 1: vtm -31(1), xta 7 */
      SET_PARITY(00033000212460000LL, PARITY_INSN), /* 2: ext 2, vtm 60000(2) */
      SET_PARITY(00040000013700003LL, PARITY_INSN), /* 3: ati, vlm 3(2) */
      SET_PARITY(00013000607700002LL, PARITY_INSN), /* 4: arx 6, vlm 2(1) */
      SET_PARITY(00330000103000005LL, PARITY_INSN), /* 5: stop 1, uj 5 */
      SET_PARITY(00000000000010001LL, PARITY_NUMBER) /* 6: =10001 */
    },
/* Hardwired program 10, magtape read */
    { 0176,
    },
};

#define ORDATAVM(nm,loc,wd) REGDATA(nm,(loc),8,wd,0,1,NULL,NULL,REG_VMIO,0,0)
#define ORDATAH(nm,loc,wd) REGDATA(nm,(loc),8,wd,0,1,NULL,NULL,REG_HIDDEN,0,0)

REG mmu_reg[] = {
    { ORDATAVM ( "БРЗ0",  BRZ[0],     50) },                      /* Буферные регистры записи */
    { ORDATAVM ( "БРЗ1",  BRZ[1],     50) },
    { ORDATAVM ( "БРЗ2",  BRZ[2],     50) },
    { ORDATAVM ( "БРЗ3",  BRZ[3],     50) },
    { ORDATAVM ( "БРЗ4",  BRZ[4],     50) },
    { ORDATAVM ( "БРЗ5",  BRZ[5],     50) },
    { ORDATAVM ( "БРЗ6",  BRZ[6],     50) },
    { ORDATAVM ( "БРЗ7",  BRZ[7],     50) },
    { ORDATA   ( "БАЗ0",  BAZ[0],     16) },                      /* Буферные адреса записи */
    { ORDATA   ( "БАЗ1",  BAZ[1],     16) },
    { ORDATA   ( "БАЗ2",  BAZ[2],     16) },
    { ORDATA   ( "БАЗ3",  BAZ[3],     16) },
    { ORDATA   ( "БАЗ4",  BAZ[4],     16) },
    { ORDATA   ( "БАЗ5",  BAZ[5],     16) },
    { ORDATA   ( "БАЗ6",  BAZ[6],     16) },
    { ORDATA   ( "БАЗ7",  BAZ[7],     16) },
    { ORDATAH  ( "ТАБСТ", TABST,      28) },                      /* Таблица старшинства БРЗ */
    { ORDATAH  ( "ЗпТР",  FLUSH,       4) },                      /* Признак выталкивания БРЗ */
    { ORDATA   ( "Старш", OLDEST,      3) },                      /* Номер вытолкнутого БРЗ */
    { ORDATAVM ( "РП0",   RP[0],      48) },                      /* Регистры приписки, по 12 бит */
    { ORDATAVM ( "РП1",   RP[1],      48) },
    { ORDATAVM ( "РП2",   RP[2],      48) },
    { ORDATAVM ( "РП3",   RP[3],      48) },
    { ORDATAVM ( "РП4",   RP[4],      48) },
    { ORDATAVM ( "РП5",   RP[5],      48) },
    { ORDATAVM ( "РП6",   RP[6],      48) },
    { ORDATAVM ( "РП7",   RP[7],      48) },
    { ORDATA   ( "РЗ",    RZ,         32) },                      /* Регистр защиты */
    { ORDATAVM ( "ТР1",   pult[0][1], 50) },                      /* Тумблерные регистры */
    { ORDATAVM ( "ТР2",   pult[0][2], 50) },
    { ORDATAVM ( "ТР3",   pult[0][3], 50) },
    { ORDATAVM ( "ТР4",   pult[0][4], 50) },
    { ORDATAVM ( "ТР5",   pult[0][5], 50) },
    { ORDATAVM ( "ТР6",   pult[0][6], 50) },
    { ORDATAVM ( "ТР7",   pult[0][7], 50) },
    { ORDATAVM ( "БРС0",  BRS[0],     50) },                      /* Буферные регистры слов */
    { ORDATAVM ( "БРС1",  BRS[1],     50) },
    { ORDATAVM ( "БРС2",  BRS[2],     50) },
    { ORDATAVM ( "БРС3",  BRS[3],     50) },
    { ORDATA   ( "БАС0",  BAS[0],     16) },                      /* Буферные адреса слов */
    { ORDATA   ( "БАС1",  BAS[1],     16) },
    { ORDATA   ( "БАС2",  BAS[2],     16) },
    { ORDATA   ( "БАС3",  BAS[3],     16) },
    { ORDATA   ( "БРСст", BRSLRU,      6) },
    { 0 }
};

#define CACHE_ENB 1

MTAB mmu_mod[] = {
    { 1, 0, "NOCACHE", "NOCACHE" },
    { 1, 1, "CACHE",   "CACHE" },
    { 0 }
};

t_stat mmu_reset (DEVICE *dptr);

t_stat mmu_examine (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw)
{
    mmu_print_brz();
    return SCPE_NOFNC;
}

DEVICE mmu_dev = {
    "MMU", &mmu_unit, mmu_reg, mmu_mod,
    1, 8, 3, 1, 8, 50,
    &mmu_examine, NULL, &mmu_reset,
    NULL, NULL, NULL, NULL,
    DEV_DEBUG
};

/*
 * Reset routine
 */
t_stat mmu_reset (DEVICE *dptr)
{
    int i;
    for (i = 0; i < 8; ++i) {
        BRZ[i] = RP[i] = BAZ[i] = 0;
    }
    TABST = 0;
    OLDEST = 0;
    FLUSH = 0;
    RZ = 0;
    /*
     * Front panel switches survive the reset
     */
    sim_cancel (&mmu_unit);
    return SCPE_OK;
}

#define loses_to_all(i) ((TABST & win_mask[i]) == 0 &&                  \
                         (TABST & lose_mask[i]) == lose_mask[i])

/*
 * N wins over M if the bit is set
 *  M=1   2   3   4   5   6   7
 * N  -------------------------
 * 0| 0   1   2   3   4   5   6
 * 1|     7   8   9  10  11  12
 * 2|        13  14  15  16  17
 * 3|            18  19  20  21
 * 4|                22  23  24
 * 5|                    25  26
 * 6|                        27
 */

static unsigned win_mask[8] = {
    0177,
    0077 << 7,
    0037 << 13,
    0017 << 18,
    0007 << 22,
    0003 << 25,
    0001 << 27,
    0
};

static unsigned lose_mask[8] = {
    0,
    1<<0,
    1<<1|1<<7,
    1<<2|1<<8|1<<13,
    1<<3|1<<9|1<<14|1<<18,
    1<<4|1<<10|1<<15|1<<19|1<<22,
    1<<5|1<<11|1<<16|1<<20|1<<23|1<<25,
    1<<6|1<<12|1<<17|1<<21|1<<24|1<<26|1<<27
};

#define set_wins(i) TABST = (TABST & ~lose_mask[i]) | win_mask[i]

void mmu_protection_check (int addr)
{
    /* Защита блокируется в режиме супервизора для физических (!) адресов 1-7 (ТО-8) - WTF? */
    int tmp_prot_disabled = (M[PSW] & PSW_PROT_DISABLE) ||
        (IS_SUPERVISOR (RUU) && (M[PSW] & PSW_MMAP_DISABLE) && addr < 010);

    /* Защита не заблокирована, а лист закрыт */
    if (! tmp_prot_disabled && (RZ & (1 << (addr >> 10)))) {
        iintr_data = addr >> 10;
        if (mmu_dev.dctrl)
            besm6_debug ("--- (%05o) защита числа", addr);
        longjmp (cpu_halt, STOP_OPERAND_PROT);
    }
}

void mmu_flush (int idx)
{
    int waddr = BAZ[idx];

    if (! BAZ[idx]) {
        /* Был пуст после сброса или выталкивания */
        return;
    }
    /* Вычисляем физический адрес выталкиваемого БРЗ */
    waddr = (waddr > 0100000) ? (waddr - 0100000) :
        (waddr & 01777) | (TLB[waddr >> 10] << 10);
    memory[waddr] = BRZ[idx];
    BAZ[idx] = 0;
    if (sim_log && mmu_dev.dctrl) {
        fprintf (sim_log, "--- (%05o) запись ", waddr);
        fprint_sym (sim_log, 0, &BRZ[idx], 0, 0);
        fprintf (sim_log, " из БРЗ[%d]\n", idx);
    }
}

void mmu_update_oldest ()
{
    int i;

    for (i = 0; i < 8; ++i) {
        if (loses_to_all(i)) {
            OLDEST = i;
            // fprintf(stderr, "Oldest = %d\r\n", i);
            return;
        }
    }
}

int mmu_match (int addr, int fail)
{
    int i;

    for (i = 0; i < 8; ++i) {
        if (addr == BAZ[i]) {
            return i;
        }
    }
    return fail;
}

/*
 * Разнообразные алгоритмы выталкивания БРЗ путем записи
 * по адресам пультовых регистров. Тест УУ проходит дальше всего
 * с mmu_flush_by_age().
 */
void mmu_flush_by_age()
{
    switch (FLUSH) {
    case 0:
        break;
    case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
        set_wins (OLDEST);
        mmu_update_oldest ();
        mmu_flush (OLDEST);
        if (FLUSH == 7) {
            TABST = 0;
            OLDEST = 0;
        }
        break;
    }
    ++FLUSH;
}

void mmu_flush_by_number()
{
    switch (FLUSH) {
    case 0:
        break;
    case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
        mmu_flush (FLUSH-1);
        set_wins (FLUSH-1);
        if (FLUSH-1 == OLDEST)
            mmu_update_oldest ();
        if (FLUSH == 7) {
            TABST = 0;
            OLDEST = 0;
        }
        break;
    }
    ++FLUSH;
}

/*
 * Запись слова в память
 */
void mmu_store (int addr, t_value val)
{
    int matching;

    addr &= BITS(15);
    if (addr == 0)
        return;
    if (sim_log && mmu_dev.dctrl) {
        fprintf (sim_log, "--- (%05o) запись ", addr);
        fprint_sym (sim_log, 0, &val, 0, 0);
        fprintf (sim_log, "\n");
    }

    mmu_protection_check (addr);

    /* Различаем адреса с припиской и без */
    if (M[PSW] & PSW_MMAP_DISABLE)
        addr |= 0100000;

    /* ЗПСЧ: ЗП */
    if (M[DWP] == addr && (M[PSW] & PSW_WRITE_WATCH))
        longjmp(cpu_halt, STOP_STORE_ADDR_MATCH);

    if (sim_brk_summ & SWMASK('W') &&
        sim_brk_test (addr, SWMASK('W')))
        longjmp(cpu_halt, STOP_WWATCH);

    if (!(mmu_unit.flags & CACHE_ENB)) {
        static int roundrobin;
        int faked = (++roundrobin ^ addr ^ val) & 7;

        if (addr > 0100000 && addr < 0100010)
            return;

        BRZ[faked] = SET_PARITY (val, RUU ^ PARITY_INSN);
        BAZ[faked] = addr;
        mmu_flush (faked);
        return;
    }

    /* Запись в тумблерные регистры - выталкивание БРЗ */
    if (addr > 0100000 && addr < 0100010) {
        mmu_flush_by_age();
        return;
    } else
        FLUSH = 0;

    matching = mmu_match(addr, OLDEST);

    BRZ[matching] = SET_PARITY (val, RUU ^ PARITY_INSN);
    BAZ[matching] = addr;
    set_wins (matching);

    if (matching == OLDEST) {
        mmu_update_oldest ();
        mmu_flush (OLDEST);
    }
}

t_value mmu_memaccess (int addr)
{
    t_value val;

    /* Вычисляем физический адрес слова */
    addr = (addr > 0100000) ? (addr - 0100000) :
        (addr & 01777) | (TLB[addr >> 10] << 10);
    if (addr >= 010) {
        /* Из памяти */
        val = memory[addr];
    } else {
        /* С тумблерных регистров */
        if (mmu_dev.dctrl)
            besm6_debug("--- (%05o) чтение ТР%o", PC, addr);
        if ((pult[pult_packet_switch][0] >> addr) & 1) {
            /* hardwired */
            val = pult[pult_packet_switch][addr];
        } else {
            /* from switch regs */
            val = pult[0][addr];
        }
    }
    if (sim_log && (mmu_dev.dctrl || (cpu_dev.dctrl && sim_deb))) {
        fprintf (sim_log, "--- (%05o) чтение ", addr & BITS(15));
        fprint_sym (sim_log, 0, &val, 0, 0);
        fprintf (sim_log, "\n");
    }

    /* На тумблерных регистрах контроля числа не бывает */
    if (addr >= 010 && ! IS_NUMBER (val)) {
        iintr_data = addr & 7;
        besm6_debug ("--- (%05o) контроль числа", addr);
        longjmp (cpu_halt, STOP_RAM_CHECK);
    }
    return val;
}

/*
 * Чтение операнда
 */
t_value mmu_load (int addr)
{
    int matching = -1;
    t_value val;

    addr &= BITS(15);
    if (addr == 0)
        return 0;

    mmu_protection_check (addr);

    /* Различаем адреса с припиской и без */
    if (M[PSW] & PSW_MMAP_DISABLE)
        addr |= 0100000;

    /* ЗПСЧ: СЧ */
    if (M[DWP] == addr && !(M[PSW] & PSW_WRITE_WATCH))
        longjmp(cpu_halt, STOP_LOAD_ADDR_MATCH);

    if (sim_brk_summ & SWMASK('R') &&
        sim_brk_test (addr, SWMASK('R')))
        longjmp(cpu_halt, STOP_RWATCH);

    if (!(mmu_unit.flags & CACHE_ENB)) {
        return mmu_memaccess (addr) & BITS48;
    }

    matching = mmu_match(addr, -1);

    if (matching == -1) {
        val = mmu_memaccess (addr);
    } else {
        /* старшинство обновляется, только если оно не затрагивает
         * старший БРЗ (ТО-2).
         */
        if (matching != OLDEST)
            set_wins (matching);
        val = BRZ[matching];
        if (sim_log && (mmu_dev.dctrl || (cpu_dev.dctrl && sim_deb))) {
            fprintf (sim_log, "--- (%05o) чтение ", addr & BITS(15));
            fprint_sym (sim_log, 0, &val, 0, 0);
            fprintf (sim_log, " из БРЗ\n");
        }
        if (! IS_NUMBER (val)) {
            iintr_data = matching;
            besm6_debug ("--- (%05o) контроль числа БРЗ", addr);
            longjmp (cpu_halt, STOP_CACHE_CHECK);
        }
    }
    return val & BITS48;
}

/* A little BRS LRU table */
#define brs_loses_to_all(i) ((BRSLRU & brs_win_mask[i]) == 0 &&         \
                             (BRSLRU & brs_lose_mask[i]) == brs_lose_mask[i])

/*
 * N wins over M if the bit is set
 *  M=1   2   3
 * N  ---------
 * 0| 0   1   2
 * 1|     3   4
 * 2|         5
 */

static unsigned brs_win_mask[4] = {
    07,
    03 << 3,
    01 << 5,
    0
};

static unsigned brs_lose_mask[8] = {
    0,
    1<<0,
    1<<1|1<<3,
    1<<2|1<<4|1<<5
};

#define brs_set_wins(i) BRSLRU = (BRSLRU & ~brs_lose_mask[i]) | brs_win_mask[i]

void mmu_fetch_check (int addr)
{
    /* В режиме супервизора защиты нет */
    if (! IS_SUPERVISOR(RUU)) {
        int page = TLB[addr >> 10];
        /*
         * Для команд в режиме пользователя признак защиты -
         * 0 в регистре приписки.
         */
        if (page == 0) {
            iintr_data = addr >> 10;
            if (mmu_dev.dctrl)
                besm6_debug ("--- (%05o) защита команды", addr);
            longjmp (cpu_halt, STOP_INSN_PROT);
        }
    }
}

/*
 * Предвыборка команды на БРС
 */
t_value mmu_prefetch (int addr, int actual)
{
    t_value val;
    int i;

    if (mmu_unit.flags & CACHE_ENB) {
        for (i = 0; i < 4; ++i) {
            if (BAS[i] == addr) {
                if (actual) {
                    brs_set_wins (i);
                }
                return BRS[i];
            }
        }

        for (i = 0; i < 4; ++i) {
            if (brs_loses_to_all (i)) {
                BAS[i] = addr;
                if (actual) {
                    brs_set_wins (i);
                }
                break;
            }
        }
    } else if (!actual) {
        return 0;
    } else {
        /* Чтобы лампочки мигали */
        i = addr;
    }

    if (addr < 0100000) {
        int page = TLB[addr >> 10];

        /* Вычисляем физический адрес слова */
        addr = (addr & 01777) | (page << 10);
    } else {
        addr = addr & BITS(15);
    }

    if (addr < 010) {
        if ((pult[pult_packet_switch][0] >> addr) & 1) {
            /* hardwired */
            val = pult[pult_packet_switch][addr];
        } else {
            /* from switch regs */
            val = pult[0][addr];
        }
    } else
        val = memory[addr];
    BRS[i & 3] = val;
    return val;
}

/*
 * Выборка команды
 */
t_value mmu_fetch (int addr)
{
    t_value val;

    if (addr == 0) {
        if (mmu_dev.dctrl)
            besm6_debug ("--- передача управления на 0");
        longjmp (cpu_halt, STOP_INSN_CHECK);
    }

    mmu_fetch_check(addr);

    /* Различаем адреса с припиской и без */
    if (IS_SUPERVISOR (RUU))
        addr |= 0100000;

    /* КРА */
    if (M[IBP] == addr)
        longjmp(cpu_halt, STOP_INSN_ADDR_MATCH);

    val = mmu_prefetch(addr, 1);

    if (sim_log && mmu_dev.dctrl) {
        fprintf (sim_log, "--- (%05o) выборка ", addr);
        fprint_sym (sim_log, 0, &val, 0, SWMASK ('I'));
        fprintf (sim_log, "\n");
    }

    /* Тумблерные регистры пока только с командной сверткой */
    if (addr >= 010 && ! IS_INSN (val)) {
        besm6_debug ("--- (%05o) контроль команды", addr);
        longjmp (cpu_halt, STOP_INSN_CHECK);
    }
    return val & BITS48;
}

void mmu_setrp (int idx, t_value val)
{
    uint32 p0, p1, p2, p3;
    const uint32 mask = (MEMSIZE >> 10) - 1;

    /* Младшие 5 разрядов 4-х регистров приписки упакованы
     * по 5 в 1-20 рр, 6-е разряды - в 29-32 рр, 7-е разряды - в 33-36 рр и т.п.
     */
    p0 = (val       & 037) | (((val>>28) & 1) << 5) | (((val>>32) & 1) << 6) |
        (((val>>36) &  1) << 7) | (((val>>40) & 1) << 8) | (((val>>44) & 1) << 9);
    p1 = ((val>>5)  & 037) | (((val>>29) & 1) << 5) | (((val>>33) & 1) << 6) |
        (((val>>37) &  1) << 7) | (((val>>41) & 1) << 8) | (((val>>45) & 1) << 9);
    p2 = ((val>>10) & 037) | (((val>>30) & 1) << 5) | (((val>>34) & 1) << 6) |
        (((val>>38) &  1) << 7) | (((val>>42) & 1) << 8) | (((val>>46) & 1) << 9);
    p3 = ((val>>15) & 037) | (((val>>31) & 1) << 5) | (((val>>35) & 1) << 6) |
        (((val>>39) &  1) << 7) | (((val>>43) & 1) << 8) | (((val>>47) & 1) << 9);

    p0 &= mask;
    p1 &= mask;
    p2 &= mask;
    p3 &= mask;

    RP[idx] = p0 | p1 << 12 | p2 << 24 | (t_value) p3 << 36;
    TLB[idx*4] = p0;
    TLB[idx*4+1] = p1;
    TLB[idx*4+2] = p2;
    TLB[idx*4+3] = p3;
}

void mmu_setup ()
{
    const uint32 mask = (MEMSIZE >> 10) - 1;
    int i;

    /* Перепись РПi в TLBj. */
    for (i=0; i<8; ++i) {
        TLB[i*4] = RP[i] & mask;
        TLB[i*4+1] = RP[i] >> 12 & mask;
        TLB[i*4+2] = RP[i] >> 24 & mask;
        TLB[i*4+3] = RP[i] >> 36 & mask;
    }
}

void mmu_setprotection (int idx, t_value val)
{
    /* Разряды сумматора, записываемые в регистр защиты - 21-28 */
    int mask = 0xff << (idx * 8);
    val = ((val >> 20) & 0xff) << (idx * 8);
    RZ = (uint32)((RZ & ~mask) | val);
}

void mmu_setcache (int idx, t_value val)
{
    BRZ[idx] = SET_PARITY (val, RUU ^ PARITY_INSN);
}

t_value mmu_getcache (int idx)
{
    return BRZ[idx] & BITS48;
}

void mmu_print_brz ()
{
    int i, k;

    for (i=7; i>=0; --i) {
        besm6_log_cont ("БРЗ [%d] = '", i);
        for (k=47; k>=0; --k)
            besm6_log_cont ("%c", (BRZ[i] >> k & 1) ? '*' : ' ');
        besm6_log ("'");
    }
}
