You are on page 1of 14

CONTENTS

BÀI 1: Hiển thị chữ và số bằng led 7 đoạn ............................................................................................. 4


BÀI 2: Counter đếm thời gian ................................................................................................................. 4
BÀI 3: Bộ giải mã BCD 8-bit ................................................................................................................... 5
BÀI 4: Mạch cộng .................................................................................................................................... 6
BÀI 5: Finite State Machine (FSM)........................................................................................................ 9
BÀI 6: Đồng hồ ...................................................................................................................................... 13
BÀI 7: Lọc Trung Vị (Median) .............................................................................................................. 13

1
PHỤ LỤC
DESIGN RULES (bắt buộc):

1. If là phải có else. Case là phải đủ trường hợp. Case mà không đủ trường hợp thì phải có default.
2. Không dùng ký hiệu timing ‘#’ trong code (trừ testbench).
3. Khi khai báo reg không được gán giá trị đầu. Muốn gì thì reset.
4. Khi dùng always@() cho một mạch combinaltional logic thì phải viết là always@(*).
5. Khi dùng always@() cho một mạch sequential logic thì phải dùng ở cạnh. Tức phải viết là
always@(posedge hoặc negedge gì đấy).
6. Không dùng vừa mức vừa cạnh trong always@().
7. Không viết các tín hiệu dư (khai báo mà không dùng). Hoặc viết dư kiểu như: If(A==1’b1) thì
phải sửa lại thành If(A) hết.
8. Nên viết tách biệt mỗi một biến reg một vòng always@() độc lập.
9. Khi viết always@(posdege hay negedge) tức là khai báo sequential logic thì trong vòng điều kiện
always@() chỉ được phép có Clock và có thể có thêm Reset, không được có tín hiệu nào khác
ngoài 2 tín hiệu đó.
10. Trong always@(posedge hay negedge) của sequential thì dùng dấu <= ; còn trong assign và
always@(*) của combinational thì dùng dấu = ; khái niệm blocking ‘=’ hay non-blocking ‘<=’
vứt đi. Cứ hễ sequential thì dùng dấu <= còn combinational thì dùng dấu =. Thế thôi.
11. Đã dùng phép shift được thì dùng phép gán { } được. Nên chỉ được dùng gán { } mà không được
dùng shift.
12. Khi set/reset trong Flip-Flop thì tín hiệu chỉ có hoặc 0 hết hoặc 1 hết, không có chuyện set/reset
về một giá trị lưng chừng.

PHÂN VÙNG CODE (khuyên chứ không bắt buộc):


Tất cả các khai báo cùng loại gom lại chung một khu, tránh assign rồi always@ rồi gọi module con lẫn
lộn vào nhau. Vì thiết kế phần cứng là coding song song, không quan trọng dòng code trước dòng code
sau, nên tất cả nên được phân loại và sắp xếp lại theo thứ tự cho dễ đọc. Thứ tự được đề nghị:
1. Khai báo parameter.
2. Khai báo port in/out.
3. Khai báo wire và reg.
4. Combinational logic.
5. Sequential logic.
6. Finite State Machine.
7. Gọi module con.
Ngoài ra, khi khai báo port in/out hay biến reg/wire cũng nên khai báo mỗi cái 1 dòng độc lập.

(cái này thì bắt buộc) Phải đặt tên mỗi khi gọi một module con. Để cho dễ quản lý thì một file chỉ được
viết một module duy nhất mà thôi, và tên module này cũng chính là tên file.

2
QUY CÁCH ĐẶT TÊN:
Đừng đặt tên theo kiểu tối nghĩa: a,b,c,x,y,z,… Chịu khó đặt tên dài một chút nhưng mang ý nghĩa thì tốt
hơn. Ngoài ra còn có các luật đặt tên sau đây:
1. Tất cả các port in/out đều phải có chữ ‘i’ nhỏ ở trước nếu là input, và chữ ‘o’ nhỏ ở trước nếu là
output.
2. Ngoài port in/out đó ra thì các tín hiệu nội reg và wire không được phép đặt tên có chữ ‘i’ hay ‘o’
ở trước tên kiểu này.
3. Một tín hiệu tích cực mức thấp thì phải có chữ ‘_n’ ở sau cùng (ví dụ Reset_n hay Enable_n), nếu
không có chữ ‘_n’ ở sau cùng thì tín hiệu đó phải là tín hiệu tích cực mức cao.
4. Không đặt tên có số hay ký tự đặc biệt ở đầu, vì tool thường sẽ không hiểu đó là tên.

Xem thêm reference.v để tham khảo cách phân vùng code cũng như cách đặt tên.

3
BÀI 1: Hiển thị chữ và số bằng led 7 đoạn
Yêu cầu đề:

Dùng các led 7 đoạn trên board FPGA để hiển thị một cụm từ. Tuy nhiên, do hạn chế của led 7 đoạn
không hiển thị được đủ bảng chữ cái nên hãy chọn một cụm từ nào mình thích mà hiển thị được
(recommended tên và ngày tháng năm sinh của mình).

Hãy chọn ra 2 cụm từ, ví dụ tên và ngày tháng năm sinh. Hai cụm từ này sẽ không hiển thị cùng lúc trên
các led 7 đoạn mà phụ thuộc vào trạng thái của một switch nào đó, ví dụ switch 0. Khi switch 0 ở trạng
thái off “0” thì sẽ hiển thị tên, khi switch ở trạng thái on “1” thì sẽ hiển thị ngày tháng năm sinh.

BÀI 2: Counter đếm thời gian


Yêu cầu đề:

Kế thừa việc hiển thị tên và ngày tháng năm sinh từ bài 2. Ở bài này ta không dùng switch để đổi chế độ
hiển thị nữa, ta gắn tên và ngày tháng năm sinh thành một chuỗi dài, sau đó cho toàn bộ chuỗi đó dịch
sang trái tự động với tốc độ 0.5 giây mỗi lần nhảy led.

Hướng dẫn:

Đầu tiên ta phải tạo ra clock chuẩn 0.5 giây bằng cách dùng counter đếm thời gian. Sau đó dùng clock 0.5
giây này để dịch toàn bộ các led đang chứa thông tin về phía bên trái. Việc trên board FPGA đang có bao
nhiêu con led 7 đoạn không quan trọng nữa, dòng chữ này sẽ chạy sang trái liên tục, là một vòng lặp tên –
ngày sinh – tên – ngày sinh – v.v..., giữa tên và ngày sinh có một khoảng trắng (led tắt).

4
BÀI 3: Bộ giải mã BCD 8-bit
Yêu cầu đề:

Phân giải ngõ vào từ một số 8-bit có giá trị [0 255] thành một bộ 3 tín hiệu BCD có giá trị [0 9]. Ví dụ số
iBianry = 8’hFD trở thành oDec_tram = 2’d2; oDec_chuc = 4’d5; oDec_donvi = 4’d2.

Hướng dẫn: bộ 4, dịch trái 1, lớn hơn hay bằng 5 thì cộng 3 còn không giữ nguyên

Ví dụ chuyển một số 8’b1001_0111

Trăm Chục Đơn vị 10010111


1 0010111
10 010111
100 10111
1001 0111
1100 0111
1 1000 111
1 1011 111
11 0111 11
11 1010 11
111 0101 1
1010 1000 1
1 0101 0001
1 5 1
8’b1001_0111 = 8’d151

Bắt đầu với 3 cột Trăm, Chục, Đơn vị như trên, mỗi cột chỉ chứa 4 bits; và một cột ngoài cùng bên phải
chứa số nhị phân gốc. Theo nguyên lý:

1. Dịch trái 1 bit


2. Xét trong 3 cột Trăm, Chục, Đơn vị 4-bit có giá trị lớn hơn hay bằng 5 hay không, nếu có thì
cộng giá trị đó cho 3 còn không thì giữ nguyên giá trị
3. Xét trong cột nhị phân gốc đã dịch hết hay chưa, nếu còn thì quay lại bước 1 còn không thì dừng
Mở rộng: nếu đầu vào là một số 10-bit thì giá trị có thể lên đến 1023, lúc này ta có thêm cột Ngàn nữa.
Thuật toán được sử dụng là tương tự cho n-bit.

5
BÀI 4: Mạch cộng
Nói về mạch cộng thì chúng ta hay nói đến mạch cộng Carry-Look-Ahead (CLA). Tuy nhiên, CLA nổi
tiếng là vì nó đạt được sự cân bằng giữa tài nguyên và tốc độ. Chứ nếu nói về vua của tốc độ trong các
phương pháp thiết kế mạch cộng thì phải nói đến Carry-Select-Adder (CSel). Bây giờ chúng ta sẽ điểm
qua 3 loại kiến trúc mạch cộng cơ bản nhất:

Ripple-Carry Adder:

Đơn giản nhất, cơ bản nhất. Tất cả chỉ là Carry-out của bit trước đấu vào Carry-in của bit sau. Đây là kiến
trúc mà bất kỳ ai học về điện tử đều thấy từ những bài giảng đầu tiên. Tuy nhiên, đừng coi thường nó,
mình học nó đầu tiên là vì nó dễ nhất và dễ dạy nhất thôi, chứ không có nghĩa nó là dở nhất.

Ripple-Carry Adder là kiến trúc cho delay cao nhất nhưng cũng là mạch tiết kiệm tài nguyên nhất. Do đó,
trong các ứng dụng không đòi hỏi về tốc độ tính toán thì người ta vẫn dùng Ripple-Carry để tiết kiệm tài
nguyên. Kiến trúc này cho đến giờ và có lẽ mãi về sau vẫn tiếp tục được sử dụng rộng rãi trong ngành
điện tử.

Carry-Select Adder (CSel):

Kiến trúc của nó cũng rất đơn giản nhưng rất hiệu quả. Như có thể thấy trong hình ta có 2 bộ Ripple-
Carry Adder 4-bit. Ý tưởng ở đây chỉ đơn giản là thay vì chờ cờ Carry-in vào rồi nó mới tính, thì ở đây nó
đặt sẵn 2 trường hợp cờ Carry-in = 0 và = 1 rồi nó tính hết cho cả 2 trường hợp luôn. Để khi Carry-in thật
đi vào chỉ là chọn (select) ra kết quả tương ứng mà thôi.

6
Như vậy thì suy nghĩ thử xem, nếu cộng càng nhiều bit thì hóa ra delay tổng của mạch chỉ ngang với
delay bộ mux thôi hay sao? Còn delay của các Full/Half-Adder đã được hóa giải. Đây chính là kiến trúc
có tốc độ cộng cao nhất. Nhưng bù lại cũng là kiến trúc tốn tài nguyên nhiều nhất (gần gấp đôi tài nguyên
Ripple-Carry).

Hình sau đây là ví dụ đầy đủ cho một bộ cộng CSel 16-bit.

Tuy nhiên, để đạt được tốc độ tối đa thì thường người ta cũng không dùng hình trên, mà sẽ dùng hình bên
dưới. Tỉ lệ chia 2-2-3-4-5 cho tốc độ tốt hơn là chia chuẩn 4-4-4-4.

Carry-Look-Ahead (CLA):

CLA thì đạt trạng thái cân bằng giữa Ripple-Carry và Carry-Select: tốc độ cao và tài nguyên tương đối ít.
Nếu sắp xếp về mặt tốn tài nguyên từ thấp đến cao: Ripple-Carry, CLA, CSel. Nếu sắp xếp về mặt tốc độ
từ cao đến thấp: CSel, CLA, Ripple-Carry.

Về mặt kiến trúc của CLA thì hơi phức tạp hơn chút và liên quan đến toán học. Cho nên ở đây sẽ không
giải thích dài dòng. Hình sau đây là bộ 4-bit Carry Look-ahead Adder (CLA).

7
Từ bộ 4-bit CLA xây dựng có tối ưu ra bộ 8-bit CLA.

Yêu cầu đề:

Thiết kế mạch cộng 2 số 8-bit, sử dụng 18 switch và 3 led 7 đoạn. Hai số 8-bit chiếm 16 switch. Còn lại 2
switch để chỉnh chế độ hiển thị ra 3 led 7 đoạn: SW=“00” hiển thị số A, SW=”01” hiển thị số B,
SW=”1x” hiển thị kết quả phép cộng. Dùng 3 con led 7 đoạn để hiển thị từ 0 đến 255, nghĩa là ta phải
dùng module BCD ở bài trên để chuyển 8-bit nhị phân sang dạng số BCD thập phân. Rồi một module nhỏ
để chuyển 4-bit BCD từ ‘0’ đến ‘9’ đó hiển thị ra các con led 7 đoạn nữa. Có thể tham khảo trong hình
bên dưới.

8
Thử nghiệm cả hai lõi cộng CLA và CSel (CSel lấy tỉ lệ 2-3-3).

BÀI 5: Finite State Machine (FSM)


Yêu cầu đề:

Tạo ra một FSM để điều khiển đèn 1 LED chơp tắt theo mã Morse. Mã Morse chỉ dùng hai ký tự : dấu
chấm (dot) và dấu gạch (dash). Khi thể hiện dot thì LED sáng ngắn (0,5s) và để thể hiện dash thì LED
sáng dài (1,5s), trạng thái mặc định (idle) thì led sẽ tắt, khi chuyển giữa dot-dot, dot-dash, dash-dot, dash-
dash thì có giai đoạn nghỉ (tắt 0,5s). Sau đây là quy ước 8 ký tự đầu tiên trong mã Morse:

Ví dụ để thể hiện chữ A thì đèn LED sẽ: dot (sáng 0,5s) – nghỉ (tắt 0,5s) – dash (sáng 1,5s) – idle (tắt
luôn). Ví dụ để thể hiện chữ C thì đèn LED sẽ : dash (sáng 1,5s) – nghỉ (tắt 0,5s) – dot (sáng 0,5s) – nghỉ
(tắt 0,5s) – dash (sáng 1,5s) – nghỉ (tắt 0,5s) – dot (sáng 0,5s) – idle (tắt luôn).

9
Với ngõ vào in[3:0] để chọn ra chữ nào muốn hiển thị từ A đến H (3’b000 tương ứng với chọn chữ A và
3’b111 tương ứng với chọn chữ H). oLed bằng 1 tương ứng đèn sáng và bằng 0 tương ứng đèn tắt, mặc
định ban đầu là đèn tắt, và sau khi hiển thị xong mã thì đèn cũng tắt.

Miêu tả cách hoạt động: ta sẽ dùng 5 switch, 4 switch cho 4-bit “in[3:0]” và 1 switch làm reset “iRst”

1. Ta sẽ dùng 4-bit switch để chọn chữ tương ứng muốn hiển thị.
2. Sau khi chọn xong thì ta sẽ hạ switch reset xuống để mạch hoạt động.
3. Sau khi led chớp tắt xong thì ta gạt switch reset lên để reset mạch.
4. Muốn hiển thị chữ khác thì ta tiếp tục lặp lại bước 1.

Hướng dẫn:

Viết FSM bằng cách dùng lệnh case, và quản lý trạng thái bằng biến State. Có hai cách viết FSM: cách
dùng 1 always và cách dùng 2 always.

// Ví dụ sử dụng 1 vòng always

// Ví dụ ngoài biến State ra ta dùng thêm 2 biến A và B


reg [1:0] State;
reg [7:0] A, B;

always@(posedge iClk)
begin
if(iRst)
begin
// tại đây reset tất cả các biến sẽ dùng trong always
// biến State và A B được reset
State <= 2'b0;
A <= 8'b0;
B <= 8'b0;
end
else
begin
case(State)
2'b00:
begin
// làm các việc trong trạng thái
// cho nhảy trạng thái bằng cách gán State <= 2’bxx
end
2'b01:
begin
// ví dụ
if(Sel)
State <= 2'b10;
else
State <= 2'b00;
// trong State này ví dụ biến A B không xài
// nghĩa là A B sẽ giữ nguyên giá trị trong State này
// thì ta vẫn phải gán cho nó như sau chứ không phải không gán
A <= A;
B <= B;

10
*mấu chốt của FSM là tất cả các biến đều phải được gán trong mọi trường hợp
case-if-else. Nếu biến đó không sử dụng thì gán bằng chính nó.
end
2'b10:
begin
...
end
default:
begin
// Nếu không xài hết các trạng thái của FSM thì cuối cùng phải có default
// ngược lại, nếu xài hết các trạng thái của FSM thì đừng có thêm default
// đừng có hình thành thói quen cái gì cũng add default

// trong default sẽ viết tất cả các biến reg và gán bằng chính nó
State <= State;
A <= A;
B <= B;
end
endcase
end
end

// Ví dụ sử dụng 2 vòng always


// thực chất nó bao gồm 1 always sequential cho FSM
// 1 always combinational cho FSM
// và 1 always sequential cho các tín hiệu output còn lại
// cái tên gọi 2 vòng always là do FSM nó tách ra làm 2 phần riêng biệt

reg [1:0] State; // biến cho phần sequen FSM


reg [1:0] nextState; // biến cho phần combi FSM
reg [7:0] A, B; // hai biến reg còn lại

// vòng always sequential cho FSM


// cái này là format luôn rồi, cứ y như vậy mà viết thôi
always@(posedge iClk)
begin
if(iRst)
State <= 2'b00;
else
State <= nextState;
end

// vòng always combinational cho FSM


always@(*)
begin
case(State)
2'b00:
begin
// có 2 điều lưu ý trong vòng always này:
// 1. Trong vòng always này chỉ có điều khiển biến nextState mà thôi
// chỉ có nextState = cái gì đó chứ không có gán cái gì = cái gì nữa
// 2. Do đây đang là always combi nên không có chuyện gán bằng chính
// nó. Ví dụ không có nextState = nextState; mà phải = số cụ thể
end
2'b01:

11
begin
// ví dụ
if(Sel)
nextState = 2'b10;
else
nextState = 2'b00;
end
2'b10:
begin
...
end
// Nếu không xài hết các trạng thái của FSM thì cuối cùng phải có default
// ngược lại, nếu xài hết các trạng thái của FSM thì không thêm default
// trong default sẽ viết theo format chuẩn như sau
default: nextState = 2'b00; // không có “nextState = nextState”
endcase
end

// vòng always sequential cho các reg output còn lại


always@(posedge iClk)
begin
if(iRst)
begin
// tương tự, reset lúc đầu cho tất cả các biến reg
A <= 8'b0;
B <= 8'b0;
end
else
begin
case(State)
2'b00:
begin
// thao tác những gì cần điều khiển trong trạng thái này
end
2'b01:
begin
// tương tự nguyên lý như trên: tất cả các biến đều phải được gán
// trong mọi trường hợp case-if-else.
// Nếu biến đó không sử dụng thì gán bằng chính nó.
A <= A;
B <= B;
end
2'b10:
begin
...
end
// Nếu không xài hết các trạng thái của FSM thì cuối cùng phải có default
// ngược lại, nếu xài hết các trạng thái của FSM thì không thêm default
// trong default sẽ viết tất cả các biến reg và gán bằng chính nó
default:
begin
A <= A;
B <= B;
end
endcase
end
end

12
BÀI 6: Đồng hồ
Yêu cầu đề:

Sử dụng 6 con led 7 đoạn để hiển thị giờ:phút:giây.

Hướng dẫn:

Tạo ra clock chuẩn 1 giây làm clock cho hệ thống đếm. Counter đếm giây cộng khi có clock, counter phút
cộng khi counter giây quay về 0, v.v... BCD để hiển thị các counter này ra các led 7 đoạn theo thập phân.

BÀI 7: Lọc Trung Vị (Median)

Hai khái niệm lọc cơ bản: lọc giá trị trung bình (Mean Value) và lọc giá trị trung vị (Median Value). Tìm
trung bình của 9 điểm nghĩa là cộng hết 9 điểm đó lại rồi chia 9. Tìm trung vị của 9 điểm thì trước hết
phải sắp xếp (sort) 9 điểm này lại đã, xong rồi lấy giá trị nằm ở chính giữa của chuỗi sau khi sort.
Tìm trung bình và tìm trung vị đều là tìm giá trị nằm ở giữa làm dại diện cho chuỗi giá trị. Nhưng khác
biệt cơ bản là trung vị là 1 trong 9 giá trị đã có trong chuỗi, còn trung bình có thể là một giá trị không tồn
tại trước đó trong chuỗi.
Ý nghĩa của lọc trung vị chứ không phải lọc trung bình là để kháng nhiễu. Khi ta lấy trung bình chẳng qua
là ta giảm sự ảnh hưởng của nhiễu do lấy một xác xuất lớn nhiều giá trị mà thôi, sự ảnh hưởng của nhiễu
vẫn tác động lên kết quả cuối cùng. Còn nếu là trung vị thì không, nếu trung vị mà bị ảnh hưởng bởi
nhiễu chỉ trừ khi số lượng điểm nhiễu vượt quá 50% tổng số điểm. Mà giả sử nếu có một dữ liệu nào mà
nhiễu vượt quá 50% thì chắc chẳng có thuật toán thần thánh nào xử lý nổi.
Do đó điểm mạnh của lọc trung vị chính là lọc khử nhiễu, yếu điểm của nó là kết quả cuối cùng không
phải là trung bình tuyệt đối mà chỉ là trung bình tương đối.

Yêu cầu đề:

Làm một mạch lọc Median cho 9 giá trị ngõ vào. Các ngõ vào là các số không dấu 8-bit.

Hướng dẫn:

Bây giờ ta thiết kế một mạch so sánh 2 ngõ vào. Với 2 số bất kỳ In1 và In2, module Comp xử lý sao cho
cuối cùng cho ra 2 ngõ ra là BigNum chứa số lớn ở đường trên và SmallNum chứa số nhỏ ở đường dưới.
Kết hợp nhiều module Comp ở trên lại ghép vào cho ra được một mạch lọc trung vị như bên dưới.

13
Hình bên dưới là mạch so sánh chuẩn cho dữ liệu vào là 2 số 4-bit. So sánh cả lớn hơn, nhỏ hơn, và bằng.
Tuy nhiên trong mạch lọc Median này có lẽ ta không cần dùng đủ cả 3 ngõ ra lớn hơn, nhỏ hơn, và bằng
của nó. Hãy dựa theo hình chuẩn này mà tối ưu lại theo cách dùng của mình.

14

You might also like