代码参考网上大神程序,加了一些修改 贴出代码:
写了个testbench测试了一下这个模块,仿真时许如下:
library ieee; use ieee.std_logic_1164.all; entity iic_master is generic ( clock_frequency : positive := 100000000; baud : positive := 100000 ); port ( i_clk : in std_logic; i_rst_n : in std_logic; i_addr : in std_logic_vector(6 downto 0); --目标地址 i_rw : in std_logic; --'0' 写, '1' 读 i_data_wr : in std_logic_vector(7 downto 0); --要写到slave的数据 o_data_rd : out std_logic_vector(7 downto 0); --从slave中读回的数据 i_trans_en : in std_logic; --启动传输 o_busy : out std_logic; --正在传输 b_ack_error : buffer std_logic; --从机的应答信号 io_sda : inout std_logic; --串行数据 io_scl : inout std_logic --串行时钟 ); end entity iic_master; architecture behave of iic_master is type machine is(ready, start, command, slv_ack1, wr, rd, slv_ack2, mstr_ack, stop); --状态 constant divider : integer := (clock_frequency/baud)/4; --1/4 cycle of scl 时钟个数 signal state : machine; --状态机 signal data_clk : std_logic; --data clock for sda signal data_clk_prev : std_logic; --data clock during previous system clock signal scl_clk : std_logic; --constantly running internal scl signal scl_en : std_logic := '0'; --enables internal scl to output signal sda_int : std_logic := '1'; --internal sda signal sda_en_n : std_logic; --enables internal sda to output signal addr_rw : std_logic_vector(7 downto 0); --latched in address and read/write signal data_tx : std_logic_vector(7 downto 0); --latched in data to write to slave signal data_rx : std_logic_vector(7 downto 0); --data received from slave signal bit_cnt : integer range 0 to 7 := 7; --tracks bit number in transaction signal stretch : std_logic := '0'; --identifies if slave is stretching scl begin --时钟产生 - 包括scl和数据更新时钟 process(i_clk, i_rst_n) variable count : integer range 0 to divider*4; --timing for clock generation begin if rising_edge(i_clk) then if i_rst_n = '0' then count := 0; stretch <= '0'; else data_clk_prev <= data_clk; --store previous value of data clock if(count = divider*4-1) then --end of timing cycle count := 0; --reset timer elsif(stretch = '0') then --clock stretching from slave not detected count := count + 1; --continue clock generation timing end if; case count is when 0 to divider-1 => --first 1/4 cycle of clocking scl_clk <= '0'; data_clk <= '0'; when divider to divider*2-1 => --second 1/4 cycle of clocking scl_clk <= '0'; data_clk <= '1'; when divider*2 to divider*3-1 => --third 1/4 cycle of clocking scl_clk <= '1'; --release scl if(io_scl = '0') then --detect if slave is stretching clock stretch <= '1'; else stretch <= '0'; end if; data_clk <= '1'; when others => --last 1/4 cycle of clocking scl_clk <= '1'; data_clk <= '0'; end case; end if; end if; end process; --状态机-将数据写到sda当io_scl为地时(data_clk 的上升沿) process(i_clk, i_rst_n) begin if rising_edge(i_clk) then if i_rst_n = '0' then state <= ready; --return to initial state o_busy <= '1'; --indicate not available scl_en <= '0'; --sets scl high impedance sda_int <= '1'; --sets sda high impedance b_ack_error <= '0'; --clear acknowledge error flag bit_cnt <= 7; --restarts data bit counter o_data_rd <= "00000000"; --clear data read port else if(data_clk = '1' and data_clk_prev = '0') then --data clock rising edge case state is when ready => if i_trans_en='1' then o_busy <= '1'; addr_rw <= i_addr & i_rw; --collect requested slave address and command data_tx <= i_data_wr; --collect requested data to write state <= start; --go to start bit else o_busy <= '0'; --unflag busy state <= ready; --remain idle end if; when start => --start bit of transaction o_busy <= '1'; --resume busy if continuous mode sda_int <= addr_rw(bit_cnt); --set first address bit to bus state <= command; --go to command when command => if bit_cnt=0 then --command transmit finished sda_int <= '1'; --release sda for slave acknowledge bit_cnt <= 7; --reset bit counter for "byte" states state <= slv_ack1; --go to slave acknowledge (command) else --next clock cycle of command state bit_cnt <= bit_cnt - 1; --keep track of transaction bits sda_int <= addr_rw(bit_cnt-1); --write address/command bit to bus state <= command; --continue with command end if; when slv_ack1 => --slave acknowledge bit (command) if(addr_rw(0) = '0') then --write command sda_int <= data_tx(bit_cnt); --write first bit of data state <= wr; --go to write byte else --read command sda_int <= '1'; --release sda from incoming data state <= rd; --go to read byte end if; when wr => --write byte of transaction o_busy <= '1'; --resume busy if continuous mode if(bit_cnt = 0) then --write byte transmit finished sda_int <= '1'; --release sda for slave acknowledge bit_cnt <= 7; --reset bit counter for "byte" states state <= slv_ack2; --go to slave acknowledge (write) else --next clock cycle of write state bit_cnt <= bit_cnt - 1; --keep track of transaction bits sda_int <= data_tx(bit_cnt-1); --write next bit to bus state <= wr; --continue writing end if; when rd => --read byte of transaction o_busy <= '1'; --resume busy if continuous mode if(bit_cnt = 0) then --read byte receive finished if(i_trans_en = '1' and addr_rw = i_addr & i_rw) then --continuing with another read at same address sda_int <= '0'; --acknowledge the byte has been received else --stopping or continuing with a write sda_int <= '1'; --send a no-acknowledge (before stop or repeated start) end if; bit_cnt <= 7; --reset bit counter for "byte" states o_data_rd <= data_rx; --output received data state <= mstr_ack; --go to master acknowledge else --next clock cycle of read state bit_cnt <= bit_cnt - 1; --keep track of transaction bits state <= rd; --continue reading end if; when slv_ack2 => --slave acknowledge bit (write) if(i_trans_en = '1') then --continue transaction o_busy <= '0'; --continue is accepted addr_rw <= i_addr & i_rw; --collect requested slave address and command data_tx <= i_data_wr; --collect requested data to write if(addr_rw = i_addr & i_rw) then --continue transaction with another write sda_int <= i_data_wr(bit_cnt); --write first bit of data state <= wr; --go to write byte else --continue transaction with a read or new slave state <= start; --go to repeated start end if; else --complete transaction state <= stop; --go to stop bit end if; when mstr_ack => --master acknowledge bit after a read if(i_trans_en = '1') then --continue transaction o_busy <= '0'; --continue is accepted and data received is available on bus addr_rw <= i_addr & i_rw; --collect requested slave address and command data_tx <= i_data_wr; --collect requested data to write if(addr_rw = i_addr & i_rw) then --continue transaction with another read sda_int <= '1'; --release sda from incoming data state <= rd; --go to read byte else --continue transaction with a write or new slave state <= start; --repeated start end if; else --complete transaction state <= stop; --go to stop bit end if; when stop => --stop bit of transaction o_busy <= '0'; --unflag busy state <= ready; --go to idle state end case; elsif(data_clk = '0' and data_clk_prev = '1') then --data clock falling edge case state is when start => if(scl_en = '0') then --starting new transaction scl_en <= '1'; --enable scl output b_ack_error <= '0'; --reset acknowledge error output end if; when slv_ack1 => --receiving slave acknowledge (command) if(io_sda /= '0' or b_ack_error = '1') then --no-acknowledge or previous no-acknowledge b_ack_error <= '1'; --set error output if no-acknowledge end if; when rd => --receiving slave data data_rx(bit_cnt) <= io_sda; --receive current slave data bit when slv_ack2 => --receiving slave acknowledge (write) if(io_sda /= '0' or b_ack_error = '1') then --no-acknowledge or previous no-acknowledge b_ack_error <= '1'; --set error output if no-acknowledge end if; when stop => scl_en <= '0'; --disable scl when others => null; end case; end if; end if; end if; end process; --set sda output with state select sda_en_n <= data_clk_prev when start, --generate start condition not data_clk_prev when stop, --generate stop condition sda_int when others; --set to internal sda signal --set scl and sda outputs io_scl <= '0' when (scl_en = '1' and scl_clk = '0') else 'Z'; io_sda <= '0' when sda_en_n = '0' else 'Z'; end architecture behave;