---------------------------------------------------------------------------------- -- Company: The Hong Kong Polytechnic University -- Engineer: Alexandr Melnikov -- -- Create Date: 10:46:07 08/16/2016 -- Design Name: -- Module Name: a2d5 - Behavioral -- Project Name: -- Target Devices: -- Tool versions: -- Description: -- -- Dependencies: -- -- Revision: -- Revision 0.01 - File Created -- Additional Comments: -- ADC block, handles conversion of the feedback voltage -- to digital form -- ADC uses I2C (2-wire) protocol. ---------------------------------------------------------------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; -- Uncomment the following library declaration if using -- arithmetic functions with Signed or Unsigned values use IEEE.NUMERIC_STD.ALL; -- Uncomment the following library declaration if instantiating -- any Xilinx primitives in this code. library UNISIM; -- to use PULLUP primitive use UNISIM.VComponents.all; entity a2d is Port ( clk100mhz : in STD_LOGIC; --baud_clk : in STD_LOGIC; dout : out STD_LOGIC_VECTOR (11 downto 0); -- data read by ADC (to FuncGen) SDA : inout STD_LOGIC; -- bidirectional ADC's data line SCL : out STD_LOGIC; -- ADC's clock line start_read : in STD_LOGIC; -- start read ADC (from FuncGen) read_done : out STD_LOGIC); -- done read ADC (to FuncGen) end a2d; architecture Behavioral of a2d is -- local clock signals for DAC -- slow clock for SCL; fast clock for read/write SDA operation -- write SDA when SCL is LOW; read SDA when SCL is HIGH -- START ADC: HIGH-TO-LOW transition of SDA when SCL is HIGH -- STOP ADC: LOW-T0_HIGH transition of SDA when SCL is HIGH signal clk100khz : std_logic := '0'; -- SCL signal clk200khz : std_logic := '0'; -- data clock signal clkdiv : integer range 0 to 999 :=0; -- ADC state machine type state_type is (adc_idle, adc_start, call_adc, adc_config, adc_read, adc_stop); signal state : state_type := adc_idle; -- ADC signals -- inner signal to write to SDA signal SDA_prime : std_logic; -- ADC address, see datasheet signal adc_addr : std_logic_vector (7 downto 0) := x"28";--"0 010 1000"; -- ADC address to write to SDA (LSB is either '1' or '0' for read/write operation) signal adc_wrt : std_logic_vector (7 downto 0); -- ADC configuration register; convert on Vin0, use external reference (2.5V is used to match DAC's reference voltage) signal config_reg : std_logic_vector (7 downto 0) := x"18"; -- counter to control read/write flow signal count : integer range 0 to 32 := 0; -- slave's (ADC) acknowledgement signal slave_ack: std_logic; -- master's (FPGA) acknowledgement signal mast_ack: std_logic := '0'; -- data read from ADC signal din : std_logic_vector (11 downto 0) := x"000"; -- temporary data signal to update ADC's data output "dout" signal din_new : std_logic_vector (11 downto 0) := x"000"; -- internal flags signal wrt_done : std_logic := '0'; -- indicates that ADC has been addressed signal rd_done : std_logic := '0'; -- indicates the end of read operation -- status bits, not used for anything signal stat_bit : std_logic_vector (3 downto 0); begin PULLUP_SDA: PULLUP PORT MAP (O => SDA); -- implement internal pull-up on SDA line -- local clock signals generation: -- fast clock to write/read data; -- slow clock for SCL. -- There is a 500 ns shift between the fast clock's rising edge and -- slow clock's rising/falling edges to avoid unintended START or STOP command -- and give time to SDA to respond the drivers' (master or slave) commands. clock_adc: process (clk100mhz) begin if (rising_edge(clk100mhz)) then if (clkdiv = 999) then clkdiv <= 0; else clkdiv <= clkdiv +1; end if; case clkdiv is when 0 to 49 => clk100khz <= '0'; clk200khz <= '0'; when 50 to 299 => clk100khz <= '0'; clk200khz <= '1'; when 300 to 499 => clk100khz <= '0'; clk200khz <= '0'; when 500 to 549 => clk100khz <= '1'; clk200khz <= '0'; when 550 to 799 => clk100khz <= '1'; clk200khz <= '1'; when 800 to 999 => clk100khz <= '1'; clk200khz <= '0'; end case; end if; end process clock_adc; adc_main: process (clk200khz, clk100khz, start_read) begin if (start_read = '0') then -- start_read acts as internal Reset for ADC module SDA_prime <= '1'; -- clear flags and data register count <= 0; din <= x"000"; wrt_done <= '0'; read_done <= '0'; -- external flag (for FuncGen) rd_done <= '0'; -- internal flag state <= adc_idle; elsif (rising_edge(clk200khz)) then SDA_prime <= '1'; -- SDA is in High Impedance state when it's not driven by anyone (master or slave) case state is -- ADC stays in idle mode until receives a start_read command from FuncGen when adc_idle => count <= 0; read_done <= '0'; SDA_prime <= '1'; -- when SCL is LOW, the START condition can be issued -- on the next rising edge of fast clock, which will take place -- during the SCL's HIGH portion of the period if (clk100khz = '0') then state <= adc_start; else state <= adc_idle; end if; -- in this state START condition (SDA's H2L transition during HIGH SCL) is issued -- this state is used repeatedly withis this FSM for both read SDA and -- write SDA operations when adc_start => SDA_prime <= '0'; -- HIGH-TO-LOW transition on SDA starts ADC read_done <= '0'; count <= 0; -- if wrt_done= is '0' then configuretion register is going to -- be written to ADC (to SDA), else ADC (SDA) is going to be read adc_wrt <= adc_addr (6 downto 0) & wrt_done; state <= call_adc; -- call ADC by writing to SDA its address (7 bit+read/write bit) when call_adc => read_done <= '0'; if (clk100khz = '0' and count < 8) then SDA_prime <= adc_wrt(7-count); state <= call_adc; elsif (clk100khz = '1' and count < 8) then SDA_prime <= adc_wrt(7-count); count <= count+1; state <= call_adc; elsif (clk100khz = '0' and count = 8) then state <= call_adc; elsif (clk100khz = '1' and count = 8) then slave_ack <= SDA; -- read slave's acknowledgement count <= 0; -- if slave's ACK is received and ADC is to be configured -- (wrt_done is '0'), go to config state if (slave_ack = '0' and wrt_done = '0') then state <= adc_config; -- if slave's ACK is received and ADC is to be read (i.e. has been configured already) -- (wrt_done is '1') go to read state elsif (slave_ack = '0' and wrt_done = '1') then state <= adc_read; -- if slave's ACK is not received go to "idle" state elsif (slave_ack = '1') then state <= adc_idle; end if; end if; -- configure ADC when adc_config => read_done <= '0'; if (clk100khz = '0' and count < 8) then SDA_prime <= config_reg(7-count); state <= adc_config; elsif (clk100khz = '1' and count < 8) then SDA_prime <= config_reg(7-count); count <= count+1; state <= adc_config; elsif (clk100khz = '0' and count = 8) then state <= adc_config; elsif (clk100khz = '1' and count = 8) then slave_ack <= SDA; -- read slave's acknowledgement count <= 0; -- if if slave's ACK is received, ADC was configured successfully -- and can be used to read data out of it if (slave_ack = '0') then wrt_done <= '1'; -- set flag indicating successful config operation state <= adc_stop; else state <= adc_idle; -- go to "idle" state if config failed end if; end if; -- in this state STOP condition (SDA's L2H transition during HIGH SCL) is issued -- this state is used repeatedly withis this FSM for both read SDA and -- write SDA operations when adc_stop => -- if SCL is HIGH, issue STOP condition immediately if (clk100khz = '1') then SDA_prime <= '1'; read_done <= '0'; rd_done <= '0'; state <= adc_idle; -- if SCL is LOW, wait until it's HIGH elsif (clk100khz = '0') then SDA_prime <= '0'; read_done <= '0'; count <= 0; state <= adc_stop; end if; -- in this state ADC is being read when adc_read => read_done <= '0'; -- external flag is not set if (clk100khz = '0' and count < 4) then -- do nothing at rising edge of fast clock if SCL is LOW state <= adc_read; elsif (clk100khz = '1' and count < 4) then -- read at rising edge of fast clock if SCL is HIGH stat_bit(3-count) <= SDA; -- read SDA: 1st 4 bits are status bits count <= count+1; state <= adc_read; elsif (clk100khz = '0' and (count > 3 and count < 8)) then -- do nothing at rising edge of fast clock if SCL is LOW state <= adc_read; elsif (clk100khz = '1' and (count > 3 and count < 8)) then -- read at rising edge of fast clock if SCL is HIGH din(15-count) <= SDA; -- read SDA: 1st 4 bits of a 12-bit data word count <= count+1; state <= adc_read; elsif (clk100khz = '0' and count = 8) then -- set ACK at rising edge of fast clk, while SCL is LOW SDA_prime <= mast_ack; -- send ADC a master's ACK state <= adc_read; elsif (clk100khz = '1' and count = 8) then -- keep ACK on SDA line while SCL is HIGH SDA_prime <= mast_ack; -- send ADC a master's ACK count <= count+1; state <= adc_read; elsif (clk100khz = '0' and (count > 8 and count < 17)) then -- do nothing at rising edge of fast clock if SCL is LOW state <= adc_read; elsif (clk100khz = '1' and (count > 8 and count < 17)) then -- read at rising edge of fast clock if SCL is HIGH din(16-count) <= SDA; -- read SDA: last 8 bits of a 12-bit data word count <= count+1; state <= adc_read; elsif (clk100khz = '0' and count = 17) then -- set NOT_ACK at rising edge of fast clk, while SCL is LOW SDA_prime <= not(mast_ack); -- NOT_ACK to interupt ADC's reading operation rd_done <= '1'; -- set INTERNAL flag to indicate end of read operation state <= adc_read; elsif (clk100khz = '1' and count = 17) then ---- keep NOT_ACK on SDA line while SCL is HIGH SDA_prime <= not(mast_ack); -- NOT_ACK to interupt ADC's reading operation count <= 0; read_done <= '1'; -- set EXTERNAL flag to indicate end of reading (for FuncGen) state <= adc_stop; -- go to stop state and issue stop condition to put the ADC into shutdown mode -- no need to synchronise this module with Baud tick, since SCL is slow enough and -- start_read acts as asynchronous reset end if; end case; end if; end process adc_main; SDA <= '0' when SDA_prime = '0' else 'Z'; -- tri-state buffer for SDA line SCL <= clk100khz; -- SCL (slow) clock -- this process handles data transfer from "din" register to "dout" data_transfer: process (clk100mhz, rd_done, din, din_new) begin -- update intermediate data register when read is done (internal flag is used) if(rising_edge(clk100mhz) and rd_done = '1') then din_new <= din; end if; dout <= din_new; -- continiously update the module's data output with ADC's data end process data_transfer; end Behavioral;