[RTLLM-p022] Memory/shifter/right_shifter 的題解


記住 在沒有思路時使用題解,不要從它複製貼上程式碼。請尊重題目和題解的作者。
在真正解決題目之前提交題解的程式碼是可以封禁的罪行。

作者: m1145505

官方解法

1. 核心思路

本題是一個 Serial-In Parallel-Out 右移暫存器。

輸入資料 d 每次只提供一個位元,而輸出 q 可以同時觀察全部八個暫存器位元。

每個時脈上升沿需要完成兩個動作:

  1. 將原本的 q 向右移動一位。
  2. 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
);

qalways 區塊中被賦值,因此必須宣告為 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]

評論

目前沒有評論。