FPGA入门

1.认识Vivado

image-20240620223058450

Sources: 显示项目中的所有源文件,如设计源文件、约束文件等。你可以在这里添加、编辑和组织Verilog文件。

点击 Create File,输入文件名,例如 top.v(对于Verilog)

为方便管理,保持顶层文件与工程文件一致。

**分析与综合:**点击Run Synthesis

**约束:**生成原理图,点开I/O Port分配管脚和电压(或直接编辑约束文件),点击 Generate Bitstream生成比特流。

烧录: Open Hardware Manager ->Open Target-> auto connect

2.例程1:LED_Bink

module top( // 定义一个名为top的模块
    //定义它的io
    input wire clk,          // FPGA的时钟信号
    output reg led           // 控制LED的输出端口
);

// 定义一个计数器,用于生成时间延迟
    reg [24:0] counter = 0;  // 定义一个25位宽的寄存器counter,初始值为0

always @(posedge clk) begin
    counter <= counter + 1;
    if(counter == 25_000_000) begin
        led <= ~led;        // 翻转LED状态
        counter <= 0;       // 重置计数器
    end
end

endmodule

2.1.Verilog模块结构

Verilog代码基于模块(module)结构,每个模块可以表示一个硬件组件。

模块以关键词 module 开始,并以 endmodule 结束。模块可以包含输入输出端口、内部信号定义、逻辑描述等。

2.2.寄存器和线网

  • verilog基本数据类型:
    • wire:用于连续赋值,不能在过程控制块(如 always 块)内被赋值。
    • reg:不仅仅是寄存器,它可以在过程控制块中被赋值。
    • parameter 关键字用于定义常量值。
    • 形如[24:0] 是一种位向量的声明方式。表示位0到位24。

2.3.重要概念:阻塞赋值与非阻塞赋值

  • 阻塞赋值=):执行顺序依赖,前一个赋值完成后,后一个才开始。
  • 非阻塞赋值<=):所有赋值看似同时发生,更接近硬件的行为。

always 块中,通常使用非阻塞赋值 <= 来更新寄存器。这是为了确保所有赋值在同一个时钟周期内并行更新,模拟真实硬件的行为。

2.4.控制过程块

  1. always

    Verilog使用 always 块来描述硬件的行为,这类似于软件中的函数,但是是并行执行的。

    基本语法:

    always @(触发条件) begin
        // 代码块
    end
    

    触发条件:

    • @(posedge):信号的上升沿
    • @(negedge):信号的下降沿
    • @(signal)@(any changes):信号值的任何变化
  2. initial

    initial 块用于在仿真开始时执行一次性的操作,通常用于初始化变量或在测试中设置环境。

    基本语法:

    initial begin
        // 代码块
    end
    

3.例程2:LED_Breath

module breath_led(
    input       sys_clk ,      //系统时钟 50MHz
    input       sys_rst_n ,    //系统复位,低电平有效
    
    output reg  led            //LED灯
);

//parameter define
parameter CNT_2US_MAX = 7'd100;    
parameter CNT_2MS_MAX = 10'd1000; 
parameter CNT_2S_MAX  = 10'd1000; 

//reg define
reg [6:0] cnt_2us; 
reg [9:0] cnt_2ms;   
reg [9:0] cnt_2s;     
reg       inc_dec_flag; //亮度递增/递减 0:递增 1:递减

//*****************************************************
//**                  main code
//*****************************************************

//cnt_2us:计数2us
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cnt_2us <= 7'b0;
    else if(cnt_2us == (CNT_2US_MAX - 7'b1 ))
        cnt_2us <= 7'b0;
    else
        cnt_2us <= cnt_2us + 7'b1;
end

//cnt_2ms:计数2ms
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cnt_2ms <= 10'b0;
    else if(cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
        cnt_2ms <= 10'b0;
    else if(cnt_2us == CNT_2US_MAX - 7'b1)
        cnt_2ms <= cnt_2ms + 10'b1;
    else
        cnt_2ms <= cnt_2ms;
end

//cnt_2s:计数2s
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cnt_2s <= 10'b0;
    else if(cnt_2s == (CNT_2S_MAX - 10'b1) && cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
        cnt_2s <= 10'b0;
    else if(cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
        cnt_2s <= cnt_2s + 10'b1;
    else
        cnt_2s <= cnt_2s;         
end

//inc_dec_flag为低电平,led灯由暗变亮,inc_dec_flag为高电平,led灯由亮变暗
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        inc_dec_flag <= 1'b0;
    else if(cnt_2s == (CNT_2S_MAX - 10'b1) && cnt_2ms ==( CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
        inc_dec_flag <= ~inc_dec_flag;
    else
        inc_dec_flag <= inc_dec_flag;
end

//led:输出信号连接到外部的led灯
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        led <= 1'b0;
    else if((inc_dec_flag == 1'b1 && cnt_2ms >= cnt_2s) || (inc_dec_flag == 1'b0 && cnt_2ms <= cnt_2s))
        led <= 1'b1;
    else
        led <= 1'b0;
end

endmodule

memo:

  • 代码通过控制一个周期内LED点亮时间来控制亮度。

  • 所有always都是并行运行的

  • reg不可在alwaysinitial外赋值

  • 重新编译完记得reload

  • 一个reg最好只在一个always内赋值

3.1.数字常量表示形式

<size>'<base><number>

  • size:数字的位宽。

  • base:数字的基数,可以是b(二进制)、o(八进制)、d(十进制)或h(十六进制)。

  • number:具体的数字值。

例如:

  • 10'd1000 表示一个十进制的1000,它是一个10位宽的数字。
  • 7'b1 表示二进制的1,它是一个7位宽的数字,实际上等同于二进制的0000001

遇到非常奇怪的问题,就是原理图无法正确生成。(发现是无论如何始终LED输出都是0导致的)

3.2.计数器实现

通过三个级联的always计时器,实现精确定时。

  • cnt_2us 计数器:这是最基础的计数器,它在每个系统时钟的上升沿递增,当达到预设的最大值,并且触发下一个级别的计数器 cnt_2ms

  • cnt_2ms 计数器:这个计数器仅在 cnt_2us 计数器达到其最大值并复位时递增。这样,每当 cnt_2us 完成一轮计数,cnt_2ms 才递增一次。

  • cnt_2s 计数器:这个计数器依赖于 cnt_2ms 的递增。当 cnt_2ms 达到其最大值并复位,且同时 cnt_2us 也在同一时刻达到最大值并复位时,cnt_2s 才会递增。

条件复位和递增

  • 对于 cnt_2ms:这个计数器检查 cnt_2us 是否达到了其最大值 (CNT_2US_MAX - 1)。仅当这个条件为真时,cnt_2ms 才会递增。

  • 对于 cnt_2s:这个计数器的递增依赖于 cnt_2ms 达到其最大值且 cnt_2us 同时达到其最大值的情况。

  • 上级的所有计数器达到最大值时当级才递增。

4.Day2

今天开始正经看文档和视频,不瞎捣鼓了。

4.1assign 语句

assign target = expression;
  • target:目标信号,线网(wire)类型的变量。不可以是寄存器(reg)类型。
  • expression:表达式,可以包含常数、信号名、逻辑运算符等,用于计算赋值给目标信号的值。
  • 特点:
    • 实时更新:当 expression 中任何值发生变化时,target 的值会立即更新。

    • 组合逻辑:通常用于实现无时钟控制的组合逻辑电路。

所以,我们有一个更好的实现计时器的方法:

//计数器溢出使能信号
assign  counter_en = (counter == (COUNT_MAX - 1'b1))  ?  1'b1  :  1'b0;
//用于产生 0.5 秒使能信号的计数器 
always @(posedge sys_clk or negedge sys_rst_n) begin 
    if (sys_rst_n == 1'b0) 
        counter <= 1'b0;      
    else if (counter_en)          
        counter <= 1'b0; 
    else 
        counter <= counter + 1'b1; 
end

4.2硬件调试

image-20240621214000545

首先检查时钟和复位。

4.2.1ILA调试

ILA可以实时监控FPGA内部的信号,不需要将信号引出到外部测试设备。

image-20240621223310899

用了ip核里的ILA,加上条件,很快就定位到问题语句

assign  pwm_width_ov = (pwm_cnt == (CNT_PWM_MAX - 16'd1))  ?  1'b1  :  1'b0;

非常低级的错误,但也算给我第一次使用ip核提供了宝贵的素材。

通过ILA的帮助,费劲千辛万苦,终于实现了一个呼吸灯。