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)
begin
case ({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;
endcase
end
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) //WRite
RAM8x64 [addr] <= data_in [7:0];
else if (~WR && CS ) // read
mem_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;
else
cnt <= 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;
else
case(state)
2'b00:state<=2'b01;
2'b01:state<=2'b11;
2'b11:state<=2'b00;
default:state<=2'b00;
endcase
always @(negedge clk or negedge reset)
if(!reset)
clk1<=1'b0;
else
clk1<=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_)
begin
cnt1 <= 4'b0;
cnt2 <= 4'b0;
end
else
begin
if (cnt1 < 2'b11)
cnt1 <= cnt1 + 1;
else
cnt1 <= 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_)
begin
cnt1 <= 4'b0;
cnt2 <= 4'b0;
end
else
begin
if (cnt1 < 2'b11)
cnt1 <= cnt1 + 1;
else
cnt1 <= cnt1;
cnt2 <= cnt1 - 1;
end
endmodule
优点
- 多数器件包含异步复位端口,异步复位会节约逻辑资源;
- 异步复位设计简单;
- 大多数FPGA,都有专用的全局复位/置位资源(GSR,Globe Set Reset),使用GSR资源,异步复位达到所有寄存器的偏斜(skew)最小。
缺点
- 异步复位的作用和释放与时钟沿没有直接关系,在异步复位神效时问题并不明显,但当异步复位释放时,如果异步复位释放时间和时钟的有效沿到达时间几乎一致,则容易造成触发器输出亚稳态,造成逻辑错误;
- 如果异步复位逻辑树的组合逻辑产生了毛刺,则毛刺的有效沿会使触发器误复位,造成逻辑错误。
推荐的复位电路设计方式
- 推荐的复位电路设计方式是异步复位,同步释放,这种方式可以有效的继承异步复位设计简单的优势,并克服异步复位的风险与缺陷;
- 在FPGA中使用异步复位,同步释放可以节约器件资源,并获得稳定可靠的复位效果。
module asyn_rst_syn_release(clk, rst_, cnt1, cnt2);
input clk;
input rst_;
output [4:0] cnt1 , cnt2;
reg [4:0] cnt1 , cnt2;
// reset release circuit
reg reset_reg;
always @ (posedge clk)
reset_reg <= rst_; //异步复位同步化
always @ (posedge clk or negedge reset_reg)
if (!reset_reg)
begin
cnt1 <= 4'b0;
cnt2 <= 4'b0;
end
else
begin
if (cnt1 < 2'b11)
cnt1 <= cnt1 + 1;
else
cnt1 <= cnt1;
cnt2 <= cnt1 - 1;
end
endmodule
- 异步复位,同步释放的具体设计方法很多,关键是如何保证同步地释放复位信号,本例举例的方法是在复位信号释放时,用系统时钟采样后再将复位信号送到寄存器的异步复位端。
- 所谓“异步复位”是针对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) begin
if(!rst_n) begin
sys_rst_n_r <= 0;
sys_rst_n <= 0;
end
else begin
sys_rst_n_r <= 1;
sys_rst_n <= sys_rst_n_r;
end
end
always @(posedge clk or negedge sys_rst_n) begin
if(!sys_rst_n)
b <= 0;
else
b <= a;
end
endmodule
用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_)
begin
case (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;
end
endcase
end
endmodule
读寄存器
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;
else
begin
if (my_rd)
begin
if (CS_reg1)
data_out <= reg1;
else if (CS_reg2)
data_out <= reg2;
else if (CS_reg3)
data_out <= reg3;
end
else
data_out <= 8'b0;
end
endmodule
写寄存器
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)
begin
reg1 <= 8'b0;
reg2 <= 8'b0;
reg3 <= 8'b0;
end
else
begin
if (my_wr)
begin
if (CS_reg1)
reg1 <= data_in;
else if (CS_reg2)
reg2 <= data_in;
else if (CS_reg3)
reg3 <= data_in;
end
else
begin
reg1 <= reg1;
reg2 <= reg2;
reg3 <= reg3;
end
end
endmodule
顶层
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 written
assign 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_)
begin
case (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;
end
endcase
end
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;
else
begin
if (my_rd)
begin
if (CS_reg1)
data_out <= reg1;
else if (CS_reg2)
data_out <= reg2;
else if (CS_reg3)
data_out <= reg3;
end
else
data_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)
begin
reg1 <= 8'b0;
reg2 <= 8'b0;
reg3 <= 8'b0;
end
else
begin
if (my_wr)
begin
if (CS_reg1)
reg1 <= data_in;
else if (CS_reg2)
reg2 <= data_in;
else if (CS_reg3)
reg3 <= data_in;
end
else
begin
reg1 <= reg1;
reg2 <= reg2;
reg3 <= reg3;
end
end
endmodule
/******************************************/
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 written
assign 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建模分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22215.html