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

 

posted @ 2023-01-20 12:30  luckylan  阅读(947)  评论(0)    收藏  举报