Прием Ethernet пакета платой Марсоход2

Плата Марсоход2 с шилдом Ethernet

К плате Марсоход2 у нас есть шилд – плата расширения с микросхемой приемопередатчика компании Realtek  

.

В принципе, для того, чтобы принимать Ethernet пакет с помощью этой микросхемы никаких особых действий делать не нужно. Конечно, микросхема сама по себе сложная, у нее есть внутренние регистры доступные для программирования всяких режимов и параметров. Но даже если всего этого программирования микросхемы не делать, то по умолчанию приемопередатчик сразу готов к работе и готов принимать и передавать пакеты данных.

Я сделал простой проект в среде Altera Quartus II для платы Марсоход2 и шилда Ethernet. Хочу рассказать про этот проект.

Вот так выглядит мой проект в среде Altera Quartus II на самом верхнем уровне:

top sch

Чтобы микросхема приемопередатчика Ethernet начала работать достаточно подать на нее тактовую частоту 25МГц через сигнал RTL_XI, и подключить контакты микросхемы rtl_resetb к логической единице, а rtl_mdc к логическому нулю.

После этого, как только пакет по сети приходит мы увидим на входном сигнале RTL_RXDV (Data Valid) единицу, обозначающию, что на линиях RTL_RXD в данный момент есть актуальные принятые данные. Шина RTL_RXD четырехбитная. Частота передачи 25МГц. Вот и получается скорость передачи по сети 25*4=100Мбит/сек.

Нужно заметить, что тактировать прием данных нужно от специального тактового сигнала RTL_RXCLK, приходящего из микросхемы приемопередатчика. Это будут те же самые 25МГц, но, возможно, они будут как-то сдвинуты по фазе относительно той частоты, которую мы подали на микросхему RTL8201BL через свой сигнал RTL_XI.

Теперь есть еще один важный вопрос: «Что такое прием пакета из сети?».
Правильней всего считать пакет принятым успешно, когда он полностью принят от первого и до последнего байта и проверена его контрольная сумма.

Формат пакета Ethernet приведен ниже.

frame fmt
 
Здесь:
•    PRE – преамбула 7 байт, все байты в преамбуле 0x55 (в шестнадцатеричном виде).
•    SFD – Start-of-Frame Delimeter, один байт 0xD5.
•    DA – Destination Address, MAC адрес назначения, 6 байт.
•    SA –  Source Address, MAC адрес источника пакета, 6 байт.
•    TYPE - Тип пакета, 2 байта. Для IP пакетов 0x0800, для ARP – 0x0806.
•    DATA, PAD - Данные пакета переменной длины.
•    FCS – Frame Check Sequence, 4 байта контрольной суммы CRC32.

Для анализа приходящих или отправленных пакетов удобно пользоваться известной программой Wireshark – это отличный свободный инструмент для анализа сетевого трафика. Правда Wireshark не показывает преамбулу и SFD и контрольную сумму FCS.

Увидеть пакет Ethernet полностью можно посмотрев реальные сигналы от микросхемы приемопередатчика Realtek. Для этого, можно к альтеровскому проекту подключить модуль SignalTap.

Преамбула в пакете Ethernet, Altera SignalTap, плата Марсоход2
В проекте есть модуль eth_receiver.v написаный на Verilog HDL.
В этом модуле и ведется обработка принимаемого пакета.
Прием данных осуществляется в 32х битный регистр receiver_reg32. Младшие тетрады приходят первые. Поэтому:


reg [31:0]receiver_reg32;
always @(posedge rtl_clk)
    if(rxdv)
        receiver_reg32 <= { rxd, receiver_reg32[31:4] };

assign rbyte = receiver_reg32[31:24];


Только что принятый байт находится в старшем байте этого 32х битного регистра receiver_reg32.

В регистре ncounter ведется подсчет числа принятых тетрад. Нам нужно их считать, так как подсчет контрольной суммы должен начаться только после преамбулы и SFD, через 16 тетрад. Кроме того, из ncounter получается адрес для записи байта пакета во внешнюю память (если когда нибудь нам это будет нужно).

В общем:


reg [11:0]ncounter;
always @(posedge rtl_clk)
    if(~rxdv_)
        ncounter <= 0;
    else
    if(rxdv_)
        ncounter <= ncounter + 1;

assign rbyte_addr = ncounter[11:1];


Подсчет контрольной суммы – это отдельный большой вопрос. Я честно провозился с этим довольно долго. Саму функцию подсчета CRC32 самому писать не нужно. Есть генераторы кода для контрольных сумм. Например, вот здесь есть онлайн инструмент: http://www.easics.be/webtools/crctool - я как раз им и пользовался. Задаю параметры нужной контрольной суммы и нажимаю кнопку Generate Verilog. Получаю код функции на языке Verilog.


  // polynomial: (0 1 2 4 5 7 8 10 11 12 16 22 23 26 32)
  // data width: 8
  // convention: the first serial bit is D[7]
  function [31:0] nextCRC32_D8;

    input [7:0] Data;
    input [31:0] crc;
    reg [7:0] d;
    reg [31:0] c;
    reg [31:0] newcrc;
  begin
    d = Data;
    c = crc;

    newcrc[0] = d[6] ^ d[0] ^ c[24] ^ c[30];
    newcrc[1] = d[7] ^ d[6] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[30] ^ c[31];
    newcrc[2] = d[7] ^ d[6] ^ d[2] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[26] ^ c[30] ^ c[31];
    newcrc[3] = d[7] ^ d[3] ^ d[2] ^ d[1] ^ c[25] ^ c[26] ^ c[27] ^ c[31];
    newcrc[4] = d[6] ^ d[4] ^ d[3] ^ d[2] ^ d[0] ^ c[24] ^ c[26] ^ c[27] ^ c[28] ^ c[30];
    newcrc[5] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[27] ^ c[28] ^ c[29] ^ c[30] ^ c[31];
    newcrc[6] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[2] ^ d[1] ^ c[25] ^ c[26] ^ c[28] ^ c[29] ^ c[30] ^ c[31];
    newcrc[7] = d[7] ^ d[5] ^ d[3] ^ d[2] ^ d[0] ^ c[24] ^ c[26] ^ c[27] ^ c[29] ^ c[31];
    newcrc[8] = d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[0] ^ c[24] ^ c[25] ^ c[27] ^ c[28];
    newcrc[9] = d[5] ^ d[4] ^ d[2] ^ d[1] ^ c[1] ^ c[25] ^ c[26] ^ c[28] ^ c[29];
    newcrc[10] = d[5] ^ d[3] ^ d[2] ^ d[0] ^ c[2] ^ c[24] ^ c[26] ^ c[27] ^ c[29];
    newcrc[11] = d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[3] ^ c[24] ^ c[25] ^ c[27] ^ c[28];
    newcrc[12] = d[6] ^ d[5] ^ d[4] ^ d[2] ^ d[1] ^ d[0] ^ c[4] ^ c[24] ^ c[25] ^ c[26] ^ c[28] ^ c[29] ^ c[30];
    newcrc[13] = d[7] ^ d[6] ^ d[5] ^ d[3] ^ d[2] ^ d[1] ^ c[5] ^ c[25] ^ c[26] ^ c[27] ^ c[29] ^ c[30] ^ c[31];
    newcrc[14] = d[7] ^ d[6] ^ d[4] ^ d[3] ^ d[2] ^ c[6] ^ c[26] ^ c[27] ^ c[28] ^ c[30] ^ c[31];
    newcrc[15] = d[7] ^ d[5] ^ d[4] ^ d[3] ^ c[7] ^ c[27] ^ c[28] ^ c[29] ^ c[31];
    newcrc[16] = d[5] ^ d[4] ^ d[0] ^ c[8] ^ c[24] ^ c[28] ^ c[29];
    newcrc[17] = d[6] ^ d[5] ^ d[1] ^ c[9] ^ c[25] ^ c[29] ^ c[30];
    newcrc[18] = d[7] ^ d[6] ^ d[2] ^ c[10] ^ c[26] ^ c[30] ^ c[31];
    newcrc[19] = d[7] ^ d[3] ^ c[11] ^ c[27] ^ c[31];
    newcrc[20] = d[4] ^ c[12] ^ c[28];
    newcrc[21] = d[5] ^ c[13] ^ c[29];
    newcrc[22] = d[0] ^ c[14] ^ c[24];
    newcrc[23] = d[6] ^ d[1] ^ d[0] ^ c[15] ^ c[24] ^ c[25] ^ c[30];
    newcrc[24] = d[7] ^ d[2] ^ d[1] ^ c[16] ^ c[25] ^ c[26] ^ c[31];
    newcrc[25] = d[3] ^ d[2] ^ c[17] ^ c[26] ^ c[27];
    newcrc[26] = d[6] ^ d[4] ^ d[3] ^ d[0] ^ c[18] ^ c[24] ^ c[27] ^ c[28] ^ c[30];
    newcrc[27] = d[7] ^ d[5] ^ d[4] ^ d[1] ^ c[19] ^ c[25] ^ c[28] ^ c[29] ^ c[31];
    newcrc[28] = d[6] ^ d[5] ^ d[2] ^ c[20] ^ c[26] ^ c[29] ^ c[30];
    newcrc[29] = d[7] ^ d[6] ^ d[3] ^ c[21] ^ c[27] ^ c[30] ^ c[31];
    newcrc[30] = d[7] ^ d[4] ^ c[22] ^ c[28] ^ c[31];
    newcrc[31] = d[5] ^ c[23] ^ c[29];
    nextCRC32_D8 = newcrc;
  end
  endfunction


Функция вычисляет следующее значение контрольной суммы при добавлении новых данных. Таким образом, вычисление происходит на лету: пришел новый байт – перевычислили контрольную сумму.

Как говорится дъявол кроется в деталях. При вычислении контрольной суммы нужно учесть, что:

•    Начальное значение для регистра crc32_, хранящего текущую контрольную сумму должно быть 0FFFFFFFFh.
•    Перевычислять контрольную сумму можно либо по получении новой тетрады (4 бита), либо по приему двух тетрад (8 бит, байт). Лучше - по приему байта, так как мы не знаем точно, где заканчивается пакет, какой он длины. Даже когда уже в пакете пошли собственно байты FCS (контрольной суммы) мы все равно продолжаем вычислять c ними CRC32, хотя это уже и не нужно. Нужно хранить последние 4 вычисленных CRC32, чтобы можно было бы сравнить CRC32[n-4] с полученной контрольной суммой. Если же вычислять контрольную сумму Ethernet пакета по каждой тетраде, то хранить придется последние 8 вычисленных CRC32.
•    Принятый байт нужно передавать функции вычисляющей CRC32 развернутым, the first serial bit is D[7]. Старший бит – на самом деле это младший и наоборот.
•    Вычисленная контрольная сумма должна быть развернута: младний бит – э то старший и наоборот.
•    Вычисленная контрольная сумма должна быть инвертирована (XOR 0xFFFFFFFF).

Таким образом, контрольная сумма crc32_ вычисляется по мере приема новых байтов из сети:


reg [31:0]crc32_;
always @(posedge rtl_clk)
    if(rbyte_valid)
    begin
        if(ncounter<16)
            crc32_ <= 32'hFFFFFFFF;
        else
            crc32_ <= nextCRC32_D8( rrbyte , crc32_ );
    end


Потом храним последние 4 вычисленных CRC32:


//reverse bits of CRC32
integer i;
always @*
begin
    for ( i=0; i < 32; i=i+1 )
        crc32[i] = crc32_[31-i];
end

reg [31:0]crc32_0;
reg [31:0]crc32_1;
reg [31:0]crc32_2;
reg [31:0]crc32_3;
always @(posedge rtl_clk)
    if(rbyte_valid)
    begin
        crc32_3 <= crc32_2;
        crc32_2 <= crc32_1;
        crc32_1 <= crc32_0;
        crc32_0 <= crc32;
    end


И по окончании пакета сравниваем вычисленную контрольную сумму (регистр crc32_3) и принятую  (регистра receiver_reg32):


assign crc32_ok = (&(crc32_3^receiver_reg32)) & rxdv_ & (~rxdv);


На снимке из SignalTap  видно откуда появляется сигнал crc32_ok.

CRC32 в пакете Ethernet, Altera SignalTap, плата Марсоход2
Сравниваются числа 4DBDE600h и B24219FFh путем исключающего ИЛИ и последующего AND между всеми битами слова.

Следующий вопрос: «Какую практическую пользу можно извлечь из этого проекта?»
К сожалению, практически никакую.

Дело в том, что устройство может принимать предназначенные ему IP пакеты только если умеет отвечать пакетом на пакет. А мой проект пока никак не отвечает.

На самом деле, конечно, сейчас наша плата сможет принимать широковещательные пакеты – это пакеты "для всех" внутри локальной сети. У таких пакетов MAC адрес будет 0FFFFFFFFFFFFh.
 
Широковещательные пакеты так же бывают разных типов. Например, широковещательный UDP пакет кроме MAC адреса 0FFFFFFFFFFFFh будет еще иметь внутри данных пакета в специальных заголовках  IP адрес приемника 0xFFFFFFFF, то есть 255.255.255.255.

Широковещательные запросы ARP – это как раз тот минимальный уровень, который нужно обрабатывать. Если вы захотите, чтобы устройство откликалось, например, на IP адрес 10.9.3.15, то как минимум нужно, чтобы на широковещательный запрос ARP “кто имеет адрес 10.9.3.15” устройство отвечало «это мой адрес 10.9.3.15, и мой MAC xx.xx.xx,xx,xx,xx » В следующий раз пакет для нас придет уже конкретно на наш MAC адрес.

Без поддержки протокола ARP (ответы на запросы) нельзя реализовать другие протоколы типа UDP, TCP, DHCP.

Ну чтобы в моем проекте Altera Quartus II для платы Марсоход2 и Ethernet шилда был хоть какой-то смысл я решил таки принимать и обрабатывать широковещательные пакеты UDP. В проекте есть Verilog модуль udpr.v который анализирует принимаемый пакет.

Из всего UDP пакета я проверяю только несколько байт: тип пакета, тип протокола, UDP порт 5050, и первые три байта пользовательских данных «ABC». Ниже на рисунке отмечено, какие поля пакета проверяются в плате Марсоход2 в моем проекте:

Пакет, принятый Wireshark

Младшие 4 бита от байта пакета по адресу 0x2D попадают на светодиоды платы Марсоход2.

Написал программу на С/С++ в Visual Studio, которая посылает бродкастом строку на UDP порт. Теперь могу удаленно по сети зажигать и гасить светодиоды на плате.

Весь проект и программу, посылающую UDP пакет, можно скачать на нашем сайте:

 


Добавить комментарий