实验八 静态存储器访问实验
实验目的
- 完成对静态随机读写存储器 SRAM 的访问;
- 进一步掌握时序逻辑电路的基本分析和设计方法;
实验内容
设计一个静态存储器读写模块,具体功能如下:
(1)写数据:由开关输入地址,对该地址写入数据,数据值为当前地址 +1;
(2)读数据:由开关输入地址,读出该地址的数据到发光二极管上;
设计需求
- 设计一个静态存储器的读写模块,通过模式开关来决定是写数据还是读数据,复位后通过时钟控制状态机完成完成的读或写操作。
- 使用时钟模块上的复位开关
CLK
作为手动时钟输入,复位开关RST
作为复位输入。 - 使用一个开关模块上的四个开关作地址输入
address[3:0]
;另一个开关模块上的一个开关作为读写模式选择rw
。 - 使用开关模块上的发光二极管作为读出的数据指示灯
out_data
和当前状态指示灯state_light
。 - 写数据时,首先复位,然后模块通过时钟控制状态机将
address[3:0]
输入的地址写入数据address[3:0] + 1
, 比如address[3:0] = 0110
时,将内存的0110
地址写入数据0111
。 - 读数据时,首先复位,然后模块通过时钟控制状态机将
address[3:0]
输入的地址上的数据读出,然后输出到数据指示灯out_data
。 - 为了方便调试,将状态机的当前状态输出到当前状态指示灯
state_light
。 - FPGA 与静态存储器
sram
之间为信号为内存地址sram_address
,内存数据sram_data
,芯片使能sram_ce_n
,输出使能sram_oe_n
,写使能sram_we_n
。
这样就可用通过读写模块对静态存储器的不同地址进行读写,并通过数据指示灯 out_data
来查看写入是否正确。
注意
由于开关数量有限,实验中只使用sram的低4位地址,其他地址均可以设成任意值,比如全 0
。
设计框图
flowchart LR
clk[复位开关clk]:::periph -- clk ---> sram_rw;
rst[复位开关rst]:::periph -- rst ---> sram_rw;
rw[拨动开关rw]:::periph -- rw ---> sram_rw;
address[拨动开关address]:::periph -- address ---> sram_rw;
subgraph FPGA
direction BT
sram_rw[内存读写sram_rw]:::seq;
end
sram_rw -- sram_address --> sram;
sram_rw -- sram_data <--> sram;
sram_rw -- sram_ce_n --> sram;
sram_rw -- sram_oe_n --> sram;
sram_rw -- sram_we_n --> sram;
sram[内存sram]:::seq;
sram_rw -- out_data --> out_data[发光二极管]:::periph;
sram_rw -- state_light --> state_light[发光二极管]:::periph;
classDef comb fill:#e8f5e9,stroke:#43a047; %% green
classDef seq fill:#ffecb3,stroke:#ffa000; %% orange
classDef periph fill:#e3f2fd,stroke:#2196f3; %% blue
linkStyle default opacity:1.0
原理图
静态随机访问存储器 sram
本实验中的存储器为静态随机访问存储器 sram,对于存储器芯片的访问,首先要熟悉存储器芯片的访问时序,并了解存储器芯片与 FPGA 芯片的具体连接方式,也就是说,如何在 FPGA 上具体实现对 RAM 芯片的访问时序。
实验模块使用了 1 片 sram(Static Random Access Memory)作为主要存储器,sram 是一种具有静态存取功能的内存,不需要动态刷新电路即能保存它内部存储的数据,其特点是访问速度快,访问操作、管理方式简单方便,不需要额外的内存刷新电路。
实验用到的 sram 芯片存储容量为 256Kx8bit,此芯片配置 8 位的数据管脚,18 位的地址管脚,以及内存使能信号、内存读写信号、内存输出使能信号,这些管脚都已经预先连接到 FPGA 的不同引脚上,具体请参见可编程模块介绍部分。
对 sram 进行读的时序如下图所示:
从读时序上看,读取数据时要在地址线上提前准备好要读取的地址,经过一定时间延时后就能够在数据线上读取到数据,因此访问的时候需要保证这个延时已经达到,在具体操作过程中就可以在前一个时钟上沿给出地址,接着在下一个时钟上沿读出数据线上的数据(时钟周期>读延迟),需要注意的是由于数据线是双向的,就需要 FPGA 在给出地址的同时将数据线设置成高阻,这样 sram 就能把数据输出到数据线上。在整个读的过程中芯片使能信号、输出使能信号都为 0
(有效),读写信号为 1
。
对 sram 进行写的时序如下图所示:
写时序稍微复杂一些,简化来看,主要是 3 个步骤,(1)在地址线和数据线上分别发送要写入的地址和数据,此时读写信号为 1
;(2)经过一定时间后,将读写信号变成 0
;(3)在经过一定时间后,再将读写信号变回 1
,此时数据才真正写入 sram 中。可以看出,在整个写过程中地址线和数据线上的值需要保持不变,芯片使能信号、输出使能信号都为 0
(有效)。实际实现的时候,可以在每个时钟上沿处理一个步骤,保证各个步骤之间的延时要求,构成一个状态机。
提示
实验模块上sram的地址线的低8位和数据线都直接连接了发光二极管,就在模块的左上角,标记为 Address
和 Data
, 可以用来查看当前的信号线上的值;同时输出使能信号、芯片使能信号和读写信号也连接了发光二极管,标记为 OE
CE
WE
,需要注意的是这三个都是低有效信号,对应的放光二极管也采用的是上拉驱动,因此只有当信号为 0
的时候相应的放光二极管才变亮。
下面代码仅供参考
module sram(
clk, rst, rw, address,
sram_address, sram_ce_n, sram_oe_n, sram_we_n, sram_data,
out_data, state_light
);
input clk; // 时钟
input rst; // 复位
input rw; // 读写, 0 为写,1 为读
input [3:0] address; // 要写入的地址
output [17:0] sram_address; // 内存地址
output reg sram_ce_n, sram_oe_n, sram_we_n; // 内存使能
inout [7:0] sram_data; // 内存数据
output reg [7:0]out_data; // 输出数据
output [1:0]state_light; // 输出状态
reg[7:0] sram_data_o;
wire[7:0] sram_data_i;
reg sram_data_t;
assign sram_data = sram_data_t ? 8'bz : sram_data_o; //双向总线设置
assign sram_data_i = sram_data;
reg [1:0]state; // 状态机
parameter s0 = 2'b00, s1 = 2'b01, s2 = 2'b10, s3 = 2'b11;
assign state_light = state;
assign sram_address = {14'b0, address}; //地址线输出
assign sram_ce_n = 1'b0;
assign sram_oe_n = 1'b0;
always @(posedge clk, posedge rst)
begin
if(rst) begin
state <= s0;
sram_we_n <= 1'b1;
sram_data_t <= 1'b0;
end else if(!rw) begin //写内存
sram_data_t <= 1'b0;
case(state)
s0: begin //发送地址和数据
sram_we_n <= 1'b1;
sram_data_o <= address + 1;
state <= s1;
end
s1: begin //发送写信号
sram_we_n <= 1'b0;
state <= s2;
end
default: begin //完成写入
sram_we_n <= 1'b1;
state <= s2;
end
endcase
end else begin //读内存
sram_data_t <= 1'b1; //输出设置成高阻
sram_we_n <= 1'b1;
case(state)
s0: begin
state <= s1;
end
s1: begin
out_data <= sram_data_i;
state <= s1;
end
default: begin
state <= s0;
end
endcase
end
end
endmodule
管脚绑定
除了可编程模块上已经预先连接好的 sram 信号需要绑定,还需要使用接插孔连接开关和发光数码管,其中 CLK 作为时钟输入 clk
,IO0 作为复位输入 rst
,IO16 ~ IO19 作为地址输入 address[3:0]
,IO20 作为读写控制 rw
,IO1 ~ IO4 IO6 ~ IO9 作为数据指示灯 out_data[7:0]
, IO11 ~Io12 作为 当前状态指示灯 state_light[1:0]
,由于发光二极管不够用,可以使用不带译码的七段数码管模块替代。
具体约束文件如下:
#CLK input
set_property -dict {PACKAGE_PIN J19 IOSTANDARD LVCMOS33} [get_ports clk]; #CLK接插孔
#RST input
set_property -dict {PACKAGE_PIN K18 IOSTANDARD LVCMOS33} [get_ports rst]; #IO0接插孔
#mode W/R
set_property -dict {PACKAGE_PIN AA21 IOSTANDARD LVCMOS33} [get_ports rw]; #IO20接插孔
#input address
set_property -dict {PACKAGE_PIN AA18 IOSTANDARD LVCMOS33} [get_ports address[3]]; #IO16接插孔
set_property -dict {PACKAGE_PIN AB18 IOSTANDARD LVCMOS33} [get_ports address[2]]; #IO17接插孔
set_property -dict {PACKAGE_PIN AA20 IOSTANDARD LVCMOS33} [get_ports address[1]]; #IO18接插孔
set_property -dict {PACKAGE_PIN AB21 IOSTANDARD LVCMOS33} [get_ports address[0]]; #IO19接插孔
#data out
set_property -dict {PACKAGE_PIN M21 IOSTANDARD LVCMOS33} [get_ports out_data[7]]; #IO1接插孔
set_property -dict {PACKAGE_PIN N20 IOSTANDARD LVCMOS33} [get_ports out_data[6]]; #IO2接插孔
set_property -dict {PACKAGE_PIN N22 IOSTANDARD LVCMOS33} [get_ports out_data[5]]; #IO3接插孔
set_property -dict {PACKAGE_PIN P21 IOSTANDARD LVCMOS33} [get_ports out_data[4]]; #IO4接插孔
set_property -dict {PACKAGE_PIN T21 IOSTANDARD LVCMOS33} [get_ports out_data[3]]; #IO6接插孔
set_property -dict {PACKAGE_PIN U21 IOSTANDARD LVCMOS33} [get_ports out_data[2]]; #IO7接插孔
set_property -dict {PACKAGE_PIN R21 IOSTANDARD LVCMOS33} [get_ports out_data[1]]; #IO8接插孔
set_property -dict {PACKAGE_PIN R22 IOSTANDARD LVCMOS33} [get_ports out_data[0]]; #IO9接插孔
#state
set_property -dict {PACKAGE_PIN W21 IOSTANDARD LVCMOS33} [get_ports state_light[1]]; #IO11接插孔
set_property -dict {PACKAGE_PIN W22 IOSTANDARD LVCMOS33} [get_ports state_light[0]]; #IO12接插孔
#SRAM address
set_property -dict {PACKAGE_PIN P1 IOSTANDARD LVCMOS33} [get_ports sram_address[17]];
set_property -dict {PACKAGE_PIN N2 IOSTANDARD LVCMOS33} [get_ports sram_address[16]];
set_property -dict {PACKAGE_PIN P2 IOSTANDARD LVCMOS33} [get_ports sram_address[15]];
set_property -dict {PACKAGE_PIN M2 IOSTANDARD LVCMOS33} [get_ports sram_address[14]];
set_property -dict {PACKAGE_PIN R2 IOSTANDARD LVCMOS33} [get_ports sram_address[13]];
set_property -dict {PACKAGE_PIN M1 IOSTANDARD LVCMOS33} [get_ports sram_address[12]];
set_property -dict {PACKAGE_PIN U2 IOSTANDARD LVCMOS33} [get_ports sram_address[11]];
set_property -dict {PACKAGE_PIN J1 IOSTANDARD LVCMOS33} [get_ports sram_address[10]];
set_property -dict {PACKAGE_PIN U1 IOSTANDARD LVCMOS33} [get_ports sram_address[9]];
set_property -dict {PACKAGE_PIN T1 IOSTANDARD LVCMOS33} [get_ports sram_address[8]];
set_property -dict {PACKAGE_PIN C2 IOSTANDARD LVCMOS33} [get_ports sram_address[7]];
set_property -dict {PACKAGE_PIN B1 IOSTANDARD LVCMOS33} [get_ports sram_address[6]];
set_property -dict {PACKAGE_PIN B2 IOSTANDARD LVCMOS33} [get_ports sram_address[5]];
set_property -dict {PACKAGE_PIN A1 IOSTANDARD LVCMOS33} [get_ports sram_address[4]];
set_property -dict {PACKAGE_PIN L4 IOSTANDARD LVCMOS33} [get_ports sram_address[3]];
set_property -dict {PACKAGE_PIN L1 IOSTANDARD LVCMOS33} [get_ports sram_address[2]];
set_property -dict {PACKAGE_PIN K2 IOSTANDARD LVCMOS33} [get_ports sram_address[1]];
set_property -dict {PACKAGE_PIN K1 IOSTANDARD LVCMOS33} [get_ports sram_address[0]];
#SRAM controll
set_property -dict {PACKAGE_PIN H2 IOSTANDARD LVCMOS33} [get_ports sram_ce_n];
set_property -dict {PACKAGE_PIN J2 IOSTANDARD LVCMOS33} [get_ports sram_oe_n];
set_property -dict {PACKAGE_PIN R1 IOSTANDARD LVCMOS33} [get_ports sram_we_n];
#SRAM data
set_property -dict {PACKAGE_PIN G2 IOSTANDARD LVCMOS33} [get_ports sram_data[7]];
set_property -dict {PACKAGE_PIN G1 IOSTANDARD LVCMOS33} [get_ports sram_data[6]];
set_property -dict {PACKAGE_PIN F3 IOSTANDARD LVCMOS33} [get_ports sram_data[5]];
set_property -dict {PACKAGE_PIN F1 IOSTANDARD LVCMOS33} [get_ports sram_data[4]];
set_property -dict {PACKAGE_PIN E2 IOSTANDARD LVCMOS33} [get_ports sram_data[3]];
set_property -dict {PACKAGE_PIN E1 IOSTANDARD LVCMOS33} [get_ports sram_data[2]];
set_property -dict {PACKAGE_PIN D2 IOSTANDARD LVCMOS33} [get_ports sram_data[1]];
set_property -dict {PACKAGE_PIN D1 IOSTANDARD LVCMOS33} [get_ports sram_data[0]];
# required if touch button used as manual clock source
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
实验步骤
- 使用 vivado2019.2 新建一个工程,编写 system verilog 代码,并进行综合;
- 使用时钟模块,开关模块和可编程模块,进行电路连接;
- 管脚绑定,编译并下载,观察实验结果。
提高要求
不需要!!有兴趣的话可以仿真一下,没兴趣的不要碰这个。
实验报告
- 整理出完整、清晰的代码,并详细添加注释说明电路的工作原理。
- 说明电路功能测试的结果。
- 总结调试中所遇到的问题及解决方法。