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

发布于:2024-06-16 ⋅ 阅读:(18) ⋅ 点赞:(0)

这篇是关于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
  1. 触发条件:
  • always @(posedge clk or posedge rst)表示每当clk上升沿或rst上升沿时,这个块中的代码会被执行。
  • 触发条件是“边沿触发”(edge-sensitive),即代码只会在信号的边沿(上升或下降)发生变化时执行,而不会响应信号的电平状态。
  1. 复位处理:
  • 在块的开头,我们首先检查复位信号rst是否为高电平(有效),如果是,则将leds重置为4’b0000。
  • 这里的复位是异步的,因为复位发生时不需要等待时钟上升沿,只要rst变为高电平就立即重置。
  1. 计数逻辑:
  • 如果复位信号不为高电平(即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
    请添加图片描述