这篇是关于always 时序逻辑的。直接上代码。
引脚配置文件
set_io leds[0] 99
set_io leds[1] 98
set_io leds[2] 97
set_io leds[3] 96
set_io -pullup yes pmod[0] 78
set_io -pullup yes pmod[1] 79
参看icestick的原理图
这里在pmod上使用了内部的上拉电阻。
代码
module top_counter (
input [1:0] pmod, // 对应icestick IO
output reg [3:0] leds // reg: 综合工具Yosys会将leds连接到D-FF
);
wire clk;
wire rst;
assign clk = ~pmod[0];
assign rst = ~pmod[1];
always @(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
leds <= 4'b0000;
end
if (clk == 1'b1) begin
leds <= leds + 1'b1;
end
end
endmodule
这段时序逻辑电路在使用Yosys 综合的时候产生了如下错误:
Creating register for signal `\SB_DFFES.\Q' using process `\SB_DFFES.$proc$/usr/local/bin/../share/yosys/ice40/cells_sim.v:803$203'.
created $adff cell `$procdff$447' with positive edge clock and positive level reset.
Creating register for signal `\SB_DFFESS.\Q' using process `\SB_DFFESS.$proc$/usr/local/bin/../share/yosys/ice40/cells_sim.v:742$196'.
created $dff cell `$procdff$448' with positive edge clock.
Creating register for signal `\SB_DFFER.\Q' using process `\SB_DFFER.$proc$/usr/local/bin/../share/yosys/ice40/cells_sim.v:662$192'.
created $adff cell `$procdff$449' with positive edge clock and positive level reset.
Creating register for signal `\SB_DFFESR.\Q' using process `\SB_DFFESR.$proc$/usr/local/bin/../share/yosys/ice40/cells_sim.v:601$185'.
created $dff cell `$procdff$450' with positive edge clock.
Creating register for signal `\SB_DFFS.\Q' using process `\SB_DFFS.$proc$/usr/local/bin/../share/yosys/ice40/cells_sim.v:527$182'.
created $adff cell `$procdff$451' with positive edge clock and positive level reset.
Creating register for signal `\SB_DFFSS.\Q' using process `\SB_DFFSS.$proc$/usr/local/bin/../share/yosys/ice40/cells_sim.v:477$179'.
created $dff cell `$procdff$452' with positive edge clock.
Creating register for signal `\SB_DFFR.\Q' using process `\SB_DFFR.$proc$/usr/local/bin/../share/yosys/ice40/cells_sim.v:406$176'.
created $adff cell `$procdff$453' with positive edge clock and positive level reset.
Creating register for signal `\SB_DFFSR.\Q' using process `\SB_DFFSR.$proc$/usr/local/bin/../share/yosys/ice40/cells_sim.v:356$173'.
created $dff cell `$procdff$454' with positive edge clock.
Creating register for signal `\SB_DFFE.\Q' using process `\SB_DFFE.$proc$/usr/local/bin/../share/yosys/ice40/cells_sim.v:311$171'.
created $dff cell `$procdff$455' with positive edge clock.
Creating register for signal `\SB_DFF.\Q' using process `\SB_DFF.$proc$/usr/local/bin/../share/yosys/ice40/cells_sim.v:271$169'.
created $dff cell `$procdff$456' with positive edge clock.
Creating register for signal `\top_counter.\leds' using process `\top_counter.$proc$top_counter.v:13$383'.
ERROR: Multiple edge sensitive events found for this signal!
make: *** [Makefile:23: top_counter.json] Error 1
两个if语句处理两种情况没有问题啊? 但其实,这是一个典型的C语言嵌入式程序猿会犯的典型错误。以下详细解释。
时钟和复位信号的处理
在时序逻辑设计中,always块的触发条件决定了什么时候执行其中的逻辑。在Verilog代码中,我们需要处理两个信号:
时钟信号(clk):通常用于在每个时钟周期(上升沿或下降沿)更新状态。
复位信号(rst):通常用于在复位条件下重置状态,一般来说是异步复位,即不依赖时钟。
为什么不在always块内部检查时钟电平?
-
冗余:在always块中检查clk == 1’b1是多余的,因为我们已经在触发条件中指定了posedge clk,这意味着我们只在clk上升沿时执行代码。在执行代码时,clk必然处于高电平,因此再检查clk的电平是多余的。
-
潜在错误:在时序逻辑中直接检查时钟的电平可能导致不一致或错误的行为,特别是在综合工具和仿真环境中。
这就解释了Yosys 所报的错误:
ERROR: Multiple edge sensitive events found for this signal!
正确的设计方式
在时序逻辑设计中,我们不应该在always块内部检查时钟信号的电平(例如clk == 1’b1),因为我们已经在always块的触发条件中指定了对时钟上升沿的响应。对于复位信号,一般我们会处理为同步或异步复位,这取决于设计要求。代码中已经指定了posedge rst,这通常表示异步复位。
修正后的always块
always @(posedge clk or posedge rst) begin
if (rst) begin
leds <= 4'b0000; // 异步复位
end else begin
leds <= leds + 1'b1; // 时钟上升沿时计数
end
end
- 触发条件:
- always @(posedge clk or posedge rst)表示每当clk上升沿或rst上升沿时,这个块中的代码会被执行。
- 触发条件是“边沿触发”(edge-sensitive),即代码只会在信号的边沿(上升或下降)发生变化时执行,而不会响应信号的电平状态。
- 复位处理:
- 在块的开头,我们首先检查复位信号rst是否为高电平(有效),如果是,则将leds重置为4’b0000。
- 这里的复位是异步的,因为复位发生时不需要等待时钟上升沿,只要rst变为高电平就立即重置。
- 计数逻辑:
- 如果复位信号不为高电平(即rst无效),那么在时钟的上升沿,leds会递增1。
- 这里的计数逻辑是同步的,因为计数操作仅在时钟的上升沿进行。
Makefile
上篇一条条输入命令有点麻烦,这次我写了一个 Makefile 方便很多。
# Define the top-level module and output files
TOP = top_counter
BLIF = top_counter.blif
JSON = top_counter.json
ASC = top_counter.asc
BIN = top_counter.bin
PCF = pinmap.pcf
# Define the Yosys, nextpnr, and icestorm commands
YOSYS_CMD = yosys -p "synth_ice40 -top $(TOP) -blif $(BLIF) -json $(JSON)" $(TOP).v
NEXTPNR_CMD = nextpnr-ice40 --hx1k --json $(JSON) --pcf $(PCF) --asc $(ASC)
ICEPACK_CMD = icepack $(ASC) $(BIN)
ICETIME_CMD = icetime -tmd hx1k $(ASC)
ICEPROG_CMD = iceprog $(BIN)
# Default target
all: $(BIN)
# Yosys synthesis
$(BLIF) $(JSON): $(TOP).v
$(YOSYS_CMD)
# nextpnr place and route
$(ASC): $(JSON) $(PCF)
$(NEXTPNR_CMD)
# Icepack to create a binary file
$(BIN): $(ASC)
$(ICEPACK_CMD)
# Timing analysis (optional)
timing: $(ASC)
icetime -tmd hx1k $(ASC)
# Program the FPGA
program: $(BIN)
iceprog $(BIN)
# Clean up
clean:
rm -f $(BLIF) $(JSON) $(ASC) $(BIN)
.PHONY: all clean timing program
烧写
$ iceprog top_counter.bin
init..
cdone: high
reset..
cdone: low
flash ID: 0x20 0xBA 0x16 0x10 0x00 0x00 0x23 0x72 0x21 0x19 0x05 0x00 0x58 0x00 0x21 0x16 0x07 0x17 0xCE 0x6D
file size: 32220
erase 64kB sector at 0x000000..
programming..
done.
reading..
VERIFY OK
cdone: high
Bye.
结果
我没有按钮,就随便用几根线模拟一下按键按下的时候的GND 下降沿和 被内部上拉电阻上拉后的上升沿。
这里时钟用一根连线模拟,不然时钟跳得太快看不见led变化。后面我们可以做一个分频。
白线 = CLK
灰线 = RST
- Count up
- Reset