3. Многобитный сумматор

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

/src/03-principles-of-constructor/adder_reuse_without_generate.sv
module adder_reuse_without_generate (
  input logic [1:0] a,
  input logic [1:0] b,
  input logic c_in,
  output logic c_out,
  output logic [1:0] sum
);
  logic carry_tmp;
  adder_logic_1_bit add1(
    .a(a[0]),
    .b(b[0]),
    .c_in(c_in),
    .c_out(carry_tmp),
    .sum(sum[0])
  );

  adder_logic_1_bit add2(
    .a(a[1]),
    .b(b[1]),
    .c_in(carry_tmp),
    .c_out(c_out),
    .sum(sum[1])
  );

endmodule

Можно синтезировать схему написанного модуля.

adder reuse without generate
Рисунок 1. Схема двухбитного сложения

Как видно, вспомогательная переменная исчезла. Точнее не исчезла, она представляет собой проводок из c_out первого блока в c_in второго блока. Это вполне логично, эта переменная несёт в себе ровно этот смысл. В SystemVerilog даже можно называть переменные wire (попробуйте заменить logic на wire).

В этом коде нет ничего нового, чего бы мы не знали. Однако если мы попробуем написать сложение для n-ой размерности, то придётся n раз прописывать использование однобитного сложения. Что никуда не годится, программисты придумали для такого циклы. В SystemVerilog они тоже есть.

/src/03-principles-of-constructor/adder_multibits_reuse.sv
module adder_multibits_reuse #(
  parameter int COUNT_OF_BITS = 4 (1)
) (
  input logic [COUNT_OF_BITS-1:0] a, (2)
  input logic [COUNT_OF_BITS-1:0] b,
  input logic c_in,
  output logic c_out,
  output logic [COUNT_OF_BITS-1:0] sum
);

  logic [COUNT_OF_BITS:0] carry; (3)

  assign carry[0] = c_in; (4)
  assign c_out = carry[COUNT_OF_BITS];

  genvar i; (5)
  generate (6)
    for (i = 0; i < COUNT_OF_BITS; i++)
      adder_logic_1_bit add( (7)
        .a(a[i]),
        .b(b[i]),
        .c_in(carry[i]),
        .c_out(carry[i + 1]), (8)
        .sum(sum[i])
      );
  endgenerate
endmodule
1 Модули можно параметризовать[1] и использовать параметр в любом месте модуля. Потом при инстанциации можно поставить то значение, которое хочется.
2 Параметры можно использовать даже при описании входных и выходных данных. Очень мощный механизм, который обычно отсутствует (ну или является инструментом продвинутого уровня) в других языках.
3 Задаём шину промежуточных битов переполнения.
4 Начальный бит переполнения нужно учесть в промежуточной шине.
5 Переменная, нужная только для генерации.
6 Сейчас нужно много раз инстанциировать модуль adder_logic_1_bit, в SystemVerilog для решения этой проблемы можно генерировать код[2].
7 Для каждого бита используем модуль однобитного сложения.
8 Ну, и, что вполне логично, запоминаем бит переполнения для использования на следующем шаге (в школе про это говорилось "а один держим в уме").

Интересно посмотреть, во что синтезируется этот модуль.

adder multibits reuse
Рисунок 2. Синтезированная схема многобитного сумматора

Тут видно, что блок модуля однобитного сложения используется четыре раза. Что ожидаемо: он был вызван для каждого бита четырёхбитной шины. Это напоминает нам о том, что, хоть они и похожи, но инстанциация модуля в SystemVerilog и вызов функции в привычных языках — разные вещи.

Можно добавить флаг --flatten в prep в гайдике. Он отвечает за то, будут ли при синтезе раскрываться используемые модули. Если это сделать, то синтезируется такая схема.

adder multibits reuse flatten
Рисунок 3. Развёрнутая схема многобитного сумматора

Ну, не стоит забывать, что всё это делается в учебных целях, и сложение битветоров реализовано, конечно, из коробки.

/src/03-principles-of-constructor/adder_multibits_bitvectors.sv
module adder_multibits_bitvectors #(
    parameter int COUNT_OF_BITS = 4
) (
    input  logic [COUNT_OF_BITS-1:0] a,
    input  logic [COUNT_OF_BITS-1:0] b,
    input logic c_in,
    output logic c_out,
    output logic [COUNT_OF_BITS-1:0] sum
);
  assign {c_out, sum} = a + b + c_in;
endmodule

Схема для этого модуля будет выглядеть довольно просто.

adder multibits bitvectors flatten
Рисунок 4. Схема многобитного сумматора на битвекторах

Человеку привычно воспринимать числа в десятичной записи, и в нормальных языках для такого есть отдельный тип int. В этом смысле SystemVerilog тоже нормальный[3]. Так что наш модуль в некотором смысле вырождается.

/src/03-principles-of-constructor/adder_multibits_int.sv
module adder_multibits_int(
  input int a,
  input int b,
  output int sum
);
  assign sum = a + b;
endmodule

По умолчанию int — знаковые числа, чтобы сделать их беззнаковыми, нужно писать int unsigned.

У int другое поведение при переполнении: для знаковых чисел оно меняет знак, для беззнаковых — уходит в 0.
adder multibits int flatten
Рисунок 5. Схема многобитного сумматора с использованием int
Обязательно порешайте упражнения.