| /* 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; | |
| } |