//
// Copyright (c) 2010-2023 Antmicro
// Copyright (c) 2011-2015 Realtime Embedded
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
using System;
using Antmicro.Renode.Peripherals.Bus;
using Antmicro.Renode.Core;
using Antmicro.Renode.Peripherals;
using Antmicro.Renode.Utilities;
using Antmicro.Renode.Logging;
using System.Collections.Generic;
using Antmicro.Renode.Core.Structure.Registers;

namespace Antmicro.Renode.Peripherals.Miscellaneous
{
    public class ESP32S3_EXTMEM : IBytePeripheral, IDoubleWordPeripheral, IKnownSize
    {
        public ESP32S3_EXTMEM(Machine machine)
        {
            var registersMap = new Dictionary<long, DoubleWordRegister>
            {
                {(long)Registers.DCACHE_CTRL, new DoubleWordRegister(this)
                    // NOTE: This appears to be inverted logic, i.e. `false` means `enable`
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => dCacheEnabled, writeCallback: (_, val) => dCacheEnabled = !val, name: "DCACHE_ENABLE") // The bit is used to activate the data cache. 0: disable, 1: enable
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, name: "DCACHE_SIZE_MODE") // The bit is used to configure cache memory size.0: 32KB, 1: 64KB
                    .WithValueField(2, 2, FieldMode.Read | FieldMode.Write, name: "DCACHE_BLOCKSIZE_MODE") // The bit is used to configure cache block size.0: 16 bytes, 1: 32 bytes,2: 64 bytes
                },
                {(long)Registers.DCACHE_CTRL1, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, name: "DCACHE_SHUT_CORE0_BUS") // The bit is used to disable core0 dbus, 0: enable, 1: disable
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, name: "DCACHE_SHUT_CORE1_BUS") // The bit is used to disable core1 dbus, 0: enable, 1: disable
                },
                {(long)Registers.DCACHE_SYNC_CTRL, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => false, name: "DCACHE_INVALIDATE_ENA") // The bit is used to enable invalidate operation. It will be cleared by hardware after invalidate operation done.
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => false, name: "DCACHE_WRITEBACK_ENA") // The bit is used to enable writeback operation. It will be cleared by hardware after writeback operation done.
                    .WithFlag(2, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => false, name: "DCACHE_CLEAN_ENA") // The bit is used to enable clean operation. It will be cleared by hardware after clean operation done.
                    .WithFlag(3, FieldMode.Read, valueProviderCallback: (_) => true, name: "DCACHE_SYNC_DONE") // The bit is used to indicate clean/writeback/invalidate operation is finished.
                },
                {(long)Registers.DCACHE_SYNC_ADDR, new DoubleWordRegister(this)
                    .WithValueField(0, 32, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => 0, name: "DCACHE_SYNC_ADDR") // The bits are used to configure the start virtual address for clean operations. It should be combined with DCACHE_SYNC_SIZE_REG.
                },
                {(long)Registers.DCACHE_SYNC_SIZE, new DoubleWordRegister(this)
                    .WithValueField(0, 32, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => 0, name: "DCACHE_SYNC_SIZE") // The bits are used to configure the length for sync operations. The bits are the counts of cache block. It should be combined with DCACHE_SYNC_ADDR_REG.
                },
                {(long)Registers.DCACHE_PRELOAD_CTRL, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => false, name: "DCACHE_PRELOAD_ENA") // The bit is used to enable preload operation. It will be cleared by hardware after preload operation done.
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => true, name: "DCACHE_PRELOAD_DONE") // The bit is used to indicate preload operation is finished.
                    .WithFlag(2, FieldMode.Read | FieldMode.Write, name: "DCACHE_PRELOAD_ORDER") // The bit is used to configure the direction of preload operation. 1: descending, 0: ascending.
                },
                {(long)Registers.DCACHE_AUTOLOAD_CTRL, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, name: "DCACHE_AUTOLOAD_SCT0_ENA") // The bits are used to enable the first section for autoload operation.
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, name: "DCACHE_AUTOLOAD_SCT1_ENA") // The bits are used to enable the second section for autoload operation.
                    .WithFlag(2, FieldMode.Read | FieldMode.Write, name: "DCACHE_AUTOLOAD_ENA") // The bit is used to enable and disable autoload operation. It is combined with dcache_autoload_done. 1: enable, 0: disable.
                    .WithFlag(3, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => true, name: "DCACHE_AUTOLOAD_DONE") // The bit is used to indicate autoload operation is finished.
                    .WithFlag(4, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => false, name: "DCACHE_AUTOLOAD_ORDER") // The bits are used to configure the direction of autoload. 1: descending, 0: ascending.
                    .WithValueField(5, 2, FieldMode.Read | FieldMode.Write, name: "DCACHE_AUTOLOAD_RQST") // The bits are used to configure trigger conditions for autoload. 0/3: cache miss, 1: cache hit, 2: both cache miss and hit.
                    .WithValueField(7, 2, FieldMode.Read | FieldMode.Write, name: "DCACHE_AUTOLOAD_SIZE") // The bits are used to configure the numbers of the cache block for the issuing autoload operation.
                    .WithFlag(9, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => false, name: "DCACHE_AUTOLOAD_BUFFER_CLEAR") // The bit is used to clear autoload buffer in dcache.
                },
                {(long)Registers.CACHE_ILG_INT_CLR, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, name: "ICACHE_SYNC_OP_FAULT_INT_CLR") // The bit is used to clear interrupt by sync configurations fault.
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, name: "ICACHE_PRELOAD_OP_FAULT_INT_CLR") // The bit is used to clear interrupt by preload configurations fault.
                    .WithFlag(2, FieldMode.Read | FieldMode.Write, name: "DCACHE_SYNC_OP_FAULT_INT_CLR") // The bit is used to clear interrupt by sync configurations fault.
                    .WithFlag(3, FieldMode.Read | FieldMode.Write, name: "DCACHE_PRELOAD_OP_FAULT_INT_CLR") // The bit is used to clear interrupt by preload configurations fault.
                    .WithFlag(4, FieldMode.Read | FieldMode.Write, name: "DCACHE_WRITE_FLASH_INT_CLR") // The bit is used to clear interrupt by dcache trying to write flash.
                    .WithFlag(5, FieldMode.Read | FieldMode.Write, name: "MMU_ENTRY_FAULT_INT_CLR") // The bit is used to clear interrupt by mmu entry fault.
                    .WithFlag(6, FieldMode.Read | FieldMode.Write, name: "DCACHE_OCCUPY_EXC_INT_CLR") // The bit is used to clear interrupt by dcache trying to replace a line whose blocks all have been occupied by occupy-mode.
                    .WithFlag(7, FieldMode.Read | FieldMode.Write, name: "IBUS_CNT_OVF_INT_CLR") // The bit is used to clear interrupt by ibus counter overflow.
                    .WithFlag(8, FieldMode.Read | FieldMode.Write, name: "DBUS_CNT_OVF_INT_CLR") // The bit is used to clear interrupt by dbus counter overflow.
                },
                {(long)Registers.CACHE_WRAP_AROUND_CTRL, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, name: "CACHE_FLASH_WRAP_AROUND") // The bit is used to enable wrap around mode when read data from flash.
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, name: "CACHE_SRAM_RD_WRAP_AROUND") // The bit is used to enable wrap around mode when read data from spiram.
                },
                {(long)Registers.CACHE_MMU_OWNER, new DoubleWordRegister(this)
                    .WithValueField(7, 2, FieldMode.Read | FieldMode.Write, name: "CACHE_MMU_OWNER") // The bits are used to specify the owner of MMU.bit0: icache, bit1: dcache, bit2: dma, bit3: reserved.
                },
                {(long)Registers.DCACHE_FREEZE, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, name: "ENA") // The bit is used to enable dcache freeze mode
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, name: "MODE") // The bit is used to configure freeze mode, 0:  assert busy if CPU miss 1: assert hit if CPU miss
                    .WithFlag(2, FieldMode.Read, valueProviderCallback: (_) => true, name: "DONE") // The bit is used to indicate dcache freeze success
                },
                {(long)Registers.ICACHE_FREEZE, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, name: "ENA") // The bit is used to enable icache freeze mode
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, name: "MODE") // The bit is used to configure freeze mode, 0:  assert busy if CPU miss 1: assert hit if CPU miss
                    .WithFlag(2, FieldMode.Read, valueProviderCallback: (_) => true, name: "DONE") // The bit is used to indicate icache freeze success
                },
                // 0x60: ******* Description ***********
                {(long)Registers.ICACHE_CTRL, new DoubleWordRegister(this)
                    // NOTE: This appears to be inverted logic, i.e. `false` means `enable`
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, valueProviderCallback: (_) => iCacheEnabled, writeCallback: (_, val) => iCacheEnabled = !val, name: "ICACHE_ENABLE") // The bit is used to activate the data cache. 0: disable, 1: enable
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, name: "ICACHE_WAY_MODE") // The bit is used to configure cache way mode.0: 4-way, 1: 8-way
                    .WithFlag(2, FieldMode.Read | FieldMode.Write, name: "ICACHE_SIZE_MODE") // The bit is used to configure cache memory size.0: 16KB, 1: 32KB
                    .WithFlag(3, FieldMode.Read | FieldMode.Write, name: "ICACHE_BLOCKSIZE_MODE") // The bit is used to configure cache block size.0: 16 bytes, 1: 32 bytes
                },
                // 0x88: ******* Description ***********
                {(long)Registers.ICACHE_SYNC_CTRL, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, name: "ICACHE_INVALIDATE_ENA") // The bit is used to enable invalidate operation. It will be cleared by hardware after invalidate operation done.
                    .WithFlag(1, FieldMode.Read, valueProviderCallback: (_) => true, name: "ICACHE_SYNC_DONE") // The bit is used to indicate invalidate operation is finished.
                },
                // 0x94: ******* Description ***********
                {(long)Registers.ICACHE_PRELOAD_CTRL, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, name: "ICACHE_PRELOAD_ENA") // The bit is used to enable preload operation. It will be cleared by hardware after preload operation done.
                    .WithFlag(1, FieldMode.Read, valueProviderCallback: (_) => true, name: "ICACHE_PRELOAD_DONE") // The bit is used to indicate preload operation is finished.
                    .WithFlag(2, FieldMode.Read | FieldMode.Write, name: "ICACHE_PRELOAD_ORDER") // The bit is used to configure the direction of preload operation. 1: descending, 0: ascending.
                },
                // 0xa0: ******* Description ***********
                {(long)Registers.ICACHE_AUTOLOAD_CTRL, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Read | FieldMode.Write, name: "ICACHE_AUTOLOAD_SCT0_ENA") // The bits are used to enable the first section for autoload operation.
                    .WithFlag(1, FieldMode.Read | FieldMode.Write, name: "ICACHE_AUTOLOAD_SCT1_ENA") // The bits are used to enable the second section for autoload operation.
                    .WithFlag(2, FieldMode.Read | FieldMode.Write, name: "ICACHE_AUTOLOAD_ENA") // The bit is used to enable and disable autoload operation. It is combined with icache_autoload_done. 1: enable, 0: disable.
                    .WithFlag(3, FieldMode.Read, valueProviderCallback: (_) => true, name: "ICACHE_AUTOLOAD_DONE") // The bit is used to indicate autoload operation is finished.
                    .WithFlag(4, FieldMode.Read | FieldMode.Write, name: "ICACHE_AUTOLOAD_ORDER") // The bits are used to configure the direction of autoload. 1: descending, 0: ascending.
                    .WithValueField(5, 2, FieldMode.Read | FieldMode.Write, name: "ICACHE_AUTOLOAD_RQST") // The bits are used to configure trigger conditions for autoload. 0/3: cache miss, 1: cache hit, 2: both cache miss and hit.
                    .WithValueField(7, 2, FieldMode.Read | FieldMode.Write, name: "ICACHE_AUTOLOAD_SIZE") // The bits are used to configure the numbers of the cache block for the issuing autoload operation.
                    .WithFlag(9, FieldMode.Read | FieldMode.Write, name: "ICACHE_AUTOLOAD_BUFFER_CLEAR") // The bit is used to clear autoload buffer in icache.
                },
                // 0x130: ******* Description ***********
                {(long)Registers.CACHE_STATE, new DoubleWordRegister(this)
                    .WithValueField(0, 12, FieldMode.Read, valueProviderCallback: (_) => {if (iCacheEnabled) return 1; else return 0;}, name: "ICACHE_STATE") // The bit is used to indicate whether  icache main fsm is in idle state or not. 1: in idle state,  0: not in idle state
                    .WithValueField(12, 12, FieldMode.Read, valueProviderCallback: (_) => {if (dCacheEnabled) return 1; else return 0;}, name: "DCACHE_STATE") // The bit is used to indicate whether dcache main fsm is in idle state or not. 1: in idle state,  0: not in idle state
                },
            };

            registers = new DoubleWordRegisterCollection(this, registersMap);
        }

        public uint ReadDoubleWord(long offset)
        {
            return registers.Read(offset);
        }

        public byte ReadByte(long offset)
        {
            if (offset % 4 != 0)
            {
                // in the current configuration, only the lowest byte
                // contains a meaningful data
                return 0;
            }
            return (byte)ReadDoubleWord(offset);
        }

        public void Reset()
        {
            // base.Reset();
            registers.Reset();

            // UpdateInterrupts();
        }

        public void WriteDoubleWord(long offset, uint value)
        {
            registers.Write(offset, value);
        }

        public void WriteByte(long offset, byte value)
        {
            if (offset % 4 != 0)
            {
                // in the current configuration, only the lowest byte
                // contains a meaningful data
                return;
            }

            WriteDoubleWord(offset, value);
        }

        public long Size => 0x17C;
        private readonly DoubleWordRegisterCollection registers;
        public bool iCacheEnabled;
        public bool dCacheEnabled;

        private enum Registers : long
        {
            DCACHE_CTRL = 0x0,
            DCACHE_CTRL1 = 0x4,
            DCACHE_TAG_POWER_CTRL = 0x8,
            DCACHE_PRELOCK_CTRL = 0xc,
            DCACHE_PRELOCK_SCT0_ADDR = 0x10,
            DCACHE_PRELOCK_SCT1_ADDR = 0x14,
            DCACHE_PRELOCK_SCT_SIZE = 0x18,
            DCACHE_LOCK_CTRL = 0x1c,
            DCACHE_LOCK_ADDR = 0x20,
            DCACHE_LOCK_SIZE = 0x24,
            DCACHE_SYNC_CTRL = 0x28,
            DCACHE_SYNC_ADDR = 0x2c,
            DCACHE_SYNC_SIZE = 0x30,
            DCACHE_OCCUPY_CTRL = 0x34,
            DCACHE_OCCUPY_ADDR = 0x38,
            DCACHE_OCCUPY_SIZE = 0x3c,
            DCACHE_PRELOAD_CTRL = 0x40,
            DCACHE_PRELOAD_ADDR = 0x44,
            DCACHE_PRELOAD_SIZE = 0x48,
            DCACHE_AUTOLOAD_CTRL = 0x4c,
            DCACHE_AUTOLOAD_SCT0_ADDR = 0x50,
            DCACHE_AUTOLOAD_SCT0_SIZE = 0x54,
            DCACHE_AUTOLOAD_SCT1_ADDR = 0x58,
            DCACHE_AUTOLOAD_SCT1_SIZE = 0x5c,
            ICACHE_CTRL = 0x60,
            ICACHE_CTRL1 = 0x64,
            ICACHE_TAG_POWER_CTRL = 0x68,
            ICACHE_PRELOCK_CTRL = 0x6c,
            ICACHE_PRELOCK_SCT0_ADDR = 0x70,
            ICACHE_PRELOCK_SCT1_ADDR = 0x74,
            ICACHE_PRELOCK_SCT_SIZE = 0x78,
            ICACHE_LOCK_CTRL = 0x7c,
            ICACHE_LOCK_ADDR = 0x80,
            ICACHE_LOCK_SIZE = 0x84,
            ICACHE_SYNC_CTRL = 0x88,
            ICACHE_SYNC_ADDR = 0x8c,
            ICACHE_SYNC_SIZE = 0x90,
            ICACHE_PRELOAD_CTRL = 0x94,
            ICACHE_PRELOAD_ADDR = 0x98,
            ICACHE_PRELOAD_SIZE = 0x9c,
            ICACHE_AUTOLOAD_CTRL = 0xa0,
            ICACHE_AUTOLOAD_SCT0_ADDR = 0xa4,
            ICACHE_AUTOLOAD_SCT0_SIZE = 0xa8,
            ICACHE_AUTOLOAD_SCT1_ADDR = 0xac,
            ICACHE_AUTOLOAD_SCT1_SIZE = 0xb0,
            IBUS_TO_FLASH_START_VADDR = 0xb4,
            IBUS_TO_FLASH_END_VADDR = 0xb8,
            DBUS_TO_FLASH_START_VADDR = 0xbc,
            DBUS_TO_FLASH_END_VADDR = 0xc0,
            CACHE_ACS_CNT_CLR = 0xc4,
            IBUS_ACS_MISS_CNT = 0xc8,
            IBUS_ACS_CNT = 0xcc,
            DBUS_ACS_FLASH_MISS_CNT = 0xd0,
            DBUS_ACS_SPIRAM_MISS_CNT = 0xd4,
            DBUS_ACS_CNT = 0xd8,
            CACHE_ILG_INT_ENA = 0xdc,
            CACHE_ILG_INT_CLR = 0xe0,
            CACHE_ILG_INT_ST = 0xe4,
            CORE0_ACS_CACHE_INT_ENA = 0xe8,
            CORE0_ACS_CACHE_INT_CLR = 0xec,
            CORE0_ACS_CACHE_INT_ST = 0xf0,
            CORE1_ACS_CACHE_INT_ENA = 0xf4,
            CORE1_ACS_CACHE_INT_CLR = 0xf8,
            CORE1_ACS_CACHE_INT_ST = 0xfc,
            CORE0_DBUS_REJECT_ST = 0x100,
            CORE0_DBUS_REJECT_VADDR = 0x104,
            CORE0_IBUS_REJECT_ST = 0x108,
            CORE0_IBUS_REJECT_VADDR = 0x10c,
            CORE1_DBUS_REJECT_ST = 0x110,
            CORE1_DBUS_REJECT_VADDR = 0x114,
            CORE1_IBUS_REJECT_ST = 0x118,
            CORE1_IBUS_REJECT_VADDR = 0x11c,
            CACHE_MMU_FAULT_CONTENT = 0x120,
            CACHE_MMU_FAULT_VADDR = 0x124,
            CACHE_WRAP_AROUND_CTRL = 0x128,
            CACHE_MMU_POWER_CTRL = 0x12c,
            CACHE_STATE = 0x130,
            CACHE_ENCRYPT_DECRYPT_RECORD_DISABLE = 0x134,
            CACHE_ENCRYPT_DECRYPT_CLK_FORCE_ON = 0x138,
            CACHE_BRIDGE_ARBITER_CTRL = 0x13c,
            CACHE_PRELOAD_INT_CTRL = 0x140,
            CACHE_SYNC_INT_CTRL = 0x144,
            CACHE_MMU_OWNER = 0x148,
            CACHE_CONF_MISC = 0x14c,
            DCACHE_FREEZE = 0x150,
            ICACHE_FREEZE = 0x154,
            ICACHE_ATOMIC_OPERATE_ENA = 0x158,
            DCACHE_ATOMIC_OPERATE_ENA = 0x15c,
            CACHE_REQUEST = 0x160,
            CLOCK_GATE = 0x164,
            CACHE_TAG_OBJECT_CTRL = 0x180,
            CACHE_TAG_WAY_OBJECT = 0x184,
            CACHE_VADDR = 0x188,
            CACHE_TAG_CONTENT = 0x18c,
            DATE = 0x3fc,
        }
    }
}
