Altifigence Academy

48 / 52 · 概念

FIFO and Memory Verification with an Independent Scoreboard

Check FIFO behavior with an independent queue model and plan boundary, reset, validity, and memory-initialization tests.

尚無譯文,以下顯示課程原文。 (한국어)

정답을 만드는 회로는 DUT와 다르게

검증기는 계약의 예상값을 DUT와 비교한다. FIFO 내부의 rptr, wptr를 복사해 예상값을 계산하면 같은 포인터 오류를 함께 재현할 수 있다. 독립 scoreboard는 SystemVerilog queue로 논리적 순서만 저장한다. push 수락이면 뒤에 추가하고 pop 수락이면 앞에서 꺼낸다.

검증 계획에는 상태와 요청의 교차 조합을 적는다. empty/중간/full 각각에서 요청 없음, push만, pop만, 동시 요청을 확인한다. 여기에 반복 wrap-around, 연속 pop, 데이터가 남았을 때 reset을 더한다. 각 경우의 관찰 여부를 기록하고, 난수 seed와 실패 입력을 남겨 재현한다.

기대 수락은 DUT의 full이나 occupancy가 아니라 queue의 이전 크기 NN으로 계산한다.

q=pop(N>0)p=push((N<4)q)N+=N+pq\begin{aligned} q&=\mathrm{pop}\land(N>0)\\ p&=\mathrm{push}\land((N<4)\lor q)\\ N^{+}&=N+p-q \end{aligned}

기존 head를 꺼낸 뒤 새 데이터를 붙여 full 동시 요청의 이전 head를 보존한다. 예상 q=1q=1일 때만 dout을 비교한다.

전체 테스트벤치

앞 강의의 fifo4와 함께 컴파일하는 전체 테스트벤치다. 지연과 queue를 쓰는 검증 코드다. 입력은 하강 엣지에 바꾸고, 상승 엣지 1ps 뒤 DUT의 nonblocking 갱신 결과를 비교한다.

SystemVerilog
module tb_fifo4;
  timeunit 1ns;
  timeprecision 1ps;
  typedef logic [7:0] word_t;
  logic clk=0, rst=1, push=0, pop=0;
  word_t din=0, dout;
  logic dout_valid, full, empty;
  logic [2:0] occupancy;
  word_t model[$], expected;
  bit expect_pop, expect_push;
  fifo4 dut (.*);
  always #5ns clk = ~clk;

  always @(posedge clk) begin
    if (rst) begin
      model.delete();
      expect_pop = 0;
    end else begin
      expect_pop = pop && (model.size() != 0);
      expect_push = push && ((model.size() < 4) || expect_pop);
      if (expect_pop) expected = model.pop_front();
      if (expect_push) model.push_back(din);
    end
    #1ps;
    assert (dout_valid === expect_pop)
      else $fatal(1, "valid mismatch");
    if (expect_pop)
      assert (dout === expected)
        else $fatal(1, "data mismatch");
    assert (int'(occupancy) === model.size())
      else $fatal(1, "count mismatch");
    assert (empty === (model.size() == 0))
      else $fatal(1, "empty mismatch");
    assert (full === (model.size() == 4))
      else $fatal(1, "full mismatch");
  end

  task automatic step(input bit r, p, o, input word_t d);
    @(negedge clk);
    rst=r; push=p; pop=o; din=d;
  endtask

  initial begin
    step(0,0,1,0);
    step(0,1,1,8'hA0);
    for (int i=1; i<4; i++)
      step(0,1,0,word_t'(160+i));
    step(0,1,0,8'hEE);
    step(0,1,1,8'hB0);
    repeat (5) step(0,0,1,0);
    repeat (3) begin
      for (int i=0; i<4; i++) step(0,1,0,word_t'(i));
      repeat (4) step(0,0,1,0);
    end
    step(0,1,0,8'h5A);
    step(1,1,1,8'hFF);
    step(0,0,1,0);
    repeat (100)
      step(0,bit'($urandom_range(1,0)),bit'($urandom_range(1,0)),
           word_t'($urandom));
    repeat (4) step(0,0,1,0);
    @(negedge clk);
    $display("PASS");
    $finish;
  end
endmodule

첫 상승 엣지는 rst=1이므로 DUT와 모델을 비운다. ===는 X까지 엄격히 비교한다. valid가 0인 때에는 초기화하지 않은 dout을 비교하지 않지만, valid 자체가 X이면 실패한다. 마지막 네 pop은 남은 데이터를 모두 검사한다. PASS는 이 테스트를 실제 실행하여 모든 assertion을 통과했을 때의 메시지이며, 여기 제시한 코드가 이미 실행되었다는 뜻은 아니다.

쓰기 데이터 B0를 넣기 전에 기존 head A0를 예상 출력으로 분리합니다. 포인터나 물리 슬롯 대신 데이터의 논리적인 순서를 비교하는 과정입니다.

full 동시 요청에서 scoreboard의 처리 순서
  1. 이전 queue: [A0, A1, A2, A3]
  2. pop 예상값: 기존 head A0를 보관
  3. push 반영: [A1, A2, A3, B0]
  4. 응답 비교: valid=1일 때 dout=A0

경계 예제로 scoreboard를 검산하기

queue가 [A0,A1,A2,A3]인 상태에서 push만 들어오면 크기 4이므로 EE는 버린다. 다음 동시 요청은 expected=A0을 먼저 보관하고 B0를 뒤에 붙여 [A1,A2,A3,B0]를 만든다. DUT가 새 B0를 즉시 내보내면 데이터 assertion이 실패한다. 반대로 empty 동시 요청은 expect_pop=0, expect_push=1이므로 출력 비교 없이 하나를 저장한다. reset 중 두 요청은 무시하고 queue를 비우며 valid=0을 검사한다.

메모리 검증에도 같은 원칙을 쓴다. 주소별 참조 배열과 initialized 표를 두고, read-first는 참조 쓰기 갱신 전에 읽기 예상값을 잡는다. 레지스터 파일의 bypass는 충돌 시 wr_data를 선택한 뒤 참조 배열에 쓰기를 반영한다. 미초기화 주소 읽기를 금지할지 미정 응답을 허용할지는 테스트의 전제에 적고, 임의의 0을 정답으로 만들지 않는다. 주소 메모리에는 별도 검증기가 필요하다.

full 동시 pop에서 새 값을 출력하기, 동시 수락 때 개수를 증가시키기, empty pop의 valid를 켜기 같은 결함을 각각 주입하면 대응 검사가 실패해야 한다. 이런 검토와 경계별 coverage를 통해 테스트 자체의 누락을 찾는다. RAM 자원 배치와 타이밍은 별도 합성·타이밍 검증으로 확인한다.

自己試試看

잘못된 scoreboard가 push 요청을 먼저 queue에 넣고, 그 뒤 갱신된 크기로 pop 허용 여부를 판단한다. empty에서 push와 pop이 동시에 오면 어떤 오판이 생기는가? full의 동시 요청에서는 예상 데이터를 언제 보관해야 하는가? reset 뒤 dout=0인지 매번 검사하는 규칙도 평가하라.

查看解說

empty 동시 요청에서 새 항목을 즉시 pop할 수 있다고 오판하여 expect_pop=1을 만든다. 계약상 수락은 엣지 직전 크기로 정하므로 push만 수락하고 출력 valid는 0이어야 한다. full에서는 queue의 기존 head를 먼저 expected로 보관하고 제거한 뒤 새 데이터를 뒤에 추가한다. reset은 dout을 0으로 만들지 않으므로 valid=0과 queue·occupancy가 비었는지만 검사한다. 데이터 비교는 expect_pop=1일 때 수행하되 DUT valid도 별도로 비교해야, valid를 항상 0으로 내보내는 오류를 놓치지 않는다.

此選擇適用於本瀏覽器,隨時可從頁尾變更。