Get it to kinda work
This commit is contained in:
@@ -1,19 +1,493 @@
|
|||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.handle import Immediate
|
from cocotb.handle import Immediate, LogicArray
|
||||||
|
|
||||||
|
from cocotb.simulator import get_sim_time
|
||||||
|
|
||||||
from cocotb.clock import Clock
|
from cocotb.clock import Clock
|
||||||
from cocotb.triggers import Timer, RisingEdge, FallingEdge, with_timeout
|
from cocotb.triggers import Timer, RisingEdge, FallingEdge, with_timeout
|
||||||
|
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from collections.abc import Mapping
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
CLK_PERIOD = 5
|
CLK_PERIOD = 5
|
||||||
|
|
||||||
|
reference_cache_data = defaultdict(bytearray)
|
||||||
|
|
||||||
|
higher_cache_data = defaultdict(bytearray)
|
||||||
|
|
||||||
|
async def cpu_sequencer(dut, sequence: Mapping[int, int, bool, bool]):
|
||||||
|
|
||||||
|
|
||||||
|
addr, do, we, sync = sequence[0]
|
||||||
|
|
||||||
|
dut.i_addr.value = addr
|
||||||
|
dut.i_data.value = do
|
||||||
|
dut.i_we.value = we
|
||||||
|
dut.i_sync.value = sync
|
||||||
|
|
||||||
|
await FallingEdge(dut.i_rst)
|
||||||
|
|
||||||
|
index = 1
|
||||||
|
|
||||||
|
while index < len(sequence):
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
if not dut.o_rdy.value:
|
||||||
|
continue
|
||||||
|
|
||||||
|
addr, do, we, sync = sequence[index]
|
||||||
|
|
||||||
|
dut.i_addr.value = addr
|
||||||
|
dut.i_data.value = do
|
||||||
|
dut.i_we.value = we
|
||||||
|
dut.i_sync.value = sync
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
await Timer(150, "ns")
|
||||||
|
|
||||||
|
async def cpu_data_monitor(dut):
|
||||||
|
previous_address = 0
|
||||||
|
address = 0
|
||||||
|
|
||||||
|
we = 0
|
||||||
|
previous_we = 0
|
||||||
|
|
||||||
|
i_data = 0
|
||||||
|
previous_i_data = 0
|
||||||
|
|
||||||
|
await FallingEdge(dut.i_rst)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
if not dut.o_rdy.value:
|
||||||
|
continue
|
||||||
|
|
||||||
|
previous_address = address
|
||||||
|
previous_we = we
|
||||||
|
address = int(dut.i_addr.value)
|
||||||
|
we = int(dut.i_we.value)
|
||||||
|
|
||||||
|
previous_i_data = i_data
|
||||||
|
i_data = int(dut.i_data.value)
|
||||||
|
|
||||||
|
data = int(dut.o_data.value)
|
||||||
|
|
||||||
|
if previous_address == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# don't care if it was a write
|
||||||
|
if previous_we:
|
||||||
|
|
||||||
|
index = (previous_address // 64) % 64
|
||||||
|
offset = previous_address % 64
|
||||||
|
|
||||||
|
cacheline = reference_cache_data[index]
|
||||||
|
|
||||||
|
cacheline[offset] = previous_i_data
|
||||||
|
logger.debug(f"We saw a write here {index=} {offset=} previous_data={previous_i_data:x}")
|
||||||
|
else:
|
||||||
|
index = (previous_address // 64) % 64
|
||||||
|
offset = previous_address % 64
|
||||||
|
|
||||||
|
cacheline = reference_cache_data[index]
|
||||||
|
|
||||||
|
expected_data = cacheline[offset]
|
||||||
|
|
||||||
|
if (data != expected_data):
|
||||||
|
logger.error(f"{get_sim_time()} {address=:x} {previous_address=:x} {data=:x} {expected_data=:x}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def mmu_sequencer(dut):
|
||||||
|
while True:
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
dut.i_phys_address.value = dut.i_addr.value
|
||||||
|
|
||||||
|
async def handle_higher_level_cache(dut):
|
||||||
|
dut.i_cache_rdy.value = 0
|
||||||
|
|
||||||
|
class CacheCmd(IntEnum):
|
||||||
|
CACHE_NONE = 0
|
||||||
|
CACHE_READ = 1
|
||||||
|
CACHE_WRITE = 2
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
dut.i_cache_rdy.value = 0
|
||||||
|
|
||||||
|
if not dut.o_cache_valid.value:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cmd = CacheCmd(dut.o_cache_cmd.value)
|
||||||
|
addr = int(dut.o_cache_addr.value)
|
||||||
|
|
||||||
|
logger.debug(f"{cmd=} {addr=}")
|
||||||
|
|
||||||
|
|
||||||
|
if cmd == CacheCmd.CACHE_READ:
|
||||||
|
|
||||||
|
if addr not in higher_cache_data:
|
||||||
|
data = bytearray(random.randbytes(64))
|
||||||
|
higher_cache_data[addr] = data
|
||||||
|
|
||||||
|
dut.i_cache_data.value = LogicArray.from_bytes(higher_cache_data[addr] , byteorder="little")
|
||||||
|
|
||||||
|
dut.i_cache_rdy.value = 1
|
||||||
|
|
||||||
|
reference_cache_data[int(dut.read_index.value)] = higher_cache_data[addr]
|
||||||
|
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
|
||||||
|
dut.i_cache_rdy.value = 0
|
||||||
|
|
||||||
|
elif cmd == CacheCmd.CACHE_WRITE:
|
||||||
|
|
||||||
|
dut.i_cache_rdy.value = 1
|
||||||
|
|
||||||
|
data = dut.o_cache_data.value.to_bytes(byteorder="little")
|
||||||
|
|
||||||
|
higher_cache_data[addr] = bytearray(data)
|
||||||
|
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
|
||||||
|
dut.i_cache_rdy.value = 0
|
||||||
|
|
||||||
@cocotb.test
|
@cocotb.test
|
||||||
async def sanity_test(dut):
|
async def sanity_test(dut):
|
||||||
|
expected_cache_misses = 0
|
||||||
|
expected_evictions = 0
|
||||||
|
|
||||||
cocotb.start_soon(Clock(dut.i_clk, CLK_PERIOD, unit="ns").start())
|
cocotb.start_soon(Clock(dut.i_clk, CLK_PERIOD, unit="ns").start())
|
||||||
|
cocotb.start_soon(mmu_sequencer(dut))
|
||||||
|
cocotb.start_soon(handle_higher_level_cache(dut))
|
||||||
|
cocotb.start_soon(cpu_data_monitor(dut))
|
||||||
|
|
||||||
|
cpu_sequence = [
|
||||||
|
(0x100, 0xaa, True, False),
|
||||||
|
(0x101, 0xbb, True, False),
|
||||||
|
(0x100, 0x00, False, False),
|
||||||
|
(0x101, 0x00, False, False),
|
||||||
|
(0x200, 0xcc, True, False),
|
||||||
|
(0x201, 0xdd, True, False),
|
||||||
|
(0x100, 0x00, False, False),
|
||||||
|
(0x101, 0x00, False, False),
|
||||||
|
(0x200, 0x00, False, False),
|
||||||
|
(0x201, 0x00, False, False),
|
||||||
|
(0x100, 0x11, True, False),
|
||||||
|
(0x101, 0x22, True, False),
|
||||||
|
(0x100, 0x00, False, False),
|
||||||
|
(0x200, 0x33, True, False),
|
||||||
|
(0x101, 0x00, False, False),
|
||||||
|
(0x201, 0x44, True, False),
|
||||||
|
(0x100, 0x00, False, False),
|
||||||
|
(0x200, 0x00, False, False),
|
||||||
|
(0x101, 0x00, False, False),
|
||||||
|
(0x201, 0x00, False, False),
|
||||||
|
]
|
||||||
|
|
||||||
dut.i_rst.value = Immediate(1)
|
dut.i_rst.value = Immediate(1)
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
await RisingEdge(dut.i_clk)
|
await RisingEdge(dut.i_clk)
|
||||||
dut.i_rst.value = 0
|
dut.i_rst.value = 0
|
||||||
|
|
||||||
await Timer(1, "us")
|
await cpu_sequencer(dut, cpu_sequence)
|
||||||
|
|
||||||
|
|
||||||
|
expected_cache_misses = 2
|
||||||
|
expected_evictions = 0
|
||||||
|
|
||||||
|
dut_evictions = int(dut.eviction_count.value)
|
||||||
|
dut_misses = int(dut.cache_miss_count.value)
|
||||||
|
|
||||||
|
if dut_evictions != expected_evictions:
|
||||||
|
logger.error(f"Eviction count mismatch! Expected {expected_evictions}, saw {dut_evictions}")
|
||||||
|
|
||||||
|
if dut_misses != expected_cache_misses:
|
||||||
|
logger.error(f"Miss count mismatch! Expected {expected_cache_misses}, saw {dut_misses}")
|
||||||
|
|
||||||
|
|
||||||
|
@cocotb.test
|
||||||
|
async def clean_evict_test(dut):
|
||||||
|
cocotb.start_soon(Clock(dut.i_clk, CLK_PERIOD, unit="ns").start())
|
||||||
|
cocotb.start_soon(mmu_sequencer(dut))
|
||||||
|
cocotb.start_soon(handle_higher_level_cache(dut))
|
||||||
|
cocotb.start_soon(cpu_data_monitor(dut))
|
||||||
|
|
||||||
|
# Read from one cacheline, then read from an aliased cacheline without writing.
|
||||||
|
# cacheline should be overwritten without evicting
|
||||||
|
cpu_sequence = [
|
||||||
|
(0x100, 0x00, False, False),
|
||||||
|
(0x1100, 0x00, False, False),
|
||||||
|
]
|
||||||
|
|
||||||
|
dut.i_rst.value = Immediate(1)
|
||||||
|
for _ in range(10):
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
dut.i_rst.value = 0
|
||||||
|
|
||||||
|
await cpu_sequencer(dut, cpu_sequence)
|
||||||
|
|
||||||
|
expected_cache_misses = 2
|
||||||
|
expected_evictions = 0
|
||||||
|
|
||||||
|
dut_evictions = int(dut.eviction_count.value)
|
||||||
|
dut_misses = int(dut.cache_miss_count.value)
|
||||||
|
|
||||||
|
if dut_evictions != expected_evictions:
|
||||||
|
logger.error(f"Eviction count mismatch! Expected {expected_evictions}, saw {dut_evictions}")
|
||||||
|
|
||||||
|
if dut_misses != expected_cache_misses:
|
||||||
|
logger.error(f"Miss count mismatch! Expected {expected_cache_misses}, saw {dut_misses}")
|
||||||
|
|
||||||
|
@cocotb.test
|
||||||
|
async def dirty_evict_test(dut):
|
||||||
|
cocotb.start_soon(Clock(dut.i_clk, CLK_PERIOD, unit="ns").start())
|
||||||
|
cocotb.start_soon(mmu_sequencer(dut))
|
||||||
|
cocotb.start_soon(handle_higher_level_cache(dut))
|
||||||
|
cocotb.start_soon(cpu_data_monitor(dut))
|
||||||
|
|
||||||
|
# Read from one cacheline, then read from an aliased cacheline without writing.
|
||||||
|
# cacheline should be overwritten without evicting
|
||||||
|
cpu_sequence = [
|
||||||
|
(0x100, 0x41, True, False),
|
||||||
|
(0x101, 0x42, True, False),
|
||||||
|
(0x1100, 0x00, False, False),
|
||||||
|
(0x1100, 0xaa, True, False),
|
||||||
|
(0x100, 0x00, False, False)
|
||||||
|
]
|
||||||
|
|
||||||
|
dut.i_rst.value = Immediate(1)
|
||||||
|
for _ in range(10):
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
dut.i_rst.value = 0
|
||||||
|
|
||||||
|
await cpu_sequencer(dut, cpu_sequence)
|
||||||
|
|
||||||
|
expected_cache_misses = 3
|
||||||
|
expected_evictions = 2
|
||||||
|
|
||||||
|
dut_evictions = int(dut.eviction_count.value)
|
||||||
|
dut_misses = int(dut.cache_miss_count.value)
|
||||||
|
|
||||||
|
if dut_evictions != expected_evictions:
|
||||||
|
logger.error(f"Eviction count mismatch! Expected {expected_evictions}, saw {dut_evictions}")
|
||||||
|
|
||||||
|
if dut_misses != expected_cache_misses:
|
||||||
|
logger.error(f"Miss count mismatch! Expected {expected_cache_misses}, saw {dut_misses}")
|
||||||
|
|
||||||
|
|
||||||
|
@cocotb.test
|
||||||
|
async def long_write_thrash_test(dut):
|
||||||
|
cocotb.start_soon(Clock(dut.i_clk, CLK_PERIOD, unit="ns").start())
|
||||||
|
cocotb.start_soon(mmu_sequencer(dut))
|
||||||
|
cocotb.start_soon(handle_higher_level_cache(dut))
|
||||||
|
cocotb.start_soon(cpu_data_monitor(dut))
|
||||||
|
|
||||||
|
num_lines_read = 2**20//64
|
||||||
|
|
||||||
|
cpu_sequence = [
|
||||||
|
(i*64, i % 256, True, False)
|
||||||
|
for i in range(num_lines_read)]
|
||||||
|
|
||||||
|
dut.i_rst.value = Immediate(1)
|
||||||
|
for _ in range(10):
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
dut.i_rst.value = 0
|
||||||
|
|
||||||
|
await cpu_sequencer(dut, cpu_sequence)
|
||||||
|
|
||||||
|
# The last 64 lines aren't evicted
|
||||||
|
expected_cache_misses = num_lines_read
|
||||||
|
expected_evictions = num_lines_read - 64
|
||||||
|
|
||||||
|
dut_evictions = int(dut.eviction_count.value)
|
||||||
|
dut_misses = int(dut.cache_miss_count.value)
|
||||||
|
|
||||||
|
if dut_evictions != expected_evictions:
|
||||||
|
logger.error(f"Eviction count mismatch! Expected {expected_evictions}, saw {dut_evictions}")
|
||||||
|
|
||||||
|
if dut_misses != expected_cache_misses:
|
||||||
|
logger.error(f"Miss count mismatch! Expected {expected_cache_misses}, saw {dut_misses}")
|
||||||
|
|
||||||
|
|
||||||
|
@cocotb.test
|
||||||
|
async def long_write_read_thrash_test(dut):
|
||||||
|
cocotb.start_soon(Clock(dut.i_clk, CLK_PERIOD, unit="ns").start())
|
||||||
|
cocotb.start_soon(mmu_sequencer(dut))
|
||||||
|
cocotb.start_soon(handle_higher_level_cache(dut))
|
||||||
|
cocotb.start_soon(cpu_data_monitor(dut))
|
||||||
|
|
||||||
|
num_lines_read = 2**20//64
|
||||||
|
|
||||||
|
|
||||||
|
cpu_sequence = [
|
||||||
|
(i*64, i % 256, True, False)
|
||||||
|
for i in range(num_lines_read)]
|
||||||
|
|
||||||
|
cpu_sequence.extend([
|
||||||
|
(i*64, 0, False, False)
|
||||||
|
for i in range(num_lines_read)])
|
||||||
|
|
||||||
|
dut.i_rst.value = Immediate(1)
|
||||||
|
for _ in range(10):
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
dut.i_rst.value = 0
|
||||||
|
|
||||||
|
await cpu_sequencer(dut, cpu_sequence)
|
||||||
|
|
||||||
|
expected_cache_misses = num_lines_read * 2
|
||||||
|
expected_evictions = num_lines_read
|
||||||
|
|
||||||
|
dut_evictions = int(dut.eviction_count.value)
|
||||||
|
dut_misses = int(dut.cache_miss_count.value)
|
||||||
|
|
||||||
|
if dut_evictions != expected_evictions:
|
||||||
|
logger.error(f"Eviction count mismatch! Expected {expected_evictions}, saw {dut_evictions}")
|
||||||
|
|
||||||
|
if dut_misses != expected_cache_misses:
|
||||||
|
logger.error(f"Miss count mismatch! Expected {expected_cache_misses}, saw {dut_misses}")
|
||||||
|
|
||||||
|
|
||||||
|
@cocotb.test
|
||||||
|
async def long_write_linear_test(dut):
|
||||||
|
cocotb.start_soon(Clock(dut.i_clk, CLK_PERIOD, unit="ns").start())
|
||||||
|
cocotb.start_soon(mmu_sequencer(dut))
|
||||||
|
cocotb.start_soon(handle_higher_level_cache(dut))
|
||||||
|
cocotb.start_soon(cpu_data_monitor(dut))
|
||||||
|
|
||||||
|
num_bytes_read = 2**16
|
||||||
|
|
||||||
|
cpu_sequence = [
|
||||||
|
(i, i % 256, True, False)
|
||||||
|
for i in range(num_bytes_read)]
|
||||||
|
|
||||||
|
dut.i_rst.value = Immediate(1)
|
||||||
|
for _ in range(10):
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
dut.i_rst.value = 0
|
||||||
|
|
||||||
|
await cpu_sequencer(dut, cpu_sequence)
|
||||||
|
|
||||||
|
expected_cache_misses = num_bytes_read // 64
|
||||||
|
expected_evictions = num_bytes_read//64 - 64
|
||||||
|
|
||||||
|
dut_evictions = int(dut.eviction_count.value)
|
||||||
|
dut_misses = int(dut.cache_miss_count.value)
|
||||||
|
|
||||||
|
if dut_evictions != expected_evictions:
|
||||||
|
logger.error(f"Eviction count mismatch! Expected {expected_evictions}, saw {dut_evictions}")
|
||||||
|
|
||||||
|
if dut_misses != expected_cache_misses:
|
||||||
|
logger.error(f"Miss count mismatch! Expected {expected_cache_misses}, saw {dut_misses}")
|
||||||
|
|
||||||
|
|
||||||
|
@cocotb.test
|
||||||
|
async def long_write_read_linear_test(dut):
|
||||||
|
cocotb.start_soon(Clock(dut.i_clk, CLK_PERIOD, unit="ns").start())
|
||||||
|
cocotb.start_soon(mmu_sequencer(dut))
|
||||||
|
cocotb.start_soon(handle_higher_level_cache(dut))
|
||||||
|
cocotb.start_soon(cpu_data_monitor(dut))
|
||||||
|
|
||||||
|
num_bytes_read = 2**16
|
||||||
|
|
||||||
|
|
||||||
|
cpu_sequence = [
|
||||||
|
(i, i % 256, True, False)
|
||||||
|
for i in range(num_bytes_read)]
|
||||||
|
|
||||||
|
cpu_sequence.extend([
|
||||||
|
(i, 0, False, False)
|
||||||
|
for i in range(num_bytes_read)])
|
||||||
|
|
||||||
|
dut.i_rst.value = Immediate(1)
|
||||||
|
for _ in range(10):
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
dut.i_rst.value = 0
|
||||||
|
|
||||||
|
await cpu_sequencer(dut, cpu_sequence)
|
||||||
|
|
||||||
|
expected_cache_misses = (num_bytes_read // 64) * 2
|
||||||
|
expected_evictions = num_bytes_read // 64
|
||||||
|
|
||||||
|
dut_evictions = int(dut.eviction_count.value)
|
||||||
|
dut_misses = int(dut.cache_miss_count.value)
|
||||||
|
|
||||||
|
if dut_evictions != expected_evictions:
|
||||||
|
logger.error(f"Eviction count mismatch! Expected {expected_evictions}, saw {dut_evictions}")
|
||||||
|
|
||||||
|
if dut_misses != expected_cache_misses:
|
||||||
|
logger.error(f"Miss count mismatch! Expected {expected_cache_misses}, saw {dut_misses}")
|
||||||
|
|
||||||
|
@cocotb.test
|
||||||
|
async def short_write_read_linear_test(dut):
|
||||||
|
# What makes this test "short" is that we read 64 cachelines,
|
||||||
|
# so we shouldn't have to make any evictions
|
||||||
|
# TODO add number of evictions and cachlines loaded as performance counteres
|
||||||
|
|
||||||
|
cocotb.start_soon(Clock(dut.i_clk, CLK_PERIOD, unit="ns").start())
|
||||||
|
cocotb.start_soon(mmu_sequencer(dut))
|
||||||
|
cocotb.start_soon(handle_higher_level_cache(dut))
|
||||||
|
cocotb.start_soon(cpu_data_monitor(dut))
|
||||||
|
|
||||||
|
num_bytes_read = 64*64
|
||||||
|
|
||||||
|
cpu_sequence = [
|
||||||
|
(i, i % 256, True, False)
|
||||||
|
for i in range(num_bytes_read)] # 64 bytes times 64 cachelines
|
||||||
|
|
||||||
|
cpu_sequence.extend([
|
||||||
|
(i, i % 256, False, False)
|
||||||
|
for i in range(num_bytes_read)]) # 64 bytes times 64 cachelines
|
||||||
|
|
||||||
|
dut.i_rst.value = Immediate(1)
|
||||||
|
for _ in range(10):
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
dut.i_rst.value = 0
|
||||||
|
|
||||||
|
await cpu_sequencer(dut, cpu_sequence)
|
||||||
|
|
||||||
|
expected_cache_misses = num_bytes_read//64
|
||||||
|
expected_evictions = num_bytes_read//64 - 64
|
||||||
|
|
||||||
|
dut_evictions = int(dut.eviction_count.value)
|
||||||
|
dut_misses = int(dut.cache_miss_count.value)
|
||||||
|
|
||||||
|
if dut_evictions != expected_evictions:
|
||||||
|
logger.error(f"Eviction count mismatch! Expected {expected_evictions}, saw {dut_evictions}")
|
||||||
|
|
||||||
|
if dut_misses != expected_cache_misses:
|
||||||
|
logger.error(f"Miss count mismatch! Expected {expected_cache_misses}, saw {dut_misses}")
|
||||||
|
|
||||||
|
@cocotb.test
|
||||||
|
async def random_access_test(dut):
|
||||||
|
# Just fully random accesses
|
||||||
|
# This is also kind of a thrash test since this is not realistic
|
||||||
|
|
||||||
|
cocotb.start_soon(Clock(dut.i_clk, CLK_PERIOD, unit="ns").start())
|
||||||
|
cocotb.start_soon(mmu_sequencer(dut))
|
||||||
|
cocotb.start_soon(handle_higher_level_cache(dut))
|
||||||
|
cocotb.start_soon(cpu_data_monitor(dut))
|
||||||
|
|
||||||
|
num_bytes_read = 2**18
|
||||||
|
|
||||||
|
cpu_sequence = [
|
||||||
|
(random.randint(0, 2**32), random.randint(0, 255), random.randint(0,1), random.randint(0,1))
|
||||||
|
for _ in range(num_bytes_read)] # 64 bytes times 64 cachelines
|
||||||
|
|
||||||
|
dut.i_rst.value = Immediate(1)
|
||||||
|
for _ in range(10):
|
||||||
|
await RisingEdge(dut.i_clk)
|
||||||
|
dut.i_rst.value = 0
|
||||||
|
|
||||||
|
await cpu_sequencer(dut, cpu_sequence)
|
||||||
@@ -11,6 +11,7 @@ module application_wrapper_cache_l1 #(
|
|||||||
/* CPU Interface */
|
/* CPU Interface */
|
||||||
input logic [ADDR_WIDTH-1:0] i_addr,
|
input logic [ADDR_WIDTH-1:0] i_addr,
|
||||||
input logic i_we,
|
input logic i_we,
|
||||||
|
input logic i_sync,
|
||||||
input logic [7:0] i_data,
|
input logic [7:0] i_data,
|
||||||
output logic [7:0] o_data,
|
output logic [7:0] o_data,
|
||||||
|
|
||||||
@@ -23,17 +24,17 @@ module application_wrapper_cache_l1 #(
|
|||||||
input logic i_mmu_valid,
|
input logic i_mmu_valid,
|
||||||
|
|
||||||
/* Higher level cache interface */
|
/* Higher level cache interface */
|
||||||
output logic [ADDR_WIDTH-1:0] o_addr,
|
output logic [ADDR_WIDTH-1:0] o_cache_addr,
|
||||||
output logic [1:0] o_cache_cmd,
|
output cache_cmd_e o_cache_cmd,
|
||||||
output logic o_cache_valid,
|
output logic o_cache_valid,
|
||||||
|
|
||||||
output logic [63:0] o_cache_data,
|
output logic [CACHELINE_SIZE*8-1:0] o_cache_data,
|
||||||
input logic [31:0] i_cache_data,
|
input logic [CACHELINE_SIZE*8-1:0] i_cache_data,
|
||||||
input logic i_cache_rdy
|
input logic i_cache_rdy
|
||||||
);
|
);
|
||||||
|
|
||||||
// we have 32 bit addresses, 64 byte cache lines, and 64 total lines.
|
// we have 32 bit addresses, 64 byte cache lines, and 64 total lines.
|
||||||
// Thats 6 bit for offset, 6 bit for index, and 20 bit for cache.
|
// Thats 6 bit for offset, 6 bit for index, and 20 bit for tag.
|
||||||
|
|
||||||
// cache is virtually indexed, physically tagged
|
// cache is virtually indexed, physically tagged
|
||||||
|
|
||||||
@@ -42,49 +43,292 @@ localparam INDEX_W = $clog2(CACHELINE_COUNT);
|
|||||||
localparam TAG_W = ADDR_WIDTH - INDEX_W - OFFSET_W;
|
localparam TAG_W = ADDR_WIDTH - INDEX_W - OFFSET_W;
|
||||||
localparam META_W = 3; // valid, unique, clean
|
localparam META_W = 3; // valid, unique, clean
|
||||||
|
|
||||||
logic [OFFSET_W-1:0] offset;
|
typedef struct {
|
||||||
logic [INDEX_W-1:0] index;
|
|
||||||
logic [TAG_W-1:0] tag;
|
logic [TAG_W-1:0] tag;
|
||||||
|
logic valid;
|
||||||
|
logic shared;
|
||||||
|
logic clean;
|
||||||
|
} meta_tag_t;
|
||||||
|
|
||||||
assign offset = i_addr[OFFSET_W-1:0];
|
logic [OFFSET_W-1:0] offset, offset_d1;
|
||||||
assign index = i_addr[INDEX_W+OFFSET_W-1:OFFSET_W];
|
logic [INDEX_W-1:0] index, index_d1, index_d2;
|
||||||
assign tag = i_addr[INDEX_W+OFFSET_W+TAG_W-1:INDEX_W+OFFSET_W];
|
logic [TAG_W-1:0] tag, tag_d1;
|
||||||
|
|
||||||
// cacheline size is in bytes, not bits
|
// cacheline size is in bytes, not bits
|
||||||
// direct mapped cache, read one line so we have data ready if its a hit.
|
// direct mapped cache, read one line so we have data ready if its a hit.
|
||||||
logic [CACHELINE_SIZE*8-1:0] data_array [CACHELINE_COUNT];
|
logic [CACHELINE_SIZE*8-1:0] data_array [CACHELINE_COUNT];
|
||||||
logic [META_W+TAG_W-1:0] meta_tag_array [CACHELINE_COUNT];
|
meta_tag_t meta_tag_array [CACHELINE_COUNT];
|
||||||
|
|
||||||
enum logic [1:0] {IDLE, READY, EVICT, READ} state, state_next;
|
logic [CACHELINE_SIZE*8-1:0] current_data, current_data_next, write_data_prev;
|
||||||
|
meta_tag_t current_meta_tag, current_meta_tag_next;
|
||||||
|
|
||||||
|
logic [OFFSET_W-1:0] read_offset, read_offset_next;
|
||||||
|
logic [INDEX_W-1:0] read_index, read_index_next;
|
||||||
|
logic [ADDR_WIDTH-1:0] read_address, read_address_next;
|
||||||
|
logic [ADDR_WIDTH-1:0] write_address, write_address_next;
|
||||||
|
|
||||||
|
logic [CACHELINE_SIZE*8-1:0] write_data;
|
||||||
|
meta_tag_t write_meta_tag;
|
||||||
|
|
||||||
|
logic [INDEX_W-1:0] write_index;
|
||||||
|
logic data_write_enable;
|
||||||
|
logic meta_tag_write_enable;
|
||||||
|
|
||||||
|
logic we_d1;
|
||||||
|
logic latched_we, latched_we_next;
|
||||||
|
|
||||||
|
logic [7:0] latched_data, latched_data_next;
|
||||||
|
logic [7:0] data_d1;
|
||||||
|
|
||||||
|
|
||||||
|
// performance counters
|
||||||
|
logic [31:0] eviction_count, eviction_count_next;
|
||||||
|
logic [31:0] cache_miss_count, cache_miss_count_next;
|
||||||
|
|
||||||
|
// reset counter
|
||||||
|
logic [INDEX_W-1:0] clear_counter, clear_counter_next;
|
||||||
|
|
||||||
|
enum logic [2:0] {RESET, CLEAR, IDLE, READY, EVICT, READ} prev_state, state, state_next;
|
||||||
|
|
||||||
always_ff @(posedge i_clk) begin
|
always_ff @(posedge i_clk) begin
|
||||||
if (i_rst) begin
|
if (i_rst) begin
|
||||||
state <= IDLE;
|
state <= RESET;
|
||||||
|
|
||||||
|
current_data <= '0;
|
||||||
|
|
||||||
|
tag_d1 <= '0;
|
||||||
|
index_d1 <= '0;
|
||||||
|
offset_d1 <= '0;
|
||||||
|
|
||||||
|
read_address <= '0;
|
||||||
|
write_address <= '0;
|
||||||
|
|
||||||
|
latched_we <= '0;
|
||||||
|
|
||||||
|
latched_data <= '0;
|
||||||
|
|
||||||
|
eviction_count <= '0;
|
||||||
|
cache_miss_count <= '0;
|
||||||
|
|
||||||
|
clear_counter <= '0;
|
||||||
|
|
||||||
end else begin
|
end else begin
|
||||||
|
prev_state <= state;
|
||||||
state <= state_next;
|
state <= state_next;
|
||||||
|
|
||||||
|
current_data <= current_data_next;
|
||||||
|
write_data_prev <= write_data;
|
||||||
|
current_meta_tag <= current_meta_tag_next;
|
||||||
|
|
||||||
|
read_offset <= read_offset_next;
|
||||||
|
read_index <= read_index_next;
|
||||||
|
|
||||||
|
read_address <= read_address_next;
|
||||||
|
write_address <= write_address_next;
|
||||||
|
|
||||||
|
if (data_write_enable) begin
|
||||||
|
data_array[write_index] <= write_data;
|
||||||
|
end
|
||||||
|
|
||||||
|
if (meta_tag_write_enable) begin
|
||||||
|
meta_tag_array[write_index] <= write_meta_tag;
|
||||||
|
end
|
||||||
|
|
||||||
|
tag_d1 <= tag;
|
||||||
|
index_d1 <= index;
|
||||||
|
index_d2 <= index_d1;
|
||||||
|
offset_d1 <= offset;
|
||||||
|
we_d1 <= i_we;
|
||||||
|
data_d1 <= i_data;
|
||||||
|
|
||||||
|
latched_we <= latched_we_next;
|
||||||
|
latched_data <= latched_data_next;
|
||||||
|
|
||||||
|
eviction_count <= eviction_count_next;
|
||||||
|
cache_miss_count <= cache_miss_count_next;
|
||||||
|
|
||||||
|
clear_counter <= clear_counter_next;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
always_comb begin
|
always_comb begin
|
||||||
state_next = state;
|
state_next = state;
|
||||||
|
|
||||||
|
current_data_next = current_data;
|
||||||
|
current_meta_tag_next = current_meta_tag;
|
||||||
|
|
||||||
|
read_offset_next = read_offset;
|
||||||
|
read_index_next = read_index;
|
||||||
|
read_address_next = read_address;
|
||||||
|
write_address_next = write_address;
|
||||||
|
|
||||||
|
latched_we_next = latched_we;
|
||||||
|
latched_data_next = latched_data;
|
||||||
|
|
||||||
o_rdy = '0;
|
o_rdy = '0;
|
||||||
|
|
||||||
|
o_cache_valid = '0;
|
||||||
|
o_cache_cmd = CACHE_NONE;
|
||||||
|
o_cache_addr = '0;
|
||||||
|
o_cache_data = '0;
|
||||||
|
|
||||||
|
// vipt
|
||||||
|
offset = i_addr[OFFSET_W-1:0];
|
||||||
|
index = i_addr[INDEX_W+OFFSET_W-1:OFFSET_W];
|
||||||
|
tag = i_phys_address[INDEX_W+OFFSET_W+TAG_W-1:INDEX_W+OFFSET_W];
|
||||||
|
|
||||||
|
write_index = '0;
|
||||||
|
write_data = '0;
|
||||||
|
data_write_enable = '0;
|
||||||
|
write_meta_tag.tag = '0;
|
||||||
|
write_meta_tag.valid = '0;
|
||||||
|
write_meta_tag.shared = '0;
|
||||||
|
write_meta_tag.clean = '0;
|
||||||
|
meta_tag_write_enable = '0;
|
||||||
|
|
||||||
|
o_data = '0;
|
||||||
|
|
||||||
|
eviction_count_next = eviction_count;
|
||||||
|
cache_miss_count_next = cache_miss_count;
|
||||||
|
|
||||||
|
clear_counter_next = clear_counter;
|
||||||
|
|
||||||
case (state)
|
case (state)
|
||||||
IDLE: begin
|
RESET: begin
|
||||||
state_next = READY;
|
state_next = CLEAR;
|
||||||
end
|
end
|
||||||
|
|
||||||
READY: begin
|
CLEAR: begin
|
||||||
|
if (clear_counter == (INDEX_W)'(CACHELINE_COUNT-1)) begin
|
||||||
|
state_next = IDLE;
|
||||||
|
end
|
||||||
|
|
||||||
|
write_data = '0;
|
||||||
|
data_write_enable = '1;
|
||||||
|
|
||||||
|
meta_tag_write_enable = '1;
|
||||||
|
write_meta_tag.tag = '0;
|
||||||
|
write_meta_tag.valid = '0;
|
||||||
|
write_meta_tag.shared = '0;
|
||||||
|
write_meta_tag.clean = '0;
|
||||||
|
|
||||||
|
write_index = clear_counter;
|
||||||
|
|
||||||
|
clear_counter_next = clear_counter + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
IDLE: begin
|
||||||
|
state_next = READY;
|
||||||
|
current_data_next = data_array[index];
|
||||||
|
current_meta_tag_next = meta_tag_array[index];
|
||||||
o_rdy = '1;
|
o_rdy = '1;
|
||||||
end
|
end
|
||||||
|
|
||||||
EVICT: begin
|
READY: begin
|
||||||
|
if (!current_meta_tag.valid || (current_meta_tag.valid && current_meta_tag.tag != tag_d1 && current_meta_tag.clean)) begin
|
||||||
|
// current line is not valid, just read
|
||||||
|
// OR current line is valid, but clean so we don't need to write it back.
|
||||||
|
state_next = READ;
|
||||||
|
|
||||||
|
read_index_next = index_d1;
|
||||||
|
read_offset_next = offset_d1;
|
||||||
|
read_address_next = {i_phys_address[31:OFFSET_W], (OFFSET_W)'('0)};
|
||||||
|
|
||||||
|
latched_we_next = we_d1;
|
||||||
|
latched_data_next = data_d1;
|
||||||
|
|
||||||
|
cache_miss_count_next = cache_miss_count + 1;
|
||||||
|
end else if (current_meta_tag.valid && current_meta_tag.tag != tag_d1 && !current_meta_tag.clean) begin
|
||||||
|
// current line was valid, but the wrong tag.
|
||||||
|
state_next = EVICT;
|
||||||
|
|
||||||
|
read_index_next = index_d1;
|
||||||
|
read_offset_next = offset_d1;
|
||||||
|
read_address_next = {i_phys_address[31:OFFSET_W], (OFFSET_W)'('0)};
|
||||||
|
write_address_next = {current_meta_tag.tag, index_d1, (OFFSET_W)'('0)};
|
||||||
|
|
||||||
|
latched_we_next = we_d1;
|
||||||
|
latched_data_next = data_d1;
|
||||||
|
|
||||||
|
cache_miss_count_next = cache_miss_count + 1;
|
||||||
|
eviction_count_next = eviction_count + 1;
|
||||||
|
end else begin
|
||||||
|
latched_we_next = i_we;
|
||||||
|
latched_data_next = i_data;
|
||||||
|
|
||||||
|
// always be loading the next data array
|
||||||
|
current_data_next = data_array[index];
|
||||||
|
current_meta_tag_next = meta_tag_array[index];
|
||||||
|
|
||||||
|
// We are accessing something we just wrote to
|
||||||
|
|
||||||
|
if (latched_we) begin
|
||||||
|
write_data = current_data;
|
||||||
|
|
||||||
|
write_data[offset_d1*8 +: 8] = latched_data;
|
||||||
|
data_write_enable = '1;
|
||||||
|
|
||||||
|
meta_tag_write_enable = '1;
|
||||||
|
write_meta_tag = current_meta_tag;
|
||||||
|
write_meta_tag.clean = '0;
|
||||||
|
|
||||||
|
write_index = index_d1;
|
||||||
|
|
||||||
|
if (index == write_index) begin
|
||||||
|
current_data_next = write_data;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
// we have a possible RAW hazard, but not after READ state
|
||||||
|
if (prev_state == READY && index_d1 == index_d2) begin
|
||||||
|
o_data = current_data[offset_d1*8 +: 8];
|
||||||
|
end else begin
|
||||||
|
o_data = current_data[offset_d1*8 +: 8];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o_rdy = '1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
EVICT: begin
|
||||||
|
o_cache_addr = write_address;
|
||||||
|
o_cache_cmd = CACHE_WRITE;
|
||||||
|
o_cache_valid = '1;
|
||||||
|
o_cache_data = current_data;
|
||||||
|
|
||||||
|
if (i_cache_rdy) begin
|
||||||
|
state_next = READ;
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
READ: begin
|
READ: begin
|
||||||
|
o_cache_addr = read_address;
|
||||||
|
o_cache_cmd = CACHE_READ;
|
||||||
|
o_cache_valid = '1;
|
||||||
|
|
||||||
|
write_index = read_index;
|
||||||
|
write_data = i_cache_data;
|
||||||
|
write_meta_tag.tag = read_address[31:INDEX_W+OFFSET_W];
|
||||||
|
write_meta_tag.valid = '1;
|
||||||
|
write_meta_tag.shared = '0;
|
||||||
|
write_meta_tag.clean = ~latched_we; // if we are about to write, then mark dirty
|
||||||
|
|
||||||
|
data_write_enable = i_cache_rdy;
|
||||||
|
meta_tag_write_enable = i_cache_rdy;
|
||||||
|
|
||||||
|
if (i_cache_rdy) begin
|
||||||
|
state_next = READY;
|
||||||
|
current_data_next = i_cache_data;
|
||||||
|
current_meta_tag_next = write_meta_tag;
|
||||||
|
|
||||||
|
index = write_index;
|
||||||
|
tag = read_address[31:INDEX_W+OFFSET_W];
|
||||||
|
offset = read_offset;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
default: begin
|
||||||
|
state_next = READY;
|
||||||
end
|
end
|
||||||
endcase
|
endcase
|
||||||
end
|
end
|
||||||
@@ -101,6 +345,20 @@ end
|
|||||||
|
|
||||||
One thing that we also need is an MMU. The TLB can be 1 cycle, then if the TLB
|
One thing that we also need is an MMU. The TLB can be 1 cycle, then if the TLB
|
||||||
says that we are allowed to read from the cache, we can read from the cache.
|
says that we are allowed to read from the cache, we can read from the cache.
|
||||||
|
|
||||||
|
how do we handle writes? Since we take 1 cycle to read from the cache, we cannot
|
||||||
|
immediately write to the cache line in one cycle, we will have to wait a cycle
|
||||||
|
in order to determine if the cacheline is valid or not. To do this, we will need
|
||||||
|
to have it be pipelined, so that we store the data temporarily while we read the
|
||||||
|
meta_tag array, then if its valid we write to the cache. To avoid RAW hazards, we
|
||||||
|
also need to store the address and check if we are reading a value we just wrote.
|
||||||
|
If so, then we return this stored value instead of reading from ram, since we would
|
||||||
|
be reading at the same time as we are writing, and that could be undefined.
|
||||||
|
|
||||||
|
basically if the index matches the previous access, then we have a hazard and need to
|
||||||
|
use the stored cacheline instead of the cacheline we read from memory, since that hasn't
|
||||||
|
been updated yet. We don't need to cache metatag since if we just wrote to it, it will
|
||||||
|
already be dirty anyway.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,4 +10,10 @@ package application_wrapper_cache_pkg;
|
|||||||
logic write_through;
|
logic write_through;
|
||||||
} page_table_entry_t;
|
} page_table_entry_t;
|
||||||
|
|
||||||
|
typedef enum logic [1:0] {
|
||||||
|
CACHE_NONE,
|
||||||
|
CACHE_READ,
|
||||||
|
CACHE_WRITE
|
||||||
|
} cache_cmd_e;
|
||||||
|
|
||||||
endpackage
|
endpackage
|
||||||
Reference in New Issue
Block a user