RAM, ROM và LIFO: phần tử nhớ phải có hợp đồng đọc

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

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

  • Định nghĩa latency và read-during-write của RAM thay vì đoán theo tên
  • Phân biệt ROM chọn dữ liệu hằng với state stack
  • Kiểm biên full/empty và độ rộng địa chỉ khi LIFO đạt capacity

Một array không cho biết mọi hành vi memory

Khai báo mem 4 x 4 chỉ cho biết hình dạng dữ liệu. Nó chưa trả lời đọc tổ hợp hay đăng ký, giá trị khi đọc lúc ghi cùng địa chỉ, reset xóa những gì, hoặc target dùng FF hay SRAM macro. Bài tách RAM, ROM và stack để các câu hỏi ấy có contract cụ thể.

Depth, width và loại port

RAM có 4 word, mỗi word 4 bit; địa chỉ 2 bit chọn 0..3. Chỉ một địa chỉ chung được dùng cho đọc/ghi trong ví dụ. Không suy từ chữ single-port rằng mọi primitive đều có cùng hành vi read-during-write hoặc cùng kiểu data bus. Nguồn đào tạo dùng một giao diện khác; code dự án dùng input/output tách để tập trung vào semantics. [1]

Đọc đăng ký và initialization có chủ ý

Ở mỗi posedge, q lấy mem[addr] của lần đọc; write enable quyết định có ghi word hay không. Array không bị reset hàng loạt. TB chủ động ghi cả 4 địa chỉ trước khi dùng nội dung đã biết, và dùng Icarus để xác nhận các lần đọc word chưa khởi tạo vẫn X. Reset một output hoặc pointer không tự chứng minh memory đã được xóa.

Cùng địa chỉ ghi, output cũ hay mới?

Hai NBA trong cùng process của RAM lấy RHS trước khi cập nhật array: overwrite địa chỉ 2 từ 7 thành 14, q của cạnh ghi vẫn 7; lần đọc sau mới 14. Đây là old-data/read-first của RTL đang xét. Nếu mapping sang memory primitive, mode hỗ trợ và collision behavior phải được đối chiếu; không suy mọi FPGA/ASIC sẽ giữ cùng mode chỉ vì RTL simulation pass.

ROM hằng chưa có state cập nhật

ROM nhỏ chọn 2/7/1/f theo bốn địa chỉ nhị phân, được mô tả bằng case đầy đủ. Test kiểm cả 4 entry. Generic synthesis không cần FF/latch cho bảng hằng này; nhưng chưa có claim đã dùng đúng một block ROM vật lý. Cũng không claim mọi địa chỉ X/Z được xử lý như một mạch có thể phát hiện unknown ngoài silicon.

LIFO trả item mới nhất trước

Stack chứa tối đa 4 nibble. Push A/B/C/D, sau đó pop phải cho D/C/B/A. Count biểu diễn 0..4, nên cần 3 bit dù địa chỉ array chỉ 2 bit. Khi full, push bị chặn; khi empty, pop bị chặn và popped giữ kết quả trước. Cả push và pop cùng 1 được contract bài này định nghĩa là không làm gì; không mặc định mọi LIFO có chính sách đó. [2]

Ngữ cảnh biểu thức địa chỉ có thể làm lộ lỗi ở full

Prototype ban đầu dùng trực tiếp count[1: 0]-2'd1 làm index. Khi count 4, pop đọc X thay vì D. Probe riêng in biểu thức có 2 bit/giá trị 11 nhưng read array trực tiếp vẫn X, trong khi qua wire 2 bit cho D. Bản sửa khai báo top_index2 bit rõ ràng rồi dùng nó cho array; không đổi expected hoặc chấp nhận X. Không dùng kết quả bits ở một context để mặc định mọi context khác có cùng sizing; quy tắc đầy đủ vẫn needs-spec-check.

Kiểm RAM, ROM và 14 ca stack

Expected tính trước gồm RAM 5/6/7/8, overwrite old 7/new 14; ROM 2/7/1/15; stack 14 check có full reject, empty reject, simultaneous ignore, thứ tự D/C/B/A và reset cuối 0.

Thí nghiệm memories

Trước chạy:RAM4word5/6/7/8, overwriteaddr 2 trảold 7 rồinew 14; ROM 2/7/1/15;LIFO 14 checkpopsd/c/b/a, chặn full/empty/simultaneous và reset cuối 0.

memory_blocks.sv RTL
`timescale 1ns/1psmodule sync_ram(input wire clk,we,input wire[1:0] addr,input wire[3:0] data,output reg[3:0] q);  reg[3:0] mem[0:3];  always @(posedge clk) begin    if(we) mem[addr]<=data;    q<=mem[addr]; // Old data on a same-address write in this RTL model.  endendmodulemodule small_rom(input wire[1:0] addr,output reg[3:0] q);  always @* case(addr)    0:q=2;1:q=7;2:q=1;default:q=15;  endcaseendmodulemodule small_lifo(input wire clk,rst_n,push,pop,input wire[3:0] data,  output reg[3:0] popped,output reg[2:0] count,output wire full,empty);  reg[3:0] mem[0:3];  assign full=count==4;  assign empty=count==0;  wire[1:0] top_index=count[1:0]-2'd1;  always @(posedge clk) begin    if(!rst_n) begin count<=0;popped<=0;end    else if(push&&!pop&&!full) begin mem[count[1:0]]<=data;count<=count+3'd1;end    else if(pop&&!push&&!empty) begin popped<=mem[top_index];count<=count-3'd1;end  endendmodule
tb_memories.sv testbench
`timescale 1ns/1psmodule tb_memories;  reg clk=0,we=0,rst_n=0,push=0,pop=0;  reg[1:0] addr=0;  reg[3:0] data=0,stack_data=0;  wire[3:0] ram_q,rom_q,popped;  wire[2:0] count;  wire full,empty;  integer i,stack_checks=0;  sync_ram ram(clk,we,addr,data,ram_q);  small_rom rom(addr,rom_q);  small_lifo stack(clk,rst_n,push,pop,stack_data,popped,count,full,empty);  always #1 clk=~clk;  task automatic ram_cycle(input reg write_it,input reg[1:0] address,input reg[3:0] value,want);    @(negedge clk);we=write_it;addr=address;data=value;    @(posedge clk);#0.1;    if(ram_q!==want) $fatal(1,"RAM mismatch addr=%0d q=%h expected=%h",address,ram_q,want);  endtask  task automatic stack_cycle(input reg reset_n,push_it,pop_it,input reg[3:0] value,input reg[2:0] expected_count,input reg[3:0] expected_pop);    @(negedge clk);rst_n=reset_n;push=push_it;pop=pop_it;stack_data=value;    @(posedge clk);#0.1;    if(count!==expected_count||popped!==expected_pop||full!==(expected_count==4)||empty!==(expected_count==0))      $fatal(1,"LIFO mismatch count=%0d popped=%h",count,popped);    stack_checks=stack_checks+1;  endtask  initial begin    for(i=0;i<4;i=i+1) ram_cycle(1,i[1:0],4'(i+5),4'bxxxx);    for(i=0;i<4;i=i+1) ram_cycle(0,i[1:0],0,4'(i+5));    ram_cycle(1,2,14,7);    ram_cycle(0,2,0,14);    $display("MEMORY RAM initialized=4 overwrite_old=7 overwrite_new=%0d",ram_q);    for(i=0;i<4;i=i+1) begin      addr=i[1:0];#0.1;      case(i)        0:if(rom_q!==2)$fatal(1,"ROM entry0");        1:if(rom_q!==7)$fatal(1,"ROM entry1");        2:if(rom_q!==1)$fatal(1,"ROM entry2");        3:if(rom_q!==15)$fatal(1,"ROM entry3");      endcase    end    $display("MEMORY ROM entries=4 last=%0d",rom_q);    stack_cycle(0,0,0,0,0,0);    stack_cycle(1,0,1,0,0,0);    stack_cycle(1,1,0,10,1,0);    stack_cycle(1,1,0,11,2,0);    stack_cycle(1,1,0,12,3,0);    stack_cycle(1,1,0,13,4,0);    stack_cycle(1,1,0,14,4,0);    stack_cycle(1,1,1,15,4,0);    stack_cycle(1,0,1,0,3,13);    stack_cycle(1,0,1,0,2,12);    stack_cycle(1,0,1,0,1,11);    stack_cycle(1,0,1,0,0,10);    stack_cycle(1,0,1,0,0,10);    stack_cycle(0,1,1,15,0,0);    if(stack_checks!=14) $fatal(1,"missing LIFO cases");    $display("MEMORY LIFO checks=%0d pop_order=d/c/b/a final_count=%0d",stack_checks,count);    $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 (Icarus Verilog 11.0, 4-state)
  • Tổng hợp, số cell khớp kỳ vọng (Yosys 0.70)

Output phải khớp: MEMORY RAM initialized=4 overwrite_old=7 overwrite_new=14MEMORY ROM entries=4 last=15MEMORY LIFO checks=14 pop_order=d/c/b/a final_count=0

Kỳ vọng sau tổng hợp: sync_ram: $_*DFF* = 20sync_ram: $_*DLATCH* = 0small_rom: $_*DFF* = 0small_rom: $_*DLATCH* = 0small_lifo: $_*DFF* = 23small_lifo: $_*DLATCH* = 0

RAM old-data là contract của RTL này, không phải mọi primitive vendor. Generic memory mapping thành FF, không claim BRAM/SRAM inference hoặc target area.

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.

Yosys generic kiểm RAM 20 FF (16 data+4 output), ROM 0 FF/0 latch và LIFO 23 FF (16 data+3 count+4 output). Đây không phải số macro SRAM/BRAM hoặc target area. [3]

Tự kiểm tra

Tự kiểm tra: memories

  1. Ghi đè addr 2 từ 7 thành 14, q RAM ở cạnh ghi và lần đọc sau là gì?
  2. Push A/B/C/D rồi pop 4 lần, thứ tự đúng là gì?
  3. Stack depth 4 cần count biểu diễn 0..4 có tối thiểu bao nhiêu bit?
  4. Cả push/pop cùng 1 trong LIFO bài này được xử lý thế nào?
  5. Yosys generic RAM 20 FF có chứng minh đã dùng một BRAM vật lý không?

Viết bảng cycle trước khi chọn primitive

Ghi latency, collision mode, reset, số entry hợp lệ và chính sách biên. Dùng dữ liệu khác nhau để bắt nhầm thứ tự, kiểm đủ địa chỉ nhỏ và đối chiếu mapping khi có target. Bài giữ draft/LRM needs-spec-check.

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

  1. Verilog Single Port RAM — bản chụp trong resources.zip, Depth và width. Trích dẫn; nguồn đã đọc các mục liên quan.
  2. Verilog Stack or LIFO — bản chụp trong resources.zip, LIFO operations. Trích dẫn; nguồn đã đọc các mục liên quan.
  3. Thí nghiệm nguyên bản ex.a8.memories, Bằng chứng. Trích dẫn; nguồn đã đọc toàn văn.
  4. IEEE 1800-2023 SystemVerilog LRM, memories semantics. Chưa đối chiếu với văn bản nguồn (chưa mở).
  5. Thí nghiệm nguyên bản ex.a8.memories, Code, metadata, self-checks và verification.json. Trích dẫn; nguồn đã đọc toàn văn.
  6. IEEE 1800-2023 SystemVerilog LRM, memories RTL/simulation semantics. Chưa đối chiếu với văn bản nguồn (chưa mở).
  7. Verilog Single Port RAM — bản chụp trong resources.zip, What is a single port RAM?. Trích dẫn; nguồn đã đọc các mục liên quan.
  8. Verilog Stack or LIFO — bản chụp trong resources.zip, What is a stack or LIFO?; How does it work?. Trích dẫn; nguồn đã đọc các mục liên quan.
  9. Thí nghiệm nguyên bản ex.a8.memories, Source, assertions, metadata và verification.json. Trích dẫn; nguồn đã đọc toàn văn.