# 《軟硬體協同設計》期末總整理
### ● 班級姓名座號:國立高雄大學電機系大四 B1095117 黃致衡
### ● 授課老師:林宏益教授
---
---
<p class="text-center">
--- divider line ---
</p>
---
---
# 〈HW1:4-bit full adder〉
## 一、作業內容:
使用fulladder製作出4bits的加法器
## 二、實作步驟:
### 1. 先寫出1bit的全加器 (fulladder.v)
```
module fadd (
a, // I 1-bit : first input
b, // I 1-bit : Second input
cin, // I 1-bit : Carry input
s, // O 1-bit : sum output
cout // O 1-bit : carry output
);
input a, b, cin;
output s, cout;
wire s, co;
assign {cout, s} = a + b + cin;
endmodule // End of Module addbit
```
### 2. 再使用 hierachy 的方式將 1bit 的 module 引用至 top module 中 (fourbits__fulladder.v)
```
`include "fulladder.v"
module add4__explicit (
s, // O 4-bit : Output of the adder
cout, // O 1-bit : Carry output of adder
r1, // I 4-bit : first input
r2, // I 4-bit : second input
ci // O 1-bit : carry input
);
input [3:0] r1;
input [3:0] r2;
input ci;
output [3:0] s;
output cout;
wire [3:0] r1, r2;
wire ci;
wire [3:0] s;
wire cout, c1, c2, c3;
fadd u0 (
.a (r1[0]), // I 1-bit : first input
.b (r2[0]), // I 1-bit : Second input
.cin (ci), // I 1-bit : Carry input
.s (s[0]), // O 1-bit : sum output
.cout (c1) // O 1-bit : carry output
);
fadd u1 (
.a (r1[1]) , // I 1-bit : first input
.b (r2[1]) , // I 1-bit : Second input
.cin (c1) , // I 1-bit : Carry input
.s (s[1]) , // O 1-bit : sum output
.cout (c2) // O 1-bit : carry output
);
fadd u2 (
.a (r1[2]) , // I 1-bit : first input
.b (r2[2]) , // I 1-bit : Second input
.cin (c2) , // I 1-bit : Carry input
.s (s[2]) , // O 1-bit : sum output
.cout (c3) // O 1-bit : carry output
);
fadd u3 (
.a (r1[3]) , // I 1-bit : first input
.b (r2[3]) , // I 1-bit : Second input
.cin (c3) , // I 1-bit : Carry input
.s (s[3]) , // O 1-bit : sum output
.cout (cout) // O 1-bit : carry output
);
endmodule // End Of Module adder
```
### 3. 撰寫 Testbench 並設定 $random 使其產生15組的測試數據 (fourbits__fulladder__tb.v)
```
`timescale 1ns/100ps
`include "fulladder.v"
`include "fourbits__fulladder.v"
module add4__explicit__tb;
reg [3:0] A, B;
reg CIN;
wire [3:0] SUM;
wire COUT;
add4__explicit add4_explicit_tb(SUM, COUT, A, B, CIN);
initial
begin
$dumpfile("hw_and_sw_hw1.vcd");
$dumpvars(0, add4__explicit__tb);
$monitor("A = %b, B = %b, SUM = %b, COUT = %b", A, B, SUM, COUT);
CIN=4'b0000;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 A={$random} % 16; B={$random} % 16;
#10 $finish;
end
endmodule
```
### 4. 使用 lcarus Verilog 經過 compile 產生 .vcd檔,並使用 GTKWave 查看 simulation 的波型結果
### 5. 開啟 Xilinx Vivado 並進行 Synthesis for FPGA
## 三、實作結果:
### ◎ schematic



### ◎ .vcd

## 四、實驗心得:
本次的作業困擾我最多的地方在於對環境的不熟悉,撰寫 Verilog 程式本身不是最難的問題,而是在於在 Compile 過程中對於 tool 的陌生使得我在過程中頻頻出現報錯的警訊等等,尤其以在 VScode 中鍵入 make 時,看不懂腳本的我更是對於指令不知如何下而不知所措。所幸在同學的協助下告訴我 Top module 的設置方式才使的我的程式能順利完成編譯過程。
也謝謝教授親自拍攝了 VScode 的設定過程影片,使我能依樣畫葫蘆把環境建置好,謝謝教授的教導。
## 五、參考文獻:
> EEF946〈軟硬體協同設計〉課堂參考講義
---
---
<p class="text-center">
--- divider line ---
</p>
---
---
# 〈HW2:BCD to Decimal Decoder with Preset/Reset〉
## 一、作業內容:
###
將 4bits 的binary inputs 訊號轉成 10bits 的 output 訊號,並使用 preset 與 reset_n 進行重設。
## 二、實作步驟:
### 1. 撰寫 BCD decoder 的 RTL code (bcd__decoder.v)
```
module bcd_decoder (
in, // I 4-bit
out, // O 10-bit
preset, // I 1-bit
reset_n // I 1-bit
);
input [3:0] in;
input preset, reset_n;
output [9:0] out;
reg [9:0] out;
always @(*) begin
if (reset_n == 1)
if (preset == 1)
out = 10'b11110_00000;
else
case (in)
4'b0000: out = 10'b11111_11110;
4'b0001: out = 10'b11111_11101;
4'b0010: out = 10'b11111_11011;
4'b0011: out = 10'b11111_10111;
4'b0100: out = 10'b11111_01111;
4'b0101: out = 10'b11110_11111;
4'b0110: out = 10'b11101_11111;
4'b0111: out = 10'b11011_11111;
4'b1000: out = 10'b10111_11111;
4'b1001: out = 10'b01111_11111;
4'b1010: out = 10'b11111_11111;
4'b1011: out = 10'b11111_11111;
4'b1100: out = 10'b11111_11111;
4'b1101: out = 10'b11111_11111;
4'b1110: out = 10'b11111_11111;
4'b1111: out = 10'b11111_11111;
endcase
else
out = 10'b00000_00000;
end
endmodule
```
> 此處為使流程清晰化,並未使用 case 函數中的 default 語法。
### 2. 再撰寫 Testbench 並設定 $random 使其產生數組 in 測試數據(bcd__decoder__tb.v)
```
`timescale 1ns/100ps
`include "bcd__decoder.v"
module bcd_decoder_tb;
reg [3:0] IN;
reg PRESET, RESET_N;
wire [9:0] OUT;
integer i;
bcd_decoder bcd_decoder_try (.in(IN), .out(OUT), .preset(PRESET), .reset_n(RESET_N));
initial begin
$dumpfile("hw_and_sw_hw2.vcd");
$dumpvars(0, bcd_decoder_tb);
$monitor("IN = %b, OUT = %b, PRESET = %b, RESET_N = %b", IN, OUT, PRESET, RESET_N);
RESET_N = 1'b0;
#10 RESET_N = 1'b1; PRESET = 1'b0;
#50 PRESET = 1'b1;
#30 PRESET = 1'b0;
end
initial begin
for (i=1 ; i<=100 ; i=i+1) #10 IN = {$random} % 16;
#10 $finish;
end
endmodule
```
> RESET_N 與 PRESET 皆參考真值表的結果所進行設定,將 high impedance 的結果設於初始狀態。
### 3. 使用 lcarus Verilog 經過 compile 產生 .vcd檔,並使用 GTKWave 查看 simulation 的波型結果
### 4. 開啟 Xilinx Vivado 並進行 Synthesis for FPGA
### 5. 更改所使用板子的 .xdc檔案中的 I/O 腳位最後進行燒錄

## 三、實作結果:
### ◎ .vcd

### ◎ schematic

### ◎ reports

### ◎ .xdc 設定

> reset_n 與 preset 使用 switches 啟動。
### ◎ FPGA 實作畫面

## 四、實驗討論:
### 1. 請說明所撰寫的 RTL 與 schematic 是否有差別
###
如圖一,可見到在程式碼中使用了兩層的 if 語法來進行編寫,因此在 schematic 圖上則有兩層 MUX 的設計結果,符合我預期中的結構圖。
### 2. 依據 vivado 所提供的分析報告(功率分析, 面積報告或時序),提供你什麼樣的想法?
###
學生發現在**功率分析**的部分與我之前其他作業來比較為高,之前寫了一個 32bits 的加法器但功耗卻僅有 0.121W,而此份報告只是 combinational logic 但功耗卻已達到 2.675W ,不知是否為 Case 語法的因素使然。
而在**面積報告**的部分則可見到大部分的晶片面積都用在了 I/O 上,其餘的部分微乎其微。
最後在**時序分析**的部分則顯示為 N/A ,由於此份作業為 combinational logic ,並無 clock 等 squential logic 的介入,因此在時序分析上則為空白。
## 五、實驗心得:
###
本次的作業屬於經典題目,不過可見到老師在 reset 的部分則進行了部分修改,使這份作業在製作的過程中,學生花費了些許的時間對波型進行了驗證,並由於在程式編寫的時候 $monitor 的位置亂放,導致在波型輸出的結果一開始並沒有從 0ns 開始,這樣的低級錯誤甚至有點讓本來就不充裕的信心有些許打擊。
而第二個打擊的部分是在 Xilinx Vivado 的設定,在跑出 reports 之前,學生好不容易通過了 synthesis 的過程,但在最後的 implementation 時卻跑出了錯誤,顯示我在對空電路進行模擬,瞬間一堆問號在我頭上展開,由於學生的畢業專題也使用過此環境撰寫 RTL code ,因此在面臨此問題時甚至是百思不得其解的,最後靠著先前上課的環境設定講義才把 implementation 的對象設定好,原來學生先前是在對 testbench 進行模擬阿......整個傻眼 = =
總之這次的作業在程式碼的部分,還有許多我認為可以改善的部分,尤其以超過 10 (含)以上的數值,皆用 (1111111111)~2~ 來表示的部分,學生想了解是否有其餘的方式可以節省其硬體使用資源等等。
## 六、參考文獻:
> EEF946〈軟硬體協同設計〉課堂參考講義
---
---
<p class="text-center">
--- divider line ---
</p>
---
---
# 〈HW3:Decimal to 7-seg display with Reset〉〈HW4:FPGA implementation for Decimal to 7-Seg Display〉
## 一、作業內容:
###
將 4bits 的 binary inputs 訊號轉成 8bits 的 output 訊號以供七段顯示器使用,並使用 reset_n 進行重設。
## 二、實作步驟:
### 1. 撰寫 Decimal to 7-Seg 的 RTL code (decimal__to__7_seg.v)
```
module decimal_to_7_seg (
dec_in, // I 4-bit
seven_seg_out, // O 10-bit
reset_n // I 1-bit
);
input [3:0] dec_in;
input reset_n;
output [7:0] seven_seg_out;
reg [7:0] seven_seg_out;
always @(dec_in, reset_n)
begin
if (reset_n == 0)
seven_seg_out = 8'b10010001; //H
else
case (dec_in)
4'b0000: seven_seg_out = 8'b00000011; //0
4'b0001: seven_seg_out = 8'b10011111; //1
4'b0010: seven_seg_out = 8'b00100101; //2
4'b0011: seven_seg_out = 8'b00001101; //3
4'b0100: seven_seg_out = 8'b10011001; //4
4'b0101: seven_seg_out = 8'b01001001; //5
4'b0110: seven_seg_out = 8'b01000001; //6
4'b0111: seven_seg_out = 8'b00011111; //7
4'b1000: seven_seg_out = 8'b00000001; //8
4'b1001: seven_seg_out = 8'b00001001; //9
4'b1010: seven_seg_out = 8'b00010001; //A
4'b1011: seven_seg_out = 8'b11000001; //B
4'b1100: seven_seg_out = 8'b11100101; //C
4'b1101: seven_seg_out = 8'b10000101; //D
4'b1110: seven_seg_out = 8'b01100001; //E
4'b1111: seven_seg_out = 8'b01110001; //F
default: seven_seg_out = 8'b00000000; //X
endcase
end
endmodule
```
> 此處使用 case 函數中的 default 語法以避免產生 latch 的可能,並將 default 值設定為七段顯示器會全亮的 (00000000)~2~。
>
> 註解表七段顯示器愈顯示的數字與文字。
### 2. 再撰寫 Testbench 並設定使其從 0 數到 15 用以產生數組 DEC_IN 測試數據(decimal__to__7_seg__tb.v)
```
`timescale 1ns/100ps
//`include "decimal__to__7_seg.v"
module decimal_to_7_seg_tb;
reg [3:0] DEC_IN;
reg RESET_N;
wire [7:0] SEVEN_SEG_OUT;
integer i;
decimal_to_7_seg decimal_to_7_seg_try (.dec_in(DEC_IN), .seven_seg_out(SEVEN_SEG_OUT), .reset_n(RESET_N));
initial
begin
RESET_N = 1'b0;
#10 RESET_N = 1'b1;
end
initial
begin
for (i=0 ; i<=100 ; i=i+1) #10 DEC_IN = i % 16;
#10 $finish;
end
initial
begin
$dumpfile("hw_and_sw_hw3.vcd");
$dumpvars();
//$sdf_annotate("decimal__to__7_seg.sdf", decimal_to_7_seg_tb);
$fsdbDumpfile("decimal__to__7_seg.fsdb");
$fsdbDumpvars;
$monitor("IN = %b, OUT = %b, RESET_N = %b", DEC_IN, SEVEN_SEG_OUT, RESET_N);
end
endmodule
```
> $fsdbDumpfile 與 $fsdbDumpvars 為後續使用 EDA cloud 時所需的語法設定。
>
> timescale 在使用 VCS compiler 時需隱藏。
### 3. 使用 lcarus Verilog 經過 compile 產生 .vcd檔,並使用 GTKWave 預先查看合成前 simulation 的波型結果

> 可見得 simulation 之結果符合 truth table 預期,因此得以進行下列步驟。
### 4. 使用 EDA cloud 將上述步驟改於工作站進行,並重新撰寫 Verilog codes
### 5. 將 (decimal__to__7_seg.v) 與 (decimal__to__7_seg__tb.v) 使用設定好的 filelist.f 與 makefile 後,改用 VCS compiler 產生 (decimal__to__7_seg.fsdb) 檔
### 6. 開啟 Verdi 檢視 RnWave 波型運行結果
### 7. 登出 EDA cloud,開啟 Xilinx Vivado 並進行 Synthesis for FPGA

> 得到 implemantation 的結果。
### 8. 更改所使用板子的 .xdc 檔案中的 I/O 腳位最後進行燒錄

> reset_n 使用 switches 啟動。

## 三、實作結果:
### ◎ RnWave

### ◎ schematic
#### 1. 在 Vivado 上開啟合成前圖片

#### 2. 在 Vivado 上開啟合成後圖片

#### 3. 使用 Verdi 開啟

### ◎ FPGA 實作畫面

> 當輸入 reset_n == 0 時,輸出 H 字型。
















## 四、實驗心得:
###
本次的作業在撰寫 Verilog 的部分不算過於困難,與 HW2 的內容相似,但在使用 EDA cloud 的部分可就麻煩了,由於不熟悉工作站的指令操作以及其所涵蓋的軟體資源,可以說在使用工作站時所遇到的問題是困難重重,再加上學生所使用的筆記型電腦的 CPU 為 i3-3120M 屬於年代久遠的硬體設備,完全無法支撐運行虛擬機 NX client 的運行環境,只好換一台設備再繼續戰。
## 五、參考文獻:
> EEF946〈軟硬體協同設計〉課堂參考講義
---
---
<p class="text-center">
--- divider line ---
</p>
---
---
# 〈HW5:Improve code coverage of your design〉
## 一、作業內容:
###
針對上課講義內容所提及的 Finite State Machine codes 進行 code coverage 分析。
## 二、實作步驟:
### 1. 撰寫 simple_moore_fsm 的 RTL code (moore__fsm.v)
```
module simple_moore_fsm(
clk,
rst_n,
inpl,
outp
);
input clk, rst_n, inpl;
output outp;
reg [1:0] curr_state, next_state;
reg outp;
parameter IDLE = 2'd0;
parameter S0 = 2'd1;
parameter S1 = 2'd2;
//state transient
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
curr_state <= IDLE;
else
curr_state <= next_state;
end
always @(curr_state or inpl)
begin
case(curr_state)
IDLE: next_state = (inpl == 1'd1) ? S0 : IDLE;
S0: next_state = (inpl == 1'd1) ? S1 : IDLE;
S1: next_state = (inpl == 1'd1) ? S1 : IDLE;
// VCS coverage off
default: next_state = IDLE;
// VCS coverage on
endcase
end
always @(curr_state)
begin
case(curr_state[1:0])
IDLE: outp = 1'd0;
S0: outp = 1'd0;
S1: outp = 1'd1;
// VCS coverage off
default: outp = 1'd0;
// VCS coverage on
endcase
end
endmodule
```
### 2. 再撰寫 simple_moore_fsm 的 Testbench (moore__fsm__tb.v)
```
module simple_moore_fsm_tb;
reg clk_in, rst_n, inp;
wire fsm_out;
parameter clk_duration = 10;
simple_moore_fsm simple_moore_fsm_try(clk_in, rst_n, inp, fsm_out);
initial
clk_in = 1'b0;
always #(clk_duration/2) clk_in = ~ clk_in;
initial
begin
$dumpfile("hw_and_sw_hw5.vcd");
$dumpvars;
$fsdbDumpfile("hw_and_sw_hw5.fsdb");
$fsdbDumpvars;
$monitor("clk = %b, rst = %b, input = %b, output = %b", clk_in, rst_n, inp, fsm_out);
end
initial
begin
rst_n = 1'b0; inp = 1'b0;
#10 rst_n = 1'b1; inp = 1'b0;
#10 inp = 1'b1;
#10 inp = 1'b0;
#10 inp = 1'b1;
#10 inp = 1'b1;
#10 inp = 1'b1;
#10 inp = 1'b0;
#10
$finish;
end
endmodule
```
### 3. 使用 lcarus Verilog 經過 compile 產生 .vcd檔,並使用 GTKWave 預先查看合成前 simulation 的波型結果

> 由此張波型結果雖可以一步一步推斷各個時間所產生的值,但無法直接了解其所處的狀態 (如:IDLE, S0, S1) 為何?
### 4. 將所有 Verilog 檔案與 filelist.f 上傳至 EDA cloud,並將上述步驟改於工作站進行
### 5. 設定所要執行的 makefile 檔案功能,並於工作站使用指令 UNIX% make vcs_kdb 進行編譯
```
.PHONY:clean vcs_kdb vcs_cov verdi_kdb verdi_cov
VCS = Rvcs
VERDI = Rverdi
#***** coverage option *****
COV_METRICS_SEL = line+cond+fsm+branch
#COV_METRICS_SEL = line+cond+fsm+branch+tgl #for postsim
#***** VCS compile option *****
VCS_COMP_OPT = \
-full64 \
-debug_acc+all \
-j4 \
-lca \
-kdb \
-sverilog \
-Xkeyopt=rtopt \
-Mupdate \
-R \
+v2k \
#***** VCS simulate option *****
VCS_SIM_OPT = \
+vcs+fsdbon \
-timescale=1ns/1ps \
-l ./logfile/vcs_kdb_compiled.logfile/vcs_kdb_compiled\
+vcs_flush+all \
#***** VCS coverage option *****
VCS_COV_OPT = \
-cm $(COV_METRICS_SEL) \
-cm_log ./logfile/vcs_cov_$(COV_METRICS_SEL)_compiled.log \
-cm_dir ./coverage/coverage.vdb \
-cm_name coverage
#***** VCS compilation *****
vcs_kdb:
$(VCS) \
$(VCS_COMP_OPT) \
$(VCS_SIM_OPT) \
-file filelist.f \
vcs_cov:
$(VCS) \
$(VCS_COMP_OPT) \
$(VCS_SIM_OPT) \
$(VCS_COV_OPT) \
-file filelist.f \
#***** Verdi compilation *****
verdi_kdb:
$(VERDI) -f filelist.f -ssf novas.fsdb
verdi_cov:
$(VERDI) -cov -covdir ./coverage/coverage.vdb
clean:
rm -rf unrSimv* csrc* ./logfile/* *key *fsdb *vcd *Log *bak *el *report no_trace* *.dump
rm -rf simv* verdi_* partition* dprof* clk* *DB *dir work *lib *.daidir nWave* DVE* *.out
rm -rf novas* cm.* ./coverage/* *.log *.vdb
```
### 6. 使用指令 UNIX% make verdi_kdb 開啟 Verdi 檢視波型運行結果與其產生的 FSM diagram


> 藉由上課講義所提供的 FSM diagram,可驗證波型結果有成功運行到每個狀態,(moore__fsm.v) 與 (moore__fsm__tb.v) 撰寫正確無誤!
### 7. 使用指令 UNIX% make vcs_cov 進行 coverage 分析,並使用指令 UNIX% make verdi_cov 開啟 Verdi 檢視分析報告
> 分析結果顯示於「三、實作結果」。
## 三、實作結果:
### ◎ fullscore

### ◎ line coverage

### ◎ FSM coverage

### ◎ condition coverage

### ◎ branch coverage

## 四、實驗討論:
### 1. 要如何提升 Line coverage 數值?
###
如實驗結果,可見到在程式碼中大部分的程式碼都被標上了綠色的底色,代表該行程式碼有被執行到。而唯一一行沒有被執行到並被標上紅色底色的程式碼為 `default: outp = 1'd0;` 考慮到若 case 法中若不使用此設定,可能造成電路在合成結果時容易產生非預期的 latch 可能,因此將其保留。
而在分析時的 Line coverage 分數在 97 分的位置,落在可以接受的範圍內,因此此狀況不予理會。
### 2. 要如何提升 FSM coverage 數值?
###
由於此作業一開始給的 testbench 程式碼僅有 75% 的 FSM coverage,因此將其修改到 100% 為此次作業的重點目標。
首先,將原程式碼進行分析時,在 FSM coverage 跑出了此狀況(如下圖)。

由上圖可知,在狀態由 S0 跳回 IDLE 時是沒有被運行到的,藉由查找原程式碼的 testbench,發現於 initial 語法中的設定全部皆設定為 100 time units。
此設定方法會使第 30 行的設定 (如下圖) 原意是要由狀態 S0 跳回 IDLE,但 CLK 寫入的速度比起訊號的更動速度還要快,導致其一直維持著 `inp = 1'b1;` 便錯誤地由狀態 S0 跳到 S1,後續的設定更是無法驗證到「由狀態 S0 跳回 IDLE」此路徑。因此我將其先改成 time units 等於 #10,使其過了 10 個 time units 馬上產生 `inp = 1'b0;` 在 CLK posedge 前成功「由狀態 S0 跳回 IDLE」。

最後,考慮到訊號更動的 time units 若設定的與 CLK duration 不同步的話,容易在 sequential 電路中撰寫 testbench 時沒有成功驗證到所有預設的狀態,因此最後我將下列包含在 initial 裡面的設定都改成 10 個 time units,而成功達到 100% 的結果顯示於「三、實作結果」。

### 3. 要如何提升 Condition coverage 數值?
###
如實驗結果,可見到每個條件判斷句都成功執行到,因此達成率 100%。
### 4. 要如何提升 Branch coverage 數值?
###
如實驗結果,受限於程式碼 `default: outp = 1'd0;` 該行於 Line coverage 分析時並未成功執行到乃肇因於 case 語法中的所有設定皆已涵蓋所有結果,因此該行於 Line coverage 分析時並未執行;同理,branch coverage 分析時也不會跳躍到該行,因此無法達成到 100%。
## 五、實驗心得:
###
本次的作業學習到了數位IC設計前端所需的三大工具之一「VCS」,而對於此工具從未觸碰過的我其實在 makefile 的調整就一頭霧水,光 userguide 裡面所提供的功能更是不勝枚舉。
而本次的作業在調整分析時並不至於太難,但在 Verdi 這個軟體所具備的功能與操作方式,我僅學習到了冰山一角而已,看來在這條學習道路上,對於環境操作以及軟體熟悉程度,我還需要付出更多心力來學習。
## 六、參考文獻:
> EEF946〈軟硬體協同設計〉課堂參考講義
---
---
<p class="text-center">
--- divider line ---
</p>
---
---
# 〈HW6:7-Seg Display controlled by UART〉
## 一、作業內容:
###
撰寫具有 UART 通訊協定中能進行 RX 功能的模組。
## 二、實作步驟:
### 1. 撰寫除頻器的 RTL code (clk_div.v)
```
module clk_div
(
input clk,
input rst_n,
output reg clkout
);
parameter BAUDRATE = 9600;
parameter DIV_NUMBER = 125000000 / BAUDRATE;
reg [13:0] num = 0;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
num <= 0;
clkout <= 0;
end
else
if(num == DIV_NUMBER-1)
begin
num <= 0;
clkout <= 1;
end
else
begin
num <= num+1;
clkout <= 0;
end
end
endmodule
```
### 2. 再撰寫除頻器的 Testbench (clk_div_tb.v)
```
`timescale 1ns / 1ps
module clk_div_tb;
reg tb_clk, tb_rst_n;
wire clkout;
parameter clk_duration = 8;
initial
begin
$dumpfile("test.vcd");
$dumpvars;
end
initial
begin
tb_clk = 1'b1;
tb_rst_n = 1'b0;
#20 tb_rst_n = 1'b1;
#20 tb_rst_n = 1'b0;
#5_000_000 $finish;
end
always #(clk_duration/2) tb_clk = ~tb_clk;
clk_div u_clk_div(.clk(tb_clk), .rst_n(tb_rst_n), .clkout(clkout));
endmodule
```
※模擬結果圖

> 由此可知 DIVISION_NUMBER 大約需設定為 13020,其公式為:
> Clock signal / BAUDRATE。
### 3. 撰寫 UART 中 RX 模組的 RTL code (clk_div.v)
```
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 22:27:09 12/04/2019
// Design Name:
// Module Name: uart_rx
// Project Name:
// Target Devices:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module uart_rx
(
input clk,
input rst_n,
input rx,
output reg [9:0] data_out
);
// State Machine Defination
parameter IDLE = 2'b01;
parameter SAMP = 2'b10;
// UART Configure Defination
parameter START_BIT = 1;
parameter DATA_BIT = 8;
parameter STOP_BIT = 1;
parameter PARI_BIT = 0;
parameter RECV_BIT = START_BIT + DATA_BIT + STOP_BIT + PARI_BIT;
// Negedge Detection Filter
reg [3:0] data_in;
always @ (posedge clk or negedge rst_n)
if (!rst_n)
begin
data_in[0] <= 1'b0;
data_in[1] <= 1'b0;
data_in[2] <= 1'b0;
data_in[3] <= 1'b0;
end
else
begin
data_in[0] <= rx;
data_in[1] <= data_in[0];
data_in[2] <= data_in[1];
data_in[3] <= data_in[2];
end
wire rx_neg = data_in[3] & data_in[2] & (~data_in[1]) & (~data_in[0]);
reg [1:0] current_state, next_state;
// Current State To Next State
always @ (posedge clk or negedge rst_n)
if(!rst_n)
current_state <= IDLE;
else
current_state <= next_state;
// State
reg sample_finish;
always @(current_state or sample_finish or rx_neg)
begin
next_state = 2'bx;
case(current_state)
IDLE :
begin
if (rx_neg) next_state = SAMP;
else next_state = IDLE;
end
SAMP :
begin
if (sample_finish) next_state = IDLE;
else next_state = SAMP;
end
default : next_state = IDLE;
endcase
end
reg [3:0] recv_cnt;
reg sample_en;
reg [RECV_BIT - 1 : 0] data_temp;
always @ (posedge clk or negedge rst_n)
if (!rst_n)
begin
data_out <= 10'bx;
data_temp <= 10'bx;
sample_finish <= 1'b0;
sample_en <= 1'b0;
recv_cnt <= 4'b0;
end
else
begin
case (next_state)
IDLE:
begin
//data_out <= 10'bx;
data_temp <= 10'bx;
sample_finish <= 1'b0;
sample_en <= 1'b0;
recv_cnt <= 4'b0;
end
SAMP:
begin
if (recv_cnt == RECV_BIT)
begin
data_out <= data_temp;
data_temp <= 10'bx;
sample_finish <= 1'b1;
sample_en <= 1'b0;
recv_cnt <= 4'b0;
end
else
begin
sample_en <= 1'b1;
if (clk)
begin
data_out <= data_out;
data_temp[recv_cnt] <= rx;
sample_finish <= 1'b0;
recv_cnt <= recv_cnt + 1'b1;
end
else
begin
data_out <= data_out;
data_temp <= data_temp;
sample_finish <= sample_finish;
recv_cnt <= recv_cnt;
end
end
end
default:
begin
data_out <= 10'bx;
sample_finish <= 1'b0;
sample_en <= 1'b0;
end
endcase
end
// Sample Counter Signal Generator
parameter BAUD_MAX = 50;
reg [5:0] baud_cnt;
always @ (posedge clk or negedge rst_n)
if (!rst_n)
begin
baud_cnt <= 6'd0;
end
else
begin
if (sample_en)
begin
if (baud_cnt == BAUD_MAX - 1) baud_cnt <= 6'd0;
else baud_cnt <= baud_cnt + 1'b1;
end
else baud_cnt <= 6'd0;
end
// Sample Clock Signal Generator
parameter BAUD_CNT_H = (BAUD_MAX / 2);
wire baud_clk = (baud_cnt == BAUD_CNT_H) ? (1'b1) : (1'b0);
endmodule
```
### 4. 再撰寫 UART 中 RX 模組的 Testbench (uart_tb.v)
```
`timescale 1ns / 1ps
// Company:
// Engineer:
//
// Create Date: 14:03:06 12/05/2019
// Design Name: uart_rx
// Module Name: D:/Xlinx_ISE_Projects/testbench/uart_rx_tb.v
// Project Name: test
// Target Device:
// Tool versions:
// Description:
//
// Verilog Test Fixture created by ISE for module: uart_rx
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
module uart_tb;
// Inputs
reg CLK;
reg RST_N;
reg RX;
// Outputs
wire [9:0] DATA_OUT;
integer i;
initial
begin
$dumpfile("tt.vcd");
$dumpvars(0,uart_tb);
$dumpvars;
//$fsdbDumpfile("test.fsdb");
//$fsdbDumpvars;
end
initial
begin
for(i=0 ; i<=700000 ; i=i+1)
begin
i = i+1;
$display("At time %t, RST_N=%b | RX=%b | data_i=%b", $realtime/1000, RST_N, RX, DATA_OUT);
#200;
end
end
always #1 CLK = ~CLK;
initial
begin
// Initialize Inputs
CLK = 0;
RST_N = 0;
RX = 1;
// Wait 100 ns for global reset to finish
#100;
// Add stimulus here
RST_N = 1;
#100;
// Generate Start bit
#200 RX = 1'b0;
// 8 data bits
#200 RX = 1'b1;
#200 RX = 1'b0;
#200 RX = 1'b1;
#200 RX = 1'b0;
#200 RX = 1'b0;
#200 RX = 1'b0;
#200 RX = 1'b0;
#200 RX = 1'b0;
// Generate Stop bit
#200 RX = 1'b1;
#600;
// Generate Start bit
#200 RX = 1'b0;
// 8 data bits
#200 RX = 1'b1;
#200 RX = 1'b1;
#200 RX = 1'b1;
#200 RX = 1'b0;
#200 RX = 1'b0;
#200 RX = 1'b1;
#200 RX = 1'b1;
#200 RX = 1'b1;
// Generate Stop bit
#200 RX = 1'b1;
#20000 $finish;
end
// Instantiate the Unit Under Test (UUT)
uart_rx U_uart_rx
(
.clk(CLK),
.rst_n(RST_N),
.rx(RX),
.data_out(DATA_OUT)
);
endmodule
```
※模擬結果圖


> 由此可見在 testbench 中產生了兩組 data_out 的值,LSB 的 0 代表通知 RX 為 start bit,而 MSB 的 1 代表通知 RX 為 end bit。
>中間夾住的 8bits 則為需要傳送的訊號內容,由右至左判讀。
---
---
<p class="text-center">
--- divider line ---
</p>
---
---
# 〈HW7:人月神話讀書心得報告〉
### ● 內容:
###
《人月神話》之所以能成為經典之作,主要是因其專注在探討人的方面,而非技術層面問題。書中特別關注軟體開發的核心是在於如何使組織擁有最高效率。
作者認為使用人月去估計時程並不是一種有效的方法,為了闡述此觀點,作者選擇一個比喻:生小孩就是需要10個月,你叫多少個媽一起生都一樣。此種情況的關鍵在於資料的相依性。只有在工作能完全切分,且各部分都是獨立時,增加人手才可能縮短所需時間;反之,則可能增加更多的溝通成本。另外,書中也有要維護產品的概念整體性,必須定義受眾的行為。只有在預期的特徵存在的情況下,才有辦法分析某種設計是否合理。此外,若是需要修改設計,則需以貼合受眾需求的方式去詮釋。
作者認為架構設計人員應該試圖去揣測受眾的行為,即便出現方向錯誤也優於失去方向。若討論結論是需要驗證受眾的行為,這就有足夠的理由花費更多的時間成本去蒐集準確的受眾行為數據。在系統設計中,維持概念完整性是最重要的原則,不論是在使用介面或後台的軟體架構。由於每個人的觀點差異,在此情況下管理就會是一大難題,因此作者認為在此種情形下,專制是必要的。在此情境中,實作人員依舊擁有相當大的發揮空間。
架構設計與實作分離,負責架構的人可提供實作意見,但不能指定具體的實作方式。這種限制實際上有助於激發實作人員更多的創意,原因在於他們會專注於限定的範圍中構思,不會過度跳脫框架,因此專制在此結果呈現雙贏的效果。在實務上須同時有清確的正式以及通俗的定義。二者則一為主,另一為輔。在專案處理上,作者以外科手術作比喻,形容在專案處理上,團隊人員分配應以一人為主導,其他成員則負責協助。此做法類似專制,由一位負責人決定專案的走向並將工作分配給其他成員完成同時,將整個公司切割成多個小團隊應對不同的專案,若需要協同處理所有專案時,這種作法則與概念整體性相似。
最後,作者討論了在軟體開發時所面臨的不確定性和複雜性,以及在這些情況下可能遭遇到的多樣挑戰,並提供了一系列的建議,以利在未來面對類似的問題時可以有更具體的參考方向。這些建議涵蓋了軟體專案管理、團隊選擇等等多個層面,為日後有組織專案需求的人提供了深刻且實用的指導原則。
---
---
<p class="text-center">
--- divider line ---
</p>
---
---
# 〈Final Project:FPGA audio jack 實作〉
### ● 展示影片:https://youtu.be/FWSER7mHw-U?si=-lFa7CzOHpmeE7Ff
## 一、作業內容:
###
使用 PYNQ-Z2 開發板,結合Pmod I2S2 進行音訊撥放以及音頻處理。
### ◎ Pmod I2S2

## 二、實作步驟:
### 1. 將設計濾波器所需使用到的參數進行生成 (filter_gen.m)
#### 參數設計數值:
>Sampling_rate : 44100KHz
Cut-off frequency : 1000Khz
Taps : 89
Coeff_width : 16-bits
#### 截止頻率設計數值:
>Low-pass filter : 1KHz
High-pass filter : 2KHz
Band-pass filter : 1KHz~4KHz
```
sampling_freq = 44100;
cutoff_freq = 1000;
taps = 89; % Number of taps of the filter
coeff_width = 16; % Width of coefficients in bits
A = int32(fir1(taps - 1, cutoff_freq / (sampling_freq / 2), 'low') * (2^(coeff_width - 1) - 1));
freqz(double(A) / (2^(coeff_width - 1) - 1)); % display for sanity check
for i = 1:length(A)
hex_value = dec2hex(abs(A(i)),coeff_width / 4); % convert to hex
if A(i) < 0 % if negative, display sign
printf('-');
end
printf('%d"h%s, ', coeff_width, hex_value); % print result
if mod(i, 5) == 0
printf('\n');
end
end
```
### 2. 撰寫 FIR 濾波器的 HDL 檔案 (fir.sv)
```
`timescale 1ns / 1ps
module dual_channel_fir_filter #(
parameter DATA_WIDTH = 24,
parameter N_FILTERS = 4 // Number of filters implemented
) (
input wire clk,
input wire [N_FILTERS-1:0] sw,
//AXIS SLAVE INTERFACE
input wire [DATA_WIDTH-1:0] s_axis_data,
input wire s_axis_valid,
output reg s_axis_ready = 1'b1,
input wire s_axis_last,
// AXIS MASTER INTERFACE
output reg [DATA_WIDTH-1:0] m_axis_data = 1'b0,
output reg m_axis_valid = 1'b0,
input wire m_axis_ready,
output reg m_axis_last = 1'b0
);
reg signed [DATA_WIDTH-1:0] input_data [1:0]; // Left and right channel data
wire signed [DATA_WIDTH-1:0] output_data [1:0]; // Left and right channel data
wire m_select = m_axis_last;
wire m_new_word = (m_axis_valid == 1'b1 && m_axis_ready == 1'b1) ? 1'b1 : 1'b0;
wire m_new_packet = (m_new_word == 1'b1 && m_axis_last == 1'b1) ? 1'b1 : 1'b0;
wire s_select = s_axis_last;
wire s_new_word = (s_axis_valid == 1'b1 && s_axis_ready == 1'b1) ? 1'b1 : 1'b0;
wire s_new_packet = (s_new_word == 1'b1 && s_axis_last == 1'b1) ? 1'b1 : 1'b0;
reg s_new_packet_r = 1'b0;
dual_channel_fir_engine fir_engine(
.clk(clk),
.sw(sw),
.new_packet(s_new_packet_r),
.input_data(input_data),
.output_data(output_data)
);
always@(posedge clk) begin
s_new_packet_r <= s_new_packet;
if (s_new_word == 1'b1) // Register AXIS slave data
input_data[s_select] <= s_axis_data;
end
// Controls the AXIS master interface by setting the validity and end-of-packet signals based on the state of the AXIS slave interface.
always@(posedge clk)
if (s_new_packet_r == 1'b1)
m_axis_valid <= 1'b1;
else if (m_new_packet == 1'b1)
m_axis_valid <= 1'b0;
always@(posedge clk)
if (m_new_packet == 1'b1)
m_axis_last <= 1'b0;
else if (m_new_word == 1'b1)
m_axis_last <= 1'b1;
// Assigns the output data on the AXIS master interface based on the validity and selection signals.
always@(m_axis_valid, output_data[0], output_data[1], m_select)
if (m_axis_valid == 1'b1)
m_axis_data = output_data[m_select];
else
m_axis_data = 'b0;
always@(posedge clk)
if (s_new_packet == 1'b1)
s_axis_ready <= 1'b0;
else if (m_new_packet == 1'b1)
s_axis_ready <= 1'b1;
endmodule
module single_channel_fir_filter #(
parameter DATA_WIDTH = 24,
parameter N_FILTERS = 4 // Number of filters implemented
) (
input wire clk,
input wire [N_FILTERS-1:0] sw,
//AXIS SLAVE INTERFACE
input wire [DATA_WIDTH-1:0] s_axis_data,
input wire s_axis_valid,
output reg s_axis_ready = 1'b1,
input wire s_axis_last,
// AXIS MASTER INTERFACE
output reg [DATA_WIDTH-1:0] m_axis_data = 1'b0,
output reg m_axis_valid = 1'b0,
input wire m_axis_ready,
output reg m_axis_last = 1'b0
);
reg signed [DATA_WIDTH-1:0] input_data [1:0]; // Left and right channel data
wire signed [DATA_WIDTH-1:0] output_data; // Combined output data
wire m_select = m_axis_last;
wire m_new_word = (m_axis_valid == 1'b1 && m_axis_ready == 1'b1) ? 1'b1 : 1'b0;
wire m_new_packet = (m_new_word == 1'b1 && m_axis_last == 1'b1) ? 1'b1 : 1'b0;
wire s_select = s_axis_last;
wire s_new_word = (s_axis_valid == 1'b1 && s_axis_ready == 1'b1) ? 1'b1 : 1'b0;
wire s_new_packet = (s_new_word == 1'b1 && s_axis_last == 1'b1) ? 1'b1 : 1'b0;
reg s_new_packet_r = 1'b0;
single_channel_fir_engine fir_engine(
.clk(clk),
.sw(sw),
.new_packet(s_new_packet_r),
.input_data(input_data),
.output_data(output_data)
);
always@(posedge clk) begin
s_new_packet_r <= s_new_packet;
if (s_new_word == 1'b1) // Register AXIS slave data
input_data[s_select] <= s_axis_data;
end
// Controls the AXIS master interface by setting the validity and end-of-packet signals based on the state of the AXIS slave interface.
always@(posedge clk)
if (s_new_packet_r == 1'b1)
m_axis_valid <= 1'b1;
else if (m_new_packet == 1'b1)
m_axis_valid <= 1'b0;
always@(posedge clk)
if (m_new_packet == 1'b1)
m_axis_last <= 1'b0;
else if (m_new_word == 1'b1)
m_axis_last <= 1'b1;
// Assigns the output data on the AXIS master interface based on the validity and selection signals.
always@(m_axis_valid, output_data, m_select)
if (m_axis_valid == 1'b1)
m_axis_data = output_data;
else
m_axis_data = 'b0;
always@(posedge clk)
if (s_new_packet == 1'b1)
s_axis_ready <= 1'b0;
else if (m_new_packet == 1'b1)
s_axis_ready <= 1'b1;
endmodule
```
### 3. 撰寫 FIR 濾波器的後端結構 HDL 檔案 (fir_backend.sv)
```
`timescale 1ns / 1ps
module dual_channel_fir_engine #(
parameter DATA_WIDTH = 24,
parameter N_FILTERS = 4, // Number of filters implemented
parameter N_TAPS = 45,
parameter COEFF_WIDTH = 16
) (
input wire clk,
input wire [N_FILTERS-1:0] sw,
input wire new_packet,
input reg signed [DATA_WIDTH-1:0] input_data [1:0],
output reg signed [DATA_WIDTH-1:0] output_data [1:0],
output reg signed [DATA_WIDTH-1:0] buffer [N_TAPS-1:0][1:0], // Buffer for data
output reg signed [COEFF_WIDTH+DATA_WIDTH-1:0] op_buffer [1:0],
output reg [2:0] selected_filter
);
// Coefficients for FIR filter
reg signed [COEFF_WIDTH-1:0] coeffs [N_FILTERS-1:0][N_TAPS-1:0] = '{
'{ // Low-pass filter: 1KHz
16'h0007, 16'h0015, 16'h002B, 16'h004C, 16'h007E,
16'h00C5, 16'h0127, 16'h01A6, 16'h0243, 16'h02FF,
16'h03D9, 16'h04CC, 16'h05D4, 16'h06EA, 16'h0807,
16'h0920, 16'h0A2E, 16'h0B25, 16'h0BFF, 16'h0CB1,
16'h0D35, 16'h0D87, 16'h0DA3, 16'h0D87, 16'h0D35,
16'h0CB1, 16'h0BFF, 16'h0B25, 16'h0A2E, 16'h0920,
16'h0807, 16'h06EA, 16'h05D4, 16'h04CC, 16'h03D9,
16'h02FF, 16'h0243, 16'h01A6, 16'h0127, 16'h00C5,
16'h007E, 16'h004C, 16'h002B, 16'h0015, 16'h0007
},
'{ // High pass filter: 2KHz
16'h0003, 16'h000F, 16'h001F, 16'h0035, 16'h0053,
16'h0076, 16'h0098, 16'h00B3, 16'h00BB, 16'h00A4,
16'h0062, 16'hFFEB, 16'hFF38, 16'hFE47, 16'hFD1E,
16'hFBC7, 16'hFA54, 16'hF8DC, 16'hF778, 16'hF642,
16'hF551, 16'hF4B8, 16'h7485, 16'hF4B8, 16'hF551,
16'hF642, 16'hF778, 16'hF8DC, 16'hFA54, 16'hFBC7,
16'hFD1E, 16'hFE47, 16'hFF38, 16'hFFEB, 16'h0062,
16'h00A4, 16'h00BB, 16'h00B3, 16'h0098, 16'h0076,
16'h0053, 16'h0035, 16'h001F, 16'h000F, 16'h0003
},
'{ // Bandpass: 1KHz to 4KHz
16'hFFF3, 16'hFFB9, 16'hFF79, 16'hFF3B, 16'hFF16,
16'hFF26, 16'hFF74, 16'hFFE3, 16'h0024, 16'hFFC6,
16'hFE5D, 16'hFBBB, 16'hF826, 16'hF468, 16'hF1B5,
16'hF15D, 16'hF45E, 16'hFAFF, 16'h0495, 16'h0F8A,
16'h19B7, 16'h20EA, 16'h2384, 16'h20EA, 16'h19B7,
16'h0F8A, 16'h0495, 16'hFAFF, 16'hF45E, 16'hF15D,
16'hF1B5, 16'hF468, 16'hF826, 16'hFBBB, 16'hFE5D,
16'hFFC6, 16'h0024, 16'hFFE3, 16'hFF74, 16'hFF26,
16'hFF16, 16'hFF3B, 16'hFF79, 16'hFFB9, 16'hFFF3
},
'{ // Moving Average Filter
16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0
}};
always@(posedge clk) begin
// Update the currently selected filter
case (sw)
4'b0000: selected_filter <= 0;
4'b0001: selected_filter <= 4;
4'b0010: selected_filter <= 3;
4'b0100: selected_filter <= 2;
4'b1000: selected_filter <= 1;
default: selected_filter <= 0;
endcase
// Hold the latest input on top of buffer (buffer[N_TAPS - 1])
if (new_packet == 1'b1) begin // New packet recieved
buffer[N_TAPS - 1][0] <= input_data[0];
buffer[N_TAPS - 1][1] <= input_data[1];
end
end
// The input buffer acts like a large shift register
generate
for (genvar i = 0; i < N_TAPS - 1; i = i+1) begin
always@(posedge clk)
if (new_packet == 1'b1) begin // New packet recieved
// Shift the old packets so that buffer[N_TAPS - 1] holds the latest one
buffer[i][0] <= buffer[i + 1][0];
buffer[i][1] <= buffer[i + 1][1];
end
end
endgenerate
// Actually compute the output
always@(posedge clk)
if (new_packet == 1'b1 && selected_filter != 0) begin // New packet recieved
op_buffer[0] = coeffs[selected_filter - 1][0] * buffer[N_TAPS - 1][0];
op_buffer[1] = coeffs[selected_filter - 1][0] * buffer[N_TAPS - 1][1];
for (int i = 1; i < N_TAPS; i = i+1) begin
op_buffer[0] = op_buffer[0] + coeffs[selected_filter - 1][i] * buffer[N_TAPS - i - 1][0];
op_buffer[1] = op_buffer[1] + coeffs[selected_filter - 1][i] * buffer[N_TAPS - i - 1][1];
end
if (op_buffer[0][COEFF_WIDTH+DATA_WIDTH-1] == 1'b1) begin // If is negative
output_data[0] = -((-op_buffer[0]) >> COEFF_WIDTH); // Convert to +ve for shifting
end
else output_data[0] <= op_buffer[0][COEFF_WIDTH+DATA_WIDTH-1:COEFF_WIDTH];
if (op_buffer[1][COEFF_WIDTH+DATA_WIDTH-1] == 1'b1) begin // If is negative
output_data[1] = -((-op_buffer[1]) >> COEFF_WIDTH); // Convert to +ve for shifting
end
else output_data[1] <= op_buffer[1][COEFF_WIDTH+DATA_WIDTH-1:COEFF_WIDTH];
end
else if (new_packet == 1'b1 && selected_filter == 0) begin
output_data[0] <= input_data[0];
output_data[1] <= input_data[1];
end
endmodule
module single_channel_fir_engine #(
parameter DATA_WIDTH = 24,
parameter N_FILTERS = 4, // Number of filters implemented
parameter N_TAPS = 89,
parameter COEFF_WIDTH = 16
) (
input wire clk,
input wire [N_FILTERS-1:0] sw,
input wire new_packet,
input reg signed [DATA_WIDTH-1:0] input_data [1:0],
output reg signed [DATA_WIDTH-1:0] output_data,
output reg signed [DATA_WIDTH-1:0] buffer [N_TAPS-1:0], // Buffer for data
output reg signed [COEFF_WIDTH+DATA_WIDTH-1:0] op_buffer,
output reg [2:0] selected_filter
);
// --
reg signed [COEFF_WIDTH-1:0] coeffs [N_FILTERS-1:0][N_TAPS-1:0] = '{
'{ // Low-pass filter: 1KHz
-16'h0006, -16'h0011, -16'h001C, -16'h0027, -16'h0031,
-16'h003A, -16'h0040, -16'h0041, -16'h003B, -16'h002C,
-16'h0013, 16'h0011, 16'h003D, 16'h0071, 16'h00A7,
16'h00DA, 16'h0104, 16'h011C, 16'h011D, 16'h00FF,
16'h00BF, 16'h005C, -16'h002A, -16'h00CB, -16'h017C,
-16'h0231, -16'h02D7, -16'h035B, -16'h03A7, -16'h03AA,
-16'h0351, -16'h028F, -16'h015E, 16'h0043, 16'h024D,
16'h04B0, 16'h0755, 16'h0A22, 16'h0CF4, 16'h0FA8,
16'h121B, 16'h142A, 16'h15BA, 16'h16B3, 16'h1707,
16'h16B3, 16'h15BA, 16'h142A, 16'h121B, 16'h0FA8,
16'h0CF4, 16'h0A22, 16'h0755, 16'h04B0, 16'h024D,
16'h0043, -16'h015E, -16'h028F, -16'h0351, -16'h03AA,
-16'h03A7, -16'h035B, -16'h02D7, -16'h0231, -16'h017C,
-16'h00CB, -16'h002A, 16'h005C, 16'h00BF, 16'h00FF,
16'h011D, 16'h011C, 16'h0104, 16'h00DA, 16'h00A7,
16'h0071, 16'h003D, 16'h0011, -16'h0013, -16'h002C,
-16'h003B, -16'h0041, -16'h0040, -16'h003A, -16'h0031,
-16'h0027, -16'h001C, -16'h0011, -16'h0006
},
'{ // High pass filter: 2KHz
16'h0003, 16'h0008, 16'h000E, 16'h0013, 16'h0019,
16'h001D, 16'h0020, 16'h0020, 16'h001D, 16'h0016,
16'h0009, -16'h0008, -16'h001F, -16'h0038, -16'h0053,
-16'h006D, -16'h0082, -16'h008E, -16'h008E, -16'h007F,
-16'h005F, -16'h002E, 16'h0015, 16'h0065, 16'h00BE,
16'h0118, 16'h016B, 16'h01AC, 16'h01D2, 16'h01D4,
16'h01A7, 16'h0147, 16'h00AE, -16'h0022, -16'h0126,
-16'h0256, -16'h03A8, -16'h050E, -16'h0676, -16'h07CF,
-16'h0907, -16'h0A0F, -16'h0AD6, -16'h0B52, 16'h7483,
-16'h0B52, -16'h0AD6, -16'h0A0F, -16'h0907, -16'h07CF,
-16'h0676, -16'h050E, -16'h03A8, -16'h0256, -16'h0126,
-16'h0022, 16'h00AE, 16'h0147, 16'h01A7, 16'h01D4,
16'h01D2, 16'h01AC, 16'h016B, 16'h0118, 16'h00BE,
16'h0065, 16'h0015, -16'h002E, -16'h005F, -16'h007F,
-16'h008E, -16'h008E, -16'h0082, -16'h006D, -16'h0053,
-16'h0038, -16'h001F, -16'h0008, 16'h0009, 16'h0016,
16'h001D, 16'h0020, 16'h0020, 16'h001D, 16'h0019,
16'h0013, 16'h000E, 16'h0008, 16'h0003
},
'{ // Bandpass: 1KHz to 4KHz
-16'h0002, -16'h0010, -16'h0017, -16'h0013, 16'h0001,
16'h0025, 16'h0054, 16'h0086, 16'h00AD, 16'h00BA,
16'h00A6, 16'h0073, 16'h0031, -16'h0006, -16'h0014,
16'h001B, 16'h0089, 16'h011D, 16'h01A8, 16'h01EC,
16'h01B6, 16'h00EE, -16'h0057, -16'h01D6, -16'h032C,
-16'h03F4, -16'h03EA, -16'h030B, -16'h01A5, -16'h004A,
16'h0050, -16'h0071, -16'h02D8, -16'h06B4, -16'h0B46,
-16'h0F67, -16'h11BF, -16'h1127, -16'h0CFB, -16'h055D,
16'h04C0, 16'h0FB6, 16'h198B, 16'h2059, 16'h22C8,
16'h2059, 16'h198B, 16'h0FB6, 16'h04C0, -16'h055D,
-16'h0CFB, -16'h1127, -16'h11BF, -16'h0F67, -16'h0B46,
-16'h06B4, -16'h02D8, -16'h0071, 16'h0050, -16'h004A,
-16'h01A5, -16'h030B, -16'h03EA, -16'h03F4, -16'h032C,
-16'h01D6, -16'h0057, 16'h00EE, 16'h01B6, 16'h01EC,
16'h01A8, 16'h011D, 16'h0089, 16'h001B, -16'h0014,
-16'h0006, 16'h0031, 16'h0073, 16'h00A6, 16'h00BA,
16'h00AD, 16'h0086, 16'h0054, 16'h0025, 16'h0001,
-16'h0013, -16'h0017, -16'h0010, -16'h0002
},
'{ // Moving Average Filter
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0
}};
always@(posedge clk) begin
// Update the currently selected filter
case (sw)
4'b0000: selected_filter <= 0;
4'b0001: selected_filter <= 4;
4'b0010: selected_filter <= 3;
4'b0100: selected_filter <= 2;
4'b1000: selected_filter <= 1;
default: selected_filter <= 0;
endcase
// Hold the latest input on top of buffer (buffer[N_TAPS - 1])
if (new_packet == 1'b1) begin // New packet recieved
buffer[N_TAPS - 1] <= (input_data[0] + input_data[1]) / 2;
end
end
generate
for (genvar i = 0; i < N_TAPS - 1; i = i+1) begin
always@(posedge clk)
if (new_packet == 1'b1) begin // New packet recieved
// Shift the old packets so that buffer[N_TAPS - 1] holds the latest one
buffer[i] <= buffer[i + 1];
end
end
endgenerate
// Actually compute the output
always@(posedge clk)
if (new_packet == 1'b1 && selected_filter != 0) begin // New packet recieved
op_buffer = coeffs[selected_filter - 1][0] * buffer[N_TAPS - 1];
for (int i = 1; i < N_TAPS; i = i+1)
op_buffer = op_buffer + coeffs[selected_filter - 1][i] * buffer[N_TAPS - i - 1];
if (op_buffer[COEFF_WIDTH+DATA_WIDTH-1] == 1'b1) // If is negative
output_data = -((-op_buffer) >> COEFF_WIDTH); // Convert to +ve for shifting
else output_data <= op_buffer[COEFF_WIDTH+DATA_WIDTH-1:COEFF_WIDTH];
end
else if (new_packet == 1'b1 && selected_filter == 0)
output_data <= (input_data[0] + input_data[1]) / 2;
endmodule
```
### 4. 撰寫單通道的 FIR 後端結構 HDL 檔案 (single_channel_fir_engine_tb.sv)
```
`timescale 1ns / 1ps
module single_channel_fir_filter_tb;
reg clk;
reg [3:0] sw;
reg new_packet;
reg signed [23:0] input_data [1:0];
wire signed [23:0] output_data;
wire signed [24-1:0] buffer [89-1:0]; // Buffer for data
wire signed [16+24-1:0] op_buffer;
wire [2:0] selected_filter;
// Instantiate the fir_filter module
single_channel_fir_engine uut (
.clk(clk),
.sw(sw),
.new_packet(new_packet),
.input_data(input_data),
.output_data(output_data),
.buffer(buffer),
.op_buffer(op_buffer),
.selected_filter(selected_filter)
);
// Clock generation for the testbench
initial begin
input_data[0] <= 24'h000000;
input_data[1] <= 24'h000000;
new_packet <= 1;
sw <= 4'b0001;
clk = 0;
forever #1 clk = ~clk;
end
// Initialize inputs for the test
initial begin
// Set up initial values for inputs
#178.5
input_data[0] <= 24'hF00000;
input_data[1] <= 24'h00000F;
#1
input_data[0] <= 24'h000000;
input_data[1] <= 24'h000000;
// TODO Add more data...
end
endmodule
```
### 5. 撰寫 Pmod I2S2 所需的 HDL 檔案 (axis_i2s2.v)
>使用 I2S 通訊協定進行 FPGA 與 Pmod I2S2 的通訊
>
>Verilog IP core 內容源自於 Diligent 網站
```
`timescale 1ns / 1ps
`default_nettype none
module axis_i2s2 (
input wire axis_clk, // require: approx 22.591MHz
input wire axis_resetn,
input wire [31:0] tx_axis_s_data,
input wire tx_axis_s_valid,
output reg tx_axis_s_ready = 1'b0,
input wire tx_axis_s_last,
output wire [31:0] rx_axis_m_data,
output reg rx_axis_m_valid = 1'b0,
input wire rx_axis_m_ready,
output reg rx_axis_m_last = 1'b0,
output wire tx_mclk, // JA[0]
output wire tx_lrck, // JA[1]
output wire tx_sclk, // JA[2]
output reg tx_sdout, // JA[3]
output wire rx_mclk, // JA[4]
output wire rx_lrck, // JA[5]
output wire rx_sclk, // JA[6]
input wire rx_sdin // JA[7]
);
reg [8:0] count = 9'd0;
localparam EOF_COUNT = 9'd455; // end of full I2S frame
always@(posedge axis_clk)
count <= count + 1;
wire lrck = count[8];
wire sclk = count[2];
wire mclk = axis_clk;
assign tx_lrck = lrck;
assign tx_sclk = sclk;
assign tx_mclk = mclk;
assign rx_lrck = lrck;
assign rx_sclk = sclk;
assign rx_mclk = mclk;
/* AXIS SLAVE CONTROLLER */
reg [31:0] tx_data_l = 0;
reg [31:0] tx_data_r = 0;
always@(posedge axis_clk)
if (axis_resetn == 1'b0)
tx_axis_s_ready <= 1'b0;
else if (tx_axis_s_ready == 1'b1 && tx_axis_s_valid == 1'b1 && tx_axis_s_last == 1'b1) // end of packet, cannot accept data until current one has been transmitted
tx_axis_s_ready <= 1'b0;
else if (count == 9'b0) // beginning of I2S frame, in order to avoid tearing, cannot accept data until frame complete
tx_axis_s_ready <= 1'b0;
else if (count == EOF_COUNT) // end of I2S frame, can accept data
tx_axis_s_ready <= 1'b1;
always@(posedge axis_clk)
if (axis_resetn == 1'b0) begin
tx_data_r <= 32'b0;
tx_data_l <= 32'b0;
end else if (tx_axis_s_valid == 1'b1 && tx_axis_s_ready == 1'b1)
if (tx_axis_s_last == 1'b1)
tx_data_r <= tx_axis_s_data;
else
tx_data_l <= tx_axis_s_data;
/* I2S TRANSMIT SHIFT REGISTERS */
reg [23:0] tx_data_l_shift = 24'b0;
reg [23:0] tx_data_r_shift = 24'b0;
always@(posedge axis_clk)
if (count == 3'b000000111) begin
tx_data_l_shift <= tx_data_l[23:0];
tx_data_r_shift <= tx_data_r[23:0];
end else if (count[2:0] == 3'b111 && count[7:3] >= 5'd1 && count[7:3] <= 5'd24) begin
if (count[8] == 1'b1)
tx_data_r_shift <= {tx_data_r_shift[22:0], 1'b0};
else
tx_data_l_shift <= {tx_data_l_shift[22:0], 1'b0};
end
always@(count, tx_data_l_shift, tx_data_r_shift)
if (count[7:3] <= 5'd24 && count[7:3] >= 4'd1)
if (count[8] == 1'b1)
tx_sdout = tx_data_r_shift[23];
else
tx_sdout = tx_data_l_shift[23];
else
tx_sdout = 1'b0;
/* SYNCHRONIZE DATA IN TO AXIS CLOCK DOMAIN */
reg [2:0] din_sync_shift = 3'd0;
wire din_sync = din_sync_shift[2];
always@(posedge axis_clk)
din_sync_shift <= {din_sync_shift[1:0], rx_sdin};
/* I2S RECEIVE SHIFT REGISTERS */
reg [23:0] rx_data_l_shift = 24'b0;
reg [23:0] rx_data_r_shift = 24'b0;
always@(posedge axis_clk)
if (count[2:0] == 3'b011 && count[7:3] <= 5'd24 && count[7:3] >= 5'd1)
if (lrck == 1'b1)
rx_data_r_shift <= {rx_data_r_shift, din_sync};
else
rx_data_l_shift <= {rx_data_l_shift, din_sync};
/* AXIS MASTER CONTROLLER */
reg [31:0] rx_data_l = 32'b0;
reg [31:0] rx_data_r = 32'b0;
always@(posedge axis_clk)
if (axis_resetn == 1'b0) begin
rx_data_l <= 32'b0;
rx_data_r <= 32'b0;
end else if (count == EOF_COUNT && rx_axis_m_valid == 1'b0) begin
rx_data_l <= {8'b0, rx_data_l_shift};
rx_data_r <= {8'b0, rx_data_r_shift};
end
assign rx_axis_m_data = (rx_axis_m_last == 1'b1) ? rx_data_r : rx_data_l;
always@(posedge axis_clk)
if (axis_resetn == 1'b0)
rx_axis_m_valid <= 1'b0;
else if (count == EOF_COUNT && rx_axis_m_valid == 1'b0)
rx_axis_m_valid <= 1'b1;
else if (rx_axis_m_valid == 1'b1 && rx_axis_m_ready == 1'b1 && rx_axis_m_last == 1'b1)
rx_axis_m_valid <= 1'b0;
always@(posedge axis_clk)
if (axis_resetn == 1'b0)
rx_axis_m_last <= 1'b0;
else if (count == EOF_COUNT && rx_axis_m_valid == 1'b0)
rx_axis_m_last <= 1'b0;
else if (rx_axis_m_valid == 1'b1 && rx_axis_m_ready == 1'b1)
rx_axis_m_last <= ~rx_axis_m_last;
endmodule
```
### 6. 將所有設計檔案進行整合 (top.v)
```
`timescale 1ns / 1ps
module top #(
parameter NUMBER_OF_SWITCHES = 4,
parameter RESET_POLARITY = 0
) (
input wire clk,
input wire [NUMBER_OF_SWITCHES-1:0] sw,
input wire reset,
output wire tx_mclk,
output wire tx_lrck,
output wire tx_sclk,
output wire tx_data,
output wire rx_mclk,
output wire rx_lrck,
output wire rx_sclk,
input wire rx_data
);
reg axis_clk;
wire [23:0] axis_tx_data;
wire axis_tx_valid;
wire axis_tx_ready;
wire axis_tx_last;
wire [23:0] axis_rx_data;
wire axis_rx_valid;
wire axis_rx_ready;
wire axis_rx_last;
wire resetn = (reset == RESET_POLARITY) ? 1'b0 : 1'b1;
reg [2:0] count;
always @(posedge clk) begin
count <= count + 1;
if(count == 3) begin
count <= 0;
axis_clk <= ~axis_clk;
end
end
axis_i2s2 m_i2s2 (
.axis_clk(axis_clk),
.axis_resetn(resetn),
.tx_axis_s_data(axis_tx_data),
.tx_axis_s_valid(axis_tx_valid),
.tx_axis_s_ready(axis_tx_ready),
.tx_axis_s_last(axis_tx_last),
.rx_axis_m_data(axis_rx_data),
.rx_axis_m_valid(axis_rx_valid),
.rx_axis_m_ready(axis_rx_ready),
.rx_axis_m_last(axis_rx_last),
.tx_mclk(tx_mclk),
.tx_lrck(tx_lrck),
.tx_sclk(tx_sclk),
.tx_sdout(tx_data),
.rx_mclk(rx_mclk),
.rx_lrck(rx_lrck),
.rx_sclk(rx_sclk),
.rx_sdin(rx_data)
);
// dual_channel_fir_filter #(
single_channel_fir_filter #(
.DATA_WIDTH(24)
) m_fir (
.clk(axis_clk),
.sw(sw),
.s_axis_data(axis_rx_data),
.s_axis_valid(axis_rx_valid),
.s_axis_ready(axis_rx_ready),
.s_axis_last(axis_rx_last),
.m_axis_data(axis_tx_data),
.m_axis_valid(axis_tx_valid),
.m_axis_ready(axis_tx_ready),
.m_axis_last(axis_tx_last)
);
endmodule
```
### 7. 撰寫單通道所需的 Testbench 進行測試 (single_channel_fir_engine_tb.sv)
```
`timescale 1ns / 1ps
module single_channel_fir_filter_tb;
reg clk;
reg [3:0] sw;
reg new_packet;
reg signed [23:0] input_data [1:0];
wire signed [23:0] output_data;
wire signed [24-1:0] buffer [89-1:0]; // Buffer for data
wire signed [16+24-1:0] op_buffer;
wire [2:0] selected_filter;
// Instantiate the fir_filter module
single_channel_fir_engine uut (
.clk(clk),
.sw(sw),
.new_packet(new_packet),
.input_data(input_data),
.output_data(output_data),
.buffer(buffer),
.op_buffer(op_buffer),
.selected_filter(selected_filter)
);
// Clock generation for the testbench
initial begin
input_data[0] <= 24'h000000;
input_data[1] <= 24'h000000;
new_packet <= 1;
sw <= 4'b0001;
clk = 0;
forever #1 clk = ~clk;
end
// Initialize inputs for the test
initial begin
// Set up initial values for inputs
#178.5
input_data[0] <= 24'hF00000;
input_data[1] <= 24'h00000F;
#1
input_data[0] <= 24'h000000;
input_data[1] <= 24'h000000;
// TODO Add more data...
end
endmodule
```
### 8. 撰寫雙通道所需的 Testbench 進行測試 (dual_channel_fir_filter_tb.sv)
```
`timescale 1ns / 1ps
module dual_channel_fir_filter_tb;
reg clk;
reg [3:0] sw;
reg new_packet;
reg [23:0] input_data [1:0];
wire [23:0] output_data [1:0];
wire [24-1:0] buffer [45-1:0][1:0]; // Buffer for data
wire [16+24-1:0] op_buffer [1:0];
wire [2:0] selected_filter;
// Instantiate the fir_filter module
dual_channel_fir_engine uut (
.clk(clk),
.sw(sw),
.new_packet(new_packet),
.input_data(input_data),
.output_data(output_data),
.buffer(buffer),
.op_buffer(op_buffer),
.selected_filter(selected_filter)
);
// Clock generation for the testbench
initial begin
input_data[0] <= 24'h000000;
input_data[1] <= 24'h000000;
new_packet <= 1;
sw <= 4'b0010;
clk = 0;
forever #1 clk = ~clk;
end
// Initialize inputs for the test
initial begin
// Set up initial values for inputs
#90.5
input_data[0] <= 24'h100000;
input_data[1] <= 24'hF0000F;
#1
input_data[0] <= 24'h000000;
input_data[1] <= 24'h000000;
// TODO Add more data...
end
endmodule
```
## 三、設計結構:
### ◎ FIR 濾波器硬體結構 schematic(前端)

### ◎ FIR 濾波器硬體結構 schematic(後端)

## 四、實驗心得:
###
本次的期末專題可謂困難重重,先是在 User Guide 的部分就一籌莫展,以為只要 .xdc 改一改就可以驅動開發板上的 3.5mm 接口,使其撥放出聲音來。但在經過好長一段時間的摸索後,發現在其 constraint file 的部分有著許多我看不懂的 pin 腳名稱。後來才了解到,原來要懂得如何操作已經焊接在開發板上的晶片 "ADAU1761 Audio Codec" 才能順利進行通訊等,礙於過程較為繁雜,面臨許多不可控的因素,**在請教老師的意見後,決定先放棄此種驅動方法。**
### ◎ ADAU1761 Audio Codec

接著我試圖使用 High Level Synthesis 的方式去驅動 Pynq Z2 開發板,發揮其能運用 Python 語言進行開發板操作的功能,至此,也算是真正做到了部分 Hardware and Softwarea Co-Design 的些許內容。不過同樣受限於練習的缺乏,使用此種方式進行實作的我還是僅能挑簡單的範例學習操作,並不能親自設計出自己所想的濾波器規格與內容。
最後我決定直接下單 Pmod I2S2,這個決定也讓我真正操作完整套的 I2S 通訊協定所制定的通訊方式,並成功實踐了以往在 DSP 課程中對於此類經典濾波器的設計模型,讓其不僅僅是在軟體上活躍的演算法,而是能實際變成商品的硬體成品。
## 五、參考文獻:
###
> EEF946〈軟硬體協同設計〉課堂參考講義
> https://www.youtube.com/watch?v=10Z_WsDO_uE&list=PLeiyJAfw9D6uvsjj5ICZyI2xcpAQRdqnA&index=9&t=917s
>https://github.com/sinandredemption