link
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139`timescale 1ns / 1ps // Engineer: Reborn Lee // Module Name: single_port_syn_ram module single_port_syn_ram#( parameter ADDR_WIDTH = 4, //地址位宽 parameter DATA_WIDTH = 16, // 数据位宽 //内存 ram 的本质是一个一维的列表 //对于这个 一维度的列表式的ram ,他的地址就是 一个序号而已 //故而 ,列表的长度最大可以使 2的 ADDR_WIDTH(4) 次方,也就是16 //其实这里你可以设置成为 任何一个比16 小的数字 parameter DEPTH = 2**ADDR_WIDTH //ram 列表长度 )( input i_clk, //时钟信号 input [ADDR_WIDTH - 1 : 0] addr, // ram 地址参数 inout [DATA_WIDTH - 1 : 0] data, //这个是一个零时变量法,位宽与ram 一样,用于存放缓存的数据 input cs, // 这是ram 使能 信号,表示是否操作这个ram //其实这个cs本质是一个选择符号 input wr, // 写使能 input oe // output enable,输出使能时RAM读取的结果才能输出 ); reg [DATA_WIDTH - 1 : 0] mem[0 : DEPTH - 1]; //ram 的位宽 是 16 DATA_WIDTH ,列表的长度是 DEPTH 16 reg [DATA_WIDTH - 1 : 0] mid_data; //中间缓存的寄存器 // write part //写数据部分 always@(posedge i_clk) begin //在时钟上升沿 if(cs&wr) begin //如果 cs=1 并且 写 wr=1 使能 ,则操作数据的写 mem[addr] <= data; //数据放在缓存寄存器 end end // read part always@(posedge i_clk) begin //在时钟上升沿 if(cs & !wr) begin //如果 cs=1 并且 写 wr=0 使能 ,则操作数据的读数据,这里一共只有两个状态, //读和写,这里并没有 读使能 read_enable mid_data <= mem[addr]; //数据放在缓存寄存器 end end //在读数据的时候,我们需要设计一个三态缓冲器,如下: /*** 读使能有效时,我们将从缓冲区读出的数据放到mid_data中,之后通过一个三态门来将数据mid_data输出到三态总线上,此三态门的使能条件为读使能! 这条语句在综合工具中就会被推断为一个三态缓冲器! 在读使能有效时,将读取数据放在总线上,否则呈现为高阻态,避免占用此数据总线。 ***/ //读使能有效时,我们将从缓冲区读出的数据放到mid_data中 //这个例子中并没有读使能,当使能cs=1且& 输出使能 oe=1 且& 写使能 wr=0 的时候 ,将读取数据放在总线上,否则呈现为高阻态,避免占用此数据总线。 assign data = (cs & oe & !wr)? mid_data: 'hz; //读数据 endmodule //下面是 testbench `timescale 1ns / 1ps // Engineer: Reborn Lee // Module Name: ram_tb module ram_tb( ); parameter ADDR_WIDTH = 4; //地址位宽 parameter DATA_WIDTH = 16; // 数据位宽 parameter DEPTH = 2**ADDR_WIDTH; //ram 列表长度 reg i_clk; //时钟 reg [ADDR_WIDTH - 1 : 0] addr; // ram 地址参数 wire [DATA_WIDTH - 1 : 0] data; //这个是一个零时变量法,位宽与ram 一样,用于存放缓存的数据 reg cs; // 这是ram 使能 信号,表示是否操作这个ram reg wr; // 写使能 reg oe; // output enable,输出使能时RAM读取的结果才能输出 reg [DATA_WIDTH-1:0] tb_data; //这个是一个零时变量法,位宽与ram 一样,用于存放缓存的数据 //generate system clock initial begin i_clk = 0; forever begin //这里的forever 不是绝对意义上的永远 而是,在数据读写的时候,永远每5ns 翻转一次 //就想说我永远爱你,不是从现在开始,到时间无涯的尽头一直爱你,而是在我的整个生命力,永远爱你 # 5 i_clk = ~i_clk; // 每5 ns 时钟翻转一次, end end assign data = !oe ? tb_data : 'hz; // 写数据, // assign data = tb_data ; initial begin {cs, wr, addr, tb_data, oe} = 0; //一开始都置零 repeat (2) @ (posedge i_clk); //先来两个时钟周期 //注意 verilog 是硬件描述语言,只要不涉及到时钟或者#的时延,都是同时 并行 //不可以 有C 语言 和python 的方法学习 verilog //write test //每个时钟周期,向 ram 中写一个随机数据,数据写的时候不能读 ,oe=0 输出使能0 for (integer i = 0; i < 2**ADDR_WIDTH; i= i+1) begin repeat (1) @(negedge i_clk) addr = i; wr = 1; cs =1; oe = 0; tb_data = $random; end //read test //两次读写之间 有两个时钟周期的延时 repeat (2) @ (posedge i_clk); //每个时钟周期,向 ram 中读一个随机数据,数据读的时候不能写 ,wr=0 写使能 0 for (integer i = 0; i < 2**ADDR_WIDTH; i= i+1) begin repeat (1) @(posedge i_clk) addr = i; wr = 0; cs = 1; oe = 1; end #20 $finish; end //下面是函数调用的接口 single_port_syn_ram #( .ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(DATA_WIDTH), .DEPTH(DEPTH) ) inst_single_port_syn_ram ( .i_clk (i_clk), .addr (addr), .data (data), .cs (cs), .wr (wr), .oe (oe) ); endmodule
我用的是 vivado 21 秒学会 vivado 仿真
最后
以上就是冷酷荔枝最近收集整理的关于verilog 实现 单口 ram ,我确定你看完后会彻底理解 ram的全部内容,更多相关verilog内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复