对读者的假设
已经掌握:
内容
1 编码器
1.1 二进制编码器
二进制编码器,即把来自2^n条输入线路的信息,编码转换成二进制码。其输入为独热码,输出为二进制码。此处以4~2编码器为例。
表1 4~2编码器的真值表
代码1.1.1 使用case语句描述的4~2编码器(可综合)
module encoder( input [3:0] iA, output reg [1:0] oQ ); always @ (*) case(iA) 4'b0001 : oQ = 2'b00; 4'b0010 : oQ = 2'b01; 4'b0100 : oQ = 2'b10; 4'b1000 : oQ = 2'b11; default : oQ = 2'b00; endcase endmodule
由于输入的选项并没有优先级,因此第6~7行,采用case语句来描述编码器。如前文所述,对于不完全的case选项,为了避免前后仿真不一致及锁存器的生成,第12行加入了deflaut语句赋初值。实际上在这种情况下,在敏感列表之后赋初值的效果是一样的。
always @ (*) case(iA) 4'b0001 : oQ = 2'b00; 4'b0010 : oQ = 2'b01; 4'b0100 : oQ = 2'b10; 4'b1000 : oQ = 2'b11; default : oQ = 2'b00; endcase
图1.1.1 4~2编码器的RTL视图
代码1.1.2 4~2编码器的testbench(不可综合,仅用于仿真)
`timescale 1ns/1ns module encoder_tb; reg [3:0] i_a; wire [1:0] o_q; initial begin i_a = 4'b0000; #20 i_a = 4'b0001; #20 i_a = 4'b0010; #20 i_a = 4'b0100; #20 i_a = 4'b1000; #20 $stop; end encoder encoder_inst( .iA(i_a), .oQ(o_q) ); endmodule
图1.1.2 使用case语句描述的4~2编码器的功能仿真波形
1.2 优先编码器
如表2所示,优先级分别为:i3 > i2 > i1 > i0;对于这种有优先级的编码器,我们可以采用casez语句来描述。
表2.1 4~2优先编码器的真值表
代码1.2.1 使用case语句描述的4~2优先编码器(可综合)
module encoder( input [3:0] iA, output reg [1:0] oQ ); always @ (*) casez(iA) 4'b0001 : oQ = 2'b00; 4'b001? : oQ = 2'b01; 4'b01?? : oQ = 2'b10; 4'b1??? : oQ = 2'b11; default : oQ = 2'b00; endcase endmodule
第6~13行,采用casez来描述优先编码器。注意:对于无关项输入的描述,使用“?”描述优于使用“x”,详情见参考1;在可综合的代码中,不要使用casex。
always @ (*) casez(iA) 4'b0001 : oQ = 2'b00; 4'b001? : oQ = 2'b01; 4'b01?? : oQ = 2'b10; 4'b1??? : oQ = 2'b11; default : oQ = 2'b00; endcase
图1.2.1 使用case语句描述的4~2优先编码器RTL视图
代码1.2.2 使用if-else-if语句描述的4~2优先编码器(可综合)
module encoder( input [3:0] iA, output reg [1:0] oQ ); always @ (*) if (iA[3]) oQ = 2'b11; else if (iA[2]) oQ = 2'b10; else if (iA[1]) oQ = 2'b01; else if (iA[0]) oQ = 2'b00; else oQ = 2'b00; endmodule
对于带优先级的电路,使用if-else-if来描述,应该是轻而易举的事情。注意:优先级高的,放在前面描述;else不要丢。
always @ (*) if (iA[3]) oQ = 2'b11; else if (iA[2]) oQ = 2'b10; else if (iA[1]) oQ = 2'b01; else if (iA[0]) oQ = 2'b00; else oQ = 2'b00;
图1.2.2 使用if-else-if语句描述的4~2优先编码器
代码1.2.3 4~2优先编码器的testbench(不可综合,仅用于仿真)
`timescale 1ns/1ns module encoder_tb; reg [3:0] i_a; wire [1:0] o_q; initial begin i_a = 4'b0; forever begin if (i_a<16) #20 i_a <= i_a + 1'b1; else #20 i_a <= 4'b0; end end initial #330 $stop; encoder encoder_inst( .iA(i_a), .oQ(o_q) ); endmodule
图1.2.3 使用case语句描述的4~2优先编码器的功能仿真波形
图1.2.4 使用if-else-if语句描述的4~2优先编码器的功能仿真波形
图1.2.3和图1.2.4的波形一致,说明两种描述方式虽然RTL视图有所不同,但其功能上是一致的。
2 译码器
译码器刚好与编码器相反,即输入二进制码,输出独热码。此处以2~4译码器为例。
代码2.1 使用case语句描述的译码器(可综合)
module decoder( input [1:0] iD, output reg [3:0] oQ ); always @ (*) begin oQ = 4'b0000; case(iD) 2'b00: oQ = 4'b0001; 2'b01: oQ = 4'b0010; 2'b10: oQ = 4'b0100; 2'b11: oQ = 4'b1000; endcase end endmodule
第6~15行,由于输入刚好是2^n个选项,因此没有加入default语句(没有其他选项可能出现),而在敏感列表之后给输出寄存器赋初值(避免生成锁存器)这一良好的代码风格一定要提现出来。
always @ (*) begin oQ = 4'b0000; case(iD) 2'b00: oQ = 4'b0001; 2'b01: oQ = 4'b0010; 2'b10: oQ = 4'b0100; 2'b11: oQ = 4'b1000; endcase end
图2.1 使用case语句描述的译码器的RTL视图
代码2.2 使用case语句描述的译码器的testbench(不可综合,仅用于仿真)
`timescale 1ns/1ns module decoder_tb; reg [1:0] i_d; wire [3:0] o_q; initial begin i_d = 2'b00; #20 i_d = 2'b01; #20 i_d = 2'b10; #20 i_d = 2'b11; #20 $stop; end decoder decoder_inst( .iD(i_d), .oQ(o_q) ); endmodule
图2.2 使用case语句描述的译码器的功能仿真波形
3 码型转换器
有了编码器和译码器的基础,下面我们举一小例来讨论一下码型转换器。此处以共阳的七段数码管段码查找表为例。
图3.1 七段数码管
表3.1 七段数码管段码查找表
代码3.1 七段数码管段码查找表(可综合)
module seg_transcoder( input [3:0] iNum, output reg [7:0] oSeg ); always @ (*) begin case(iNum) 4'h0 : oSeg = 8'hC0; 4'h1 : oSeg = 8'hF9; 4'h2 : oSeg = 8'hA4; 4'h3 : oSeg = 8'hB0; 4'h4 : oSeg = 8'h99; 4'h5 : oSeg = 8'h92; 4'h6 : oSeg = 8'h82; 4'h7 : oSeg = 8'hF8; 4'h8 : oSeg = 8'h80; 4'h9 : oSeg = 8'h90; 4'hA : oSeg = 8'h88; 4'hB : oSeg = 8'h83; 4'hC : oSeg = 8'hC6; 4'hD : oSeg = 8'hA1; 4'hE : oSeg = 8'h86; 4'hF : oSeg = 8'h8E; default : oSeg = 8'hFF; endcase end endmodule
图3.1 七段数码管段码查找表的RTL视图
代码3.2 七段数码管段码查找表的testbench(不可综合,仅用于仿真)
`timescale 1ns/1ns module seg_transcoder_tb; reg [3:0] i_num; wire [7:0] o_seg; initial begin i_num = 4'h0; for(i_num=0; i_num<4'hF; i_num=i_num+1'b1) #20 ; end initial #330 $stop; seg_transcoder seg_transcoder_inst( .iNum(i_num), .oSeg(o_seg) ); endmodule
第8~9行,使用for语句来产生激励数据。注意for内不能没有表达式哟。for语句主要用作复制电路,没有语句,就像此处一样,用“;”表示无需复制电路。
for(i_num=0; i_num<4'hF; i_num=i_num+1'b1) #20 ;
右键所需观察的信号,选择Radix>Hexadecimal,以十六进制形式查看信号值。如图3.2所示。
图3.2 以十六进制形式查看信号值
图3.3 七段数码管段码查找表的功能仿真波形
4 小结
经过上面的讨论,我们发现,一般情况下,可综合的代码仅仅使用了if-else-if和case语句,几乎涉及不到其他语句。由于可用综合的语句较少,因此描述起来也比较灵活,这就使得代码风格显得尤为重要。然而,好的代码风格需要长时间方能练就,作为初学者,我们不仅要经常比对自己所描述电路综合和仿真效果是否和想法一致,还需要多读一些官方推荐的文档及一些大师的论文,因为我们个人的能力毕竟有限,我们要善于借鉴已有的经验以消化利用。所谓心中有点路,此话应当这样理解,心中本无电路,练得多了,电路才能扎根于我们的思维中,彼时我们才能游刃有余地来描述所需电路。
辅助阅读
1. Altera.Recommended HDL Coding Styles
参考
1. Cliff Cummings' Award-Winning Verilog & SystemVerilog Papers. "full_case parallel_case", the Evil Twins of Verilog Synthesis
2. Wikipedia.Seven-segment display