verilog组合逻辑电路之编码器/译码器
1. 组合逻辑电路
1.1 组合逻辑划分
组合逻辑可以分为:always 模块的电平敏感信号触发; assign 关键字描述的数据流赋值语句。
②电平敏感信号的always 模块几乎可以完成对所有组合逻辑电路的建模。敏感列表为所有判断条件信号和输入信号,但一定要注意敏感列表的完整性(注意通配符*的使用)。
③由于赋值语句有阻塞赋值和非阻塞赋值两类,建议组合逻辑中使用阻塞赋值语句“=”,
④always 模块中的信号必须定义为reg 型,不过最终的实现结果中并没有寄存器。这是由于在组合逻辑电路描述中,将信号定义为reg 型,只是为了满足语法要求。
⑤assign 语句的描述,利用条件符“?”可以描述一些相对简单的组合逻辑电路,左边的赋值信号只能被定义为wire 型。当组合逻辑比较复杂时,需要很多条语句assign 语句或者多重嵌套“?”,使得代码可读性极差,因此此时推荐always组合逻辑建模方式。
1.2 敏感变量的描述完备性
Verilog中,用always块设计组合逻辑电路时,在赋值表达式右端参与赋值的所有信号都必须在always @(敏感电平列表)中列出,always中if语句的判断表达式必须在敏感电平列表中列出。
如果在赋值表达式右端引用了敏感电平列表中没有列出的信号,在综合时将会为没有列出的信号隐含地产生一个透明锁存器。这是因为该信号的变化不会立刻引起所赋值的变化,而必须等到敏感电平列表中的某一个信号变化时,它的作用才表现出来,即相当于存在一个透明锁存器,把该信号的变化暂存起来,待敏感电平列表中的某一个信号变化时再起作用,纯组合逻辑电路不可能作到这一点。综合器会发出警告。
example1: d没有在敏感电平列表中,d变化时e不会立刻变化,直到a,b,c中某一个变化
input a,b,c; reg e,d; always @(a or b or c) begin e=d&a&b; d=e |c; end
example2:d在敏感电平列表中,d变化时e立刻变化
input a,b,c; reg e,d; always @(a or b or c or d) begin e=d&a&b; d=e |c; end
1.3 条件的描述完备性
如果if语句和case语句的条件描述不完备,也会造成不必要的锁存器。
example1: //如果a==1'b0,q=? q将保持原值不变,生成锁存器!
if (a==1'b1) q=1'b1;
example2://q有明确的值。不会生成锁存器!
if (a==1'b1) q=1'b1; else q=1'b0;
example3: //如果a==2'b10或a==2'b11,q=? q将保持原值不变,锁存器!
reg[1:0] a,q;
case (a)
2'b00 : q=2'b00;
2'b01 : q=2'b11;
endcase
example4: //q有明确的值。不会生成锁存器!
reg[1:0] a,q;
case (a)
2'b00 : q=2'b00;
2'b01 : q=2'b11;
default: q=2'b00;
endcase
1.3.1 if 优先级属性
module mult_if(a,b,c,d,sel,z) input a,b,c,d; input[3:0]sel; output reg z; always@(a or b or c or d or sel) begin z=0; if(sel[0])z=a; if(sel[1])z=b; if(sel[2])z=c; if(sel[3])z=d; end endmodule

1.4 优先编码器
总线上挂3个信号A,B,C,请求信号req[2:0],仲裁结果grant[2:0],req[2]对应A的总线请求,最高优先级,req[1]对应B的总线请求,req[0]对应C的总线请求,最低优先级。
grant[2:0]=2’b100,A获得总线;grant[2:0]=2’b010,B获得总线; grant[2:0]=2’b001,C获得总线
1.4.1 固定优先级1
module fix_arbit (
input clk,
input rst_n,
input [2:0] req,
output reg[2:0] grant
);
always@(posedge clk)
if(~rst_n)
grant<=3’d0;
else
case(req)
3’b100,3’b101,3b110,3’b111:grant<=3’b100;
3’b010,3b011 : grant<=3’b010;
3’b001 : grant<=3’b001;
default: grant<=3’b000;
endcase
endmodule
1.4.2 固定优先级2
module fix_arbit (
input clk,
input rst_n,
input [2:0] req
output reg[2:0] grant
);
always@(posedge clk)
if(~rst_n)
grant<=3’d0;
else begin
if(req[2])
grant<=3’b100;
else if(req[1])
grant<=3’b010;
else if(reg[0])
grant<=3’b001;
else
grant<=3’b100;
end
endmodule
1.4.3 固定优先级3
module fix_arbit (
input clk,
input rst_n,
input [2:0] req,
output reg [2:0] grant
);
assign g2 = r2;
assign g1 = !r2&r1;
assign g0 = !r2&!r1&r0;
always@(posedge clk)
if(~rst_n)
grant<=3’d0;
else
grant<={g2,g1,g0};
endmodule


1.4.4 参数化的固定优先级模块
如何设计一个参数化的模块。对于上面的仲裁器来说,我们希望可以参数化产生请求的个数,即设计下面的模块:
module priority_arbiter #(parameter REQ_WIDTH = 16 )( input [REQ_WIDTH-1:0] req, output [REQ_WIDTH-1:0] gnt );
这样我们可以根据不同场合产生request的模块的个数来例化同一个仲裁器,只需要改变参数即可。很明显,上面利用case的写法不能scalable,我们需要用更加巧妙的办法。
首先可以想到的办法是利用for循环,思路其实非常直接,从低位到高位依次去判断,借助一个pre_req来记录低位是否已经有了request, 如果第i位有了request,那么第i+1位一直到最高位的pre_req都是1。
module prior_arb #( parameter REQ_WIDTH = 16 )(
input logic [REQ_WIDTH-1:0] req,
output logic [REQ_WIDTH-1:0] grant
);
logic [REQ_WIDTH-1:0] pre_req;
always_comb begin
pre_req[0] = req[0];
grant[0] = req[0];
for (int i = 1; i < REQ_WIDTH; i = i + 1) begin
grant[i] = req[i] & !pre_req[i-1]; // current req & no higher priority request
pre_req[i] = req[i] | pre_req[i-1]; // or all higher priority requests
end
end
endmodule
里面的pre_req的意义就是:如果第i位的req为第一个1,那么pre_req从i+1位开始每一位都是1,而第0位到第i位都是0。这其实就是我们要找的mask! 只需要把req和上一个周期的pre_req AND起来,那么我们自然就得到了一个新的request,这个request里之前grant的位以及之前的位都被mask掉了,允许通过的是在之前优先级更低的那些位,如果那些位上之前有request但是没有被grant,现在就可以轮到他们了。每次新的grant之后mask里0的位数会变多,从而mask掉更多位,直到所有的低优先级的request都被grant了一次之后,req AND mask的结果变成全0了,这个时候就说明我们已经轮询完毕,要重新来过了.
下面介绍更简洁的方式,只有3行code。
module prior_arb #( parameter REQ_WIDTH = 16 )(
input [REQ_WIDTH-1:0] req,
output [REQ_WIDTH-1:0] gnt
);
logic [REQ_WIDTH-1:0] pre_req;
assign pre_req[0] = 1'b0;
assign pre_req[REQ_WIDTH-1:1] = req[REQ_WIDTH-2:0] | pre_req[REQ_WIDTH-2:0];
assign gnt = req & ~pre_req;
endmodule
下面的这种实现方式就更简单,就一行实现。
module prior_arb #( parameter REQ_WIDTH = 16 ) (
input [REQ_WIDTH-1:0] req,
output [REQ_WIDTH-1:0] gnt
);
assign gnt = req & (~(req-1));
endmodule
本质上,我们要做的是找req这个信号里从低到高第一个出现的1,那么我们给req减去1会得到什么?假设req的第i位是1,第0到第i-1位都是0,那么减去1之后我们知道低位不够减,得要向高位借位,直到哪一位可以借到呢?就是第一次出现1的位,即从第i位借位,第0到i-1位都变成了1,而第i位变为了0,更高位不变。然后我们再给减1之后的结果取反,然后把结果再和req本身按位与,可以得出,只有第i位在取反之后又变成了1,而其余位都是和req本身相反的,按位与之后是0,这样就提取出来了第一个为1的那一位,也就是我们需要的grant。再考虑一下特殊情况req全0,很明显,按位与之后gnt依然都是全0,没有任何问题。
减1再取反,即计算2的补码的算法,只不过给一个数求2的补码的方法是取反再加1,这里倒过来,减1再取反,本质上是一样的。这其实是2的补码的一个特性,即一个数和它的补码相与,得到的结果是一个独热码,独热码为1的那一位是这个数最低的1。所以这个仲裁器的设计方法用一句话概括:request和它的2的补码按位与。
1.5 轮询优先级
总线上挂3个信号A,B,C,req为来自三个从设备的请求信号,请求信号req[2:0],仲裁结果grant[2:0],req[2]对应A的总线请求,req[1]对应B的总线请求,req[0]对应C的总线请求。grant [2]对应A获得总线,grant [1]对应B获得总线,grant [0]对应C获得总线.A→B→C→A
// 总线轮询算法a.如果当前只有一个信号请求,则处理.
// b.如果没有请求,那么A获得总线.
// c.如果同时有多个信号请求,考虑上一个请求信号,
// 如果上一个请求信号是A,那么轮询的是BCA,
// 如果上一个请求信号是B,那么轮询的是CAB,
// 如果上一个请求信号是C,那么轮询的是ABC
module round _arbiter( input clk, input rst_n, input [2:0] req, output reg [2:0] grant ); always @ (posedge clk or negedge rst_n) if (!rst_n) grant <= 1'b11; else case(grant) 3'b100: //之前A获得总线 case (req) 3'b000,3'b100: grant <= 2'b100; 3'b001,3'b101: grant <= 2'b001; 3'b010,3'b011,3'b110,3'b111: grant <= 2'b010; default: grant <= 2'b100; endcase 3'b010: //之前B获得总线 case (req) 3'b000,3'b010:grant <= 2'b010; 3'b001,3'b011,3'b101,3'b111: grant <= 2'b001; 3'b100,3'b110:grant <= 2'b100; default: grant <= 2'b010; endcase 3'b001: //之前C获得总线 case (req) 3'b000,3'b001:grant <= 2'b001; 3'b010,3'b011: grant <= 2'b010; 3'b100,3'b101,3'b110,3'b111: grant <= 2'b100; default: grant <= 2'b001; endcase default: grant <= 2'b000; endcase endmodule
1.5.1 固定优先级实现轮询优先级
module round_arbiter(
input clk,
input rst_n,
input [2:0] req,
output reg [2:0] grant
);
reg [2:0] req_seq;
reg [2:0] grant_tmp;
wire[2:0] grant_seq;
always@(posedge clk)
if(~rst_n)begin
grant <= 0;
end
else if(|req) begin
grant <= grant_tmp;
end
always@(*)
case(grant)
3'b100: begin req_seq = {req[1:0],req[2]};grant_tmp={grant_seq[0],grant_seq[2:1]};end
3'b010: begin req_seq = {req[0],req[2:1]};grant_tmp={grant_seq[1:0],grant_seq[2]};end
3'b001: begin req_seq = req;grant_tmp=grant_seq;end
default:begin req_seq = req;grant_tmp=grant_seq;end
endcase
fix_arbit u1(
.req(req_seq),
.grant(grant_seq)
);
endmodule
module fix_arbit (
input [2:0] req,
output [2:0]grant
);
assign g2 = req[2];
assign g1 = !req[2]&req[1];
assign g0 = !req[2]&!req[1]&req[0];
assign grant={g2,g1,g0};
endmodule

module arbiter_pri(r,g)
input [3:0] r;
input [1:0] pri_ctrl;
output [3:0] g;
reg [3:0] wr;
always@(*) //pri_decode
case(pri_ctrl)
2’b00: wr = r;
2’b01: wr = {r[0],r[3:1]};
2’b10: wr = {r[1:0],r[3:2]};
2’b11: wr = {r[2:0],r[3]};
endcase
Arbiter_fix Uarbiter_fix
(.r(wr), .g(g));
endmodule
将wr的四位数重新按照译码器要求排列,即实现了可变优先级
1.5.2 方式2
module arbiter_base #(parameter NUM_REQ = 4)(
input [NUM_REQ-1:0] req,
input [NUM_REQ-1:0] base,
output [NUM_REQ-1:0] gnt
);
wire[2NUM_REQ-1:0] double_req = {req,req};
wire[2NUM_REQ-1:0] double_gnt = double_req & ~(double_req - base);
assign gnt = double_gnt[NUM_REQ-1:0] | double_gnt[2*NUM_REQ-1:NUM_REQ];
endmodule
在这个模块中,base是一个onehot的信号,它为1的那一位表示这一位的优先级最高,然后其次是它的高位即左边的位,直到最高位后回到第0位绕回来,优先级依次降低,直到为1那一位右边的这位为最低。咱们以4位为例,如果base = 4’b0100, 那么优先级是bit[2] > bit[3] > bit[0] > bit[1]。
这个设计的思路和 Fixed Priority Arbiter最后那个1行设计的思路很像,里面double_req & ~(double_req-base)其实就是利用减法的借位去找出base以上第一个为1的那一位,只不过由于base值可能比req值要大,不够减,所以要扩展为{req, req}来去减。当base=4‘b0001的时候就是咱们上一篇里面的最后的算法。当然base=4’b0001的时候不存在req不够减的问题,所以不用扩展。
既然有了可以根据输入给定优先级的固定优先级仲裁器,每次grant之后,把优先级调整一下就可以了。而且这个设计妙就妙在,base要求是一个onehot signal,而且为1的那一位优先级最高。我们前面说过,grant一定是onehot,grant之后被grant的那一路优先级变为最低,它的高1位优先级变为最高,所以,我只需要一个history_reg,来去记录之前最后grant的值,然后只需要将grant的值左移一下就变成了下一个周期的base。比如说,假设我上一个周期grant为4’b0010,那么bit[2]要变为最高优先级,那只需要base是grant的左移即可。RTL代码如下
module round_robin_arbiter #(parameter NUM_REQ = 4)( input clk, input rstn, input [NUM_REQ-1:0] req, output [NUM_REQ-1:0] gnt ); logic [NUM_REQ-1:0] hist_q, hist_d; always_ff@(posedge clk) begin if(!rstn) hist_q <= {{NUM_REQ-1{1’b0}}, 1’b1}; else if(|req) hist_q <= {gnt[NUM_REQ-2:0, gnt[NUM_REQ-1]}; end arbiter_base #(.NUM_REQ(NUM_REQ)) arbiter( .req (req), .gnt (gnt), .base (hist_q) ); endmodule
我们注意到,和Fixed Priority Arbiter不同,Round robin arbiter不再是纯的组合逻辑电路,而是要有时钟和复位信号,因为里面必须要有个寄存器来记录之前grant的状态。
1.5.2 无优先级case
always]语句中的case/ casex:分支语句赋值:case语句不带有逻辑优先级属性,各个分支具有相同的优先级,而casex语句可以体现逻辑优先性。

键盘是一个编码器的例子:
键值:给键盘上的每一个按键分配一个数字,代表这个按键,这个数字就称为键值,将多个键盘输入的一系列0或1值编码为对应的2进制数。将多个输入端的1和0编码为对应的2进制数。执行相应的操作。

1.5.3 74ls148优先级编码器
为扩展输出端,是控制标志。=0表示有编码信号输入,只要有一个输入信号有效,
输出为有效电平。
为使能输入端,低电平有效,可以作为片选信号。
为使能输出端,表明当前芯片被选中。
配合可以实现多级编码器之间的优先级别的控制。

1.6 译码器
1.6.1 地址译码器
假如一个芯片的地址分配如下:
|
芯片名称 |
地址范围 |
使能信号 |
芯片名称 |
地址范围 |
使能信号 |
|
DMA控制器1 |
000-01F |
dma1_en |
定时器 |
040-05F |
timer_en |
|
DMA控制器2 |
0C0-0DF |
dma2_en |
键盘接口 |
060-06F |
key_en |
|
DMA寄存器 |
080-09F |
dma_en |
wdrc |
070-07F |
wdrc_en |
|
中断控制器1 |
020-03F |
int1_en |
协处理器 |
0F0-0FF |
core_en |
|
中断控制器2 |
0A0-0BF |
int2_en |
SRAM |
0E0-0EF |
sram_en |
当总线送来对应地址和控制信号时,通过判断地址范围来确定到底是对哪个模块进行读写操作,因此就需要对地址译码,产生对应模块存储器的写使能信号
地址译码器是这样一种器件:输入为数字量(即地址),根据输入的数字量在多个输出端中选一个有效。
module addr_encoder(
input clk,
input rst_n,
input [11:0]addr,
output [9:0]device_en
);
assign dma1_en=(addr[7:4]==4’h0)|( addr[7:4]==4’h1);
assign dma2_en= (addr[7:4]==4’hC)|( addr[7:4]==4’hD);
assign dma_en= (addr[7:4]==4’h8)|( addr[7:4]==4’h9);
assign int1_en=(addr[7:4]==4’h2)|( addr[7:4]==4’h3);
assign int2_en= (addr[7:4]==4’hA)|( addr[7:4]==4’hB);
assign timer_en= (addr[7:4]==4’h4)|( addr[7:4]==4’h5); …
endmodule
1.6.2 优先级译码器
地址译码器是这样一种器件:输入为数字量(即地址),根据输入的数字量在多个输出端中选一个有效。
module PRIO_ENCODER (Cin,Din,Ein,Fin,Sin,Pout);
input Cin,Din,Ein,Fin; // input signals
input [1:0] Sin; //输入选择控制
output reg Pout; //输出选择结果
always @(Sin or Cin or Din or Ein or Fin) begin
if (Sin == 2’b00) Pout = Cin;
else if (Sin == 2’b01) Pout = Din;
else if (Sin == 2’b10) Pout = Ein;
else Pout = Fin;
end
endmodule

优先译码器电路图
1.6.3 74ls138
用一块3线8线译码器 74LS138可以组成任何一个三变量输入的逻辑函数,任意一个输入三变量的逻辑函数都可以用一块3线8线译码器 74LS138来实现。因为任意一个组合逻辑表达式都可以写成标准与或式的形式,即最小项之和的形式,

A2、A1、A0为二进制译码输入端,
为译码输出端(低电平有效),S1、
为选通控制端。当S1=1、
时,译码器处于工作状态;当S1=0、
时,译码器处于禁止状态。
3-8 译码器是一个很常用的器件,其真值表如下所示,根据 A2,A1,A0 的值,得出不同的结果。

1.6.4 集成8421 BCD码译码器74LS42

浙公网安备 33010602011771号