library ieee; use ieee.std_logic_1164.all; library vunit_lib; context vunit_lib.vunit_context; context vunit_lib.com_context; use work.i2c_bus_pkg.all; entity i2c_bus_mod is generic ( inst_name : string; default_scl_freq: real); port ( sda_io : inout std_logic; scl_io : inout std_logic; clk_i : in std_logic; scl_rising_o : out std_logic; scl_falling_o : out std_logic); end entity i2c_bus_mod; architecture behav of i2c_bus_mod is constant logger : logger_t := get_logger("i2c_bus_mod::" & inst_name); constant checker : checker_t := new_checker(logger); function get_period ( constant frequency : in real) return time is begin -- procedure get_period return (1_000_000_000.0 / frequency) * 1 ns; end function get_period; signal s_free_sda_req : event_t := new_event(inst_name & "free sda"); signal s_free_scl_req : event_t := new_event(inst_name & "free scl"); signal s_start_cond_req : event_t := new_event(inst_name & "start cond gen"); signal s_start_cond_done : event_t := new_event(inst_name & "start cond done"); signal s_stop_cond_req : event_t := new_event(inst_name & "stop cond gen"); signal s_stop_cond_done : event_t := new_event(inst_name & "stop cond done"); signal s_check_data_req : event_t := new_event(inst_name & "check data req"); signal s_check_data_done : event_t := new_event(inst_name & "check data done"); signal s_data_req : event_t := new_event(inst_name & "gen data"); signal s_data_done : event_t := new_event(inst_name & "data done"); signal s_clk_req : event_t := new_event(inst_name & "gen clk"); signal s_clk_done : event_t := new_event(inst_name & "clk done"); signal s_scl_frequency : real := default_scl_freq; signal s_scl_period : time := get_period(default_scl_freq); signal s_auto_ack_req : event_t := new_event(inst_name & "auto ack"); signal s_auto_ack_address : std_logic_vector(6 downto 0); signal s_auto_ack_active : boolean; signal s_auto_ack_count : natural; signal s_data_count : natural; signal s_data : std_logic_vector(1023 downto 0); signal s_clk_count : natural; signal s_clk_start : std_logic; signal s_timeout : time; signal s_byte_sent : event_t := new_event(inst_name & "byte sent"); signal s_start_cond : event_t := new_event(inst_name & "start cond"); signal s_bus_busy : std_logic; signal s_read_byte : std_logic_vector(7 downto 0); signal s_bits_since_start_cond : natural := 0; procedure wait_for_start ( signal sda : inout std_logic; signal scl : inout std_logic; variable timeout : inout time) is constant v_now : time := now; begin wait until scl = 'H' and falling_edge(sda) for timeout; if scl /= 'H' or not falling_edge(sda) then failure(logger, "Timed out when waiting for start condition!"); else timeout := timeout - (now - v_now); end if; end procedure wait_for_start; procedure wait_for_stop ( signal sda : inout std_logic; signal scl : inout std_logic; variable timeout : inout time) is constant v_now : time := now; begin wait until scl = 'H' and rising_edge(sda) for timeout; if scl /= 'H' or not rising_edge(sda) then error(logger, "Timed out when waiting for stop condition!"); else timeout := timeout - (now - v_now); end if; end procedure wait_for_stop; procedure wait_for_clock ( signal sda : inout std_logic; signal scl : inout std_logic; variable timeout : inout time) is variable v_now : time := now; begin -- TODO: start or stop condition? wait until rising_edge(scl) for timeout; if not rising_edge(scl) then error(logger, "Timed out when waiting for clock!"); else timeout := timeout - (now - v_now); end if; end procedure wait_for_clock; procedure write_bit ( signal sda : inout std_logic; signal scl : inout std_logic; constant data : in std_logic; variable timeout : inout time; variable continue : inout boolean) is variable v_now : time := now; begin if scl = 'H' then wait until (sda'event and scl = 'H') or falling_edge(scl) for timeout; end if; if sda'event and scl = 'H' then wait for 0 ns; error(logger, "Got start or stop condition when trying to write a bit!"); continue := false; return; elsif falling_edge(scl) then -- nop wait for s_scl_period / 4; timeout := timeout - (now - v_now); elsif scl = '0' then else error(logger, "Timed out when waiting for falling edge of scl to write a bit!"); continue := false; -- timeout return; end if; if data = '1' then sda <= 'Z'; else sda <= data; end if; -- wait for next clock wait until (sda'event and scl = 'H') or falling_edge(scl) for timeout; if sda'event and scl = 'H' then wait for 0 ns; error(logger, "Got start or stop condition when trying to write a bit!"); continue := false; sda <= 'Z'; elsif falling_edge(scl) then wait for s_scl_period / 4; sda <= 'Z'; else continue := false; error(logger, "Could not get rising edge of scl to write data."); end if; end procedure write_bit; procedure read_bit ( signal sda : inout std_logic; signal scl : inout std_logic; variable data : out std_logic; variable timeout : inout time; variable continue : inout boolean) is variable v_now : time := now; begin if not continue then data := 'X'; return; end if; wait until (sda'event and scl = 'H') or rising_edge(scl) for timeout; if sda'event and scl = 'H' then -- start / stop condition. Cannot read further. error(logger, "Got start or stop condition when trying to read a bit!"); continue := false; data := 'X'; return; elsif rising_edge(scl) then if sda = 'H' then data := '1'; return; end if; timeout := timeout - (now - v_now); data := sda; return; else -- timeout error(logger, "Timed out when waiting for rising edge to read a bit!"); continue := false; end if; end procedure read_bit; procedure read_data ( signal sda : inout std_logic; signal scl : inout std_logic; constant count : in natural; variable data : out std_logic_vector; variable timeout : inout time; variable continue : inout boolean) is begin for i in (count - 1) downto 0 loop read_bit(sda, scl, data(i), timeout, continue); if not continue then return; end if; end loop; -- i end procedure read_data; procedure process_disconnect_req ( signal evnt : inout event_t; signal sda : inout std_logic; signal scl : inout std_logic) is variable scl_freed : boolean := false; variable sda_freed : boolean := false; begin while not scl_freed or not sda_freed loop if is_active(s_free_sda_req) then sda_freed := true; sda <= 'Z'; end if; if is_active(s_free_scl_req) then scl_freed := true; scl <= 'Z'; end if; if is_active(evnt) then exit; end if; if not scl_freed or not sda_freed then wait until is_active(s_free_scl_req) or is_active(s_free_sda_req) or is_active(evnt); end if; end loop; end procedure process_disconnect_req; procedure req_disconnect( signal free_sda_req: inout event_t; signal free_scl_req: inout event_t) is begin notify(free_sda_req); notify(free_scl_req); end procedure req_disconnect; procedure dc( signal sda : inout std_logic; signal scl : inout std_logic) is begin -- procedure disconnect sda <= 'Z'; scl <= 'Z'; end procedure dc; begin -- architecture behav scl_io <= 'H'; sda_io <= 'H'; start_cond_gen: process is begin -- process start_cond_gen dc(sda_io, scl_io); if not is_active(s_start_cond_req) then wait until is_active(s_start_cond_req); end if; info(logger, "Initiating communication on the bus by generating a start condition."); check(checker, sda_io = 'H', "SDA cannot be low to generate a start condition!", level => failure); check(checker, scl_io = 'H', "SCL cannot be low to generate a start condition!", level => failure); req_disconnect(s_free_sda_req, s_free_scl_req); sda_io <= '0'; wait until scl_io'event for s_scl_period / 4; scl_io <= '0'; wait for s_scl_period / 4; info(logger, "Start condition generated."); notify(s_start_cond_done); process_disconnect_req(s_start_cond_req, sda_io, scl_io); end process start_cond_gen; stop_cond_gen: process is variable v_timeout : time; begin -- process stop_cond_gen dc(sda_io, scl_io); if not is_active(s_stop_cond_req) then wait until is_active(s_stop_cond_req); end if; v_timeout := s_timeout; check(checker, scl_io = '0', "SCL cannot be high to generate a stop condition!", level => failure); scl_io <= '0'; sda_io <= '0'; req_disconnect(s_free_sda_req, s_free_scl_req); info(logger, "Stopping communication on the bus by generating a stop condition."); wait for s_scl_period / 4; scl_io <= 'Z'; wait until scl_io = 'H' for v_timeout; if scl_io /= 'H' then error(logger, "Timed out when waiting for scl rising edge to generate a stop condition!"); end if; wait for s_scl_period / 4; sda_io <= 'Z'; wait until sda_io = 'H' for v_timeout; if sda_io /= 'H' then error(logger, "Timed out when waiting for sda rising edge to generate a stop condition!"); end if; wait for s_scl_period / 4; info(logger, "Stop condition generated."); notify(s_stop_cond_done); -- already freed... -- process_disconnect_req(sda_io, scl_io); end process stop_cond_gen; data_gen: process is variable v_timeout : time; variable v_position : natural; variable v_continue : boolean; variable v_data : std_logic_vector(1023 downto 0); variable v_count : natural; begin -- process data_gen dc(sda_io, scl_io); if not is_active(s_data_req) then wait until is_active(s_data_req); end if; v_timeout := s_timeout; v_continue := true; v_data := s_data; v_count := s_data_count; notify(s_free_sda_req); v_position := 0; while v_continue and v_position < v_count loop write_bit(sda_io, scl_io, v_data(v_count - 1 - v_position), v_timeout, v_continue); v_position := v_position + 1; end loop; if v_continue and v_count = 8 then info(logger, "Sent I2C frame of data."); elsif v_count = 8 then warning(logger, "Could not send requested data to the bus."); end if; if s_bits_since_start_cond <= 8 and v_count = 8 then -- send address and rw info(logger, "Sent I2C Frame: " & to_string(v_data(v_count - 1 downto 0))); info(logger, " Slave Address: " & to_string(v_data(7 downto 1))); info(logger, " RW: " & to_string(v_data(0))); elsif v_count = 8 then -- sent just data info(logger, "I2C Frame: " & to_string(v_data(v_count - 1 downto 0))); end if; if v_count = 1 and s_bits_since_start_cond mod 9 = 8 then if v_continue then info(logger, "Sent ACK."); else warning(logger, "Could not send ACK!"); end if; end if; notify(s_data_done); wait until is_active(s_free_sda_req) or is_active(s_data_req); sda_io <= 'Z'; end process data_gen; clk_gen: process is variable v_start : std_logic; variable v_continue : boolean; variable v_count : natural; variable v_position : natural; variable v_timeout : time; variable v_now : time; begin -- process clk_gen dc(sda_io, scl_io); if not is_active(s_clk_req) then wait until is_active(s_clk_req); end if; v_count := s_clk_count; v_timeout := s_timeout; v_start := s_clk_start; v_now := now; scl_io <= v_start; notify(s_free_scl_req); if v_start = 'H' then if scl_io /= 'H' then wait until scl_io = 'H' for v_timeout; end if; if scl_io /= 'H' then error(logger, "Timed out when waiting for scl rising edge to generate clock."); end if; wait until scl_io /= 'H' for s_scl_period / 2; if scl_io /= 'H' then error(logger, "Got instability at scl when generating clock!"); end if; scl_io <= '0'; wait for s_scl_period / 4; end if; v_position := 0; v_continue := true; while v_continue and v_position < v_count loop wait for s_scl_period / 4; scl_io <= 'Z'; if scl_io /= 'H' then wait until scl_io = 'H' for v_timeout; end if; if scl_io /= 'H' then error(logger, "Timed out when waiting for scl rising edge to generate clock."); end if; wait until scl_io /= 'H' for s_scl_period / 2; if scl_io /= 'H' then error(logger, "Got instability at scl when generating clock!"); end if; scl_io <= '0'; wait for s_scl_period / 4; v_position := v_position + 1; end loop; notify(s_clk_done); wait until is_active(s_free_scl_req) or is_active(s_clk_req); end process clk_gen; data_checker: process is variable v_timeout : time; variable v_position : natural; variable v_continue : boolean; variable v_exp_data : std_logic_vector(1023 downto 0); variable v_read_data : std_logic_vector(1023 downto 0); variable v_count : natural; begin -- process data_checker dc(sda_io, scl_io); if not is_active(s_check_data_req) then wait until is_active(s_check_data_req); end if; v_timeout := s_timeout; v_exp_data := s_data; v_count := s_data_count; v_position := 0; v_continue := true; while v_continue and v_position < v_count loop read_bit(sda_io, scl_io, v_read_data(v_count - 1 - v_position), v_timeout, v_continue); v_position := v_position + 1; end loop; if v_continue and v_count = 8 then info(logger, "Received I2C frame of data."); elsif v_count = 8 then warning(logger, "Could not receive requested data on the bus."); end if; if v_exp_data(v_count - 1 downto 0) = v_read_data(v_count - 1 downto 0) then info(logger, "Got expected data."); else error(logger, "Received data do not match expected data."); end if; if s_bits_since_start_cond <= 8 and v_count = 8 then -- send address and rw info(logger, "Expected I2C Frame: " & to_string(v_exp_data(v_count - 1 downto 0))); info(logger, " Slave Address: " & to_string(v_exp_data(7 downto 1))); info(logger, " RW: " & to_string(v_exp_data(0))); info(logger, "Received I2C Frame: " & to_string(v_read_data(v_count - 1 downto 0))); info(logger, " Slave Address: " & to_string(v_read_data(7 downto 1))); info(logger, " RW: " & to_string(v_read_data(0))); elsif v_count = 8 then -- sent just data info(logger, "Received I2C Frame: " & to_string(v_read_data(v_count - 1 downto 0))); info(logger, "Expected I2C Frame: " & to_string(v_exp_data(v_count - 1 downto 0))); end if; if v_count = 1 and s_bits_since_start_cond mod 9 = 8 then if v_continue then if v_exp_data(0) = '0' then if v_read_data(0) = '0' then info(logger, "Received ACK."); else error(logger, "Received NACK instead of ACK."); end if; else if v_read_data(0) = '0' then error(logger, "Received unexpected ACK."); else info(logger, "Received expected NACK."); end if; end if; else warning(logger, "Could not receive ACK!"); end if; end if; notify(s_check_data_done); end process data_checker; gen_rising_pulse: process is begin -- process gen_rising_pulse dc(sda_io, scl_io); scl_rising_o <= '0'; wait until rising_edge(scl_io); scl_rising_o <= '1'; wait until rising_edge(clk_i); wait until falling_edge(clk_i); end process gen_rising_pulse; gen_falling_pulse: process is begin -- process gen_rising_pulse dc(sda_io, scl_io); scl_falling_o <= '0'; wait until falling_edge(scl_io); scl_falling_o <= '1'; wait until rising_edge(clk_i); wait until falling_edge(clk_i); end process gen_falling_pulse; auto_ack_gen: process is variable v_address : std_logic_vector(6 downto 0); variable v_rw : std_logic; variable v_continue : boolean; variable v_timeout : time; begin -- process auto_ack_gen dc(sda_io, scl_io); if not s_auto_ack_active then wait until is_active(s_auto_ack_req); end if; wait until is_active(s_byte_sent) or is_active(s_auto_ack_req); if is_active(s_byte_sent) and s_auto_ack_active then if s_bits_since_start_cond = 8 then v_address := s_read_byte(7 downto 1); v_rw := s_read_byte(0); end if; v_timeout := time'high; v_continue := true; if v_address = s_auto_ack_address and v_rw = '0' then write_bit(sda_io, scl_io, '0', v_timeout, v_continue); info(logger, "Sent automatic ACK as " & to_string(v_address)); write_bit(sda_io, scl_io, '1', v_timeout, v_continue); elsif v_address = "0000000" and v_rw = '1' and s_bits_since_start_cond > 8 then write_bit(sda_io, scl_io, '0', v_timeout, v_continue); info(logger, "Sent automatic ACK as master."); write_bit(sda_io, scl_io, '1', v_timeout, v_continue); end if; end if; end process auto_ack_gen; bit_counter: process is variable v_started : boolean; variable v_read : std_logic; variable v_timeout : time; begin -- process bit_counter dc(sda_io, scl_io); if not v_started then v_timeout := time'high; wait_for_start(sda_io, scl_io, v_timeout); v_started := true; notify(s_start_cond); s_bus_busy <= '1'; s_bits_since_start_cond <= 0; end if; wait until (sda_io'event and scl_io = 'H') or rising_edge(scl_io); if rising_edge(scl_io) then if sda_io = 'H' then v_read := '1'; else v_read := sda_io; end if; s_read_byte <= s_read_byte(6 downto 0) & v_read; s_bits_since_start_cond <= s_bits_since_start_cond + 1; wait for 0 ns; if s_bits_since_start_cond mod 8 = 0 then notify(s_byte_sent); end if; else s_bits_since_start_cond <= 0; wait for 0 ns; if sda_io = 'H' then v_started := false; s_bus_busy <= '0'; else notify(s_start_cond); end if; end if; end process bit_counter; message_handler: process is constant self : actor_t := new_actor(inst_name); variable msg : msg_t; variable msg_type : msg_type_t; variable v_frequency : real; variable v_count : natural; variable v_timeout : time; begin -- process message_handler dc(sda_io, scl_io); receive(net, self, msg); msg_type := message_type(msg); if msg_type = free_bus_msg then wait for s_scl_period / 4; req_disconnect(s_free_sda_req, s_free_scl_req); elsif msg_type = set_scl_freq_msg then v_frequency := pop(msg); s_scl_frequency <= v_frequency; s_scl_period <= get_period(v_frequency); elsif msg_type = gen_start_cond_msg then s_timeout <= pop(msg); notify(s_start_cond_req); wait until is_active(s_start_cond_done); elsif msg_type = gen_stop_cond_msg then s_timeout <= pop(msg); notify(s_stop_cond_req); wait until is_active(s_stop_cond_done); elsif msg_type = gen_clocks_msg then s_clk_start <= scl_io; s_clk_count <= pop(msg); s_timeout <= pop(msg); notify(s_clk_req); wait until is_active(s_clk_done); elsif msg_type = send_data_msg then s_data_count <= pop(msg); s_data <= pop(msg); s_timeout <= pop(msg); notify(s_data_req); wait until is_active(s_data_done); elsif msg_type = send_data_clocks_msg then s_clk_start <= scl_io; v_count := pop(msg); s_data_count <= v_count; s_clk_count <= v_count; s_data <= pop(msg); s_timeout <= pop(msg); notify(s_clk_req, s_data_req); wait until is_active(s_clk_done); elsif msg_type = auto_ack_msg then s_auto_ack_active <= pop(msg); s_auto_ack_address <= pop(msg); s_auto_ack_count <= pop(msg); notify(s_auto_ack_req); elsif msg_type = wait_start_cond_msg then req_disconnect(s_free_sda_req, s_free_scl_req); v_timeout := pop(msg); wait_for_start(sda_io, scl_io, v_timeout); elsif msg_type = wait_stop_cond_msg then req_disconnect(s_free_sda_req, s_free_scl_req); v_timeout := pop(msg); wait_for_stop(sda_io, scl_io, v_timeout); elsif msg_type = wait_clocks_msg then req_disconnect(s_free_sda_req, s_free_scl_req); v_count := pop(msg); v_timeout := pop(msg); for i in 1 to v_count loop wait_for_clock(sda_io, scl_io, v_timeout); end loop; -- i elsif msg_type = check_data_msg then req_disconnect(s_free_sda_req, s_free_scl_req); s_data_count <= pop(msg); s_data <= pop(msg); s_timeout <= pop(msg); notify(s_check_data_req); wait until is_active(s_check_data_done); elsif msg_type = check_data_clocks_msg then s_clk_start <= scl_io; notify(s_free_sda_req); v_count := pop(msg); s_data_count <= v_count; s_clk_count <= v_count; s_data <= pop(msg); s_timeout <= pop(msg); notify(s_clk_req); notify(s_check_data_req); wait until is_active(s_clk_done); elsif msg_type = wait_until_idle_msg then acknowledge(net, msg, true); end if; end process message_handler; end architecture behav;