fpga: Implement single-instruction prefetching from external RAM

This commit is contained in:
Markus Koch 2024-08-01 21:06:14 +02:00
parent 280b354813
commit 74280fb79a
3 changed files with 406 additions and 68 deletions

View File

@ -0,0 +1,177 @@
-- -------------------------------------------------------------------------- --
-- TRASHERNET SoC - A Trashy Ethernet SoC for FPGAs --
-- -------------------------------------------------------------------------- --
-- VUnit test bench for the aps6404l Wishbone IF
-- -------------------------------------------------------------------------- --
-- Author : Markus Koch <markus@notsyncing.net>
-- Contributors : None
-- License : Mozilla Public License (MPL) Version 2
-- -------------------------------------------------------------------------- --
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library design;
use design.all;
library generics;
use generics.wishbone_pkg.all;
library device_models;
use device_models.all;
library vunit_lib;
context vunit_lib.vunit_context;
entity bench_aps6404l_wb is
generic(
runner_cfg : string := runner_cfg_default
);
end entity bench_aps6404l_wb;
architecture RTL of bench_aps6404l_wb is
signal wbi_o : wishbone_slave_out;
signal wbi_i : wishbone_slave_in;
signal wb_o : wishbone_slave_out;
signal wb_i : wishbone_slave_in;
signal clk : std_logic;
signal rst : std_logic;
signal psram_ce_n : std_logic;
signal psram_sclk : std_logic;
signal psram_sio : std_logic_vector(3 downto 0);
begin
aps6404l_wb_inst : entity design.aps6404l_wb
port map(
clk => clk,
rst => rst,
wbi_o => wbi_o,
wbi_i => wbi_i,
wb_o => wb_o,
wb_i => wb_i,
psram_ce_n => psram_ce_n,
psram_sclk => psram_sclk,
psram_sio => psram_sio
);
aps6404l_inst : entity device_models.aps6404l
port map(
ce_n => psram_ce_n,
sclk => psram_sclk,
sio => psram_sio
);
test : process is
procedure bus_write(
signal master_o : out wishbone_master_out;
signal master_i : in wishbone_master_in;
constant address : in wishbone_address;
constant data : in wishbone_data
) is
begin
info("Writing to address 0x" & to_hstring(address));
master_o.we <= '1';
master_o.adr <= address;
master_o.dat <= data;
master_o.sel <= (others => '1');
master_o.cyc <= '1';
master_o.stb <= '1';
loop
wait until rising_edge(clk);
exit when master_i.ack = '1';
end loop;
master_o.cyc <= '0';
master_o.stb <= '0';
end procedure bus_write;
procedure bus_read(
signal master_o : out wishbone_master_out;
signal master_i : in wishbone_master_in;
constant address : in wishbone_address;
variable data : out wishbone_data
) is
begin
info("Reading from address 0x" & to_hstring(address));
master_o.we <= '0';
master_o.adr <= address;
master_o.sel <= (others => '1');
master_o.cyc <= '1';
master_o.stb <= '1';
loop
wait until rising_edge(clk);
exit when master_i.ack = '1';
end loop;
data := master_i.dat;
master_o.cyc <= '0';
master_o.stb <= '0';
end procedure bus_read;
procedure readcheck(signal master_o : out wishbone_master_out;
signal master_i : in wishbone_master_in;
constant address : in wishbone_address;
constant expected : in wishbone_data) is
variable data : wishbone_data;
variable time_start : time;
begin
time_start := now;
bus_read(wbi_i, wbi_o, address, data);
info("Read data: 0x" & to_hstring(data) & " after " & time'image(now - time_start));
check(data = expected, "Data readback incorrect.");
end procedure readcheck;
variable data : wishbone_data;
begin
test_runner_setup(runner, runner_cfg);
wbi_i.sel <= (others => '1');
wbi_i.cyc <= '0';
wbi_i.stb <= '0';
wb_i.cyc <= '0';
wb_i.stb <= '0';
while test_suite loop
info("Resetting DUT");
rst <= '1';
wait for 30 ns;
rst <= '0';
if run("data") then
bus_write(wb_i, wb_o, x"00000000", x"12345678");
bus_read(wb_i, wb_o, x"00000000", data);
info("Read data: 0x" & to_hstring(data));
check(data = x"12345678", "Data readback incorrect.");
wait for 500 ns;
elsif run("instruction") then
bus_write(wb_i, wb_o, x"00000000", x"11223344");
bus_write(wb_i, wb_o, x"00000004", x"55667788");
bus_write(wb_i, wb_o, x"00000008", x"99aabbcc");
bus_write(wb_i, wb_o, x"0000000c", x"ddeeff00");
wait for 1 us;
readcheck(wbi_i, wbi_o, x"00000000", x"11223344");
wait for 1 us;
readcheck(wbi_i, wbi_o, x"00000004", x"55667788");
wait for 1 us;
readcheck(wbi_i, wbi_o, x"00000008", x"99aabbcc");
wait for 1 us;
readcheck(wbi_i, wbi_o, x"0000000c", x"ddeeff00");
wait for 1 us;
end if;
end loop;
test_runner_cleanup(runner);
end process test;
test_runner_watchdog(runner, 1 ms);
clock_driver : process
constant period : time := 20 ns;
begin
clk <= '0';
wait for period / 2;
clk <= '1';
wait for period / 2;
end process clock_driver;
end architecture RTL;

View File

@ -8,6 +8,9 @@
-- License : Mozilla Public License (MPL) Version 2
-- -------------------------------------------------------------------------- --
-- TODO: By default, some (random) value is placed in cached_address, which may corrupt data if this happens to be the
-- first address accessesd after reset.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
@ -20,6 +23,10 @@ entity aps6404l_wb is
clk : in std_logic;
rst : in std_logic; --
-- Instruction Wishbone IF (32b read access only, caches one instruction ahead)
wbi_o : out wishbone_slave_out; -- Instruction Wishbone bus (out)
wbi_i : in wishbone_slave_in; -- Instruction Wishbone bus (in)
-- Wishbone IF
wb_o : out wishbone_slave_out; -- Wishbone bus (out)
wb_i : in wishbone_slave_in; -- Wishbone bus (in)
@ -32,49 +39,165 @@ entity aps6404l_wb is
end entity aps6404l_wb;
architecture rtl of aps6404l_wb is
signal apsc_addr : std_logic_vector(23 downto 0);
signal apsc_data_in : std_logic_vector(7 downto 0);
signal apsc_data_in_next : std_logic;
signal apsc_data_out : std_logic_vector(7 downto 0);
signal apsc_data_out_valid : std_logic;
signal apsc_write_en : std_logic;
signal apsc_read_en : std_logic;
type apsc_in is record
addr : std_logic_vector(23 downto 0);
data_in : std_logic_vector(7 downto 0);
write_en : std_logic;
read_en : std_logic;
end record apsc_in;
type apsc_out is record
data_in_next : std_logic;
data_out : std_logic_vector(7 downto 0);
data_out_valid : std_logic;
end record apsc_out;
signal apsc_i : apsc_in;
signal apsc_o : apsc_out;
signal cached_address : std_logic_vector(apsc_i.addr'range);
signal cached_data : wishbone_data;
signal api_i : apsc_in;
signal api_o : apsc_out;
signal api_request_count : integer range 0 to 2;
signal api_bit_cnt : integer range 0 to 3;
signal apd_i : apsc_in;
signal apd_o : apsc_out;
signal bit_cnt : integer range 0 to 4;
signal data : std_logic_vector(wb_i.dat'range);
signal ap_sel : integer range 0 to 1;
begin
aps6404l_controller_inst : entity work.aps6404l_controller
port map(
clk => clk,
rst => rst,
addr => apsc_addr,
data_in => apsc_data_in,
data_in_next => apsc_data_in_next,
data_out => apsc_data_out,
data_out_valid => apsc_data_out_valid,
write_en => apsc_write_en,
read_en => apsc_read_en,
addr => apsc_i.addr,
data_in => apsc_i.data_in,
data_in_next => apsc_o.data_in_next,
data_out => apsc_o.data_out,
data_out_valid => apsc_o.data_out_valid,
write_en => apsc_i.write_en,
read_en => apsc_i.read_en,
psram_ce_n => psram_ce_n,
psram_sclk => psram_sclk,
psram_sio => psram_sio
);
ap_arb : process(clk, rst) is
begin
if rst then
ap_sel <= 0;
elsif rising_edge(clk) then
if ap_sel = 0 then
if not (api_i.read_en or api_i.read_en) and (apd_i.read_en or apd_i.write_en) then
ap_sel <= 1;
end if;
else
if (apd_i.read_en nor apd_i.write_en) then -- lock until APD releases
ap_sel <= 0;
end if;
end if;
end if;
end process ap_arb;
ap_mux : process(all) is
constant APSC_IN_DEFAULT : apsc_in := (
addr => (others => '-'),
data_in => (others => '-'),
write_en => '0',
read_en => '0'
);
constant APSC_OUT_DEFAULT : apsc_out := (
data_in_next => '0',
data_out => (others => '-'),
data_out_valid => '0'
);
begin
apsc_i <= APSC_IN_DEFAULT;
api_o <= APSC_OUT_DEFAULT;
apd_o <= APSC_OUT_DEFAULT;
if ap_sel = 0 then
api_o <= apsc_o;
apsc_i <= api_i;
else
apd_o <= apsc_o;
apsc_i <= apd_i;
end if;
end process ap_mux;
instruction_memory_p : process(clk, rst) is
begin
if rst then
wbi_o.ack <= '0';
cached_address <= (others => '1'); -- TODO: Set cached_address to 0xFFFFFF
api_request_count <= 0;
elsif rising_edge(clk) then
wbi_o.ack <= '0';
if wbi_i.cyc and wbi_i.stb then
if wbi_i.adr(cached_address'range) = cached_address then -- cache hit, return current immediately, retrieve next in meantime
wbi_o.dat <= cached_data;
wbi_o.ack <= '1';
api_i.addr <= std_logic_vector(unsigned(wbi_i.adr(apsc_i.addr'range)) + 4);
if not api_i.read_en then -- If we haven't yet triggered the retrieve from memory, start it now
api_request_count <= 1;
end if;
else -- cache miss, retrieve current (return asap) + next
if not api_i.read_en then -- If we haven't yet triggered the retrieve from memory, start it now
api_i.addr <= wbi_i.adr(apsc_i.addr'range);
api_request_count <= 2;
end if;
end if;
end if;
if api_o.data_out_valid then
cached_data <= api_o.data_out & cached_data(cached_data'high downto 8); -- TODO: this assumes the CPU quickly processes the ACK (within one SPI byte)
if api_bit_cnt = 3 then
cached_address <= api_i.addr;
if api_request_count > 0 then
api_request_count <= api_request_count - 1;
end if;
api_bit_cnt <= 0;
else
api_bit_cnt <= api_bit_cnt + 1;
end if;
end if;
if not api_i.read_en then
api_bit_cnt <= 0;
end if;
end if;
end process instruction_memory_p;
api_i.read_en <= '1' when api_request_count > 0 else '0';
api_i.write_en <= '0';
wbi_o.err <= '0';
wbi_o.rty <= '0';
wbi_o.stall <= not wbi_o.ack;
main : process(clk, rst) is
variable start_addr : integer range 0 to 3;
variable stop_addr : integer range 0 to 3;
variable success : std_logic;
variable we : std_logic;
begin
if rst then
wb_o.ack <= '0';
bit_cnt <= 0;
wb_o.ack <= '0';
bit_cnt <= 0;
apd_i.read_en <= '0';
apd_i.write_en <= '0';
elsif rising_edge(clk) then
wb_o.ack <= '0';
if bit_cnt = 0 then
apsc_read_en <= '0';
apsc_write_en <= '0';
apd_i.read_en <= '0';
apd_i.write_en <= '0';
start_addr := 0;
for i in 0 to 3 loop
@ -91,35 +214,39 @@ begin
end if;
end loop;
apsc_addr <= wb_i.adr(apsc_addr'length - 1 downto 2) & std_logic_vector(to_unsigned(start_addr, 2));
data <= wb_i.dat;
apd_i.addr <= wb_i.adr(apsc_i.addr'length - 1 downto 2) & std_logic_vector(to_unsigned(start_addr, 2));
data <= wb_i.dat;
we := wb_i.we;
if not wb_o.ack and wb_i.stb and wb_i.cyc then
bit_cnt <= 4;
if wb_i.we then
wb_o.ack <= '1'; -- We can ack a write immediately
end if;
end if;
else
success := '0';
if bit_cnt >= (4 - stop_addr) and bit_cnt <= (4 - start_addr) then
apsc_read_en <= not wb_i.we;
apsc_write_en <= wb_i.we;
apd_i.read_en <= not we;
apd_i.write_en <= we;
else
apsc_read_en <= '0';
apsc_write_en <= '0';
success := '1';
apd_i.read_en <= '0';
apd_i.write_en <= '0';
success := '1';
end if;
if wb_i.we and apsc_data_in_next then
if we and apd_o.data_in_next then
success := '1';
elsif not wb_i.we and apsc_data_out_valid then
elsif not we and apd_o.data_out_valid then
success := '1';
end if;
if success then
bit_cnt <= bit_cnt - 1;
wb_o.dat <= apsc_data_out & wb_o.dat(wb_o.dat'high downto 8);
wb_o.dat <= apd_o.data_out & wb_o.dat(wb_o.dat'high downto 8);
data <= x"--" & data(data'high downto 8);
if bit_cnt = 1 then
if (bit_cnt = 1) and (we = '0') then -- We only need to ack reads, writes were already acked in bit_cnt = 0
wb_o.ack <= '1';
end if;
end if;
@ -127,7 +254,7 @@ begin
end if;
end process main;
apsc_data_in <= data(apsc_data_in'range);
apd_i.data_in <= data(apsc_i.data_in'range);
wb_o.err <= '0';
wb_o.rty <= '0';

View File

@ -75,42 +75,62 @@ architecture rtl of top is
-- System Timer
signal irq_timer : std_logic;
-- # Instruction Bus
-- Wishbone interconnect (master)
signal wb_masters_o : wishbone_slave_in_vector(1 downto 0);
signal wb_masters_i : wishbone_slave_out_vector(wb_masters_o'range);
alias wbi_o is wb_masters_o(0);
alias wbi_i is wb_masters_i(0);
alias wbd_o is wb_masters_o(1);
alias wbd_i is wb_masters_i(1);
signal wbi_masters_o : wishbone_slave_in_vector(0 downto 0);
signal wbi_masters_i : wishbone_slave_out_vector(wbi_masters_o'range);
alias wbi_o is wbi_masters_o(0);
alias wbi_i is wbi_masters_i(0);
-- Wishbone interconnect (slave)
signal wb_slaves_o : wishbone_master_in_vector(4 downto 0);
signal wb_slaves_i : wishbone_master_out_vector(wb_slaves_o'range);
alias wb_rom_o is wb_slaves_o(0);
alias wb_rom_i is wb_slaves_i(0);
alias wb_ram_o is wb_slaves_o(1);
alias wb_ram_i is wb_slaves_i(1);
alias wb_timer_o is wb_slaves_o(2);
alias wb_timer_i is wb_slaves_i(2);
alias wb_uart_o is wb_slaves_o(3);
alias wb_uart_i is wb_slaves_i(3);
alias wb_eth_o is wb_slaves_o(4);
alias wb_eth_i is wb_slaves_i(4);
signal wbi_slaves_o : wishbone_master_in_vector(1 downto 0);
signal wbi_slaves_i : wishbone_master_out_vector(wbi_slaves_o'range);
alias wbi_rom_o is wbi_slaves_o(0);
alias wbi_rom_i is wbi_slaves_i(0);
alias wbi_ram_o is wbi_slaves_o(1);
alias wbi_ram_i is wbi_slaves_i(1);
-- Slave address map
constant wishbone_addresses : wishbone_address_vector := (
constant wishbone_instruction_addresses : wishbone_address_vector := (
0 => x"00000000", -- Boot ROM
1 => x"40000000", -- RAM
2 => x"80000000", -- Timer
3 => x"81000000", -- UART
4 => x"82000000" -- Eth
1 => x"40000000" -- RAM
);
constant wishbone_masks : wishbone_address_vector := (
0 => x"FF000000", -- Boot ROM: 256b
1 => x"FF000000", -- RAM: Max 16M
2 => x"FF000000", -- Timer
3 => x"FF000000", -- UART
4 => x"FF000000" -- Eth
constant wishbone_instruction_masks : wishbone_address_vector := (
0 => x"C0000000", -- Boot ROM
1 => x"C0000000" -- RAM
);
-- # Data Bus
-- Wishbone interconnect (master)
signal wbd_masters_o : wishbone_slave_in_vector(0 downto 0);
signal wbd_masters_i : wishbone_slave_out_vector(wbd_masters_o'range);
alias wbd_o is wbd_masters_o(0);
alias wbd_i is wbd_masters_i(0);
-- Wishbone interconnect (slave)
signal wbd_slaves_o : wishbone_master_in_vector(3 downto 0);
signal wbd_slaves_i : wishbone_master_out_vector(wbd_slaves_o'range);
alias wb_ram_o is wbd_slaves_o(0);
alias wb_ram_i is wbd_slaves_i(0);
alias wb_timer_o is wbd_slaves_o(1);
alias wb_timer_i is wbd_slaves_i(1);
alias wb_uart_o is wbd_slaves_o(2);
alias wb_uart_i is wbd_slaves_i(2);
alias wb_eth_o is wbd_slaves_o(3);
alias wb_eth_i is wbd_slaves_i(3);
-- Slave address map
constant wishbone_data_addresses : wishbone_address_vector := (
0 => x"40000000", -- RAM
1 => x"80000000", -- Timer
2 => x"81000000", -- UART
3 => x"82000000" -- Eth
);
constant wishbone_data_masks : wishbone_address_vector := (
0 => x"FF000000", -- RAM: Max 16M
1 => x"FF000000", -- Timer
2 => x"FF000000", -- UART
3 => x"FF000000" -- Eth
);
constant IN_SIMULATION : boolean := false --
@ -199,14 +219,16 @@ begin
port map(
clk => clk,
clr => clr,
wb_o => wb_rom_o,
wb_i => wb_rom_i
wb_o => wbi_rom_o,
wb_i => wbi_rom_i
);
aps6404l_wb_inst : entity work.aps6404l_wb
port map(
clk => clk,
rst => rst,
wbi_o => wbi_ram_o,
wbi_i => wbi_ram_i,
wb_o => wb_ram_o,
wb_i => wb_ram_i,
psram_ce_n => psram_ce_n,
@ -257,16 +279,28 @@ begin
eth_tx_p <= (others => eth_tx_p_i);
eth_tx_n <= (others => eth_tx_n_i);
wishbone_crossbar_inst : entity generics.wishbone_arbiter
wishbone_crossbar_data_inst : entity generics.wishbone_arbiter
port map(
clk => clk,
rst => rst,
masters_o => wb_masters_o,
masters_i => wb_masters_i,
slaves_o => wb_slaves_o,
slaves_i => wb_slaves_i,
address => wishbone_addresses,
mask => wishbone_masks
masters_o => wbd_masters_o,
masters_i => wbd_masters_i,
slaves_o => wbd_slaves_o,
slaves_i => wbd_slaves_i,
address => wishbone_data_addresses,
mask => wishbone_data_masks
);
wishbone_crossbar_instruction_inst : entity generics.wishbone_arbiter
port map(
clk => clk,
rst => rst,
masters_o => wbi_masters_o,
masters_i => wbi_masters_i,
slaves_o => wbi_slaves_o,
slaves_i => wbi_slaves_i,
address => wishbone_instruction_addresses,
mask => wishbone_instruction_masks
);
eth_led_green_n <= not eth_led_green;