From 74280fb79adc60064dc429cf702e1ff8d1d20f99 Mon Sep 17 00:00:00 2001 From: Markus Koch Date: Thu, 1 Aug 2024 21:06:14 +0200 Subject: [PATCH] fpga: Implement single-instruction prefetching from external RAM --- fpga/hdl/bench/bench_aps6404l_wb.vhd | 177 +++++++++++++++++++++++++ fpga/hdl/design/aps6404l_wb.vhd | 187 ++++++++++++++++++++++----- fpga/hdl/design/top.vhd | 110 ++++++++++------ 3 files changed, 406 insertions(+), 68 deletions(-) create mode 100644 fpga/hdl/bench/bench_aps6404l_wb.vhd diff --git a/fpga/hdl/bench/bench_aps6404l_wb.vhd b/fpga/hdl/bench/bench_aps6404l_wb.vhd new file mode 100644 index 0000000..d16ba73 --- /dev/null +++ b/fpga/hdl/bench/bench_aps6404l_wb.vhd @@ -0,0 +1,177 @@ +-- -------------------------------------------------------------------------- -- +-- TRASHERNET SoC - A Trashy Ethernet SoC for FPGAs -- +-- -------------------------------------------------------------------------- -- +-- VUnit test bench for the aps6404l Wishbone IF +-- -------------------------------------------------------------------------- -- +-- Author : Markus Koch +-- 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; diff --git a/fpga/hdl/design/aps6404l_wb.vhd b/fpga/hdl/design/aps6404l_wb.vhd index 018c69f..50a1cde 100644 --- a/fpga/hdl/design/aps6404l_wb.vhd +++ b/fpga/hdl/design/aps6404l_wb.vhd @@ -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'; diff --git a/fpga/hdl/design/top.vhd b/fpga/hdl/design/top.vhd index dab827f..09b43e4 100644 --- a/fpga/hdl/design/top.vhd +++ b/fpga/hdl/design/top.vhd @@ -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;