/* vax4xx_stddev.c: KA4xx standard devices

   Copyright (c) 2019, Matt Burke
   This module incorporates code from SimH, Copyright (c) 1998-2008, Robert M Supnik

   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
   THE AUTHOR(S) 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(s) of the author(s) shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from the author(s).

   rom          bootstrap ROM (no registers)
   nvr          non-volatile ROM (no registers)
   or           option ROMs (no registers)
   clk          100Hz and TODR clock
*/

#include "vax_defs.h"

#define UNIT_V_NODELAY  (UNIT_V_UF + 0)                 /* ROM access equal to RAM access */
#define UNIT_NODELAY    (1u << UNIT_V_NODELAY)

#define CLKCSR_IMP      (CSR_IE)                        /* real-time clock */
#define CLKCSR_RW       (CSR_IE)
#define CLK_DELAY       5000                            /* 100 Hz */
#define TMXR_MULT       1                               /* 100 Hz */

uint32 *rom = NULL;                                     /* boot ROM */
uint32 *nvr = NULL;                                     /* non-volatile mem */
int32 clk_csr = 0;                                      /* control/status */
int32 clk_tps = 100;                                    /* ticks/second */
int32 tmr_int = 0;                                      /* interrupt */
int32 tmxr_poll = CLK_DELAY * TMXR_MULT;                /* term mux poll */
int32 tmr_poll = CLK_DELAY;                             /* pgm timer poll */

t_stat rom_ex (t_value *vptr, t_addr exta, UNIT *uptr, int32 sw);
t_stat rom_dep (t_value val, t_addr exta, UNIT *uptr, int32 sw);
t_stat rom_reset (DEVICE *dptr);
t_stat rom_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
const char *rom_description (DEVICE *dptr);
t_stat nvr_ex (t_value *vptr, t_addr exta, UNIT *uptr, int32 sw);
t_stat nvr_dep (t_value val, t_addr exta, UNIT *uptr, int32 sw);
t_stat nvr_reset (DEVICE *dptr);
t_stat nvr_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
t_stat nvr_attach (UNIT *uptr, CONST char *cptr);
t_stat nvr_detach (UNIT *uptr);
const char *nvr_description (DEVICE *dptr);
t_stat or_reset (DEVICE *dptr);
t_stat or_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
const char *or_description (DEVICE *dptr);
t_stat clk_svc (UNIT *uptr);
t_stat clk_reset (DEVICE *dptr);
const char *clk_description (DEVICE *dptr);

extern int32 sysd_hlt_enb (void);

/* ROM data structures

   rom_dev      ROM device descriptor
   rom_unit     ROM units
   rom_reg      ROM register list
*/

UNIT rom_unit = { UDATA (NULL, UNIT_FIX+UNIT_BINK, ROMSIZE) };

REG rom_reg[] = {
    { NULL }
    };

MTAB rom_mod[] = {
    { UNIT_NODELAY, UNIT_NODELAY, "fast access", "NODELAY", NULL, NULL, NULL, "Disable calibrated ROM access speed" },
    { UNIT_NODELAY, 0, "1usec calibrated access", "DELAY",  NULL, NULL, NULL, "Enable calibrated ROM access speed" },
    { 0 }
    };

DEVICE rom_dev = {
    "ROM", &rom_unit, rom_reg, rom_mod,
    1, 16, ROMAWIDTH, 4, 16, 32,
    &rom_ex, &rom_dep, &rom_reset,
    NULL, NULL, NULL,
    NULL, 0, 0, NULL, NULL, NULL, &rom_help, NULL, NULL,
    &rom_description
    };

/* NVR data structures

   nvr_dev      NVR device descriptor
   nvr_unit     NVR units
   nvr_reg      NVR register list
*/

UNIT nvr_unit =
    { UDATA (NULL, UNIT_FIX+UNIT_BINK, NVRSIZE) };

REG nvr_reg[] = {
    { NULL }
    };

DEVICE nvr_dev = {
    "NVR", &nvr_unit, nvr_reg, NULL,
    1, 16, NVRAWIDTH, 4, 16, 32,
    &nvr_ex, &nvr_dep, &nvr_reset,
    NULL, &nvr_attach, &nvr_detach,
    NULL, 0, 0, NULL, NULL, NULL, &nvr_help, NULL, NULL,
    &nvr_description
    };

/* OR data structures

   or_dev      OR device descriptor
   or_unit     OR unit descriptor
   or_reg      OR register list
*/

UNIT or_unit[] = {
    { UDATA (NULL, UNIT_FIX+UNIT_RO+UNIT_BINK, ORSIZE) },
    { UDATA (NULL, UNIT_FIX+UNIT_RO+UNIT_BINK, ORSIZE) },
    { UDATA (NULL, UNIT_FIX+UNIT_RO+UNIT_BINK, ORSIZE) },
    { UDATA (NULL, UNIT_FIX+UNIT_RO+UNIT_BINK, ORSIZE) }
    };

REG or_reg[] = {
    { NULL }
    };

DEVICE or_dev = {
    "OR", or_unit, or_reg, NULL,
    OR_COUNT, 16, ORAWIDTH, 4, 16, 32,
    NULL, NULL, &or_reset,
    NULL, NULL, NULL,
    NULL, 0, 0, NULL, NULL, NULL, &or_help, NULL, NULL,
    &or_description
    };

/* CLK data structures

   clk_dev      CLK device descriptor
   clk_unit     CLK unit descriptor
   clk_reg      CLK register list
*/

UNIT clk_unit = { UDATA (&clk_svc, UNIT_IDLE, 0), CLK_DELAY };

REG clk_reg[] = {
    { HRDATAD (CSR,          clk_csr,        16, "control/status register") },
    { FLDATAD (INT,          tmr_int,         0, "interrupt request") },
    { FLDATAD (IE,           clk_csr,  CSR_V_IE, "interrupt enable flag (CSR<6>)") },
    { DRDATAD (TIME,   clk_unit.wait,        24, "initial poll interval"), REG_NZ + PV_LEFT },
    { DRDATAD (POLL,        tmr_poll,        24, "calibrated poll interval"), REG_NZ + PV_LEFT + REG_HRO },
    { DRDATAD (TPS,          clk_tps,         8, "ticks per second (100)"), REG_NZ + PV_LEFT },
#if defined (SIM_ASYNCH_IO)
    { DRDATAD (ASYNCH,            sim_asynch_enabled,         1, "asynch I/O enabled flag"), PV_LEFT },
    { DRDATAD (LATENCY,           sim_asynch_latency,        32, "desired asynch interrupt latency"), PV_LEFT },
    { DRDATAD (INST_LATENCY, sim_asynch_inst_latency,        32, "calibrated instruction latency"), PV_LEFT },
#endif
    { NULL }
    };

DEVICE clk_dev = {
    "CLK", &clk_unit, clk_reg, NULL,
    1, 0, 0, 0, 0, 0,
    NULL, NULL, &clk_reset,
    NULL, NULL, NULL,
    NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, 
    &clk_description
    };

/* ROM: read only memory - stored in a buffered file
   Register space access routines see ROM twice

   ROM access has been 'regulated' to about 1Mhz to avoid issues
   with testing the interval timers in self-test.  Specifically,
   the VAX boot ROM (ka4xx.bin) contains code which presumes that
   the VAX runs at a particular slower speed when code is running
   from ROM (which is not cached).  These assumptions are built
   into instruction based timing loops. As the host platform gets
   much faster than the original VAX, the assumptions embedded in
   these code loops are no longer valid.

   Code has been added to the ROM implementation to limit CPU speed
   to about 500K instructions per second.  This heads off any future
   issues with the embedded timing loops.
*/

int32 rom_rd (int32 pa)
{
int32 rg = ((pa - ROMBASE) & ROMAMASK) >> 2;
int32 val = rom[rg];

if (rom_unit.flags & UNIT_NODELAY)
    return val;

return sim_rom_read_with_delay (val);
}

void rom_wr_B (int32 pa, int32 val)
{
int32 rg = ((pa - ROMBASE) & ROMAMASK) >> 2;
int32 sc = (pa & 3) << 3;

rom[rg] = ((val & 0xFF) << sc) | (rom[rg] & ~(0xFF << sc));
return;
}

/* ROM examine */

t_stat rom_ex (t_value *vptr, t_addr exta, UNIT *uptr, int32 sw)
{
uint32 addr = (uint32) exta;

if ((vptr == NULL) || (addr & 03))
    return SCPE_ARG;
if (addr >= ROMSIZE)
    return SCPE_NXM;
*vptr = rom[addr >> 2];
return SCPE_OK;
}

/* ROM deposit */

t_stat rom_dep (t_value val, t_addr exta, UNIT *uptr, int32 sw)
{
uint32 addr = (uint32) exta;

if (addr & 03)
    return SCPE_ARG;
if (addr >= ROMSIZE)
    return SCPE_NXM;
rom[addr >> 2] = (uint32) val;
return SCPE_OK;
}

/* ROM reset */

t_stat rom_reset (DEVICE *dptr)
{
if (rom == NULL)
    rom = (uint32 *) calloc (ROMSIZE >> 2, sizeof (uint32));
if (rom == NULL)
    return SCPE_MEM;
return SCPE_OK;
}

t_stat rom_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "Read-only memory (ROM)\n\n");
fprintf (st, "The boot ROM consists of a single unit, simulating the 256KB boot ROM.  It has\n");
fprintf (st, "no registers.  The boot ROM is loaded with a binary byte stream using the \n");
fprintf (st, "LOAD -r command:\n\n");
fprintf (st, "   LOAD -r KA410.BIN      load ROM image KA410.BIN\n\n");
fprintf (st, "When the simulator starts running (via the BOOT command), if the ROM has\n");
fprintf (st, "not yet been loaded, an attempt will be made to automatically load the\n");
fprintf (st, "ROM image from the file ka410.bin in the current working directory.\n");
fprintf (st, "If that load attempt fails, then a copy of the missing ROM file is\n");
fprintf (st, "written to the current directory and the load attempt is retried.\n\n");
fprintf (st, "ROM accesses a use a calibrated delay that slows ROM-based execution to\n");
fprintf (st, "about 500K instructions per second.  This delay is required to make the\n");
fprintf (st, "power-up self-test routines run correctly on very fast hosts.\n");
fprint_set_help (st, dptr);
return SCPE_OK;
}

const char *rom_description (DEVICE *dptr)
{
return "read-only memory";
}

/* NVR: non-volatile RAM - stored in a buffered file */

int32 nvr_rd (int32 pa)
{
int32 rg = (pa - NVRBASE) >> 2;
int32 val;

if (rg < 14)                                            /* watch chip */
    val = wtc_rd (rg);
else
    val = nvr[rg];
return (val << 2);
}

void nvr_wr (int32 pa, int32 val, int32 lnt)
{
int32 rg = (pa - NVRBASE) >> 2;
val = val >> 2;
if (rg < 14)                                            /* watch chip */
    wtc_wr (rg, val);
else {
    int32 sc = (pa & 3) << 3;                           /* merge */
    int32 mask = 0xFF;
    nvr[rg] = ((val & mask) << sc) | (nvr[rg] & ~(mask << sc));
    }
}

/* NVR examine */

t_stat nvr_ex (t_value *vptr, t_addr exta, UNIT *uptr, int32 sw)
{
uint32 addr = (uint32) exta;

if ((vptr == NULL) || (addr & 03))
    return SCPE_ARG;
if (addr >= NVRSIZE)
    return SCPE_NXM;
*vptr = nvr[addr >> 2];
return SCPE_OK;
}

/* NVR deposit */

t_stat nvr_dep (t_value val, t_addr exta, UNIT *uptr, int32 sw)
{
uint32 addr = (uint32) exta;

if (addr & 03)
    return SCPE_ARG;
if (addr >= NVRSIZE)
    return SCPE_NXM;
nvr[addr >> 2] = (uint32) val;
return SCPE_OK;
}

/* NVR reset */

t_stat nvr_reset (DEVICE *dptr)
{
if (nvr == NULL) {
    nvr = (uint32 *) calloc (NVRSIZE >> 2, sizeof (uint32));
    nvr_unit.filebuf = nvr;
    }
if (nvr == NULL)
    return SCPE_MEM;
return SCPE_OK;
}

t_stat nvr_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "Non-volatile Memory (NVR)\n\n");
fprintf (st, "The NVR simulates %d bytes of battery-backed up memory.\n", NVRSIZE);
fprintf (st, "When the simulator starts, NVR is cleared to 0, and the battery-low indicator\n");
fprintf (st, "is set.  Alternately, NVR can be attached to a file.  This allows the NVR\n");
fprintf (st, "state to be preserved across simulator runs.  Successfully attaching an NVR\n");
fprintf (st, "image clears the battery-low indicator.\n\n");
return SCPE_OK;
}

/* NVR attach */

t_stat nvr_attach (UNIT *uptr, CONST char *cptr)
{
t_stat r;

uptr->flags = uptr->flags | (UNIT_ATTABLE | UNIT_BUFABLE);
r = attach_unit (uptr, cptr);
if (r != SCPE_OK)
    uptr->flags = uptr->flags & ~(UNIT_ATTABLE | UNIT_BUFABLE);
else {
    uptr->hwmark = (uint32) uptr->capac;
    wtc_set_valid ();
    }
return r;
}

/* NVR detach */

t_stat nvr_detach (UNIT *uptr)
{
t_stat r;

r = detach_unit (uptr);
if ((uptr->flags & UNIT_ATT) == 0) {
    uptr->flags = uptr->flags & ~(UNIT_ATTABLE | UNIT_BUFABLE);
    wtc_set_invalid ();
    }
return r;
}

const char *nvr_description (DEVICE *dptr)
{
return "non-volatile memory";
}

/* OR routines

   or_rd       I/O page read
   or_reset    process reset
*/

int32 or_rd (int32 pa)
{
uint32 off = (pa - ORBASE);                             /* offset from start of option roms */
uint32 rn = ((off >> 18) & 0x3);                        /* rom number (0 - 3) */
UNIT *uptr = &or_dev.units[rn];                         /* get unit */
uint8 *opr = (uint8*) uptr->filebuf;
int32 data = 0;
uint32 rg;

if ((uptr->flags & UNIT_ATT) && (opr != NULL)) {
    switch (opr[0]) {                                    /* number of ROM chips */
        case 1:
            rg = (off >> 2) & (uptr->capac - 1);
            data = (0xFFFFFF00 | (opr[rg] & 0xFF));
            return sim_rom_read_with_delay (data);

        case 2:
            rg = (off >> 1) & (uptr->capac - 1);
            data = data | opr[rg++];
            data = data | (opr[rg] << 8);
            data = (0xFFFF0000 | data);
            return sim_rom_read_with_delay (data);

        case 4:
            rg = off & (uptr->capac - 1);
            data = data | opr[rg++];
            data = data | (opr[rg++] << 8);
            data = data | (opr[rg++] << 16);
            data = data | (opr[rg++] << 24);
            return sim_rom_read_with_delay (data);
            }
    }
return sim_rom_read_with_delay (0xFFFFFFFF);
}

t_stat or_reset (DEVICE *dptr)
{
return SCPE_OK;
}

t_stat or_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "Option ROMs (OR)\n\n");
fprintf (st, "The OR simulates the read only memory present on option boards.\n");
fprintf (st, "These ROMs contain the self-test code that is called by the main\n");
fprintf (st, "ROM during system power up. The system uses these ROMs to determine\n");
fprintf (st, "which option boards are present.\n");
return SCPE_OK;
}

t_stat or_map (uint32 index, uint8 *rom, t_addr size)
{
UNIT *uptr = &or_unit[index];
uptr->filebuf = (void *)rom;
uptr->capac = size;
uptr->flags |= UNIT_ATT;
return SCPE_OK;
}

t_stat or_unmap (uint32 index)
{
UNIT *uptr = &or_unit[index];
uptr->filebuf = NULL;
uptr->capac = 0;
uptr->flags &= ~UNIT_ATT;
return SCPE_OK;
}

const char *or_description (DEVICE *dptr)
{
return "option ROMs";
}

/* Clock MxPR routines

   iccs_rd/wr   interval timer
*/

int32 iccs_rd (void)
{
return (clk_csr & CLKCSR_IMP);
}

void iccs_wr (int32 data)
{
if ((data & CSR_IE) == 0)
    tmr_int = 0;
if (data & CSR_DONE)
    sim_rtcn_tick_ack (20, TMR_CLK);
clk_csr = (clk_csr & ~CLKCSR_RW) | (data & CLKCSR_RW);
return;
}

/* Clock routines

   clk_svc          process event (clock tick)
   clk_reset        process reset
   clk_description  return device description
*/

t_stat clk_svc (UNIT *uptr)
{
int32 t;

if (clk_csr & CSR_IE)
    tmr_int = 1;
t = sim_rtcn_calb (clk_tps, TMR_CLK);                   /* calibrate clock */
sim_activate_after (uptr, 1000000/clk_tps);             /* reactivate unit */
tmr_poll = t;                                           /* set tmr poll */
tmxr_poll = t * TMXR_MULT;                              /* set mux poll */
AIO_SET_INTERRUPT_LATENCY(tmr_poll*clk_tps);            /* set interrrupt latency */
return SCPE_OK;
}

/* Reset routine */

t_stat clk_reset (DEVICE *dptr)
{
int32 t;

clk_csr = 0;
tmr_int = 0;
t = sim_rtcn_init_unit (&clk_unit, clk_unit.wait, TMR_CLK);/* init 100Hz timer */
sim_activate_after (&clk_unit, 1000000/clk_tps);        /* activate 100Hz unit */
tmr_poll = t;                                           /* set tmr poll */
tmxr_poll = t * TMXR_MULT;                              /* set mux poll */
return SCPE_OK;
}

const char *clk_description (DEVICE *dptr)
{
return "100hz clock tick";
}

/* Dummy I/O space functions */

int32 ReadIO (uint32 pa, int32 lnt)
{
return 0;
}

void WriteIO (uint32 pa, int32 val, int32 lnt)
{
return;
}

int32 ReadIOU (uint32 pa, int32 lnt)
{
return 0;
}

void WriteIOU (uint32 pa, int32 val, int32 lnt)
{
return;
}