Сейчас, мы уже знаем про модули, их входные и выходные сигналы и как они могут быть соединены друг с другом. На прошлом уроке я рассказал, как можно сделать многобитный сумматор. Нужно ли каждый раз, когда складываем два числа, делать такие сложные модули, как на том уроке? Конечно нет! Давайте познакомимся с основными арифметическими и логическими операторами языка Verilog.
С помощью комбинаторной логики посчитаем некоторые арифметические и логические функции. Программисты C/C++ будут чувствовать себя просто как дома.
- Сложение и вычитание.
Вот пример модуля, который одновременно и складывает и вычитает два числа. Здесь входные операнды у нас 8-ми битные, а результат 9-ти битный. Verilog корректно сгенерирует бит переноса (carry bit) и поместит его в девятый бит выходного результата. С точки зрения Verilog входные операнды беззнаковые. Если нужна знаковая арифметика, то об этом нужно отдельно позаботиться.
module simple_add_sub(
operandA, operandB,
out_sum, out_dif);
//два входных 8-ми битных операнда
input [7:0] operandA, operandB;
//Выходы для арифметических операций имеют дополнительный 9-й бит переполнения
output [8:0] out_sum, out_dif;
assign out_sum = operandA + operandB;
assign out_dif = operandA - operandB;
endmodule
- Логический и арифметический сдвиг.
module simple_shift (
operandA, operandB,
out_shl, out_shr, out_sar);
// два входных 8-ми битных операнда
input [7:0] operandA, operandB;
// Выходы для операций сдвига
output [15:0] out_shl;
output [7:0] out_shr;
output [7:0] out_sar;
//логический сдвиг влево
assign out_shl = operandA << operandB;
// пример: на сколько сдвигать определяется 3-мя битами второго операнда
assign out_shr = operandA >> operandB[2:0];
//арифметический сдвиг вправо (сохранение знака числа)
assign out_sar = operandA >>> operandB[2:0];
endmodule
- Битовые логические операции
Битовые операции в Verilog выглядят так же, как и в языке C. Каждый бит результата вычисляется отдельно соответственно битам операндов. Вот пример:
module simple_bit_logic (
operandA, operandB,
out_bit_and, out_bit_or, out_bit_xor, out_bit_not);
//два входных 8-ми битных операнда
input [7:0] operandA, operandB;
//Выходы для битовых (bit-wise) логических операций
output [7:0] out_bit_and, out_bit_or, out_bit_xor, out_bit_not;
assign out_bit_and = operandA & operandB;
assign out_bit_or = operandA | operandB;
assign out_bit_xor = operandA ^ operandB;
assign out_bit_not = ~operandA;
endmodule
- Булевые логические операции.
Булевые логические операторы отличаются от битовых операций. Так же, как и в языке С, здесь значение всей шины рассматривается как ИСТИНА если хотя бы один бит в шине не ноль или ЛОЖЬ, если все биты шины – ноль. Результат получается всегда однобитный (независимо от разрядности операндов) и его значение "1" (ИСТИНА) или "0" (ЛОЖЬ).
module simple_bool_logic (
operandA, operandB,
out_bool_and, out_bool_or, out_bool_not);
//два входных 8-ми битных операнда
input [7:0] operandA, operandB;
// Выходы для булевых (boolean) логических операций
output out_bool_and, out_bool_or, out_bool_not;
assign out_bool_and = operandA && operandB;
assign out_bool_or = operandA || operandB;
assign out_bool_not = !operandA;
endmodule
- Операторы редукции.
Verilog имеет операторы редукции. Эти операторы позволяют выполнять операции между битами внутри одной шины. Так, можно определить все ли биты в шине равны единице (&bus), или есть ли в шине хотя бы одна единица (|bus). Приведу пример:
module simple_reduction_logic (
operandA,
out_reduction_and, out_reduction_or, out_redution_xor);
//входной 8-ми битный операнд
input [7:0] operandA;
// Выходы для логических операций редукции
output out_reduction_and, out_reduction_or, out_reduction_xor;
assign out_reduction_or = |operandA;
assign out_reduction_and = &operandA;
assign out_reduction_xor = ^operandA;
endmodule
А вот еще полезные операторы редукции:
~|operandA обозначает, что в шине нет единиц.
~&operandA обозначает, что некоторые биты в шине равны нулю.
- Оператор условного выбора
Язык C имеет оператор ‘? :’. С его помощью можно выбрать одно значение из двух по результату логического выражения. В Verilog тоже есть подобный оператор. Он фактически реализует мультиплексор. В данном примере на выходе мультиплексора окажется значение operandA если сигнал sel_in единица. И наоборот. Если входной сигнал sel_in равен нулю, то на выходе мультиплексора будет значение operandB.
module simple_mux (
operandA, operandB, sel_in, out_mux);
//входные 8-ми битные операнды
input [7:0] operandA, operandB;
//входной сигнал селектора
input sel_in;
//Выход мультиплексора
output [7:0]out_mux;
assign out_mux = sel_in ? operandA : operandB;
endmodule
- Операторы сравнения.
Можно ли сравнивать "числа" (а точнее значения регистров или шин) в Verilog? Конечно можно! Вот такой пример (все сравнения происходят с беззнаковыми числами):
module simple_compare (
operandA, operandB,
out_eq, out_ne, out_gt, out_lt, out_ge, out_le);
//входные 8-ми битные операнды
input [7:0] operandA, operandB;
//Выходы операций сравнения
output out_eq, out_ne, out_gt, out_lt, out_ge, out_le;
assign out_ne = operandA != operandB;
assign out_ge = operandA >= operandB;
assign out_le = operandA <= operandB;
assign out_gt = operandA > operandB;
assign out_lt = operandA < operandB;
endmodule
Ну вот, в приведенных примерах были рассмотрены основные арифметические и логические операторы языка Verilog. Конечно, кое-что я упустил в этом описании, но базовые операции рассмотрены. Вы можете заметить, что некоторые операторы совсем не описаны. Это такие операторы, как умножение (*) или деление (/), или какой нибудь модуль, остаток от деления (%). Мне кажется такие вещи обычно лучше избегать для синтеза. Конечно они полезны для написания моделей симуляции. Некоторые чипы могут иметь встроенные умножители, если синтезатор знает об этом, он может использовать их. Тем не менее, лучше делать такую работу вручную – так больше возможностей контролировать возможности конкретного чипа.
Подробнее...