45 / 52 · Concept
Synchronous Memory and Read-During-Write Contracts
Define registered read timing and collision behavior, and separate response validity from memory initialization.
Les leçons sont gratuites. Inscrivez-vous pour enregistrer vos progrès.
La traduction n’est pas encore disponible. La leçon originale est affichée. (한국어)
주소보다 먼저 정할 것: 응답 시점
메모리를 연결할 때는 용량뿐 아니라 읽기 지연과 충돌 동작을 정해야 한다. 여기서는 16개 주소에 각각 8비트를 저장하고, 하나의 상승 엣지에서 읽기 하나와 쓰기 하나를 받을 수 있는 동기식 메모리를 만든다. 두 요청은 같은 클록을 쓰지만 주소는 서로 독립이다. 용량은 16×8=128비트, 주소 폭은 비트다.
읽기 주소와 rd_en을 상승 엣지 전에 안정시키면, 그 엣지 뒤에 등록된 rd_data와 rd_valid가 나온다. 이를 등록형 읽기 1단의 지연이라고 부른다. 다른 always_ff가 같은 엣지에서 이 출력을 읽으면 갱신 전 값을 보므로, 응답을 소비하는 레지스터는 다음 상승 엣지에서 받는다. 요청 시점과 응답 관찰 시점을 파형에 따로 표시해야 한다. 새 요청은 매 클록 받을 수 있으므로 지연 1단이 처리율 1/2을 뜻하지 않는다.
같은 주소를 읽고 쓰면
엣지 직전 M[3]=8'h25이고, 주소 3을 읽으면서 8'h9A를 쓴다고 하자. read-first는 25를 응답하고 저장소를 9A로 바꾼다. write-first는 새 값 9A를 응답한다. no-change는 출력 레지스터를 유지하므로, 그 출력이 주소 3의 이전 값이라는 보장은 없다. 세 방식은 정상 동작의 서로 다른 계약이다. AMD의 RAM 동기화 모드 문서도 이 세 모드를 구분한다.
이번 예제는 read-first다. 를 엣지 직전, 를 엣지 뒤의 배열이라고 쓰면, reset이 아닌 엣지에서 임의의 주소 에 대해 다음과 같다.
충돌해도 읽기는 가 아닌 를 사용한다. 읽기 요청이 없을 때 rd_data는 유지한다.
module sync_mem16x8 (
input logic clk, rst,
input logic wr_en, rd_en,
input logic [3:0] wr_addr, rd_addr,
input logic [7:0] wr_data,
output logic [7:0] rd_data,
output logic rd_valid
);
timeunit 1ns;
timeprecision 1ps;
logic [7:0] mem [0:15];
always_ff @(posedge clk) begin
if (rst) begin
rd_valid <= 1'b0;
end else begin
rd_valid <= rd_en;
if (wr_en)
mem[wr_addr] <= wr_data;
if (rd_en)
rd_data <= mem[rd_addr];
end
end
endmodule두 대입의 오른쪽은 갱신 전 값을 사용하고, nonblocking 대입은 그 뒤 반영된다. 따라서 쓰기 문장을 읽기 문장보다 먼저 적어도 새 값을 읽는 동작으로 바뀌지 않는다. write-first가 필요하면 읽기 식을 (wr_en && wr_addr==rd_addr) ? wr_data : mem[rd_addr]로 바꾸어 의도를 드러낼 수 있다. 이 우회 경로에도 읽기 출력 레지스터는 그대로 있다.
valid와 초기화는 서로 다른 약속이다
rst는 active-high 동기 reset이다. 상승 엣지에서 rst=1이면 두 요청을 무시하고 rd_valid만 0으로 만든다. 배열과 rd_data를 0으로 채우지 않는다. 전원 투입 직후 한 번도 쓰지 않은 주소의 내용은 이 계약에서 정의하지 않는다. 시뮬레이터에서는 X가 보일 수 있고, 실제 하드웨어에서도 특정 수치를 가정할 수 없다.
rd_valid=1은 “읽기 요청의 응답 주기”라는 뜻이지 “주소가 이미 초기화되어 값이 알려져 있음”이라는 뜻은 아니다. 사용자는 먼저 쓴 주소만 읽거나, 별도의 주소별 초기화 표를 두어야 한다. 최초 쓰기와 같은 엣지에서 그 주소를 read-first로 읽어도 이전 내용은 여전히 미정이다. valid=0일 때 rd_data를 계산에 사용하지 않는 valid guard도 필요하다. 초기화된 주소만 읽는 조건과 valid guard는 서로 대체되지 않는다.
reset은 저장소를 지우지 않으므로 이미 쓴 값은 RTL상 남는다. 다만 시스템이 reset 뒤 이전 내용을 폐기하기로 했다면 외부 초기화 표도 다시 관리해야 한다. 민감한 정보를 지우는 요구는 제어 reset과 별도 설계 항목이다.
손으로 따라가는 세 엣지
E0에서 주소 3에 25를 쓰고 rd_en=0으로 둔다. E1에서 주소 3에 9A를 쓰며 같은 주소를 읽으면, E1 뒤 valid=1, data=25다. E2에서 쓰기 없이 주소 3을 다시 읽으면 data=9A다. E3에 rd_en=0이면 valid=0이고 data는 9A로 남는다. E3의 출력 버스가 9A라는 이유로 새 응답을 세면 같은 데이터를 두 번 처리한다.
주소가 다를 때는 읽기와 쓰기가 독립이다. 예를 들어 주소 7을 읽으며 주소 3을 써도 주소 7의 기존 값이 응답한다. 실제 FPGA에서 이 RTL이 어떤 RAM 자원에 대응하는지, 그 자원이 같은 주소 충돌을 어떻게 처리하는지는 합성 보고서와 해당 소자의 문서로 확인해야 한다. RTL의 의미를 정한 것과 특정 장치에서 구현을 검증한 것은 별개의 단계다.
Essayez vous-même
M[6]=8'h10이 이미 저장되어 있다. E0에서 주소 6을 읽으며 8'h20을 쓰고, E1에는 쓰기 없이 다시 읽고, E2에는 rd_en=0으로 둔다. read-first의 각 엣지 뒤 data/valid를 구하라. 같은 실험을 write-first로 바꾸면 무엇이 달라지는가? 한 번도 쓰지 않은 주소의 첫 read-first 충돌은 정의된 값을 주는가?
Lire l’explication
E0은 data=10, valid=1이고 배열은 20으로 갱신된다. E1은 data=20, valid=1이다. E2는 data=20을 유지하지만 valid=0이므로 응답으로 세지 않는다. write-first에서는 E0의 data만 20으로 달라진다. 최초 쓰기와 겹친 read-first 읽기는 쓰기 이전의 미초기화 내용을 반환하므로 특정 값을 정답으로 둘 수 없다. valid=1도 초기화를 보장하지 않는다.