01signal.com

Vivado에서 부분 재구성(Partial Reconfiguration)을 이용한 Remote Update 구현

이 글은 Xilinx Vivado의 부분 재구성(Partial Reconfiguration), 즉 DFX(Dynamic Function eXchange)에 관한 네 편짜리 시리즈의 마지막 글입니다. 주로 Remote Update(원격 업데이트) 시나리오에서 부분 재구성(Partial Reconfiguration)을 사용하려는 분들을 위한 내용입니다. 앞선 세 편을 이미 읽었다는 가정하에 작성되었습니다.

소개

지금까지 부분 재구성(Partial Reconfiguration) 일반과 Vivado에서의 일반적인 절차를 다루었습니다. 이 글에서는 이 기법을 FPGA 로직의 Remote Update(원격 업데이트)에 사용하기 위한 기반을 마련합니다.

이 사용 시나리오의 핵심 문제는 부분 비트스트림(partial bitstream)이 수년 전에 생성되었을 수도 있는 초기 비트스트림(initial bitstream)과 호환되어야 한다는 것입니다. 따라서 재구성 로직을 구현할 때 원래의 부모 구현(Parent Implementation) 결과를 사용할 수 있어야 하며, 그렇지 않으면 정확히 같은 결과, 즉 정확히 같은 배치 및 배선(place and route) 결과를 만들어 내도록 부모 구현을 다시 재생성해야 합니다.

Vivado 프로젝트 전체를 그대로 보존해서 부모 구현을 다시 실행하지 않아도 되게 할 수 있습니다. 또한 이 구현을 반복해도 정확히 같은 결과를 얻을 수도 있습니다. 그러나 어느 쪽 방법으로든 향후 부분 비트스트림을 계속 배포할 수 있음을 보장하기는 어렵습니다.

이 문제에는 확실한 해결책이 있습니다. 하지만 그 작동 방식을 이해하려면 먼저 DCP와 OOC에 익숙해져야 합니다. 두 주제에 대한 간단한 소개가 아래 이어집니다.

DCP(Design Checkpoint)

Vivado가 FPGA 설계의 구현을 여러 개의 설계 런(Design run)으로 수행한다는 점을 기억하세요. 이 런은 보통 synth_1, impl_1이라고 하며, 그 외에도 OOC(Out-of-Context) 모듈 런으로 분류되는 추가 런이 있습니다.

각 런은 Tcl 스크립트(script)를 실행하는 것으로 구성됩니다. 그 스크립트는 임시 "인메모리 프로젝트(in-memory project)"를 만들고, 설계 파일을 로드한 다음, 속성과 특성을 설정하고, 합성(synthesis), 배치, 배선 및 비트스트림 생성 등의 작업을 수행하는 Tcl 함수를 호출합니다.

이 인메모리 프로젝트는 디스크에 파일을 만들지 않으며, GUI에 보이는 Vivado 프로젝트와는 아무 관련이 없습니다. Tcl 명령으로 많은 순차 작업을 수행할 수 있게 해 주는 메모리상의 객체일 뿐입니다.

구현이 진행됨에 따라 DCP(Design CheckPoint) 파일이 디스크에 기록됩니다(Tcl의 write_checkpoint 명령으로). DCP 파일의 내용은 인메모리 프로젝트의 스냅샷입니다. 즉, 로드된 설계 파일들과 그 로드 이후 프로젝트에 수행된 작업들을 반영하는 데이터베이스입니다.

예를 들어, 합성 런(보통 synth_1)은 모든 HDL 파일, 제약 파일, IP 파일을 로드한 다음, synth_design이라는 Tcl 명령을 사용해 HDL 합성을 수행합니다. 그 결과는 이러한 모든 소스(IP 포함)를 합친 하나의 큰 네트리스트(netlist)입니다. 다만 네트리스트는 인메모리 프로젝트의 일부로 메모리에 저장된다는 점을 유의하세요. 따라서 합성 런의 Tcl 스크립트는 합성의 결과물인 DCP 파일을 만들기 위해 write_checkpoint 함수를 호출합니다. 이로써 synth_1 런이 끝납니다.

실제로 synth_1이 생성하는 DCP는 보통 XDC 파일의 제약 조건도 포함하지만, 흔히 네트리스트 DCP(netlist DCP)라고 불립니다.

그 뒤에 오는 구현 런(보통 impl_1)은 새 인메모리 프로젝트를 만들고 후속 작업의 출발점으로 이 네트리스트 DCP를 로드합니다. 이 런은 처리 단계(예: 설계 최적화, 배치, 물리 최적화, 라우팅 등)가 끝날 때마다 해당 스냅샷인 DCP 파일을 여러 개 기록합니다.

synth_1을 포함한 모든 런은 DCP를 인메모리 프로젝트에 로드할 수 있습니다. 실제로 그렇게 합니다.

OOC(Out-Of-Context) 모듈 런

Vivado에서 IP는 보통 GUI 도구로 구성됩니다. 그렇게 하면 스크립트가 소스 파일(주로 HDL 및 제약 파일)을 생성하고 이에 대한 합성을 수행합니다. 그 결과로 IP의 네트리스트 DCP가 만들어지고, 이후 메인 프로젝트의 합성 런과 구현 런이 그 DCP를 로드합니다. 이렇게 하면 IP 소스를 다시 생성하고 합성을 반복할 필요가 없어 전체 프로젝트 구현이 완료되는 시간이 줄어듭니다.

Vivado 용어에서, 원시 산출물(IP 구성 정보, 일부 HDL 파일, 또는 그 밖의 무엇이든)을 가져와 이를 네트리스트 DCP로 바꾸는 런을 OOC(Out-of-Context) 런이라고 합니다. 이 용어는 Vivado와 관련해서만 사용되며, 제가 아는 한 다른 용도는 없습니다. 사실 그다지 좋은 이름 선택은 아닙니다.

이 네트리스트 DCP는 보통 read_ip라는 Tcl 명령으로 합성 런과 구현 런에 로드됩니다. read_ip는 보통 OOC 런이 생성한 DCP를 로드하는 것으로 귀결됩니다.

Vivado는 메인 프로젝트에서 HDL 모듈을 선택하여 그 합성을 OOC로 수행하도록 요청하는 것도 가능합니다(Project Manager의 소스 트리에서 소스를 마우스 오른쪽 버튼으로 클릭하고 "Set as Out-of-Context for Synthesis…" 선택).

OOC의 단점은 합성 도구(synthesizer)가 전체 설계를 하나의 프로젝트로 받을 때 모듈 경계를 넘어 특정 최적화를 수행할 수 있다는 것입니다. 따라서 OOC로 개별 합성하면 성능이 떨어지고 리소스가 낭비될 수 있습니다.

부분 재구성(Partial Reconfiguration)에서의 OOC와 DCP

소스 파일이 재구성 모듈(reconfigurable module)의 최상위 모듈로 선택되면, Vivado는 이 모듈과 그 하위 모듈의 합성을 위해 OOC 런을 만듭니다. 이 런은 관련 구현에서 사용할 네트리스트 DCP를 생성합니다. 이는 부모 구현과 자식 구현 모두에 해당됩니다. 재구성 모듈은 항상 별도의 DCP로 표현됩니다.

그러나 이 네트리스트 DCP는 부모 구현과 자식 구현에서 다르게 사용됩니다. 부모 구현에 할당된 네트리스트 DCP는 일반 구현에서 IP를 처리하는 것과 마찬가지로 synth_1과 impl_1 양쪽에 로드됩니다. 부분 재구성이 관여한다는 사실은 주로 플로어플래닝(floorplanning)에서 부과되는 배치 제약을 통해 이 과정에 영향을 미칩니다.

반면 자식 구현에는 전용 합성 단계가 없습니다. 대신 재구성 모듈의 네트리스트 DCP를 부모 구현의 최종 DCP(즉, 배치 및 배선 후의 DCP)와 혼합합니다. 더 정확히는, 자식 구현이 부모 구현의 최종 DCP에서 재구성 로직 부분을 제거하고, 그 자리에 자신의 재구성 모듈 네트리스트 DCP를 삽입합니다. 채소의 중간 부분을 도려내고 속을 채우는 요리를 만드는 것과 비슷한 이치입니다.

이제 이것을 Tcl 명령 수준으로 분해할 차례입니다.

부모-자식 구현(Parent-Child Implementation)의 실제 동작

구현이 내부적으로 어떻게 동작하는지 보려면 프로젝트 이름(확장자 .tcl)을 가진 Tcl 파일을 살펴보면 됩니다. 이 파일은 구현 관련 파일들이 생성되는 디렉터리에 있습니다.

특히 자식 구현(Child Implementation)용 구현 스크립트가 흥미롭습니다. 관련 사용자 가이드인 UG909의 3장("Vivado Software Flow")이 그 스크립트를 직접 말하지는 않더라도 보여 주고 설명하는 것은 우연이 아닙니다.

앞서 언급했듯이 부모 구현에는 재구성 모듈의 네트리스트를 위해 DCP에 의존하고 플로어플래닝 제약이 적용된다는 점 외에는 특별한 것이 많지 않습니다. 계층적 설계(Hierarchical Design)와 거의 같습니다.

그러나 부모 구현은 비트스트림(bitstream)을 하나가 아니라 두 개 기록합니다. 다음과 같은 Tcl 명령을 사용합니다.

write_bitstream -force -no_partial_bitfile theproject.bit
write_bitstream -force -cell pr_block_ins pr_block_ins_lpf_partial.bit

그리고 나중에 자식 구현이 사용할 DCP를 만듭니다.

update_design -cell pr_block_ins -black_box
lock_design -level routing
write_checkpoint -force theproject_postroute_physopt_bb.dcp

이 부분이 실행되는 동안 인메모리 프로젝트가 있으며, 그 프로젝트는 네트리스트 DCP를 로드하는 것으로 시작하여 배치, 배선 및 기타 모든 최적화를 거쳤다는 점을 기억하세요. 위 세 줄의 Tcl은 비트스트림을 기록한 후에 실행되므로 인메모리 프로젝트는 정말 마지막 단계에 있습니다.

이제 구멍을 뚫고 속을 채우는 작업을 할 때입니다. update_design 명령은 재구성 모듈을 블랙박스(black box)로 만듭니다. 즉, 그 로직이 모두 제거되어 다른 로직이 들어올 자리가 마련됩니다.

그런 다음 lock_design 명령으로 설계의 배치 및 배선을 잠급니다. 그 후 프로젝트 스냅샷이 theproject_postroute_physopt_bb.dcp에 기록됩니다. "bb"는 물론 "Black Box"를 뜻합니다.

자식 구현 스크립트의 관련 부분은 다음과 같습니다.

create_project -in_memory -part xc7k325tffg900-2
set_property design_mode GateLvl [current_fileset]
add_files -quiet .../impl_1/theproject_postroute_physopt_bb.dcp
add_files -quiet .../two_synth_1/pr_block.dcp
set_property SCOPED_TO_CELLS pr_block_ins [get_files .../two_synth_1/pr_block.dcp]
link_design -top theproject -part xc7k325tffg900-2 -reconfig_partitions pr_block_ins
opt_design
write_checkpoint -force theproject_opt.dcp
[ ... ]

이 지점부터 배치 및 배선 등의 작업이 계속됩니다.

위 구현 스크립트는 단 두 개의 소스만 사용하며 둘 다 DCP입니다.

구현이 계속될 때 첫 번째 DCP가 잠겨 있다는 사실은 정적 로직의 어떤 것도 움직이지 않도록 보장합니다. 그럼에도 재구성 모듈의 배치 및 배선은 네트리스트 DCP를 기반으로 평소처럼 수행됩니다.

참고로, 재구성 모듈에 속하는 XCI IP가 있다면, 위의 add_files 두 명령 사이에 IP마다 다음과 같은 줄이 추가됩니다.

read_ip -quiet .../theproject.srcs/sources_1/ip/blkmem/blkmem.xci

하지만 이것은 부분 재구성에만 해당하는 것은 아닙니다. 모든 구현에서 이렇게 처리합니다.

두 비트스트림을 기록하기 직전에 자식 구현은 자신이 얻은 배선된 설계가 부모 구현의 정적 로직과 호환되는지, 특히 배치 및 배선과 관련해 호환되는지 확인합니다.

이를 위한 Tcl 명령은 대략 다음과 같습니다.

pr_verify -full_check -initial /path/to/impl_1/theproject_postroute_physopt.dcp -additional /path/to/child_1_impl_1/theproject_routed.dcp -file child_1_impl_1_pr_verify.log

이것은 인메모리 프로젝트와 무관하게 두 DCP 파일을 비교한다는 점을 유의하세요. 부모 구현의 배선된 DCP를 자식 구현의 최종 DCP와 비교합니다. 비교 결과는 *_pr_verify.log라는 파일에 기록됩니다.

이 비교는 부모의 비트스트림이 이미 FPGA에 로드된 상태에서 부분 비트스트림을 로드할 수 있다는 의미에서 호환성을 보장합니다. 정적 파티션의 로직 요소와 라우팅을 모두 검사합니다.

pr_verify는 비호환성이 있으면 실패 상태를 반환합니다. 그 경우 Vivado의 스크립트에서는 비트스트림 생성이 중단됩니다. 사용자 정의 구현 스크립트를 작성할 때는 이 단계를 반드시 염두에 두어야 합니다.

이 검증이 실패할 이유는 없어야 하지만, 만약 실패하면 비트스트림 생성은 다음과 같은 오류와 함께 실패합니다. "ERROR: [Constraints 18-891] HDPRVerify-08: design check point .../impl_1/theproject_postroute_physopt.dcp places instance ... at site SLICE_X118Y125, yet design check point .../impl_2/theproject_routed.dcp does not. Both check point must have the same static placement result".

이런 오류 메시지는 아마 100개 한도에 도달한 뒤 더 이상 출력되지 않을 것입니다.

Remote Update 시나리오를 위한 해결책

위에서 언급했듯이 문제는 재구성 로직의 구현을 자식 구현(child implementation)으로 수행할 때 원래 부모 구현의 결과를 사용할 수 있어야 한다는 것입니다.

단순한 해결책은 Vivado 프로젝트 디렉터리와 그 프로젝트가 의존할 수 있는 모든 파일을 통째로 복사해 두는 것입니다. 새 부분 비트스트림을 생성해야 할 때 모든 파일을 복원하고, 부모 구현을 강제로 최신 상태(up-to-date)로 만든 다음, 자식 구현에 대해서만 비트스트림을 생성하면 됩니다. 이 방법은 기술적으로는 문제없지만 장기적으로는 번거로울 가능성이 높습니다. 이 방법을 선택한다면, 원래 부모 구현의 DCP 파일에 대해 pr_verify를 직접 실행해야 합니다. 이 방법으로는 부모 구현의 의도하지 않은 변경을 감지할 수 없기 때문입니다.

두 가지 다른 대안이 있습니다. 부모 구현과 자식 구현이 위에서 설명한 두 DCP 파일을 통해 상호 작용한다는 원리를 활용하는 것입니다. 이 두 대안의 분명한 장점은 자신이 무슨 일을 하는지 안다는 것입니다.

두 대안의 공통 원리는 부분 비트스트림이 초기 비트스트림과 함께 생성된 두 DCP 파일, 즉 제가 골든 DCP(Golden DCP)라고 부를 파일들에 의존하도록 하는 것입니다.

첫 번째 대안은 부분 비트스트림의 구현을 비프로젝트(non-project) 방식의 Tcl 스크립트로 수행하는 것입니다. 기본적으로는 Vivado가 자식 구현을 위해 만든 스크립트를 실행하되, 골든 DCP를 사용하도록 수정하는 것입니다. 보다 정확히는 link_design 명령과 pr_verify 명령의 인수를 수정하여 골든 DCP를 참조하도록 하는 것입니다.

이 대안의 가장 큰 단점은 비프로젝트 방식으로 Tcl 스크립트를 실행하면 Vivado GUI와의 통합이 매끄럽지 않아, 메시지 처리나 구현된 설계를 열어 검토하는 등의 작업이 상당히 불편해진다는 점입니다.

골든 DCP 활용 요령

이상적으로는 Vivado가 자식 구현 런에 대해 생성하는 스크립트가 골든 DCP를 참조하도록 자동으로 수정할 수 있으면 좋겠습니다. 그러나 안타깝게도 이를 안정적으로 수행할 수 있는 방법은 없는 것 같습니다.

Vivado는 구현의 특정 단계 전후에 실행할 Tcl 스크립트를 정의하는 기능을 제공합니다. 이러한 스크립트는 구현 런의 스크립트 내에서 실행되므로, 런 스크립트 자체를 변경하는 데는 사용할 수 없습니다.

그럼에도 이 기능은 다소 지저분해 보이는 두 번째 대안을 가능하게 합니다. 아이디어는 부모 구현의 DCP를 골든 DCP로 덮어쓰는 것입니다. 이렇게 하면 자식 구현은 정상적으로 실행되지만, 부모 구현이 무엇을 생성했는지와 무관하게 골든 DCP에 의존하게 됩니다.

이 방법의 장점은 Vivado에서의 평소 작업 습관을 그대로 유지할 수 있다는 것입니다. 재구성 로직을 변경하면 Vivado가 재구성 모듈 합성을 위해 OOC를 다시 실행하고, 그다음 부분 비트스트림 생성을 위해 자식 구현을 실행합니다. 정적 로직에는 변경 사항이 없으므로 Vivado가 관련 런을 다시 시작할 이유가 없습니다.

이 골든 DCP 복사 방법을 구현하는 Tcl 스크립트는 다음과 같습니다.

if { [catch {
    set parentimpldir "[ file normalize "../impl_1"]"
    set goldendir "[ file normalize "/path/to/golden"]"

    file copy -force "[file normalize "$goldendir/theproject_postroute_physopt_bb.dcp"]" "$parentimpldir/"
    file copy -force "[file normalize "$goldendir/theproject_postroute_physopt.dcp"]" "$parentimpldir/"
} errmsg ] } {
    send_msg_id golden-reconfig-1 error "Failed to copy golden parent reconfiguration file(s): $errmsg"
    return -code error
}

이 스크립트는 부모 구현이 자식 구현 디렉터리 옆의 impl_1 디렉터리에 보관되어 있다고 가정합니다(거의 확실히 그렇습니다). 또한 두 골든 DCP가 이 스크립트 3행에 정의된 디렉터리에 저장되어 있다고 가정합니다.

Design Runs 탭에서 자식 런(예: child_0_impl_1)을 마우스 오른쪽 버튼으로 클릭하고 "Change Run Settings…"을 선택한 다음, 나타나는 대화상자에서 Design Initialization(init_design) 단계의 tcl.pre에 이 스크립트를 지정합니다. 또는 스크립트를 golden_pr.tcl로 저장했다면 Tcl에서는 다음과 같이 합니다.

add_files -fileset utils_1 -norecurse /path/to/golden_pr.tcl
set_property STEPS.INIT_DESIGN.TCL.PRE [ get_files /path/to/golden_pr.tcl -of [get_fileset utils_1] ] [get_runs child_0_impl_1]

이 스크립트를 사용할 때 중요한 점은 Vivado가 부모 구현에 대해 표시하는 모든 정보와 부모 구현 쪽에서 생성되는 파일들을 무시하는 것입니다. Vivado가 Implemented Design GUI를 열고 리포트를 보여 줄 수도 있지만, 그것들은 전혀 관련이 없을 수 있습니다. 따라서 혼란이 생길 여지가 있습니다.

또 하나의 잠재적 불편은 Vivado가 프로젝트 설정 변경이나 제약 파일 변경에 대응하여 부모 구현을 때때로 다시 실행할 수 있다는 것입니다. 이는 Design Runs 탭에서 해당 행을 마우스 오른쪽 버튼으로 클릭하고 "Force Up-to-Date"를 선택하여 해결할 수 있습니다. 이 메뉴는 런이 Out-of-Date 상태, 즉 완료되었지만 Vivado가 갱신이 필요하다고 판단한 상태일 때만 나타납니다. 관련 Tcl 명령은 예를 들어 다음과 같습니다.

set_property needs_refresh false [get_runs synth_1]

골든 DCP와 함께 저장해야 할 파일

당연히 골든 DCP는 향후 호환 가능한 비트스트림 파일을 생성할 수 있도록 안전한 곳에 보관해야 합니다. 또한 전체 Vivado 프로젝트를 .tar.gz / .zip 파일로 압축해 두면 나중에 작업을 신속히 재개할 수 있어 좋습니다. 추가로(또는 그 대신) File > Project > Archive… 또는 다음과 같은 명령으로 프로젝트 아카이브를 생성할 수 있습니다.

archive_project /path/to/theproject.xpr.zip -force -include_local_ip_cache -include_config_settings

다만 Vivado 프로젝트의 프로젝트 파일과 다른 파일들에는 절대 경로가 포함될 수 있으므로, 프로젝트를 다른 디렉터리나 다른 컴퓨터에 배포하면 예상대로 작동하지 않을 수 있습니다. 아카이브도 마찬가지입니다.

사용 중인 Vivado 버전은 모든 리포트 파일과 .xpr 프로젝트 파일에 기록되지만, 따로 메모해 두는 것도 나쁘지 않습니다. 별다른 이유는 없어 보이지만 해당 버전의 Vivado 실행 파일 사본을 보관해 두는 것도 좋습니다. 한 Vivado 버전의 골든 DCP와 다른 버전의 재구성 로직 네트리스트 DCP를 섞는 것은 아마 잘 동작하겠지만, Vivado가 설계한 용도는 아닙니다.

요약하면, 최소한으로 보관해야 할 파일은 다음과 같습니다.

Ultrascale FPGA에서 clearing bitstream의 사용은 FPGA에 이미 들어 있는 로직과 관련이 있다는 점을 유의하세요. 따라서 초기 비트스트림에 해당하는 clearing bitstream을 보관해야 합니다. 또한 초기 비트스트림이 어떤 자식 구현의 결과라면, 해당 자식 구현의 clearing bitstream도 보관해야 합니다.

부모 구현의 소스를 보관하는 것이 중요한 이유는 그것이 정적 로직과 재구성 로직 사이의 연결을 결정하기 때문입니다. 예를 들어 소스를 편집하여 재구성 로직에 포트를 추가하고 그 포트가 인스턴스화에 나타나면, 파티션 핀(partition pin)의 집합이 바뀝니다. 그 결과 재구성 로직의 네트리스트에는 원래 정적 설계에 없던 외부 핀이 생기게 됩니다.

이런 불일치가 발생하면 자식 구현은 다음과 같은 오류 메시지와 함께 실패합니다. "ERROR: [Netlist 29-77] Could not replace (cell 'pr_block_bb', library 'work_pr_block_ins_pr_block_ins_4', file 'NOFILE') with (cell 'pr_block', library 'work', file 'pr_block.edf') because of a port interface mismatch; in strict mode, no extra ports are allowed. 8 ports are missing on the original cell. 5 of the missing ports are: 'thingy[7]' 'thingy[6]' 'thingy[5]' 'thingy[1]' 'thingy[0]'".

이 경우 실제로 실패하는 것은 정적 로직과 재구성 로직을 결합하는 link_design 명령(위 참조)입니다.

이 문제를 피하는 간단하고 명백한 방법은 정적 설계 부분과 재구성 로직의 포트 목록을 변경하지 않는 것입니다.

그러나 프로젝트의 정적 부분을 변경하는 것은 사실 괜찮습니다. 재구성 모듈의 인스턴스화만 동일하게 유지되면 됩니다. 마지막 검증을 포함해 구현이 순조롭게 진행되기만 하면 문제가 없습니다.

요약

부분 재구성(Partial Reconfiguration)을 Remote Update(원격 업데이트) 용도로 사용하기 위해 완전히 매끄러운 해결책은 없지만, 그 목표를 달성할 수 있는 몇 가지 전략이 있습니다.

중요한 점은 원칙적으로 두 골든 DCP만 있으면 기존 프로젝트의 부분 비트스트림을 만들고 검증할 수 있다는 것입니다.

여기서 제시한 전략이 아무리 비전통적으로 보여도, pr_verify가 수행하는 검증이 이미 자리 잡은 정적 로직과 부분 비트스트림 사이의 호환성에 대한 종합적인 검사라는 점을 기억하세요. 올바른 골든 DCP로 검증을 수행하고 통과하기만 하면 더 이상 걱정할 것이 없습니다. 물론 자식 구현이 타이밍 제약(timing constraints)을 충족해야 한다는 점도 덤으로 따라오지만, 그것은 어떤 설계 구현에서도 마찬가지입니다.

이 페이지는 영어 원문을 기계 번역한 것입니다. 의문이 드는 부분이 있으면 원문을 참조하시기 바랍니다.
Copyright © 2021-2026. All rights reserved. (dcc38493)