//
// vr4300/dcache.c: VR4300 instruction cache.
//
// CEN64: Cycle-Accurate Nintendo 64 Emulator.
// Copyright (C) 2015, Tyler J. Stachecki.
//
// This file is subject to the terms and conditions defined in
// 'LICENSE', which is part of this source code package.
//

#include "common.h"
#include "vr4300/dcache.h"

static inline struct vr4300_dcache_line* get_line(
  struct vr4300_dcache *dcache, uint64_t vaddr);

static inline uint32_t get_tag(const struct vr4300_dcache_line *line);
static inline bool is_valid(const struct vr4300_dcache_line *line);

static void invalidate_line(struct vr4300_dcache_line *line);
static bool is_dirty(const struct vr4300_dcache_line *line);
static void set_dirty(struct vr4300_dcache_line *line);
static void set_tag(struct vr4300_dcache_line *line, uint32_t tag);
static void set_taglo(struct vr4300_dcache_line *line, uint32_t taglo);
static void validate_line(struct vr4300_dcache_line *line, uint32_t tag);

// Returns the line for a given virtual address.
struct vr4300_dcache_line* get_line(
  struct vr4300_dcache *dcache, uint64_t vaddr) {
  return dcache->lines + (vaddr >> 4 & 0x1FF);
}

// Returns the physical tag associated with the line.
uint32_t get_tag(const struct vr4300_dcache_line *line) {
  return line->metadata & ~0xFFFU;
}

// Invalidates the line, but leaves the physical tag untouched.
void invalidate_line(struct vr4300_dcache_line *line) {
  line->metadata &= ~0x1;
}

// Returns true if the line is valid, otherwise returns false.
bool is_dirty(const struct vr4300_dcache_line *line) {
  return (line->metadata & 0x2) == 0x2;
}

// Returns true if the line is valid, otherwise returns false.
bool is_valid(const struct vr4300_dcache_line *line) {
  return (line->metadata & 0x1) == 0x1;
}

// Sets the state of the line to dirty.
void set_dirty(struct vr4300_dcache_line *line) {
  line->metadata |= 0x2;
}

// Sets the tag of the specified line, retaining current valid bit.
void set_tag(struct vr4300_dcache_line *line, uint32_t tag) {
  line->metadata = tag | (line->metadata & 0x1);
}

// Sets the tag of the specified line and valid bit.
void set_taglo(struct vr4300_dcache_line *line, uint32_t taglo) {
  line->metadata = (taglo << 4 & 0xFFFFF000U) | (taglo >> 7 & 0x1) | (taglo >> 5 & 0x2);
}

// Sets the line's physical tag and validates the line.
static void validate_line(struct vr4300_dcache_line *line, uint32_t tag) {
  line->metadata = tag | 0x1;
}

// Sets the physical tag associated with the line, marks as dirty.
void vr4300_dcache_create_dirty_exclusive(
  struct vr4300_dcache *dcache, uint64_t vaddr, uint32_t paddr) {
  struct vr4300_dcache_line *line = get_line(dcache, vaddr);

  set_tag(line, paddr & ~0xFFFU);
  set_dirty(line);
}

// Fills an instruction cache line with data.
void vr4300_dcache_fill(struct vr4300_dcache *dcache,
  uint64_t vaddr, uint32_t paddr, const void *data) {
  struct vr4300_dcache_line *line = get_line(dcache, vaddr);

  memcpy(line->data, data, sizeof(line->data));
  validate_line(line, paddr & ~0xFFFU);
}

// Returns the tag of the line associated with vaddr.
uint32_t vr4300_dcache_get_tag(const struct vr4300_dcache_line *line, uint64_t vaddr) {
  return get_tag(line) | (vaddr & 0xFF0U);
}

// Gets the physical tag associated with the line.
uint32_t vr4300_dcache_get_taglo(struct vr4300_dcache *dcache, uint64_t vaddr) {
  struct vr4300_dcache_line *line = get_line(dcache, vaddr);

  uint32_t taglo = ((line->metadata & 0x1) << 1) | ((line->metadata & 0x2) >> 1);
  return taglo | (line->metadata >> 4 & 0x0FFFFF00U);
}

// Initializes the instruction cache.
void vr4300_dcache_init(struct vr4300_dcache *dcache) {
}

// Invalidates an instruction cache line (regardless if hit or miss).
void vr4300_dcache_invalidate(struct vr4300_dcache_line *line) {
  invalidate_line(line);
}

// Invalidates an instruction cache line (only on a hit).
void vr4300_dcache_invalidate_hit(struct vr4300_dcache *dcache,
  uint64_t vaddr, uint32_t paddr) {
  struct vr4300_dcache_line *line = get_line(dcache, vaddr);
  uint32_t ptag = get_tag(line);

  if (ptag == (paddr & ~0xFFFU) && is_valid(line))
    invalidate_line(line);
}

// Probes the instruction cache for a matching line.
struct vr4300_dcache_line* vr4300_dcache_probe(
  struct vr4300_dcache *dcache, uint64_t vaddr, uint32_t paddr) {
  struct vr4300_dcache_line *line = get_line(dcache, vaddr);
  uint32_t ptag = get_tag(line);

  // Virtually index, and physically tagged.
  if (ptag == (paddr & ~0xFFFU) && is_valid(line))
    return line;

  return NULL;
}

// Marks the line as dirty.
void vr4300_dcache_set_dirty(struct vr4300_dcache_line *line) {
  set_dirty(line);
}

// Sets the physical tag associated with the line.
void vr4300_dcache_set_taglo(struct vr4300_dcache *dcache,
  uint64_t vaddr, uint32_t taglo) {
  struct vr4300_dcache_line *line = get_line(dcache, vaddr);

  set_taglo(line, taglo);
}

// Returns the line if it's dirty and valid.
// Call before replacement of writeback entry.
struct vr4300_dcache_line *vr4300_dcache_should_flush_line(
  struct vr4300_dcache *dcache, uint64_t vaddr) {
  struct vr4300_dcache_line *line = get_line(dcache, vaddr);

  return is_dirty(line) && is_valid(line)
    ? line : NULL;
}

// Writes back the block if the line is valid, then invalidates the line.
struct vr4300_dcache_line *vr4300_dcache_wb_invalidate(
  struct vr4300_dcache *dcache, uint64_t vaddr) {
  struct vr4300_dcache_line *line = get_line(dcache, vaddr);

  if (is_valid(line)) {
    invalidate_line(line);
    return line;
  }

  return NULL;
}

