56 / 63 · 개념
읽기 포트 2개·쓰기 포트 1개인 레지스터 파일과 bypass
독립된 두 읽기 포트와 동기식 쓰기를 구현하고, 주소 충돌에 따른 bypass와 초기화·valid 처리 규칙을 확인합니다.
내용은 무료로 볼 수 있습니다. 수강 신청하면 학습 기록을 저장할 수 있어요.
두 값을 동시에 꺼내는 저장소
연산기에서 A+B를 계산하려면 같은 주기에 두 피연산자가 모두 있어야 합니다. 2-read/1-write 레지스터레지스터 여러 비트의 상태를 저장하는 회로입니다. 이 과정의 동기식 레지스터는 클록 에지에서 정해진 입력을 저장합니다. 자세히 보기 파일은 읽기 주소 두 개와 쓰기 주소 한 개를 제공합니다. 여기서는 16비트 단어 8개를 저장하고, 두 읽기 결과도 상승 에지에서 레지스터에 저장합니다. 주소 폭은 3비트이고 저장 용량은 8×16=128비트입니다. 주소 0도 일반 저장소이며, 상수 레지스터로 취급하는 특별한 규칙은 없습니다.
읽기 포트마다 enable과 valid를 따로 둡니다. 한 포트가 쉬어도 다른 포트는 읽을 수 있고, 두 포트가 같은 주소를 읽어도 됩니다. 서로 다른 두 주소를 동시에 읽으려면 저장 용량을 늘릴 때와는 다른 추가 자원이 필요합니다. 작은 회로는 플립플롭플립플롭 클록의 지정된 에지에서 입력을 저장하고 다음 에지까지 유지하는 소자입니다. 입력은 setup·hold 조건을 만족해야 합니다. 자세히 보기 배열과 두 개의 선택 회로로 구현할 수 있습니다. RAM을 이용한다면 읽기 포트를 확보하기 위해 저장소를 복제하고 각 저장소에 쓰기를 함께 전달하는 구성도 고려합니다. 어떤 자원으로 합성되는지는 RTL 선언만으로 확정할 수 없습니다.
읽는 주소에 동시에 값을 쓰면
이번 설계에서는 같은 에지에서 같은 주소를 읽고 쓰면 새 쓰기 데이터를 반환합니다. 저장 배열이 갱신되기를 기다리지 않고 wr_data를 읽기 출력 레지스터의 입력으로 직접 보내는 bypass 경로를 둡니다. 포트 의 충돌 조건은 다음과 같이 정의합니다.
일 때 응답은 다음과 같습니다.
는 에지 직전에 저장된 값입니다. 는 각 포트의 읽기 주소를 따로 비교해 구합니다. 한 포트에서 충돌했다고 두 출력 모두에 쓰기 데이터를 보내면, 다른 주소의 피연산자까지 바뀝니다. 또 wr_en을 검사하지 않으면 실제 쓰기가 없는 주기에도 wr_addr가 같다는 이유만으로 잘못된 우회가 일어납니다.
module regfile8x16_2r1w (
input logic clk, rst, wr_en,
input logic [2:0] wr_addr,
input logic [15:0] wr_data,
input logic rd_en0, rd_en1,
input logic [2:0] rd_addr0, rd_addr1,
output logic [15:0] rd_data0, rd_data1,
output logic rd_valid0, rd_valid1
);
timeunit 1ns;
timeprecision 1ps;
logic [15:0] mem [0:7];
always_ff @(posedge clk) begin
if (rst) begin
rd_valid0 <= 1'b0;
rd_valid1 <= 1'b0;
end else begin
rd_valid0 <= rd_en0;
rd_valid1 <= rd_en1;
if (wr_en)
mem[wr_addr] <= wr_data;
if (rd_en0)
rd_data0 <= (wr_en && wr_addr == rd_addr0)
? wr_data : mem[rd_addr0];
if (rd_en1)
rd_data1 <= (wr_en && wr_addr == rd_addr1)
? wr_data : mem[rd_addr1];
end
end
endmodule이 bypass는 입력 선택만 바꾸며 출력 레지스터 단계는 그대로 둡니다. 주소와 요청을 에지 전에 주면 그 에지 뒤에 응답을 관찰할 수 있습니다. 다음 회로의 레지스터는 다음 상승 에지에서 응답을 받습니다. 읽기를 조합회로로 처리하도록 바꾸면 주소 변화가 출력으로 전달되는 경로와 읽기 지연까지 달라집니다. 따라서 단순히 코드를 줄이는 변경으로 취급하면 안 됩니다.
예제: 한 포트만 충돌하는 경우
이전에 M[2]=12, M[5]=7을 써 두었다고 합시다. E0 직전에 두 읽기 enable을 켜고 rd_addr0=2, rd_addr1=5, wr_en=1, wr_addr=2, wr_data=20을 줍니다. E0 뒤 포트 0은 bypass로 전달된 20을, 포트 1은 저장된 7을 반환합니다. 두 유효 응답을 더하면 27입니다. bypass가 없는 read-first 구현에서는 19가 되므로 두 동작을 구분할 수 있습니다.
E1에는 두 읽기 주소를 모두 2로 하고 그 주소에 30을 씁니다. 두 출력 모두 30이 됩니다. E2에는 wr_en=0, wr_addr=2, wr_data=99를 둔 채 주소 2를 읽습니다. 이때 두 출력은 30이어야 합니다. 주소가 같다는 이유만으로 우회하는 오류를 이 한 주기에서 확인할 수 있습니다. E3에 rd_en0=0, rd_en1=1이면 포트 0의 valid는 0이고 포트 1의 valid는 1입니다.
각 열은 한 상승 에지상승 에지 클록이 0에서 1로 바뀌는 순간입니다. CLK=1인 구간 전체를 뜻하는 레벨과 구별합니다. 자세히 보기에서 관찰한 값을 보여 줍니다. 입력은 에지 직전의 값이고, 이름에 after가 붙은 상태는 갱신 직후의 값입니다. 열은 샘플링 순서대로 놓았으며, 실제 전파 지연전파 지연 입력이 바뀐 뒤 출력이 올바른 값으로 안정되기까지 걸리는 시간입니다. 논리식의 등가성과 시간 특성은 별개입니다. 자세히 보기을 나타내지는 않습니다. 미리 M[2]=12, M[5]=7을 저장했고 두 읽기 enable은 항상 1입니다. 쓰기 주소는 2입니다. 마지막 열에서는 wr_en=0이므로 wr_data=99를 우회시키면 안 됩니다.
파형 데이터 보기
| 신호 | 파형 | 버스 값 |
|---|---|---|
| edge | 234 | E0 → E1 → E2 |
| wr_en | 1.0 | |
| wr_data | 234 | 20 → 30 → 99 |
| rd_addr0 | 2.. | 2 |
| rd_addr1 | 23. | 5 → 2 |
| out0 after | 23. | 20 → 30 |
| out1 after | 23. | 7 → 30 |
무엇을 초기화해야 하는가
active-high 동기 reset은 쓰기를 막고 두 valid를 0으로 만듭니다. 배열과 출력 데이터는 reset하지 않습니다. 따라서 reset 뒤 출력 버스가 이전 값을 유지해도 valid=0이면 정상입니다. 이미 저장된 배열의 값은 이 RTL의 reset으로 지워지지 않습니다. 시스템이 reset 이후 새로운 작업만 허용한다면 그 작업에 필요한 초기화 순서를 따로 정해야 합니다.
처음 쓰는 주소를 같은 에지에서 읽으면 이 구현은 bypass를 통해 새 값을 반환할 수 있습니다. 하지만 쓰기 없이 한 번도 초기화하지 않은 주소를 읽으면 값은 여전히 미정입니다. rd_valid는 요청에 응답하는 시점을 나타내며, 주소별 초기화 여부를 나타내는 initialized 비트가 아닙니다. 검증기는 읽기 예상값을 만들기 전에 해당 주소의 값을 알 수 있는지 확인하고, 같은 에지에서 bypass하는 경우를 따로 처리해야 합니다.
검증할 항목은 두 읽기 enable의 4가지 조합, 두 주소가 같은 경우와 다른 경우, 쓰기와 충돌하는 포트가 없는 경우·0번·1번·둘 다인 경우, 연속 쓰기, reset 우선순위입니다. 이 강의에서는 쓰기 포트가 여러 개인 설계는 다루지 않습니다. 쓰기 포트를 추가한다면 같은 주소에 두 쓰기가 겹칠 때의 우선순위부터 새로 정해야 합니다. 포트 수가 바뀌면 동작 규칙과 필요한 자원이 함께 달라집니다.
직접 생각해 보기
M[1]=4, M[4]=9인 상태입니다. 두 읽기 enable이 1이고 주소가 각각 1과 4일 때 주소 4에 13을 쓰면, 두 출력은 각각 얼마인가요? 다음 에지에 wr_en=0, wr_addr=1, wr_data=99, 두 읽기 주소=1이면 어떻게 되나요? 마지막으로 포트 0의 enable만 0으로 만들었을 때 valid의 값과 출력 데이터를 사용하는 조건을 설명하세요.
해설 보기
첫 응답은 (4,13)입니다. 포트 1만 쓰기 주소와 충돌하므로 새 값 13을 우회 경로로 보냅니다. 다음 응답은 (4,4)이며 wr_en=0이므로 99는 사용하지 않습니다. 마지막 주기에는 rd_valid0=0, rd_valid1=1입니다. 포트 0의 데이터 레지스터는 이전 값을 유지하지만 유효한 응답으로 사용해서는 안 됩니다. 주소 0도 다른 주소와 똑같이 쓰고 읽을 수 있습니다.