Verilog-硬件描述语言使用

语言简介

Verilog在线学习HDLBits主页

基本语句

常量

1
2
3
3'b101 	// binary 101
4'hf // binary 1111
4'd10 // binary 1010

线wire

物理线路没有方向,但建模上需要指定方向。

创造一个模型,有三个输入和四个输出

1
2
3
4
a -> w
b -> x
b -> y
c -> z

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module top_module( 
input a,b,c,
output w,x,y,z );

assign w=a;
assign x=b;
assign y=b;
assign z=c;

// If we're certain about the width of each signal, using
// the concatenation operator is equivalent and shorter:
// assign {w,x,y,z} = {a,b,b,c};

endmodule

非门Inverter

电路中产生一个非门

1
2
3
4
5
module top_module( input in, output out );
assign out = !in;
// similar
// assign out = ~in;
endmodule

与门AND gate

电路中产生一个与门

1
2
3
4
5
6
7
8
module top_module( 
input a,
input b,
output out );

assign out = a & b;

endmodule

或非门Nor gate

电路中产生一个或非门

1
2
3
4
5
6
7
8
module top_module( 
input a,
input b,
output out );

assign out = ~(a | b);
endmodule

异或非门XNOR gate

电路中产生一个异或非门

1
2
3
4
5
6
7
8
module top_module( 
input a,
input b,
output out );

assign out = ~(a ^ b);
endmodule

声明线Declaring wires

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
`default_nettype none
module top_module(
input a,
input b,
input c,
input d,
output out,
output out_n );

wire w1, w2, w3;
assign out = w3;
assign out_n = ~w3;

assign w3 = w1 | w2;
assign w1 = a & b;
assign w2 = c & d;
endmodule

7458芯片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module top_module ( 
input p1a, p1b, p1c, p1d, p1e, p1f,
output p1y,
input p2a, p2b, p2c, p2d,
output p2y );

wire wp11,wp12;
assign p1y = wp11 | wp12;
assign wp11 = p1a & p1b & p1c;
assign wp12 = p1f & p1e & p1d;

wire wp21,wp22;
assign p2y = wp21 | wp22;
assign wp21 = p2a & p2b;
assign wp22 = p2c & p2d;
endmodule

向量Vector

基本使用

声明语法:

1
type [upper:lower] vector_name;

类型可以是wire, reg等等

1
2
3
4
5
6
wire [7:0] w;         // 8-bit wire
reg [4:1] x; // 4-bit reg
output reg [0:0] y; // 1-bit reg that is also an output port (this is still a vector)
input wire [3:-2] z; // 6-bit wire input (negative ranges are allowed)
output [3:0] a; // 4-bit output wire. Type is 'wire' unless specified otherwise.
wire [0:7] b; // 8-bit wire where b[0] is the most-significant bit.

隐含声明可能导致意想不到的结果

1
2
3
4
5
6
wire [2:0] a, c;   // Two vectors
assign a = 3'b101; // a = 101
assign b = a; // b = 1 implicitly-created wire
assign c = b; // c = 001 <-- bug
my_module i1 (d,e); // d and e are implicitly one-bit wide if not declared.
// This could be a bug if the port was intended to be a

unpacked数组维度跟在名称后,packed数组维度放在名称前

1
2
reg [7:0] mem [255:0];   // 256 unpacked elements, each of which is a 8-bit packed vector of reg.
reg mem2 [28:0]; // 29 unpacked elements, each of which is a 1-bit reg.

描绘如下电路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module top_module ( 
input wire [2:0] vec,
output wire [2:0] outv,
output wire o2,
output wire o1,
output wire o0 ); // Module body starts after module declaration

assign {o2,o1,o0} = vec[2:0];
assign outv = vec;

// This is ok too
//assign o0 = vec[0];
//assign o1 = vec[1];
//assign o2 = vec[2];
endmodule

将字数据(2字节)按高低位分离

1
2
3
4
5
6
7
8
9
10
11
12
`default_nettype none     // Disable implicit nets. Reduces some types of bugs.
module top_module(
input wire [15:0] in,
output wire [7:0] out_hi,
output wire [7:0] out_lo );

assign out_hi = in[15:8];
assign out_lo = in[7:0];

// Concatenation operator also works: assign {out_hi, out_lo} = in;
endmodule

向量门

按位与&和逻辑与&对于N-bit位输入来说,会产生不同结果。前者会产生N-bit位输出,而后者只产生1-bit位输出(作为布尔值,非0值为true,0值为false)。

描绘如下电路,out_not高5-3位为b的非,其余为a的非。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module top_module( 
input [2:0] a,
input [2:0] b,
output [2:0] out_or_bitwise,
output out_or_logical,
output [5:0] out_not
);

assign out_or_bitwise = a | b;
assign out_or_logical = a || b;
// ! is logical inverse
assign out_not = {~b, ~a};
endmodule

颠倒8位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
module top_module (
input [7:0] in,
output [7:0] out
);
assign out = {in[0],in[1],in[2],in[3],in[4],in[5],in[6],in[7]};
//assign {out[0],out[1],out[2],out[3],out[4],out[5],out[6],out[7]} = in;

/*
// I know you're dying to know how to use a loop to do this:

// Create a combinational always block. This creates combinational logic that computes the same result
// as sequential code. for-loops describe circuit *behaviour*, not *structure*, so they can only be used
// inside procedural blocks (e.g., always block).
// The circuit created (wires and gates) does NOT do any iteration: It only produces the same result
// AS IF the iteration occurred. In reality, a logic synthesizer will do the iteration at compile time to
// figure out what circuit to produce. (In contrast, a Verilog simulator will execute the loop sequentially
// during simulation.)
always @(*) begin
for (int i=0; i<8; i++) // int is a SystemVerilog type. Use integer for pure Verilog.
out[i] = in[8-i-1];
end


// It is also possible to do this with a generate-for loop. Generate loops look like procedural for loops,
// but are quite different in concept, and not easy to understand. Generate loops are used to make instantiations
// of "things" (Unlike procedural loops, it doesn't describe actions). These "things" are assign statements,
// module instantiations, net/variable declarations, and procedural blocks (things you can create when NOT inside
// a procedure). Generate loops (and genvars) are evaluated entirely at compile time. You can think of generate
// blocks as a form of preprocessing to generate more code, which is then run though the logic synthesizer.
// In the example below, the generate-for loop first creates 8 assign statements at compile time, which is then
// synthesized.
// Note that because of its intended usage (generating code at compile time), there are some restrictions
// on how you use them. Examples: 1. Quartus requires a generate-for loop to have a named begin-end block
// attached (in this example, named "my_block_name"). 2. Inside the loop body, genvars are read only.
generate
genvar i;
for (i=0; i<8; i = i+1) begin: my_block_name
assign out[i] = in[8-i-1];
end
endgenerate
*/

endmodule

重复操作

声明语法:

1
{num{vector}}

示例

1
2
3
4
{5{1'b1}}           // 5'b11111 (or 5'd31 or 5'h1f)
{2{a,b,c}} // The same as {a,b,c,a,b,c}
{3'd5, {2{3'd6}}} // 9'b101_110_110. It's a concatenation of 101 with
// the second vector, which is two copies of 3'b110.

将8位数据扩展为32位,高24位为符号位(bit[7]),余下8位为输入

1
2
3
4
5
6
7
8
9
10
11
module top_module (
input [7:0] in,
output [31:0] out
);

// Concatenate two things together:
// 1: {in[7]} repeated 24 times (24 bits)
// 2: in[7:0] (8 bits)
assign out = { {24{in[7]}}, in };

endmodule

描述电路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module top_module (
input a, b, c, d, e,
output [24:0] out
);

wire [24:0] top, bottom;
assign top = { {5{a}}, {5{b}}, {5{c}}, {5{d}}, {5{e}} };
assign bottom = {5{a,b,c,d,e}};
assign out = ~top ^ bottom; // Bitwise XNOR

// This could be done on one line:
// assign out = ~{ {5{a}}, {5{b}}, {5{c}}, {5{d}}, {5{e}} } ^ {5{a,b,c,d,e}};

endmodule

模块Module

基本使用

基本语法

1
2
3
module mod_a ( input in1, input in2, output out );
// Module body
endmodule

线与模块端口连接有两种方式:通过位置和通过名称

通过位置连接,模块实例instance1三个端口分别连接线wa, wb, wc

1
mod_a instance1 ( wa, wb, wc );

通过名称连接,模块实例instance2三个端口名称与对应线连接:

1
mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );

描述电路,其中模块mod_a在其他地方描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module top_module (
input a,
input b,
output out
);

// Create an instance of "mod_a" named "inst1", and connect ports by name:
mod_a inst1 (
.in1(a), // Port"in1"connects to wire "a"
.in2(b), // Port "in2" connects to wire "b"
.out(out) // Port "out" connects to wire "out"
// (Note: mod_a's port "out" is not related to top_module's wire "out".
// It is simply coincidence that they have the same name)
);

/*
// Create an instance of "mod_a" named "inst2", and connect ports by position:
mod_a inst2 ( a, b, out ); // The three wires are connected to ports in1, in2, and out, respectively.
*/

endmodule

移位模块

给已经实现的D触发器(D flip-flop)模块module my_dff ( input clk, input d, output q );,按下图描述电路

1
2
3
4
5
6
7
8
module top_module ( input clk, input d, output q );
wire q1,q2;

my_dff d1 (clk, d, q1);
my_dff d2 (clk, q1, q2);
my_dff d3 (clk, q2, q);
endmodule

8位移位器

描绘电路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module top_module (
input clk,
input [7:0] d,
input [1:0] sel,
output reg [7:0] q
);

wire [7:0] o1, o2, o3; // output of each my_dff8

// Instantiate three my_dff8s
my_dff8 d1 ( clk, d, o1 );
my_dff8 d2 ( clk, o1, o2 );
my_dff8 d3 ( clk, o2, o3 );

// This is one way to make a 4-to-1 multiplexer
always @(*) begin // Combinational always block
case(sel)
2'h0: q = d;
2'h1: q = o1;
2'h2: q = o2;
2'h3: q = o3;
endcase
end
endmodule

全加器

add16全加器已经定义module add16 ( input[15:0] **a**, input[15:0] **b**, input **cin**, output[15:0] **sum**, output **cout** );,它包含16个add1全加器,描述电路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module top_module (
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire o1,o2;

add16 inst1(a[15:0],b[15:0],0,sum[15:0],o1);
add16 inst2(a[31:16],b[31:16],o1,sum[31:16],o2);
endmodule

module add1 ( input a, input b, input cin, output sum, output cout );
// Full adder module here
assign sum = cin ? ~(a^b) : a^b;
assign cout = cin ? a|b : a&b;
endmodule

加减器

减法器由全加器变化而来,只需将对第二个操作数取补码即可(反码加一)。等效的电路可以有两种效果:a+b+0a+~b+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);

wire o1,o2;
wire [31:0] b1 = b^{32{sub}};

add16 inst1(a[15:0],b1[15:0],sub,sum[15:0],o1);
add16 inst2(a[31:16],b1[31:16],o1,sum[31:16],o2);
endmodule

过程Procedure

对于综合硬件,有两种相关always块:

  • 组合型:always @(*)
  • 时序型:always @(posedge clk)

块语句需要使用beginend标记出来,若语句只有“单句”时,块标记可以省略。

Always块

对于简单的场景,组合型always块等价于assign语句。

1
2
assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;

assign语句左边符号对应的类型是net(例如:wire),而过程赋值对应的类型是variable(例如:reg)。两者的差异不会对综合的电路产生任何影响,只是一种不成文的约定。

在VHDL中有三种赋值类型:

  • 持续赋值assign x = y;
  • 过程阻塞赋值,在过程中使用x = y;
  • 过程非阻塞赋值,在过程中使用x <= y;

在组合alsways块中,使用阻塞赋值。在时序always块中,使用非阻塞赋值。

使用三种方式,用异或门构建电路。值得注意的是,第三种方式产生的结果会因为有触发器而产生延时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// synthesis verilog_input_version verilog_2001
module top_module(
input clk,
input a,
input b,
output wire out_assign,
output reg out_always_comb,
output reg out_always_ff );

assign out_assign = a^b;
always @(*) out_always_comb = a^b;
always @(posedge clk) out_always_ff <= a^b;
endmodule

if语句

描绘一个2-1选择器

对应的值表

sel_b1sel_b2out_assign out_always
00a
01a
10a
11b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
input sel_b1,
input sel_b2,
output wire out_assign,
output reg out_always );

assign out_assign = sel_b1&sel_b2 ? b : a;

always @(*) begin
if (sel_b1&sel_b2) begin
out_always = b;
end
else begin
out_always = a;
end
end
endmodule

if语句锁存

修复下图错误,当cpu_overheated时,产生关闭信号(shut_off_computer设置为1)。

修改前,错误的示意图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// synthesis verilog_input_version verilog_2001
module top_module (
input cpu_overheated,
output reg shut_off_computer,
input arrived,
input gas_tank_empty,
output reg keep_driving ); //

always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
end

always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
end

endmodule

修改后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// synthesis verilog_input_version verilog_2001
module top_module (
input cpu_overheated,
output reg shut_off_computer,
input arrived,
input gas_tank_empty,
output reg keep_driving ); //

always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
else
shut_off_computer = 0;
end

always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
else
keep_driving = 0;
end

endmodule

case语句

基本语法

1
2
3
4
5
6
case (cond)
2'h0: statments0;
2'h1: statments1;
...
default: statmentsx;
endcase

建立一个6-1选择器,否则输出0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// synthesis verilog_input_version verilog_2001
module top_module (
input [2:0] sel,
input [3:0] data0,
input [3:0] data1,
input [3:0] data2,
input [3:0] data3,
input [3:0] data4,
input [3:0] data5,
output reg [3:0] out );//

always@(*) begin // This is a combinational circuit
case(sel)
4'h0: out = data0;
4'h1: out = data1;
4'h2: out = data2;
4'h3: out = data3;
4'h4: out = data4;
4'h5: out = data5;
default: out = 0;
endcase
end

endmodule

构建一个4bits的优先编码器,输出第一个1所在的比特位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module top_module (
input [3:0] in,
output reg [1:0] pos
);

always @(*) begin // Combinational always block
case (in)
4'h0: pos = 2'h0; // I like hexadecimal because it saves typing.
4'h1: pos = 2'h0;
4'h2: pos = 2'h1;
4'h3: pos = 2'h0;
4'h4: pos = 2'h2;
4'h5: pos = 2'h0;
4'h6: pos = 2'h1;
4'h7: pos = 2'h0;
4'h8: pos = 2'h3;
4'h9: pos = 2'h0;
4'ha: pos = 2'h1;
4'hb: pos = 2'h0;
4'hc: pos = 2'h2;
4'hd: pos = 2'h0;
4'he: pos = 2'h1;
4'hf: pos = 2'h0;
default: pos = 2'b0; // Default case is not strictly necessary because all 16 combinations are covered.
endcase
end

// There is an easier way to code this. See the next problem (always_casez).

endmodule

casez

当部分比特位不关心时,可以使用casez。符号z?在一下场景都是等价的。与casez类似的是casex,后者使用符号x

1
2
3
4
5
6
7
8
9
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // in[3:1] can be anything
4'bzz1z: out = 1;
4'b?1??: out = 2;
4'b1???: out = 3;
default: out = 0;
endcase
end

构建8bits优先编码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// synthesis verilog_input_version verilog_2001
module top_module (
input [7:0] in,
output reg [2:0] pos );

always @(*) begin
casez (in[7:0])
8'h0: pos = 0;
8'bzzzz_???1: pos = 0;
8'bzzzz_zz10: pos = 1;
8'bzzzz_z100: pos = 2;
8'bzzzz_1000: pos = 3;
8'bzzz1_0000: pos = 4;
8'bzz10_0000: pos = 5;
8'bz100_0000: pos = 6;
8'b1000_0000: pos = 7;
default: pos = 3'bzzz;
endcase
end
endmodule

键盘扫描码

处理如下四个按键

Scancode [15:0]Arrow key
16'he06bleft arrow
16'he072down arrow
16'he074right arrow
16'he075up arrow
Anything elsenone
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// synthesis verilog_input_version verilog_2001
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up );

always @(*) begin
left = 0; down = 0; right = 0; up = 0;
case (scancode)
16'he06b: left = 1;
16'he072: down = 1;
16'he074: right = 1;
16'he075: up = 1;
default: ;
endcase
end
endmodule

更多特性

条件

三元条件操作符?:

1
(condition ? if_true : if_false)

逻辑操作简化

若需要对向量所有位进行门操作时,正常书写比较冗余,可以对与、或和异或操作简化

1
2
3
& a[3:0]     // AND: a[3]&a[2]&a[1]&a[0]. Equivalent to (a[3:0] == 4'hf)
| b[3:0] // OR: b[3]|b[2]|b[1]|b[0]. Equivalent to (b[3:0] != 4'h0)
^ c[2:0] // XOR: c[2]^c[1]^c[0]

实现奇偶校验位算法

1
2
3
4
5
6
7
module top_module (
input [7:0] in,
output parity);

assign parity = ^in[7:0];
endmodule

100bits翻转

1
2
3
4
5
6
7
8
9
10
11
module top_module (
input [99:0] in,
output reg [99:0] out
);

always @(*) begin
for (int i=0;i<$bits(out);i++) // $bits() is a system function that returns the width of a signal.
out[i] = in[$bits(out)-i-1]; // $bits(out) is 100 because out is 100 bits wide.
end

endmodule

比特1计数

1
2
3
4
5
6
7
8
9
10
11
module top_module( 
input [254:0] in,
output [7:0] out );

always @(*) begin // Combinational always block
out = 0;
for (int i=0; i<$bits(in); i++)
out += in[i];
end
endmodule

100bits全加器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module top_module( 
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );

genvar i;
generate
for (i=0; i<$bits(a); ++i) begin: adder_gen
adder_1bit u_adder (
.a(a[i]),
.b(b[i]),
.cin(i==0?cin:cout[i-1]),
.sum(sum[i]),
.cout(cout[i])
);
end
endgenerate

endmodule

module adder_1bit (
input a,b,
input cin,
output cout,
output sum
);
assign {cout,sum} = a+b+cin;
// assign sum = cin ? ~(a^b) : a^b;
// assign cout = cin ? a|b : a&b;
endmodule

100位BCD码全加器

已经定义1位BCD码全加器

1
2
3
4
5
6
module bcd_fadd (
input [3:0] a,
input [3:0] b,
input cin,
output cout,
output [3:0] sum );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module top_module( 
input [399:0] a, b,
input cin,
output cout,
output [399:0] sum );

wire [100:0] c;
assign cout = c[100];
assign c[0] = cin;

genvar i;
generate
for(i=0; i<100; ++i) begin: bcd_gen
bcd_fadd u_bcd (
.a( a[(4*i+3):(4*i)] ),
.b( b[(4*i+3):(4*i)] ),
.cin( c[i] ),
.cout( c[i+1] ),
.sum( sum[(4*i+3):(4*i)] )
);
end
endgenerate

endmodule

仿真

timescale

定义仿真中时间单位的比例。它的作用是指定时序仿真中时间单位的大小,以便仿真器可以正确地模拟设计中的时序行为。

基本语法

1
`timescale unit / precision

其中,unit是时间单位,可以是1ns1ps1us等,表示一个时钟周期的时间长度;precision是时间精度,表示仿真的最小时间单位;

#num

等待num个时间单位后,执行后面的语句。

基本语法

1
#num statement

task

Verilog语言中具有类似C语言函数的结构有task和function,他们可以增加代码可读性和重复使用性。Function用来描述组合逻辑,只能有一个返回值,function的内部不能包含时序控制。Task类似procedure,执行一段verilog代码,task中可以有任意数量的输入和输出,task也可以包含时序控制。

基本语法

1
2
task TASK_NAME;
endtask

PS/2键盘设备仿真

当用户按键或松开时,键盘以每帧11位的格式串行传送数据给主机,同时在PS2_CLK时钟信号上传输对应的时钟(一般为10.0–16.7kHz)。第一位是开始位(逻辑0),后面跟8位数据位(低位在前),一个奇偶校验位(奇校验)和一位停止位(逻辑1)。每位都在时钟的 下降沿 有效,下图显示了键盘传送一字节数据的时序。在下降沿有效的主要原因是下降沿正好在数据位的中间,因此可以让数据位从开始变化到接收采样时能有一段信号建立时间。

键盘输出数据时序图

键盘通过PS2_DAT引脚发送的信息称为扫描码,每个扫描码可以由单个数据帧或连续多个数据帧构成。当按键被按下时送出的扫描码被称为 通码(Make Code) ,当按键被释放时送出的扫描码称为 断码(Break Code) 。以 W 键为例, W 键的通码是1Dh,如果 W 键被按下,则PS2_DAT引脚将输出一帧数据,其中的8位数据位为1Dh,如果 W 键一直没有释放,则不断输出扫描码1Dh 1Dh … 1Dh,直到有其他键按下或者 W 键被放开。某按键的断码是F0h加此按键的通码,如释放 W 键时输出的断码为F0h 1Dh,分两帧传输。

多个键被同时按下时,将逐个输出扫描码,如:先按左 Shift 键(扫描码为12h)、再按 W 键、放开 W 键、再放开左 Shift 键,则此过程送出的全部扫描码为:12h 1Dh F0h 1Dh F0h 12h。

键盘扫描码

每个键都有唯一的通码和断码。键盘所有键的扫描码组成的集合称为扫描码集。共有三套标准的扫描码集,所有现代的键盘默认使用第二套扫描码。下图显示了键盘各键的扫描码(以十六进制表示),如Caps键的扫描码是58h。 下图可以看出,键盘上各按键的扫描码是随机排列的,如果想迅速的将键盘扫描码转换为ASCII码,一个最简单的方法就是利用查找表 LookUp Table, LUT ,扫描码到ASCII码的转换表格请读者自己生成。

键盘扫描码
扩展键盘和数字键盘的扫描码

键盘控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
module ps2_keyboard(clk,clrn,ps2_clk,ps2_data,data,
ready,nextdata_n,overflow);
input clk,clrn,ps2_clk,ps2_data;
input nextdata_n;
output [7:0] data;
output reg ready;
output reg overflow; // fifo overflow
// internal signal, for test
reg [9:0] buffer; // ps2_data bits
reg [7:0] fifo[7:0]; // data fifo
reg [2:0] w_ptr,r_ptr; // fifo write and read pointers
reg [3:0] count; // count ps2_data bits
// detect falling edge of ps2_clk
reg [2:0] ps2_clk_sync;

always @(posedge clk) begin
ps2_clk_sync <= {ps2_clk_sync[1:0],ps2_clk};
end

wire sampling = ps2_clk_sync[2] & ~ps2_clk_sync[1];

always @(posedge clk) begin
if (clrn == 0) begin // reset
count <= 0; w_ptr <= 0; r_ptr <= 0; overflow <= 0; ready<= 0;
end
else begin
if ( ready ) begin // read to output next data
if(nextdata_n == 1'b0) //read next data
begin
r_ptr <= r_ptr + 3'b1;
if(w_ptr==(r_ptr+1'b1)) //empty
ready <= 1'b0;
end
end
if (sampling) begin
if (count == 4'd10) begin
if ((buffer[0] == 0) && // start bit
(ps2_data) && // stop bit
(^buffer[9:1])) begin // odd parity
fifo[w_ptr] <= buffer[8:1]; // kbd scan code
w_ptr <= w_ptr+3'b1;
ready <= 1'b1;
overflow <= overflow | (r_ptr == (w_ptr + 3'b1));
end
count <= 0; // for next
end else begin
buffer[count] <= ps2_data; // store ps2_data
count <= count + 3'b1;
end
end
end
end
assign data = fifo[r_ptr]; //always set output data

endmodule

键盘仿真模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
`timescale 1ns / 1ps
module ps2_keyboard_model(
output reg ps2_clk,
output reg ps2_data
);
parameter [31:0] kbd_clk_period = 60;
initial ps2_clk = 1'b1;

task kbd_sendcode;
input [7:0] code; // key to be sent
integer i;

reg[10:0] send_buffer;
begin
send_buffer[0] = 1'b0; // start bit
send_buffer[8:1] = code; // code
send_buffer[9] = ~(^code); // odd parity bit
send_buffer[10] = 1'b1; // stop bit
i = 0;
while( i < 11) begin
// set kbd_data
ps2_data = send_buffer[i];
#(kbd_clk_period/2) ps2_clk = 1'b0;
#(kbd_clk_period/2) ps2_clk = 1'b1;
i = i + 1;
end
end
endtask

endmodule

键盘测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
`timescale 1ns / 1ps
module keyboard_sim;

/* parameter */
parameter [31:0] clock_period = 10;

/* ps2_keyboard interface signals */
reg clk,clrn;
wire [7:0] data;
wire ready,overflow;
wire kbd_clk, kbd_data;
reg nextdata_n;

ps2_keyboard_model model(
.ps2_clk(kbd_clk),
.ps2_data(kbd_data)
);

ps2_keyboard inst(
.clk(clk),
.clrn(clrn),
.ps2_clk(kbd_clk),
.ps2_data(kbd_data),
.data(data),
.ready(ready),
.nextdata_n(nextdata_n),
.overflow(overflow)
);

initial begin /* clock driver */
clk = 0;
forever
#(clock_period/2) clk = ~clk;
end

initial begin
clrn = 1'b0; #20;
clrn = 1'b1; #20;
model.kbd_sendcode(8'h1C); // press 'A'
#20 nextdata_n =1'b0; #20 nextdata_n =1'b1;//read data
model.kbd_sendcode(8'hF0); // break code
#20 nextdata_n =1'b0; #20 nextdata_n =1'b1; //read data
model.kbd_sendcode(8'h1C); // release 'A'
#20 nextdata_n =1'b0; #20 nextdata_n =1'b1; //read data
model.kbd_sendcode(8'h1B); // press 'S'
#20 model.kbd_sendcode(8'h1B); // keep pressing 'S'
#20 model.kbd_sendcode(8'h1B); // keep pressing 'S'
model.kbd_sendcode(8'hF0); // break code
model.kbd_sendcode(8'h1B); // release 'S'
#20;
$stop;
end

endmodule

电路

组合逻辑

基本门

GND

1
assign out = 1'b0;

NOR

1
assign out = ~(in1|in2);

多路选择器

9路选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module top_module( 
input [15:0] a, b, c, d, e, f, g, h, i,
input [3:0] sel,
output [15:0] out );

// Case statements can only be used inside procedural blocks (always block)
// This is a combinational circuit, so use a combinational always @(*) block.
always @(*) begin
out = '1; // '1 is a special literal syntax for a number with all bits set to 1.
// '0, 'x, and 'z are also valid.
// I prefer to assign a default value to 'out' instead of using a
// default case.
case (sel)
4'h0: out = a;
4'h1: out = b;
4'h2: out = c;
4'h3: out = d;
4'h4: out = e;
4'h5: out = f;
4'h6: out = g;
4'h7: out = h;
4'h8: out = i;
endcase
end
endmodule

4位256路选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module top_module (
input [1023:0] in,
input [7:0] sel,
output [3:0] out
);

// We can't part-select multiple bits without an error, but we can select one bit at a time,
// four times, then concatenate them together.
assign out = {in[sel*4+3], in[sel*4+2], in[sel*4+1], in[sel*4+0]};

// Alternatively, "indexed vector part select" works better, but has an unfamiliar syntax:
// assign out = in[sel*4 +: 4]; // Select starting at index "sel*4", then select a total width of 4 bits with increasing (+:) index number.
// assign out = in[sel*4+3 -: 4]; // Select starting at index "sel*4+3", then select a total width of 4 bits with decreasing (-:) index number.
// Note: The width (4 in this case) must be constant.

endmodule