Jump to content

    
nice_vladi

Ethernet коммутатор L2 буферизация и память

Recommended Posts

Всем здравствуйте.

Довелось заниматься разработкой Ethernet 100 коммутатора L2 на ПЛИС. Здесь, для примера возьмём 4х портовый.

 

Моя проблема - я не до конца понимаю, как должен буферизироваться трафик. То, как я сделал: В каждом порте стоит 2 FIFO - TX/RX. Пакет пришёл, обработался, передался из буфера порта 1 в буфер порта 2. Вроде, всё ок, всё работает. Но возникают проблемы:

1. Блокировка. Текущий пакет не будет передан, пока занят выходной буфер. Это тормозит все последующие пакеты в очереди.

2. Нерациональное использование памяти. В каждом из портов используются буфера 1520*количество портов байт, для того, что бы успевать обработать все, без потери пакеты.

3. Большая утилизация памяти ПЛИС. Логики используется не так уж много, но вот затраты памяти растут катастрофически. И, главное, мне непонятно, как можно использовать внешнюю память?

 

Если 1-2 как-то терпимо, то 3й пункт очень мешает. Я знаю про round-robin и тому подобные вещи, но не могу понять, как это применить без огромного перетактирования. Например, у меня внутренние шины работают на 125 МГц*8 бит. "В лоб" это означает, что мне нужно (125*количество портов) МГц частоты памяти, что бы сложить туда все порты параллельно.

 

Так же вся внешняя память, как правило, 1-2 портового исполнения. Как туда затолкнуть чтение-запись из 4, 8 портов параллельно?

 

В интернетах нашел много общих слов на эту тему: виртуальные входные очереди, динамически распределяемая память. Но конкретного примера, в котором описан алгоритм работы с единым буфером для множественных каналов БЕЗ большого перетактирования не встретил. Да и вообще, что у буржуев, что у нас вразумительных и четких примеров не получилось найти.

 

Собственно, основной вопрос к знающим людям: как, вообще, это делается? Как работать с памятью в коммутаторе?

 

Спасибо.

Share this post


Link to post
Share on other sites

Переделайте внутренние шины на размер 2*N*8 бит, где N - количество портов. К каждому из портов у Вас будет дополнительно два небольших FIFO данных, с одной стороны 2*N*8, с другой 8 бит. Почему есть множитель 2 - потому что каждый порт в обе стороны работает.

В терминах аппаратуры лучше работать, видимо, вообще с FIFO. Общее поле памяти разбивается на блоки размером с пакет (размер шины внутренней памяти - 2*N*8).

Есть FIFO номеров пустых блоков, которое изначально заполнено номерами всех доступных блоков.

Если какому-то каналу нужен блок (у него RXDV активизировался), то он вычитывает номер нового блока из этого FIFO. Конечно, т.к. FIFO номеров один, то должен быть предусмотрен арбитр. Но время на этот доступ есть, т.к. заполняется FIFO данных, о котором я говорил в самом начале.

Далее, у каждого канала есть FIFO номеров для передачи.

После заполнения блока определяется, в какой канал его передавать, и в FIFO номеров выходного канала заталкивается номер блока.

Ну и передатчик тривиален - если появился номер в FIFO, то передаем блок с этим номером, после окончания передачи запихиваем уже освободившийся номер в FIFO пустых блоков.

Итого нужно реализовать самое главное - FIFO, у которого может быть много источников и много приемников. С арбитражем, естественно.

Хотя пожалуй даже и арбитр не нужен, ибо он сложный. Просто работа с FIFO номеров делится на N тактов, в каждом такте доступ имеет только один канал. Да и работа с общим полем памяти разбивается точно так же, только 2*N, потому что отдельно прием и передача.

В общем да, самый простой способ такой. Скажем, у Вас 8 портов. Тогда вся работа с памятью происходит за 16 тактов. В каждом такте доступ ко всем FIFO и общему полю памяти разрешен каналу n*2+dir, где n - номер, dir - направление (0 или 1). Маленькие FIFO данных в каждом канале позволяют дождаться нужного слота доступа без потери данных. Ширина шин данных общего поля памяти будет 2*8*8=128 бит. Все.

Share this post


Link to post
Share on other sites
1 hour ago, Rst7 said:

... Все.

Спасибо большое, очень хорошее и доступное описание. Хочу уточнить пару нюансов:

Quote

В терминах аппаратуры лучше работать, видимо, вообще с FIFO. Общее поле памяти разбивается на блоки размером с пакет (размер шины внутренней памяти - 2*N*8).

Если чуть усложнить - можно передавать указатели на начало/конец пакета, что бы не было пустых слов в блоке. Но в приведенном вами простейшем случае мы бьём общий пулл на много блоков, фиксированной длины (максимальный размер пакета), я правильно понял?

Quote

К каждому из портов у Вас будет дополнительно два небольших FIFO данных, с одной стороны 2*N*8, с другой 8 бит

Здесь получается преобразование разрядности? А как обрабатываются пакеты длины не кратной 2*N*8? Какой-то дополнительный маркер вводится?

Quote

Да и работа с общим полем памяти разбивается точно так же, только 2*N, потому что отдельно прием и передача.

Это означает, что нужно либо увеличить частоту работы с общим полем в 2 раза относительно шины данных, либо потерять половину пропускной способности? Ну, или предположить, что общее поле двухпортовое.

Share this post


Link to post
Share on other sites
29 minutes ago, nice_vladi said:

Если чуть усложнить - можно передавать указатели на начало/конец пакета, что бы не было пустых слов в блоке. Но в приведенном вами простейшем случае мы бьём общий пулл на много блоков, фиксированной длины (максимальный размер пакета), я правильно понял?

Это очень плохой план делать аналог менеджера кучи с произвольным размером блоков. а) Замучаетесь реализовывать, а когда реализуете - б) фрагментация потом все убъет. В таких случаях всегда делаются блоки фиксированного размера, а дальше пока проще для начала реализовать вариант с блоками максимальной длины. Потом можно будет приделать туда блоки размером короче, чем максимальный пакет, скажем, по 128 байт. Или по 256.

31 minutes ago, nice_vladi said:

Здесь получается преобразование разрядности? А как обрабатываются пакеты длины не кратной 2*N*8? Какой-то дополнительный маркер вводится?

Ну так как основное поле памяти - это не FIFO, а обычная RAM, то можно просто в первой ячейке блока хранить его длину в байтах. Приняли пакет, записали в нулевую ячейку размер, а потом номер пакета положили в FIFO номеров нужного передатчика. Передатчик взял из своего FIFO номеров номер пакета, прочитал первую ячейку, сохранил в счетчик количество байт и пошел выдавать остальное.

33 minutes ago, nice_vladi said:

Это означает, что нужно либо увеличить частоту работы с общим полем в 2 раза относительно шины данных, либо потерять половину пропускной способности? Ну, или предположить, что общее поле двухпортовое.

Зачем? Не надо увеличивать частоту. Вы разрядностью все делаете. Увеличиваете ее так (*2*N), что общая пропускная способность осталось той же.

Share this post


Link to post
Share on other sites
Quote

Зачем? Не надо увеличивать частоту. Вы разрядностью все делаете. Увеличиваете ее так (*2*N), что общая пропускная способность осталось той же.

@Rst7 для 8 портов тогда будет 8*8*2 = 128 бит, как вы писали выше. Для 16 - уже 256. Это ок? На мой взгляд, 16 портов на 125 МГц даже для современных ПЛИС не так уже легко... Повторюсь, я не знаю, как правильно, просто рассуждаю. Бесконечно увеличивать разрядность мы же не сможем.

Про разбиение буфера на какие-то атомарные куски - круто, спасибо.

Share this post


Link to post
Share on other sites
2 hours ago, nice_vladi said:

Это ок?

Ну а какие проблемы? Шин, конечно, много, к сожалению, z-состояние внутри FPGA недоступно, с ним, конечно, намного меньше. Увеличение разрядности упирается только в ресурсы FPGA как по лутам, так и по трассировке. Которых явно больше, чем мегагерц.

Share this post


Link to post
Share on other sites
5 minutes ago, Rst7 said:

Ну а какие проблемы? Шин, конечно, много, к сожалению, z-состояние внутри FPGA недоступно, с ним, конечно, намного меньше. Увеличение разрядности упирается только в ресурсы FPGA как по лутам, так и по трассировке. Которых явно больше, чем мегагерц.

Спасибо за разъяснения, буду обдумывать

Share this post


Link to post
Share on other sites

Еще может помочь описание свичей от марвел. Не знаю как сейчас, а раньше там была описана организация движка. Сам свич был со внутренней памятью, порядка 256килобайт. ЕМНИП, все адресное пространство делилось на блоки по 64 байта, туда укладывались данные и к ним списки контекстов, с дескрипторами того, что храниться в памяти)  Можно поискать старую документацию)

Share this post


Link to post
Share on other sites

Наткнулся в сети на довольно интересные материалы по теме, да ещё и на русском. Судя по всему, будет и продолжение этих статей. Ссылки:

https://nag.ru/articles/reviews/106515/intellekt-seti-kak-peredat-paket.html

https://nag.ru/articles/reviews/106886/pamyat-seti-kak-sohranit-paket.html

Так же нашёл очень интересную книжку которая затрагивает в том числе и организацию и структуру свитчей:

Network Routing. Algorithms, Protocols, and Architectures. Deepankar Medhi, Karthikeyan Ramasamy

Share this post


Link to post
Share on other sites

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

С передачей указателя всё понятно. Указатель передаётся в очередь каждого из портов, кроме порта-источника. А вот как *правильно* возвращать этот указатель в список свободных?

Что я пробовал:

1. Передача пакетов блокируется, пока не будет передан широковещательный пакет. Работает, но однозначно плохой вариант, не обсуждаю.

2. К указателям на пакет добавляется флаг broadcast. С этим флагом передающие порты не возвращают указатель в общий пул. Вместо этого есть отдельный буфер для указателей, относящихся к широковещательным пакетам. Это буфер копит указатели и возвращает их по таймауту в общий пул. Основной недостаток: необходимо выбрать таймаут *гарантирующий*, что пакет будет разослан во все порты до того, как указатель вернётся в общий пул и, возможно, будет использован каким-либо приёмным портом. При большом количестве широковещательных пакетов память очень быстро заканчивается.

Собственно, вопрос: как правильно обрабатывать указатель на пакет, предназначенный для широковещательной рассылки? Есть ли общепринятые правила?

Share this post


Link to post
Share on other sites
On 7/3/2020 at 10:39 AM, nice_vladi said:

Есть ли общепринятые правила?

Конечно. Заводите у блока N флагов broadcast (N - количество портов). Порт, в который пришел широковещательный пакет, выставляет все флаги в 1, кроме собственного, и засовывает этого номер блока в fifo всем остальным портам. Каждый порт после отправки содержимого блока сбрасывает в 0 только флаг, соответствующий своему номеру и проверяет, если все флаги стали равны 0, то отправляет блок в список свободных. Если не все флаги равны 0, то с блоком ничего не делается. Освободит его последний, кто его передал.

Share this post


Link to post
Share on other sites
23 hours ago, Rst7 said:

Конечно. Заводите у блока N флагов broadcast (N - количество портов). Порт, в который пришел широковещательный пакет, выставляет все флаги в 1, кроме собственного, и засовывает этого номер блока в fifo всем остальным портам. Каждый порт после отправки содержимого блока сбрасывает в 0 только флаг, соответствующий своему номеру и проверяет, если все флаги стали равны 0, то отправляет блок в список свободных. Если не все флаги равны 0, то с блоком ничего не делается. Освободит его последний, кто его передал.

Интересная схема. Меня чуть смущает, что в таком случае усложняется арбитр доступа к памяти. Получается, порты должны ещё обновлять содержимое памяти (внутренний заголовок с флагами).

 

Сейчас сделал немного по-другому: модифицировал свой второй вариант. Только теперь он работает не по таймауту, а дожидается наполнения <кол-во портов> буферов с указателями на широковещательный пакет.

 

Получается: принял широковещательный пакет -> выставил ему бит "широковещательный" -> засунул во все выходные фифошки -> после передачи все пакеты с флагом "широковещательный" проходят через модуль, в котором <кол-во портов> фифо -> когда ВСЕ фифо в этом модуле не пустые оттуда вычитывается указатель и передаётся в список свободных.

 

Здесь подразумевается, что указатели ВСЕГДА выходят из передающих портов в "правильном" порядке - fifo. Теоретически, если что-то сломается в выходном порте указатель может потеряться. На этот случай сделал проверку равенства считанных указателей. Но, скорее всего, переделаю по вашей подсказке. Это мне видится более правильным решением, которое "не ломается". Но нужно еще подумать.

 

В любом случае, спасибо, взял на вооружение.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.