本文对SRAM进行介绍,并对其内部的存储器矩阵、地址译码器、列I/O及I/O数据电路、控制电路、SRAM的读写流程进行简要介绍,并给出SRAM IS62LV256-45U读写实例。
文章目录
- 存储容量的计算
- SRAM控制原理
- SRAM信号线
- 存储器矩阵
- 地址译码器、列I/O及I/O数据电路
- 控制电路
- SRAM的读写时序
- SRAM读写实例
- 顶层模块
- SRAM控制器模块
- 其余模块
- 仿真模块
- 实验现象演示
存储容量的计算
根据下面"SRAM控制原理"小节的介绍,我们可以知道存储器存储容量的计算由地址线和数据线数可以得到:
例如:一个存储器有16根地址线,8根数据线,求此存储器存储容量?
按位求取 219 x 16位 =29 x 210 x 16位 =512K x 16位
按字节求取 219 x 16位 /8 = 1024K x B = 1024KB
SRAM控制原理
SRAM信号线
任何一颗 SRAM 芯片的 datasheet,会发现它们的时序操作大同小异。SRAM 内部的结构如图 所示,要访问实际的 Momory 区域,FPGA 必须送地址和控制信号,SRAM 内部有与此对应的地址译码(decoder)和控制处理电路(control circuit)。这样,数据总线上的数据就可以相应的读或写了。
以下是一种SRAM芯片的内部结构框图,其左侧引出的是SRAM芯片的控制引脚。
其中的引脚说明如下表:
信号线 | 类型 | 说明 |
---|---|---|
A0-A18 | I | 地址输入 |
I/O0-I/O7 | I/O | 数据输入输出信号,低字节 |
I/O8-I/O15 | I/O | 数据输入输出信号,高字节 |
C S 1 ‾ \overline{CS1} CS1和 C S 2 ‾ \overline{CS2} CS2 | I | 片选信号, C S 2 ‾ \overline{CS2} CS2高电平有效, C S 1 ‾ \overline{CS1} CS1低电平有效,部分芯片只有其中一个引脚 |
O E ‾ \overline{OE} OE | I | 输出使能信号,低电平有效 |
W E ‾ \overline{WE} WE | I | 写入使能,低电平有效 |
U B ‾ \overline{UB} UB | I | 数据掩码信号Upper Byte,高位字节允许访问,低电平有效 |
L B ‾ \overline{LB} LB | I | 数据掩码信号Lower Byte,低位字节允许访问,低电平有效 |
SRAM的控制比较简单,只要控制信号线使能了访问,从地址线输入要访问的地址,即可从I/O数据线写入或读出数据。
存储器矩阵
以下框图表示的是存储器矩阵,SRAM内部包含的存储阵列,可以把它理解成一张表格,数据就填在这张表格上。和表格查找一样,指定一个行地址和列地址,就可以精确地找到目标单元格, 这是SRAM芯片寻址的基本原理。这样的每个单元格被称为存储单元,而这样的表则被称为存储矩阵。
地址译码器、列I/O及I/O数据电路
地址译码器把N根地址线转换成2N根信号线,每根信号线对应一行或一列存储单元,通过地址线找到具体的存储单元,实现寻址。 如果存储阵列比较大,地址线会分成行和列地址,或者行、列分时复用同一地址总线,访问数据寻址时先用地址线传输行地址再传输列地址。 对于较小的SRAM,没有列地址线,以SRAM芯片的内部结构框图中的SRAM为例,它的数据宽度为16位,即一个行地址对应2字节空间,框图中左侧的A0-A18是行址信号, 18根地址线一共可以表示218=28x1024=512K行存储单元, 所以它一共能访问512Kx16bits大小的空间。访问时, 使用 U B ‾ \overline{UB} UB或 L B ‾ \overline{LB} LB线控制数据宽度,例如,当要访问宽度为16位的数据时,使用行地址线指出地址,然后把 U B ‾ \overline{UB} UB和 L B ‾ \overline{LB} LB线都设置为低电平, 那么I/O0-I/O15线都有效,它们一起输出该地址的16位数据(或者接收16位数据到该地址);当要访问宽度为8位的数据时,使用行地址线指出地址, 然后把 U B ‾ \overline{UB} UB或 L B ‾ \overline{LB} LB其中一个设置为低电平,I/O会对应输出该地址的高8位和低8位数据,因此它们被称为数据掩码信号。
控制电路
控制电路主要包含了片选、读写使能以及上面提到的宽度控制信号 U B ‾ \overline{UB} UB和 L B ‾ \overline{LB} LB。利用 C S 1 ‾ \overline{CS1} CS1或 C S 2 ‾ \overline{CS2} CS2片选信号,可以把多个SRAM芯片组成一个大容量的内存条。 O E ‾ \overline{OE} OE和 W E ‾ \overline{WE} WE可以控制读写使能,防止误操作。
SRAM的读写时序
对SRAM进行读写数据时,它各个信号线的时序流程见图SRAM的读时序及图 SRAM的写时序。
SRAM读时序:
SRAM写时序:
读写时序的流程很类似,下面我们统一解说:
(1) 主机使用地址信号线发出要访问的存储器目标地址;
(2) 控制片选信号
C
S
1
‾
\overline{CS1}
CS1和
C
S
2
‾
\overline{CS2}
CS2使能存储器芯片;
(3) 若是要进行读操作,则控制读使能信号
O
E
‾
\overline{OE}
OE表示要读数据,若进行写操作则控制写使能信号
W
E
‾
\overline{WE}
WE表示要写数据;
(4) 使用掩码信号
U
B
‾
\overline{UB}
UB和
L
B
‾
\overline{LB}
LB指示要访问目标地址的高、低字节部分;
(5) 若是读取过程,存储器会通过数据线向主机输出目标数据,若是写入过程,主要使用数据线向存储器传输目标数据。
在读写时序中,有几个比较重要的时间参数,以IS62WV51216BLL-55ns型号SRAM的时间参数为例:
时间参数 | IS62WV51216BLL-55ns时间要求 | 说明 |
---|---|---|
tRC | 不小于55ns | 读操作的总时间(地址信号要大于此时间) |
tAA | 最迟不大于55ns | 从接收到地址信号到给出有效数据的时间(有效数据出现的时间应大于此时间) |
tDOE | 最迟不大于25ns | 从接收到读使能信号到给出有效数据的时间(有效数据出现的时间应大于此时间) |
tWC | 不小于55ns | 写操作的总时间(地址信号要大于此时间) |
tSA | 大于0ns | 从发送地址信号到给出写使能信号的时间 |
tPWE | 不小于40ns | 从接收到写使能信号到数据采样的时间 |
tSD | 大于25ns | 写结束前的数据建立时间 |
tHD | 大于0ns | 写结束后的数据保持时间 |
SRAM读写实例
实验平台:Xilinx Spartan 6、SRAM芯片型号为IS62LV256-45U。
实现效果:两个按键分别为写数据按键、读数据按键;按下写数据按键后,开始向SRAM中地址依次写数据2、4、6、8······直至写满,写满后led灯亮;按下读按键后依次向SRAM地址读数据并用数码管显示读出的数据。
顶层模块
整体框图如下,实验由以下模块构成:
PLL模块:产生系统时钟50MHz以及系统复位;
按键消抖模块:对读写按键进行消抖,产生读写标志信号;
按键读写控制:实现写按键产生写请求将SRAM写满,读按键每隔1s产生一次读请求读数据;
SRAM控制器模块:控制SRAM进行读写,为控制SRAM的核心部分;
BCD模块:将读取的数据转换为二进制编码的十进制,如将1234转换为0001_0010_0011_0100;
数码管控制模块:将二十进制进行数码管显示。
//========================================================================
// module_name.v :top.v
// Author :YprgDay
// Description :顶层
//========================================================================
module top(
input wire ext_clk_25m ,
input wire ext_key_wr ,
input wire ext_key_rd ,
output wire [7:0] led ,
output wire sram_cs_n , // SRAM片选信号,低电平有效。
output wire sram_we_n , // SRAM写选通信号,低电平有效。
output wire sram_oe_n , // SRAM输出选通信号,低电平有效。
output wire [14:0] sram_addr , // SRAM地址总线。
inout [7:0] sram_data , // SRAM数据总线。
output wire [3:0] dtube_cs_n , //7段数码管位选信号
output wire [7:0] dtube_data //7段数码管段选信号(包括小数点为8段)
);
wire sys_rst_n ; //复位信号,低电平有效
wire key_flag_wr ;
wire key_flag_rd ;
wire sramwr_req ;
wire [14:0] sramwr_addr ;
wire [7:0] sramwr_data ;
wire sramrd_req ;
wire [14:0] sramrd_addr ;
wire [7:0] sramrd_data ;
wire [7:0] sramrd_data_tube;
wire [3:0] unit ; //个位BCD码
wire [3:0] ten ; //十位BCD码
wire [3:0] hun ; //百位BCD码
wire [3:0] tho ; //千位BCD码
wire [3:0] t_tho ; //万位BCD码
wire [3:0] h_hun ; //十万位BCD码
pll_controller u_pll_controller(
.CLK_IN1 (ext_clk_25m), // IN
// Clock out ports
.CLK_OUT1 (sys_clk ), // 50MHz
.LOCKED (sys_rst_n )
);
key_filter u_key_wr(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.key_in (ext_key_wr ),
.key_flag (key_flag_wr)
);
key_filter u_key_rd(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.key_in (ext_key_rd ),
.key_flag (key_flag_rd)
);
rd_wr_ctrl u_rd_wr_ctrl(
.sys_clk (sys_clk ), //时钟信号
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.key_wr (key_flag_wr), //写按键
.led_wr_full_flag (led[0] ), //写满后led亮
.key_rd (key_flag_rd), //读按键
.sramwr_req (sramwr_req ), // SRAM写请求信号,高电平有效。
.sramwr_addr (sramwr_addr), // SRAM写入地址寄存器。
.sramwr_data (sramwr_data), // SRAM写入数据寄存器。
.sramrd_req (sramrd_req ), // SRAM读请求信号,高电平有效。
.sramrd_addr (sramrd_addr), // SRAM读出地址寄存器。
.sramrd_data (sramrd_data), // SRAM读出数据寄存器。
.sramrd_data_tube (sramrd_data_tube)//读出数据给数码管
);
sram_controller u_sram_controller
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.sramwr_req (sramwr_req ),
.sramwr_addr (sramwr_addr),
.sramwr_data (sramwr_data),
.sramrd_req (sramrd_req ),
.sramrd_addr (sramrd_addr),
.sramrd_data (sramrd_data),
.sram_cs_n (sram_cs_n ),
.sram_we_n (sram_we_n ),
.sram_oe_n (sram_oe_n ),
.sram_addr (sram_addr ),
.sram_data (sram_data )
);
bcd_8421 u_bcd_8421(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.data ({12'd0,sramrd_data_tube}), //输入需要转换的数据
.unit (unit ), //个位BCD码
.ten (ten ), //十位BCD码
.hun (hun ), //百位BCD码
.tho (tho ), //千位BCD码
.t_tho (t_tho ), //万位BCD码
.h_hun (h_hun ) //十万位BCD码
);
seg7 u_seg7(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.display_num ({tho,hun,ten,unit}), //数码管显示数据,[15:12]--数码管千位,[11:8]--数码管百位,[7:4]--数码管十位,[3:0]--数码管个位
.dtube_cs_n (dtube_cs_n ), //7段数码管位选信号
.dtube_data (dtube_data )
);
assign led[7:1] = 7'b1111111;//关掉其他led灯
endmodule
SRAM控制器模块
读写时序要求如下,读写操作总时间大于45ns即可,我们取100ns;读数据有效数据出现的时间最大45ns,我们取80ns处,保证时序需求。
写数据时输入写请求、写数据、写地址后对SRAM进行写数据;其余时刻sram_data为高阻态。由于数据总线为双向口,使用inout定义。
读数据时输入读请求、读地址后对SRAM进行读数据;
//========================================================================
// module_name.v :sram_controller.v
// Author :YprgDay
// Description :SRAM的读写控制器
//========================================================================
module sram_controller
(
input sys_clk, //时钟信号
input sys_rst_n, //复位信号,低电平有效
//FPGA内部对SRAM的读写控制信号
input sramwr_req , // SRAM写请求信号,高电平有效,用于状态机控制。
input [14:0] sramwr_addr , // SRAM写入地址寄存器。
input [7:0] sramwr_data , // SRAM写入数据寄存器。
input sramrd_req , // SRAM读请求信号,高电平有效,用于状态机控制。
input [14:0] sramrd_addr , // SRAM读出地址寄存器。
output reg [7:0] sramrd_data , // SRAM读出数据寄存器。
//FPGA与SRAM芯片的接口信号
output reg sram_cs_n , // SRAM片选信号,低电平有效。
output reg sram_we_n , // SRAM写选通信号,低电平有效。
output reg sram_oe_n , // SRAM输出选通信号,低电平有效。
output reg [14:0] sram_addr , // SRAM地址总线。
inout [7:0] sram_data // SRAM数据总线。
);
//状态机控制SRAM的状态。
parameter IDLE = 3'd0 ;
parameter WRT0 = 3'd1 ;
parameter WRT1 = 3'd2 ;
parameter REA0 = 3'd3 ;
parameter REA1 = 3'd4 ;
//=========================< Variable >==============================
reg [2:0] state,next_state; //状态机跳转
reg [2:0] cnt; //用于WRT0、REA0读写状态下的计数
reg wr_data_falg; //写数据时一直拉高
//=========================< Block >=================================
always @ (posedge sys_clk or negedge sys_rst_n) begin//时序逻辑控制状态变迁。
if(sys_rst_n == 1'b0)
cnt <= 3'd0;
else if(state == IDLE)
cnt <= 3'd0;
else if((state == WRT0 )||(state == REA0))
cnt <= cnt + 1'b1;
else
cnt <= 3'd0;
end
//=========================< State machine >=========================
always @ (posedge sys_clk or negedge sys_rst_n) begin//时序逻辑控制状态变迁。
if(sys_rst_n == 1'b0)
state <= IDLE;
else
state <= next_state;
end
always @ (*) begin //组合逻辑控制不同状态的转换。
case (state)
IDLE:if(sramwr_req)
next_state = WRT0; //进入写状态。
else if(sramrd_req)
next_state = REA0;
else
next_state = IDLE;
WRT0:if(cnt == 3'd4)
next_state = WRT1;
else
next_state = WRT0;
WRT1:next_state = IDLE; //过渡状态保证写入完成
REA0:if(cnt == 3'd4) //进入读状态。
next_state = REA1;
else
next_state = REA0;
REA1:next_state = IDLE; //过渡状态保证读取完成
default: next_state = IDLE;
endcase
end
//地址与使能
always @ (posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0) begin
sram_addr <= 15'd0;
sram_cs_n <= 1'b1;
sram_we_n <= 1'b1;
sram_oe_n <= 1'b1;
end
else if(state == WRT0) begin
sram_addr <= sramwr_addr; //写SRAM地址
sram_cs_n <= 1'b0;
sram_we_n <= 1'b0;
sram_oe_n <= 1'b1;
end
else if(state == WRT1) begin
sram_addr <= 15'd0;
sram_cs_n <= 1'b1;
sram_we_n <= 1'b1;
sram_oe_n <= 1'b1;
end
else if(state == REA0) begin
sram_addr <= sramrd_addr; //读SRAM地址
sram_cs_n <= 1'b0;
sram_we_n <= 1'b1;
sram_oe_n <= 1'b0;
end
else if(state == REA1) begin
sram_addr <= 15'd0;
sram_cs_n <= 1'b1;
sram_we_n <= 1'b1;
sram_oe_n <= 1'b1;
end
else;
end
//读数据
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
sramrd_data <= 8'd0;
else if((state == REA0) && (cnt == 3'd4))//保证数据稳定再读
sramrd_data <= sram_data;
else
sramrd_data <= sramrd_data;
end
//写数据
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
wr_data_falg <= 1'd0;
else if(state == WRT0)
wr_data_falg <= 1'd1;
else
wr_data_falg <= 1'd0;
end
assign sram_data = wr_data_falg ? sramwr_data : 8'hzz;
endmodule
其余模块
按键消抖模块:对读写按键进行消抖,产生读写标志信号。
//========================================================================
// module_name.v :key_filter.v
// Author :YprgDay
// Description :按键消抖20ms
//========================================================================
module key_filter
#(
parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire key_in , //按键输入信号
output reg key_flag //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);
//reg define
reg [19:0] cnt_20ms ; //计数器
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'b0;
else if(key_in == 1'b1)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX && key_in == 1'b0)//未按下是按键为高,按下时按键为低
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 1'b1;
//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
endmodule
按键读写控制:实现写按键产生写请求将SRAM写满,读按键每隔1s产生一次读请求读数据。
//========================================================================
// module_name.v :rd_wr_ctrl.v
// Author :YprgDay
// Description :外部按键控制SRAM读写,按一下写按键向SRAM中写数据到满为止,按一下读按键每秒读出一个数据供后级数码管显示用
//========================================================================
module rd_wr_ctrl
#(
parameter CNT_RD_MAX = 26'd49_999_999
)
(
input sys_clk , //时钟信号
input sys_rst_n , //复位信号,低电平有效
//FPGA内部对SRAM的读写控制信号
input key_wr , //写按键
output reg led_wr_full_flag,//写满后led亮
input key_rd , //读按键
output reg sramwr_req , // SRAM写请求信号,高电平有效。
output reg [14:0] sramwr_addr , // SRAM写入地址寄存器。
output reg [7:0] sramwr_data , // SRAM写入数据寄存器。
output reg sramrd_req , // SRAM读请求信号,高电平有效。
output reg [14:0] sramrd_addr , // SRAM读出地址寄存器。
input [7:0] sramrd_data , // SRAM读出数据寄存器。
output reg [7:0] sramrd_data_tube//读出数据给数码管
);
//=========================< Variable >==============================
reg wr_flag;//按下写按键后拉高直至写满
reg rd_flag;//按下读按键后拉高直至读完
reg [3:0] cnt_wrreq;//每计数10一个请求写
reg [25:0] cnt_rdreq;//每秒读一次@50MHz
//=========================< Block >=================================
//写请求计数
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
wr_flag <= 1'b0;
else if(((&sramwr_addr)&&(cnt_wrreq == 4'd10))||(key_rd))
wr_flag <= 1'b0;
else if(key_wr == 1'b1)
wr_flag <= 1'b1;
else
wr_flag <= wr_flag;
end
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_wrreq <= 4'd0;
else if(cnt_wrreq == 4'd10)
cnt_wrreq <= 4'd0;
else if(wr_flag == 1'b1)
cnt_wrreq <= cnt_wrreq + 1'b1;
else
cnt_wrreq <= 4'd0;
end
//写请求
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
sramwr_req <=1'd0;
else if(key_wr == 1'b1)
sramwr_req <= 1'd1;
else if(cnt_wrreq == 4'd10)
sramwr_req <= 1'd1;
else
sramwr_req <= 1'd0;
end
//写地址数据以此存放2/4/6/8···
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)begin
sramwr_addr<=15'd0;
sramwr_data<=8'd2;
end
else if(cnt_wrreq == 4'd10)begin
sramwr_addr<=sramwr_addr + 15'd1;
sramwr_data<=sramwr_data + 8'd2;
end
else begin
sramwr_addr<=sramwr_addr;
sramwr_data<=sramwr_data;
end
end
//写满后led亮,读时led灭
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
led_wr_full_flag <=1'd1;
else if(key_rd == 1'b1)
led_wr_full_flag <=1'd1;
else if(&sramwr_addr)
led_wr_full_flag <=1'd0;
else
led_wr_full_flag <=led_wr_full_flag;
end
//读请求计数
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
rd_flag <= 1'b0;
else if((&sramrd_addr)&&(cnt_rdreq == CNT_RD_MAX)||(key_wr))
rd_flag <= 1'b0;
else if(key_rd == 1'b1)
rd_flag <= 1'b1;
else
rd_flag <= rd_flag;
end
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_rdreq <= 26'd0;
else if(cnt_rdreq == CNT_RD_MAX)
cnt_rdreq <= 26'd0;
else if(rd_flag == 1'b1)
cnt_rdreq <= cnt_rdreq + 1'b1;
else
cnt_rdreq <= 26'd0;
end
//读请求
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
sramrd_req <=1'd0;
else if(key_rd == 1'b1)
sramrd_req <= 1'd1;
else if(cnt_rdreq == CNT_RD_MAX)
sramrd_req <= 1'd1;
else
sramrd_req <= 1'd0;
end
//读地址
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)begin
sramrd_addr<=15'd0;
sramrd_data_tube<=8'd0;
end
else if(cnt_rdreq == CNT_RD_MAX)begin
sramrd_addr<=sramrd_addr + 15'd1;
sramrd_data_tube<=sramrd_data;
end
else begin
sramrd_addr<=sramrd_addr;
sramrd_data_tube<=sramrd_data_tube;
end
end
endmodule
BCD模块:将读取的数据转换为二进制编码的十进制,如将1234转换为0001_0010_0011_0100;(此模块参考野火BCD模块,数据位宽20,需求位宽为8,例化时高位补零即可)
module bcd_8421
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [19:0] data , //输入需要转换的数据
output reg [3:0] unit , //个位BCD码
output reg [3:0] ten , //十位BCD码
output reg [3:0] hun , //百位BCD码
output reg [3:0] tho , //千位BCD码
output reg [3:0] t_tho , //万位BCD码
output reg [3:0] h_hun //十万位BCD码
);
//********************************************************************//
//******************** Parameter And Internal Signal *****************//
//********************************************************************//
//reg define
reg [4:0] cnt_shift ; //移位判断计数器
reg [43:0] data_shift ; //移位判断数据寄存器
reg shift_flag ; //移位判断标志信号
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt_shift:从0到21循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_shift <= 5'd0;
else if((cnt_shift == 5'd21) && (shift_flag == 1'b1))
cnt_shift <= 5'd0;
else if(shift_flag == 1'b1)
cnt_shift <= cnt_shift + 1'b1;
else
cnt_shift <= cnt_shift;
//data_shift:计数器为0时赋初值,计数器为1~20时进行移位判断操作
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_shift <= 44'b0;
else if(cnt_shift == 5'd0)
data_shift <= {24'b0,data};
else if((cnt_shift <= 20) && (shift_flag == 1'b0))
begin
data_shift[23:20] <= (data_shift[23:20] > 4) ? (data_shift[23:20] + 2'd3) : (data_shift[23:20]);
data_shift[27:24] <= (data_shift[27:24] > 4) ? (data_shift[27:24] + 2'd3) : (data_shift[27:24]);
data_shift[31:28] <= (data_shift[31:28] > 4) ? (data_shift[31:28] + 2'd3) : (data_shift[31:28]);
data_shift[35:32] <= (data_shift[35:32] > 4) ? (data_shift[35:32] + 2'd3) : (data_shift[35:32]);
data_shift[39:36] <= (data_shift[39:36] > 4) ? (data_shift[39:36] + 2'd3) : (data_shift[39:36]);
data_shift[43:40] <= (data_shift[43:40] > 4) ? (data_shift[43:40] + 2'd3) : (data_shift[43:40]);
end
else if((cnt_shift <= 20) && (shift_flag == 1'b1))
data_shift <= data_shift << 1;
else
data_shift <= data_shift;
//shift_flag:移位判断标志信号,用于控制移位判断的先后顺序
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
shift_flag <= 1'b0;
else
shift_flag <= ~shift_flag;
//当计数器等于20时,移位判断操作完成,对各个位数的BCD码进行赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
unit <= 4'b0;
ten <= 4'b0;
hun <= 4'b0;
tho <= 4'b0;
t_tho <= 4'b0;
h_hun <= 4'b0;
end
else if(cnt_shift == 5'd21)
begin
unit <= data_shift[23:20];
ten <= data_shift[27:24];
hun <= data_shift[31:28];
tho <= data_shift[35:32];
t_tho <= data_shift[39:36];
h_hun <= data_shift[43:40];
end
endmodule
数码管控制模块:将二十进制进行数码管显示。
module seg7(
input clk, //时钟信号,50MHz
input rst_n, //复位信号,低电平有效
input[15:0] display_num, //数码管显示数据,[15:12]--数码管千位,[11:8]--数码管百位,[7:4]--数码管十位,[3:0]--数码管个位
output reg[3:0] dtube_cs_n, //7段数码管位选信号
output reg[7:0] dtube_data //7段数码管段选信号(包括小数点为8段)
);
//-------------------------------------------------
//参数定义
//数码管显示 0~F 对应段选输出
parameter NUM0 = 8'h3f,//c0,
NUM1 = 8'h06,//f9,
NUM2 = 8'h5b,//a4,
NUM3 = 8'h4f,//b0,
NUM4 = 8'h66,//99,
NUM5 = 8'h6d,//92,
NUM6 = 8'h7d,//82,
NUM7 = 8'h07,//F8,
NUM8 = 8'h7f,//80,
NUM9 = 8'h6f,//90,
NUMA = 8'h77,//88,
NUMB = 8'h7c,//83,
NUMC = 8'h39,//c6,
NUMD = 8'h5e,//a1,
NUME = 8'h79,//86,
NUMF = 8'h71,//8e;
NDOT = 8'h80; //小数点显示
//数码管位选 0~3 对应输出
parameter CSN = 4'b1111,
CS0 = 4'b1110,
CS1 = 4'b1101,
CS2 = 4'b1011,
CS3 = 4'b0111;
//-------------------------------------------------
//分时显示数据控制单元
reg[3:0] current_display_num; //当前显示数据
reg[7:0] div_cnt; //分时计数器
//分时计数器
always @(posedge clk or negedge rst_n)
if(!rst_n) div_cnt <= 8'd0;
else div_cnt <= div_cnt+1'b1;
//显示数据
always @(posedge clk or negedge rst_n)
if(!rst_n) current_display_num <= 4'h0;
else begin
case(div_cnt)
8'hff: current_display_num <= display_num[3:0];
8'h3f: current_display_num <= display_num[7:4];
8'h7f: current_display_num <= display_num[11:8];
8'hbf: current_display_num <= display_num[15:12];
default: ;
endcase
end
//段选数据译码
always @(posedge clk or negedge rst_n)
if(!rst_n) dtube_data <= NUM0;
else begin
case(current_display_num)
4'h0: dtube_data <= NUM0;
4'h1: dtube_data <= NUM1;
4'h2: dtube_data <= NUM2;
4'h3: dtube_data <= NUM3;
4'h4: dtube_data <= NUM4;
4'h5: dtube_data <= NUM5;
4'h6: dtube_data <= NUM6;
4'h7: dtube_data <= NUM7;
4'h8: dtube_data <= NUM8;
4'h9: dtube_data <= NUM9;
4'ha: dtube_data <= NUMA;
4'hb: dtube_data <= NUMB;
4'hc: dtube_data <= NUMC;
4'hd: dtube_data <= NUMD;
4'he: dtube_data <= NUME;
4'hf: dtube_data <= NUMF;
default: ;
endcase
end
//位选译码
always @(posedge clk or negedge rst_n)begin
if(!rst_n) dtube_cs_n <= CSN;
else begin
case(div_cnt[7:6])
2'b00: dtube_cs_n <= CS0;
2'b01: dtube_cs_n <= CS1;
2'b10: dtube_cs_n <= CS2;
2'b11: dtube_cs_n <= CS3;
default: dtube_cs_n <= CSN;
endcase
end
end
endmodule
仿真模块
//========================================================================
// module_name.v :tb_top.v
// Author :YprgDay
// Description :顶层仿真
//========================================================================
`timescale 1ns / 1ns
module tb_top();
reg ext_clk_25m ;
reg ext_key_wr ;
reg ext_key_rd ;
wire [7:0] led ;
wire sram_cs_n ; // SRAM片选信号,低电平有效。
wire sram_we_n ; // SRAM写选通信号,低电平有效。
wire sram_oe_n ; // SRAM输出选通信号,低电平有效。
wire [14:0] sram_addr ; // SRAM地址总线。
wire [7:0] sram_data ; // SRAM数据总线。
wire [3:0] dtube_cs_n ; //7段数码管位选信号
wire [7:0] dtube_data ;//7段数码管段选信号(包括小数点为8段)
//=========================< Parameter >==============================
parameter CLK_PERIOD = 40 ;//设置时钟信号周期
parameter HALF_CLK_PERIOD = CLK_PERIOD/2;//生成时钟信号半周期
defparam u_top.u_key_wr.CNT_MAX = 20'd9 ;
defparam u_top.u_key_rd.CNT_MAX = 20'd9 ;
defparam u_top.u_rd_wr_ctrl.CNT_RD_MAX = 26'd99;
//=========================< Variable >==============================
reg slink;
reg [7:0] sram_datar ;
//==========================< Clock block >============================
always #HALF_CLK_PERIOD ext_clk_25m = ~ext_clk_25m;
//==========================< Reset block >============================
initial begin
slink = 0;
sram_datar <= 8'd0 ;
ext_clk_25m = 1'b1 ;
ext_key_wr <= 1'b1 ;
ext_key_rd <= 1'b1 ;
#20
wait(u_top.sys_rst_n == 1'b1)begin
ext_key_wr <= 1'b0 ;
end
#400
ext_key_wr <= 1'b1 ;
#20
wait(u_top.u_rd_wr_ctrl.sramwr_addr == 15'd3000)begin
ext_key_rd <= 1'b0 ;
end
#400
ext_key_rd <= 1'b1 ;
end
top u_top(
.ext_clk_25m (ext_clk_25m),
.ext_key_wr (ext_key_wr ),
.ext_key_rd (ext_key_rd ),
.led (led ),
.sram_cs_n (sram_cs_n ), // SRAM片选信号,低电平有效。
.sram_we_n (sram_we_n ), // SRAM写选通信号,低电平有效。
.sram_oe_n (sram_oe_n ), // SRAM输出选通信号,低电平有效。
.sram_addr (sram_addr ), // SRAM地址总线。
.sram_data (sram_data ), // SRAM数据总线。
.dtube_cs_n (dtube_cs_n ), //7段数码管位选信号
.dtube_data (dtube_data ) //7段数码管段选信号(包括小数点为8段)
);
always @(negedge sram_oe_n) begin
slink <= 1;
sram_datar <= 2*sram_addr[7:0];
@(posedge sram_cs_n);
slink <= 0;
end
assign sram_data = slink ? sram_datar:8'hzz;
endmodule
实验现象演示
上板验证实验效果,与设计一致。
参考资料:
1.野火-SRAM控制原理简介
2.《勇敢的芯 伴你玩转 Xilinx FPGA》吴厚航