我有一个FSM设计,它使用计数器在特定状态内计数并保持在那里,直到表达式&counter
产生TRUE
,但是当它完成时(得到1111 ... 111 - 检查通过模拟器)&counter
永远不会进入HIGH
并像无限循环一样停留在那个状态。如何克服这个问题?我将提供我的错误部分代码。我是verilog的新手,所以希望这是一个容易捕捉的问题。
reg [2:0] State, NextState;
reg [20:0] counter;
reg EN;
//State register
always @(posedge Clk) begin
if(Rst) begin
counter <= 0;
State <= S0;
end
else begin
State <= NextState;
counter <= EN ? counter + 1 : 0;
end
end
//Next State Logic
always @(State, COL)
case(State)
S0: begin
NextState <= S1;
EN <= 0;
end
S1: begin
if(ROW) begin
if(COL) begin
if(&counter[18:0]) begin // <--- THIS EXP. NEVER GETS 1
PrevCOL <= COL;
NextState <= S3;
EN <= 0;
end
else begin
EN <= 1;
NextState <= S1;
end
end
else
NextState <= S2;
end
else
NextState <= S0;
end
S2: NextState <= S1;
S3: begin
if(PrevCOL == COL) begin
if(&counter) begin // <-------- THIS EXPRESSION NEVER GETS 1
NextState <= S4;
EN <= 0;
case(ROW)
8:
case(COL)
8: ID <= I0;
4: ID <= I1;
2: ID <= I2;
1: ID <= I3;
endcase
4:
case(COL)
8: ID <= I4;
4: ID <= I5;
2: ID <= I6;
1: ID <= I7;
endcase
2:
case(COL)
8: ID <= I8;
4: ID <= I9;
2: ID <= I10;
1: ID <= I11;
endcase
1:
case(COL)
8: ID <= I12;
4: ID <= I13;
2: ID <= I14;
1: ID <= I15;
endcase
endcase
end
else begin
NextState <= S3;
EN <= 1;
end
end
else
NextState <= S0;
end
S4: NextState <= S0;
endcase
错误区域用注释表示。为什么它不起作用?
谢谢!
答案 0 :(得分:3)
对于组合逻辑,请避免自己编写灵敏度列表。你应该用这个代替:
always @(State, COL)
使用
always @(*)
您也可以使用always_comb。基本上,当计数器递增时,第二个always块不会执行。它仅在State或COL信号发生变化时执行。
其次,您不应在组合逻辑中使用非阻塞分配(&lt; =)。使用阻塞赋值(=)替换第二个“始终”块中的所有内容。
答案 1 :(得分:2)
正如Convergent指出的那样。 always @(State, COL)
应为always @*
(与always @(*)
同义)或SystemVerilog的always_comb
,并且阻止分配(=
)应在组合块中使用;不是非阻塞的(<=
)。
另一个问题是EN
,PrevCOL
和ID
中的推断复数锁存器。当我说锁存器时,我指的是电平敏感(有源高/低)锁存器。当我说翻牌时,我指的是边缘触发的触发器。
推断锁存器是指未为reg
分配始终块内所有可能路径的值的情况。就其本身而言,锁存器本身并不是一件坏事,数据需要设置和保持时间,并且启用引脚无故障。通常,没有必要并被许多人视为设计错误。 FPGA通常具有更多的触发器和逻辑门然后锁存器(锁存器使得形式意图逻辑门的时序变得更差,然后是专用锁存器)。如果使用锁存器,最好保持它尽可能简单,将其放在与解码逻辑分开的始终块中,并使用非阻塞分配。 Verilog只涉及锁存器; lint和LEC(逻辑等效检查)将针对可能的设计错误发出警告。 SystemVerilog允许显式锁存;链接和LEC不会发出警告。以下示例。总之,仅在需要时使用锁存器并保持简单。
// Verilog clean implicit latch
always @* begin
if (lat_en) // requirement: 'lat_en' must be glitch free
lat <= data; // requirement: 'data' must respects setup/hold time
end
// SystemVerilog explicit latch
always_latch begin
if (lat_en) // requirement: 'lat_en' must be glitch free
lat <= data; // requirement: 'data' must respects setup/hold time
end
推断的复杂锁存器比简单的锁存器更危险。复数锁存器的使能引脚或数据基于其他组合逻辑或锁存器。很难满足时机和经常出现故障。这意味着您的代码可以运行一段时间,然后意外地获取错误的数据。它们很难调试作为时序窗口,当应用合成和SDF注释时,可能会发生故障,并且可以根据其他变量再次更改FPGA或芯片。根据事件调度,在0次RLT模拟中甚至可能发生故障。总而言之,仅在需要时使用锁存器并保持简单。
谈到RTL编码风格,我通常遵循Cliff Cummings的2-always阻塞解决方案的建议(一个用于下一个状态组合逻辑,另一个用于同步分配):
在我的个人资料中还有其他链接指向有用的Verilog / SystemVerilog引用。
我建议将EN
,PrevCOL
和ID
放入适当的触发器中,并在组合块中计算出被尊重的next_*
分配。
//Synchronous Logic Assignments
always @(posedge Clk) begin // SV: use 'always_ff' instead of 'always'
if(Rst) begin
counter <= 0;
State <= S0;
EN <= 0;
PrevCOL <= 0;
ID <= 0;
end
else begin
State <= NextState;
counter <= next_EN ? counter + 1 : 0;
EN <= next_EN;
PrevCOL <= next_PrefCOL;
ID <= next_ID;
end
end
//Next State Logic Calculations
always @* begin // SV: use 'always_comb' instead of 'always @*'
// Default value assignments
next_EN = EN;
next_PrevCOL = PrevCOL;
next_ID = ID;
// Update value logic
case(State)
// Update NextState and next_* values in here
endcase
end
always @*
的FYI,其中RHS只是常量,然后可能不会在模拟中执行。这个条件没有定义LRM,模拟器的某些创建者决定执行时间0执行,其他人没有。它会正确合成。 SystemVerilog的always_comb
更好,因为它将在时间0执行。与always_comb
相比,请参阅IEEE Std 1800-2012§9.2.2.2.2 always @*
。使用Verilog,您可以使用assign
语句。