# OpenRoad - Synthesis 本次project4藉由OpenROAD-Project中的OpenROAD-flow-scripts(ORFS)項目來觀察IC design flow是如何藉由EDA(Electronic Design Automation)實現,其相較於project1會更加詳細。 在part2的部分會著重介紹”Synthesis”。 --- ## 項目結構 主要從**flow**這個目錄下開始觀察。 ![](https://hackmd.io/_uploads/Hy1mg22Rh.png) ### design ![](https://hackmd.io/_uploads/Bk7gWn3R2.png) design中有各個PDK,各PDK之下還有幾個檔案,例如本次以gcd(Greatest Common Divisor)該項目舉例,而每個項目的HDL source code也都在design下的src目錄之下。 >PDK(Process Design Kit):例如design下的gf180意指GlobalFoundries 180nm --- - $config.mk$ ![](https://hackmd.io/_uploads/B1sxQnhAn.png) 只是一些簡單的環境設置,真正要的重要資訊大多不在這裡,其中我們可以透過*VERILOG_FILES*那條去得知我們的HDL source code在哪 - $constraint.sdc$ ![](https://hackmd.io/_uploads/HkplV2hR2.png) .sdc檔,全名為Synopsys design constraints,如其名是對電路的時序、面積、功耗有一定的約束,是設計前的基本規範,由使用者定義,而此檔案內的都是本來就有提供的預設值。 - $rules-base.json$ 該晶片的應有的執行後數據標準,以用於$Metric$比對,如果數值出來較該檔案中紀錄的數值更差,需要重新執行或評估。 - $metadata-base-ok.json$ run完之後的數值紀錄。 ### logs ![](https://hackmd.io/_uploads/rJou5hnCh.png) 在$make$後我們可以看到一些相關的**過程紀錄**,它會很貼心的把每個階段都分類得很清楚,可以依需求去找相對應的檔案觀察。 ### result ![](https://hackmd.io/_uploads/BygH6ph0n.png) 如果跑完會有相對應的資料夾產生:PDK下的晶片項目中的base和base下的result檔案,例如經歷floorplan處理後會有.odb和.sdc。與logs不同的是它在每個被分隔成小階段的結果外還會有整個大階段跑完的結果,例如2_1到2_6後還會有一個2_floorplan.*檔案。 ### script ![](https://hackmd.io/_uploads/SJx3RThRh.png) 自動化執行所需要的腳本檔案,也就是最重要的部分,真的去執行這些各階段任務的檔案內容,若是想要了解是如何運作的,實際上除了用$Makefile$去觀察flow外,看tcl才會知道詳細的執行過程。 --- ## Part.1 OpenROAD Flow (EDA flow) ![figure1.](https://hackmd.io/_uploads/BJGMkRnA3.png) [figure1.] ![figure2.](https://hackmd.io/_uploads/SkydkGC0h.png) [figure2.] (節錄自OpenROAD-flow-scripts github & openROAD github) 該**OpenROAD FLOW(EDA FLOW)**實際運作內容我們可以透過flow目錄下的**Makefile**來了解 ![](https://hackmd.io/_uploads/HkrylR30h.png) 可以選擇其中一個來執行,利用#(comment)來控制 如前面所說我們使用規模較小的gcd(也就是default)來做觀察 (一開始安裝步驟中的Verify Installation所make的就是默認的gcd,而make gui_final顯示的也是gcd項目最後layout圖的樣子) --- ### STEP1. Synthesis ![](https://hackmd.io/_uploads/HkEm-CnR3.png) ![](https://hackmd.io/_uploads/BkG_ZCnCn.png) 可以看到該step會產生的目標為*1_synth.v和1_synth.sdc*,但.sdc是由剛剛所說的/OpenROAD-flow-scripts/flow/designs/nangate45/gcd/constraint.sdc所複製過來的,用於之後的step所用。 - 自動化部份透過*synth.tcl*來執行,其中會記錄各種log紀錄檔。 - 使用yosys將RTL code(*.v)轉換成gate-level netlist >RTL 描述的是高層次的硬體行為和date flow(data stream),而 gate-level netlist 描述的是具體的數位邏輯閘和連接。後者更接近於實際硬體的結構。 從register-transfer-level code轉成gate-level code ![](https://hackmd.io/_uploads/B1voH1a02.png) ![](https://hackmd.io/_uploads/S1TpSkT0h.png) 最後在這步驟來看一下晶片布局半成品圖 ``` $make gui_synth ``` ![](https://hackmd.io/_uploads/SkAing0A2.png) ![](https://hackmd.io/_uploads/Syt0hlCC2.png) 一片黑,很好,這應該是我們所要的,因為本步驟只是合成verilog成netlist表示而已, 大概要等到第一個place動作出來我們才會可以看到有基本的圖。 --- ### STEP2. Floorplan ![](https://hackmd.io/_uploads/BktXxMA0h.png) 如同前面一樣先觀察一下輸出檔案和它的依賴關係是什麼。 > .odb ODB++(Open Data Base)is a proprietary CAD-to-CAM data exchange format used in the design and manufacture of electronic devices. Its purpose is to exchange printed circuit board design information between design and manufacturing and between design tools from different EDA/ECAD vendors. reference : [wiki](https://en.wikipedia.org/wiki/ODB%2B%2B) 白話來說,大概就是macro/block等手工、半手工設計過的一些元件的擺放資訊、size大小...等等重要資訊紀錄,後續會隨著各流程而陸續增加更多資訊。 *之後有一段code是避開依賴性檢查,makefile有強大的依賴性這個特性,但對於這個project來說依賴性不一定是必須的,因為追求模組化可以使其各個階段分開執行或分別利用,但在我們make的過程中它執行時還是有相依的,每個步驟都會input前一個步驟的output,只是我們還是需要避開依賴性檢查以達到最好的利用(無圖搭配)。* #### step.2.1 Translate verilog to obd ![](https://hackmd.io/_uploads/rkqz_UyyT.png) 如同註解所述,將前一階段輸出的.v檔轉成.odb檔 #### step.2.2 IO Placement (random) ![](https://hackmd.io/_uploads/HkppTUJy6.png) 根據PPA(Power, Performance, Area)擺放IO,較為沒有嚴格約束的擺放 此時我們已經可以看到有元件顯示在gui上了(最外圈),正是剛剛擺放上去的IO pin ![](https://hackmd.io/_uploads/ryi4aIkyT.png) #### step.2.3 Timing Driven Mixed Sized Placement ![](https://hackmd.io/_uploads/B1S21wyJT.png) ![](https://hackmd.io/_uploads/ryi4aIkyT.png) 擺放後沒有元件顯示,猜測可能是gcd規模太小 後來陸續換了幾個觀察,即使是nangate45/tinyRocket也沒有看到有元件擺放 #### step.2.4 Macro Placement 根據設計經驗,macro盡量在core四周擺放。對於儲存單元來說有port很方便,但同時也功耗很大 這樣也利於接電方便供電,並且可以防止這些元件過多的pin對其他單元的layout造成影響。 其中演算法是用Modern Floorplanning Based on B∗-Tree and Fast Simulated Annealing[4] ![](https://hackmd.io/_uploads/HybGAP1Ja.png) ![](https://hackmd.io/_uploads/BksAavkka.png) 這邊因為gcd沒有變化繼續看nangate45/tinyRocket ![](https://hackmd.io/_uploads/HJTmk_1k6.png) 這裡終於可以看到有個name為blockage(阻礙區)的macro被擺進來,用於預設位置。 --- #### step.2.5 Tapcell and Welltie insertion Tap cell是為了防止CMOS設計中的latch-up。 >Latch-up/閂鎖效應:指的是在積體電路中的電源和地線之間形成的短路/低阻抗路徑,導致高電流通過並損壞集成電路。它是由PNP和NPN電晶體之間的相互作用引起。這些結構類似於可控矽(SCR)。它們形成一個正反饋環路,通過短路電源線和地線,最終導致通過高電流,甚至可能永久損壞元件。 ![](https://hackmd.io/_uploads/SyIt4_yJT.png) 這次換回gcd繼續觀察,畢竟不可能不需要這個元件。 ![](https://hackmd.io/_uploads/ryALr_kyT.png) --- #### step.2.6 PDN generation ![](https://hackmd.io/_uploads/B1b3H_kka.png) 這個步驟是擺上VSS和VDD以利電源供應,顧名思義Power Delivery Network。 ![](https://hackmd.io/_uploads/ByTN8uyJ6.png) 通過gui右側的資訊可以得知 Row是VSS和VDD平行交錯擺放,以利元件擺放時上下都能接到供電跟導地 再來看到colume是一條左VSS和右VDD貫穿上下 - 總結一下Floorplan大概就是 : 1. 確定晶片尺寸和位置 2. IO,角落PAD位置(還沒有filler) 3. macro和blockage的擺放 4. VSS,VDD的分布 --- ### STEP3. Placement Placement跟floorplann很像,但這時是擺std cell/standard cells(標準元件/標準單元),所謂標準就像是已有的library中的元件,是指那些通用已設計好的cell,還是跟前一步驟所擺放手工元件有所區別的,但兩者主要的目標都是要對delay、總面積以及interconnect cost作最佳化。 正因為關係到最佳化,所以這個步驟中可能會基於幾個因素去衡量如何進行placement 1. Timing Driven 顧名思義就是指base on Timing Driven的placement。 2. Congestion Driven 在place過程中EDA tool會用global route來估算繞線的情況,所以就會考慮到壅塞的問題,以此去進行place的最佳化。 3. Power Optimization 功耗是理所當然會考慮的。 Floorplan跟Placement白話點理解可以想成先打底再去細放。 --- #### step.3.1 Global placement without placed IOs, timing-driven, and routability-driven. ![](https://hackmd.io/_uploads/HyxSnhb16.png) 進行全域佈局(Global placement)但忽略了IO port、timing和繞線等問題,在這個階段主要是將元件**大致**放置在其合適位置,但同時有考慮到timing-driven和routability-driven。 ![](https://hackmd.io/_uploads/HyEyp2b1p.png) ![](https://hackmd.io/_uploads/Hylh63hWJp.png) 所以我們看到凌亂的擺放方式是正常的,畢竟只是大致擺放,後續會再進行最佳化。 --- #### step.3.2 IO placement (non-random) ![](https://hackmd.io/_uploads/Sy4XTnWyT.png) 這邊我們可以看到步驟名為non-random,跟前面floorplan有所區別 其中一定會有限制條件或是規則,這點顯示在*IO_CONSTRANINTS*這個文件的引用上。 IOs會基於剛剛的instance擺放的位置進行一個調整重新擺放,以便後續。 而openROAD程式碼相較於以前的版本有更新過,現在都用do-step和do-copy去使整體code方面更加模組化 ![](https://hackmd.io/_uploads/ryWkwpZ1p.png) 所以這邊新增了echo方便觀察 可以得知這裡用的.tcl是*flow/scripts/io_placement.tcl* 而先前floorplan沒有產生相對應的def檔案,應該是包含在.odb了 ![](https://hackmd.io/_uploads/SJInK6-J6.png) 進入else後這個gcd項目沒有*IO_CONSTRAINTS*,所以繼續往下執行再用puts去trace的結果是*IO_PLACE_H=metal5, IO_PLACE_V=metal6*,對應下圖。 ![](https://hackmd.io/_uploads/BJKQo6-yT.png) ![](https://hackmd.io/_uploads/SJTBs6Zyp.png) 實際透過gui會看到port各自對應其layer(正方形那個)。 ![](https://hackmd.io/_uploads/BycD0hWk6.png) 如上是這個3.2step整體圖。 --- #### step.3.3 Global placement with placed IOs, timing-driven, and routability-driven. ![](https://hackmd.io/_uploads/BkAj8A-kp.png) 與3.1不同的是**with** placed IOs. 有了IO port正確的擺放之後方能讓剛剛雜亂的cell正式佈局 ![](https://hackmd.io/_uploads/ByJ5vAb1T.png) 但白色網格無從觀察是什麼。 --- #### step.3.4 Resizing & Buffering 為了符合.sdc檔案定義的timing、power規範,所以: - Resizing 依照需求調整gate大小,舉凡可能會考慮: 1. 體積占用比 2. timing 3. power consume - Buffering/ buffer insertion 增加緩衝區主要是為了改善信號完整性,信號有可能會衰減或失真,buffer可以幫助維持信號完整。 ![](https://hackmd.io/_uploads/ry26HlMJ6.png) ![](https://hackmd.io/_uploads/BJAWLlf1a.png) --- #### step.3.5 Detail placement 光看標題有些含糊,我們從makefile開始觀察 ![](https://hackmd.io/_uploads/Sy64Klzka.png) 接下來一樣去找到相對應的.tcl,也就是detail_place.tcl ![](https://hackmd.io/_uploads/ByMYYxzya.png) 可以看到中間第10行有句:detailed_placement 也是沒頭沒尾的,所以拿去google會發現它是屬於openDP支援的command: [detailed_placement](https://github.com/The-OpenROAD-Project/OpenROAD/blob/ef5421b408f17fa3556a48824c5b26590ecd9169/src/dpl/README.md) 簡單來說就是會進行一些合法化之類的最佳化,例如 1. 元件重疊:避免所有元件/單元不會在同平面重疊。 2. 網格對齊:元件要放在預先定義好的網格上,確保對齊。 (前面step3.3還搞不太清楚地那個白色網格) 3. 邊界限制:保持元件都被限制在晶片的物理邊界內。 4. ...e.t.c. ![](https://hackmd.io/_uploads/ryHwcgfy6.png) 本章節小結論:基於PPA做各種Placement的最佳化。 --- ### STEP4. CTS Clock Tree Synthesis(時鐘樹綜合)主要是為了確保時鐘信號能在最短的時間內傳遞到其驅動的所有DFF(Data Filp Flop)。 對於時鐘樹綜合,主要關注以下三個指標,並希望能盡量最佳化: 1. Low Latency 希望每個時鐘信號能在最短的時間內到達它所驅動的各個sink(也就是DFF)。 2. Low Clock Skew 偏斜是指同一clock信號到達不同DFF的時間差。理想情況下是希望所有DFF同時接收到訊號,但由於實際layout、阻抗等因素的影響,會難以達成。 3. Long Common Path 希望每個時鐘信號的共同路徑能盡量長,這有助於進一步降低clock skew,從而提高整體的性能,這是因為在共同路徑下幾乎承受的同樣的環境條件,也就是傳輸訊號的速度會幾乎相同,所以這條共同的路徑越長越好。 ![](https://hackmd.io/_uploads/BkNPhgGyp.png) #### step.4.1 Run tritonCTS ![](https://hackmd.io/_uploads/HyVdZbMya.png) 在cts.tcl這段中可以看到它在裡面直接call tritonCTS的*clock_tree_synthesis*和設置相關的參數。 ![](https://hackmd.io/_uploads/S1LufbzkT.png) --- #### step.4.2 Filler cell insertion ![](https://hackmd.io/_uploads/B14EM-fJT.png) 由fiilcel.tcl實現,以提高整體可靠性跟改善性能。 ![](https://hackmd.io/_uploads/rkdaMZzya.png) 直接放大可以看到filler都就位了。 --- ### STEP5. Routing ![](https://hackmd.io/_uploads/BkJy6bzka.png) 如同前面的結構,熟悉的寫法 #### step.5.1 global route 那就直接看第一個tcl: global_route.tcl ![](https://hackmd.io/_uploads/H104abMyp.png) 前面第九行這個判斷式是判斷有沒有要用[fastroute](https://github.com/The-OpenROAD-Project-Attic/FastRoute) 然後重點是24行的[global_route](https://openroad.readthedocs.io/en/latest/main/src/grt/README.html) 1. 在這階段它會布局但不會走線,也就是說先無視地形連連看(想像成打草稿) 2. 實際走線還要搭配它生成的route.guide(指導各種routing時該如何穿梭於layer) 3. 最後如果沒有輸入相關的參數,它默認是會壅塞迭代20次,每5次產生一個.drc(Design Rules Check)檔案以供參考。變相來說是提供了一個能讓使用者能夠動態地指定額外的route參數。 ![](https://hackmd.io/_uploads/S1HnrMMka.png) 我把全部圈選起來,雖然不太清楚,但還是可以看到route是挺隨心所欲的。 --- #### step.5.2 detail route detail_route.tcl部分段落如下圖所示 ![](https://hackmd.io/_uploads/SJKeOGGyp.png) [detail_route](https://openroad.readthedocs.io/en/latest/main/src/grt/README.html)透過topology algo去實現最佳化。 ![](https://hackmd.io/_uploads/SkEq_GM1p.png) 執行完之後就可以看到完全體了! --- ### STEP.6 Finish ![](https://hackmd.io/_uploads/BJSLVym1a.png) 最後FINISH依賴於: - *.log* - *.v* - *.sdc* - *GDS_FNAL_FILE* 而*elapsed*會透過$genlapsedtime.py$在*log*目錄下產生對應的*.json*檔案去紀錄處理時間 ![](https://hackmd.io/_uploads/ryF-vJmJa.png)   這裡根據 *$(USE_FILL)* 決定是否進行*density_fill*的填充步驟;否則,它會直接複製 5_route.odb 到 6_1_fill.odb,意味著不進行填充。   density_fill也就是去做Dummy Metal的增加,晶圓廠對金屬密度有一定的規定,以防止在晶片製造中的蝕刻階段會過度蝕刻而導致影響性能,所以才要增加金屬密度。 ![](https://hackmd.io/_uploads/SJDVY17ka.png) mock測試中如如果有一些很難產生的檔案對象,例如上圖所提到的resize、CTS等等,它們都很耗時,所以若在具體測試時我們可以直接利用已有的檔案來加速模擬測試。可以看到上述都是*copy*指令。 ![](https://hackmd.io/_uploads/H1K19k71a.png) 目標一樣是GDSII,這裡使用klayout去整合包裝好的Macros,也就是IP整合。 ![](https://hackmd.io/_uploads/SylmiyXJ6.png) ![](https://hackmd.io/_uploads/BySusk7yT.png) 最後在這階段與上個merge不同的是這是針對GDS檔案之間的合併。 - DRC (Design Rule Check) 用來檢查佈局是否符合製程技術的規則。這包括規則如金屬層寬度、空間、孔徑大小等。 - CDL (Circuit Description Language) 主要用於描述模擬Netlist,通常是要用於傳遞netlist訊息給相關的simulator,如 SPICE(Simulation Program with Integrated Circuit Emphasis) - .cdl檔案用於LVS(Layout Versus Schematic)檢查。 - LVS (Layout Versus Schematic) cdl檔案(代表netlist)會與GDS檔案(代表佈局)進行比對,以確認它們等效。 也就這段中的*GDS_FINAL_FILE*和(OBJECTS_DIR)/6_final_concat.cdl來比較。 再來整段finish終於結束了!用*make gui_final*看一次。 ![](https://hackmd.io/_uploads/BJ41JlQk6.png) ## Part.2 Synthesis 細項講解 ```cmake= # ============================================================================== # ______ ___ _ _____ _ _ _____ ____ ___ ____ # / ___\ \ / / \ | |_ _| | | | ____/ ___|_ _/ ___| # \___ \\ V /| \| | | | | |_| | _| \___ \| |\___ \ # ___) || | | |\ | | | | _ | |___ ___) | | ___) | # |____/ |_| |_| \_| |_| |_| |_|_____|____/___|____/ # .PHONY: synth synth: versions.txt \ $(RESULTS_DIR)/1_synth.v \ $(RESULTS_DIR)/1_synth.sdc .PHONY: synth-report synth-report: synth $(UNSET_AND_MAKE) do-synth-report .PHONY: do-synth-report do-synth-report: ($(TIME_CMD) $(OPENROAD_CMD) $(SCRIPTS_DIR)/synth_metrics.tcl) 2>&1 | tee -a $(LOG_DIR)/1_1_yosys.log # ============================================================================== # Run Synthesis using yosys #------------------------------------------------------------------------------- SYNTH_SCRIPT ?= $(SCRIPTS_DIR)/synth.tcl $(SYNTH_STOP_MODULE_SCRIPT): mkdir -p $(RESULTS_DIR) $(LOG_DIR) $(REPORTS_DIR) ($(TIME_CMD) $(YOSYS_CMD) $(YOSYS_FLAGS) -c $(HIER_REPORT_SCRIPT)) 2>&1 | tee $(LOG_DIR)/1_1_yosys_hier_report.log ifeq ($(SYNTH_HIERARCHICAL), 1) $(RESULTS_DIR)/1_1_yosys.v: $(SYNTH_STOP_MODULE_SCRIPT) endif $(RESULTS_DIR)/1_1_yosys.v $(RESULTS_DIR)/1_synth.sdc &: $(DONT_USE_LIBS) $(WRAPPED_LIBS) $(DONT_USE_SC_LIB) $(DFF_LIB_FILE) $(VERILOG_FILES) $(CACHED_NETLIST) $(LATCH_MAP_FILE) $(ADDER_MAP_FILE) $(SDC_FILE) mkdir -p $(RESULTS_DIR) $(LOG_DIR) $(REPORTS_DIR) ($(TIME_CMD) $(YOSYS_CMD) $(YOSYS_FLAGS) -c $(SYNTH_SCRIPT)) 2>&1 | tee $(LOG_DIR)/1_1_yosys.log cp $(SDC_FILE) $(RESULTS_DIR)/1_synth.sdc $(RESULTS_DIR)/1_synth.v: $(RESULTS_DIR)/1_1_yosys.v mkdir -p $(RESULTS_DIR) $(LOG_DIR) $(REPORTS_DIR) cp $< $@ .PHONY: clean_synth clean_synth: rm -f $(RESULTS_DIR)/1_*.v $(RESULTS_DIR)/1_synth.sdc rm -f $(REPORTS_DIR)/synth_* rm -f $(LOG_DIR)/1_* rm -f $(SYNTH_STOP_MODULE_SCRIPT) rm -rf _tmp_yosys-abc-* ``` 以上是Synthesis步驟的核心makefile,由此來窺探一二。 ```cmake= .PHONY: synth synth: versions.txt \ $(RESULTS_DIR)/1_synth.v \ $(RESULTS_DIR)/1_synth.sdc .PHONY: synth-report synth-report: synth $(UNSET_AND_MAKE) do-synth-report .PHONY: do-synth-report do-synth-report: ($(TIME_CMD) $(OPENROAD_CMD) $(SCRIPTS_DIR)/synth_metrics.tcl) 2>&1 | tee -a $(LOG_DIR)/1_1_yosys.log ``` 這段可發現主要的方式是$make synth$指令依賴於三個檔案 - version.txt用以記錄版本。從-V,>>等指令可觀察出(如下code)。 - 1_synth.v後續尤其他指令執行生成。 - 1_synth.sdc同上。 ```cmake= # Utility to print tool version information #------------------------------------------------------------------------------- .PHONY: versions.txt versions.txt: @$(YOSYS_CMD) -V > $@ @echo openroad `$(OPENROAD_EXE) -version` >> $@ @$(KLAYOUT_CMD) -zz -v >> $@ ``` 延續上一段,synth-report依賴於synth,也就是一旦執行到make synth,他會執行do-synth-report,用以生成相關量測/度量的資訊。 ```cmake= # Run Synthesis using yosys #------------------------------------------------------------------------------- SYNTH_SCRIPT ?= $(SCRIPTS_DIR)/synth.tcl $(SYNTH_STOP_MODULE_SCRIPT): mkdir -p $(RESULTS_DIR) $(LOG_DIR) $(REPORTS_DIR) ($(TIME_CMD) $(YOSYS_CMD) $(YOSYS_FLAGS) -c $(HIER_REPORT_SCRIPT)) 2>&1 | tee $(LOG_DIR)/1_1_yosys_hier_report.log ifeq ($(SYNTH_HIERARCHICAL), 1) $(RESULTS_DIR)/1_1_yosys.v: $(SYNTH_STOP_MODULE_SCRIPT) endif $(RESULTS_DIR)/1_1_yosys.v $(RESULTS_DIR)/1_synth.sdc &: $(DONT_USE_LIBS) $(WRAPPED_LIBS) $(DONT_USE_SC_LIB) $(DFF_LIB_FILE) $(VERILOG_FILES) $(CACHED_NETLIST) $(LATCH_MAP_FILE) $(ADDER_MAP_FILE) $(SDC_FILE) mkdir -p $(RESULTS_DIR) $(LOG_DIR) $(REPORTS_DIR) ($(TIME_CMD) $(YOSYS_CMD) $(YOSYS_FLAGS) -c $(SYNTH_SCRIPT)) 2>&1 | tee $(LOG_DIR)/1_1_yosys.log cp $(SDC_FILE) $(RESULTS_DIR)/1_synth.sdc $(RESULTS_DIR)/1_synth.v: $(RESULTS_DIR)/1_1_yosys.v mkdir -p $(RESULTS_DIR) $(LOG_DIR) $(REPORTS_DIR) cp $< $@ .PHONY: clean_synth clean_synth: rm -f $(RESULTS_DIR)/1_*.v $(RESULTS_DIR)/1_synth.sdc rm -f $(REPORTS_DIR)/synth_* rm -f $(LOG_DIR)/1_* rm -f $(SYNTH_STOP_MODULE_SCRIPT) rm -rf _tmp_yosys-abc-* ``` (再來這邊才是實際上做合成動作的地方。如下,我們先將所有的export都先放上來以方便對照,$DIR等path就不放上來,僅放重要資訊。) 看到ifeq語句那邊,他用來判斷是否要使用SYNTH_HIERARCHICAL的選項。如果hierarchical成立,則往看看到\$(SYNTH_STOP_MODULE_SCRIPT)。 在它的執行規則中最重要是的-c所引入的腳本不同:synth_hier_report.tcl,其內容大概是指定yosys(此階段邏輯合成所用的開源專案)使用階層式的方式去合成。(其展示內容在export展示區下面) :::info 題外話:在本學期上林銘波老師的FPGA實務時有聽到關於階層式合成的分享,現在合成都是以module為一個單位,若是用非階層式(扁平式),將所有module都拆解開來視為一個大module去合成,雖然可優化的空間和潛力都更多,但整體時間會非常耗時;反之用階層式的好處除了時間較少以外,也更好驗證和debug,畢竟是以各個module為單位。而業界目前都是採用後者居多(階層式)。 ::: ```cmake= # Enables hierarchical yosys export SYNTH_HIERARCHICAL ?= 0 export SYNTH_STOP_MODULE_SCRIPT = $(OBJECTS_DIR)/mark_hier_stop_modules.tcl ifeq ($(SYNTH_HIERARCHICAL), 1) export HIER_REPORT_SCRIPT = $(SCRIPTS_DIR)/synth_hier_report.tcl export MAX_UNGROUP_SIZE ?= 0 endif # ----------------- TIME_CMD = /usr/bin/time -f 'Elapsed time: %E[h:]min:sec. CPU time: user %U sys %S (%P). Peak memory: %MKB.' TIME_TEST = $(shell $(TIME_CMD) echo foo 2>/dev/null) ifeq (, $(strip $(TIME_TEST))) TIME_CMD = /usr/bin/time endif # ----------------- # The following determine the executable location for each tool used by this flow. # Priority is given to # 1 user explicit set with variable in Makefile or command line, for instance setting OPENROAD_EXE # 2 ORFS compiled tools: openroad, yosys export OPENROAD_EXE ?= $(abspath $(FLOW_HOME)/../tools/install/OpenROAD/bin/openroad) OPENROAD_ARGS = -no_init $(OR_ARGS) OPENROAD_CMD = $(OPENROAD_EXE) -exit $(OPENROAD_ARGS) OPENROAD_NO_EXIT_CMD = $(OPENROAD_EXE) $(OPENROAD_ARGS) OPENROAD_GUI_CMD = $(OPENROAD_EXE) -gui $(OR_ARGS) YOSYS_CMD ?= $(abspath $(FLOW_HOME)/../tools/install/yosys/bin/yosys) LSORACLE_PLUGIN ?= $(abspath $(FLOW_HOME)/../tools/install/yosys/share/yosys/plugin/oracle.so) ifneq ($(USE_LSORACLE),) YOSYS_FLAGS ?= -m $(LSORACLE_PLUGIN) endif YOSYS_FLAGS += -v 3 ``` ```clike= # synth_hier_report.tcl part.1 source $::env(SCRIPTS_DIR)/synth_preamble.tcl ``` synth_preamble.tcl主要是用來初始化設定一些文件或lib的位置,以及作一些前置的初步設定,還有讀取相關verilog file和lib。 ```clike= # synth_hier_report.tcl part.2 set constr [open $::env(OBJECTS_DIR)/abc.constr w] puts $constr "set_driving_cell $::env(ABC_DRIVER_CELL)" puts $constr "set_load $::env(ABC_LOAD_IN_FF)" close $constr ``` 設置abc.constr約束文件檔,以方便後續階層化合成。 ```clike= # synth_hier_report.tcl part.3 # Hierarchical synthesis synth -top $::env(DESIGN_NAME) ``` 合成時都須先指定top module。 ```clike= # synth_hier_report.tcl part.4 if { [info exist ::env(ADDER_MAP_FILE)] && [file isfile $::env(ADDER_MAP_FILE)] } { techmap -map $::env(ADDER_MAP_FILE) } techmap ``` 針對特定的原件,改變了元件(module)的映射實現方式。 ```clike= # synth_hier_report.tcl part.5 # ...省略 set out_script_ptr [open $::env(OBJECTS_DIR)/mark_hier_stop_modules.tcl w] puts $out_script_ptr "hierarchy -check -top $::env(DESIGN_NAME)" foreach module $module_list { tee -o $::env(REPORTS_DIR)/synth_hier_stat_temp_module.txt stat -top "$module" {*}$stat_libs set fptr1 [open $::env(REPORTS_DIR)/synth_hier_stat_temp_module.txt r] set contents1 [read -nonewline $fptr1] close $fptr1 set split_cont1 [split $contents1 "\n"] foreach line $split_cont1 { if {[regexp { +Chip area for top module '(\S+)': (.*)} $line -> module_name area]} { puts "Area of module $module_name is $area" if {[expr $area > $ungroup_threshold]} { puts "Preserving hierarchical module: $module_name" puts $out_script_ptr "select -module {$module_name}" puts $out_script_ptr "setattr -mod -set keep_hierarchy 1" puts $out_script_ptr "select -clear" } } } file delete -force $::env(REPORTS_DIR)/synth_hier_stat_temp_module.txt } close $out_script_ptr } ``` 對特定的module去設定屬性,指定他們使用階層化合成。 將這些yosys的指令寫入mark_hier_stop_modules.tcl,以利後續的合成時可以invoke這個文件去執行tcl中剛剛紀錄的這些命令。 ```clike= # ~/OpenROAD-flow-scripts/flow/objects/nangate45/tinyRocket/ # base/mark_hier_stop_modules.tcl hierarchy -check -top RocketTile select -module {\RocketTile} setattr -mod -set keep_hierarchy 1 select -clear select -module {\DCache} setattr -mod -set keep_hierarchy 1 select -clear select -module {\Frontend} setattr -mod -set keep_hierarchy 1 select -clear select -module {\Rocket} setattr -mod -set keep_hierarchy 1 select -clear select -module {\CSRFile} setattr -mod -set keep_hierarchy 1 select -clear ``` 可以看到指定module後使用-set keep_hierarchy 1去指定使用階層化合成的屬性,然後-clear是取消對現在module的操作,繼續往下執行...。 再來回到makefile中 # Run Synthesis using yosys那段繼續看下去。 ```clike= # Run Synthesis using yosys #------------------------------------------------------------------------------- SYNTH_SCRIPT ?= $(SCRIPTS_DIR)/synth.tcl $(SYNTH_STOP_MODULE_SCRIPT): mkdir -p $(RESULTS_DIR) $(LOG_DIR) $(REPORTS_DIR) ($(TIME_CMD) $(YOSYS_CMD) $(YOSYS_FLAGS) -c $(HIER_REPORT_SCRIPT)) 2>&1 | tee $(LOG_DIR)/1_1_yosys_hier_report.log ifeq ($(SYNTH_HIERARCHICAL), 1) $(RESULTS_DIR)/1_1_yosys.v: $(SYNTH_STOP_MODULE_SCRIPT) endif $(RESULTS_DIR)/1_1_yosys.v $(RESULTS_DIR)/1_synth.sdc &: $(DONT_USE_LIBS) $(WRAPPED_LIBS) $(DONT_USE_SC_LIB) $(DFF_LIB_FILE) $(VERILOG_FILES) $(CACHED_NETLIST) $(LATCH_MAP_FILE) $(ADDER_MAP_FILE) $(SDC_FILE) mkdir -p $(RESULTS_DIR) $(LOG_DIR) $(REPORTS_DIR) ($(TIME_CMD) $(YOSYS_CMD) $(YOSYS_FLAGS) -c $(SYNTH_SCRIPT)) 2>&1 | tee $(LOG_DIR)/1_1_yosys.log cp $(SDC_FILE) $(RESULTS_DIR)/1_synth.sdc $(RESULTS_DIR)/1_synth.v: $(RESULTS_DIR)/1_1_yosys.v mkdir -p $(RESULTS_DIR) $(LOG_DIR) $(REPORTS_DIR) cp $< $@ ``` 1_1_yosys.v, 1_synth.sdc 這邊是使用"&:" Group target的方式去編譯,以保持這兩個都是最新的。1_1_yosys.v執行規則那與剛剛SYNTH_STOP_MODULE_SCRIPT不同的是指定的script tcl不同。 然後1_synth.sdc是由\$(SDC_FILE)直接複製過來。 再來看本次主軸,也是完整合成流程的synth.tcl ```clike= # synth.tcl part.1 source $::env(SCRIPTS_DIR)/synth_preamble.tcl ``` 同之前,一樣是前置設定。 ```clike= # synth.tcl part.2 if { [info exist ::env(SYNTH_HIERARCHICAL)] && $::env(SYNTH_HIERARCHICAL) == 1 && [file isfile $::env(SYNTH_STOP_MODULE_SCRIPT)] } { puts "Sourcing $::env(SYNTH_STOP_MODULE_SCRIPT)" source $::env(SYNTH_STOP_MODULE_SCRIPT) } ``` 如果有hierarchical的話就source啟用我們剛剛的SYNTH_STOP_MODULE_SCRIPT,好方便module的階層化屬性設置。 ```clike= # synth.tcl part.3 # Generic synthesis synth -top $::env(DESIGN_NAME) {*}$::env(SYNTH_ARGS) if { [info exists ::env(USE_LSORACLE)] } { set lso_script [open $::env(OBJECTS_DIR)/lso.script w] puts $lso_script "ps -a" puts $lso_script "oracle --config $::env(LSORACLE_KAHYPAR_CONFIG)" puts $lso_script "ps -m" puts $lso_script "crit_path_stats" puts $lso_script "ntk_stats" close $lso_script # LSOracle synthesis lsoracle -script $::env(OBJECTS_DIR)/lso.script -lso_exe $::env(LSORACLE_CMD) techmap } ``` 一樣先用top設定主要module。 檢查是否有用LSORACLE。條件式寫入lsoracle相關命令到lso.script中。 - LSORACLE yosys的插件,2.0版本中使用machine learning搭配AIG boolean function的方式去做優化,執行時間有效減少,以及對減少面積使用率等優勢[1]。 ```clike= # synth.tcl part.4 # Optimize the design opt -purge ``` 使用opt做優化, -purge標誌是為了消除冗餘的element,等於在優化過程中還刪去或整併多餘的元件。 ```clike= # synth.tcl part.5 # Technology mapping of adders if {[info exist ::env(ADDER_MAP_FILE)] && [file isfile $::env(ADDER_MAP_FILE)]} { # extract the full adders extract_fa # map full adders techmap -map $::env(ADDER_MAP_FILE) techmap # Quick optimization opt -fast -purge } # Technology mapping of latches if {[info exist ::env(LATCH_MAP_FILE)]} { techmap -map $::env(LATCH_MAP_FILE) } # Technology mapping of flip-flops # dfflibmap only supports one liberty file if {[info exist ::env(DFF_LIB_FILE)]} { dfflibmap -liberty $::env(DFF_LIB_FILE) } else { dfflibmap -liberty $::env(DONT_USE_SC_LIB) } opt ``` 這些是我們熟悉的techmap,對各元件做mapping,然後會在過程中穿插opt,利用更好的優化空間/潛力去做優化。 其中可以看到有adder, latches, dff相關的map操作,其中dfflibmap是會根據library的配置文件(DFF_LIB_FILE或DONT_USE_SC_LIB)去執行。 ```clike= # synth.tcl part.6 set constr [open $::env(OBJECTS_DIR)/abc.constr w] puts $constr "set_driving_cell $::env(ABC_DRIVER_CELL)" puts $constr "set_load $::env(ABC_LOAD_IN_FF)" close $constr ``` 向abc.constr 文件寫入兩個命令。這些命令分別設置了驅動單元(set_driving_cell)和負載(set_load),其參數來自環境變量 ABC_DRIVER_CELL 和 ABC_LOAD_IN_FF。 ```clike= # synth.tcl part.7 if {$::env(ABC_AREA)} { puts "Using ABC area script." set abc_script $::env(SCRIPTS_DIR)/abc_area.script } else { puts "Using ABC speed script." set abc_script $::env(SCRIPTS_DIR)/abc_speed.script } ``` 根據ABC_AREA來選擇相應的script. ```clike= # synth.tcl part.8 # Technology mapping for cells # ABC supports multiple liberty files, but the hook from Yosys to ABC doesn't if {[info exist ::env(ABC_CLOCK_PERIOD_IN_PS)]} { puts "\[FLOW\] Set ABC_CLOCK_PERIOD_IN_PS to: $::env(ABC_CLOCK_PERIOD_IN_PS)" abc -D [expr $::env(ABC_CLOCK_PERIOD_IN_PS)] \ -script $abc_script \ -liberty $::env(DONT_USE_SC_LIB) \ -constr $::env(OBJECTS_DIR)/abc.constr } else { puts "\[WARN\]\[FLOW\] No clock period constraints detected in design" abc -liberty $::env(DONT_USE_SC_LIB) \ -constr $::env(OBJECTS_DIR)/abc.constr } ``` 檢查ABC_CLOCK_PERIOD_IN_PS。如果成立,則在使用 ABC 進行techmap時設置clock cycle約束;如果不存在,則print警告訊息。 abc 命令使用了先前設置的script、liberty檔(DONT_USE_SC_LIB)和約束檔(abc.constr)。 -D 選項用於設置clock peroid 總結來說就是碼負責準備ABC tool的執行環境,包括設置約束、選擇合適的腳本,並根據時鐘周期的存在與否執行 ABC 命令。 ```clike= # synth.tcl part.9 # Replace undef values with defined constants setundef -zero # Splitting nets resolves unwanted compound assign statements in netlist (assign {..} = {..}) splitnets # Remove unused cells and wires opt_clean -purge # Technology mapping of constant hi- and/or lo-drivers hilomap -singleton \ -hicell {*}$::env(TIEHI_CELL_AND_PORT) \ -locell {*}$::env(TIELO_CELL_AND_PORT) # Insert buffer cells for pass through wires insbuf -buf {*}$::env(MIN_BUF_CELL_AND_PORTS) ``` 這段是passes。 - setundef -zero 命令用於將所有未定義(undef)的signal都設置為零。未定義的signal可能導致非預期的行為,因此將它們設置為已知常數,可以提高設計的穩定性。 - splitnets 切割network。例如複合賦值語句(例如 assign {a, b} = {c, d})可能會在netlish中產生不需要的複雜結構。這個命令將這些複合結構拆分成更簡單的單一連接的樣子。 - opt_clean -purge 移除未使用cells & wires,助於簡化設計,減少不必要的元件。 - hilomap 用於techmap中將常數1或0的這種穩定輸出的driving signal去做特定的mapping。 - -singleton 指定"單例模式"(Singleton Pattern),只實例化一個,不需要重複的。 - insbuf 用於在經過的連線上插入緩衝元件(buffer cells)以改善訊號完整性(增強其訊號)。特別是在長距離連線或高頻設計中。 - -buf 參數指定用於buffer的元件及其port。 ```clike= # synth.tcl part.10 # Reports tee -o $::env(REPORTS_DIR)/synth_check.txt check # Create argument list for stat set stat_libs "" foreach lib $::env(DONT_USE_LIBS) { append stat_libs "-liberty $lib " } tee -o $::env(REPORTS_DIR)/synth_stat.txt stat {*}$stat_libs # Write synthesized design write_verilog -noattr -noexpr -nohex -nodec $::env(RESULTS_DIR)/1_1_yosys.v ``` 最後紀錄一些logs和輸出我們要的1_1_yosys.v,至此最重要的synth.tcl都大致了解了。 ## YOSYS 因為openROAD在合成步驟主要是藉由yosys來完成,而不是某種特定的單一演算法,所以我們直接概略性的來介紹一下yosys[2]。 ![image](https://hackmd.io/_uploads/B1k90ugwa.png) 首先做為一個synthesizer它所涵蓋的能力如下。 ![image](https://hackmd.io/_uploads/r1sJJFevT.png) 作為design compiler其實它的架構和一般的high-level program compiler如LLVM的架構是很像的,都是由frontend經過各種pass再到backend。 - Front-End :主要擔當讀取並paring的工作,會轉換成AST(Abstract Symbol Tree)。 - Passes : 其實這邊Pass不一定像LLVM那樣是ordering處理的,它可能是平行的處理各部件,但這裡因為簡化表示而做成sequency的呈現方式。 - Back-End : 做最後轉換成我們要的目標平台的檔案。 然後我們搭配$make synth$後會產生的1_1_yosys.log來觀察一下它是怎麼個流程,其實就跟tcl的流程一樣,不過藉由log更能了解其內容。 ``` # 1_1_yosys.log part.1 1. Executing Verilog-2005 frontend: ./designs/src/tinyRocket/AsyncResetReg.v 2. Executing Verilog-2005 frontend: ./designs/src/tinyRocket/ClockDivider2.v ... 7. Executing Liberty frontend: ./objects/nangate45/tinyRocket/base/lib/NangateOpenCellLibrary_typical.lib ... ``` 這裡是透過synth_preamble.tcl去執行 $read_verilog $read_liberty ``` # 1_1_yosys.log part.2 Sourcing ./objects/nangate45/tinyRocket/base/mark_hier_stop_modules.tcl ``` source \$::env(SYNTH_STOP_MODULE_SCRIPT) ``` # 1_1_yosys.log part.3 11. Executing HIERARCHY pass (managing design hierarchy). ``` $synth -top \$::env(DESIGN_NAME) {*}$::env(SYNTH_ARGS) ``` # 1_1_yosys.log part.4 12. Executing AST frontend in derive mode using pre-parsed AST for module `\RocketTile'. 12.1. Analyzing design hierarchy.. 12.2. Executing AST frontend in derive mode using pre-parsed AST for module `\Rocket'. ... ``` 由front-end的parser開始轉換成AST表達形式。 ``` # 1_1_yosys.log part.5 13. Executing SYNTH pass. 13.1. Executing HIERARCHY pass (managing design hierarchy). 13.1.1. Analyzing design hierarchy.. 13.1.2. Analyzing design hierarchy.. 13.2. Executing PROC pass (convert processes to netlists). 13.2.1. Executing PROC_CLEAN pass (remove empty switches from decision trees). ... ``` $synth -top \$::env(DESIGN_NAME) {*}$::env(SYNTH_ARGS) ``` # 1_1_yosys.log part.6 13.3. Executing FLATTEN pass (flatten design). ... ``` export SYNTH_ARGS ?= -flatten ``` # 1_1_yosys.log part.7 15. Executing EXTRACT_FA pass (find and extract full/half adders). 16. Executing TECHMAP pass (map to technology primitives). ... ``` \# extract the full adders extract_fa \# map full adders techmap -map $::env(ADDER_MAP_FILE) techmap ``` # 1_1_yosys.log part.8 19.2. Continuing TECHMAP pass. 20. Executing DFFLIBMAP pass (mapping DFF cells to sequential cells from liberty file). Warning: Found unsupported expression 'SE*SI+D*!SE' in pin attribute of cell 'SDFFRS_X1' - skipping. Warning: Found unsupported expression 'SE*SI+D*!SE' in pin attribute of cell 'SDFFRS_X2' - skipping. ... 20.1. Executing DFFLEGALIZE pass (convert FFs to types supported by the target). ``` \# Technology mapping of flip-flops \# dfflibmap only supports one liberty file if {[info exist ::env(DFF_LIB_FILE)]} { dfflibmap -liberty $::env(DFF_LIB_FILE) } else { dfflibmap -liberty $::env(DONT_USE_SC_LIB) } opt ``` # 1_1_yosys.log part.9 Using ABC speed script. [FLOW] Set ABC_CLOCK_PERIOD_IN_PS to: 2.0322 22. Executing ABC pass (technology mapping using ABC). 22.1. Extracting gate netlist of module `\CSRFile' to `<abc-temp-dir>/input.blif'.. 22.1.1. Executing ABC. 22.1.2. Re-integrating ABC results. 22.2. Extracting gate netlist of module `\DCache' to `<abc-temp-dir>/input.blif'.. 22.2.1. Executing ABC. ``` puts "Using ABC speed script."[3] set abc_script $::env(SCRIPTS_DIR)/abc_speed.script ``` # 1_1_yosys.log part.10 23. Executing SETUNDEF pass (replace undef values with defined constants). 24. Executing SPLITNETS pass (splitting up multi-bit signals). 25. Executing OPT_CLEAN pass (remove unused cells and wires). 26. Executing HILOMAP pass (mapping to constant drivers). 27. Executing INSBUF pass (insert buffer cells for connected wires). 28. Executing CHECK pass (checking for obvious problems). 29. Printing statistics. ``` \# Replace undef values with defined constants setundef -zero \# Splitting nets resolves unwanted compound assign statements in netlist (assign {..} = {..}) splitnets \# Remove unused cells and wires opt_clean -purge \# Technology mapping of constant hi- and/or lo-drivers hilomap -singleton \ -hicell {*}\$::env(TIEHI_CELL_AND_PORT) \ -locell {*}\$::env(TIELO_CELL_AND_PORT) \# Insert buffer cells for pass through wires insbuf -buf {*}$::env(MIN_BUF_CELL_AND_PORTS) \# Reports tee -o $::env(REPORTS_DIR)/synth_check.txt check \# Create argument list for stat set stat_libs "" foreach lib $::env(DONT_USE_LIBS) { append stat_libs "-liberty $lib " } tee -o \$::env(REPORTS_DIR)/synth_stat.txt stat {*}$stat_libs ``` # 1_1_yosys.log part.11 30. Executing Verilog backend. ``` \# Write synthesized design write_verilog -noattr -noexpr -nohex -nodec \$::env(RESULTS_DIR)/1_1_yosys.v ``` # 1_1_yosys.log part.12 Warnings: 9 unique messages, 75 total End of script. Logfile hash: 3e646a7759, CPU: user 77.98s system 0.04s, MEM: 243.56 MB peak Yosys 0.33 (git sha1 2584903a0, gcc 9.4.0-1ubuntu1~20.04.2 -fPIC -Os) Time spent: 51% 1x extract_fa (52 sec), 23% 2x abc (23 sec), ... Elapsed time: 1:41.14[h:]min:sec. CPU time: user 100.15 sys 0.99 (100%). Peak memory: 249408KB. ``` 最後統計結果輸出。 藉由這種觀察與整理可以很清楚地了解整個tcl的運作流程。 ### Try yosys 這裡因為gcd專案體量較小,所以用gcd嘗試。 ``` $yosys // 啟動 $read_verilog gcd.v // 讀檔 $synth // 合成 $select gcd // 指定 $show // 展示netlist圖 ``` ![image](https://hackmd.io/_uploads/S1KesKgwa.png) 可以看到read後,居然是轉換成RTLIL(Register Transfer Level Intermediate Language),其實這是再經由AST轉化而成的tree,也是一種中間表達式,以這種模組化compiler來說中間表達是很重要,可以無視前後端而專注於優化這件事。下圖是轉換flow。 ![image](https://hackmd.io/_uploads/HJce3tgwT.png) ![image](https://hackmd.io/_uploads/HJWN2tevT.png) 繼續執行$synth ![image](https://hackmd.io/_uploads/SJEP9FePa.png) 我們會得到這種以人類來說可讀性不高,但對後續演算法很有幫助的netlist表達法。可以看到D Flip Flop全都被轉換成固有的表達方式。 ![](https://hackmd.io/_uploads/HJan7yT03.png) 最後延續剛剛的\$select gcd後直接\$show 這就是最終的netlist圖示。 ## 心得 對於這次的報告選擇合成是因為剛好這學期有修FPGA實務,想說對design compiler希望能多了解。openROAD本身的makefile和文件管理都做得挺不錯的,可讀性十分之高,幾乎看著makefile和一些些資料就可以看得懂了,其中困難處是yosys和ABC這邊,畢竟是編譯器/合成器,內部的演算法實作有點多而雜,很難一時之間有個了解,而ABC這邊是用And-Inverter Graph表示,教授上課提過的陳勇志老師會開一門邏輯合成與驗證會教的內容也是AIG,但很難看到內部實現的過程,所以目前來說再深入的話有點困難,但整體上還是比第一次使用openROAD時有了更多的了解。 以目前的OpenROAD-flow-scripts其實有很多細項沒有幫含在內,例如本文有說到openDB,所以正常在合成完後會去做static timing analysis和驗證,不過這個專案內容中不包含,要去另外找到openSTA才有這項功能。 我真心覺得這項作業甚至是這門課作為EDA概論真的設計得很不錯,最後希望能讓我pass這門課,謝謝。 --- ## Reference 1. chrome-extension://bocbaocobfecmglnmeaeppambideimao/pdf/viewer.html?file=https%3A%2F%2Fwoset-workshop.github.io%2FPDFs%2F2021%2Fa12.pdf 2. https://www.youtube.com/watch?v=tqreXj5GP_4 3. https://people.eecs.berkeley.edu/~alanmi/abc/abc.htm 4. chrome-extension://bocbaocobfecmglnmeaeppambideimao/pdf/viewer.html?file=http%3A%2F%2Fcc.ee.ntu.edu.tw%2F~ywchang%2FPapers%2Ftcad06-mfloorplanning.pdf