硬件
数码管
高电平位选
led
高电平点亮
按键
按下导通,低电平变成高电平,产生上升沿
VGA
时序
行时序
场时序
不同配置时序表
设计思路
对于 640x480 分辨率的时序,其完整的一行包括 800 个像素时钟周期,因 此我们设计时,只需要使用一个计数器循环计数 800 个时钟周期,并在对应的计数值时候产 生相应的动作,例如:
在计数值为 0 时刻,拉低行同步信号(HSYNC),以示产生行同步低脉冲,然后让行同 步信号(HSYNC)保持 H Sync Time 个时钟周期低电平,此阶段就是 H Sync Time 时序。此阶 段处于消隐阶段(关闭电子管),消隐信号保持为有效电平。
拉高行同步信号(HSYNC)并保持 H Bach Porch 个时钟周期的高电平,此阶段就是回扫 阶段。此时数据总线应该保持全 0 状态。且消隐信号也保持为有效电平。
让行同步信号(HSYNC)继续保持 H Left Border 个时钟周期的高电平,该阶段就是左廊 阶段。此时数据总线应该保持全 0 状态。消隐信号被关闭。
正式进入输出图像数据的阶段后,在 H Data Time 阶段,行同步信号(HSYNC)继续保 持高电平,并在 RGB 数据总线上每个时钟周期输出一个颜色数据。
当 H Data Time 个数据输出完成后,进入 H Right Border 阶段,此时,继续保持行同步 信号(HSYNC)为高电平。但是数据总线不再输出颜色数据。
H Right Border 阶段过后,进入 H Front Porch 阶段,此阶段继续保持行同步信号(HSYNC) 为高电平,数据线不再输出颜色数据,且将消隐信号开启。至此,一行图像的扫描过程结束。
分析完了行扫描时序的实现方案,再来设计场扫描时序的实现方案就非常简单了,思 路和行扫描时序的实现方案完全一致,区别仅在于,场扫描时序中的时序参数都是以完成一 行扫描所耗费的时间为基本时间单位的。
VGA控制器时序分析
对于行扫描来说,主要关心的就是在什么时刻产生 HS 脉冲信号,以及在一行中的什么 位置输出图像内容。如果以 HS 信号的下降沿作为一行扫描的起点时刻(此时刻为 0 时刻),那么我们只需要知道在第几个时刻 HS 脉冲信号结束(VGA_HS_end),并知道图像数据在一 行中什么时刻开始输出(hdat_begin)、什么时刻结束输出(hdat_end),还需要知道一行扫 描到什么时刻停止(hpixel_end)。所以,对于 640x480 分辨率的时序,据此可以归纳得到如下 4 个与行图像扫描相关的参数:
同样的,对于场扫描来说,主要关心的是在什么时刻产生 VS 脉冲信号,以及在一场中 的什么位置输出图像内容。如果以 VS 信号的下降沿作为一行扫描的起点时刻(此时对应 0 行),那么我们只需要知道在第几个行时 VS 脉冲信号结束(VGA_VS_end),并知道图像数据 在一行中什么时刻开始输出(vdat_begin)、什么时刻结束输出(vdat_end),还需要知道一行 扫描到什么时刻停止(vline_end)。所以,对于 640x480 分辨率的时序,据此可以归纳得到 如下 4 个与场图像扫描相关的参数:
行扫描计数器设计
行扫描计数器即每个像素时钟自加 1,一旦加满到 799(刚好 800 个时钟周期),也就 是 H Total Time 个时钟周期后,计数器清零并重新计数,该部分代码可如下设计:
reg [9:0] hcount_r; //VGA 行扫描计数器
//**********************VGA 驱动部分**********************
//行扫描计数器
always @(posedge Clk25M or negedge Rst_n)begin
if(!Rst_n) //复位时,让行扫描计数器清零
hcount_r<=10'd0;
else if(hcount_r==10'd799) //当一行数据扫完后,再次清零行扫描计数器
hcount_r<=10'd0;
else //0~799 之间,每个像素时钟时行扫描计数器自加 1
hcount_r<=hcount_r+10'd1;
end
场扫描计数器设计
由于场扫描计数器是在每次一行扫描完成后加 1 的,即场扫描计数器的自加条件是行 扫描计数器溢出。所以,场扫描计数器的自加条件为行扫描完成,即“hcount_r== 10'd799”, 场扫描计数器代码如下所示
reg [9:0] vcount_r; //VGA 场扫描计数器
//场扫描
always@(posedge Clk25M or negedge Rst_n)begin
if(!Rst_n) //复位时让场计数器清零
vcount_r<=10'd0;
else if(hcount_r==10'd799) begin //每次一行扫描完成
if(vcount_r==10'd524) //每次一场扫描结束,清零计数器
vcount_r<=10'd0;
else
vcount_r<=vcount_r+10'd1;//场计数器在 0~524,满足条件自加 1
end
else //不满足行扫描结束条件器件,让场扫描计数器保持不变
vcount_r<=vcount_r;
end
行场同步信号设计
根据 VGA 工业标准时序,我们知道每一个完整的 VGA 帧都包含了数据段和消隐段,在 消隐段期间,行同步信号和场同步信号分别有一段行同步头和场同步头。在同步期间,对应 行同步信号或者场同步信号为低电平。因此我们可以根据行、场计数器的值来确定行、场同 步信号的电平状态。对于行同步信号,其行同步头为一行扫描的前 96 个像素时钟周期,因 此行同步信号可用如下的简单方式控制:
assign VGA_HS=(hcount_r>10’d95);
对于场同步信号,其场同步头为一行扫描的前 2 个像素时钟周期,因此行同步信号可 用如下的简单方式控制:
assign VGA_VS=(vcount_r>10’d1);
输出数据*
VGA 控制器的设计目的是为了驱动 VGA 显示器显示需求的图像内容,因此需要设计数 据输出部分,这里,数据来源可以为其它部分产生的图像信号,如摄像头数据、BMP 图片数 据。我们在驱动 VGA 时,只需要保证在扫描正确的像素点时,其它部分产生的图像信号能 够与该像素点位置对应上,则不需要对图像数据再进行二次处理,但是,在行、场消隐期间, 需要保证输出到 VGA 的 RGB 数据线上的数据全部为 0,因此可以设置一个二选一多路器, 只有在非消隐期间,VGA 控制器才直接输出其他部分输入的图像数据,而消隐期间则强制输 出全 0。
我们可以首先产生一个图像数据有效标志信号,然后使用该标志信号控制 VGA 输出数 据的内容,即切换二选一多路器的通道,从而实现消隐期间数据全 0 的功能。
图像数据有效标志信号产生代码如下所示:
//数据、同步信号输出
assign dat_act=((hcount_r>=10'd143)&&(hcount_r<10'd783)) &&
((vcount_r>=10'd34)&&(vcount_r<10'd514));
dat_act 即为图像数据有效标志信号。
VGA_BLANK 信号和 dat_act 信号功能一致,因此直接将 dat_act 信号赋值给 VGA_BLANK 即可。
assign VGA_BLK = dat_act;
另外,也可以在消隐期间,设置 RGB 输出端口上数据为 0 来强制产生消隐效果。 消隐强制输出 0 二选一多路器代码如下所示:
assign VGA_RGB=(dat_act)?data_in:24'h000000;
其中,VGA_RGB 是输出到 VGA 接口上的数据,而 data_in 则是其他模块传递过来的正 确的图像数据。
输出行列扫描位置
为了使其他模块能够根据当前扫描位置正确的输出图像数据,因此需要将 VGA 控制器 的实时扫描位置输出,以供其他模块使用。
assign hcount=dat_act?(hcount_r- 10'd143):10'd0;
assign vcount=dat_act?(vcount_r- 10'd34):10'd0;
输出数据锁存时钟信号
对于 FPGA 上实现的 VGA 控制器,在时钟信号的上升沿输出数据,而对于 DAC 芯片来 说,需要在数据的中点采集数据,根据实际板级调试效果,将 VGA 控制器的时钟取反后输 出作为 DAC 的数据锁存时钟信号,能够确保 DAC 芯片在锁存数据时数据刚好是稳定的,如 下所示
//将 VGA 控制器时钟信号取反输出,作为 DAC 数据锁存信号
assign VGA_CLK = ~Clk25M;
常用模板
时序电路块
always @(posedge clk or negedge rst) begin
if(!rst)begin
d_in<=0;
end
else if(sel_cnt==0)begin
d_in<=d;
end
end
case使用
always@(posedge clk or negedge rst)
if(!rst)begin
out<=0;
end
else begin
case (sel_cnt)
0: out<=d;
1: out<=(d_in<<2)-d_in;
2: out<=(d_in<<3)-d_in;
3: out<=(d_in<<3);
default: out<=d_in;
endcase
end
generate使用
module gen_for_module(
input [7:0] data_in,
output [7:0] data_out
);
genvar i;
generate
for(i = 0; i < 8; i = i + 1)
begin : bit_reverse
assign data_out[i] = data_in[7 - i];
end
endgenerate
// 在begin之后的bit_reverse是需要的,表示该generate…for语句块的名称,可以根据需要修改。特别需要注意的是,
// 即使只有一个语句,也需要使用begin…end。同时需要使用endgenerate表示结束。
endmodule
编写函数示例
`timescale 1ns/1ns
module function_mod(
input [3:0]a,
input [3:0]b,
output [3:0]c,
output [3:0]d
);
assign c = data_rev(a);
assign d = data_rev(b);
function [3:0] data_rev;
input [3:0] data_in;
begin
data_rev[0] = data_in[3];
data_rev[1] = data_in[2];
data_rev[2] = data_in[1];
data_rev[3] = data_in[0];
end
endfunction
endmodule
常用模块
D触发器
D触发器
input d;
input clk;
output reg q;
output reg q1;
always @(posedge clk)
begin
q <= d ;
q1 <= q;
end
异步复位D触发器
input d ;
input rst ;
input clk ;
output reg q ;
always @(posedge clk or negedge rst)
begin
if (rst == 1'b0)
q <= 0 ;
else
q <= d ;
end
异步复位同步清零D触发器
input d ;
input rst ;
input clr ;
input clk ;
output reg q ;
always @(posedge clk or negedge rst)
begin
if (rst == 1'b0)
q <= 0 ;
else if (clr == 1'b1)
q <= 0 ;
else
q <= d ;
end
单口ram
单口 RAM 的写地址与读地址共用一个地址,代码如下,其中 reg [7:0] ram [63:0]意思是定义了 64 个 8 位宽度的数据。其中定义了 addr_reg,可以保持住读地址,延迟一周期之后将数据送出。
//单口ram
module top
(
input [7:0] data,
input [5:0] addr,
input wr,
input clk,
output [7:0] q
);
reg [7:0] ram[63:0]; //declare ram
reg [5:0] addr_reg; //addr register
always @ (posedge clk)
begin
if (wr) //write
ram[addr] <= data;
addr_reg <= addr;
end
assign q = ram[addr_reg]; //read
data
endmodule
伪双口RAM
伪双口 RAM 的读写地址是独立的,可以随机选择写或读地址,同时进行读写操作。代码如下,在激励文件中定义了 en 信号,在其有效时发送读地址。
module top
(
input [7:0] data,
input [5:0] write_addr,
input [5:0] read_addr,
input wr,
input rd,
input clk,
output reg [7:0] q
);
reg [7:0] ram[63:0]; //declare ram
reg [5:0] addr_reg; //addr register
always @ (posedge clk)
begin
if (wr) //write
ram[write_addr] <= data;
if (rd) //read
q <= ram[read_addr];
end
endmodule
真双口ram
真双口 RAM 有两套控制线,数据线,允许两个系统对其进行读写操作,代码如下:
module ram_dual_port_simple #(parameter data_width = 8,data_num = 16,num_width = data_num > 0 ? clog2(data_num) : 1)(
output reg[data_width-1:0] douta,
output reg[data_width-1:0] doutb,
input[data_width-1:0] dina,
input[num_width-1:0] addra,
input[num_width-1:0] addrb,
input wea,
input clka,
input clkb
);
function integer clog2;
input integer value;
begin
value = value - 1;
for (clog2 = 0; value > 0; clog2 = clog2 + 1) begin
value = value >> 1;
end
end
endfunction
reg [data_width-1:0] ram [0:data_num-1];
always@(posedge clka) begin
if(wea) begin
ram[addra] <= dina;
douta <= dina;
end
else
douta <= ram[addra];
end
always@(posedge clkb) begin
doutb <= ram[addrb];
end
endmodule
有限状态机
在 verilog 里经常会用到有限状态机,处理相对复杂的逻辑,设定好不同的状态,根据触 发条件跳转到对应的状态,在不同的状态下做相应的处理。有限状态机主要用到 always 及 case 语句。下面以一个四状态的有限状态机举例说明。
在程序中设计了 8 位的移位寄存器,在 Idle 状态下,判断 shift_start 信号是否为高,如果 为高,进入 Start 状态,在 Start 状态延迟 100 个周期,进入 Run 状态,进行移位处理,如果 shift_stop 信号有效了,进入 Stop 状态,在 Stop 状态,清零 q 的值,再跳转到 Idle 状态。
Mealy有限状态机实现
Mealy 有限状态机,输出不仅与当前状态有关,也与输入信号有关,在 RTL 中会与输入信 号有连接。
//Mealy有限状态机
module top
(
input shift_start,
input shift_stop,
input rst,
input clk,
input d,
output reg [7:0] q
);
parameter Idle = 2'd0 ; //Idle state
parameter Start = 2'd1 ; //Start state
parameter Run = 2'd2 ; //Run state
parameter Stop = 2'd3 ; //Stop state
reg [1:0] state ; //statement
reg [4:0] delay_cnt ; //delay counter
always @(posedge clk or negedge rst)
begin
if (!rst)
begin
state <= Idle ;
delay_cnt <= 0 ;
q <= 0 ;
end
else
case(state)
Idle : begin
if (shift_start)
state <= Start ;
end
Start : begin
if (delay_cnt == 5'd99)
begin
delay_cnt <= 0 ;
state <= Run ;
end
else
delay_cnt <= delay_cnt + 1'b1 ;
end
Run : begin
if (shift_stop)
state <= Stop ;
else
q <= {q[6:0], d} ;
end
Stop : begin
q <= 0 ;
state <= Idle ;
end
default: state <= Idle ;
endcase
end
endmodule
Meere三段式状态机实现*
Moore 有限状态机,输出只与当前状态有关,与输入信号无关,输入信号只影响状态的改 变,不影响输出,比如对 delay_cnt 和 q 的处理,只与 state 状态有关。
我们主推这种状态机,因为这种状态机可以实现一个always只管一个变量,非常的清晰明了
当然还有一种二段式状态机就是把三段式的第一二段合成一下
//Moore有限状态机
module top
(
input shift_start,
input shift_stop,
input rst,
input clk,
input d,
output reg [7:0] q
);
parameter Idle = 2'd0 ; //Idle state
parameter Start = 2'd1 ; //Start state
parameter Run = 2'd2 ; //Run state
parameter Stop = 2'd3 ; //Stop state
reg [1:0] current_state ; //statement
reg [1:0] next_state ;
reg [4:0] delay_cnt ; //delay counter
//First part: statement transition
always @(posedge clk or negedge rst)
begin
if (!rst)
current_state <= Idle ;
else
current_state <= next_state ;
end
//Second part: combination logic, judge statement transition condition
always @(*)
begin
case(current_state)
Idle : begin
if (shift_start)
next_state <= Start ;
else
next_state <= Idle ;
end
Start : begin
if (delay_cnt == 5'd99)
next_state <= Run ;
else
next_state <= Start ;
end
Run : begin
if (shift_stop)
next_state <= Stop ;
else
next_state <= Run ;
end
Stop : next_state <= Idle ;
default: next_state <= Idle ;
endcase
end
//Last part: output data
always @(posedge clk or negedge rst)
begin
if (!rst)
delay_cnt <= 0 ;
else if (current_state == Start)
delay_cnt <= delay_cnt + 1'b1 ;
else
delay_cnt <= 0 ;
end
always @(posedge clk or negedge rst)
begin
if (!rst)
q <= 0 ;
else if (current_state == Run)
q <= {q[6:0], d} ;
else
q <= 0 ;
end
endmodule
跨时钟域握手
data_driver
`timescale 1ns/1ns
module data_driver(
input clk_a,
input rst_n,
input data_ack,
output reg [3:0]data,
output reg data_req
);
reg data_ack_reg_1;
reg data_ack_reg_2;
reg [9:0] cnt;
always @ (posedge clk_a or negedge rst_n)
if (!rst_n)
begin
data_ack_reg_1 <= 0;
data_ack_reg_2 <= 0;
end
else
begin
data_ack_reg_1 <= data_ack;
data_ack_reg_2 <= data_ack_reg_1;
end
always @ (posedge clk_a or negedge rst_n)
if (!rst_n)
begin
data <= 0;
end
else if(data_ack_reg_1 && !data_ack_reg_2)
begin
data <= data+1;
end
else begin
data <= data;
end
//同时在data_ack有效之后,开始计数五个时钟,之后发送新的数据,也就是再一次拉高data_req.
always @ (posedge clk_a or negedge rst_n)
if (!rst_n)
cnt <= 0;
else if (data_ack_reg_1 && !data_ack_reg_2)
cnt <= 0;
else if (data_req)
cnt <= cnt;
else
cnt <= cnt+1;
always @ (posedge clk_a or negedge rst_n)
if (!rst_n)
data_req <= 0;
else if (cnt == 3'd4)
data_req <= 1'b1;
else if (data_ack_reg_1 && !data_ack_reg_2)
data_req <= 1'b0;
else
data_req <= data_req;
endmodule
data_receiver
module data_receiver(
input clk_b,
input rst_n,
output reg data_ack,
input [3:0]data,
input data_req
);
reg [3:0]data_in_reg;
reg data_req_reg_1;
reg data_req_reg_2;
always @ (posedge clk_b or negedge rst_n)
if (!rst_n)
begin
data_req_reg_1 <= 0;
data_req_reg_2 <= 0;
end
else
begin
data_req_reg_1 <= data_req;
data_req_reg_2 <= data_req_reg_1;
end
always @ (posedge clk_b or negedge rst_n)
if (!rst_n)
data_ack <= 0;
else if (data_req_reg_1)
data_ack <= 1;
else data_ack <=0 ;
always @ (posedge clk_b or negedge rst_n)
if (!rst_n)
data_in_reg <= 0;
else if (data_req_reg_1 && !data_req_reg_2)
data_in_reg <= data;
else data_in_reg <= data_in_reg ;
endmodule
同步复位异步释放
`timescale 1ns/1ns
module ali16(
input clk,
input rst_n,
input d,
output reg dout
);
//*************code***********//
reg rst_0,rst_1;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rst_0<=0;
rst_1<=0;
end
else begin
rst_0<=rst_n;
rst_1<=rst_0;
end
end
always@(posedge clk or negedge rst_1)begin
if(!rst_1)
dout<=0;
else
dout<=d;
end
//*************code***********//
endmodule
任意奇数分频(50占空比)
`timescale 1ns/1ns
module clk_divider
#(parameter dividor = 5)
( input clk_in,
input rst_n,
output clk_out
);
reg [$clog2(dividor):0] cnt1;
reg clk1, clk2;
//计数模块,计数到dividor-1归0,因为整个电路并行执行,复位后cnt为0,
//第一个时钟上升沿到来时,cnt为0,整个电路同时工作;所以cnt为0时整个电路已经一起工作过一次。
always @ (posedge clk_in, negedge rst_n) begin
if(!rst_n) begin
cnt1 <= 0;
end
else
cnt1 <= (cnt1 == dividor-1 ) ? 0 : cnt1 + 1;
end
//上升沿有效,在(dividor - 1)>>1和(dividor-1)时翻转
always @ (posedge clk_in, negedge rst_n)begin
if(!rst_n) begin
clk1 <= 1'b0;
end
else if(cnt1 == (dividor - 1)>>1) begin
clk1 <= ~clk1;
end
else if (cnt1 == (dividor-1)) begin
clk1 <= ~clk1;
end
else begin
clk1 <= clk1;
end
end
//下升沿有效,在(dividor - 1)>>1和(dividor-1)时翻转
always @ (negedge clk_in, negedge rst_n)begin
if(!rst_n) begin
clk2 <= 1'b0;
end
else if(cnt1 == (dividor - 1)>>1) begin
clk2 <= ~clk2;
end
else if (cnt1 == (dividor-1)) begin
clk2 <= ~clk2;
end
else begin
clk2 <= clk2;
end
end
assign clk_out = clk1 || clk2;
endmodule
电路优化知识
1、异步复位比同步复位使用的资源更少
FPGA同步复位、异步复位、异步复位同步释放_fpga 异步复位-CSDN博客
2、有关亚稳态的知识
FPGA设计的“打拍(寄存)”和“亚稳态” 到底是什么?_fpga打拍的作用-CSDN博客












Comments 2 条评论
请问可以询问下,该比赛的一些细节么,我想参加这个比赛。
@1039214848 这是AMD公司命题的命题式赛道,和蓝桥杯形式差不多