Verilog实战学习到RiscV - 4 : ICEStick 评估板计数器

这篇是关于always 时序逻辑的。直接上代码。

引脚配置文件

s 复制代码
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上使用了内部的上拉电阻。

代码

verilog 复制代码
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 综合的时候产生了如下错误:

sh 复制代码
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 所报的错误:

sh 复制代码
ERROR: Multiple edge sensitive events found for this signal!

正确的设计方式

在时序逻辑设计中,我们不应该在always块内部检查时钟信号的电平(例如clk == 1'b1),因为我们已经在always块的触发条件中指定了对时钟上升沿的响应。对于复位信号,一般我们会处理为同步或异步复位,这取决于设计要求。代码中已经指定了posedge rst,这通常表示异步复位。

修正后的always块

verilog 复制代码
always @(posedge clk or posedge rst) begin
    if (rst) begin
        leds <= 4'b0000; // 异步复位
    end else begin
        leds <= leds + 1'b1; // 时钟上升沿时计数
    end
end
  1. 触发条件:
  • always @(posedge clk or posedge rst)表示每当clk上升沿或rst上升沿时,这个块中的代码会被执行。
  • 触发条件是"边沿触发"(edge-sensitive),即代码只会在信号的边沿(上升或下降)发生变化时执行,而不会响应信号的电平状态。
  1. 复位处理:
  • 在块的开头,我们首先检查复位信号rst是否为高电平(有效),如果是,则将leds重置为4'b0000。
  • 这里的复位是异步的,因为复位发生时不需要等待时钟上升沿,只要rst变为高电平就立即重置。
  1. 计数逻辑:
  • 如果复位信号不为高电平(即rst无效),那么在时钟的上升沿,leds会递增1。
  • 这里的计数逻辑是同步的,因为计数操作仅在时钟的上升沿进行。

Makefile

上篇一条条输入命令有点麻烦,这次我写了一个 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

烧写

sh 复制代码
$ 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
相关推荐
西岸行者9 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
ZPC82109 天前
docker 镜像备份
人工智能·算法·fpga开发·机器人
ZPC82109 天前
docker 使用GUI ROS2
人工智能·算法·fpga开发·机器人
悠哉悠哉愿意9 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码9 天前
嵌入式学习路线
学习
毛小茛9 天前
计算机系统概论——校验码
学习
babe小鑫9 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms9 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下9 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。9 天前
2026.2.25监控学习
学习