445 lines
10 KiB
C
445 lines
10 KiB
C
/* mem.c --- memory for RL78 simulator.
|
|
|
|
Copyright (C) 2011-2022 Free Software Foundation, Inc.
|
|
Contributed by Red Hat, Inc.
|
|
|
|
This file is part of the GNU simulators.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* This must come before any other includes. */
|
|
#include "defs.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "opcode/rl78.h"
|
|
#include "mem.h"
|
|
#include "cpu.h"
|
|
|
|
#define ILLEGAL_OPCODE 0xff
|
|
|
|
int rom_limit = 0x100000;
|
|
int ram_base = 0xf8000;
|
|
unsigned char memory[MEM_SIZE];
|
|
#define MASK 0xfffff
|
|
|
|
unsigned char initted[MEM_SIZE];
|
|
int skip_init = 0;
|
|
|
|
#define tprintf if (trace) printf
|
|
|
|
void
|
|
init_mem (void)
|
|
{
|
|
memset (memory, ILLEGAL_OPCODE, sizeof (memory));
|
|
memset (memory + 0xf0000, 0x33, 0x10000);
|
|
|
|
memset (initted, 0, sizeof (initted));
|
|
memset (initted + 0xffee0, 1, 0x00120);
|
|
memset (initted + 0xf0000, 1, 0x01000);
|
|
}
|
|
|
|
void
|
|
mem_ram_size (int ram_bytes)
|
|
{
|
|
ram_base = 0x100000 - ram_bytes;
|
|
}
|
|
|
|
void
|
|
mem_rom_size (int rom_bytes)
|
|
{
|
|
rom_limit = rom_bytes;
|
|
}
|
|
|
|
static int mirror_rom_base = 0x01000;
|
|
static int mirror_ram_base = 0xf1000;
|
|
static int mirror_length = 0x7000;
|
|
|
|
void
|
|
mem_set_mirror (int rom_base, int ram_base, int length)
|
|
{
|
|
mirror_rom_base = rom_base;
|
|
mirror_ram_base = ram_base;
|
|
mirror_length = length;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Note: the RL78 memory map has a few surprises. For starters, part
|
|
of the first 64k is mapped to the last 64k, depending on an SFR bit
|
|
and how much RAM the chip has. This is simulated here, as are a
|
|
few peripherals. */
|
|
|
|
/* This is stdout. We only care about the data byte, not the upper byte. */
|
|
#define SDR00 0xfff10
|
|
#define SSR00 0xf0100
|
|
#define TS0 0xf01b2
|
|
|
|
/* RL78/G13 multiply/divide peripheral. */
|
|
#define MDUC 0xf00e8
|
|
#define MDAL 0xffff0
|
|
#define MDAH 0xffff2
|
|
#define MDBL 0xffff6
|
|
#define MDBH 0xffff4
|
|
#define MDCL 0xf00e0
|
|
#define MDCH 0xf00e2
|
|
static long long mduc_clock = 0;
|
|
static int mda_set = 0;
|
|
#define MDA_SET 15
|
|
|
|
static int last_addr_was_mirror;
|
|
|
|
static int
|
|
address_mapping (int address)
|
|
{
|
|
address &= MASK;
|
|
if (address >= mirror_ram_base && address < mirror_ram_base + mirror_length)
|
|
{
|
|
address = address - mirror_ram_base + mirror_rom_base;
|
|
if (memory[RL78_SFR_PMC] & 1)
|
|
{
|
|
address |= 0x10000;
|
|
}
|
|
last_addr_was_mirror = 1;
|
|
}
|
|
else
|
|
last_addr_was_mirror = 0;
|
|
|
|
return address;
|
|
}
|
|
|
|
static void
|
|
mem_put_byte (int address, unsigned char value)
|
|
{
|
|
address = address_mapping (address);
|
|
memory [address] = value;
|
|
initted [address] = 1;
|
|
if (address == SDR00)
|
|
{
|
|
putchar (value);
|
|
fflush (stdout);
|
|
}
|
|
if (address == TS0)
|
|
{
|
|
if (timer_enabled == 2)
|
|
{
|
|
total_clocks = 0;
|
|
pending_clocks = 0;
|
|
memset (counts_per_insn, 0, sizeof (counts_per_insn));
|
|
memory[0xf0180] = 0xff;
|
|
memory[0xf0181] = 0xff;
|
|
}
|
|
if (value & 1)
|
|
timer_enabled = 1;
|
|
else
|
|
timer_enabled = 0;
|
|
}
|
|
if (address == RL78_SFR_SP && value & 1)
|
|
{
|
|
printf ("Warning: SP value 0x%04x truncated at pc=0x%05x\n", value, pc);
|
|
value &= ~1;
|
|
}
|
|
|
|
if (! g13_multiply)
|
|
return;
|
|
|
|
if (address == MDUC)
|
|
{
|
|
if ((value & 0x81) == 0x81)
|
|
{
|
|
/* division */
|
|
mduc_clock = total_clocks;
|
|
}
|
|
}
|
|
if ((address & ~3) == MDAL)
|
|
{
|
|
mda_set |= (1 << (address & 3));
|
|
if (mda_set == MDA_SET)
|
|
{
|
|
long als, ahs;
|
|
unsigned long alu, ahu;
|
|
long rvs;
|
|
long mdc;
|
|
unsigned long rvu;
|
|
mda_set = 0;
|
|
switch (memory [MDUC] & 0xc8)
|
|
{
|
|
case 0x00:
|
|
alu = mem_get_hi (MDAL);
|
|
ahu = mem_get_hi (MDAH);
|
|
rvu = alu * ahu;
|
|
tprintf ("MDUC: %lu * %lu = %lu\n", alu, ahu, rvu);
|
|
mem_put_hi (MDBL, rvu & 0xffff);
|
|
mem_put_hi (MDBH, rvu >> 16);
|
|
break;
|
|
case 0x08:
|
|
als = sign_ext (mem_get_hi (MDAL), 16);
|
|
ahs = sign_ext (mem_get_hi (MDAH), 16);
|
|
rvs = als * ahs;
|
|
tprintf ("MDUC: %ld * %ld = %ld\n", als, ahs, rvs);
|
|
mem_put_hi (MDBL, rvs & 0xffff);
|
|
mem_put_hi (MDBH, rvs >> 16);
|
|
break;
|
|
case 0x40:
|
|
alu = mem_get_hi (MDAL);
|
|
ahu = mem_get_hi (MDAH);
|
|
rvu = alu * ahu;
|
|
mem_put_hi (MDBL, rvu & 0xffff);
|
|
mem_put_hi (MDBH, rvu >> 16);
|
|
mdc = mem_get_si (MDCL);
|
|
tprintf ("MDUC: %lu * %lu + %lu = ", alu, ahu, mdc);
|
|
mdc += (long) rvu;
|
|
tprintf ("%lu\n", mdc);
|
|
mem_put_si (MDCL, mdc);
|
|
break;
|
|
case 0x48:
|
|
als = sign_ext (mem_get_hi (MDAL), 16);
|
|
ahs = sign_ext (mem_get_hi (MDAH), 16);
|
|
rvs = als * ahs;
|
|
mem_put_hi (MDBL, rvs & 0xffff);
|
|
mem_put_hi (MDBH, rvs >> 16);
|
|
mdc = mem_get_si (MDCL);
|
|
tprintf ("MDUC: %ld * %ld + %ld = ", als, ahs, mdc);
|
|
tprintf ("%ld\n", mdc);
|
|
mdc += rvs;
|
|
mem_put_si (MDCL, mdc);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extern long long total_clocks;
|
|
|
|
static unsigned char
|
|
mem_get_byte (int address)
|
|
{
|
|
address = address_mapping (address);
|
|
switch (address)
|
|
{
|
|
case SSR00:
|
|
case SSR00 + 1:
|
|
return 0x00;
|
|
case 0xf00f0:
|
|
return 0;
|
|
case 0xf0180:
|
|
case 0xf0181:
|
|
return memory[address];
|
|
|
|
case MDUC:
|
|
{
|
|
unsigned char mduc = memory [MDUC];
|
|
if ((mduc & 0x81) == 0x81
|
|
&& total_clocks > mduc_clock + 16)
|
|
{
|
|
unsigned long a, b, q, r;
|
|
memory [MDUC] &= 0xfe;
|
|
a = mem_get_si (MDAL);
|
|
b = mem_get_hi (MDBL) | (mem_get_hi (MDBH) << 16);
|
|
if (b == 0)
|
|
{
|
|
q = ~0;
|
|
r = ~0;
|
|
}
|
|
else
|
|
{
|
|
q = a / b;
|
|
r = a % b;
|
|
}
|
|
tprintf ("MDUC: %lu / %lu = q %lu, r %lu\n", a, b, q, r);
|
|
mem_put_si (MDAL, q);
|
|
mem_put_si (MDCL, r);
|
|
}
|
|
return memory[address];
|
|
}
|
|
case MDCL:
|
|
case MDCL + 1:
|
|
case MDCH:
|
|
case MDCH + 1:
|
|
return memory[address];
|
|
}
|
|
if (address < 0xf1000 && address >= 0xf0000)
|
|
{
|
|
#if 1
|
|
/* Note: comment out this return to trap the invalid access
|
|
instead of returning an "undefined" value. */
|
|
return 0x11;
|
|
#else
|
|
fprintf (stderr, "SFR access error: addr 0x%05x pc 0x%05x\n", address, pc);
|
|
exit (1);
|
|
#endif
|
|
}
|
|
#if 0
|
|
/* Uncomment this block if you want to trap on reads from unwritten memory. */
|
|
if (!skip_init && !initted [address])
|
|
{
|
|
static int uninit_count = 0;
|
|
fprintf (stderr, "\033[31mwarning :read from uninit addr %05x pc %05x\033[0m\n", address, pc);
|
|
uninit_count ++;
|
|
if (uninit_count > 5)
|
|
exit (1);
|
|
}
|
|
#endif
|
|
return memory [address];
|
|
}
|
|
|
|
extern jmp_buf decode_jmp_buf;
|
|
#define DO_RETURN(x) longjmp (decode_jmp_buf, x)
|
|
|
|
#define CHECK_ALIGNMENT(a,v,m) \
|
|
if (a & m) { printf ("Misalignment addr 0x%05x val 0x%04x pc %05x\n", (int)a, (int)v, (int)pc); \
|
|
DO_RETURN (RL78_MAKE_HIT_BREAK ()); }
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
#define SPECIAL_ADDR(a) (0xffff0 <= a || (0xffee0 <= a && a < 0xfff00))
|
|
|
|
void
|
|
mem_put_qi (int address, unsigned char value)
|
|
{
|
|
if (!SPECIAL_ADDR (address))
|
|
tprintf ("\033[34m([%05X]<-%02X)\033[0m", address, value);
|
|
mem_put_byte (address, value);
|
|
}
|
|
|
|
void
|
|
mem_put_hi (int address, unsigned short value)
|
|
{
|
|
if (!SPECIAL_ADDR (address))
|
|
tprintf ("\033[34m([%05X]<-%04X)\033[0m", address, value);
|
|
CHECK_ALIGNMENT (address, value, 1);
|
|
if (address > 0xffff8 && address != RL78_SFR_SP)
|
|
{
|
|
tprintf ("Word access to 0x%05x!!\n", address);
|
|
DO_RETURN (RL78_MAKE_HIT_BREAK ());
|
|
}
|
|
mem_put_byte (address, value);
|
|
mem_put_byte (address + 1, value >> 8);
|
|
}
|
|
|
|
void
|
|
mem_put_psi (int address, unsigned long value)
|
|
{
|
|
tprintf ("\033[34m([%05X]<-%06lX)\033[0m", address, value);
|
|
mem_put_byte (address, value);
|
|
mem_put_byte (address + 1, value >> 8);
|
|
mem_put_byte (address + 2, value >> 16);
|
|
}
|
|
|
|
void
|
|
mem_put_si (int address, unsigned long value)
|
|
{
|
|
tprintf ("\033[34m([%05X]<-%08lX)\033[0m", address, value);
|
|
CHECK_ALIGNMENT (address, value, 3);
|
|
mem_put_byte (address, value);
|
|
mem_put_byte (address + 1, value >> 8);
|
|
mem_put_byte (address + 2, value >> 16);
|
|
mem_put_byte (address + 3, value >> 24);
|
|
}
|
|
|
|
void
|
|
mem_put_blk (int address, const void *bufptr, int nbytes)
|
|
{
|
|
const unsigned char *bp = (unsigned char *)bufptr;
|
|
while (nbytes --)
|
|
mem_put_byte (address ++, *bp ++);
|
|
}
|
|
|
|
unsigned char
|
|
mem_get_pc (int address)
|
|
{
|
|
/* Catch obvious problems. */
|
|
if (address >= rom_limit && address < 0xf0000)
|
|
return 0xff;
|
|
/* This does NOT go through the flash mirror area; you cannot
|
|
execute out of the mirror. */
|
|
return memory [address & MASK];
|
|
}
|
|
|
|
unsigned char
|
|
mem_get_qi (int address)
|
|
{
|
|
int v;
|
|
v = mem_get_byte (address);
|
|
if (!SPECIAL_ADDR (address))
|
|
tprintf ("\033[35m([%05X]->%04X)\033[0m", address, v);
|
|
if (last_addr_was_mirror)
|
|
{
|
|
pending_clocks += 3;
|
|
tprintf ("ROM read\n");
|
|
}
|
|
return v;
|
|
}
|
|
|
|
unsigned short
|
|
mem_get_hi (int address)
|
|
{
|
|
int v;
|
|
v = mem_get_byte (address)
|
|
| mem_get_byte (address + 1) * 256;
|
|
CHECK_ALIGNMENT (address, v, 1);
|
|
if (!SPECIAL_ADDR (address))
|
|
tprintf ("\033[35m([%05X]->%04X)\033[0m", address, v);
|
|
if (last_addr_was_mirror)
|
|
{
|
|
pending_clocks += 3;
|
|
tprintf ("ROM read\n");
|
|
}
|
|
return v;
|
|
}
|
|
|
|
unsigned long
|
|
mem_get_psi (int address)
|
|
{
|
|
int v;
|
|
v = mem_get_byte (address)
|
|
| mem_get_byte (address + 1) * 256
|
|
| mem_get_byte (address + 2) * 65536;
|
|
tprintf ("\033[35m([%05X]->%04X)\033[0m", address, v);
|
|
return v;
|
|
}
|
|
|
|
unsigned long
|
|
mem_get_si (int address)
|
|
{
|
|
int v;
|
|
v = mem_get_byte (address)
|
|
| mem_get_byte (address + 1) * 256
|
|
| mem_get_byte (address + 2) * 65536
|
|
| mem_get_byte (address + 2) * 16777216;
|
|
CHECK_ALIGNMENT (address, v, 3);
|
|
tprintf ("(\033[35m[%05X]->%04X)\033[0m", address, v);
|
|
return v;
|
|
}
|
|
|
|
void
|
|
mem_get_blk (int address, void *bufptr, int nbytes)
|
|
{
|
|
unsigned char *bp = (unsigned char *)bufptr;
|
|
while (nbytes --)
|
|
*bp ++ = mem_get_byte (address ++);
|
|
}
|
|
|
|
int
|
|
sign_ext (int v, int bits)
|
|
{
|
|
if (bits < 8 * sizeof (int))
|
|
{
|
|
v &= (1 << bits) - 1;
|
|
if (v & (1 << (bits - 1)))
|
|
v -= (1 << bits);
|
|
}
|
|
return v;
|
|
}
|