Skip to content

AMDS Data Sign Extension Error #448

@codecubepi

Description

@codecubepi

I think there may be an error with how the FPGA layer of the AMDS driver handles incoming data from the AMDS mainboard.

The Texas Instruments ADS8860 analog-to-digital converter on the sensors cards requires that the analog voltage measured is prescaled to a 0.0 - $$V_{REF}$$ range (we use 5V for $$V_{REF}$$, see sensor card schematics here in the AMDS repo). The ADC then outputs over SPI (to the AMDS board's STM MCU) an unsigned 16-bit number. The following chart shows how the card input voltage is prescaled for the ADC and then mapped to a 16-bit number (see page 21 of the ADC datasheet to verify).

Card Input Voltage ADC Analog Input Voltage ADC Output (16-bit unsigned)
500V 5V ($$V_{REF}$$) 0xFFFF
250V 3.75V 0xC000
0V 2.5V 0x8000
-250V 1.25V 0x4000
-500V 0V 0x0000

This 16-bit unsigned number is then sent from the AMDS STM MCU to its AMDC FPGA driver over UART as described here. It is at this point I believe the error occurs. The FPGA performs a sign-extension to 32-bits when placing the unsigned value into the output register, instead of a zero-extension.

always @(posedge S_AXI_ACLK) begin
if (~S_AXI_ARESETN)
adc_dout0 <= 32'b0;
else if (adc_uart0_done)
adc_dout0 <= {{16{my_adc_data0[15]}}, my_adc_data0};
end
always @(posedge S_AXI_ACLK) begin
if (~S_AXI_ARESETN)
adc_dout1 <= 32'b0;
else if (adc_uart0_done)
adc_dout1 <= {{16{my_adc_data1[15]}}, my_adc_data1};
end

The C code layer of the driver (amds_get_data()) then reads the full 32-bit signed value. This will cause any value over the middle of the range (unsigned numbers with a leading 1) to be treated incorrectly treated as negative, and the negative values (which in unsigned have a leading 0) to be treated as positive.

int amds_get_data(uint8_t port, amds_channel_e channel, int32_t *out)
{
uint32_t base_addr = amds_port_to_base_addr(port);
if (base_addr == 0) {
// This means an invalid port argument was passed
return FAILURE;
}
if (!is_amds_channel_in_bounds(channel)) {
return FAILURE;
} else {
*out = (int32_t)(Xil_In32(base_addr + channel * sizeof(uint32_t)));
return SUCCESS;
}
}

If all this is the case, I'm not sure how things have ever worked. As far as I can tell, this sign-extension has been performed since the first release revision of the "motherboard" driver back in AMDC-Firmware v1.0.0:

else if (is_dout_valid0 & is_dout_valid1) begin
adc_dout0 <= {{16{my_adc_data0[15]}}, my_adc_data0};
adc_dout1 <= {{16{my_adc_data1[15]}}, my_adc_data1};
adc_dout2 <= {{16{my_adc_data2[15]}}, my_adc_data2};
adc_dout3 <= {{16{my_adc_data3[15]}}, my_adc_data3};
adc_dout4 <= {{16{my_adc_data4[15]}}, my_adc_data4};
adc_dout5 <= {{16{my_adc_data5[15]}}, my_adc_data5};
adc_dout6 <= {{16{my_adc_data6[15]}}, my_adc_data6};
adc_dout7 <= {{16{my_adc_data7[15]}}, my_adc_data7};

When I was updating the "motherboard" driver to the Timing Manager-compatible "AMDS" driver in Summer 2024, I did not catch this issue as I was more concerned with the timing of sampling and data transmission, not with the data itself. So the issue persists into the current release (v1.4.x).

For now, a very simple quick-fix that we've applied locally in the C code layer of the AMDS driver is to mask the value read from the register so that only the last 16 bits are pulled into the C space, and then treat the value as unsigned.

int amds_get_data(uint8_t port, amds_channel_e channel, uint32_t *out)
{
    uint32_t base_addr = amds_port_to_base_addr(port);

    if (base_addr == 0) {
        // This means an invalid port argument was passed
        return FAILURE;
    }

    if (!is_amds_channel_in_bounds(channel)) {
        return FAILURE;
    } else {
        *out = (Xil_In32(base_addr + channel * sizeof(uint32_t))) & 0xFFFF;
        return SUCCESS;
    }
}

A better long-term fix would be to update the FPGA IP to do zero-extension instead.

 always @(posedge S_AXI_ACLK) begin 
     if (~S_AXI_ARESETN) 
         adc_dout0 <= 32'b0; 
     else if (adc_uart0_done) 
         adc_dout0 <= {16'b0, my_adc_data0}; 
 end 
 always @(posedge S_AXI_ACLK) begin 
     if (~S_AXI_ARESETN) 
         adc_dout1 <= 32'b0; 
     else if (adc_uart0_done) 
         adc_dout1 <= {16'b0, my_adc_data1}; 
 end 
// ... six other adc data channels ...

This issue was found by @psflann while prepping for the ECE 504 Drive Lab course at UW-Madison.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions