Altifigence Academy

45 / 52 · 概念

Synchronous Memory and Read-During-Write Contracts

Define registered read timing and collision behavior, and separate response validity from memory initialization.

译文尚未提供,以下显示课时原文。 (한국어)

주소보다 먼저 정할 것: 응답 시점

메모리를 연결할 때는 용량뿐 아니라 읽기 지연과 충돌 동작을 정해야 한다. 여기서는 16개 주소에 각각 8비트를 저장하고, 하나의 상승 엣지에서 읽기 하나와 쓰기 하나를 받을 수 있는 동기식 메모리를 만든다. 두 요청은 같은 클록을 쓰지만 주소는 서로 독립이다. 용량은 16×8=128비트, 주소 폭은 log216=4\log_2 16=4비트다.

읽기 주소와 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다. MM^{-}를 엣지 직전, M+M^{+}를 엣지 뒤의 배열이라고 쓰면, reset이 아닌 엣지에서 임의의 주소 aa에 대해 다음과 같다.

M+[a]={wr_data,wr_en=1a=wr_addrM[a],otherwiseM^{+}[a]=\begin{cases} \mathrm{wr\_data}, & \mathrm{wr\_en}=1\land a=\mathrm{wr\_addr}\\ M^{-}[a], & \text{otherwise} \end{cases}
rd_data+={M[rd_addr],rd_en=1rd_data,rd_en=0rd_valid+=rd_en\begin{aligned} \mathrm{rd\_data}^{+}&=\begin{cases} M^{-}[\mathrm{rd\_addr}], & \mathrm{rd\_en}=1\\ \mathrm{rd\_data}^{-}, & \mathrm{rd\_en}=0 \end{cases}\\ \mathrm{rd\_valid}^{+}&=\mathrm{rd\_en} \end{aligned}

충돌해도 읽기는 M+M^{+}가 아닌 MM^{-}를 사용한다. 읽기 요청이 없을 때 rd_data는 유지한다.

SystemVerilog
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과 별도 설계 항목이다.

각 열은 한 상승 에지의 관찰 기록입니다. 입력은 에지 직전, 이름에 after가 붙은 상태는 갱신 직후 값입니다. 열의 정렬은 샘플 순서를 나타내며 물리적인 전파 지연을 그린 것이 아닙니다. 모든 읽기·쓰기 주소는 3입니다. E0에서 25를 처음 쓰고 E1에서 9A로 덮어쓰며 읽습니다. data의 대시는 미정 값이고, valid=0인 버스는 응답으로 세지 않습니다. 데이터 표기는 16진수입니다.

read-first 충돌에서 이전 값을 읽기
View waveform data
Wave data: each character is one interval; a dot holds the previous state; p is a clock cycle.
SignalWaveBus values
edge2345E0 → E1 → E2 → E3
wr_en1.0.
wr_data23..25 → 9A
rd_en01.0
M[3] after23..25 → 9A
rd_data after234.- → 25 → 9A
rd_valid after01.0

손으로 따라가는 세 엣지

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의 의미를 정한 것과 특정 장치에서 구현을 검증한 것은 별개의 단계다.

自己试试

M[6]=8'h10이 이미 저장되어 있다. E0에서 주소 6을 읽으며 8'h20을 쓰고, E1에는 쓰기 없이 다시 읽고, E2에는 rd_en=0으로 둔다. read-first의 각 엣지 뒤 data/valid를 구하라. 같은 실험을 write-first로 바꾸면 무엇이 달라지는가? 한 번도 쓰지 않은 주소의 첫 read-first 충돌은 정의된 값을 주는가?

阅读解释

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도 초기화를 보장하지 않는다.

你的选择适用于此浏览器,可随时在页脚更改。