[RTLLM-p022] Memory/shifter/right_shifter 的題解
記住 只 在沒有思路時使用題解,不要從它複製貼上程式碼。請尊重題目和題解的作者。
在真正解決題目之前提交題解的程式碼是可以封禁的罪行。
在真正解決題目之前提交題解的程式碼是可以封禁的罪行。
作者:
官方解法
1. 核心思路
本題是一個 Serial-In Parallel-Out 右移暫存器。
輸入資料 d 每次只提供一個位元,而輸出 q 可以同時觀察全部八個暫存器位元。
每個時脈上升沿需要完成兩個動作:
- 將原本的
q向右移動一位。 - 將
d放入最高位元q[7]。
2. 使用串接運算子
最清楚的實作方式為:
q <= {d, q[7:1]};
其中:
d
成為新的 q[7],而:
q[7:1]
成為新的 q[6:0]。
原本的 q[0] 不會出現在新值中,因此會被捨棄。
3. 使用右移運算子的方式
也可以依照題目描述寫成:
q <= q >> 1;
q[7] <= d;
由於兩個敘述都使用 nonblocking assignment,右側運算式都會讀取時脈上升沿之前的舊值。
第二個敘述會更新 q[7],因此模擬結果可符合需求。
不過,同一個 always 區塊中對整個 q 以及 q[7] 分別賦值較不清楚,也可能造成部分綜合工具或程式檢查工具的警告。
因此,建議使用單一串接賦值:
q <= {d, q[7:1]};
4. 完整 Verilog 解答
module right_shifter (
input clk,
input d,
output reg [7:0] q
);
initial begin
q = 8'b00000000;
end
always @(posedge clk) begin
q <= {d, q[7:1]};
end
endmodule
5. 程式碼說明
模組介面
module right_shifter (
input clk,
input d,
output reg [7:0] q
);
q 在 always 區塊中被賦值,因此必須宣告為 reg。
初始值
initial begin
q = 8'b00000000;
end
模擬開始時,將所有位元初始化為零。
由於題目沒有提供 Reset 輸入,因此需要使用 initial 完成初始化。
時脈觸發
always @(posedge clk)
表示只有在 clk 從低電位變成高電位時,才更新 q。
右移操作
q <= {d, q[7:1]};
例如,若目前:
q = 10110110
d = 0
則下一個時脈上升沿之後:
q = 01011011
若 d = 1,則結果為:
q = 11011011
6. Nonblocking Assignment
時序邏輯通常應使用:
<=
而不是:
=
Nonblocking assignment 可以正確描述所有正反器在同一個時脈邊緣同時更新的行為。
7. 另一種等價寫法
以下寫法也可清楚描述每個位元的移動:
always @(posedge clk) begin
q[7] <= d;
q[6] <= q[7];
q[5] <= q[6];
q[4] <= q[5];
q[3] <= q[4];
q[2] <= q[3];
q[1] <= q[2];
q[0] <= q[1];
end
但使用串接運算子的版本更簡潔:
q <= {d, q[7:1]};
8. 驗證重點
測試程式應涵蓋:
- 初始值是否為
00000000 - 連續輸入零
- 連續輸入一
- 交錯輸入零與一
- 先輸入一再輸入零
- 超過八個週期的連續移位
- 原本的最低位元是否正確被捨棄
d是否只在時脈上升沿被取樣d在非時脈邊緣改變時,q是否保持不變
9. 總結
本題可使用一行時序賦值完成:
q <= {d, q[7:1]};
這行程式碼會:
- 將
d放入最高位元 - 將原本的
q[7:1]移到q[6:0] - 捨棄原本的
q[0]
評論