基础设计三——FPGA学习笔记<4>

2023-11-07

前置学习:

基础设计二——FPGA学习笔记<3>

基础设计一——FPGA学习笔记<2>

verilog语法——FPGA学习笔记<1>

参考书目:《野火FPGA Verilog 开发实战指南》

目录

一.串口 RS232

<1>简介

<2>物理模型

<3>RS232通信协议

<4>设计实践

1.硬件资源

2.程序设计

(1)uart_rx设计

①波形图分析

②代码设计

③仿真设计

(2)uart_tx设计

①波形图分析

②代码设计

(3)顶层设计

①代码设计

②仿真设计

3.总结


一.串口 RS232

<1>简介

        通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作 UART。UART 是一种通用的数据通信协议,也是异步串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。它包括了 RS232、RS499、RS423、RS422 和 RS485 等接口标准规范和总线标准规范。三大低速总线(UART、SPI、IIC)

        UART 和 SPI、IIC 不同的是,它是异步通信接口,异步通信中的接收方并不知道数据什么时候会到达,所以双方收发端都要有各自的时钟,在数据传输过程中是不需要时钟的,发送方发送的时间间隔可以不均匀,接受方是在数据的起始位和停止位的帮助下实现信息同步的。而 SPI、IIC 是同步通信接口(后面的章节会做详细介绍),同步通信中双方使用频率一致的时钟,在数据传输过程中时钟伴随着数据一起传输,发送方和接收方使用的时钟都是由主机提供的。

         UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫 rx(Receiver),如图 26-1 所示,对于 PC 来说它的 tx 要和对于 FPGA 来 说的 rx 连接,同样 PC 的 rx 要和 FPGA 的 tx 连接,如果是两个 tx 或者两个 rx 连接那数据 就不能正常被发送出去和接收到,所以不要弄混,记住 rx 和 tx 都是相对自身主体来讲的。 UART 可以实现全双工,即可以同时进行发送数据和接收数据。        

        设计 FPGA 部分接收串口数据和发送串口数据的模块,最后把两个模块拼接起来,最后通过 loopback 测试(回环测试)来验证设计模块的正确性。所谓 loopback 测试就是发送端发送什么数据,接收端就接收什么数据。

串口 RS232 缺点:距离不远,传输速率相对较慢

串口 RS232优点:

1、很多传感器芯片或 CPU 都带有串口功能,目的是在使用一些传感器或 CPU 时可以通过串口进行调试,十分方便;

2、在较为复杂的高速数据接口和数据链路集合的系统中往往联合调试比较困难,可以先使用串口将数据链路部分验证后,再把串口换成高速数据接口。如在做以太网相关的项目时,可以在调试时先使用串口把整个数据链路调通,然后再把串口换成以太网的接口;

3、串口的数据线一共就两根,也没有时钟线,节省了大量的管脚资源。

<2>物理模型

设备被分为数据终端设备 DTE(计算机、路由)和 数据通讯设备 DCE(调制调解器)。我们以这种通讯模型讲解它们的信号线连接方式及各个 信号线的作用。

旧式台式计算机 RS-232 标准 COM 口(也称 DB9 接口):

串口线中的 RTS、CTS、DSR、DTR 及 DCD 信号,使用逻辑 1 表示信号有效,逻辑 0 表示信号无效。例如,当计算机端控制 DTR 信号线表示为逻辑 1 时,它是为了告知远端的调制调解器,本机已准备好接收数据, 0 则表示还没准备就绪。

<3>RS232通信协议

1、RS232 是 UART 的一种,没有时钟线,只有两根数据线,分别是 rx 和 tx,这两根线都是 1bit 位宽的。其中 rx 是接收数据的线,tx 是发送数据的线。

2、rx 位宽为 1bit,PC 机通过串口调试助手往 FPGA 发 8bit 数据时,FPGA 通过串口线 rx 一位一位地接收,从最低位到最高位依次接收,最后在 FPGA 里面位拼接成 8 比特数据。

3、tx 位宽为 1bit,FPGA 通过串口往 PC 机发 8bit 数据时,FPGA 把 8bit 数据通过 tx 线一位一位的传给 PC 机,从最低位到最高位依次发送,最后上位机通过串口助手按照 RS232 协议把这一位一位的数据位拼接成 8bit 数据。

4、串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除 了中间包含 8bit 有效数据外,还在每一帧的开头都必须有一个起始位,且固定为 0在每 一帧的结束时也必须有一个停止位,且固定为 1即最基本的帧结构(不包括校验等)有 10bit。在不发送或者不接收数据的情况下,rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持 高电平,如果有数据帧传输时,首先会有一个起始位,然后是 8bit 的数据位,接着有 1bit 的停止位,然后 rx 和 tx 继续进入空闲状态,然后等待下一次的数据传输。如图为一个最基本的 RS232 帧结构。

5、波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是 1bit 进 行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、9600、115200 等,我们选用 9600 的波特率进行串口章节的讲解。

6、比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为 “每秒比特数(bps)”。比特率可由波特率计算得出,公式为:比特率=波特率 * 单个调制状态对应的二进制位数。如果使用的是 9600 的波特率,其串口的比特率为:9600Bps * 1bit= 9600bps。

7、由计算得串口发送或者接收 1bit 数据的时间为一个波特,即 1/9600 秒,如果用 50MHz(周期为 20ns)的系统时钟来计数,需要计数的个数为 cnt = (1s * 10^9)ns / 9600bit)ns / 20ns ≈ 5208 个系统时钟周期,即每个 bit 数据之间的间隔要在 50MHz 的时钟频率下计数 5208 次

8、上位机通过串口发 8bit 数据时,会自动在发 8 位有效数据前发一个波特时间的起始位,也会自动在发完 8 位有效数据后发一个停止位。同理,串口助手接收上位机发送的数据前,必须检测到一个波特时间的起始位才能开始接收数据,接收完 8bit 的数据后,再接收一个波特时间的停止位。

<4>设计实践

1.硬件资源

Artix-7开发板上使用CH340芯片将Rx、Tx信号线转成USB,硬件电路图见参考图书。

在使用时需将 J9 口的 1、2 脚以及 3、4 脚用跳帽连接起来才能正常使 用。

2.程序设计

整体框图:

(1)uart_rx设计

        uart_rx按照规定波特率将接受到的1bit串行数据转成8bit并行数据po_data,并有效并行数据有效的标志信号 po_data_flag

①波形图分析

波形设计思路详细解析:

第一部分

首先画出三个输入信号,时钟和复位,另一个是串行输入数据 rx, rx 串行数据一开始经过了 两级寄存器

理论上我们应该按照串口接收数据的时序要求找到 rx 的下降沿,然后开始接收起始位的数据,但为什么先将数据打了两拍呢?那就要先从跨时钟域会导致“亚稳态” 的问题上说起。

        把一个矩形脉冲的上升沿或下降沿放大后会发现其上升沿和下降沿并不是瞬间被拉高或拉低的,而是有一个倾斜变化的过程,这在 运放中被称为“压摆率”。

        如果 FPGA 的系统时钟刚好采集到 rx 信号上升沿或下降沿的中间位置附近(按照概率来讲,如果数据传输量足够大或传输速度足够快时一定会产生这种情况),即 FPGA 在接收 rx 数据时不满足内部寄存器的建立时间 Tsu(指触发器的时钟信号上升沿到来以前,数据稳定不变的最小时间)和保持时间 Th(指触发器的时钟信号上升沿到来以后,数据稳定不变的最小时间),此时 FPGA 的第一级寄存器的输出端在时钟沿到来之后比较长的一段时间内都处于不确定的状态,在 0 和 1 之间处于振荡状态,而不是 等于串口输入的确定的 rx 值。

        如图为产生亚稳态的波形示意图,rx 信号经过 FPGA 中的第一级寄存器后输出的 rx_reg1 信号在时钟上升沿 Tco 时间后会有 Tmet(决断时间)的振荡时段,当第一 个寄存器发生亚稳态后,经过 Tmet 的振荡稳定后,第二级寄存器就能采集到一个相对稳定的值。但由于振荡时间 Tmet 是受到很多因素影响的,所以 Tmet 时间有长有短。如图所示,当 Tmet1 时间长到大于一个采样周期后,那第二级寄存器就会采集到亚稳态,但是从第二级寄存器输出的信号就是相对稳定的了。当然会人会问到第二级寄存器的 Tmet2 的持续时间会不会继续延长到大于一个采样周期?这种情况虽然会存在,但是其概率是极小的,寄存器本身就有减小 Tmet 时间让数据快速稳定的作用。 由于在 PC 机中波特率和 rx 信号是同步的,而 rx 信号和 FPGA 的系统时钟 sys_clk 是异步的关系,我们此时要做的是将慢速时钟域(PC 机中的波特率)系统中的 rx 信号同步到快速时钟域(FPGA 中的 sys_clk)系统中,所使用的方法叫电平同步,俗称“打两拍法”。所以 rx 信号进入 FPGA 后会首先经过一级寄存器,出现如图 26-13 所示的亚稳态现 象,导致 rx_reg1 信号的状态不确定是 0 还是 1,就会受其影响使其他相关信号做出不同的判断,有的判断到“0”有的判断到“1”,有的也进入了亚稳态并产生连锁反应,导致后 级相关逻辑电路混乱。为了避免这种情况,rx 信号进来后首先进行打一拍的处理,打一拍后产生 rx_reg1 信号。但 rx_reg1 可能还存在低概率的亚稳态现象,为了进一步降低出现亚稳态的概率,我们将从 rx_reg1 信号再打一拍后产生 rx_reg2 信号,使之能够较大概率保证 rx_reg2 信号是 0 或者 1 中的一种确定情况,这样 rx_reg2 所影响的后级电路就都是相对稳定的了。但一定要注意:打两拍后虽然能让信号稳定到 0 或者 1 中确定的值,但究竟是 0 还是 1 却是随机的,与打拍之前输入信号的值没有必然的关系

注:单比特信号从慢速时钟域同步到快速时钟域需要使用打两拍的方式消除亚稳态。 第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为 70%~80%左右,第二级寄存器可以稳定输出的概率为 99%左右后面再多加寄存器的级数改善效果就不明显了,所以 数据进来后一般选择打两拍即可。 另外单比特信号从快速时钟域同步到慢速时钟域还仅仅使用打两拍的方式会漏采数据,所以往往使用脉冲同步法或的握手信号法;而多比特信号跨时钟域需要进行格雷码编码多比特顺序数才可以)后才能进行打两拍的处理,或者通过使用 FIFO、RAM 来处理数据与时钟同步的问题。 亚稳态振荡时间 Tmet 关系到后级寄存器的采集稳定问题,Tmet 影响因素包括:器件的生产工艺、温度、环境以及寄存器采集到亚稳态里稳定态的时刻等。甚至某些特定条件,如干扰、辐射等都会造成 Tmet 增长。

第二部分:

      打两拍后的 rx_reg2 信号就是我们可以在后级逻辑电路中使用的相对稳定的信号,只 比 rx 信号延后两。下一步我们就可以根据串口接收数据的时序要求找到串口帧起始开始的标志——下降沿,然后按顺序接收数据。由第一部分的分析得 rx_reg1 信号可能是不稳定的, 而 rx_reg2 信号是相对稳定的,所以不能直接用 rx_reg1 信号和 rx_reg2 信号来产生下降沿标志信号,因为 rx_reg1 信号的不稳定性可能会导致由它产生的下降沿标志信号也不稳定。所以如图所示,我们将 rx_reg2 信号再打一拍,得到 rx_reg3 信号,用 rx_reg2 信 号和 rx_reg3 信号产生 staet_nedge 作为下降沿标志信号。

第三部分:

        我们检测到了第一个下降沿,后面的信号将以下降沿标志信号 start_nedge 为条件开始接收一帧 10bit 的数据。但新的问题又出现了,我们的 rx 信号本身就是 1bit 的,如 果在判断第一个下降沿后,后面帧中的数据还可能会有下降沿出现,那我们会又产生一个start_nedge 标志信号。我们知道在 Verilog 代码中标志信号(flag)和使能信号(en)都是非常有用的,标志信号只有一拍,非常适合我们产生像下降沿标志这种信号,而使能信号就特别适合在此处使用,即对一段时间区域进行控制锁定。如图所示,当下降沿标志信号 start_nedge 为高电平时拉高工作使能信号 work_en(什么时候拉低在后面讲解),在 work_en 信号为高的时间区域内虽然也会有下降沿 start_nedge 标志信号产生,但是我们可 以根据 work_en 信号就可以判断出此时出现的 start_nedge 标志信号并不是我们想要的串口帧起始下降沿,从而将其过滤除掉。

        开始接收一帧数据。我们使用的是 9600bps 的波特率 和 PC 机进行串口通信,PC 机的串口调试助手要将发送数据波特率调整为 9600bps。而 FPGA 内部使用的系统时钟是 50MHz,前面也进行过计算,得出 1bit 需要的时间约为 5208 个(因为一帧只有 10bit,细微的近似计数差别不会产生数据错误,但是如果计数值差的过大,则会产生接收数据的错误)系统时钟周期,那么我们就需要产生一个能计 5208 个数的计数器来依次接收 10 个比特的数据,计数器每计 5208 个数就接收一个新比特的数据。如 图 26-17 所示,计数器名为 baud_cnt,当 work_en 信号为高电平的时候就让计数器计数当计数器计 5208 个数(从 0 到 5207)或 work_en 信号为低电平时计数器清零。

第四部分:

        现在我们可以根据波特率计数器一个一个接收数据了,我们发现 baud_cnt 计数 器在计数值为 0 到 5207 期间都是数据有效的时刻,那我们该什么时候取数据呢?理论上讲,在数据变化的地方取数是不稳定的,所以我们选择当 baud_cnt 计数器计数到 2603,即中间位置时取数最稳定(其实只要 baud_cnt 计数器在计数值不是在 0 和 5207 这两个最不稳定的时刻取数都可以,更为准确的是多次取值取概率最大的情况)。所以如图所示,在 baud_cnt 计数器计数到中点时产生一个时钟周期的 bit_flag 的取数标志信号,用于 指示该时刻的数据可以被取走。

        也就是说我们需要准确的知道此时此刻接收的是第几比特,当接收够 10bit 数据后,我们就停止继续接收数据,等 rx 信号被拉高待恢复到空闲状态后再等待接收下一帧的数据。所以我们还需要 产生一个用于计数该时刻接收的数据是第几个比特的 bit_cnt 计数器。如图所示,刚好可以利用我们已经产生的 bit_flag 取数标志信号,对该信号进行计数既可以知道此时我们接收的数据是第几个比特了。这里我们只让 bit_cnt 计数器的计数值为 8 时再清零,虽然 bit_cnt 计数器的计数值从 0 计数到 8 只有 9 个 bit,但这 9 个 bit 中已经包含的我们所需要 的 8bit 有用的数据,最后的 1bit 停止位没有用,可以不用再进行计数了,但如果非要将 bit_cnt 计数器的计数值计数到 9 后再清零也是可以的。

第五部分:

        我们接收到的 rx 信号是串行的,后面的系统要使用的是完整的 8bit 并行数据。 也就是说我们还需要将 1bit 串行数据转换为 8bit 并行数据的串并转换的工作,这也是我们在接口设计中常遇到的一种操作。串并转换就需要做移位,我们要考虑清楚什么时候开始移位,不能提前也不能推后,否则会将无用的数据也移位进来,所以我们需要卡准时间。 如图所示 PC 机的串口调试助手发送的数据是先发送的低位后发送的高位,所以我们接收的 rx 信号也是先接收的低位后接收的高位,我们采用边接收边移位的操作。移位操作的方法我们已经在前面的流水灯章节中讲过,这里不再重复。接下来我们需要确定移位开始和结束的时间。如图所示,当 bit_cnt 计数器的计数值为 1 时说明第一个有用数据已经接收到了,刚好剔除了起始位,就可以进行移位了。注意移位的条件,要在 bit_cnt 计 数器的计数值为 1 到 8 区间内且 bit_flag 取数标志信号同时为高时才能移位,也就是移动 7 次即可,接收最后 1bit 有用数据时就不需要再进行移位了。当移位 7 次后 1bit 的串行数据 已经变为 8bit 的并行数据了,此时产生一个移位完成标志信号 rx_flag。

第六部分:

        最后一 点,rx_data 信号是参与移位的数据,在移位的过程中数据是变动的,不可以被后级模块所使用,而可以肯定的是在移位完成标志信号 rx_flag 为高时,rx_data 信号一定是移位完成的 稳定的 8bit 有用数据。如图所示,此时我们当移位完成标志信号 rx_flag 为高时让 rx_data 信号赋值给专门用于输出稳定 8bit 有用数据的 po_data 信号就可以了,但 rx_flag 信号又不能作为 po_data 信号有效的标志信号,所以需要将 rx_flag 信号再打一拍。最后输出的有用 8bit 数据为 po_data 信号和伴随 po_data 信号有效的标志信号 po_flag 信号。

        到此为止我们 uart_rx 模块的波形就全部设计好了,此时再看时序图就能理解各个设计。为了获得数据到来标志start_nedge设置了三级寄存器;work_en确定了接收状态,对start_nedge进行管控,不再变化,并开启计数器baud_cnt ;在计数中央采集数据并记录数据个数bit_cnt ;结束置标志位rx_flag,但为稳定,滞后一拍得最终结果和标志位

②代码设计
module  uart_rx
#(
    parameter   UART_BPS    =   'd9600,         //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000    //时钟频率
)
(
    input   wire            sys_clk     ,   //系统时钟50MHz
    input   wire            sys_rst_n   ,   //全局复位
    input   wire            rx          ,   //串口接收数据

    output  reg     [7:0]   po_data     ,   //串转并后的8bit数据
    output  reg             po_flag         //串转并后的数据有效标志信号
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//localparam    define
localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;

//reg   define
reg         rx_reg1     ;
reg         rx_reg2     ;
reg         rx_reg3     ;
reg         start_nedge ;
reg         work_en     ;
reg [12:0]  baud_cnt    ;
reg         bit_flag    ;
reg [3:0]   bit_cnt     ;
reg [7:0]   rx_data     ;
reg         rx_flag     ;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg1 <= 1'b1;
    else
        rx_reg1 <= rx;

//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg2 <= 1'b1;
    else
        rx_reg2 <= rx_reg1;

//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg3 <= 1'b1;
    else
        rx_reg3 <= rx_reg2;

//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        start_nedge <= 1'b0;
    else    if((~rx_reg2) && (rx_reg3))
        start_nedge <= 1'b1;
    else
        start_nedge <= 1'b0;

//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else    if(start_nedge == 1'b1)
        work_en <= 1'b1;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        work_en <= 1'b0;

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        baud_cnt <= 13'b0;
    else    if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
        baud_cnt <= 13'b0;
    else    if(work_en == 1'b1)
        baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_flag <= 1'b0;
    else    if(baud_cnt == BAUD_CNT_MAX/2 - 1)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;

//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'b0;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        bit_cnt <= 4'b0;
     else    if(bit_flag ==1'b1)
         bit_cnt <= bit_cnt + 1'b1;

//rx_data:输入数据进行移位
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_data <= 8'b0;
    else    if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
        rx_data <= {rx_reg3, rx_data[7:1]};

//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_flag <= 1'b0;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        rx_flag <= 1'b1;
    else
        rx_flag <= 1'b0;

//po_data:输出完整的8位有效数据
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_data <= 8'b0;
    else    if(rx_flag == 1'b1)
        po_data <= rx_data;

//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_flag <= 1'b0;
    else
        po_flag <= rx_flag;

endmodule

        可以看到,在2-5行声明参数方便修改;7-13行声明输入输出接口;20行定义局部变量;23-32行声明内部寄存器;40-58行rx数据经过三级寄存器赋值;start_nedge是判断(~rx_reg2) && (rx_reg3)进行赋值,对应波形图上的关系;在112行进行了移位赋值,rx_data <= {rx_reg3, rx_data[7:1]},使得低位在右;后面要注意start_nedge和work_en的关系,后面一大部分实际上是以baud_cnt为基石,所以在baud_cnt的赋值中引入work_en的限制即可(另一种不同思路是start_nedge的赋值引入work_en作为判断【&&~work_en】,然后baud_cnt引入start_nedge)。

③仿真设计
module  tb_uart_rx();

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg   define
reg             sys_clk;
reg             sys_rst_n;
reg             rx;

//wire  define
wire    [7:0]   po_data;
wire            po_flag;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位和输入信号
initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        rx        <= 1'b1;
        #20;
        sys_rst_n <= 1'b1;
end

//模拟发送8次数据,分别为0~7
initial begin
        #200
        rx_bit(8'd0);  //任务的调用,任务名+括号中要传递进任务的参数
        rx_bit(8'd1);
        rx_bit(8'd2);
        rx_bit(8'd3);
        rx_bit(8'd4);
        rx_bit(8'd5);
        rx_bit(8'd6);
        rx_bit(8'd7);
end

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//定义一个名为rx_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来
//任务以task开头,后面紧跟着的是任务名,调用时使用
task rx_bit(
    //传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
        input   [7:0]   data
);
        integer i;      //定义一个常量
//用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1
//不可以写成C语言i=i++的形式
        for(i=0; i<10; i=i+1) begin
            case(i)
                0: rx <= 1'b0;
                1: rx <= data[0];
                2: rx <= data[1];
                3: rx <= data[2];
                4: rx <= data[3];
                5: rx <= data[4];
                6: rx <= data[5];
                7: rx <= data[6];
                8: rx <= data[7];
                9: rx <= 1'b1;
            endcase
            #(5208*20); //每发送1位数据延时5208个时钟周期
        end
endtask         //任务以endtask结束

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------uart_rx_inst------------------------
uart_rx uart_rx_inst(
        .sys_clk    (sys_clk    ),  //input           sys_clk
        .sys_rst_n  (sys_rst_n  ),  //input           sys_rst_n
        .rx         (rx         ),  //input           rx
                
        .po_data    (po_data    ),  //output  [7:0]   po_data
        .po_flag    (po_flag    )   //output          po_flag
);

endmodule

        8-9行因要对输入信号赋值使用reg变量;21-27初始化系统时钟、全局复位和输入信号;29-40模拟拟发送 8 次数据;43行对时钟进行规定;77-86行进行实例化,实例名为代码设计中的模块名,实例化名可以是实例名加_inst;最关键的是48-70行对发送任务的定义,类似c语言中的函数,单独分析:

//定义一个名为rx_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来
//任务以task开头,后面紧跟着的是任务名,调用时使用
task rx_bit(
    //传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
        input   [7:0]   data
);
        integer i;      //定义一个常量
//用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1
//不可以写成C语言i=i++的形式
        for(i=0; i<10; i=i+1) begin
            case(i)
                0: rx <= 1'b0;
                1: rx <= data[0];
                2: rx <= data[1];
                3: rx <= data[2];
                4: rx <= data[3];
                5: rx <= data[4];
                6: rx <= data[5];
                7: rx <= data[6];
                8: rx <= data[7];
                9: rx <= 1'b1;
            endcase
            #(5208*20); //每发送1位数据延时5208个时钟周期
        end
endtask         //任务以endtask结束

        注意这是第一次for循环的使用,for 括号中最后执行的内容只能写 i=i+1;任务以 task 开头,后面紧跟着的是任务名,调用时使用,以 endtask 结束;任务名紧接着是传入参数的定义,i是内部参数定义在括号外(类比c语言函数定义)

        第一、第二、第三部分仿真波形如图所示,我们可以清晰的看到将 rx 信号打三拍的操作,并产生了串口帧起始的下降沿标志信号,以及 work_en 信号在串口帧起始的下降沿标志信号为高时拉高,baud_cnt 计数器在 work_en 信号为高时开始计数。

......

(2)uart_tx设计

①波形图分析

        前6个信号通过uart_rx的设计可以知道其用途

        下面我们就可以按照 5208 个系统时钟周期的波特率间隔来发送 1bit 数据了。理论上我们在第一个 5208 系统时钟周期内 的任意一个位置发送数据都可以,这和接收数据时要在中间位置不同,所以我们直接让当 baud_cnt 计数器的计数值为 1(选择其他的值也可以,但是尽量不要选择 baud_cnt 计数器的计数值为 0 或 5207 这种端点,因为容易出问题)的时候作为发送数据的点,产生 bit_flag 信号,并使 bit_cnt 计数值加一,而下一个 baud_cnt 计数器的计数值为 1 的时候和上一个正好相差 5208 个系统时钟周期,是完全可以满足要求的。发送完一帧数据后要将 work_en 信号拉低。

        bit_cnt清零和work_en拉低的条件:让 bit_cnt 计数器计数到 9,停止位和空闲情况下都为高电平,所以最有一个停止位就没有必要再单独计数了,所以 bit_cnt 计数器计数到 9 清零是完全可以 的,当然让 bit_cnt 计数器计数到 10 更是可以的。 最后再来说说 work_en 信号拉低的条件,work_en 存在的原因就是为了方便 baud_cnt 计数器计数的,当我们不需要 baud_cnt 计数器计数的时候也就可以让 work_en 信号拉低 了。当 bit_cnt 计数器计数到 9 且 bit_flag 信号有效时停止位就可以被发送出去了,此时就不再需要 baud_cnt 计数器计数了,就可以把 work_en 信号拉低了,但同时还要将 baud_cnt 计数器清零,等待下一次发送数据时再从 0 开始计数。

②代码设计
module  uart_tx
#(
    parameter   UART_BPS    =   'd9600,         //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000    //时钟频率
)
(
     input   wire            sys_clk     ,   //系统时钟50MHz
     input   wire            sys_rst_n   ,   //全局复位
     input   wire    [7:0]   pi_data     ,   //模块输入的8bit数据
     input   wire            pi_flag     ,   //并行数据有效标志信号
 
     output  reg             tx              //串转并后的1bit数据
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//localparam    define
localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;

//reg   define
reg [12:0]  baud_cnt;
reg         bit_flag;
reg [3:0]   bit_cnt ;
reg         work_en ;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            work_en <= 1'b0;
        else    if(pi_flag == 1'b1)
            work_en <= 1'b1;
        else    if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
            work_en <= 1'b0;

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            baud_cnt <= 13'b0;
        else    if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
            baud_cnt <= 13'b0;
        else    if(work_en == 1'b1)
            baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            bit_flag <= 1'b0;
        else    if(baud_cnt == 13'd1)
            bit_flag <= 1'b1;
        else
            bit_flag <= 1'b0;

//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'b0;
    else    if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
        bit_cnt <= 4'b0;
    else    if((bit_flag == 1'b1) && (work_en == 1'b1))
        bit_cnt <= bit_cnt + 1'b1;

//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            tx <= 1'b1; //空闲状态时为高电平
        else    if(bit_flag == 1'b1)
            case(bit_cnt)
                0       : tx <= 1'b0;
                1       : tx <= pi_data[0];
                2       : tx <= pi_data[1];
                3       : tx <= pi_data[2];
                4       : tx <= pi_data[3];
                5       : tx <= pi_data[4];
                6       : tx <= pi_data[5];
                7       : tx <= pi_data[6];
                8       : tx <= pi_data[7];
                9       : tx <= 1'b1;
                default : tx <= 1'b1;
            endcase

endmodule

③仿真设计

module  tb_uart_tx();

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg   define
reg         sys_clk;
reg         sys_rst_n;
reg [7:0]   pi_data;
reg         pi_flag;

//wire  define
wire        tx;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位
initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        #20;
        sys_rst_n <= 1'b1;
end

//模拟发送7次数据,分别为0~7
initial begin
        pi_data <= 8'b0;
        pi_flag <= 1'b0;
        #200
        //发送数据0
        pi_data <= 8'd0;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
//每发送1bit数据需要5208个时钟周期,一帧数据为10bit
//所以需要数据延时(5208*20*10)后再产生下一个数据
        #(5208*20*10);
        //发送数据1
        pi_data <= 8'd1;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据2
        pi_data <= 8'd2;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据3
        pi_data <= 8'd3;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据4
        pi_data <= 8'd4;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据5
        pi_data <= 8'd5;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据6
        pi_data <= 8'd6;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据7
        pi_data <= 8'd7;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
end

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------uart_rx_inst------------------------
uart_tx uart_tx_inst(
        .sys_clk    (sys_clk    ),  //input           sys_clk
        .sys_rst_n  (sys_rst_n  ),  //input           sys_rst_n
        .pi_data    (pi_data    ),  //output  [7:0]   pi_data
        .pi_flag    (pi_flag    ),  //output          pi_flag

        .tx         (tx         )   //input           tx
);

endmodule

仿真这里测试了发送数据0~7

        第三部分仿真波形如图所示,我们可以清晰地看到最后一个 bit_flag 信号为高的时刻,且 bit_cnt 计数器也计数到 9,将停止位发送出去,同时 work_en 信号拉低, baud_cnt 计数器检测到 work_en 信号为低电平后立刻清零并停止计数,等待下一次发送数据时再工作。

(3)顶层设计
①代码设计
`timescale  1ns/1ns

module  rs232
(
    input   wire    sys_clk     ,   //系统时钟50MHz
    input   wire    sys_rst_n   ,   //全局复位
    input   wire    rx          ,   //串口接收数据

    output  wire    tx              //串口发送数据
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter   UART_BPS    =   20'd9600        ,   //比特率
            CLK_FREQ    =   26'd50_000_000  ;   //时钟频率

//wire  define
wire    [7:0]   po_data;
wire            po_flag;

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------ uart_rx_inst ------------------------
uart_rx
#(
    .UART_BPS    (UART_BPS  ),  //串口波特率
    .CLK_FREQ    (CLK_FREQ  )   //时钟频率
)
uart_rx_inst
(
    .sys_clk    (sys_clk    ),  //input             sys_clk
    .sys_rst_n  (sys_rst_n  ),  //input             sys_rst_n
    .rx         (rx         ),  //input             rx
            
    .po_data    (po_data    ),  //output    [7:0]   po_data
    .po_flag    (po_flag    )   //output            po_flag
);

//------------------------ uart_tx_inst ------------------------
uart_tx
#(
    .UART_BPS    (UART_BPS  ),  //串口波特率
    .CLK_FREQ    (CLK_FREQ  )   //时钟频率
)
uart_tx_inst
(
    .sys_clk    (sys_clk    ),  //input             sys_clk
    .sys_rst_n  (sys_rst_n  ),  //input             sys_rst_n
    .pi_data    (po_data    ),  //input     [7:0]   pi_data
    .pi_flag    (po_flag    ),  //input             pi_flag
                
    .tx         (tx         )   //output            tx
);

endmodule

        可以看到,顶层模块先定义好顶层输入输出线in/output wire,需要用到的参数parameter和内部模块的连线wire型变量;然后实例化设计好的模块,按照模块代码实例化参数,格式与模块定义时一致,·+参数名引出模块内部参数括号内是顶层模块的变量,起连接作用。

②仿真设计
module  tb_rs232();

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//wire  define
wire    tx          ;

//reg   define
reg     sys_clk     ;
reg     sys_rst_n   ;
reg     rx          ;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位和输入信号
initial begin
    sys_clk    = 1'b1;
    sys_rst_n <= 1'b0;
    rx        <= 1'b1;
    #20;
    sys_rst_n <= 1'b1;
end

//调用任务rx_byte
initial begin
    #200
    rx_byte();
end

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//创建任务rx_byte,本次任务调用rx_bit任务,发送8次数据,分别为0~7
task    rx_byte();  //因为不需要外部传递参数,所以括号中没有输入
    integer	j;
    for(j=0; j<8; j=j+1)    //调用8次rx_bit任务,每次发送的值从0变化7
        rx_bit(j);
endtask

//创建任务rx_bit,每次发送的数据有10位,data的值分别为0到7由j的值传递进来
task    rx_bit(
    input   [7:0]   data
);
    integer i;
    for(i=0; i<10; i=i+1)   begin
        case(i)
            0: rx <= 1'b0;
            1: rx <= data[0];
            2: rx <= data[1];
            3: rx <= data[2];
            4: rx <= data[3];
            5: rx <= data[4];
            6: rx <= data[5];
            7: rx <= data[6];
            8: rx <= data[7];
            9: rx <= 1'b1;
        endcase
        #(5208*20); //每发送1位数据延时5208个时钟周期
    end
endtask

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------ rs232_inst ------------------------
rs232   rs232_inst
(
    .sys_clk    (sys_clk    ),  //input         sys_clk
    .sys_rst_n  (sys_rst_n  ),  //input         sys_rst_n
    .rx         (rx         ),  //input         rx

    .tx         (tx         )   //output        tx
);

endmodule


        这里的仿真使用了task的嵌套,再实例化了顶层设计模块;从此对模块设计 .V 文件中的参数及实例化理解加深,类似于顶层模块的综合,仿真模块开始时定义的变量时为了后面的实例化所服务的,要么起连线作用(wire),要么起赋值仿真作用(reg),在实例化的括号里连接。

3.总结

        “在本章的 Testbench 的设计中我们第一次使用到了 task 任务以及 for 循环语句,这两个语法都在仿真中使用的较多,虽然都是可以综合的但还是推荐初学者尽量不要在 RTL 代码中使用,尤其是对它们理解不深刻的情况下。而我们在 Testbench 中使用就不用担心这么多,且可以大大简化我们的代码,提高效率,是十分好用的,也推荐大家以后再 Testbench 中多尝试使用。”

        以及更深入了解Verilog HDL代码的编写,深刻理解了参数、变量、实例化。

知识点总结:

1. 理解亚稳态产生的原理,掌握单比特数据从慢速时钟域到快速时钟域处理亚稳态的方法。

2. 学会使用边沿检测,并记住代码的格式,理解原理。(第三级寄存器和第二级寄存器共同构成下降沿检测(~rx_reg2) && (rx_reg3))

3. 串并转换是接口中很常用的一种方法,用到了移位,要熟练掌握。

4. 掌握 loopback 测试的方法,以后用于我们模块中代码的调试。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基础设计三——FPGA学习笔记<4> 的相关文章

  • 24分+的医药顶刊带你学习表观组学解析超级热点“肿瘤耐药”的机制

    对癌症患者采用治疗干预时获得性耐药是转移性癌症复发的主要原因 此前 获得性耐药发展的研究主要集中在识别耐药肿瘤中常见的基因突变 越来越多的证据表明 在永久性获得性耐药出现之前 癌症中存在一种表观遗传调控的可逆耐药状态 这种可逆状态可能会导致
  • RT-Thread 内核基础(五)

    使用static修饰全局变量作用 限制作用域 如果全局变量前面加上 static 关键字 那么该变量的作用域将被限制在声明它的源文件中 即它将成为一个文件作用域的静态变量 其它源文件无法访问这个变量 这对于控制变量的可见性和避免命名冲突是有
  • INT201 形式语言与自动机笔记(下)

    L6 Context Free Languages 上下文无关语言 Context Free Grammar CFG 是一组用于生成字符串模式的递归规则 上下文无关的语法可以描述所有的常规语言 但它们不能描述所有可能的语言 e g 遵循这些
  • INT201 形式语言与自动机笔记(上)

    Lec1 Overview Alphabet and String 字母表与字符串 Alphabet 字母表 a finite nonempty set of symbols String word a finite sequence of
  • 网络安全从入门到精通(超详细)学习路线

    首先看一下学网络安全有什么好处 1 可以学习计算机方面的知识 在正式学习网络安全之前是一定要学习计算机基础知识的 只要把网络安全认真的学透了 那么计算机基础知识是没有任何问题的 操作系统 网络架构 网站容器 数据库 前端后端等等 可以说不想
  • 阿里巴巴大神发布的Java零基础笔记,实战教程多到手软,跪了

    前言 现值金九银十之际 是面试高峰季 很多学校开始校招 也是跳槽转行的最佳时机 根据数据显示 程序员是金九银十里最热门的行业 也是需求量最大的行业 但是程序员是个门槛低 但金字塔顶峰比较高的行业 意味着你的付出要比别人多才能拔尖 我们都知道
  • MIT_线性代数笔记:复习二

    目录 第二单元主要内容 例题 第二单元主要内容 正交矩阵 Q 用矩阵形式描述正交性质 投影矩阵 P 最小二乘法 在方程无解时求 最优解 Gram Schmidt 正交化 从任意一组基得到标准正交基 策略是从向量 中减去投影到其它向量方向的分
  • The Planets:Venus

    靶场下载 The Planets Venus VulnHub 信息收集 arp scan l Interface eth0 type EN10MB MAC 00 0c 29 43 7c b1 IPv4 192 168 1 60 Starti
  • 【计算机毕业设计】实验室预约管理

    身处网络时代 随着网络系统体系发展的不断成熟和完善 人们的生活也随之发生了很大的变化 人们在追求较高物质生活的同时 也在想着如何使自身的精神内涵得到提升 而读书就是人们获得精神享受非常重要的途径 为了满足人们随时随地只要有网络就可以看书的要
  • 用户数据中的幸存者偏差

    幸存者偏差 Survivorship bias 是一种常见的逻辑谬误 意思是没有考虑到筛选的过程 忽略了被筛选掉的关键信息 只看到经过筛选后而产生的结果 先讲个故事 二战时 无奈德国空防强大 盟军战机损毁严重 于是军方便找来科学家统计飞机受
  • 白帽子如何快速挖到人生的第一个漏洞 | 购物站点挖掘商城漏洞

    本文针对人群 很多朋友们接触安全都是通过书籍 网上流传的PDF 亦或是通过论坛里的文章 但可能经过了这样一段时间的学习 了解了一些常见漏洞的原理之后 对于漏洞挖掘还不是很清楚 甚至不明白如何下手 可能你通过 sql labs 初步掌握了sq
  • 2024年金三银四网络安全考试试题

    2023年金三银四网络安全考试试题 1 关于数据使用说法错误的是 A 在知识分享 案例中如涉及客户网络数据 应取敏感化 不得直接使用 B 在公开场合 公共媒体等谈论 传播或发布客户网络中的数据 需获得客户书面授权或取敏感化 公开渠道获得的除
  • msyql 异常,别干着急,70%的问题都在这里!

    2024软件测试面试刷题 这个小程序 永久刷题 靠它快速找到工作了 刷题APP的天花板 CSDN博客 文章浏览阅读2 3k次 点赞85次 收藏11次 你知不知道有这么一个软件测试面试的刷题小程序 里面包含了面试常问的软件测试基础题 web自
  • 如何设计一个高并发系统?

    所谓高并发系统 是指能同时处理大量并发请求 并及时响应 从而保证系统的高性能和高可用 那么我们在设计一个高并发系统时 应该考虑哪些方面呢 1 搭建集群 如果你只部署一个应用 只部署一台服务器 那抗住的流量请求是非常有限的 并且 单体的应用
  • windows 杀死占用端口的程序

    在Windows上 你可以使用以下命令来查找并杀死占用某个端口 如9200 的程序 打开命令提示符 Command Prompt 或者PowerShell 运行以下命令来查找占用9200端口的程序的进程ID PID netstat ano
  • 为什么这么多人自学黑客,但没过多久就放弃了(掌握正确的网络安全学习路线很重要)

    网络安全是一个 不断发展和演变 的领域 以下是一个 网络安全学习路线规划 旨在帮助初学者快速入门和提高自己的技能 基础知识 网络安全的 基础知识 包括 网络结构 操作系统 编程语言 等方面的知识 学习这些基础知识对理解网络安全的原理和技术至
  • Cortex-M3与M4权威指南

    处理器类型 所有的ARM Cortex M 处理器是32位的精简指令集处理器 它们有 32位寄存器 32位内部数据路径 32位总线接口 除了32位数据 Cortex M处理器也可以有效地处理器8位和16位数据以及支持许多涉及64位数据的操作
  • if 语句导致 Verilog 中的锁存推断?

    我正在编写用于合成算法的 Verilog 代码 我对哪些情况可能导致推断锁存器有点困惑 下面是这样的一段代码 虽然它在模拟中工作得很好 但我担心它可能会导致硬件问题 always b1 or b2 b1 map b2 map m1 map
  • FPGA 有哪些实际应用?

    我对我的程序为一个小型七段显示器提供动力感到非常兴奋 但是当我向不在现场的人展示它时 他们总是说 那么你能用它做什么 我永远无法给他们一个简洁的答案 谁能帮我吗 第一 它们不需要具有易失性存储器 事实上 大厂商 Xilinx Altera
  • FPGA大输入数据

    我正在尝试向 FPGA 发送 4 KB 字符串 最简单的方法是什么 是我正在使用的fpga的链接 我正在使用 Verilog 和 Quartus 您的问题的答案在很大程度上取决于将数据输入 FPGA 的内容 即使没有您需要遵守的特定协议 S

随机推荐

  • 对于Linux中errno使用的问题

    最近在网络编程使用的过程中 发现errno会经常使用 因此决定在此做个留用 以备以后使用 虽然errno是非线程安全的 但是可以通过几种机制保证其安全 最近在使用的过程中获得了errno 程序无法执行 也不知道如何解决问题 因此 理解每一个
  • springboot如何进行混淆加密(proguard+xjar)

    一 背景 项目组核心代码模块部署于用户服务器上 另外一家公司获取了该服务器的root密码 常规的通过配置环境变量来进行数据库加密处理的方式 直接甩jar包到服务器的方式 极有可能导致数据泄露和代码泄露 二 代码混淆 1 常用的混淆工具 软件
  • NoSQL与关系数据库的比较

    表中给出了NoSQL和关系数据库 Relational DataBase Management System RDBMS 的简单比较 对比指标包括数据库原理 数据规模 数据库模式 查询效率 一致性 数据完整性 扩展性 可用性 标准化 技术支
  • 一站集齐近半年大模型前沿动态

    点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入 大 模 型 LLM 近半年大模型一路狂飙 席卷全球 已经成为了AI领域的研究热点与必争之地 AI TIME大模型系列活动定期特邀来自全球知名高校与研究机构的青年学者 分享最新大模
  • 第十个项目遥感处理cgal+pcl+gdal+opencv+qt+osg(2018年1月开始)

    这个项目是正式入职的第一个公司项目 学的东西很多 每天都在学习新东西 只是和以前的积累有点偏 严格地讲 也不叫偏 以前纯粹是瞎胡搞 API的调用而已 现在业务层次是图像处理 没有硕士学位的人不好弄 提高了门槛 也算是一种保护 免得吃青春饭
  • MongoDB进阶指南!

    想必大家很多人都在业务开发的时候遇到这样的痛点 最近在用数据库存储数据的时候发现这么一个坑 例如从消息队列中监听消息的时候 原来的做法是将监听的消息json数据存储在数据库 以便好对异常消息数据进行追溯 消息内容使用text类型存储 起初因
  • JAVA IO流详解

    File File是java io包下的类 代表与平台无关的文件和目录 File能创建 删除 重命名文件和目录 也能检测 访问文件和目录本身 File不能访问文件中的内容 如果要访问内容 则需要使用输入 输出流 过滤文件 File类的lis
  • Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerExcepti

    本文目录 一 背景描述 二 原因分析 三 解决方案 一 背景描述 项目架构 Spring Boot v2 0 0 RELEASE Mybatis Plus v3 1 1 今天在一个老项目 运行的非常正常 上开发一个新的功能 添加新功能之前
  • anaconda中安装pytorch(GPU版)(离线安装)(最简单)

    本文介绍在anaconda中安装pytorch 最近因为学习需要 要下载pytorchGPU版本来训练网络 相信pytorch大家都不陌生了 PyTorch 是一个 Torch7 团队开源的 Python 优先的深度学习框架 提供两个高级功
  • 华为od最短木板长度

    题目描述 小明有n块木板 第i 1 i n 块木板的长度为ai 小明买了一块长度为m的木料 这块木料可以切割成任意块 拼接到已有的木板上 用来加长木板 小明想让最短的木板尽量长 请问小明加长木板后 最短木板的长度最大可以为多少 输入描述 输
  • 安装、卸载mysql服务命令

    1 dos下用命令 进入mysql的bin目录下 mysqld nt exe install mysql 服务名字 mysqld nt exe remove mysql 服务名字 2 安装卸载mysql服务的bat文件的写法 安装mysql
  • Mac下Flutter环境配置

    最近研究Flutter Flutter环境配置弄了一下午 总算弄好了 所以整理下文章记录分享给大家 如有不全面的地方 还望大家指正 步骤如下 1 首先 下载Flutter SDK 提供两种方式 一 从git下载Flutter https g
  • “unable to find a medium containing a live file system“问题真正有效的解决方法。

    真正有效的关于ubuntu 16 04安装U盘安装出现 unable to find a medium containing a live file system 问题的解决方法 网上搜到的都是乱弹琴 一个靠谱的都没有 真正的解决方法 出现
  • maven的settings.xml,pom.xml配置

    1settings xml
  • 找不到d3dx9_43.dll丢失怎么解决(分享几种解决方法)

    为什么我们打开电脑软件或许游戏时候 电脑会报错出现d3dx9 43 dll丢失 或许找不到d3dx9 43 dll等等的提示 下面来详细介绍一下d3dx9 43 dll详细解决方法跟d3dx9 43 dll是什么 如果你的系统中没有安装或安
  • 【Python】 _tkinter.TclError: bitmap "xzw.ico" not defined

    问题描述 在Python中可以使用pyinstaller命令将 py文件打包成 exe文件 但是成功打包成 exe文件后 在Windows系统上运行却出现了如下错误 tkinter TclError bitmap xzw ico not d
  • ESP32-CAM网络摄像头系列-01-基于RTSP协议的局域网视频推流/拉流的简单实现

    前言 由于项目需要 最近开始开坑关于ESP32 CAM系列的RTSP网络摄像头系列 该文章为该系列的第一篇文章 用于记录项目开发过程 本文解决的问题 使用ESP32 CAM获取图像数据 并通过RTSP协议将获取到的视频流传输到上位机进行显示
  • mysql存储loop_mysql存储loop

    mysql数据库存储过程 存储过程简介 存储过程可以简单理解为一条或者多条sql语句的集合 存储过程用来实现将一组关于表的操作的sql语句当作一个整体来执行 存储过程在实际应用中最主要的特点的事提高执行效率以及sql代码封装功能 特别是sq
  • 今天我要写Code吗?

    Manager还能不能写Code 如果你刚从技术开发职位升迁到管理职位 这会是一个在相当长一段时间内非常纠结你的问题 如果你之前技术做得还不错 算是个 高手 你应该会更加纠结一点 也许每天都在挣扎着 今天我要写Code吗 在深入探讨这个问题
  • 基础设计三——FPGA学习笔记<4>

    前置学习 基础设计二 FPGA学习笔记 3 基础设计一 FPGA学习笔记 2 verilog语法 FPGA学习笔记 1 参考书目 野火FPGA Verilog 开发实战指南 目录 一 串口 RS232 lt 1 gt 简介 lt 2 gt