[HARMAN] 세미콘 아카데미/SystemVerilog

[Harman 세미콘 아카데미] Day_56(System Verilog)

uiop1716 2025. 4. 23. 09:58

오늘은 어제에 이어 SystemVerilog의 Testbench에 대해 공부하였습니다.
어제는 Monitor, ScoreBoard를 제외한 나머지 부분들의 설계를 교수님과 진행하고, 전체적인 Testbench 설계를 과제 아닌 과제(?)로 진행하였습니다. 참고로 MCU Peripheral Slave FND Controller에 대한 Testbech입니다. 
제가 수행한 과제 중, 어려운 부분은 ScoreBoard의 Reference Model을 설계하는 부분이 조금 어려웠습니다. 교수님께서 쥐선생(GPT)의 도움을 받아서 연습겸 해봐도 된다고 하셔서... 이 부분만 도움을 받았습니다.
 
아래의 파일은 제가 작성한, Testbench 코드입니다.

testbench_내가 짰던 것.txt
0.01MB

 
 
그리고 교수님과 함께 Testbench 전체를 함께 설계해 보았습니다. 또한 아래의 사이트를 통해 SystemVerilog의 기능들에 대해 배울 수 있습니다. 참고하시면 좋을 것 같습니다.
https://chipverify.com/

 

ChipVerify

Learn Verilog, SystemVerilog, UVM with code examples, quizzes, interview questions and more !

chipverify.com

 
 
먼저, Code 리뷰입니다.
Transaction의 경우, 테스트하기 위해 randomize함수를 사용하여 랜덤한 입력 인자를 생성해 내는 역할을 합니다.
그리하여, 저희가 Test할 FND_Controller의 입력 값들인 PADDR, PWDATA을 rand를 사용하여 랜덤 값을 만들게 됩니다.
(PWRITE, PENABLE, PSEL의 신호는 rand 사용하지 않아도 됩니다.)

Transaction Code

 
Genertor는 Trasaction을 생성하는 친구입니다. 위의 그림을 보면 Trasaction을 생성하여 Mailbox에 넣는 역할을 합니다. 동작의 중복 방지를 위해 Driver로부터 event라는 신호를 기다렸다가 신호를 받게 되면 다시 역할을 수행합니다.
 
그리하여  function을 통해 generator 클래스와 연결된 mailbox, gen_next_event를 멤버변수를 매개변수로 만들어 놓습니다. 이는 task run 동작을 실행할때, 매개변수들을 사용하여 동작하도록 합니다.
trasaction이라는 클래스의 handler인 fnd_tr을 new()를 통해 새로운 instance를 만들어 냅니다. 이를 통해, Heap영역에서 trasaction이 차지하는 메모리 공간이 생성되면, Stack 영역에서 fnd_tr이라는 handler를 통해 instance의 주소와 reference의 값에 접근하도록 합니다.
 
또한, generator는 transaction 한개를 mailbox에 넣은 후, driver가 trasaction을 받았나는 신호인 event가 올 때까지 기다립니다. event 신호를 통해 generator가 transaction을 계속 생성해 넣는 것을 방지하기 위함입니다.

Generator Code

 
 
Driver는 계속 mailbox를 감지해 generator가 tranasction을 넣는지 확인합니다. 그래서 mailbox를 통해 transaction값을 .get하게 되고, 이를 interface에 전달해 줍니다. 이때, interface는 SW/HW의 영역에 걸쳐 있는데, 최종적으로 HW인 DUT에 도달해야 하므로 Driver는 SW로 작성된 Transaction을 HW로 변환합니다. 또한, 동작이 완료되면 generator에게 event 신호를 보내어 "나 동작 끝났어, 너 할일 해도 돼"라고 이야기를 해줍니다.
그리하여, 멤버변수로 mailbox, event, transaction의 값들을 가지게 되고, 연결되어 있는 interface를 가상으로 하여 연결합니다.
 
그리고, Driver의 역할은 task run();에서 나오는데. SW로 받은 PADDR, PWDATA, PWRITE, PENABLE, PSEL에 대한 transaction 신호들을 HW 신호들로 변환하는 역할을 합니다. SW의 신호들을  fnd_intf 라는 interface(HW)로 사용될 수 있도록 값을 변환해 주는 것을 확인할 수 있습니다. 그리고 아래의 코드에서는 APB-Protocol의 SETUP구간 ACCESS 구간을 만들어 놓은 코드 입니다. Randomize된 PADDR, PDAT의 값들은 interface(HW)로 변경해서 넣어 주고 Write, Enable, SEL 신호에 대해서는 APB-Protocol에 맞게 값을 넣어 interface(HW)에 넣어 변환시켜 줍니다. 
모든 동작이 종료되면, event신호를 generator에 보내어 동작 종료를 알려줍니다.

Driver Code

 
Environment는 앞서 언급한 Class들의 instance를 만들어주는 환경입니다. 지금까지의 generator, mailbox, driver등이 해당되고 추후, monitor, mailbox, scoreboard 등이 추가됩니다. Interface와 연결되어 driver가 만든 DUT에 보낼 HW신호들을 전선 다발로 묶어서 보냅니다.
또한, fork join_any를 통해 하나의 Process내에 여러 Thread를 생성하고 하나라도 끝나면 다음 코드를 수행하도록 합니다. 아래의 경우, fnd_gen.run(count), fnd_dv.run, fnd_mon.run(), fnd_scb.run() 중 하나라도 끝나면 다음 코드가 수행됩니다. 끝나지 않은 Thread들은 background로 실행됩니다.
 
* environment의 경우, generator, mailbox, driver등이 이미 instance화 되어 들어있기 때문에 function new에 매개변수로 받을 필요가 없고, 충돌이 없기 때문에 this.을 안해줘도 됩니당.

Environment Code

 
function new()의 매개변수 사용을 위한 그림입니다. Stack영역의 handler가 heap영역의 instance들에 접근하여 동작을 수행함을 확인할 수 있습니다. 

 
 
fork -join / join_any / join_none의 차이는 아래와 같습니다.
fork를 통해 Processor를 생성하고 Thread의 종료 시점에 관해 다르게 동작합니다.
 
fork - join : Thread가 모두 끝나야 다음 동작 수행
fork - join_any : Thread 중 하나라도 끝나면 다음 동작 수행
fork - join_none: Thread 생성 후, 바로 다음 동작 수행
 
* 끝나지 않은 Thread는 background로 동작합니다. 

fork Series

 
monitor의 경우, driver가 interface에 보낸 다발과 DUT를 통해 너온 결과 값들을 받게 됩니다. 받게된 HW값들을 SW로 변환하여 mailbox에 넣는 역할을 합니다. 그림의 오른쪽단에 위치한 driver와 비슷하지만 약간 다른 역할을 합니다.

Monitor Code

 
Scoreboard의 경우에는 monitor에서 mailbox로 넣은 값들을 가져와서 class내부의 reference model을 통해 검증을 진행합니다. 입력값과 입력값에 대한 기대값을 비교하여 올바르게 동작하는지 확인합니다.

Scoreboard Code


이렇게 모든 class들을 만들게 되면 Testbench로 감싸줘야 합니다. Environment, Interface, DUT를 묶어 Testbench를 돌리게 되고, 각 모듈이나 클래스를 지나며 입력된 값과 비교된 값의 일치 여부를 판단하여 TCI Console에 출력되게 하여 검증을 진행합니다.


Fnd에 대한 실습으로 0~9999까지 증가되는 값을 Led로 출력하고, 원하는 위치의 dotproduct 또한 on/off 하도록 설계하였습니다.
 - FCR_EN : fnd_comm을 제어
 - FDR : fnd에 출력할 값
 - FPR : dotproduct 제어

아래의 그림대로, FND_controller를 새로이 구성해 주었습니다. FCR_En 신호를 FndComm과 연결되는 Decoder의 제어 신호로하여 High일때만 FND에 값이 출력되도록 하였습니다.
또한, 4자리 표현을 위해 digital splitter로 1, 10, 100, 1000 자리를 나누고 4bit의 각 자리별 숫자로 나타낸 뒤 bcdtoseg 모듈을 통해 fnd에 들어갈 8bit로 변환을 시켜주었습니다. 8bit의 하위 7비트와 4bit FPR의 값을 1, 10, 100, 1000자리로 나눠 FPR[0], FPR[1], FPR[2], FPR[3]을 상위비트로 두어 결합해서 총 8bit의 fndFont를 만들어 내게 됩니다.
이를 통해 dotproduct를 포함한 fnd출력을 만들어 내었습니다.
*FPR의 1은 dotproduct를 켠다는 뜻인데, fnd의 7segment는 common anode type이므로, 0일때 켜지므로 ~fndDp를 해줘야 정상 작동합니다.

0~9999 counter FND Control BlockDiagram



그리하여, FDR에는 c언어를 통해 0-9999까지 증가하는 카운터를 만들고, 이 값을 받아 넣었습니다. FPR의 경우에는 기존에 만들어 놓았던 GPIB의 switch 입력을 통해 해당 위치의 점이 on/off 하도록 설계하였습니다. 또한 Dotdata 함수와 offDotdata 함수를 번갈아가며 동작시켜 dotproduct가 점멸하도록 설계하였습니다.
C언어 코드는 아래와 같습니다.

Fnd 제어 code


verilog 코드:
https://github.com/Heeju99/Code_Verilog_SystemVerilog/tree/main/workspace/250423_svtb_prac/250423_svtb_prac.srcs/sources_1/imports/sources_1/new

 

Code_Verilog_SystemVerilog/workspace/250423_svtb_prac/250423_svtb_prac.srcs/sources_1/imports/sources_1/new at main · Heeju99/C

Contribute to Heeju99/Code_Verilog_SystemVerilog development by creating an account on GitHub.

github.com

 

결과:
아래의 동영상으로 c코드에거 작성한 것처럼, 상위 8개의 스위치를 통해 fnd를 on/off하고, GPIB의 영역인 하위 8개 스위치 중에서 하위 4개의 스위치를 통해 해당 위치의 dotproduct를 on/off 하는 것을 확인하였습니다.

 

동작 영상