2
votes

I want to send a 128-bit value to an FPGA, bit-by-bit, from an AVR. The FPGA expects the following transaction to occur:
1) RST signal to clear the FPGA's 128-bit register. Goes high, then goes low. A "CLOCK" signal is set to low.
2) An ENABLE bit is set to high to indicate that a transfer is underway.
3) An INPUT bit is set to the value of arrayOfBinaryValues[i].
4a) CLOCK signal goes high. On leading edge, the value of INPUT is stored in position i on the FPGA.
4b) CLOCK signal goes low.
4c) INPUT bit set to next value of arrayOfBinaryValues[i]
[Repeat 4a-4c until whole array is sent]

So, I wrote a function to get this done. It happens in stages. Step 1, the user inputs a 32-character value that is stored as a c-string. Since these are characters, I have to convert them to corresponding hex values:

void transmitToFPGA(unsigned char hash[32]) {
    // convert the characters to their corresponding hex values
    unsigned char hash_hex[32];
    unsigned char j = 0;

    SET_BIT(FPGA_DDR,MD5_RST); // sets reset bit high

    for (unsigned char i=0; i<32; i++) {
        switch (hash[i]) {
            case '0': hash_hex[i] = 0x00; break;
            case '1': hash_hex[i] = 0x01; break;
            case '2': hash_hex[i] = 0x02; break;
            case '3': hash_hex[i] = 0x03; break;
            case '4': hash_hex[i] = 0x04; break;
            case '5': hash_hex[i] = 0x05; break;
            case '6': hash_hex[i] = 0x06; break;
            case '7': hash_hex[i] = 0x07; break;
            case '8': hash_hex[i] = 0x08; break;
            case '9': hash_hex[i] = 0x09; break;
            case 'A': hash_hex[i] = 0x0a; break;
            case 'B': hash_hex[i] = 0x0b; break;
            case 'C': hash_hex[i] = 0x0c; break;
            case 'D': hash_hex[i] = 0x0d; break;
            case 'E': hash_hex[i] = 0x0e; break;
            case 'F': hash_hex[i] = 0x0f; break;
            default:  hash_hex[i] = 0x00; break;
        }
    }

Then I tried converting the corresponding bits into an array of binary values like so:

   unsigned char hash_bin[128];
    for (unsigned char i=0; i<32; i++) {
        hash_bin[j] = hash_hex[i] & 0x01; j++;
        hash_bin[j] = hash_hex[i] & 0x02; j++;
        hash_bin[j] = hash_hex[i] & 0x04; j++;
        hash_bin[j] = hash_hex[i] & 0x08; j++;
    }

Then I perform the transmission

    // conduct transmission
    CLR_BIT(FPGA_DDR,MD5_RST);  // clear reset
    delay_ms(1);
    CLR_BIT(FPGA_DDR,AVR_CLK);         // AVR_CLK = 0
    delay_ms(1);
    CLR_BIT(FPGA_DDR,AVR_EN);          // AVR_EN = 0
    delay_ms(1);
    CLR_BIT(FPGA_DDR,AVR_IN);        // AVR_IN = 0
    delay_ms(1);
    for (unsigned char i=0; i<128; i++) {
        CLR_BIT(FPGA_DDR,AVR_CLK);     // AVR_CLK = 0
        delay_ms(1);
        SET_BIT(FPGA_DDR,AVR_EN);      // AVR_EN = 1
        delay_ms(1);
        if (hash_bin[i] == 0) {         // AVR_IN = hash_bin[i]
            CLR_BIT(FPGA_DDR,AVR_IN);
        } else {
            SET_BIT(FPGA_DDR,AVR_IN);
        }
        delay_ms(1);
  t      SET_BIT(FPGA_DDR,AVR_EN);      // AVR_CLK = 1
        delay_ms(1);
    }
}

Unfortunately, this doesn't seem to work and I'm not entirely sure why. I suspect the way I perform conversion is not working properly. Does anyone have any insights?

edit: This is the VHDL module this code communicates with:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity SPI_Slave is
    Port ( AVR_IN   :   in      STD_LOGIC;
           AVR_CLK  :  in   STD_LOGIC;
           RST      :   in      STD_LOGIC;
              ENABLE        :   in  STD_LOGIC;
              READY         :   out     STD_LOGIC;
           HASH_OUT     :   out     STD_LOGIC_VECTOR (127 downto 0) := x"00000000000000000000000000000000" );
end SPI_Slave;

architecture Behavioral of SPI_Slave is

    shared variable count : integer := 0;
    signal hash : std_logic_vector(127 downto 0);   

begin

    PROCESS(AVR_CLK, ENABLE)
    BEGIN
        IF (ENABLE = '1') THEN                  -- If ENABLE is HIGH
            IF (rising_edge(AVR_CLK)) THEN  -- If CLK goes HIGH
                IF (RST = '1') THEN             -- If RST is HIGH
                    hash <= x"00000000000000000000000000000000"; -- then zero HASH_OUT and count
                    count := 0;
                    READY <= '0';               
                ELSE                                -- Otherwise, if RST is LOW
                    IF (count > 126) THEN
                        hash(count) <= AVR_IN;
                        HASH_OUT <= hash (127 downto 0);
                        READY <= '1';
                        count := count + 1;
                    ELSE 
                        hash(count) <= AVR_IN;
                        count := count + 1;
                        READY <= '0';
                    END IF;
                END IF;
            END IF;
        END IF;
    END PROCESS;





end Behavioral;
1
Are you sure your clocking sequence is correct? The instruction says you should write the data and then strobe the clock hi and low. - Weather Vane
I tested the clocking sequence in a VHDL testbench and from all appearances it was correct. I could re-examine that aspect of it if you see no issues with the C as written. This is all custom modules I wrote. Prolly could have done SPI, but this is a bit of a hack and personal exercise. - Bradley Evans
You have not brought the clock low at the end of the loop but at the start. Where clause 4a) says "the output is written...." that means "the output you have already written gets transmitted", and not "this is when you write the data." - Weather Vane
The clock is brought low at the beginning of the loop, then the input bit is set, then the clock is brought high. The FPGA sets the bit on the rising edge. My mistake if my description didn't reflect that. - Bradley Evans
That step where you convert "bits into an array of binary values" makes no sense to me. You should be able to send bits directly from the 128-bit array you end up with after the hex-to-binary conversion. - unwind

1 Answers

1
votes

If I were you, I'd write the function so that it takes the 128 bits in binary.

Also, I suspect your bit setting is incorrect (as of writing this answer). I've tried to explain my logic in the comments in the code:

#include <stdint.h>
#include <errno.h>

#define FPGA_LONG_DELAY() delay_ms(1)
#define FPGA_DELAY()      delay_ms(1)

int fpga_write_128bit(const uint8_t data[16])
{
    int i;

    if (!data)
        return EINVAL;

    /* Ensure non-active state. */
    CLR_BIT(FPGA_DDR, AVR_EN);
    CLR_BIT(FPGA_DDR, AVR_CLK);
    CLR_BIT(FPGA_DDR, MD5_RST);
    CLR_BIT(FPGA_DDR, AVR_IN);
    FPGA_LONG_DELAY();

    /* Prepare for reset on rising clock edge. */
    SET_BIT(FPGA_DDR, AVR_EN);
    SET_BIT(FPGA_DDR, MD5_RST);
    FPGA_DELAY();

    /* Pulse clock (rising and trailing edges). */
    SET_BIT(FPGA_DDR, AVR_CLK);
    FPGA_DELAY();
    CLR_BIT(FPGA_DDR, AVR_CLK);

    /* Set reset low, and prepare for sending the data bits. */
    CLR_BIT(FPGA_DDR, MD5_RST);

    for (i = 0; i < 128; i++) {

        /* AVR_CLK is low and AVR_EN high at this point. */

        /* Set data; LSB of first byte first. */
        if ((data[i / 8] & (1U << (i & 7))))
            SET_BIT(FPGA_DDR, AVR_IN);
        else
            CLR_BIT(FPGA_DDR, AVR_IN);

        /* Ensure data bit state is stable before rising edge of clock. */
        FPGA_DELAY();

        /* Clock pulse (both rising and trailing edges) */
        SET_BIT(FPGA_DDR, AVR_CLK);
        FPGA_DELAY();
        CLR_BIT(FPGA_DDR, AVR_CLK);
    }

    /* All bits transferred, clock is low. */

    CLR_BIT(FPGA_DDR, AVR_IN);   /* Irrelevant, but let's leave a known state behind */
    CLR_BIT(FPGA_DDR, AVR_EN);

    return 0;
}

You probably noticed that I used two macros, FPGA_LONG_DELAY() and FPGA_DELAY() in there. As they are defined now, sending a single 128 bit value takes 259ms, or a bit over a quarter second. You can probably define both as empty strings; if not, at least much shorter than 1ms.

Test the above function that it works as you need it to.

If you still want a function that takes the 128-bit word as a string, just create a wrapper function that parses the hex value, then calls the above function.

The above function sends the least significant bit of the first byte first. If we consider the hex string as a single 128-bit number, then we need to parse it from right to left, with the first (rightmost) character denoting the least significant nibble (four bits).

If you want the VHDL HASH_OUT states to be

HASH_OUT(   0 ) = 0
HASH_OUT(   1 ) = 0
HASH_OUT(   2 ) = 0
HASH_OUT(   3 ) = 0
HASH_OUT(   4 ) = 0
HASH_OUT(   5 ) = 0
HASH_OUT(   6 ) = 0
HASH_OUT(   7 ) = 0

HASH_OUT(   8 ) = 1
HASH_OUT(   9 ) = 0
HASH_OUT(  10 ) = 0
HASH_OUT(  11 ) = 0
HASH_OUT(  12 ) = 0
HASH_OUT(  13 ) = 0
HASH_OUT(  14 ) = 0
HASH_OUT(  15 ) = 0

HASH_OUT(  16 ) = 0
HASH_OUT(  17 ) = 1
HASH_OUT(  18 ) = 0
HASH_OUT(  19 ) = 0
HASH_OUT(  20 ) = 0
HASH_OUT(  21 ) = 0
HASH_OUT(  22 ) = 0
HASH_OUT(  23 ) = 0

HASH_OUT(  24 ) = 1
HASH_OUT(  25 ) = 1
HASH_OUT(  26 ) = 0
HASH_OUT(  27 ) = 0
HASH_OUT(  28 ) = 0
HASH_OUT(  29 ) = 0
HASH_OUT(  30 ) = 0
HASH_OUT(  31 ) = 0

and so on, up to

HASH_OUT( 120 ) = 1
HASH_OUT( 121 ) = 1
HASH_OUT( 122 ) = 1
HASH_OUT( 123 ) = 1
HASH_OUT( 124 ) = 0
HASH_OUT( 125 ) = 0
HASH_OUT( 126 ) = 0
HASH_OUT( 127 ) = 0

you can use the following macro and data array:

#define PACKBYTE(b0, b1, b2, b3, b4, b5, b6, b7) \
                (  (uint8_t)(!!(b0))       \
                | ((uint8_t)(!!(b1)) << 1) \
                | ((uint8_t)(!!(b2)) << 2) \
                | ((uint8_t)(!!(b3)) << 3) \
                | ((uint8_t)(!!(b4)) << 4) \
                | ((uint8_t)(!!(b5)) << 5) \
                | ((uint8_t)(!!(b6)) << 6) \
                | ((uint8_t)(!!(b7)) << 7) )

const uint8_t data[16] = {
     PACKBYTE( 0, 0, 0, 0, 0, 0, 0, 0 ),
     PACKBYTE( 1, 0, 0, 0, 0, 0, 0, 0 ),
     PACKBYTE( 0, 1, 0, 0, 0, 0, 0, 0 ),
     PACKBYTE( 1, 1, 0, 0, 0, 0, 0, 0 ),
     PACKBYTE( 0, 0, 1, 0, 0, 0, 0, 0 ),
     /* 10 PACKBYTE() lines omitted for brevity */
     PACKBYTE( 1, 1, 1, 1, 0, 0, 0, 0 )
};

Note that the !! in the macro is just two not operators. !!x evaluates to 0 if x is zero, and to 1 if x is nonzero. Unlike normally, the leftmost bit is the least significant, so that the array above has the bits in the same order as the HASH_OUT in the VHDL.

For the string-in-hex version, I'd recommend using a function to convert the character to decimal:

static uint8_t hex_digit(const char c)
{
    switch (c) {
    case '0':           return  0U;
    case '1':           return  1U;
    case '2':           return  2U;
    case '3':           return  3U;
    case '4':           return  4U;
    case '5':           return  5U;
    case '6':           return  6U;
    case '7':           return  7U;
    case '8':           return  8U;
    case '9':           return  9U;
    case 'A': case 'a': return 10U;
    case 'B': case 'b': return 11U;
    case 'C': case 'c': return 12U;
    case 'D': case 'd': return 13U;
    case 'E': case 'e': return 14U;
    case 'F': case 'f': return 15U;
    default:            return 255U; /* Invalid value. */
    }
}

int fpga_write_hex_string(const char *const hex)
{
    uint8_t data[16], hi, lo;
    int     i;

    if (!hex)
        return EINVAL;

    for (i = 0; i < 16; i++) {

        /* Note: we parse the input string in pairs of
                 characters, leftmost first, so that if
                 it happens to be short, we won't try
                 to access it past its end. */

        hi = hex_digit(hex[2*i]);
        if (hi > 15U)
            return EINVAL;

        lo = hex_digit(hex[2*i + 1]);
        if (lo > 15U)
            return EINVAL;

        /* The i'th pair of hex digits form the
           (15-i)'th byte value. */
        data[15 - i] = lo | (hi << 4);
    }

    return fpga_write_128bit(data);
}

You could of course just accept a binary string (composed of 128 characters of 0 or 1, in the same order as the VHDL HASH_OUT):

int fpga_write_bin_string(const char *const bin)
{
    uint8_t data[16] = {0};
    int     i;

    if (!bin)
        return EINVAL;

    for (i = 0; i < 128; i++)
        if (bin[i] == '1')
            data[i/8] |= 1U << (i & 7);
        else
        if (bin[i] != '0')
            return EINVAL;

    return fpga_write_128bit(data);
}

I wish I had one of these (AVR or ARM with an FPGA) to play with myself. So, obviously, all of the above code is untested. Feel free to use it as you wish (it's in public domain), but don't blame me if it breaks your board.