Mảng và bộ nhớ: chọn word, chọn bit, chọn thời điểm

Khoảng 35 phútMức 1/3Bản nháp: một số khẳng định chưa đối chiếu spec

Sau bài này bạn sẽ

  • Đọc đúng vị trí kích thước packed/unpacked trong khai báo
  • Phân biệt dung lượng 32 bit với một vector packed 32 bit
  • Dự đoán đọc cùng địa chỉ ghi trước và sau cạnh clock của RTL cụ thể
  • Thiết kế scoreboard bắt lỗi ghi nhầm ô, write enable và ưu tiên reset
  • Phân biệt khai báo memory với kết quả mapping sang FF hoặc RAM macro

Một tủ bốn ngăn, mỗi ngăn tám bit

Hãy hình dung bốn ngăn có địa chỉ 0, 1, 2, 3; mỗi ngăn chứa một byte. Khi ghi địa chỉ 2, yêu cầu là thay đúng byte của ngăn 2, giữ nguyên ba ngăn còn lại. Khi đọc địa chỉ 1, phải lấy byte ở ngăn 1, không phải “bit số 1” của cả tủ.

Trong ví dụ, dung lượng dữ liệu là 4*8=32 bit. Địa chỉ 2 bit đủ chọn một trong bốn word. Word ở đây là đơn vị một lần đọc/ghi của thiết kế: 8 bit, không phải quy ước mọi máy đều có word 8 bit.

Ta sẽ kiểm một bộ nhớ nhỏ có ghi đồng bộ, đọc tổ hợp và reset đồng bộ. Đây là mô hình RTL cụ thể để học cách suy luận, không phải lời hứa về một SRAM/BRAM bất kỳ. Quy tắc ngôn ngữ tổng quát vẫn cần đối chiếu [1].

Kích thước nằm trước hay sau tên?

So sánh hai khai báo SystemVerilog trong testbench:

logic [3:0][7:0] packed_words;
logic [7:0] unpacked_words [0:3];
  • packed_words có hai kích thước packed trước tên. Trong khai báo này nó là một giá trị packed 32 bit, chia thành bốn nhóm 8 bit.
  • unpacked_words có phần tử 8 bit và kích thước unpacked sau tên: một mảng bốn phần tử, chỉ số 0 đến 3.
  • $bits của cả hai là 32 trong TB đã chạy. Cùng dung lượng không làm hai kiểu trở thành tương đương trong mọi phép gán, cổng module hoặc ép kiểu.

TB gán lần lượt word 0, 1, 2, 3 bằng hex 11, 22, 33, 44. Với đúng chiều chỉ số packed [3:0][7:0], word 3 chiếm byte cao nên toàn vector in ra 44332211. Không được lấy kết quả này làm quy tắc “index 0 luôn nằm bên phải” cho mọi chiều khai báo.

Mảng unpacked trong bài được ghi/đọc từng phần tử. Ta không ép cả mảng sang số nguyên rồi suy đoán thứ tự byte. Những phép chuyển kiểu và assignment pattern ngoài ví dụ vẫn cần đối chiếu LRM. [2]

Chọn word trước, chọn bit sau

Với unpacked_words[2][0], phép chọn đầu lấy phần tử số 2 (một byte), phép chọn sau lấy bit 0 của byte đó. Với dữ liệu 33 hex = 00110011, bit 0 là 1. TB cũng kiểm packed_words[2][0] bằng 1 và cả hai word 2 bằng 33.

Đọc khai báo RTL:

reg [7:0] mem [0:3];
assign rdata = mem[raddr];

raddr chọn một trong bốn byte. Output rdata rộng 8 bit. Không nhầm với reg [31:0] mem, nơi mem[raddr] chỉ chọn một bit của một vector một chiều.

Từ khóa reg trong khai báo này không tự quyết định phần tử sẽ thành flip-flop hay RAM macro. Cách đọc/ghi, clock/reset và công cụ mapping mới quyết định phần cứng suy ra. Bài dùng reg cho mảng RTL và logic cho biến TB; cả chương trình được biên dịch theo SystemVerilog.

Hợp đồng đọc và ghi của memory4

RTL của bài quy định:

  1. Chỉ cạnh lên clk mới làm thay đổi dữ liệu lưu.
  2. Khi cạnh lên thấy rst=1, cả bốn word thành 0; reset ưu tiên ghi.
  3. Nếu không reset và we=1, chỉ mem[waddr] nhận wdata bằng non-blocking assignment.
  4. Nếu cả rst và we đều 0, mọi word giữ nguyên.
  5. rdata đọc tổ hợp từ mem[raddr]: đổi địa chỉ đọc có thể đổi output mà không cần cạnh clock mới.

Reset ở đây đồng bộ: đưa rst=1 giữa hai cạnh chưa lập tức xóa dữ liệu. Việc đặt lại nhiều word diễn ra bởi cùng một cạnh trong mô hình RTL; vòng for reset không phải bốn lệnh ghi nối tiếp qua bốn chu kỳ.

Ghi dữ liệu cũng không lập tức xảy ra khi thay waddr/wdata trong pha clock thấp. TB kiểm dữ liệu cũ trước cạnh, rồi kiểm dữ liệu mới sau khi NBA đã cập nhật. Cơ chế scheduling được học sâu hơn ở A7; ở đây chỉ cần giữ rõ thời điểm quan sát.

Chạy 2051 chu kỳ với scoreboard

Tính tay trước khi chạy:

  • Reset ban đầu: 1 chu kỳ.
  • Ghi mọi cặp địa chỉ/data: 4*256=1024 chu kỳ. Cuối lượt ghi, mỗi word đều bằng ff.
  • Thử mọi cặp địa chỉ/data với we=0: 1024 chu kỳ; cả bốn word phải giữ ff.
  • Ghi riêng địa chỉ 2 bằng a5: 1 chu kỳ.
  • Reset khi vẫn yêu cầu ghi 5a vào địa chỉ 3: 1 chu kỳ; reset ưu tiên nên cả bốn word bằng 0.

Tổng 1+1024+1024+1+1=2051 chu kỳ: 1025 ghi có hiệu lực, 1024 giữ, 2 reset. Đây là mọi cặp địa chỉ/data trong chuỗi kiểm tra đã chọn, không phải vét cạn mọi trạng thái bộ nhớ 32 bit hoặc mọi chuỗi giao dịch.

Bốn word 8 bit: ghi đồng bộ, đọc tổ hợp

Phân biệt packed/unpacked và kiểm bộ nhớ nhỏ bằng scoreboard độc lập, gồm write enable, giữ word khác và reset ưu tiên.

memory4.sv RTL
`timescale 1ns/1psmodule memory4 (  input wire clk, rst, we,  input wire [1:0] waddr, raddr,  input wire [7:0] wdata,  output wire [7:0] rdata);  reg [7:0] mem [0:3];  always @(posedge clk) begin    if (rst) begin      for (integer i = 0; i < 4; i = i + 1)        mem[i] <= 8'h00;    end else if (we) begin      mem[waddr] <= wdata;    end  end  assign rdata = mem[raddr];endmodule
tb_memory.sv testbench
module tb_memory;  timeunit 1ns;  timeprecision 1ps;  logic clk = 0, rst = 0, we = 0;  logic [1:0] waddr = 0, raddr = 0;  logic [7:0] wdata = 0;  wire [7:0] rdata;  // Oracle uses integer words; it does not read DUT internals.  int reference_words [0:3];  bit initialized = 0;  int cycles = 0, writes = 0, holds = 0, resets = 0;  logic [3:0][7:0] packed_words;  logic [7:0] unpacked_words [0:3];  memory4 dut (.clk(clk), .rst(rst), .we(we), .waddr(waddr),               .raddr(raddr), .wdata(wdata), .rdata(rdata));   task automatic cycle(input bit reset_en, input bit write_en,                       input int address, input int data);    rst = reset_en; we = write_en;    waddr = 2'(address); wdata = 8'(data); raddr = 2'(address);    #2;    if (initialized && int'(rdata) != reference_words[address])      $fatal(1, "read changed before clock: cycle=%0d addr=%0d", cycles, address);    if (cycles == 2049)      $display("COLLISION BEFORE old=%0h", rdata);    clk = 1;    // Sample after NBA updates; inputs were stable before the edge.    #1;    if (reset_en) begin      for (int i = 0; i < 4; i++) reference_words[i] = 0;      resets++; initialized = 1;    end else if (write_en) begin      reference_words[address] = data; writes++;    end else holds++;    if (cycles == 2049)      $display("COLLISION AFTER new=%0h", rdata);    for (int i = 0; i < 4; i++) begin      raddr = 2'(i);      #1;      if (int'(rdata) != reference_words[i])        $fatal(1, "memory mismatch: cycle=%0d addr=%0d got=%0d expected=%0d",               cycles, i, rdata, reference_words[i]);    end    clk = 0;    cycles++;  endtask   initial begin    for (int i = 0; i < 4; i++) begin      packed_words[i] = 8'(17 * (i + 1));      unpacked_words[i] = 8'(17 * (i + 1));    end    if ($bits(packed_words) != 32 || $bits(unpacked_words) != 32 ||        packed_words != 32'h44332211 || packed_words[2] != 8'h33 ||        unpacked_words[2] != 8'h33 || packed_words[2][0] != 1'b1 ||        unpacked_words[2][0] != 1'b1)      $fatal(1, "packed/unpacked layout mismatch");    $display("LAYOUT RESULT packed=%0h word2=%0h unpacked_bits=%0d bit0=%0b",             packed_words, unpacked_words[2], $bits(unpacked_words), packed_words[2][0]);    cycle(1, 0, 0, 0);    for (int a = 0; a < 4; a++)      for (int value = 0; value < 256; value++)        cycle(0, 1, a, value);    for (int a = 0; a < 4; a++)      for (int value = 0; value < 256; value++)        cycle(0, 0, a, value);    // All four words are ff. Read/write same address: ff before, a5 after edge.    cycle(0, 1, 2, 165);    // Reset has priority even when a write of 5a is requested.    cycle(1, 1, 3, 90);    if (cycles != 2051 || writes != 1025 || holds != 1024 || resets != 2)      $fatal(1, "test sequence count mismatch");    $display("RESET RESULT priority=reset rdata=%0h", rdata);    $display("MEMORY RESULT cycles=%0d writes=%0d holds=%0d resets=%0d",             cycles, writes, holds, resets);    $finish;  endendmodule

Đã kiểm chứng thế nào

  • Phân tích và elaborate theo LRM (pyslang 12.1.0)
  • Lint -Wall (Verilator 5.050)
  • Mô phỏng, exit 0 và output khớp kỳ vọng tính tay (Verilator 5.050, 2-state)
  • Tổng hợp, số cell khớp kỳ vọng (Yosys 0.70)

Output phải khớp: LAYOUT RESULT packed=44332211 word2=33 unpacked_bits=32 bit0=1COLLISION BEFORE old=ffCOLLISION AFTER new=a5RESET RESULT priority=reset rdata=0MEMORY RESULT cycles=2051 writes=1025 holds=1024 resets=2

Kỳ vọng sau tổng hợp: memory4: $_*DFF* = 32

Verilator 2-state; chỉ đọc dữ liệu DUT sau reset, không suy ra giá trị khởi động hoặc hành vi địa chỉ X/Z.

Kiểm mọi cặp địa chỉ/data cho một lần ghi và một lần we=0 trong chuỗi cụ thể; không vét cạn mọi trạng thái bộ nhớ hoặc mọi chuỗi giao dịch.

Yosys synth generic hạ bộ nhớ này thành 32 FF; không chứng minh BRAM/SRAM inference hoặc timing trên FPGA/ASIC.

Đọc tổ hợp, ghi cạnh lên, reset đồng bộ ưu tiên ghi; đây là hợp đồng của RTL này, không phải mọi memory macro.

Các kiểm tra chạy lúc xây dựng nội dung (2026-11-06), không chạy trong trình duyệt. Kỳ vọng được tính tay trước khi chạy.

Scoreboard lưu bốn số nguyên riêng, không đọc dut.mem để tự coi kết quả của DUT là chuẩn. Sau mỗi cạnh, TB quét cả bốn địa chỉ đọc và so với mô hình. Nhờ vậy, ghi nhầm một word khác cũng bị phát hiện dù word đích có vẻ đúng. TB còn kiểm đọc trước cạnh để bắt ghi sớm, và dùng $fatal khi dữ liệu hoặc số lượt sai.

Đọc cùng địa chỉ ghi: ff hay a5?

Trước chu kỳ ghi riêng, mem[2]=ff. Đặt raddr=waddr=2, wdata=a5, we=1, rst=0 trong pha clock thấp:

  • Trước cạnh lên: bộ nhớ chưa được ghi, output là ff.
  • Sau cạnh lên và sau cập nhật NBA: mem[2]=a5, đường đọc tổ hợp phản ánh a5.

Mô phỏng thực in COLLISION BEFORE old=ff rồi COLLISION AFTER new=a5. TB đợi một bước thời gian sau cạnh trước khi so giá trị mới, tránh testbench tranh thời điểm đọc với cập nhật NBA.

Không trả lời “read-during-write luôn ra dữ liệu mới” mà bỏ thời điểm quan sát và loại cổng đọc. Nếu đổi sang output đọc có thanh ghi, hoặc dùng macro có chế độ old-data/new-data/no-change, cần hợp đồng mới và TB mới. Bằng chứng trên chỉ áp dụng cho cách gán trong memory4, không xác minh các biến thể đó. [4]

Khai báo bộ nhớ không bảo đảm suy ra BRAM

Có 32 bit dữ liệu lưu, không có nghĩa tool bắt buộc tạo một khối RAM vật lý. Với lệnh synthesis generic của pipeline hiện tại, Yosys hạ thiết kế này thành 32 cell FF theo nhóm tên $_*DFF*, kèm logic chọn đọc/ghi và reset. Kỳ vọng 32 được tính từ bốn word × tám bit trước khi chạy, không lấy số tool in ra rồi sửa expected cho khớp.

Đây là kết quả cho top memory4 trong flow đang dùng. Chưa chạy target FPGA cụ thể, không có báo cáo BRAM utilization, không có thư viện SRAM ASIC và không đo timing/PPA. Reset đồng thời toàn bộ dữ liệu và kiểu cổng đọc có thể ảnh hưởng khả năng dùng memory primitive của target; cần xem tài liệu primitive và báo cáo mapping thực tế.

Debug theo địa chỉ, dữ liệu và thời điểm

  • Đọc thành bit thay vì byte: kiểm tra chiều packed/unpacked và độ rộng output, đừng chỉ nhìn tên mem.
  • Ghi phá word khác: scoreboard phải quét cả các word không được ghi.
  • Bỏ qua write enable: thử nhiều data với we=0; không chỉ thử ghi rồi đọc lại một lần.
  • Sai ưu tiên reset: kiểm rst=1 và we=1 cùng cạnh, không chỉ reset riêng rẽ.
  • TB so quá sớm: xác định đang quan sát trước cạnh, tại vùng cập nhật nào hay sau NBA. Đổi expected thành dữ liệu cũ để che lỗi sampling sẽ làm sai hợp đồng.
  • Tin giá trị khởi động bằng 0: bài không kiểm DUT trước reset; simulator 2-state không chứng minh giá trị khởi động của silicon hoặc hành vi X/Z.
  • Gọi cả bộ nhớ là đã vét cạn: 1024 cặp ghi không phủ mọi chuỗi đọc/ghi và mọi trạng thái 32 bit.

Tự kiểm tra

Chọn đúng word và đúng thời điểm

  1. reg [7:0] mem [0:3] trong bài chứa bao nhiêu word và bao nhiêu bit dữ liệu?
  2. packed_words có kiểu logic [3:0][7:0], word 0..3 lần lượt là 11,22,33,44 hex. In toàn vector dạng hex được gì?
  3. unpacked_words[2] bằng 33 hex. unpacked_words[2][0] chọn gì và bằng bao nhiêu?
  4. memory4 đang có mem[2]=ff; raddr=waddr=2, we=1, rst=0, wdata=a5. Đọc trước cạnh lên rồi sau NBA cho gì?
  5. Tại cạnh lên cuối bài, rst=1 và we=1, yêu cầu ghi 5 a vào word 3. Sau NBA, word 3 là gì?
  6. Yosys generic trong bài cho 32 FF. Có thể kết luận điều gì?

Checklist khi review một bộ nhớ nhỏ

Ghi rõ số word, số bit mỗi word, miền địa chỉ, kiểu đọc, cạnh ghi, write enable, reset và ưu tiên. Tính một tình huống đọc cùng địa chỉ ghi ở hai thời điểm; sau đó dùng mô hình độc lập kiểm cả word đích lẫn word phải giữ.

Cuối cùng đọc báo cáo synthesis cho đúng target. Bài đã kiểm layout, chuỗi 2051 chu kỳ và mapping generic 32 FF; chưa kiểm X/Z, RAM macro, timing hay toàn bộ quy tắc array của LRM. Các nhãn draft/needs-spec-check vẫn giữ để người học thấy đúng phạm vi bằng chứng.

Nguồn và mức độ đối chiếu

  1. IEEE 1800-2023 SystemVerilog LRM, LRM về arrays và assignments. Chưa đối chiếu với văn bản nguồn (chưa mở).
  2. Thử nghiệm arrays/memory: content/code/ex.a6.arrays-memories/verification.json, TB kiểm layout. Trích dẫn; nguồn đã đọc toàn văn.
  3. Thử nghiệm arrays/memory: content/code/ex.a6.arrays-memories/verification.json, Bằng chứng tool. Trích dẫn; nguồn đã đọc toàn văn.
  4. Thử nghiệm arrays/memory: content/code/ex.a6.arrays-memories/verification.json, Quan sát trước/sau cạnh. Trích dẫn; nguồn đã đọc toàn văn.
  5. IEEE 1800-2023 SystemVerilog LRM, Packed and unpacked arrays; memory indexing; nonblocking assignments. Chưa đối chiếu với văn bản nguồn (chưa mở).
  6. Thử nghiệm arrays/memory: content/code/ex.a6.arrays-memories/verification.json, memory4.sv declaration; Yosys 32 FF check. Trích dẫn; nguồn đã đọc toàn văn.
  7. Thử nghiệm arrays/memory: content/code/ex.a6.arrays-memories/verification.json, verification.json: LAYOUT RESULT. Trích dẫn; nguồn đã đọc toàn văn.
  8. Thử nghiệm arrays/memory: content/code/ex.a6.arrays-memories/verification.json, tb_memory.sv layout assertions. Trích dẫn; nguồn đã đọc toàn văn.
  9. Thử nghiệm arrays/memory: content/code/ex.a6.arrays-memories/verification.json, verification.json: COLLISION BEFORE and AFTER. Trích dẫn; nguồn đã đọc toàn văn.
  10. Thử nghiệm arrays/memory: content/code/ex.a6.arrays-memories/verification.json, tb_memory.sv final reset cycle; RESET RESULT. Trích dẫn; nguồn đã đọc toàn văn.
  11. Thử nghiệm arrays/memory: content/code/ex.a6.arrays-memories/verification.json, meta.yaml synth expectation and verification.json. Trích dẫn; nguồn đã đọc toàn văn.
  12. IEEE 1800-2023 SystemVerilog LRM, Packed and unpacked arrays; array indexing; nonblocking assignments. Chưa đối chiếu với văn bản nguồn (chưa mở). Chưa đối chiếu đầy đủ LRM; bài giữ draft, bằng chứng tool chỉ áp dụng cho chương trình đã chạy.
  13. Thử nghiệm arrays/memory: content/code/ex.a6.arrays-memories/verification.json, content/code/ex.a6.arrays-memories/verification.json và tb_memory.sv. Trích dẫn; nguồn đã đọc toàn văn.