Think about the types of cache patterns you would like to see, and you should be able to code them fairly easily in your testbench using IEEE.MATH_REAL.uniform.
To take your example of sequential access with occasional random jumps:
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.all;
use IEEE.MATH_REAL.all;
entity ...
architecture ...
signal cache_read : std_logic := '0';
signal cache_addr : unsigned(31 downto 0) := (others=>'0');
...
begin
sequential_and_jumps: process
variable seed1, seed2 : positive := 42;
variable rand : real;
variable loops : integer;
variable addr : integer;
begin
cache_read <= '0';
wait until reset='0' and rising_edge(clk);
-- Starting address.
addr := 0;
-- Continual repeated accesses.
loop
-- Randomly jump to a new address between 0x0 and 0x10000
-- with a chance of 10%.
uniform(seed1, seed2, rand);
if(rand < 0.1) then
uniform(seed1, seed2, rand);
addr := integer(trunc(rand * real(16#10000#)));
end if;
-- Wait 0-15 cycles prior to the cache request.
uniform(seed1, seed2, rand);
loops := integer(trunc(rand*16.0));
for i in 1 to loops loop
wait until rising_edge(clk);
end loop;
-- Randomly request 1-32 words.
uniform(seed1, seed2, rand);
loops := integer(trunc(rand*32.0)) + 1;
for i in 1 to loops loop
cache_addr <= to_unsigned(addr, cache_addr'length);
cache_read <= '1';
wait until rising_edge(clk);
cache_read <= '0';
-- Assumes word addresses.
-- For byte addresses, increment by e.g. 4 for 32-bit words.
addr := addr + 1;
end loop;
end loop;
end process;
end architecture;
Other access patterns can be achieved in a similar manner.