Представьте, что мы хотим описать комбинационную схему, которая удовлетворяет следующей таблице истинности:
a b | s0 s1 s2 s3
-----------------
0 0 | 1 d d d
0 1 | 0 1 d d
1 0 | 0 0 1 d
1 1 | 0 0 0 1
(где d
означает значение "не заботится", то есть нам не важно, имеет ли значение этого вывода 0 или 1)
Если мы пройдем традиционный дизайн, мы сможем воспользоваться этими "не заботимся" и присвоить им наиболее удобные значения, чтобы полученные уравнения (и, следовательно, схема) были самыми простыми. Например, мы могли бы изменить предыдущую таблицу истинности на эту:
a b | s0 s1 s2 s3
-----------------
0 0 | 1 1 1 1
0 1 | 0 1 0 1
1 0 | 0 0 1 1
1 1 | 0 0 0 1
И окончательные уравнения были бы (с использованием нотации Verilog):
s0 = ~a & ~b;
s1 = ~a;
s2 = ~b;
s3 = 1;
(помните, когда вам приходилось выбирать значения для ваших выходов на K-карте, чтобы вы могли группировать столько клеток, сколько сможете)
Но что, если я захочу его спроектировать с помощью Verilog? Я не могу этого сделать:
module encoder (
input wire a,
input wire b,
output reg [3:0] s
);
always @* begin
case ({a,b})
2'b00 : s = 4'b1ddd;
2'b01 : s = 4'b01dd;
2'b10 : s = 4'b001d;
2'b11 : s = 4'b0001;
default: s = 4'bdddd;
endcase
end
endmodule
Мне сказали как присваивать значения по умолчанию выводам в комбинационном всегда блоке..., что я не мог использовать x
как результат, только как вход. И если я использую z
, результирующая схема будет еще хуже с точки зрения сложности и используемых ресурсов, поскольку необходимы тристатические буферы.
Поэтому мне приходится выбирать во время разработки, которые значения (1
или 0
) я хочу вывести, и эти значения не должны давать самую оптимизированную схему:
module encoder (
input wire a,
input wire b,
output reg [3:0] s
);
always @* begin
case ({a,b})
2'b00 : s = 4'b1000;
2'b01 : s = 4'b0100;
2'b10 : s = 4'b0010;
2'b11 : s = 4'b0001;
default: s = 4'b0000;
endcase
end
endmodule
Что приводит к этим уравнениям (игнорируя предложение default
на данный момент):
s0 = ~a & ~b;
s1 = ~a & b;
s2 = a & ~b;
s3 = a & b;
Или эта реализация (взята из вывода YOSIS 0.3.0 на EdaPlayGround):
Это может быть или не быть лучшим решением для данной цели, но это то, что мы позволяем синтезатору вывести данные, которые мы хотим.
Используя синтезатор XST, нацеленный на FPGA Spartan 3E-100k, в этом модуле используется 2 фрагмента и 4 LUT.
Я предполагаю, что Verilog (или любой другой HDL) должен освободить разработчика от необходимости делать такие выборы, поэтому синтезатор может применять любые оптимизации, если дизайнер позволяет ему выбирать наиболее удобное значение для данного вывода и для заданный набор входов. Если бы это было так, то предыдущий проект мог быть оптимизирован так, чтобы выглядеть так:
Направляя те же FPGA, что и выше, он использует 2 среза и 3 LUT.
В этом примере я смог сделать оптимизацию вручную, но рассмотрю модуль контроллера с десятком выходов в модуль данных. Могут быть выходные сигналы от контроллера, которые могут не иметь значения для заданного состояния контроллера.
Например: контроллер выводит сигнал на select
из регистра A или регистр B и другой сигнал для включения load
регистра C, поэтому регистр C может быть загружен либо A, либо B, либо сохранить его текущее значение.
Если load
равно 0, я действительно не забочусь о значении select
, поэтому каждый раз в описании контроллера я выводим load = 0
, я должен иметь возможность выводить "не заботьтесь" select
.
Итак, мои вопросы:
-
Есть ли способ написать описание Verilog (не SystemVerilog), чтобы я мог давать "не заботятся значения" для выходов из комбинационных блоков?
-
Если это не так, это ограничение на языке, или это вопрос "вы должны сделать ваши проекты так" неважно ", что значения не нужны"?
ДОПОЛНЕНИЕ
К моему удивлению, XST распознает `x` как допустимый результат. Он синтезирует и, кажется, ведет себя так, как я ожидал, в результате чего одна и та же схема будет реализована с помощью 2 срезов и 3 LUT. YOSIS, с другой стороны, похоже, игнорирует его и производит тот же результат, что и не оптимизированный дизайн.
Исправление: я тестировал XST с другой конструкцией: схема, которая создает эту таблицу истинности:
a b | s0 s1 s2 s3
-----------------
0 0 | 0 d d d
0 1 | 1 0 d d
1 0 | 1 1 0 d
1 1 | 1 1 1 0
Соответствующий модуль Verilog, написанный без особого внимания, может быть записан несколькими способами, например:
module encoder (
input wire a,
input wire b,
output reg [3:0] s
);
always @* begin
case ({a,b})
2'b00 : s = 4'b0111;
2'b01 : s = 4'b1011;
2'b10 : s = 4'b1101;
2'b11 : s = 4'b1110;
default: s = 4'b1111;
endcase
end
endmodule
Что дает худший результат с точки зрения минимизации (2 среза, 4 LUT в спартанском 3P FPGA)
Оптимизированную вручную версию можно получить, начиная с этой таблицы истинности:
a b | s0 s1 s2 s3
-----------------
0 0 | 0 0 0 0
0 1 | 1 0 1 0
1 0 | 1 1 0 0
1 1 | 1 1 1 0
Здесь легко заметить, что 3 из 4 выходов могут быть получены без единого логического затвора. Таким образом, XST сообщает 1 срез, 1 LUT (единственное, что необходимо для вычисления s0)
module encoder (
input wire a,
input wire b,
output reg [3:0] s
);
always @* begin
case ({a,b})
2'b00 : s = 4'b0000;
2'b01 : s = 4'b1010;
2'b10 : s = 4'b1100;
2'b11 : s = 4'b1110;
default: s = 4'b1110; // yes, same output as above
endcase
end
endmodule
Если использовать x
грязный трюк как "не заботьтесь":
module encoder (
input wire a,
input wire b,
output reg [3:0] s
);
always @* begin
case ({a,b})
2'b00 : s = 4'b0xxx;
2'b01 : s = 4'b10xx;
2'b10 : s = 4'b110x;
2'b11 : s = 4'b1110;
default: s = 4'bxxxx;
endcase
end
endmodule
Конструкция синтезирует, но результат не минимален. XST сообщает 1 срез, 2 LUT.
Ссылки на бумагу @Tim в его комментарии очень понятны в этом вопросе: не используйте x
в своих проектах. Но в соответствии с этим примером язык не позволяет нам помогать синтезатору минимизировать схему.
Сохранение одного или двух LUT может быть не очень большим, но если экономия позволяет этому модулю оставаться внутри среза, у P & R будет меньше работы, чтобы разместить его там, где он хочет.