Обновить
101
0
Иван @ishevchuk

Пользователь

Отправить сообщение
Шикарно! ЦОС на FPGA для меня всегда была неизведанной областью, но благодаря Вам смогу покопаться в чужих исходниках, спасибо!

Если не секрет, сколько времени заняло создание проекта без написания статьи?
Быстрые каналы это PCIe? Либо какие-то специфичные для GSM?
Если не секрет, чем у Вас в приборе занимается Cyclone (IV, V?) GX. Тоже просто «коммутацией» или какая-то обработка повешена? ЦОС, может быть?
Никакой, т.к. автор сам пишет «в железо он не синтезируется» :)
Однако, такой код хавают симуляторы)
Если точнее говорить, это ядро не самописное, а открытое.
Своими исходники MAC-ядра я не планирую делиться, т.к. за это на работе могут наругать)
На самописное ядро я дал ссылку в статье: opencores.org/project,xge_mac,overview (Из-за запятых ссылка может не выделиться цветом полностью).

Во-вторых, для тех трансиверов с которыми я работаю, у конторы, в которой я работаю, подписан NDA и никакую их настройку, разумеется, я не могу выкладывать.
А что Вы вкладываете в бытовой уровень? Какие задачи?
Возможно идет стагнация всего рынка процессоров, но я не верю, что в Intel/AMD сидят дураки. Возможно есть задачи, которые x86 не очень хорошо решает, но ведь это процессоры общего назначения. Его может использовать и домохозяйка, чтобы смотреть сериалы, сын-геймер, и муж-писатель. Если компания понимает, что x86 не подходит для решения их задач, никто не мешает сделать им свой ASIC, только это будет стоить недешево) Intel добавляет же специальные инструкции по запросу рынка: AES, к примеру. Всё регулирует рынок, и я ничего не вижу в этом плохого.

А вам действительно надо решать задачи на FPGA? Обычным процессором не обойтись?
Не могу с вами согласиться: технология FPGA не может спасти в этой ситуации.
FPGA действительно может решать некоторый класс задач лучше чем процессор. Но эти задачи должны быть относительно простые и поддаваться параллельности и конвейеризации. Я не очень представляю, как сделать на FPGA какую-нибудь сложную БД за адекватное время и деньги, чтобы по производительности она обогнала реализацию на процессоре. Я никогда не видел, чтобы Altera/Xilinx ставило перед собой задачу обогнать CPU на «всех» задачах, т.к. по тактовой частоте FPGA всегда будет проигрывать CPU, и они выезжают на других задачах.

Писать чудовищно тяжело.

Мне кажется, это миф. Конечно, реализовать какой-нибудь tcp-клиент/сервер на FPGA чудовищно тяжело по сравнению с java/python, где это делается в 10-50 строчек, но может не надо этого делать? А если надо, т.к. встал вопрос о производительности (хотите весь 10G забить TCP), то Вы продумываете архитектуру на уровне триггеров/блоков памяти, делаете свою обработку и за это естесственно платите временем/деньгами. Однако, это едиственный способ выжать максимальную производительности в любой задаче, которую решаете на FPGA. Есть вещи, в которых это критично. Вы не сможете продать сетевую карточку 10G, которая может принять только 5 Gbit/s, т.к. там написано не оптимально и о производительности не думали. Конечно, когда тактовые частоты в FPGA будут под несколько ГГц, то низкоуровневая оптимизация для этих задач уйдет на второй план)

Для упрощения разработки сейчас идет тренд использования OpenCL в FPGA, он показывает неплохие результаты на задачах, связанных с обработкой картинок/видео. Возможно, для этих задач разработка уйдет с HDL на OpenCL, если это будет выгодно рынку.
Чтобы была видна разница, я решил три теста одновременно показать в симуляторе:
test0 — данные идут с триггеров gen_x.
test1 — данные по @( posedge clk ) делаются.
test2 — данные по #20 выставляются.

Код:
Скрытый текст
module top_tb;

logic clk;
logic rst;

initial
  begin
    clk = 1'b0;
    forever 
      begin
        #10;
        clk = !clk;
      end
  end

initial
  begin
    rst = 1'b0;
    
    #2;
    rst <= 1'b1;

    #2;
    rst <= 1'b0;
  end

// ********** TEST 0 ***********
logic [7:0] test0_res_w;

logic [7:0] test0_x_1_w;
logic [7:0] test0_x_2_w;
logic [7:0] test0_x_3_w;
logic [7:0] test0_x_4_w;

gen_x gen_x(
  .clk_i                                  ( clk               ),
  .rst_i                                  ( rst               ),

  .x_1_o                                  ( test0_x_1_w       ),
  .x_2_o                                  ( test0_x_2_w       ),
  .x_3_o                                  ( test0_x_3_w       ),
  .x_4_o                                  ( test0_x_4_w       )
);

no_pipe_example no_pe_0(
  .clk_i                                  ( clk               ),

  .x_1                                    ( test0_x_1_w       ),
  .x_2                                    ( test0_x_2_w       ),
  .x_3                                    ( test0_x_3_w       ),
  .x_4                                    ( test0_x_4_w       ),

  .no_pipe_res_o                          ( test0_res_w       )
);

// ********** TEST 1 ***********
logic [7:0] test1_res_w;

logic [7:0] test1_x_1_w;
logic [7:0] test1_x_2_w;
logic [7:0] test1_x_3_w;
logic [7:0] test1_x_4_w;

initial
  begin
    test1_x_1_w = '0; 
    test1_x_2_w = '0;
    test1_x_3_w = '0;
    test1_x_4_w = '0;
    
    for( int i = 1; i < 10; i++ )
      begin
        @( posedge clk );
        test1_x_1_w <= i*1; 
        test1_x_2_w <= i*3;
        test1_x_3_w <= i*5;
        test1_x_4_w <= i*7;
      end
  end

no_pipe_example no_pe_1(
  .clk_i                                  ( clk               ),

  .x_1                                    ( test1_x_1_w       ),
  .x_2                                    ( test1_x_2_w       ),
  .x_3                                    ( test1_x_3_w       ),
  .x_4                                    ( test1_x_4_w       ),

  .no_pipe_res_o                          ( test1_res_w       )
);

// ********** TEST 2 ***********
logic [7:0] test2_res_w;

logic [7:0] test2_x_1_w;
logic [7:0] test2_x_2_w;
logic [7:0] test2_x_3_w;
logic [7:0] test2_x_4_w;

initial
  begin
    test2_x_1_w = '0; 
    test2_x_2_w = '0;
    test2_x_3_w = '0;
    test2_x_4_w = '0;
    
    #10;
    for( int i = 1; i < 10; i++ )
      begin
        test2_x_1_w = i*1; 
        test2_x_2_w = i*3;
        test2_x_3_w = i*5;
        test2_x_4_w = i*7;
        #20;
      end
  end

no_pipe_example no_pe_2(
  .clk_i                                  ( clk               ),

  .x_1                                    ( test2_x_1_w       ),
  .x_2                                    ( test2_x_2_w       ),
  .x_3                                    ( test2_x_3_w       ),
  .x_4                                    ( test2_x_4_w       ),

  .no_pipe_res_o                          ( test2_res_w       )
);
endmodule



Если что, no_pipe_example — тот, который под Icarus «адаптирован».

А вот и симуляция:
Скрытый текст



Несмотря на то, что по картинке test0_x*, test1_x*, test2_x* полностью идентичны, симулятор их совершенно по разному воспринимает: test0 и test1 полностью идентичны, а test2 выполняет всё на такт раньше)
Зависит от того, что Вы вкладываете в слово «реальная жизнь».
В симуляторе мы отсматриваем модель того, что происходит в жизни. Есть модель функциональная, а есть временная. Еще можно симулировать не RTL-код, а нетлист или гейты, и тогда еще более реальная жизнь происходит. Когда мы выбираем какой-то тип симуляции, то соглашаемся с теми допущениями, что в ней есть. Все эти картинки, это лишь то, как симулятор видит нашу схему на тех входных воздействиях, что в нее подали)
Разницу между функциональной и временной можно увидеть выше.

К сожалению, из литературы по верификации мне ничего не приходит в голову хорошого и простого. Есть «SystemVerilog for Verification», там есть глава Connecting Testbench and Design, но там чисто SystemVerilog'овские штуки раскрываются и может быть всё усложнено. Посмотрите примеры на testbench.in / asic-world.com, например, www.asic-world.com/examples/verilog/arbiter.html
Смысл заключается в том, что бы сделать так, что бы эти сигналы были синхронны с клоком.
Попробуйте следующий пример:
Модуль 1:
module gen_x(
  input clk_i,
  input rst_i,
  
  output logic [7:0] x_1_o,
  output logic [7:0] x_2_o,
  output logic [7:0] x_3_o,
  output logic [7:0] x_4_o

);

always @( posedge clk_i or posedge rst_i )
  if( rst_i )
    begin
      x_1_o <= '0;
      x_2_o <= '0;
      x_3_o <= '0;
      x_4_o <= '0;
    end
  else
    begin
      x_1_o <= x_1_o + 8'd1;
      x_2_o <= x_2_o + 8'd3;
      x_3_o <= x_3_o + 8'd5;
      x_4_o <= x_4_o + 8'd7;
    end
endmodule


Модуль 2:
module top(
  input clk_i,
  input rst_i,
  
  output logic [7:0] no_pipe_res_o
);
  
logic [7:0] x_1_w;
logic [7:0] x_2_w;
logic [7:0] x_3_w;
logic [7:0] x_4_w;

gen_x gen_x(
  .clk_i ( clk_i ),
  .rst_i ( rst_i ),

  .x_1_o ( x_1_w ),
  .x_2_o ( x_2_w ),
  .x_3_o ( x_3_w ),
  .x_4_o ( x_4_w )
);

no_pipe_example no_pipe(
  .clk_i ( clk_i )

  .x_1  (  x_1_w ),
  .x_2   ( x_2_w ),
  .x_3   ( x_3_w ),
  .x_4   ( x_4_w ),

  .no_pipe_res_o ( no_pipe_res_o )
);



Просто просимулируйте и скиньте сюда картинку) В этом примере x_1-4 идут с триггеров.
SystemVerilog это продолжение стандарта Verilog.
Туда добавили кучу новых плюшек, как синтезируемых, так и не синтезируемых, которые упрощают и убыстряют разработку.

Я предлагаю вынести создание клока в отдельный initial, что бы он не мешался.
В вашем примере вы явно не сообщаете симулятору, что x_* это сигналы, которые привязаны (либо синхронны) с tb_clk. Для того, что бы это указать можно использовать @( posedge tb_clk ) или сделать clocking block и писать что-то типа @cb.

Попробуйте так:
initial
  begin
    tb_clk = 1'b0;
    forever
      begin
         #100;
         tb_clk = !tb_clk;
      end
  end

initial
  begin
     @( posedge tb_clk );
     x_1 <= 4;
     x_2 <= 8;
     x_3 <= 15;
     x_4 <= 23;

     @( posedge tb_clk );
     x_1 <= 0;
     x_2 <= 0;
     x_3 <= 0;
     x_4 <= 0;
  end

Мне кажется, что вы подаете данные на tb конструкцией вида
initial
  begin
    #100;
    x_1 = 4;
    x_2 = 8;
    ...
  end


Попробуйте вот так:
initial
  begin
    @( posedge tb_clk )
    x_1 <= 4;
    x_2 <= 8;
    ...
  end
Мои все примеры на SystemVerilog)

always_ff @( posedge clk_i ) в этом примере эквивалентен always @( posedge clk_i ).
Попробуйте пример из сообщения чуть выше с sync подать в симуляцию, подав данные не по положительному фронту)
Какой конструкцией подаете сигналы x1-4 в Вашем примере?
Возьмем такой пример:
module some_example(
  input              clk_i,

  input        [7:0] a_i,
  input        [7:0] b_i,

  output logic [7:0] no_pipe_res_o

);

logic [7:0] a_sync;
logic [7:0] b_sync;
logic [7:0] c;

always_ff @( posedge clk_i )
  begin
    a_sync <= a_i;
    b_sync <= b_i;
  end

assign c = a_sync + b_sync;

always_ff @( posedge clk_i )
  begin
    no_pipe_res_o <= c;
  end
endmodule


Я специально сигналы a и b пересохраняю в триггер, что бы с триггера отдались данные на сумматоры.

Так выглядит функциональная симуляция в Квартусе (кстати 13):



Вот так временная:


Теперь представьте, что a/b_sync вынесены из этого модуля и выходы этих триггеров подаются на a_i, b_i в таком примере:

module some_example_2(
  input              clk_i,

  input        [7:0] a_i,
  input        [7:0] b_i,

  output logic [7:0] no_pipe_res_o

);

always_ff @( posedge clk_i )
  begin
    no_pipe_res_o <= a_i + b_i;
  end
endmodule


По поведению ничего не поменяется)
Здесь есть тонкий момент, о котором я не написал, но подразумевал.
Считается, что схема полностью синхронная: вход x_i является выходом какого-то триггера, который тоже работает по clk_i. В первый posedge данные защелкнулись в триггер x_i, и стали валидны на его выходе. Весь такт сумматоры считают свои значения и на следующий положительный фронт сумма защелкнется в no_pipe_res_o.

Читаю про D-триггер, там речь о том, что по переднему фронту С он заносит значение в первый Т триггер и после падения С уже подается на выход.

Если честно, такого никогда не видел…
SystemVerilog реально крутая вещь)
Для меня не очень понятны советы новичкам, которые спрашивают «VHDL или Verilog?», и ему отвечают Verilog, мол он проще и похож на С. Должен быть спор VHDL vs SystemVerilog) Сейчас всё быстро меняется, и если надо разрабатывать большие проекты, то там только SystemVerilog ИМХО. (Хотя от приложения зависит, может если тим DSP, то тех извращений, что я делаю с данными, там не надо делать)

Правда, насколько я знаю, есть проблема в том, что не все синтезаторы поддерживают, но Synplify вроде впереди планеты всей и всё поддерживает)
Можно и так, но добавилась «лишняя» информация в ввиде data_d1[3:0]. :) Большой разницы через assign либо через так как Вы предложили я не вижу.

Мне приходится со структурами похожую операцию проделывать:
typedef struct packed {
  logic [7:0] a;
  logic [7:0] b;
  logic [7:0] c;
} s_t;

s_t s_comb;
s_t s_input;
s_t s_delayed; // задержанная на несколько тактов s_input

// подменяем в s_delayed только поле c
always_comb
  begin
    s_comb = s_delayed;

    if( some_flag )
      s_comb.c = 8'd42;
  end


Как это красиво переписать без первой строчки?
Я прекрасно понимаю, что это может быть неинтуитивно для тех, кто так не писал :)
Возможную проблему с латчем, если кто-то удалит первую строчку я тоже знаю. Никаких нарушений канонов либо устоявшихся рекомендаций (например, смешивание блокирующих и неблокирующих присваиваний в одном always-блоке) в таком написании я не вижу.

Раньше я писал через assign как раз так, как Вы предлагаете, но у меня очень часто возникает задача в длинном слове поменять часть байт и через assign и кучу конкатенаций мне не нравилось делать, т.к. пишется много кода. Больше кода — больше ошибок: можно не те биты заменить, либо на один промазать, читаемость хуже…

Этот прием мне пришел в голову, когда я понял, что намного проще сначала присвоить, грубо говоря, default value, задержанное на несколько тактов, а потом уже его модифицировать, подменяя только нужные байты. Такая подмена это более высокоуровневое написание, чем assign, следовательно, должна быть более интуитивным и понятным. Думаю, дело вкуса и не более.

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность