25 Mar Driver Sequencer Handshake Mechanism
일반적인 UVM agent는 하나의 driver, 하나의 sequencer 그리고 하나의 monitor를 가진다. 물론 active agent가 아닌 passive agent의 경우 driver, sequencer 없이 monitor로만 구성되기도 한다. Active agent 내에서 test sequence가 DUT(Design-under-Test)에 signal-level로 전달되는 과정과 driver와 sequencer가 communication 하는 handshake mechanism에 대하여 설명한다.
위 그림에서 각 component의 역할을 간단히 기술하면 다음과 같다.
- Sequence – 검증하고자 하는 scenario/plan/intent를 transaction-level로 기술하는 역할
- Sequencer – sequence를 driver로 전달하고, driver로부터 response를 받는 역할
- Driver – transaction을 signal-level로 변환하여 virtual interface를 통해 DUT로 전달하는 역할
Sequence의 가장 핵심이 되는 body
부분의 code부터 하나씩 확인해보자. Sequence에서 transaction, 즉, sequence item을 driver로 보내기 위해 start_item
이라는 method를 사용한다. 필요에 따라 randomization을 거친 후, 최종적으로 finish_item
을 통해 driver와의 communication을 끝낸다. 이러한 단계는 `uvm_do
또는 `uvm_create
, `uvm_send
와 같은 매크로를 사용하여 단순화 가능하지만, 이러한 방식을 권장하지는 않는다. 자세한 내용은 Sending a Transaction 페이지를 참고하자.
class my_sequence extends uvm_sequence#(my_transaction); // ... virtual task body(); req = my_transaction::type_id::create("req"); start_item(req); if (!req.randomize()) begin `uvm_fatal(get_type_name(), "randomization fail"); end finish_item(req); endtask: body endclass: my_sequence
보통의 경우 sequencer는 매우 간단한 형태를 갖는다. Sequencer에서 특별한 작업을 해야 하는 경우가 아니라면 아래와 같이 한 줄로 표현 가능하다.
typedef uvm_sequencer#(my_transaction) my_sequencer;
Driver는 sequencer가 보내주는 transaction이 있는지 기다리기 위한 get_next_item
과 signal-level의 DUT driving 그리고 DUT driving이 끝났음을 알리는 item_done
을 계속해서 반복한다.
class my_driver extends uvm_driver#(my_transaction); // ... virtual task run_phase(uvm_phase phase); // ... forever begin seq_item_port.get_next_item(req); drive(); seq_item_port.item_done(); end endtask: run_phase virtual task drive(); @(negedge vif.clk) vif.in <= req.data; endtask: drive endclass: my_driver
또한 Starting a Sequence 페이지에 설명된 것과 같이 sequencer에 원하는 sequence를 start 시키고, 실제로 sequencer와 driver를 연결하는 작업까지 완료되어야 한다. Driver와 sequencer의 연결은 미리 정의된 seq_item_port
와 seq_item_export
를 이용하면 된다.
class my_test extends uvm_test; // ... task run_phase(uvm_phase phase); super.run_phase(phase); phase.raise_objection(this); seq.start(env.agent.sequencer); phase.drop_objection(this); endtask: run_phase endclass: my_test
class my_agent extends uvm_agent; // ... function void connect_phase(uvm_phase phase); super.connect_phase(phase); driver.seq_item_port.connect(sequencer.seq_item_export); endfunction: connect_phase endclass: my_agent
다음은 실제로 sequence에서 하나의 transaction을 실행할 때 sequence와 driver에서 code가 실행되는 순서를 출력한 것이다.
UVM_INFO my_sequence.sv(30) @ 0: uvm_test_top.env.agent.sequencer@@seq [my_sequence] start_item UVM_INFO my_driver.sv(43) @ 20000: uvm_test_top.env.agent.driver [my_driver ] get_next_item UVM_INFO my_sequence.sv(33) @ 20000: uvm_test_top.env.agent.sequencer@@seq [my_sequence] randomize UVM_INFO my_sequence.sv(36) @ 20000: uvm_test_top.env.agent.sequencer@@seq [my_sequence] finish_item UVM_INFO my_driver.sv(46) @ 20000: uvm_test_top.env.agent.driver [my_driver ] drive UVM_INFO my_driver.sv(49) @ 25000: uvm_test_top.env.agent.driver [my_driver ] item_done
Driver-sequencer communication에서 문제가 있는 경우 simulation hang으로 이어지는 경우도 있는데, 위와 같이 단순한 경우는 이해하기 어렵지 않지만, 여러 개의 sequence에서 여러 개의 transaction이 동시에 수행되는 경우, 동작에 대한 정확한 이해 및 관련 동작의 debugging이 다소 어려울 수도 있다. 위에서 살펴보았던 그림을 다시 한 번 살펴보자.
start_item
, get_next_item
, finish_item
의 경우 점선으로 표시된 박스는 해당 구문에서 출발한 화살표가 자신에게 돌아오기 전까지 blocking 되는 구문임을 표시하기 위하여 크게 표시하였으므로 이에 유의하자. Handshake 동작을 제대로 이해하기 위해서는 blocking 개념에 대해서도 이해하고 있어야 하므로 위 그림의
을 기준으로 blocking 개념에 대해 간단히 설명한다. Sequence에는 start_item
, start_item
, randomize
이 차례대로 기술되어 있지만, finish_item
이 시작된 후 바로 start_item
가 수행되는 것이 아니라, driver에서 randomize
이 수행된 이후에서야 비로소 get_next_item
randomize
가 수행된다. 이를 blocking 된다고 표현하며
, get_next_item
에도 동일하게 적용된다.item_done
- start_item @ sequence – blocking
- get_next_item @ driver – blocking
- randomize @ sequence
- finish_item @ sequence – blocking
- drive DUT @ driver
- item_done @ driver
마지막으로 driver에서 사용하는 try_next_item
에 대해서 간단히 설명한다. 종종 get_next_item
을 대신하여 사용되는데, get_next_item
과 달리 non-blocking 구문이라는 차이가 있다. 따라서 다음과 같이 try_next_item
의 결과가 null
인지 아닌지 check 할 필요가 있다. try_next_item
의 결과가 null
이 아닐 경우에만 DUT를 drive 하고, null
일 경우 idle clock을 추가하는 등의 처리를 해줌으로써 simulation hang에 대비해야 한다. 일반적으로 get_next_item
을 더 많이 사용하지만, reusability 측면에서 try_next_item
의 사용을 권장하기도 한다.
class my_driver extends uvm_driver#(my_transaction); // ... virtual task run_phase(uvm_phase phase); // ... forever begin seq_item_port.try_next_item(req); if (req == null) begin // insert idle clock idle(); end else begin // drive DUV drive(); end seq_item_port.item_done(); end endtask: run_phase virtual task idle(); @(negedge vif.clk) vif.in <= 0; endtask: drive virtual task drive(); @(negedge vif.clk) vif.in <= req.data; endtask: drive endclass: my_driver
사실 UVM에서의 driver와 sequencer 간의 communication은 위 설명보다 더 복잡하기 때문에 이해를 쉽게 하기 위해 개념 위주로만 간략히 설명하였음을 밝힌다.
References
- http://www.learnuvmverification.com/index.php/2015/07/07/uvm-driver-and-sequencer-communication
- http://www.verificationguide.com/p/how-to-use-uvm-trynextitem.html
Jung Ik Moon
Verification Engineer
초보개발자
Posted at 14:17h, 16 September잘 정리된 포스트 정말 감사합니다. 잘 보고있습니다.
IKS
Posted at 14:17h, 16 September감사합니다.
초보개발자2
Posted at 10:46h, 17 July여러 개의 sequence에서 여러 개의 transaction이 동시에 수행되는 경우, 라고 말씀하신 부분이 궁금해서요.
예를 들어 sequence 가 A,B,C,가 있고, start_item() 으로 sequence 를 실행시키면 A,B,C 순서로 시킬 수 밖에 없지 않나요?
드라이버가 각각 3개씩 있는 것이 아니라면, 하나의 Driver 입장에서는 A B C 순으로 들어올 것 같은데요.
어떻게 여러 개의 Transaction이 동시에 수행이 될 수 있는지 궁금합니다.
IKS
Posted at 19:01h, 18 AugustUVM sequence arbitration에 대해서 한 번 알아보시면 될 것 같아요.
여러 개의 sequence가 하나의 driver를 access 할 수 있고, 그 순서는 설정된 arbitration mode에 따라 다르게 동작합니다.
FIFO, weighted, random, strict, user 등 다양한 arbitration mode가 존재하구요.
아래 링크 한 번 참고해 보세요.
https://learnuvmverification.com/index.php/2016/07/19/uvm-sequence-arbitration/