======Verilog Notes======
====Метастабильность, сэмплирование фронтов внешнего сигнала CLK:====
Внешний входной сигнал SCK, например от интерфейса SPI, и внутренний системный сигнал CLK микросхемы FPGA являются асинхронными друг относительно друга. Поэтому в какие-то моменты времени может возникать ситуация, когда переключения сигналов возникают в одно и тоже время. Т.е. фронт сигнала CLK, по которому работает логика в FPGA, совпадает с фронтом/спадом внешнего сигнала.
В таких случаях D-триггер не может достоверно "захватить" входной сигнал, возникает неопределенность на его выходе - напряжение не //"0"// и не //"1"//. Это неопределенное состояние не может длиться долго, и через некоторое время за счет внутренних токов выход уплывет либо в //"0"//, либо в //"1"//. Но, в течение этого времени нельзя неопределенное состояние пропускать дальше в схему. Поэтому, используется второй и более D-триггеров в цепочке, которые не пропускают эту неопределенность дальше. Предполагается, что к моменту выхода из цепочки D-триггеров неопределенность должна исчезнуть.
module spi_slave(
input clk,
input SCK,
...
);
reg [2:0] SCK_sync;
always @(posedge clk) SCK_sync <= {SCK_sync[1:0], SCK};
wire SCK_rising_edge = (SCK_sync[2:1]==2'b01); // detect SCK rising edges
wire SCK_falling_edge = (SCK_sync[2:1]==2'b10); // detect SCK falling edges
...
endmodule
Links:
* [[https://marsohod.org/verilog/191-synchronizer|МАРСОХОД: Синхронизатор сигнала для CDC на Verilog]]
* [[https://marsohod.org/11-blog/190-meta1|МАРСОХОД: Еще о метастабильности]]
====Присваивания====
**Непрерывное присваивание, =**
* используется для описания простой комбинационной логики
* Вместе с оператором assign и при определении wire
* Слева - **wire** или выходной порт модуля, справа - любые выходы и операции
wire [1:0] a;
wire [1:0] b = 2'b10;
assign a = b + 2'b01;
**Неблокирующее присваивание, <=**
* Используется только для последовательностной логики
* Только внутри **always** блока, порядок присваивания не важен
* Слева **reg**, справа - любые выходы и операции
reg [1:0] a;
reg [1:0] b;
always @(posedge clk)
begin
if (swap_en)
begin
a <= b;
b <= a;
end;
end
**Блокирующее присваивание, =**
* Используется только для последовательностной логики
* Только внутри **always** блока, **важен** порядок присваивания.
* Пока выражения не зависят друг от друга - получается такая же схема, что и при неблокирующих присваиваниях
always @(posedge clk) begin
x = x + 1;
y = x;
end
равнозначно
always @(posedge clk) begin
x <= x + 1;
y <= x + 1;
end
Links:
* [[http://sensi.org/~svo/verilog/assignments/|О видах присваиваний в Верилоге]]
* [[https://russianblogs.com/article/294987036/|Блокирующие и неблокирующие назначения операторов присваивания]]
* [[https://www.youtube.com/watch?v=Vdz7_wMFAy8|YouTube, ФРТК МФТИ: Verilog. Последовательностная логика. (Посл. логика, счетчик и делитель частоты)]]
====reg vs wire====
Регистровый тип является логической сущностью Верилога и не всегда превращается в физический регистр при синтезе. Иногда регистровый тип нужен для того, чтобы обойти некоторые ограничения синтаксиса языка.
reg [7:0] data_in;
always
case ({cpu_memr,cpu_inport,interrupt})
3'b100: data_in <= ram_data;
3'b010: data_in <= io_data;
3'b001: data_in <= 8'hFF;
default: data_in <= 8'hZZ;
endcase
Несмотря на то, что переменная data_in формально является регистром, она будет синтезирована как обычная шина типа wire, а присоединена эта шина будет к выходу описанного оператором case мультиплексора. Переменная регистрового типа превращается в регистр только тогда, когда её присваивание происходит по перепаду тактового сигнала. В противном же случае она фактически является эквивалентом переменной типа wire.
Links:
* [[http://sensi.org/~svo/verilog/assignments/|О видах присваиваний в Верилоге]]
====Количество бит под значение, $clog2() ====
clog2(N) = ceil(log2(N))
Функция **$clog2(N)** возвращает **количество бит, необходимых для хранения N значений**. Или можно считать что это количество бит адреса, для памяти размером N.
Важно, что само число N может и не влезть в полученное количество бит! Например, $clog2(8) = 3. Эти три бита позволяют хранить 8-мь значений от 0 (3'b000) до 7 (3'b111), но само число 8 требует 4 бита для представления.
Поэтому для хранения самого числа N необходимо использовать $clog2(N+1):
^ N ^ $clog2(N) ^ $clog2(N+1) ^ N Binary ^
| 0 | 0 | 0 | 1'b0 |
| 1 | 0 | 1 | 1'b0 |
| 2 | 1 | 2 | 2'b10 |
| 3 | 2 | 2 | 2'b11 |
| 4 | 2 | 3 | 3'b100 |
| 5 | 3 | 3 | 3'b101 |
| 6 | 3 | 3 | 3'b110 |
| 7 | 3 | 3 | 3'b111 |
| 8 | 3 | 4 | 4'b1000 |
| 9 | 4 | 4 | 4'b1001 |
Прочие функции: [[https://www.chipverify.com/verilog/verilog-math-functions|verilog-math-functions]]
====Прореживание CLK с периодом (делитель частоты)====
module CLK_Div_Period #(
parameter CLK_PERIOD = 2
)
(
input clk,
input rst_n,
output o_clk
);
localparam CNT_BITS = $clog2(CLK_PERIOD+1);
reg [CNT_BITS-1:0] count;
reg r_dv;
assign o_clk = r_dv;
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
begin
count <= {CNT_BITS{1'b0}};
r_dv <= 1'b0;
end
else
begin
if (count == (CLK_PERIOD - 1))
begin
count <= {CNT_BITS{1'b0}};
r_dv <= 1'b1;
end
else
begin
count <= count + 1'b1;
r_dv <= 1'b0;
end
end
end
endmodule
====Сложение, синхронное vs асинхронное====
Если верить этой ссылке [[https://stackoverflow.com/questions/73249585/adding-large-numbers-in-fpga-in-one-clock-cycle|Stackoverflow: Adding large numbers in FPGA in one clock cycle]], то асинхронная схема дает меньшее быстродействие. Потому что, входные сигналы приходят на сумматор откуда-то "издалека" (где они формируются), и выходные сигналы уходят куда-то "вдаль", туда где они используются дальше. Эти задержки распространения снижают максимальную частоту работы схемы.
В случае с синхронной логикой, результат сложения "сохраняется" в ближайший регистр. Поэтому сумматор может работать на большей частоте.
Это относится не только к сумматору, но и к любой другой коммутационной схеме.
====Умножение знаковое и беззнаковое====
* При перемножении **беззнаковых** чисел размерностью **P** и **Q** бит, результат получается размерностью **P+Q** бит.
* При перемножении **знаковых** чисел размерностью **P** и **Q** бит, результат получается размерностью **P+Q-1** бит. Два старших бита получаются одинаковыми и отражают знак результата.
====Pipeline====
[[https://www.allaboutcircuits.com/technical-articles/why-how-pipelining-in-fpga/|конвейерная обработка в ПЛИС]]
====CLK posedge====
* По переднему фронту CLK входные сигналы защелкиваются в триггере (регистре) и становятся валидными на его выходах.
* Последующая комбинационная схема (сложение/умножение/...) должна отработать с этими выходными сигналами регистра и успеть сформировать окончательный ответ к следующему фронту CLK.
* На фронте CLK выходы комбинационной схемы защелкиваются в следующем триггере.
====Формирование Max и Min значения для заданного количества бит====
wire [Y_DATA_BITS-1:0] y_max = {{1'b0},{Y_DATA_BITS-1{1'b1}}};
wire [Y_DATA_BITS-1:0] y_min = {{1'b1},{Y_DATA_BITS-1{1'b0}}};
====Сравнение значений====
При сравнении чисел разной разрядности будет расширено число с меньшей разрядностью. Старшим битом, если число знаковое, и 0-м если число беззнаковое.
====Quartus and modelsim Warning 3016====
Warning: (vsim-3016) Port type is incompatible with connection (port 'clock').
Warning выскочил при назначении сигнала clk на вход clock модуля altsyncram (мегафункция встроенной памяти). Решение нашлось тут: [[https://community.intel.com/t5/Intel-Quartus-Prime-Software/Question-about-warning-quot-Port-type-is-incompatible-with/td-p/109417|link]]
// =========== SinMem.v (Generated by Quartus megafunction) =============
module SinMem (
address,
clock,
q);
input [11:0] address;
input clock;
output [15:0] q;
`ifndef ALTERA_RESERVED_QIS
// synopsys translate_off
`endif
tri1 clock;
`ifndef ALTERA_RESERVED_QIS
// synopsys translate_on
`endif
wire [15:0] sub_wire0;
wire [15:0] q = sub_wire0[15:0];
altsyncram altsyncram_component (
.address_a (address),
.clock0 (clock),
.q_a (sub_wire0),
.aclr0 (1'b0),
.aclr1 (1'b0),
.address_b (1'b1),
.addressstall_a (1'b0),
.addressstall_b (1'b0),
.byteena_a (1'b1),
.byteena_b (1'b1),
.clock1 (1'b1),
.clocken0 (1'b1),
.clocken1 (1'b1),
.clocken2 (1'b1),
.clocken3 (1'b1),
.data_a ({16{1'b1}}),
.data_b (1'b1),
.eccstatus (),
.q_b (),
.rden_a (1'b1),
.rden_b (1'b1),
.wren_a (1'b0),
.wren_b (1'b0));
defparam
altsyncram_component.address_aclr_a = "NONE",
altsyncram_component.clock_enable_input_a = "BYPASS",
altsyncram_component.clock_enable_output_a = "BYPASS",
altsyncram_component.init_file = "SinTable.mif",
altsyncram_component.intended_device_family = "Cyclone V",
altsyncram_component.lpm_hint = "ENABLE_RUNTIME_MOD=NO",
altsyncram_component.lpm_type = "altsyncram",
altsyncram_component.numwords_a = 4096,
altsyncram_component.operation_mode = "ROM",
altsyncram_component.outdata_aclr_a = "NONE",
altsyncram_component.outdata_reg_a = "CLOCK0",
altsyncram_component.widthad_a = 12,
altsyncram_component.width_a = 16,
altsyncram_component.width_byteena_a = 1;
endmodule
// ======= Implementation =======
// test.v
module test(
input rst_n,
input clk,
...
);
wire clk_fix = clk;
SinMem sinTable(
.address(addr),
.clock (clk_fix), // OK
//.clock (clk), - Warning: (vsim-3016) Port type is incompatible with connection (port 'clock').
.q (data)
);