0
\$\begingroup\$

I have this ADS4249 ADC from Texas Instruments. I want the data output interface to be CMOS therefore I need to write REG 41 with the data "11000000" (datasheet p.41 and p.46). In the image below you can see the timing diagram for this ADC. Datasheet for the ADC: https://www.ti.com/lit/ds/symlink/ads4249.pdf

enter image description here Below you can see my code. I am using an ODDR to forward my systemclock (SYSCLK) to the ADCs SCLK(SCLK_ADC) input and connected them properly in the top level domain. I can't get the ADC to output CMOS signals. I think my fault is somewhere in the ADC entity.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity ADC is
Port 
(   
    SYSCLK: in STD_LOGIC;
    RST_ADC   : out STD_LOGIC;
    SCLK_ADC  : out STD_LOGIC;    
    SDATA_ADC : out STD_LOGIC;
    SEN_ADC   : out STD_LOGIC
);
end ADC;

architecture Behavioral of ADC is
signal count : integer range 0 to 4 := 0;
signal ADDR : STD_LOGIC_VECTOR (7 downto 0) := "01000001"; 
signal DATA : STD_LOGIC_VECTOR (7 downto 0) := "11010000";  
signal ADDR_DATA : STD_LOGIC_VECTOR (15 downto 0) := ADDR & DATA;

begin
process(SYSCLK, count)
begin
    if(rising_edge(SYSCLK)) then
        if(count = 0) then
            SEN_ADC <= '1';
        elsif(count = 1) then
            RST_ADC <= '1';
        elsif(count = 2) then
            RST_ADC <= '0';
        elsif(count = 3) then
            SEN_ADC <= '0';
        elsif(count = 4) then
            for I in 15 downto 0 loop
                SDATA_ADC <= ADDR_DATA(I);
            end loop;            
        end if;
        count <= count + 1;
    end if;
end process;
end Behavioral;

EDIT:

@MituRaj thanks for your help. My code looks like this now but it is still not working.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity ADC is
Port 
(   
    ADCINIT: in STD_LOGIC;
    ADCINITRDY: out STD_LOGIC;
    SYSCLK: in STD_LOGIC;
    RST_ADC   : out STD_LOGIC;
    SDATA_ADC : out STD_LOGIC;
    SEN_ADC   : out STD_LOGIC
);
end ADC;

architecture Behavioral of ADC is
signal count : integer range 0 to 20 := 0;
signal bitcount : integer range 15 downto 0 := 0;
signal ADDR : STD_LOGIC_VECTOR (7 downto 0) := "01000001"; 
signal DATA : STD_LOGIC_VECTOR (7 downto 0) := "11010000";  
signal ADDR_DATA : STD_LOGIC_VECTOR (15 downto 0) := ADDR & DATA;

begin
process(SYSCLK, count, ADCINIT)
begin
    if(rising_edge(SYSCLK) AND ADCINIT = '1') then
        if(count = 0) then
            SEN_ADC <= '1';
            count <= count + 1;
        elsif(count = 1) then
            RST_ADC <= '1';
            count <= count + 1;
        elsif(count = 2) then
            RST_ADC <= '0';
            count <= count + 1;
        elsif(count = 3) then
            SEN_ADC <= '0';
            count <= count + 1;
        elsif(count = 4) then
            SDATA_ADC <= ADDR_DATA(bitcount);
            bitcount <= bitcount - 1;
            if(bitcount = 0) then
                count <= count + 1;
            end if;    
        elsif(count = 5) then
            SEN_ADC <= '1';
            ADCINITRDY <= '1';
        end if;
    end if;
end process;
end Behavioral;

Here is my ODDR:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
Library UNISIM;
use UNISIM.vcomponents.all;

entity ODDR_instiation is
    Port ( SYSCLK : in STD_LOGIC;       --systemclock
           OUTCLK : out STD_LOGIC);    --sclk adc
end ODDR_instiation;

architecture Behavioral of ODDR_instiation is

begin
 DDR_inst : ODDR
  generic map(
     DDR_CLK_EDGE => "OPPOSITE_EDGE", -- "OPPOSITE_EDGE" or "SAME_EDGE" 
     INIT => '0',   -- Initial value for Q port ('1' or '0')
     SRTYPE => "SYNC") -- Reset Type ("ASYNC" or "SYNC")
  port map (
     Q => OUTCLK,   -- 1-bit DDR output
     C => SYSCLK,    -- 1-bit clock input
     CE => '1',  -- 1-bit clock enable input
     D1 => '1',  -- 1-bit data input (positive edge)
     D2 => '0',  -- 1-bit data input (negative edge)
     R => '0',    -- 1-bit reset input
     S =>  '0'     -- 1-bit set input
  );     
end Behavioral;

Here is my top-level module:

entity TT is
Port
(
    --SYSCLK
    SYSCLK: in STD_LOGIC;
    --ADC
    SCLK_ADC  : out STD_LOGIC;
    SDATA_ADC : out STD_LOGIC;
    SEN_ADC   : out STD_LOGIC;
    PDN_ADC   : out STD_LOGIC;
    RST_ADC   : out STD_LOGIC;
);
end TT;

architecture Behavioral of TT is

signal ADCINIT, ADCINITRDY := '0';

component ADC
Port 
(   
    ADCINIT : IN STD_LOGIC;
    ADCINITRDY : OUT STD_LOGIC;
    SYSCLK: in STD_LOGIC;
    RST_ADC   : out STD_LOGIC;
    SDATA_ADC : out STD_LOGIC;
    SEN_ADC   : out STD_LOGIC
);
end component;

component ODDR_instiation is
Port 
( 
    SYSCLK : in STD_LOGIC;       --systemclock
    OUTCLK : out STD_LOGIC
);    --sclk adc
end component;

begin

ODDRS:
ODDR_instiation port map
(
    SYSCLK => SYSCLK,
    OUTCLK => SCLK_ADC
);

ADCS:
ADC port map
(
    ADCINITRDY => ADCINITRDY,
    ADCINIT => ADCINIT,
    SYSCLK => SYSCLK,
    RST_ADC => RST_ADC,
    SDATA_ADC => SDATA_ADC,
    SEN_ADC => SEN_ADC
);

process(SYSCLK, ADCINITRDY)
begin
    if(rising_edge(SYSCLK)) then
            ADCINIT <= '1';
            if(ADCINITRDY = '1') then
                ADCINIT <= '0';
            end if;
    end if;
end process;

end Behavioral;

EDIT: I added this two lines in the top-level module

attribute IOB : string;
attribute IOB of SDATA_ADC: signal is "{TRUE}";
\$\endgroup\$
37
  • \$\begingroup\$ Not working on board or simulation? \$\endgroup\$
    – Mitu Raj
    Nov 26, 2020 at 13:45
  • 2
    \$\begingroup\$ VHDL for does not behave the way you might expect it would when coming from sequential programming languages \$\endgroup\$
    – po.pe
    Nov 26, 2020 at 13:50
  • 1
    \$\begingroup\$ Shame on me, I've never used it this way before so I guess I was a bit overreacting. But what could be a problem is that the ADC reads the data on the rising edge clock and you are writing on the rising edge clock. And you do not have a reset state in your process, how do you make sure the ADC is ready to receive data? \$\endgroup\$
    – po.pe
    Nov 26, 2020 at 14:14
  • 1
    \$\begingroup\$ How about all four line shown on the datasheet? Then hold the datasheet up to the oscilloscope and see how they don't match -- that should get you a lot closer to figuring out why. \$\endgroup\$
    – TimWescott
    Nov 26, 2020 at 18:05
  • 1
    \$\begingroup\$ I'd also expect the SEN signal to be a kind of clock gate. Switching it while the clock is high as in the diagram is safe, switching it with the rising edge might introduce a glitch, so you might want to put a timing constraint on that. The next falling edge while enabled will sample data, so you would output the first data bit together with SEN, not one cycle later. \$\endgroup\$ Nov 26, 2020 at 19:35

3 Answers 3

2
\$\begingroup\$

Misundertanding in the working of for loop

When your count = 4, unroll the loop to understand what happens in the hardware. You assign multiple vales to SDATA_ADC:

SDATA_ADC <= ADDR_DATA (15);

SDATA_ADC <= ADDR_DATA (14);

.

.

SDATA_ADC <= ADDR_DATA (0);

But only one serial data will be sent via SDATA_ADC in that clock edge, ie., ADDR_DATA (0).

count is incrementing in every clock edge. So in the next clock edge count becomes 5. SDATA_ADC remains same until count rolls over from 7 to 0 (since 3-bit register will be inferred after synthesis for count), then the whole process restarts again from count = 0.

You need to stay in the state called count = 4 until all bits of SDATA_ADC are sent out. You have to stay in that state for 16 clock cycles. And finally increment to count = 5, set SEN back to high, stop incrementing count and go to some idle state.

Or else, use range on if condition like if (count > 3 and count < 20). Send out all 16 bits of SDATA_ADC, when count = 20, set SEN back to high, stop incrementing count and go to some idle state.

It would have been more understandable if you implemented the whole logic using FSMs

Some observations/comments

  1. You need a reset signal in your entity that resets all internal registers/signals like ADDR, DATA etc and output ports to an initial value. Don't trust all FPGA synthesisers to synthesise initial values.

  2. If this is in vivado, use flip-flop in the IOB for SDATA_ADC, as you need kind of a source synchronous interface with SCLK_ADC which you forward to ADC using ODDR.

  3. No need of count in the sensitivity list.

\$\endgroup\$
5
  • \$\begingroup\$ Ok thanks I will implement the IO flipflop \$\endgroup\$ Nov 26, 2020 at 17:06
  • \$\begingroup\$ Can you give me a source or an example on how to implement the flip flop in the IOB? \$\endgroup\$ Nov 26, 2020 at 17:45
  • \$\begingroup\$ You can search in Google on mapping flip flop to IOB. Its tool specific. There are multiple ways to do it. For eg: xilinx.com/support/documentation/sw_manuals/xilinx2013_2/… -- page 47 \$\endgroup\$
    – Mitu Raj
    Nov 26, 2020 at 17:51
  • \$\begingroup\$ Thanks. I have one question, there is this line: attribute IOB of <port_name>: signal is "{TRUE|FALSE}";. What do I have to choose TRUE or FALSE? \$\endgroup\$ Nov 26, 2020 at 17:57
  • \$\begingroup\$ TRUE .......... \$\endgroup\$
    – Mitu Raj
    Nov 26, 2020 at 18:05
1
\$\begingroup\$

For the Finite State Machine (FSM), you need to clearly define your inputs, outputs, states, and transitions.

And for VHDL-2008, sensitivity lists are a thing of the past (they used to cause lots of bugs due to simulator/synthesiser mismatches). They have been replaced with process(all).

States

This is an example of state names you could use without using a counter. You could combine the 16 send states into 2 states or even 1 state using a counter, but that's up to you.

type TState is
(
    ST_IDLE,
    ST_BEGIN,
    ST_RESET_ADC,
    ST_WAIT_RESET_ADC,
    ST_SEND_A7,
    ST_SEND_A6,
    ST_SEND_A5,
    ST_SEND_A4,
    ST_SEND_A3,
    ST_SEND_A2,
    ST_SEND_A1,
    ST_SEND_A0,
    ST_SEND_D7,
    ST_SEND_D6,
    ST_SEND_D5,
    ST_SEND_D4,
    ST_SEND_D3,
    ST_SEND_D2,
    ST_SEND_D1,
    ST_SEND_D0,
    ST_END
);

State Register and Outputs Register

This is synchronous logic with an asynchronous reset. It keeps the change of outputs synchronised to the clock and the change of state.

state: out natural range 0 to NUM_STATES - 1;  -- Optional feedback to the outside world.
...
constant STATE_RESET: TState := ST_IDLE;
constant OUTPUTS_RESET: std_logic_vector(0 to NUM_OUTPUTS - 1) := "0001";

signal present_state, next_state: TState;
signal outputs, next_outputs: std_logic_vector(0 to NUM_OUTPUTS - 1);
signal inputs: std_logic_vector(0 to NUM_INPUTS - 1);
...
process(all)
begin
    if reset then
        present_state <= STATE_RESET;
        outputs <= OUTPUTS_RESET;
    elsif rising_edge(clock) then
        state <= TState'pos(next_state);  -- Optional feedback to the outside world.
        present_state <= next_state;
        outputs <= next_outputs;          -- This keeps the outputs synchronised to the clock and state. Avoids asynchronous combinational output logic after the clock edge, and avoids a clock delay after the change of state.
    end if;
end process;

Next State Logic

This is combinational logic that figures out what the next state should be. It will be clocked through to the present state at the next positive clock edge.

process(all)
begin
    case present_state is
        when ST_IDLE => if inputs = "1" then next_state <= ST_BEGIN; else next_state <= ST_IDLE; end if;
        ...
    end case;
end process;

Next Outputs Logic

This is combinational logic that figures out what the next outputs should be, and will be clocked through to the actual outputs at the next positive clock edge and be synchronised to the change of state.

process(all)
begin
    case next_state is
        when ST_IDLE => next_outputs <= "0001";
        ...
    end case;
end process;

Input and Output Assignments

inputs <=
(
    0 => ADCINIT
);

(
    0 => ADCINITRDY,
    1 => RST_ADC,
    2 => SDATA_ADC,
    3 => SEN_ADC
) <= outputs;

I'll let you have a go at filling in the ellipses, but here is the simulation to prove it works.

Simulation of ADS4249 Interface

Simulation of ADS4249 Interface

Schematic of a Fully Synchronous Finite State Machine (FSM)

Notice that there is no combinational output logic after the registers. The output logic has been moved to the left of the registers and renamed Next Outputs Logic because it calculates the next outputs prior to the clock edge in the same manner that the next state is calculated. This avoids having any combinational logic with its associated hazards and glitches due to propagation delays after the register/clock edge.

Schematic of a Fully Synchronous Finite State Machine (FSM)

\$\endgroup\$
0
\$\begingroup\$

I think your main issue is not setting the bitcount correctly. Also is is good practice to sample asynchronous signals with the system clock to avoid race conditions. I did this with the ADCINIT signal to create TRIGGERED.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity ADC is
Port 
(   
    ADCINIT: in STD_LOGIC;
    ADCINITRDY: out STD_LOGIC;
    SYSCLK: in STD_LOGIC;
    RST_ADC   : out STD_LOGIC;
    SDATA_ADC : out STD_LOGIC;
    SEN_ADC   : out STD_LOGIC
);
end ADC;

architecture Behavioral of ADC is
signal count : integer range 0 to 5 := 0;
signal bitcount : integer range 15 downto 0 := 15;
signal ADDR : STD_LOGIC_VECTOR (7 downto 0) := "01000001"; 
signal DATA : STD_LOGIC_VECTOR (7 downto 0) := "11010000";  
signal ADDR_DATA : STD_LOGIC_VECTOR (15 downto 0) := ADDR & DATA;
signal ADCINIT_d1 : std_logic;
signal ADCINIT_d2 : std_logic;
signal TRIGGERED : std_logic;

begin
process(SYSCLK, count, ADCINIT)
begin
    if(rising_edge(SYSCLK)) then
        ADCINIT_d1 <= ADCINIT;
        ADCINIT_d2 <= ADCINIT_d1; 
        if ADCINIT_d2 = '0' and ADCINIT_d1='1' then
          TRIGGERED <='1';
          count <= 0;
          bitcount <= 15;
          SEN_ADC <= '1';
          RST_ADC <= '0';

        end if;

        if TRIGGERED = '1' then
          if(count = 0) then
              SEN_ADC <= '1';
              count <= count + 1;
          elsif(count = 1) then
              RST_ADC <= '1';
              count <= count + 1;
          elsif(count = 2) then
              RST_ADC <= '0';
              count <= count + 1;
          elsif(count = 3) then
              SEN_ADC <= '0';
              count <= count + 1;
              bitcount <= 15;
          elsif(count = 4) then
              SDATA_ADC <= ADDR_DATA(bitcount);
              bitcount <= bitcount - 1;
              if(bitcount = 0) then
                  count <= count + 1;
              end if;    
          elsif(count = 5) then
              SEN_ADC <= '1';
              ADCINITRDY <= '1';
              -- This is optional if you want to be able to try it multiple times.
              TRIGGERED <= '0';
          end if;
        end if;
    end if;
end process;
end Behavioral;
```
\$\endgroup\$
1
  • \$\begingroup\$ is it possible to post the complete top level model of this design and also controlling of the ADC for latency and data validity. \$\endgroup\$
    – srihari
    Dec 6, 2021 at 20:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.