FPGA学习历程(三):HC05串口通信与SDC

  本文的实际完成时间是在博客 SDRAM那些事儿第一季教程 —— 小结 之后,此前只是挖了个坑没填完。又是一语成谶(为什么要说又?),回来重新学 FPGA 了,并对原来的学习笔记进行整理、修缮和补完。此为串口收发模块设计及与调试流程的补完部分,并在此基础上衍生了蓝牙模块的应用。


以下内容学习自开源骚客教程: SDRAM那些事儿第一季。

一、串口接收模块

1.1 时序设计

  设计思路在之前的小结文章里写得很详细,并非本文补完的重点,因此只贴出时序图。
在这里插入图片描述

1.2 模块源码

1.2.1 uart_rx.v

  参照时序设计图很容易理解源码的构建思路(自认为注释已经够多、够友好了)。

`define SIM                                 // Modelsim 仿真宏定义

module uart_rx
(
    // system signals
    input               sclk        ,       // 系统时钟 50MHz          
    input               s_rst_n     ,       // 系统复位信号,低电平有效
    
    // UART Interface
    input               rs232_rx    ,       // rx 信号
    
    // others
    output  reg [7:0]   rx_data     ,       // rx 数据
    output  reg         po_flag             // 数据可用标识
);

/**************************************************************************/
/***************** Define Parameter and Internal Signals ******************/
/**************************************************************************/
// 内部参数定义
`ifndef SIM
localparam              BAUD_END    =   5207            ;       // 真实参数
`else
localparam              BAUD_END    =   56              ;       // 仿真参数,改小的目的仅为方便仿真
`endif
localparam              BAUD_M      =   BAUD_END/2 - 1  ;
localparam              BIT_END     =   8               ;

// 以下全是模块设计时序图中出现的非输出信号
reg                     rx_r1                           ;       // rx 信号延一拍
reg                     rx_r2                           ;
reg                     rx_r3                           ;
reg                     rx_flag                         ;
reg [12:0]              baud_cnt                        ;
reg                     bit_flag                        ;
reg [3:0]               bit_cnt                         ;

// 模块内部信号
wire                    rx_neg                          ;       //  rx 信号下降沿

/**************************************************************************/
/******************************* Main Code ********************************/
/**************************************************************************/
// 捕获下降沿:rx_r2 为低电平且 rx_r3 为高电平时
assign  rx_neg  =   ~rx_r2 & rx_r3;

// 延三拍处理
always  @(posedge sclk)
begin
    rx_r1 <= rs232_rx;
    rx_r2 <= rx_r1;
    rx_r3 <= rx_r2;
end

// rx_flag
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        rx_flag <= 1'b0;
    else if(rx_neg == 1'b1)     // 在起始位就置位了,所以即便在数据传输过程中出现了下降沿,rx_flag 也还是为高,并无影响
        rx_flag <= 1'b1;
    else if(bit_cnt == 'd0 && baud_cnt == BAUD_END)
        rx_flag <= 1'b0;
end

// baud_cnt
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        baud_cnt <= 'd0;
    else if(baud_cnt == BAUD_END)       // 计满清零
        baud_cnt <= 'd0;
    else if(rx_flag == 1'b1)
        baud_cnt <= baud_cnt + 1'b1;
    else                                // rx_flag 为低时当然要清零
        baud_cnt <= 'd0;
end

// bit_flag
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        bit_flag <= 1'b0;
    else if(baud_cnt == BAUD_M)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;
end

// bit_cnt
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        bit_cnt <= 'd0;
    else if(bit_flag == 1'b1 && bit_cnt == BIT_END)     // 这里和下面的顺序不能颠倒,因为两者的适用范围的大小关系
        bit_cnt <= 'd0;
    else if(bit_flag == 1'b1)
        bit_cnt <= bit_cnt + 1'b1;
end

// rx_data
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        rx_data <= 'd0;
    else if(bit_flag == 1'b1 && bit_cnt >= 'd1)         // bit_cnt 设置为大于 1 只是为了滤除起始位,实际上不加这个条件也可以,起始位也会被移出去
        rx_data <= {rx_r2,rx_data[7:1]};
end

// po_flag
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        po_flag <= 1'b0;
    else if(bit_cnt == BIT_END && bit_flag == 1'b1)
        po_flag <= 1'b1;
    else
        po_flag <= 1'b0;
end

endmodule 

1.2.2 tb_uart_rx.v

`timescale 1ns/1ns

module tb_uart_rx;

reg             sclk;
reg             s_rst_n;
reg             rs232_tx;

wire            po_flag;
wire    [7:0]   rx_data;

reg     [7:0]   mem_a[3:0]; // 定义一个存储器

/************************************ define system signals ************************************/
initial begin
    sclk = 1;
    s_rst_n <= 0;
    rs232_tx <= 1;          // 为高表示处于空闲状态
    #100                    // 延时 100ns
    s_rst_n <= 1;           // 释放复位信号
    #100
    tx_byte();
end

always  #5  sclk = ~sclk;   // 模拟系统时钟,延时 5ns 翻转一次,10ns 一个周期

/***********************************************************************************************/
initial $readmemh("./tx_data.txt",mem_a);   // 从当前目录下读取 txt 文本内容并存放到存储器中(内容为 55 12 34 AA)

// 发送串口数据
task tx_byte();
    integer i;
    for(i=0; i<4; i=i+1)
    begin
        tx_bit(mem_a[i]);
    end
endtask

// 模拟串口发送时序
task tx_bit
(
    input   [7:0]   data
);
    integer i;                              // 整型变量
    for(i=0; i<10; i=i+1)                   // 仿真脚本里用用就行,正式模块里不建议使用 for
    begin
        case(i)
            0:  rs232_tx    <=  1'b0;       // 发送起始位
            1:  rs232_tx    <=  data[0];
            2:  rs232_tx    <=  data[1];
            3:  rs232_tx    <=  data[2];
            4:  rs232_tx    <=  data[3];
            5:  rs232_tx    <=  data[4];
            6:  rs232_tx    <=  data[5];
            7:  rs232_tx    <=  data[6];
            8:  rs232_tx    <=  data[7];
            9:  rs232_tx    <=  1'b1;       // 发送停止位
        endcase
        #560;                               // 延时,防止瞬时发送
    end
endtask

// uart_rx 模块例化
uart_rx uart_rx_inst
(
    // system signals
    .sclk        (sclk),            // 系统时钟 50MHz          
    .s_rst_n     (s_rst_n),         // 系统复位信号,低电平有效
    
    // UART Interface
    .rs232_rx    (rs232_tx),        // rx 信号
    
    // others
    .rx_data     (rx_data),         // rx 数据
    .po_flag     (po_flag)          // 数据可用标识
);

endmodule 

  跟着 Kevin 大佬一个字一个字写下来,基本没有理解困难。唯一脑子一时间没转过弯来的地方就是 uart_rx.v 中的 BAUD_END(56) 和 tb_uart_rx.v 中 task tx_bit 里延时防止瞬时发送全部数据位的延时长度(560)之间的 10 倍关系。
  解释:BAUD_END 在原设计中的定义就是 “串口发送 1bit 的数据占用的时钟周期的个数”,而换算到激励文件中的 task tx_bit 中发送 1bit 的数据占用的时间,就需要乘以激励文件中定义的时钟周期,也就是 10ns,所以两者是 10 倍关系。

1.3 Modelsim仿真

  新建好工程,把要测试的模块文件与激励文件都添加到工程里后编译(也相当于进一步检查了),如果没问题就会在下面的信息栏中输出绿色信息:
在这里插入图片描述
在这里插入图片描述
  来到 Library 页面,找到激励文件,单击右键选择仿真(Simulate without Optimization):
在这里插入图片描述
  将要测试的模块以及激励文件中包含的一众内部信号添加到 Wave 虚拟示波器中(选中要添加的信号组,快捷键 Ctrl + W)。之后在 Wave 下就会出现所有添加进来的信号。左键单击一下下图中左上角的图标,可以将所有信号的冗余前缀去除,方便阅读:
在这里插入图片描述
在这里插入图片描述
  工具栏中部可以指定要仿真的时间,设置好以后点击 Run 开始仿真:
在这里插入图片描述
  为方便阅读一些信号的实时数值,可以选中信号,右键单击,选择菜单栏下的 Radix 中的其他进制显示方式,如下图的仿真结果中就将 rx_data 修改为十六进制显示,bit_cnt 修改为 Unsigned 无符号整型显示:
在这里插入图片描述

二、串口发送模块

2.1 时序设计

  设计思路与串口接收高度一致。这里也只贴出时序图。
在这里插入图片描述

2.2 模块源码

2.2.1 uart_tx.v

`define SIM                                 // Modelsim 仿真宏定义

module uart_tx
(
    // system signals
    input               sclk        ,       // 系统时钟 50MHz          
    input               s_rst_n     ,       // 系统复位信号,低电平有效
    
    // UART Interface
    output  reg         rs232_tx    ,       // tx 信号
    
    // others
    input               tx_trig     ,       // 串口发送信号
    input       [7:0]   tx_data             // 待发送数据
);

/**************************************************************************/
/***************** Define Parameter and Internal Signals ******************/
/**************************************************************************/
// 内部参数定义
`ifndef SIM
localparam              BAUD_END    =   5207            ;       // 真实参数
`else
localparam              BAUD_END    =   56              ;       // 仿真参数,改小的目的仅为方便仿真
`endif
localparam              BAUD_M      =   BAUD_END/2 - 1  ;
localparam              BIT_END     =   8               ;

// 以下全是模块设计时序图中出现的非输出信号
reg [7:0]               tx_data_r                       ;       // tx_data 锁存
reg                     tx_flag                         ;
reg [12:0]              baud_cnt                        ;
reg                     bit_flag                        ;
reg [3:0]               bit_cnt                         ;

/**************************************************************************/
/******************************* Main Code ********************************/
/**************************************************************************/
// tx_data_r
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        tx_data_r <= 'd0;
    else if(tx_trig == 1'b1 && tx_flag == 1'b0)                 // tx_flag == 1'b0 是为了防止发送状态下被新数据扰乱原数据
        tx_data_r <= tx_data;
end

// tx_flag
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        tx_flag <= 1'b0;
    else if(tx_trig == 1'b1)                                    // 即便在数据传输过程中又出现了 tx_trig 拉高的情况,tx_flag 也还是为高,并无影响
        tx_flag <= 1'b1;
    else if(bit_cnt == BIT_END && bit_flag == 1'b1)
        tx_flag <= 1'b0;
end

// baud_cnt
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        baud_cnt <= 'd0;
    else if(baud_cnt == BAUD_END)       // 计满清零
        baud_cnt <= 'd0;
    else if(tx_flag == 1'b1)
        baud_cnt <= baud_cnt + 1'b1;
    else                                // tx_flag 为低时当然要清零
        baud_cnt <= 'd0;
end

// bit_flag
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        bit_flag <= 1'b0;
    else if(baud_cnt == BAUD_END)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;
end

// bit_cnt
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        bit_cnt <= 'd0;
    else if(bit_flag == 1'b1 && bit_cnt == BIT_END)     // 这里和下面的顺序不能颠倒,因为两者的适用范围的大小关系
        bit_cnt <= 'd0;
    else if(bit_flag == 1'b1)
        bit_cnt <= bit_cnt + 1'b1;
end

// rs232_tx
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        rs232_tx <= 1'b1;
    else if(tx_flag == 1'b1)
        case(bit_cnt)
            0:          rs232_tx <= 1'b0;               // 起始位
            1:          rs232_tx <= tx_data_r[0];
            2:          rs232_tx <= tx_data_r[1];
            3:          rs232_tx <= tx_data_r[2];
            4:          rs232_tx <= tx_data_r[3];
            5:          rs232_tx <= tx_data_r[4];
            6:          rs232_tx <= tx_data_r[5];
            7:          rs232_tx <= tx_data_r[6];
            8:          rs232_tx <= tx_data_r[7];
            default:    rs232_tx <= 1'b1;
        endcase
    else
        rs232_tx <= 1'b1;
end

endmodule 

2.2.2 tb_uart_tx.v

`timescale 1ns/1ns

module tb_uart_tx;

reg             sclk;
reg             s_rst_n;
reg             tx_trig;
reg [7:0]       tx_data;

wire            rs232_tx;

/************************************ define system signals ************************************/
initial begin
    sclk = 1;
    s_rst_n <= 0;
    #100                    // 延时 100ns
    s_rst_n <= 1;           // 释放复位信号
end

always  #5  sclk = ~sclk;   // 模拟系统时钟,延时 5ns 翻转一次,10ns 一个周期

/***********************************************************************************************/
initial begin
    tx_data <=  0'd0;
    tx_trig <=  0;
    #200
    tx_data <=  8'h55;
    tx_trig <=  1;
    #10
    tx_trig <=  0;
end

uart_tx uart_tx_inst
(
    // system signals
    .sclk        (sclk),            // 系统时钟 50MHz          
    .s_rst_n     (s_rst_n),         // 系统复位信号,低电平有效
    
    // UART Interface
    .rs232_tx    (rs232_tx),        // tx 信号
    
    // others
    .tx_trig     (tx_trig),         // 串口发送信号
    .tx_data     (tx_data)          // 待发送数据
);
endmodule 

2.3 Modelsim仿真

  十六进制的 0x55 转化为二进制就是 01010101,而使用 LSB 优先发送则会把顺序颠倒,算上拉低的起始位,rs232_tx 已经很好地完成了任务:
在这里插入图片描述

三、串口收发模块整合

  整合的时候在板卡上找了个扩展排座上的几个管脚当成 UART 模块用了:
在这里插入图片描述
在这里插入图片描述
  最终的测试结果还是成功的。这里有个小插曲:一开始咱用的串口调试助手是 XCOM(ALIENTEK官方推荐),结果测试的时候发现里面的通信协议似乎夹带了私货。比如下面的图示,明明只以十六进制发送了一个 55,但是回发的内容里却多了 0D 和 0A,底边栏的计数也是收和发的数据数量均为 3,那只能解释为串口助手本身在用户指定发送的内容后又补发了 0D 和 0A 了。
在这里插入图片描述

PS:后来发现好像是因为勾选了 “发送新行”。

  最后换了一个串口助手测试,同样的串口配置参数,这回对了,没有夹带私货,发送区里几个数据下面的计数器也显示几,回发的数据和数据数量也没有问题。
在这里插入图片描述
  如果要把波特率换成 115200 的话,只需要重新计算串口发送和串口接收模块里的 BAUD_END 就可以了。


以下内容为自身学习拓展需求。

四、HC05蓝牙调试

  这里使用的 HC05 是带按键的蓝牙模块(听说有带按键的和不带按键的两种,调试方法还不一样)。

4.1 进入 AT 模式

  使用 USB 转 TTL 连接 HC05 与 PC,按住 HC05 上的按键给蓝牙上电,等 STATE LED 亮起再松开,此时会发现蓝牙的 STATE LED 进入慢闪状态,表明此时蓝牙已经进入 AT 模式,可以在 PC 端使用串口助手向其发送 AT 指令。
  这里补充说明一下,在使用串口助手和蓝牙进行通信时,需要把波特率设置为 38400 ,停止位 1 位,数据为 8 位,无奇偶校验。另外,对于不同的串口助手也有一些格式上的差异。比如下面的 XCOM 和串口调试小助手 1.3。XCOM 下方有一个 “发送新行” 可以勾选,所以发送指令的时候不用多敲一次回车键,只需要发送 AT+RESET <空格>:
在这里插入图片描述
  而串口调试小助手 1.3 里没有 “发送新行” 的可勾选选项,所以要想正确发送指令,就需要在输入完整的指令后补一个回车键再发送(AT+RESET <空格> <回车>),才能正确被蓝牙识别:
在这里插入图片描述

4.2 AT常用指令

  • AT+RESET <空格>:重启模块,之后可以看到返回的 OK,以及蓝牙的 STATE LED 进入快闪状态;
  • AT+NAME=AX4010:设置蓝牙模块名称为 AX4010,之后可以看到返回的 OK;
  • AT+PSWD=ALINX:设置蓝牙配对码为 ALINX,之后可以看到返回的 OK;
  • AT+UART=115200,1,0:设置蓝牙的常规工作波特率为 115200,停止位1位,无校验位(AT 模式下的波特率还是 38400 不会变),之后可以看到返回的 OK;
  • AT+ROLE=1:设置蓝牙为主机模式;
  • AT+NAME?:查询蓝牙设备名(AT 模式下按住按键发送指令才能得到正常回应)
  • AT+ROLE?:查询蓝牙主从模式(默认为从机);
  • AT+UART?:查询蓝牙串口参数;
  • AT+PSWD?:查询蓝牙配对码;
  • AT+ADDR?:查询蓝牙地址(蓝牙模块间配对会用到);

4.3 模块间配对

  这里使用的是新的蓝牙模块,如果以前有设置过想重新配对的可以先恢复蓝牙的默认设置再进行配对。

  • 分别配置好两个蓝牙模块的 NAME、PSWD、UART 参数,注意 PSWD 要保持一致才能正确配对,UART 参数保持一致以保证数据的正常传输;
  • 将蓝牙 A 和蓝牙 B 的连接模式均修改为自动连接(AT+CMODE=0);
  • 将蓝牙 A 配置为主机模式,蓝牙 B 不用配置,因为默认就是从机模式;
  • 获取蓝牙 B 的地址,并将其绑定到蓝牙 A 上(此时需要将冒号改成逗号,如查询到蓝牙 B 的地址为 98d3:31:fda83f,在蓝牙 A 端绑定时的指令就应该是 AT+BIND=98d3,31,fda83f);
  • 将两个蓝牙模块复位,之后稍微等待一段时间两者就会自行连接(配对成功的标志就是蓝牙的 STATE LED 隔一段时间闪烁两次)。

在这里插入图片描述
  至此,两个蓝牙模块就已经完成了基本的配对,两者之间可以相互通信了:
在这里插入图片描述


以下内容学习自小梅哥时序分析与时序约束教程(小梅哥讲课讲得是真的好)。

五、时序分析与时序约束

5.1 起因

  原本按文章标题来看,到第四节为止就应该结束了,然而苦 SDC 久矣(不是.jpg),避无可避,终于还是出现了编号 332148 的严重警告:
在这里插入图片描述
  然而咱真的只是点了个流水灯 Orz…
在这里插入图片描述
  虽然知道这种情况下做不做约束其实也无伤大雅,但保不准以后能一直莽下去,终归还是得学一下怎么写 SDC 文件,遂有此大章节。

5.2 基本概念与模型

  本节的增设目的在于快速建立时序分析与时序约束的相关概念,以学会如何写 SDC 文件。第一节的 FPGA 基础讲解就不记录了,但是确实是后续的理论支撑,如果没学过或者忘了还是建议补完再来看简化版的记录(或者直接跟着小梅哥走一遍)。

  • 时序分析
      时序分析的目的就是通过分析 FPGA 设计中各个寄存器之间的数据和时钟传输路径(由 EDA 软件通过针对特定器件布局布线得到),来分析数据延迟和时钟延迟之间的关系。一个设计 OK 的系统,必然能够保证整个系统中所有的寄存器都能够正确地寄存数据。
  • 时序约束
      时序约束的作用有二:一是告知 EDA 软件,该设计需要达到怎样的时序指标,然后 EDA 软件会根据时序约束的各个参数,尽力优化布局布线,以达到该约束的指标;二是协助 EDA 软件分析设计的时序路径,以产生相应的时序报告(这个才是给设计者看的)。

  以小梅哥课程上分析的电路为例(先不考虑时钟延迟,假设 clk 到达所有寄存器的时间完全一致,仅分析数据延迟,下图中 DFF 为 D 触发器,LE 逻辑单元内部的 LUT 为查找表):
在这里插入图片描述
在这里插入图片描述

  • Tco:数据输出时间,即时钟上升沿到达 D 触发器数据输出到 Q 端的延迟(如红刻度线与 a_reg_Q 下降沿之间的时间差,或者绿刻度线与 c_reg_Q 下降沿之间的时间差);
  • Tsu:建立时间,为保证能正确地寄存数据,D 触发器的 D 端数据必须在时钟上升沿到达的前 N ns 稳定下来(c_reg_D 下降沿与绿刻度线之间的时间差);
  • Tdata:a_reg_Q 下降沿到 c_reg_D 下降沿之间的时间差,由两个寄存器之间的级联情况决定

  现在引入 clk 延迟:
在这里插入图片描述
在这里插入图片描述

  • Tskew:时钟偏斜,即时钟从源端口触发,到达目的寄存器和源寄存器的时间差(Tclk2 - Tclk1,不一定是正值);
  • Slack:时间余量,即 Tdata 时刻点与 Tsu 时刻点之间的时间差,为正则表示满足 Tsu 条件,为负则表示不满足。

由此可以得到时序约束中建立时间余量的基本公式:
  极限情况下满足公式:
T c l k 1 + T c o + T d a t a + S l a c k + T s u = T c l k ( 时钟周期 ) + T c l k 2 Tclk1 + Tco + Tdata + Slack + Tsu = Tclk(时钟周期) + Tclk2 Tclk1+Tco+Tdata+Slack+Tsu=Tclk(时钟周期)+Tclk2   让 Slcak 为非负,也就是:
T c l k 1 + T c o + T d a t a + T s u < = T c l k ( 时钟周期 ) + T c l k 2 Tclk1 + Tco + Tdata + Tsu <= Tclk(时钟周期) + Tclk2 Tclk1+Tco+Tdata+Tsu<=Tclk(时钟周期)+Tclk2 T c o + T d a t a + T s u < = T c l k + T s k e w Tco + Tdata + Tsu <= Tclk + Tskew Tco+Tdata+Tsu<=Tclk+Tskew S l a c k = T c l k + T s k e w − T s u − T c o − T d a t a > = 0 Slack = Tclk + Tskew - Tsu - Tco - Tdata >= 0 Slack=Tclk+TskewTsuTcoTdata>=0

5.3 Quartus II 时序报告分析

  要看到时序报告,可以将工程全编译一次,之后软件会自动弹出编译报告页面,又或者在已经全编译完成的基础上,直接点开编译报告页面:
在这里插入图片描述
  左下角标红的部分就是时序分析报告了:
在这里插入图片描述
  点开时序分析报告,在 Clocks 一栏可以看到软件检测出的所有时钟信号的信息(应该是 50MHz 的,但是因为没有在 SDC 文件中添加时钟约束,导致其使用默认的时钟频率 1GHz 来进行编译):
在这里插入图片描述
  接下来的三个 Slow 1200mV 85C Model、Slow 1200mV 0C Model、Fast 1200mV 0C Model 分别是软件提供的三种时序模型。以 Slow 1200mV 85C Model 为例,其含义为:芯片内核供电电压 1200mV,工作温度 85 ℃ 情况下的慢速传输模型,一般建立时间余量的话分析这个模型就足够了。
在这里插入图片描述
  点进第一个模型,默认跳转到第一个子页面 Fmax Summary,里面记录了该设计能够运行的最大频率。可以看到最大工作频率是 222.92 MHz,所以设计的逻辑电路在 50MHz 的实际时钟驱动下是可以正常使用的,有问题的只是软件做时序分析的时候用的 1GHz 的时钟来分析,这样当然就不满足了,所以报告标红了:
在这里插入图片描述
  再往下看,有一个 Worst-Case Timing Paths ,也就是最差情况下的时序路径情况(即此时 Tdata 最大),这是软件计算最大工作频率时的假设前提:
在这里插入图片描述
  简而言之,目前的时序报告并没有什么实际参考意义,因为软件是用 1GHz 的时钟频率来分析和报告的(正经人谁用 1GHz 的时钟),所以应该想办法告诉软件该设计使用的时钟频率是多少。于是时钟约束(SDC)就来了。

5.4 SDC文件编写(生成)方法

  首先打开 TTA(TimeQuest Timing Analyzer) 软件:
在这里插入图片描述
  进入软件页面后双击左下角的 CreateTiming Netlist ,创建时序网表:
在这里插入图片描述
  之后双击 Read SDC File,(创建并)读取 SDC 文件。此时可以看到软件内部更为详细的报告,也可以开始添加约束:
在这里插入图片描述
  找到 Report Clocks 一栏双击,可以看到当前的时钟频率。现在开始添加时钟约束:选择菜单栏 Constraints ⟶ \longrightarrow Create Clock…
在这里插入图片描述
  可以在弹出来的页面里配置具体的时钟约束。Clock name 只是约束中的时钟代号,可以和设计中实际的时钟名称不一致(后面直接在 Targets 这一栏绑定);Waveform edges 可以设定时钟的占空比(默认是 50%);最底下对应的 SDC command 很明显就是这样配置下来最终要写入到 SDC 文件中的实际约束指令了。
在这里插入图片描述
在这里插入图片描述
  修改好之后点击 OK 和 Run,之后在左上角(文件列表里单击右键) Regenerate All Out of Date 就可以了:
在这里插入图片描述
在这里插入图片描述
  最后一步,双击 Write SDC File,大功告成:
在这里插入图片描述
  之后在 Quartus 工程下重新全编译一下,时序警告就消失了:
在这里插入图片描述
  再看一看可运行的最大工作频率,发现降低到了 151.68MHz,这是因为人为添加了约束,EDA 只根据约束需求去布局布线以达到约束要求,但是不会每次编译都按最优的情况去编译(简单来说就是偷懒):
在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值