2. Комбинационные схемы
В этом разделе познакомимся с комбинационными схемами, тестбенчами[1] (коротко, аналог юнит-тестов), напишем простейший сумматор, на примере которого покажем всё вышеперечисленное.
Обязательно ознакомьтесь с настройкой окружения, в дальнейшем будет предполагаться, что все шаги оттуда были выполнены. |
Разберёмся же с концепцией комбинационных схем на примере сумматора. Нужно сложить два бита с переполнением (оно же — перенос разряда). Разберём код, реализующий эту функциональность:
module adder_plus_1_bit (
input logic a, (1)
input logic b, (2)
input logic c_in, (3)
output logic c_out, (4)
output logic sum (5)
); (6)
assign {c_out, sum} = a + b + c_in; (7)
endmodule
1 | Первый входной бит. |
2 | Второй входной бит. |
3 | Бит, отвечающий за переполнение на входе. |
4 | Бит, отвечающий за переполнение на выходе. |
5 | Результат сложения. |
6 | Не забывайте ставить ; . |
7 | Сложение работает как привычное сложение двоичных чисел.
Соответственно, сначала будет один бит переполнения, а потом — результирующий бит.
Отловить это можно с помощью оператора конкатенации[2] через фигурные скобки.
Тогда мы явно свяжем шину, состоящую из двух битов (c_out и sum ) с результатом сложения.
Также важно отметить использование непрерывного присваивания через assign [3].
Оно "сцепляет" элементы друг с другом, то есть при любом изменении одного элемента, изменится и другой. |
Непрерывное присваивание — это единственный способ "положить" что-то в результирующие переменные.
То есть выражения для них могут быть очень сложными, со сложной логикой, но всё сведётся к assign output_var = expr .
|
Однако странно использовать встроенное сложение в своей реализации сложения. Опираясь на выкладки умных людей, можно избавится от этого недоразумения и предложить реализацию исключительно на логических операциях:
module adder_logic_1_bit (
input logic a,
input logic b,
input logic c_in,
output logic c_out,
output logic sum
);
assign sum = a ^ b ^ c_in;
assign c_out = (a & b) | (c_in & (a ^ b));
endmodule
Можно воспользоваться конкатенацией, как в прошлом примере, это избавит нас от лишнего
Однако, как видно, это несколько ухудшает читаемость кода. |
Важно не забывать специфику: все модули (если в них не используются некоторые специальные конструкции), написанные на SystemVerilog, должны синтезироваться в принципиальную схему. Существуют механизмы, позволяющие сделать это. Подробнее, как получить схему в виде картинки, можно почитать тут. Проделав необходимые действия с нашим сумматором, получаем схему:
Конечно, после того, как мы написали что-то простое, хочется вывести результат и посмотреть, адекватно ли всё работает. Давайте сделаем это:
module adder_logic_1_bit_tb;
logic a, b, c_in, c_out, sum; (1)
adder_logic_1_bit add ( (2)
.a(a), (3)
.b(b),
.c_in(c_in),
.c_out(c_out),
.sum(sum)
);
initial (4)
begin
a = 0;
b = 1;
c_in = 0;
#10; (5)
$display("%b (из переполнения) + %b + %b = %b (%b в переполнении)",
c_in, a, b, sum, c_out);
a = 1;
b = 1;
c_in = 0;
#10;
$display("%b (из переполнения) + %b + %b = %b (%b в переполнении)",
c_in, a, b, sum, c_out);
a = 0;
b = 0;
c_in = 1;
#10;
$display("%b (из переполнения) + %b + %b = %b (%b в переполнении)",
c_in, a, b, sum, c_out);
end
endmodule
1 | Объявляем всё, что нам потребуется для работы. |
2 | Задаём тестируемый модуль[4]. Тут почти то же самое, что и вызов функции, но результат записывается в выделенные переменные и изменяется непрерывно с изменением входных данных. |
3 | Передавать аргументы можно и просто по порядку, но их зачастую больше, чем мы привыкли, легко запутаться. |
4 | Выполняем блок в начальный момент времени[5]. Этот блок ломает синтезируемость модуля, так что обычно его используют только в тестбенчах. |
5 | В SystemVerilog всё довольно сложно с последовательностью выполнения команд, здесь серьёзное отличие с привычными нам языками, потому что тут мы можем управлять ходом выполнения программы потактово.
Так что, чтобы быть уверенным в том, в какой момент исполнятся команды, нужно задавать задержку[6] (либо делать что-то хитрее, о чём мы поговорим позже).
Именно это мы и сделали.
У нас задержка в 10 наносекунд (это изменяемый параметр, называемый time_unit [7]), можно взять и другую, но исторически повелось брать именно 10. |
Давайте просимулируем полученный файл. Этот модуль использует другой, так что iverilog-у необходимо передать и его тоже:
$ iverilog -s adder_logic_1_bit_tb adder_logic_1_bit.sv adder_logic_1_bit_tb.sv -g2012 -o build/adder_logic_1_bit_tb
Флаг |
После запустим его:
$ build/adder_logic_1_bit_tb
Вывод должен выглядеть так:
0 (из переполнения) + 0 + 1 = 1 (0 в переполнении)
0 (из переполнения) + 1 + 1 = 0 (1 в переполнении)
1 (из переполнения) + 0 + 0 = 1 (0 в переполнении)
В следующем разделе мы подробнее поговорим про тестбенчи и настроим CI.
После прохождения обязательно ознакомьтесь с упражнениями. |