一天一个设计实例-RAM、ROM模块程序设计

1.1.1RAM和ROM
前面已经介绍了,RAM和SRAM之间的区别,这里就详细介绍RAM和ROM。
前面说,存储分为“存储资源”和“存储方式”。
代码3‑3 简单的静态 ROM 模块
1.module rom( input [1:0]iAddr, output [7:0]oData );
2.
3.
4.reg [7:0]D1;
5.always @ (*)
6.    if( iAddr == 2’ b00 ) D1 = 8’ hA;
7.    else if( iAddr == 2’ b01 ) D1 = 8’ hB;
8.    else if( iAddr == 2’ b10 ) D1 = 8’ hC;
9.    else if( iAddr == 2’ b11 ) D1 = 8’ hD;
10.    else D1 = 8’ dx;
11.
12.assign oData = D1;
13.
14.endmodule
例如一个简单的静态 ROM 模块,它可以基于寄存器或者片上内存,结果如代码3‑3与代码3‑4所示。代码代码3‑3是基于寄存器的静态 ROM,它有 2 位 iAddr 与 8 位的 oData,其中第 3~8 行是 ROM 的内容定义,第 10 行则是输出驱动,为此 oData 会根据 iAddr的输入产生不同的输出。
代码3‑4 基于片上内存的静态 ROM
1.module rom( input [1:0]iAddr, output [7:0]oData );
2.
3.reg [7:0] RAM [3:0];
4.
5.initial begin
6.    RAM[0] = 8’ hA;
7.    RAM[1] = 8’ hB;
8.    RAM[2] = 8’ hC;
9.    RAM[3] = 8’ hD;
10.end
11.
12.assign oData = RAM[ iAddr ];
13.
14.endmodule
反之,代码3‑4是基于片上内存的静态 ROM,它也有 2 位 iAddr 与 8 位 oData,第 3~7行是内容的定义也是初始化片上内存,第 12 行则是输出驱动, oData 会根据 iAddr 的输出产生不同的输出。
代码3‑3与代码3‑4虽然都是静态 ROM,不过却有根本性的不同,因为两者源于不同的储存资源,其中最大的证据就是第 12 行的输出驱动,前者由寄存器驱动,后者则由片上内存驱动。不同的储存资源也有不同的性质,例如寄存器操作简单,而且布线有余,不过不支持大容量的储存行为。换之,片上内存虽然操作麻烦,布线也紧凑,可是却支持大容量的储存行为。
储存方式相较储存资源理解起来稍微抽象一点,而且想象范围也非常广大 ... 如果储存资源是“容器的种类”,那么储存方式就是“容器的用法”。举例而言,一个简单静态ROM,根据需要它还可以演变成为其它亚种,例如常见的单口 ROM 或者双口 ROM 或等。
代码3‑5 单口 ROM
1.module rom( input CLOCK, input [1:0]iAddr, output [7:0]oData );
2.reg [7:0] RAM [3:0];
3.initial begin
4.    RAM[0] = 8’ hA;
5.    RAM[1] = 8’ hB;
6.    RAM[2] = 8’ hC;
7.    RAM[3] = 8’ hD;
8.end
9.
10.reg [1:0] D1;
11.always @ ( posedge CLOCK)
12.    D1 <= iAddr;
13.
14.
15.assign oData = RAM[ D1 ];
16.
17.endmodule
如代码3‑5所示,那是单口 ROM 的典型例子,然而单口 ROM 与静态 ROM 之间的差别就在于前者有时钟信号,后者没有时钟信号。期间,代码3‑5用 D1 暂存 iAddr,然后再由 D1 充当 RAM 的寻址工具。
代码3‑6 双口 ROM
1.module rom( input CLOCK, input [1:0]iAddr1, iAddr2, output [7:0]oData1, oData2 );
2.reg [7:0] RAM [3:0];
3.initial begin
4.    RAM[0] = 8’ hA;
5.    RAM[1] = 8’ hB;
6.    RAM[2] = 8’ hC;
7.    RAM[3] = 8’ hD;
8.end
9.
10.reg [1:0] D1;
11.always @ ( posedge CLOCK)
12.    D1 <= iAddr1;
13.
14.assign oData1 = RAM[ D1 ];
15.
16.reg [1:0] D2;17. always @ ( posedge CLOCK)
17.    D2 <= iAddr2;
18.
19.assign oData2 = RAM[ D2 ];
20.
21.endmodule
如代码3‑6所示,那是双口 ROM 的典型例子,如果将其比较单口 ROM,它则多了一组 iAddr 与 oData 而已,即 iAddr1 与 oData1, iAddr2 与 oData2。第 10~14 行是第一组(第一口),第 16~20 行则是第二组(第二口),不过两组 iAddr 与 oData 都从同样的RAM 资源哪里读取结果。
事实上, ROM 还会根据更多不同要求产生更多亚种,而且亚种的种类也绝非局限在于专业规范, 因为亚种的储存模块会依照设计者的欲望——有多畸形就多畸形,死守传统只会固步自封而已。无论模块对象是静态 ROM,单口 ROM 还是双口 ROM 等 ... 笔者眼中,它们都是任意的“储存方式”而已。
根据笔者的妄想,储存方式的覆盖范围非常之广。简单而言,凡是模块涉及数据的储存操作,低级建模 II 都视为储存类。举例而言, ROM 模块储存自读不写的数据;RAM模块储存又读又写的数据; FIFO 模块储存先写先读的数据。
为此,我们可以这样命名它们:
rom_savemod.v // rom 储存模块
ram_savemod.v // ram 储存模块
FIFO_savemod.v // FIFO 储存模块
FPGA和查找表
查表就是顺序语言“空间换速度”的优化手段。查表既是 ROM 也是一种储存方式。如果把话说难听一点,所谓查表也不过是顺序语言在利用数组模仿 ROM 而已,它除了便捷性好以外,无论是资源的消耗,还是时钟的消耗等效率都远远不及描述语言的 ROM。顺序语言偶尔虽然也有山寨的 FIFO, Shift 等储存方式,不过性能却是差强人意。
顺序语言之所以那么逊色,那是因为被钢铁一般坚固的顺序结构绑得死死。述语言是自由的语言,结构也是自由。虽然自由结构为人们带来许多麻烦,但是“储存方式”可以描述的范畴,绝对超乎人们的估量。归根究底,究竟是顺序语言好,还是描述语言模比较厉害呢?除了见仁见智以外,答案也只有天知晓。
随着时代不断变迁,“储存方式”的需求也逐渐成长,例如 50 年代需要 rom, 60 年代需要 ram, 70 年代需要 FIFO。二十一世纪的今天,保守的规范再也无法压抑“储存方式”的放肆衍生,例如 rom 衍生出来静态 rom,单口 rom,双口 rom 等许多亚种;此外, FIFO也衍生出同步 FIFO 或者异步 FIFO 等亚种。至于 ram 的亚种,比前两者更加恐怖!不管怎么样,大伙都是笔者的好孩子,亦即 ××_savemod。
虽然伟大的官方早已准备数之不尽的储存模块,但是笔者还是强调手动建模比较好,因为官方的东西有太多限制了。此刻,可能有人跳出来反驳道:“为什么不用官方插件模块,它们既完整又便捷,那个白痴才不吃天上掉下来的馅饼!笔者是呆子!蠢货!“。
话说这位同学也别那么激动,如果读者一路索取它人的东西,学习只会本末倒置而已。除此之外,官方插件模块是商业的产物,不仅自定义有限内容也是隐性,而且还是不择不扣的快餐。快餐即美味也方便,偶尔吃下还不错,但是长期食用就会危害健康,危害学习。
“FIFO 插件的数据位宽能不能设为 11 位?”,某人求救道。
“ram 插件怎样调用?怎样仿真?”,某人求救道。
类似问题每月至少出现数十次,而且还是快餐爱好者提问的。笔者也有类似的经验,所以非常明白这种心境。年轻的笔者就是爱好快餐,凡事拿来主义,伸手比吃饭更多。渐渐地,笔者愈来愈懒,能不增反降,最终变成只会求救的肥仔而已。后悔以后,笔者才脚踏实地自力建模,慢慢减肥。
在此,笔者滔滔不绝只想告知读者 ... 自由结构虽然麻烦,不过这是将想象力具体化的关键因素,储存模块的潜能远超保守的规范。规范有时候就像一粒绊脚石,让人不经意跌倒一次又一次,阻碍人们前进,限制人们想象,最后让人成为不动手即不动脑的懒人。
1.1.2ROM和RAM组合程序设计
图3‑12 实验建模图
图3‑12是本次设计的建模图,组合模块 savemod_demo 的内容包括一支核心操作,一只数码管基础模块,还有一个储存模块。核心操作会拉高 oEn,并且将相关的 Addr 与 Data 写入储存模块,紧接着该储存模块会经由 oData 驱动数码管基础模块。
图3‑13 推挤位移存储模块的建模图
顾名思义,该模块是推挤功能再加上位移功能的储存模块,左边是储存模块常见的 iEn,iAddr 与 iData,右边则是超乎常规的 oData。
代码3‑7 推挤位移存储模块代码
1.module pushshift_savemod
2.(
3.    input CLOCK,RESET,
4.     input iEn,
5.     input [3:0]iAddr,
6.     input [3:0]iData,
7.     output [23:0]oData
8.);
9.    reg [3:0] RAM [15:0];
10.     reg [23:0] D1;
11.
12.    always @ ( posedge CLOCK or negedge RESET )
13.         if( !RESET )
14.              begin
15.                    D1 <=  24'd0;
16.                end
17.          else if( iEn )
18.              begin
19.                    RAM[ iAddr ] <= iData;
20.                     D1[3:0] <= RAM[ iAddr ];
21.                     D1[7:4] <= D1[3:0];
22.                     D1[11:8] <= D1[7:4];
23.                     D1[15:12] <= D1[11:8];
24.                     D1[19:16] <= D1[15:12];
25.                     D1[23:20] <= D1[19:16];
26.                end
27.
28.    assign oData = D1;
29.
30.endmodule
第 3~7 行是相关的出入端声明。
第 9 行是片上内存 RAM 的声明,第 10 行则是寄存器 D1 的声明。第 15 行则是 D1 的复位操作。
第 17 行表示 iEn 不拉高该模块就不工作。第 18~26 行是该模块的核心操作,第 19 行表示 RAM 将 iData 储存至 iAddr 指定的位置;第 20 行表示, RAM 将 iAddr 指定的内容赋予 D1[3:0]。如此一来,第 19 行与第 20 行的结合就成为推挤功能。至于第 21~25 行则是 6 个深度的位移功能(即 4 位宽为一个深度), iEn 每拉高一个时钟, D1 的内容就向左移动一个深度。
savemod_demo.v组合模块的连线部署根据图3‑12,具体内容我们还是来看代码吧。
代码3‑8 savemod_demo.v组合代码
1.module savemod_demo
2.(
3.    input CLOCK,RESET,
4.     output [7:0]DIG,
5.     output [5:0]SEL
6.);
7.     reg [3:0]i;
8.     reg [3:0]D1,D2;  // D1 for Address, D2 for Data
9.     reg isEn;
10.
11.    always @ ( posedge CLOCK or negedge RESET ) // Core
12.         if( !RESET )
13.              begin
14.                    i <= 4'd0;
15.                     { D1,D2 } <= 8'd0;
16.                     isEn <= 1'b0;
17.                end
18.          else
19.              case( i )
20.
21.                     0:
22.                     begin isEn <= 1'b1; D1 <= 4'd0; D2 <= 4'hA; i <= i + 1'b1; end
23.
24.                     1:
25.                     begin isEn <= 1'b1; D1 <= 4'd0; D2 <= 4'hB; i <= i + 1'b1; end
26.
27.                     2:
28.                     begin isEn <= 1'b1; D1 <= 4'd0; D2 <= 4'hC; i <= i + 1'b1; end
29.
30.                     3:
31.                     begin isEn <= 1'b1; D1 <= 4'd0; D2 <= 4'hD; i <= i + 1'b1; end
32.
33.                     4:
34.                     begin isEn <= 1'b1; D1 <= 4'd0; D2 <= 4'hE; i <= i + 1'b1; end
35.
36.                     5:
37.                     begin isEn <= 1'b1; D1 <= 4'd0; D2 <= 4'hF; i <= i + 1'b1; end
38.
39.                     6:
40.                     begin isEn <= 1'b1; D1 <= 4'd0; D2 <= 4'h0; i <= i + 1'b1; end
41.
42.                     7:
43.                     begin isEn <= 1'b0; i <= i; end
44.
45.                endcase
46.
47.     wire [23:0]DataU1;
48.
49.    pushshift_savemod U1
50.     (
51.         .CLOCK( CLOCK ),
52.          .RESET( RESET ),
53.          .iEn( isEn ),  // < Core
54.          .iAddr( D1 ),  // < Core
55.          .iData( D2 ),  // < Core
56.          .oData( DataU1 ) // > U2
57.     );
58.
59.     smg_basemod U2
60.     (
61.         .CLOCK( CLOCK ),
62.          .RESET( RESET ),
63.          .DIG( DIG ),     // top
64.          .SEL( SEL ),     // top
65.          .iData( DataU1 )  // < U1
66.     );
67.
68.endmodule
第3~5行内容是相关的出入端声明。
第7~18行内容是相关的寄存器声明以及复位操作。其中 D1 用来暂存地址数据, D2 用来暂存读写数据。第 12~17 行是这些寄存器的复位操作
第19~45行内容为核心操作,操作过程如下:
步骤 0 为地址 0 写入数据 4’hA;,将原本的数据挤出来,并且发生位移。
步骤 1 为地址 0 写入数据 4’hB;,将 4’hA 挤出来,并且发生位移。
步骤 2 为地址 0 写入数据 4’hC;,将 4’hB 挤出来,并且发生位移。
步骤 3 为地址 0 写入数据 4’hD;,将 4’hC 挤出来,并且发生位移。
步骤 4 为地址 0 写入数据 4’hE;,将 4’hD 挤出来,并且发生位移。
步骤 5 为地址 0 写入数据 4’hF,将 4’hE 挤出来,并且发生位移。
步骤 6 为地址 0 写入数据 4’d0,将 4’hF 挤出来,并且发生位移。
步骤 7 结束操作。
图3‑14 savemod_demo 部分时序图
图3‑14是 savemod_demo 部分重要的理想时序图,其中 isEn, D1 与 D2 是核心操作所发送的数据,至于 RAM[0]与 oData 是推挤位移储存模块的内部状况与输出结果。时序过程如下:
T0,核心操作拉高 isEn,发送 4’d0 地址数据与 4’hA 读写数据。
T1,核心操作拉高 isEn,发送 4’d0 地址数据与 4’hB 读写数据。储存模块将 4’hA 载入地址 0。
T2,核心操作拉高 isEn,发送 4’d0 地址数据与 4’hC 读写数据。储存模块将 4’hB 载入地址 0,并且将数据 4’hA 挤出, oData 的结果为 24’h00000A。
T3,核心操作拉高 isEn,发送 4’d0 地址数据与 4’hD 读写数据。储存模块将 4’hC 载入地址 0,并且将数据 4’hB 挤出,同时发生位移, oData 的结果为 24’h0000AB。
T4,核心操作拉高 isEn,发送 4’d0 地址数据与 4’hE 读写数据。储存模块将 4’hD 载入地址 0,并且将数据 4’hC 挤出,同时发生位移, oData 的结果为 24’h000ABC。
T5,核心操作拉高 isEn,发送 4’d0 地址数据与 4’hF 读写数据。储存模块将 4’hE 载入地址 0,并且将数据 4’hD 挤出,同时发生位移, oData 的结果为 24’h00ABCD。
T6,核心操作拉高 isEn,发送 4’d0 地址数据与 4’d0 读写数据。储存模块将 4’hF 载入地址 0,并且将数据 4’hE 挤出,同时发生位移, oData 的结果为 24’h0ABCDE。
T7,储存模块将 4’d0 载入地址 0,并且将数据 4’hF 挤出,同时发生位移, oData 的结果为 24’hABCDEF。
第 47~58 行是该储存模块的实例化。
第 59~66 行是数码管基础模块的实例化。编译完毕便下载程序,如果数码管从左至右显示“ABCDEF”,那么表示实验成功。最后还是要强调一下,推挤位移目前是没有意义的储存模块,可是实验十四的目的也非常清楚,就是解释储存模块,演示畸形的储存模块。
(0)

相关推荐

  • ram在单片机里有什么样的作用?

    单片机就是个小计算机,大计算机少不了的数据存储系统,单片机一样有,而且往往和CPU集成在一起,更加显得小巧灵活.直到90年代初,国内容易得到的单片机就是8031:不带存储器的芯片,要想工作,还必须外加 ...

  • 一天一个设计实例-FIFO先进先出模块程序设计

    万字长文实例讲解FIFO. 先进先出(First In first Out, FIFO)是数据通信中的一种等待处理的方式,即对先到达的数据先处理.根据 FIFO 原现设计的 FIFO 存储器,是一个带 ...

  • 一天一个设计实例-LED显示模块设计

    LED显示模块设计 LED点阵模块指的是利用封装8*8的模块组合点元板形成模块,而LED模组应用中一般指两类产品:一种是用插灯或表贴封装做成的单元板,常用户外门头单红屏.户外全彩屏,室内全彩屏等:另外 ...

  • 一天一个设计实例-ROM和RAM组合程序设计

    图3‑12 实验建模图 图3‑12是本次设计的建模图,组合模块 savemod_demo 的内容包括一支核心操作,一只数码管基础模块,还有一个储存模块.核心操作会拉高 oEn,并且将相关的 Addr ...

  • 一天一个设计实例-基于FPGA的模数、数模转换器应用设计

    基于FPGA的模数.数模转换器应用设计 本节设计采用黑金ADDA模块,模块硬件结构如下: 图7‑32 硬件结构 数模转换( DA) 电路 如硬件结构图所示, DA 电路由高速 DA 芯片. 7 阶巴特 ...

  • 一天一个设计实例-基于FPGA的数模转换器应用设计

    基于FPGA的数模转换器应用设计 1.1.1带 EEPROM 存储器的 12 位MCP4725应用设计 7.4.1.1 MCP4725简介 MCP4725 是低功耗.高精度.单通道的 12 位缓冲电压 ...

  • 一天一个设计实例-基于FPGA的模数转换器应用设计

    基于FPGA的模数转换器应用设计 1.1.1八通道AD7606应用设计 7.3.1.1 AD7606简介 AD7606 是一款集成式 8 通道数据采集系统,片内集成输入放大器.过压保护电路.二阶模拟抗 ...

  • 一天一个设计实例-AD转换器原理

    AD转换器原理 A/D 转换器的基本原理 图7‑2 A/D转换器功能示意图 A/D转换器(Analog-to-Digital Converter)又叫模/数转换器,即是将模拟信号(电压或是电流的形式) ...

  • 一天一个设计实例-LCD12864的应用设计

    LCD12864的应用设计 LCD12864的应用基本和LCD1602的自定义字库非常类似,下面就简单介绍下LCD12864. 12864 中文 汉字图形点阵液晶显示模块,可显示汉字及图形,内置 81 ...

  • 一天一个设计实例-​LCD1602的应用设计

    LCD1602的应用设计 1.1.1LCD1602的简介 工业字符型液晶,能够同时显示16x02即32个字符.(16列2行) 图6‑16 LCD1602实物图 注:为了表示的方便 ,后文皆以1表示高电 ...