Programmable Devices
CPLDs, FPGAs, SoC FPGAs, Configuration, and Transceivers
21568 Discussions

ADC interfacing code for 3-wire SPI using Verilog

Altera_Forum
Honored Contributor II
6,022 Views

Hi guys, 

 

I'm writing some Verilog code to interface a simple 12-bit serial ADC to an FPGA and I have some questions about my methodology. The ADC that I'm using is the ADS7818 from Texas Instruments..  

 

I think the code should be very simple, but I just want to get a second opinion so I don't end up in the wrong direction. Basically, here's what I've done so far, but I seem to be having some trouble getting it works. In actual fact, I'm not sure if I'm asserting and reading the data correctly. Anyway, this is what I have so far: 

 

1. Since the ADC is going to be operating at its full 500 KSPS, my input clock needs to be 8 MHz. This is going to be fed into the ADC at all time. For this reason, I think I should be using the DSP Interface Timing style on page 12 of the datasheet. 

 

2. Make a simple counter than counts up to 16 and then resets after that. This counter is operating at 8 MHz. This will allow me to keep track of all the current clock cycle, so I can grab the data and know when to assert the CONV. 

 

3. CONV will be asserted 0 when the falling edge of the CLK = 15 is present. At the falling edge CLK = 16, the CONV is asserted 1.  

 

4. At the falling edge of CLK = 1, I will grab the data and place them into the register. I will stop at CLK = 13, and at CLK = 13, I will put them into a another register. 

 

5. I will assert a ready signal (RDY) at the rising edge of CLK = 14 and make it low at the rising edge of CLK = 1. This RDY signal will tell my other modules that the data is ready and valid. My other modules, however, will be operating at 200 MHz, but the clock should be synchronized  

 

6. Everything will now repeat.  

 

Does this seem like it would work? I've never worked with SPI or external devices, nor have I ever written code from a timing diagram.  

 

In case you're interested, my code can be seen below. 

 

I'd appreciate any input you have for modification to get this thing going.  

 

//register declarations reg conv; reg rdy; reg count; reg data_temp; reg tmp; //The clock counter. Starts at 0, so clock is from 0-15 instead of 1-16. always @ (posedge CLOCK_8) begin count <= count + 1; end //Assert the CONV signal always @ (negedge CLOCK_8) begin if ((count == 14) || (count == 15)) conv = 1'b0; else conv = 1'b1; end //Read the serial data into a 12-bit register. Afterwards, convert it to parallel if the count is 13 (end of data stream) always @ (negedge CLOCK_8) begin if (count == 13) tmp <= data_temp; case (count) 1: data_temp <= serial_data; 2: data_temp <= serial_data; 3: data_temp <= serial_data; 4: data_temp <= serial_data; 5: data_temp <= serial_data; 6: data_temp <= serial_data; 7: data_temp <= serial_data; 8: data_temp <= serial_data; 9: data_temp <= serial_data; 10: data_temp <= serial_data; 11: data_temp <= serial_data; 12: data_temp <= serial_data; endcase endThis is also a cross-post to the same thread on the allaboutcircuits embedded systems forum. I have posted here because I haven't been able to get a reply.
0 Kudos
5 Replies
Altera_Forum
Honored Contributor II
4,616 Views

Your thinking seems correct to me. Except I think you are off by a clock cycle (actually half a cycle). The ADC will drive data out on the falling edge of clock. If you sample on the negedge of cycle 1, you're going to get the active low data enable rather than the MSB of the data. 

 

 

I really think you ought to be doing everything here on the rising edge. How are you driving the clock to the ADC? It appears you need a 10ns hold time from the falling edge of clock to conv. You need to ensure this occurs. Again, use the rising clock edge. 

 

Consider the following: 

//register declarations reg conv; reg rdy; reg count; reg data_temp; reg tmp; //The clock counter. Starts at 0, so clock is from 0-15 instead of 1-16. always @ (posedge CLOCK_8) count <= count + 4'd1; //Assert the CONV signal always @ (posedge CLOCK_8) conv <= !&count; //Read the serial data into a 12-bit register. Afterwards, convert it to parallel if the count is 13 (end of data stream) always @ (posedge CLOCK_8) begin data_temp <= {data_temp,serial_data]}; if (count == 13) tmp <= data_temp; end
0 Kudos
Altera_Forum
Honored Contributor II
4,616 Views

Yes, the CONV input is sampled and DATA set at the CLK falling edge, so the SPI master should set/sample the signals at the rising edge. That's the standard method to operate SPI interfaces, except for high speeds. With ADS7818, the 30 to 50 ns tco of DATA nearly suggests to sample it at the next falling CLK edge. I would decide this depending on CLK8 generation method and possibly involved additional circuit delays. 

 

If I counted right, D0 is present at DATA for (count == 13), so either data_temp should be sampled for (count == 14) or tmp[0] must be copied direct from serial input. Every thing else is perfect in jakobjones' suggestion, I think.
0 Kudos
Altera_Forum
Honored Contributor II
4,616 Views

Thanks very much for the suggestions guys. I'm driving the clock through a PLL. The only way I was able to get a clock of that speed was through the PLL, first by generating a 32 MHz clock and then dividing it by 4 to get 8 MHz. For the added delays, is the only way to test than through actual experimentation? I'll have a go at it tomorrow to see how it fares.

0 Kudos
Altera_Forum
Honored Contributor II
4,616 Views

The delays can be predicted rather exactly for a known design structure. When CLK8 is sourced from the FPGA internally, the I/O delays effectively add to the ADC tco. I think, DATA should be sampled at the falling clock in this case.  

 

If you want to visualize the timing, you can operate SignalTap at the 200 MHz system clock or the 32 MHz PLL clock and acquire the respective ADC signals at the pins.
0 Kudos
Altera_Forum
Honored Contributor II
4,616 Views

FvM is correct. data_tmp should be transferred to tmp when count = 14 in my code rather than 13.

0 Kudos
Reply