RTL概念与常用RTL建模
RTL和综合的概念
RTL(Register Transfer Level,寄存器传输级)指:不关注寄存器和组合逻辑的细节(如使用了多少逻辑门,逻辑门之间的连接拓扑结构等),通过描述寄存器到寄存器之间的逻辑功能描述电路的HDL层次。RTL级是比门级更高的抽象层次,使用RTL级语言描述硬件电路一般比门级描述简单高效得多。
- RTL级语言的最重要的特性是:RTL级描述是可综合的描述层次。
综合(Synthesize)是指将HDL语言、原理图等设计输入翻译成由与、或、非门等基本逻辑单元组成的门级连接(网表),并根据设计目标与要求(约束条件)优化所生成的逻辑连接,输出门级网表文件。RTL级综合指将RTL级源码翻译并优化为门级网表。
RTL级的基本要素和设计步骤
典型的RTL设计包含一下3个部分
时钟域描述:描述所使用的所有时钟,时钟之间的主从与派生关系,时钟域之间的转换;
时序逻辑描述(寄存器描述):根据时钟沿的变换,描述寄存器之间的数据传输方式;
组合逻辑描述:描述电平敏感信号的逻辑组合方式与逻辑功能。
书中推荐的设计步骤:
- 功能定义与模块划分:根据系统功能的定义和模块划分准则划分各个功能模块;
- 定义所有模块的接口:首先清晰定义每个模块的接口,完成每个模块的信号列表,这种思路与Modular Design(模块化设计方法)一致,利于模块重用、调试、修改;
- 设计时钟域:根据设计的时钟复杂程度定义时钟之间的派生关系,分析设计中有哪些时钟域,是否存在异步时钟域之间的数据交换;对于PLD器件设计,还需要确认全局时钟是否使用PLL/DLL完成时钟的分频、倍频、移相等功能,哪些时钟使用全局时钟资源布线,哪些时钟使用第二全局时钟资源布线;全局时钟的特点是:几乎没有Clock Skew(时钟倾斜),有一定的Clock Delay(时钟延迟),驱动能力最强;第二全局时钟的特点是:有较小的Clock Shew,较小的Clock Delay,时钟驱动能力较强;
- 补充:时钟抖动(Clock Jitter):指芯片的某一个给定点上时钟周期发生暂时性变化,使得时钟周期在不同的周期上可能加长或缩短。时钟偏移(Clock Skew):是由于布线长度及负载不同引起的,导致同一个时钟信号到达相邻两个时序单元的时间不一致。区别:Jitter是在时钟发生器内部产生的,和晶振或者PLL内部电路有关,布线对其没有影响。Skew是由不同布线长度导致的不同路径的时钟上升沿到来的延时不同。
- 考虑设计的关键路径:关键路径是指设计中时序要求最难以满足的路径,设计的时序要求主要体现在频率、建立时间、保持时间等时序指标上,;在设计初期,设计者可以根据系统的频率要求,粗略的分析出设计的时序难点(如最高频率路径、计数器的最低位、包含复杂组合逻辑的时序路径等),通过一些时序优化手段(如Pipeline、Retiming、逻辑复制等)从代码上缓解设计的时序压力,这种方法以但依靠综合与布线工具的自动优化有效的多;
- 顶层设计:RTL设计推荐使用自顶而下的设计方法,因为这种设计方法与模块规划的顺序一致,而且更有利于进行Modular Design,可以并行开展设计工作,提高模块复用率;
- FSM设计:FSM是逻辑设计最重要的内容之一;
- 时序逻辑设计:首先根据时钟域规划好寄存器组,然后描述各个寄存器组之间的数据传输方式;
- 组合逻辑设计:一般来说,大段的组合逻辑最好与时序逻辑分开描述,这样更有利于时序约束和时序分析,使综合器和布局布线器达到更好的优化效果。
常用RTL级建模
非阻塞赋值、阻塞赋值、连续赋值
- 对于时序逻辑,即always块的敏感信号列表为边沿敏感信号,统一使用非阻塞赋值“<=”;
- 对于always块敏感信号列表为电平敏感的组合逻辑,统一使用阻塞赋值“=”;
- 对于assign关键字描述的组合逻辑,统一使用阻塞赋值“=”,变量被定义为wire型信号。
寄存器电路建模
寄存器和组合逻辑是数字逻辑电路的两大基本要素,寄存器一般和同步时序逻辑关联,其特点是仅当时钟的边沿到达时,才有可能发生输出的改变。
- 寄存器变量声明:寄存器定义为reg型,但要注意的是,反之不一定成立;
- 时钟输入:在每个时钟的正沿或负沿对数据从进行处理。
- 异步复位/置位:绝大多数目标器件的寄存器模型都包含异步复位/置位端;
- 同步复位/置位:任何寄存器都可以实现同步复位/置位功能;
- 同时使用时钟上升沿和下降沿的问题:有时因为数据采样或者调整数据相位等需求,设计者会在一个always的敏感信号列表中同时使用时钟的posedge和negedge,或者在两个always的敏感信号列表中分别使用posedge和nesedge对某个寄存器电路操作;这两种描述下,时钟上升沿和下降沿到来时,寄存器电路都会做相应的操作,这个双边沿电路等同于使用了原来时钟的倍频时钟的单边沿操作电路,这种操作是不推荐的;芯片内部的PLL/DLL和一些时钟电路往往只能对一个边沿有非常好的指标,而另一个沿的抖动、偏移、斜率等指标不见得非常优化,有时同时使用时钟的正负边沿会因为时钟的抖动、偏斜、占空比、斜率等问题造成一定的性能恶化;一般推荐将原时钟通过PLL/DLL倍频,然后使用倍频时钟的单边沿进行操作。
组合逻辑建模
always 模块的敏感信号列表为电平敏感信号的组合逻辑电路
always模块的敏感信号列表为所有判定条件和输入信号,在使用这种结构描述组合逻辑时一定要将敏感列表列写完整。在always块中可以使用高级编程语言,使用阻塞赋值“=”,虽然信号被定义位reg型,但最终综合实现结果并不是寄存器,而是组合逻辑,定义为reg型是纯语法需要。
assign 等语句描述的组合逻辑电路
这种形式描述组合逻辑电路适用于描述那些相对简单的组合逻辑,信号一般被定义位wire型。
双向端口与三态信号建模
所有的双向总线应该在顶层模块定义为三态信号,禁止在顶层以外的其他子层次定义双向端口。为了避免仿真和综合实现结果不一致,并便于维护,强烈建议仅在顶层定义双向总线和例化三态信号,禁止在除顶层以外的其他层次赋值高阻态"Z",在顶层将双向信号分为输入和输出信号两种类型,然后根据需要分别传递到不同的子模块中,这样做的另一个好处是便于描述仿真激励。
module bibus (clk, rst, sel, data_bus, addr);
input clk, rst, sel;
input [7:0] addr;
inout [7:0] data_bus;
wire [7:0] data_in, data_out;assign data_in = data_bus;
assign data_bus = (sel) ? data_out : 8'bZ;decode decode_inst (.clock (clk),.reset (rst),.data_bus_in (data_in),.addr_bus (addr),.data_bus_out (data_out));
endmodule
如果三态总线的使能关系比较复杂,不是单一信号,此时可以使用嵌套的问号表达式,或者使用case语句描述。
- 嵌套的问号表达式
module complex_bibus (clk, rst, sel1, sel2, sel3, data_bus, addr);
input clk, rst;
input sel1, sel2, sel3;
input [7:0] addr;
inout [7:0] data_bus;wire [7:0] data_in;
//wire [7:0] data_out; //use wire type
wire [7:0] decode_out;
wire [7:0] cnt_out;assign data_in = data_bus;
assign data_bus = (sel1)? decode_out : ((sel2)? cnt_out : ((sel3)? 8'b11111111: 8'bZZZZZZZZ)); decode decode_inst (.clock (clk),.reset (rst),.data_bus_in (data_in),.addr_bus (addr),.data_bus_out (decode_out));counter counter_inst (.clock (clk),.reset (rst),.data_bus_in (data_in),.cnt_out (cnt_out));
endmodule
- case语句(如果使能情况比较复杂,通过case进行罗列,更清晰)
input sel1, sel2, sel3;
input [7:0] addr;
inout [7:0] data_bus;wire [7:0] data_in;
reg [7:0] data_out; //use reg type, but not registers
wire [7:0] decode_out;
wire [7:0] cnt_out;assign data_in = data_bus;decode decode_inst (.clock (clk),.reset (rst),.data_bus_in (data_in),.addr_bus (addr),.data_bus_out (decode_out));counter counter_inst (.clock (clk),.reset (rst),.data_bus_in (data_in),.cnt_out (cnt_out));always @ (decode_out or cnt_out or sel1 or sel2 or sel3)begincase ({sel1, sel2, sel3})3'b100: data_out = decode_out;3'b010: data_out = cnt_out;3'b001: data_out = 8'b11111111;default: data_out = 8'bZZZZZZZZ;endcaseend
assign data_bus = data_out;
endmodule
mux 建模
简单的使用assign和?,相对复杂的使用always和if…else、case等条件判断语句建模。
存储器建模
逻辑电路设计经常使用一些单口RAM、双口RAM和ROM等存储器。Verilog 语法中基本的存储单元定义格式为:
reg [datawidth] MemoryName [addresswidth]
如定义一个数据位宽为8bit,地址为63为的RAM8x64:
reg [7:0] RAM8x64 [0:63];
在使用存储单元时,不能直接操作存储器某地址的某位,需要先将存储单元赋值给某个寄存器,然后再对该存储器的某位进行相关操作。
module ram_basic (clk, CS, WR, addr, data_in, data_out, en);
input clk;
input CS; //CS = 1, RAM enable
input WR; //WR =1 then WRite enable; WR = 0 then read enable
input en; //data_out enable, convert the data sequency
input [5:0] addr;
input [7:0] data_in;
output [7:0] data_out;reg [7:0] RAM8x64 [0:63];reg [7:0] mem_data;always @ (posedge clk)if (WR && CS) //WRiteRAM8x64 [addr] <= data_in [7:0];else if (~WR && CS ) // readmem_data <= RAM8x64 [addr]; assign data_out = (en)? mem_data[7:0] : {~mem_data[7], mem_data[6:0]};
endmodule
- FPGA中内嵌的RAM资源分为两类:块RAM(Block RAM)资源和分布式RAM(Distributed RAM)资源,BRAM作为FPGA内部硬件资源,使用时不会占用其他逻辑资源,分布式RAM是通过查找表和触发器实现的RAM结构。
- 使用RAM等资源时通常不使用这种Verilog语言进行建模,一般使用厂商提供IP核通过GUI完成相关参数配置,并生成相关IP。
简单的时钟分频电路
- 偶数分频十分简单,只需要用高速时钟驱动一个同步计数器;
module clk_div_phase (rst, clk_200K, clk_100K, clk_50K, clk_25K);
input clk_200K;
input rst;
output clk_100K, clk_50K, clk_25K;
wire clk_100K, clk_50K, clk_25K;reg [2:0] cnt; always @ (posedge clk_200K or negedge rst)if (!rst)cnt <= 3'b000;elsecnt <= cnt + 1;assign clk_100K = ~cnt [0];//2分频
assign clk_50K = ~cnt [1];//4分频
assign clk_25K = ~cnt [2];//8分频endmodule
上例通过对计数器每个bit的反向,完成了所有分频后的时钟调整,保证了3个分频后时钟的相位严格同相,也与源时钟同相,有共同的上升沿。
- 奇数分频
module clk_3div (clk,reset,clk_out);
input clk, reset;
output clk_out;
reg[1:0] state;
reg clk1;
always @(posedge clk or negedge reset)
if(!reset)state<=2'b00;
elsecase(state)2'b00:state<=2'b01;2'b01:state<=2'b11;2'b11:state<=2'b00;default:state<=2'b00;endcasealways @(negedge clk or negedge reset)if(!reset)clk1<=1'b0;elseclk1<=state[0];assign clk_out=state[0]&clk1;
endmodule
串/并转换建模
根据数据的排序和数量的要求,可以选用移位寄存器、RAM等实现;对于数量比较小的设计可以采用移位寄存器完成串/并转换(串转并:先移位,再并行输出;并转串:先加载并行数据,再移位输出);对于排列顺序有规律的串/并转换,可以使用case语句进行判断实现;对于复杂的串/并转换,还可以用状态机实现。
同步复位与异步复位
同步复位
建模
module syn_rst (clk, rst_, cnt1, cnt2);
input clk;
input rst_;
output [4:0] cnt1 , cnt2;
reg [4:0] cnt1 , cnt2;always @ (posedge clk)if (!rst_)begincnt1 <= 4'b0;cnt2 <= 4'b0;endelsebeginif (cnt1 < 2'b11)cnt1 <= cnt1 + 1;elsecnt1 <= cnt1; cnt2 <= cnt1 - 1; end
endmodule
很多目标器件的触发器本身本身并不包含同步复位端口,则同步复位可以通过下图结构实现:
优点
- 同步复位利于基于周期机制的仿真器仿真;
- 使用同步复位可以设计100%的同步时序电路,利于时序分析,其综合结果的频率往往更高;
- 同步复位仅在时钟的上升沿生效,可以有效的避免因复位电路毛刺造成的亚稳态和错误;在进行复位和释放复位信号时,都是仅当时钟沿采到复位电平变化时才进行相关操作,如果复位信号树的组合逻辑出现了某些毛刺,此时时钟边沿采集到毛刺的概率非常低,通过时钟沿采样,可以十分有效地过滤复位电路的组合逻辑毛刺,增强电路的稳定性。
缺点
很多目标器件的触发器本身不包含同步复位端口,使用同步复位会增加很多逻辑资源;
同步复位的最大问题在于必须保证复位信号的有效时间足够长,才能保证所有触发器都有效复位,所以同步复位信号的持续时间必须大于设计的最长时钟周期,以保证所有时钟的有效沿都能采样到同步复位信号。
其实仅仅保证同步复位信号的持续时间大于最慢的时钟周期还是不够的,设计中还要考虑到同步复位信号树通过所有组合逻辑路径的延时以及由于时钟布线产生的偏斜(skew),只有同步复位大于时钟最大周期加上同步信号穿过的组合逻辑路径延时加上时钟偏斜时,才能保证同步复位可靠、彻底。
上图中,假设同步复位逻辑树组合逻辑的延时为t1,复位信号传播路径的最大延迟为t2,最慢时钟的周期为Period max,时钟的skew为clk2-clk1,则同步复位的周期Tsys_rst应满足:Tsys_rst > Period max + (clk2-clk1) + t1 + t2;
异步复位
建模
module asyn_rst (clk, rst_, cnt1, cnt2);
input clk;
input rst_;
output [4:0] cnt1 , cnt2;
reg [4:0] cnt1 , cnt2;always @ (posedge clk or negedge rst_)if (!rst_)begincnt1 <= 4'b0;cnt2 <= 4'b0;endelsebeginif (cnt1 < 2'b11)cnt1 <= cnt1 + 1;elsecnt1 <= cnt1; cnt2 <= cnt1 - 1; end
endmodule
优点
- 多数器件包含异步复位端口,异步复位会节约逻辑资源;
- 异步复位设计简单;
- 大多数FPGA,都有专用的全局复位/置位资源(GSR,Globe Set Reset),使用GSR资源,异步复位达到所有寄存器的偏斜(skew)最小。
缺点
- 异步复位的作用和释放与时钟沿没有直接关系,在异步复位神效时问题并不明显,但当异步复位释放时,如果异步复位释放时间和时钟的有效沿到达时间几乎一致,则容易造成触发器输出亚稳态,造成逻辑错误;
- 如果异步复位逻辑树的组合逻辑产生了毛刺,则毛刺的有效沿会使触发器误复位,造成逻辑错误。
推荐的复位电路设计方式——异步复位,同步释放
- 推荐的复位电路设计方式是异步复位,同步释放,这种方式可以有效的继承异步复位设计简单的优势,并克服异步复位的风险与缺陷;相较于纯粹的异步复位,降低了异步复位信号释放导致的亚稳态的可能性,相较于同步复位,能够识别到同步复位中检测不到的复位信号。
- 在FPGA中使用异步复位,同步释放可以节约器件资源,并获得稳定可靠的复位效果。
- 异步复位同步释放,既能很快的检测到复位信号,不需要复位保持超过一个时钟周期,又能解决释放时的亚稳态问题(降低亚稳态发生的概率)。
- 异步复位,同步释放的具体设计方法很多,关键是如何保证同步地释放复位信号,本例举例的方法是在复位信号释放时,用系统时钟采样后再将复位信号送到寄存器的异步复位端。
- 所谓“异步复位”是针对D触发器的复位端口,它是异步的,但是设计中已经同步了异步复位信号,所以笔者(Crazybingo)认为这只是某种意义上的“异步复位”。
- 所谓“同步释放”,实际上是由于我们设计了同步逻辑电路,外部复位信号不会在出现释放时与clk信号竞争,整个系统将与全局时钟clk信号同步。
- 使用时钟将外部输入的异步复位信号寄存一个节拍后,再送到触发器异步复位端口的设计方法的另一个好处在于:做STA(静态时序分析)分析时,时序工具会自动检查同步后的异步复位信号和时钟的到达(Recovery)/撤销(Removal)时间关系,如果因布线造成的skew导致该到达/撤销时间不能满足,STA工具会上报该路径,帮助设计者进一步分析问题。
module system_ctrl //异步复位,同步释放——by 特权同学
//==================<端口>==================================================
(
//globel clock ----------------------------------
input wire clk , //时钟,50Mhz
input wire rst_n , //复位,低电平有效
//user interface --------------------------------
input wire a , //输入信号
output reg b //输出信号
);//==========================================================================
//== 异步复位的同步化设计
//==========================================================================
reg sys_rst_n_r;
reg sys_rst_n;always @(posedge clk or negedge rst_n)
beginif(!rst_n) beginsys_rst_n_r <= 1'b0;sys_rst_n <= 1'b0;endelse beginsys_rst_n_r <= 1'b1;sys_rst_n <= sys_rst_n_r; //注意这里的rst_sync_n才是我们真正对系统输出的复位信号end
endalways @(posedge clk or negedge sys_rst_n) //注意这里将同步后的信号仍作为异步复位信号进行处理,Altera推荐
beginif(!sys_rst_n)b <= 0;elseb <= a;
endendmodule
上图是Altera推荐的异步复位,同步释放示意图
module reset_gen ( output rst_sync_n, input clk, rst_async_n); //此模块对应前一个黄框中的逻辑,输出信号在后级电路中仍作为异步复位信号进行处理
reg rst_s1, rst_s2;
wire rst_sync_n ;always @ (posedge clk, posedge rst_async_n)if (rst_async_n)begin rst_s1 <= 1'b0;rst_s2 <= 1'b0;endelse beginrst_s1 <= 1'b1; //针对Altera FPGArst_s2 <= rst_s1;endassign rst_sync_n = rst_s2; //注意这里的rst_sync_n才是我们真正对系统输出的复位信号endmodule
Xilinx
Xilinx的FPGA,高电平复位其Filp-Flop同时支持同步/异步复位,复位准则:
- 尽量少使用复位,特别是少用全局复位,能不用复位就不用,一定要用复位的使用局部复位;
- 如果必须要复位,在同步和异步复位上,则尽量使用同步复位(BRAM DSP48不支持异步复位),一定要用异步复位的地方,采用“异步复位、同步释放”;
- 复位电平选择高电平复位;
只要存在复位都会增加布局布线的负担,因为复位会像时钟一样连接到每一个寄存器上,是相当复杂的工程,会增加时序收敛的难度。
对于同一个触发器逻辑,因为同时支持异步和同步复位,所以异步复位并不会节省资源;对于其他的资源,比如 DSP48 等,同步复位更加节省资源。
首先,对于 DSP48,其内部还带有一些寄存器(只支持同步复位),如果使用异步复位,则会额外使用外部 Slice 中带异步复位的寄存器,而使用同步复位时,可以利用 DSP48 内部的寄存器;Xilinx 的 FPGA,对于 DSP48、BRAM 资源,使用同步复位比异步复位更节省资源。
对于高电平复位,使用异步复位同步释放,则第一个寄存器的 D 输入是 0,这里使用了 4 个触发器打拍同步。The number of flip-flops in the chain determines the minimum duration of the reset pulse issued to the localized network.
always @(posedge clk or posedge rst_async)
beginif(rst_async == 1'b1) beginrst_sync_reg1 <= 1'b1; //Xilinx的FPGA高电平复位rst_sync_reg2 <= 1'b1;rst_sync_reg3 <= 1'b1;rst_sync_reg4 <= 1'b1;endelse beginrst_sync_reg1 <= 1'b0;rst_sync_reg2 <= rst_sync_reg1;rst_sync_reg3 <= rst_sync_reg2;rst_sync_reg4 <= rst_sync_reg3;end
end wire sys_rst;
assign sys_rst = rst_sync_reg4;always @(posedge clk) //同步后的信号当作同步复位信号处理
beginif(sys_rst == 1'b1) begindata_out_rst_async <= 1'b0;endelse begindata_out_rst_async <= a & b & c & d;end
end
同步后的信号如果作为同步复位信号进行处理:
rst_async异步复位一旦给出,用于同步的4个寄存器rst_sync_reg1~4立刻输出高电平“1”,在下一个时钟上升沿检测到同步复位并将输出data_out_rst_async复位;
异步复位信号释放后,经过同步的sys_rst经过一定周期后在时钟边沿同步释放;
同步后的信号如果作为异步复位信号进行处理:
区别在于异步复位信号rst_async一旦产生,输出立刻复位,且同样是同步释放,好像这种处理才更符合异步复位、同步释放。
那么为什么Xilinx白皮书还是将sys_rst按照同步复位去做的呢? 综合考虑可能有这样的因素:
当作同步复位的差别只在于复位时间会稍晚一些,要在时钟的下一个边沿检测到,但是还是能够识别到输入的rst_async异步复位信号,所以从复位角度来说,都能够后实现复位效果;
根据Xilinx复位准则,我们知道同步复位相比异步复位有很多好处,具体参见:Xilinx FPGA 复位策略白皮书(WP272) 公众号-FPGA探索者做了翻译可以参考,既然两者对后级复位没有功能上的差别,那么优先选择同步复位;
Xilinx 推荐的复位准则:
尽量少使用复位,特别是少用全局复位,能不用复位就不用,一定要用复位的使用局部复位;
如果必须要复位,在同步和异步复位上,则尽量使用同步复位,一定要用异步复位的地方,采用“异步复位、同步释放”;
复位电平选择高电平复位;
Altera
Altera的FPGA,低电平复位,其触发器只有异步复位端口,所以如果想要用同步复位,需要额外的资源来实现,这也是“异步复位节省资源”这一说法的原因。
具体电路及代码见上文
用case和if…else建模
略
可综合的Verilog语法子集
在RTL建模时,使用可综合的Verilog语法是整个Verilog语法中的非常小的一个子集。其实可综合的Verilog常用关键字非常有限,这恰恰体现了Verilog语言是硬件描述语言的本质,Verilog作为HDL,其本质在于把电路流畅、合理的转换为语言形式,而使用较少的一些关键字就可以有效的将电路转换到可综合的RTL语言结构。
常用的RTL语法结构列举:
- 模块声明:module…endmodule;
- 端口声明:input、outpu、inout;
- 信号类型:wire、reg、tri等,integer通常用于for语句中索引;
- 参数定义:parameter
- 运算操作符:逻辑操作、移位操作、算术操作;
- 比较判断:case…endcase(casex/casez)、if…else;
- 连续赋值:assign、问号表达式
- always模块:建模时序和组合逻辑
- 语法分割符:begin…end
- 任务定义:task…endtask
- 循环语句:for
CPU读/写PLD寄存器接口设计实例
- CS:片选(低有效、input)
- OE:输出使能信号(低有效、input)
- WR:读/写指示,低-读数据,高-写数据(input)
- Address:地址总线(input)
- Data:双向数据总线(inout)
地址译码器电路
module decode (CS_, OE_, WR_, Addr, my_wr, my_rd, CS_reg1, CS_reg2, CS_reg3);input CS_, OE_, WR_;
input [7:0] Addr;output my_wr, my_rd;
output CS_reg1, CS_reg2, CS_reg3;reg CS_reg1, CS_reg2, CS_reg3;assign my_wr = (!WR_) && (!CS_) && (!OE_);
assign my_rd = (WR_) && (!CS_) && (!OE_);always @ (Addr or CS_)if (!CS_)begincase (Addr)8'b 11110000: CS_reg1 <= 1'b1;8'b 00001111: CS_reg2 <= 1'b1;8'b 10100010: CS_reg3 <= 1'b1;default: beginCS_reg1 <= 1'b0;CS_reg2 <= 1'b0;CS_reg3 <= 1'b0;endendcaseendendmodule
读寄存器
module read_reg (clk, rst, data_out, my_rd, CS_reg1, CS_reg2, CS_reg3, reg1, reg2, reg3);input clk, rst, my_rd, CS_reg1, CS_reg2, CS_reg3;
input [7:0] reg1, reg2, reg3;
output [7:0] data_out;
reg [7:0] data_out;always @ (posedge clk or negedge rst)if (!rst)data_out <= 8'b0;elsebeginif (my_rd)beginif (CS_reg1)data_out <= reg1;else if (CS_reg2)data_out <= reg2;else if (CS_reg3)data_out <= reg3;endelsedata_out <= 8'b0; endendmodule
写寄存器
module write_reg (clk, rst, data_in, my_wr, CS_reg1, CS_reg2, CS_reg3, reg1, reg2, reg3);input clk, rst, my_wr, CS_reg1, CS_reg2, CS_reg3;
input [7:0] data_in;
output [7:0] reg1, reg2, reg3;
reg [7:0] reg1, reg2, reg3;always @ (posedge clk or negedge rst)if (!rst)beginreg1 <= 8'b0;reg2 <= 8'b0;reg3 <= 8'b0; endelsebeginif (my_wr)beginif (CS_reg1)reg1 <= data_in;else if (CS_reg2)reg2 <= data_in;else if (CS_reg3)reg3 <= data_in;endelsebeginreg1 <= reg1;reg2 <= reg2;reg3 <= reg3;end endendmodule
顶层
module top (clk_cpu, rst, CS_, OE_, WR_, Addr, data_bus);input clk_cpu, rst;
input CS_, OE_, WR_;
input [7:0] Addr;
inout [7:0] data_bus;wire [7:0] data_in;
wire [7:0] data_out;
wire my_wr, my_rd;
wire CS_reg1, CS_reg2, CS_reg3; // the register selection
wire [7:0] reg1, reg2, reg3; // the register to be read and writtenassign data_in = data_bus;
assign data_bus = ((!CS_) && (!OE_))? data_out : 8'bZZZZZZZZ;decode decode_u1 (.CS_(CS_),.OE_(OE_),.WR_(WR_),.Addr(Addr),.my_wr(my_wr),.my_rd(my_rd),.CS_reg1(CS_reg1),.CS_reg2(CS_reg2),.CS_reg3(CS_reg3));write_reg write_reg_u1 ( .clk(clk_cpu),.rst(rst),.data_in(data_in),.my_wr(my_wr),.CS_reg1(CS_reg1),.CS_reg2(CS_reg2),.CS_reg3(CS_reg3),.reg1(reg1),.reg2(reg2),.reg3(reg3));read_reg read_reg_u1 ( .clk(clk_cpu),.rst(rst),.data_out(data_out),.my_rd(my_rd),.CS_reg1(CS_reg1),.CS_reg2(CS_reg2),.CS_reg3(CS_reg3),.reg1(reg1),.reg2(reg2),.reg3(reg3));endmodule
使用OE/WR边沿读写
使用OE或WR的沿读写寄存器的描述看起来比前面介绍的使用CPU时钟同步读写寄存器的描述简单,但是读者必须明确这种方式正常工作有两个前提条件:
- OE的上升沿可以有效地采样数据总线,即OE的上升沿采样数据总线时Setup和Hold都能保证满足;
- WR和CS信号都比OE信号宽,即OE上升沿读写寄存器时,CS和WR信号始终保持有效。
只有这两个条件同时满足的前提下,才能保证使用OE的沿读写PLD寄存器电路是可靠的。
/******************************************/
module decode (CS_, WR_, Addr, my_wr, my_rd, CS_reg1, CS_reg2, CS_reg3);input CS_, WR_;
input [7:0] Addr;output my_wr, my_rd;
output CS_reg1, CS_reg2, CS_reg3;reg CS_reg1, CS_reg2, CS_reg3;assign my_wr = (!WR_) && (!CS_);
assign my_rd = (WR_) && (!CS_);always @ (Addr or CS_)if (!CS_)begincase (Addr)8'b 11110000: CS_reg1 <= 1'b1;8'b 00001111: CS_reg2 <= 1'b1;8'b 10100010: CS_reg3 <= 1'b1;default: begin CS_reg1 <= 1'b0; CS_reg2 <= 1'b0; CS_reg3 <= 1'b0; endendcaseend
endmodule
/******************************************/
module read_reg (OE_, rst, data_out, my_rd, CS_reg1, CS_reg2, CS_reg3, reg1, reg2, reg3);input OE_, rst, my_rd, CS_reg1, CS_reg2, CS_reg3;
input [7:0] reg1, reg2, reg3;
output [7:0] data_out;
reg [7:0] data_out;always @ (posedge OE_ or negedge rst)if (!rst)data_out <= 8'b0;elsebeginif (my_rd)beginif (CS_reg1)data_out <= reg1;else if (CS_reg2)data_out <= reg2;else if (CS_reg3)data_out <= reg3;endelsedata_out <= 8'b0; end
endmodule
/******************************************/
module write_reg (OE_, rst, data_in, my_wr, CS_reg1, CS_reg2, CS_reg3, reg1, reg2, reg3);input OE_, rst, my_wr, CS_reg1, CS_reg2, CS_reg3;
input [7:0] data_in;
output [7:0] reg1, reg2, reg3;
reg [7:0] reg1, reg2, reg3;always @ (posedge OE_ or negedge rst)if (!rst)beginreg1 <= 8'b0;reg2 <= 8'b0;reg3 <= 8'b0; endelsebeginif (my_wr)beginif (CS_reg1)reg1 <= data_in;else if (CS_reg2)reg2 <= data_in;else if (CS_reg3)reg3 <= data_in;endelsebeginreg1 <= reg1;reg2 <= reg2;reg3 <= reg3;end endendmodule
/******************************************/
module top (rst, CS_, OE_, WR_, Addr, data_bus);input rst;
input CS_, OE_, WR_;
input [7:0] Addr;
inout [7:0] data_bus;wire [7:0] data_in;
wire [7:0] data_out;
wire my_wr, my_rd;
wire CS_reg1, CS_reg2, CS_reg3; // the register selection
wire [7:0] reg1, reg2, reg3; // the register to be read and writtenassign data_in = data_bus;
assign data_bus = ((!CS_) && (!OE_))? data_out : 8'bZZZZZZZZ;decode decode_u1 (.CS_(CS_), // .OE_(OE_), .WR_(WR_), .Addr(Addr), .my_wr(my_wr), .my_rd(my_rd), .CS_reg1(CS_reg1), .CS_reg2(CS_reg2), .CS_reg3(CS_reg3));write_reg write_reg_u1 ( .OE_(OE_), .rst(rst),.data_in(data_in), .my_wr(my_wr), .CS_reg1(CS_reg1), .CS_reg2(CS_reg2), .CS_reg3(CS_reg3), .reg1(reg1), .reg2(reg2), .reg3(reg3));read_reg read_reg_u1 ( .OE_(OE_),.rst(rst),.data_out(data_out), .my_rd(my_rd), .CS_reg1(CS_reg1), .CS_reg2(CS_reg2), .CS_reg3(CS_reg3), .reg1(reg1), .reg2(reg2), .reg3(reg3));endmodule
/******************************************/
如果译码电路是组合逻辑,则其译码结果就有可能带有毛刺,另外由于CPU总线的时序在电压、温度、环境变化的情况下时序可能遭到破坏,造成OE,WR,CS等信号的时序余量恶化,如果此时使用译码结果的电平做电平敏感的always模块,进行读写寄存器操作(如Example-4-21\ asyn_bad目录下的read_reg.v和write_reg.v),则会因为毛刺和错误电平造成读写错误。所以不同将OE或WR的电平作为敏感信号来进行读写。
RTL概念与常用RTL建模相关推荐
- Josh 的学习笔记之 Verilog(Part 4——RTL 概念与常用 RTL 建模)
文章目录 1. RTL 和综合 2. RTL 级的基本要素和设计步骤 3. 常用 RTL 级建模 3.1 非阻塞赋值.阻塞赋值.连续赋值 3.2 寄存器电路建模 3.3 组合逻辑建模 3.4 双线端口 ...
- 解题报告(二)C、(darkBZOJ 2194) 快速傅立叶之二(FFT、卷积的概念、常用变换)
繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我写的超高质量题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数(1∼51 \sim 51∼5),以模板题难度 11 ...
- 已知三个用不同数制表示的整数_数制的概念与常用的数制之间的转换。大学生必看!...
数制的概念与常用的数制之间如何实现转化呢 一.首先我们来看一下,数制和与它相关的基数.位权是什么? 1. 数制就是表示数值大小的各种计数体制,简单来说就像是1,2,3···之类的用来计数的,只不过有很 ...
- Linux基础概念及常用命令
Linux基础概念及常用命令 文章目录 Linux基础概念及常用命令 1 Linux概述 1.1 为什么要学Linux 1.2 Linux简介 1.3 Linux 发行版 1.4 Linux 应用领域 ...
- Kubernetes与Docker基本概念与常用命令对照
摘要: Docker是众多用户上手入门的基础容器和编排工具,提供了良好的开发者体验.Kubernetes是强大的容器编排平台,功能丰富.它们有很多概念和操作都有类似之处.我们今天会和大家对比基本概念与 ...
- Bitmap详解(上)常用概念和常用API
前言: 图片的操作我相信大家都操作过,在算法层面大家往往都是把图片转成MAT矩阵处理的,而Android 开发层面大多数都是bitmap位图操作.接下来我将分算法层面以及android层面来讲解一下图 ...
- Docker——使用docker工具管理软件/组件的运行,镜像、容器、数据卷的基本概念,常用指令,使用docker搭建Java微服务运行环境
Docker--使用docker工具管理软件/组件的运行,镜像.容器.数据卷的基本概念,常用指令,使用docker搭建Java微服务运行环境 一.docker的安装和卸载 1.卸载 2.安装 3. 导 ...
- 几款常用UML建模工具解析
本节向大家介绍几款常用UML建模工具,UML是个好东西,但是过分的依赖于UML也不是一件好事,因为有时候它会把简单的东西复杂化.请看下面详细介绍. 常用UML建模工具 UML不算是个新名词,但是实际中 ...
- arcgis坐标系未定义_科学网—ArcGIS中的坐标系:基本概念和常用操作 - 李郎平的博文...
ArcGIS中的坐标系:基本概念和常用操作 李郎平,Email: lilp@lreis.ac.cn 中国科学院地理科学与资源研究所,资源与环境信息系统国家重点实验室 缘由:介绍GIS(地理信息系统)中 ...
最新文章
- 【学习笔记】15、标准数据类型—集合
- 红黑树的删除_Python实现红黑树的删除操作
- 在线安装docker
- git status查看文件的状态
- Android开发推荐资料大合集
- android 点滴积累
- C语言学生成绩管理系统设计 《C语言程序设计》实训报告
- Centos Siege测试使用
- 程序设计基础java_Java程序设计基础
- 帧动画和骨骼动画 本质的理解
- html 按钮变成椭圆,HTML 渐变椭圆按钮
- 安卓框架访问QQ文件的路径miui13
- 录像机中码流类型中定时、事件、网传代表什么意思?
- 公司的电脑监控软件一般能够监控到什么程度
- C语言数据结构实现——一元多项式的基本运算
- vue input输入框事件
- C语言实现天气获取 + HTML页面
- python数圈算法_Python实现随机爬山算法
- [分享]解读软件外包
- hx711称重模块调试