이 글은 2000년 쯤에 정리한 것으로 새로운 ASIC의 합성에 즈음하여 전에 대강 알고 있던 것을 다시 remind하고 새로 중요한 부분을 정리하기 위해 Kluwer Academic Publishers에서 나온 Logic Synthesis Using Synopsys, 2nd edition, by Pran Kurup & Taher Abbasi 라는 책을 읽다가 돌발적으로 정리한 글입니다. 합성에 있어 기본적인 기술을, 또는 가능하면 전문적인 기술을 터득하고 싶으신 분이라면 한번 읽어 보시면 도움이 될 것입니다. 물론 직접 일을 통해 어려운 경우와 씨름을 해 보고 dc_shell의 매뉴얼을 많이 읽고 시도해 보아야 완전히 터득하겠지요?
(* 저의 주종목은 주로 ASIC개발에 있어 coding 및 simulation을 통한 verification이라고 할 수 있습니다
합성을 못했던 것은 아니었지만 몇 년 동안 연구소에서 개발한 ASIC에서는 합성은 주로 외부의 용역회사에서 하였기 때문에 coding이나 verification 기술에 걸맞는 합성기술을 터득할 기회가 없었던 것 같습니다(합성작업을 할 때는 주로 옆에서 보는 입장이었죠). 그러다가 2004년에 EPMC ASIC 개발하면서 합성을 직접 하게 되어 새로운 기술도 익히고 어느 정도 자신있게 되었습니다. 무슨 기술이든 책만 보고 이해한다고 되는 것이 아니고 옆에서 본다고 되는 것도 아니며 직접 처음부터 끝까지 해야지 자기 것이 되는 법이니까요. )
가장 기본적인 사항
먼저 SYNOPSY의 design compiler(dc_shell)에서 기본적으로 설정해야 할 변수로 아래의 네 개가 있습니다.
dc_shell> search_path = search_path+{".","./lib",."/vhdl","./script"}
dc_shell> target_library = {target.db}
dc_shell> link_library = {link.db}
dc_shell> symbol_library = {symbol.sdb}
위의 의미는 아시겠죠? Search path는 뒤에 지정할 library 파일을 찾을 위치를 지정하는 것이고(db directory를 search_path에 포함시켜 두고 그 곳에 기존에 합성된 db가 있으면 자동으로 link됩니다.) target_library는 합성할 때 사용할 technology library, link_library는 이미 합성된 netlist가 있을 때 어떤 technology를 사용하여 연결할 것인가를 지정합니다. 보통 link_library는 target_library를 포함하겠지요? Symbol_library는 schematic을 보고자 할 때 사용할 symbol의 모양을 가지고 있습니다. 이렇게 사용할 library file들을 환경에 연결시켜 주어야 합성을 시작할 수 있습니다. 그리고 *는 현재 dc_shell에 올라와 있는 design들을 뜻합니다. (보통 .synopsys_dc.setup 파일에 미리 써 둡니다.)
DC(design compiler)에서 cell이나 instance는 정말로 instance의 이름입니다. 해당 entity에서 불려지는 hierarchy상의 이름이고요, reference는 instantiated된 그 entity의 종류입니다.
analyze하면 default library 또는 정해진 Library 에 들어가는 것이고(.mra, .syn, .sim 파일 생성)
elaborate는 library에 있는 내용을 elaborate하는 것입니다.
그리고 read = analyze + elaborate 라고 생각하시면 됩니다.
따라서
dc_shell> define_design_lib states -path ./lib
dc_shell> analyze -f vhdl my_pack.vhd -lib states
하면 my_pack.vhd가 analyzed되어 states라는 library(./lib에 위치)에 들어갑니다. default 로 design_library는 work가 되지만 만약 lib1이라는 library에 analyzed되어 있는 leaf라는 entity를 elaborate하고 싶으면
dc_shell> elaborate leaf -library lib1
하면 되는 것입니다.
예)
dc_shell> read -format vhdl test.vhd
dc_shell> include constraints.scr
dc_shell> compile
dc_shell> write -f vhdl test -output test_netlist.vhd (netlist를 vhdl로 씁니다.)
dc_shell> list search_path
dc_shell> find(cell,libA/LD1)
dc_shell> list -libraries
또, 주의할 점이 있는데 target_library에는 worst case의 library만 지정해 주고, 별도로 set_min_library 명령을 사용하여 best case library를 지정해 준 후에 합성할 때는 set_operating_conditions -max slow -min fast와 같이 한꺼번에 지정해 주고 max와 min에 대해 동시에 constraint를 맞추도록 합성하는 것이 요즘(2004년 기준)의 방법입니다.
Constraints and Optimizing Designs
먼저 DC에는 두 가지 constraints가 있습니다. 하나는 design rule constraints 로 max fanout, max transition, max capacitance가 있는데 이것은 technology library 자체가 규정하는 값으로서 이것을 위반하면 회로자체가 제대로 동작할 수 없으므로 우선순위가 높습니다(따로 지정하지 않으면 technolyg 자체가 규정하는 제한이 적용됩니다.). 다른 하나는 optimization constraints로서 speed와 area를 optimize하는 것입니다.
1. Design Rule Constraints
Set_max_fanout : 어떤 output 이 drive할 수 있는 fanout을 제한하는 것으로 모든 cell 마다 fanout load값이 있습니다. 이 값들은 보통 integer로 표시되고 technology에서 결정하는 값입니다. 어떤 cell의 fanout_load를 알고 싶으면
dc_shell> get_attribute find(pin, "libA/AND2/i") fanout_load
와 같이 알아낼 수 있습니다.
set_max_transition : 어떤 net의 RC(time constant)에 의한 rise 혹은 fall time을 제한할 때 사용합니다.
set_max_capacitance : transition time과 독립적으로 capacitance를 제한하는데 사용되고 port나 design에 대해 적용할 수 있습니다.
예를 보면,
dc_shell> set_max_transition 5 test
dc_shell> set_max_fanout 5
dc_shell> set_max_capacitance 5
2. Optimization Constraints
DC는 area를 최적화하기 전에 speed를 먼저 최적화합니다.
Synchronous 회로에서는 그렇지만
전체의 area는 set_max_area로 잡아줍니다.(cell당 area가 technology에서 주어지지요?)
일반적으로는
create_clock : 클럭의 주파수를 정해주고
set_input_delay : 입력신호가 clock에 비해 얼마나 늦게 들어오는지를 알려 주기 위해
set_output_delay : 출력신호가 clock을 기준으로 외부에 얼마만큼의 지연이 추가로 있음을 알려 주기 위해(의미를 정확히 아셔야...클럭주기가 10ns일 때 output_delay를 3 ns로 하면 현재 block에서 clock edge기준으로 최소한 7ns 전에 신호가 나가야 한다는 뜻입니다.)
set_driving_cell : 입력신호에 대해 외부에서 어떤 cell이 drive하고 있는지 알려줌(얼마나 세게 driven되는 신호인지, 따라서 그 신호를 받아서 쓸 때 fan-out을 맞추기 위한 buffering을 어느 정도 해 줘야 하는지 알려줌)
set_load : 출력신호에 대해 외부에 얼마만큼의 load가 있는지 알려줌(따라서 얼마나 세게 drive해 줘야 하는지 알려줌)
asynchronous path 신호에 대해 지연값을 정해주기 위해서는 set_max_delay 와 set_min_delay 명령을 사용합니다. 이 max_delay와 min_delay는 또한 clock을 create_clock 을 사용하여 가상의 클럭을 기준으로 정해줄 수도 있습니다. set_fix_hold명령은 delay를 추가함으로써 hold time위반을 잡아줍니다.
max_area
optimization 도중에는 max_delay, min_delay, max_power, max_area에 대한 cost값이 최소가 되도록 회로가 여러번 수정되는 것입니다.(cost계산 방식은 생략)
3. timing report
합성을 할 때에는 보통 report_timing명령으로 timing위반을 알아봄으로써 합성이 성공적으로 이루어졌는지 알 수 있습니다. report_timing은 create_clock으로 만들어진 clock path에 대해서 worst path부터 보여줍니다. 입력~레지스터의 입력, 또는 레지스터의 출력으로부터 레지스터의 입력, 입력에서 출력까지, 레지스터의 출력에서 출력까지에 대해서 정해진 timing요구에 대해 가장 나쁜(요구를 만족시킬 수도 있고 그렇지 못할 수도 있습니다.) path부터 보여줍니다.
예)
dc_shell> read -f verilog test.v
dc_shell> link_library = target_library = lsi_10k.db
dc_shell> create_clock clk -period 5
dc_shell> compile
dc_shell> report_timing -max_paths 5
4. 많이 사용되는 design compiler 명령들
set_dont_touch : dont_touch는 design이나 cell에 붙는 attribute로서 일단 set되고 나면 더 이상 re-optimzed되지 않습니다. db파일로 save하더라도 이 성질은 살아있습니다. cell에 set_dont_touch하면 해당 design의 cell이 변하지 않는 것이고 design에 set_dont_touch하면 해당 referece는 어디에 instatiated되건 수정되지 않습니다. 아래와 같이 사용됩니다.
dc_shell> current_design = TOP
dc_shell> set_dont_touch u1 또는 set_dont_touch find(cell, u1)
또는
dc_shell> current_design = BlockB
dc_shell> set_dont_touch find(design,BlockA)
또 report_referece나 report_cell 명령으로 attribute를 확인할 수 있고 remove_attribute로 성질을 삭제할 수 있습니다.
dc_shell> set_flatten true (hierarchy를 flatten하는게 아님, combinational logic을 flatten하는 것임)
dc_shell> set_structure -timing true
dc_shell> report_compile_options
도 써보진 않았지만 필요할 것 같습니다.
Sub-block A가 instance a1로 있고 그 밑에 u2가 있다면
dc_shell> ungroup a1 하면 a1/u2 라는 새로운 instance가 보이게 되고
dc_shell> ungroup -flatten -all 하면 모두 풀어버립니다. (물론 dont_touch되어 있으면 제외)
반대로
dc_shell> group{u1 u2} -design_name new_block -cell_name Z1
도 있네요.(의미는 아시겠죠?)
dc_shell> set_dont_use libB/NAND2 (dont_use attribute를 추가, 보통 scan 을 위해서 scan type을 제외하고자 할 때 사용됩니다.)
5. characterize (중요!)
어떤 design sub1과 sub2가 각각 무사히 합성되어 top에 사용되었다고 합시다. 그런데 top에 두고 보니 이제 constraint를 위반하게 될 수 있습니다. (실제로 sub1과 sub2에 주어진 환경이 각각을 합성할 때 주었던 조건과 다르기 때문에) 이럴 경우에는 sub1과 sub2를 characterize한 후에 이 조건을 write script하여 다시 optimize할 수 있습니다. 다음의 예를 보면 알 수 있습니다.
(예)
dc_shell> read -f db top.db
dc_shell> characterize u1 /* 각각을 따로 characterize하는 게 좋겠죠? */
dc_shell> current_design = sub1
dc_shell> write_script > sub1.scr
dc_shell> compile
dc_shell> current_design top
dc_shell> characterize u2
dc_shell> current_design = sub2
dc_shell> write_script > sub2.scr
dc_shell> compile
(아마 script를 write한 후에 compile하기 전에 다시 include하지 않아도 되는 걸로 알고 있습니다. write script는 나중에 써먹기 위해서 합니다.)
요즘은 tool이 좋아져서 보통 10만 게이트까지는 한번에 합성해도 좋다고 합니다.(실제로 여러번 그렇게 해 봤고요. Sub-block부터 해당하는 중간블럭까지 한꺼번에 읽어 들인 후에 constraint를 주고 그냥 compile합니다.)
설계된 블럭의 성능에 대해 아무 생각이 없을 때 쓸 수 있는 방법으로..
아무 조건을 주지않고 한 번 compile하여 timing_report를 본 후에 적당한 조건을 주고 다시 compile한 후에
report_constraints -all_violators -verbose
를 사용하여 위반된 조건을 조사한 후에 다음과 같은 것을 해 볼 수 있습니다.
- 다른 구조나 다른 방식의 코딩
- 실현가능한 조건으로 수정
- false_path나 multi-cycle path 지정
- asynch신호에 대해 max_delay/min_delay 지정
- group_path 지정하여 weight를 주는 법(Cost 계산의 weight를 조정하여 특정 path에 대해 무시한다거나 위반이 많이 난 path를 집중적으로 optimize하게 한다거나 할 수 있겠죠?)
- incremental compile방법(아주 약간만 더 하면 될 때)
set_critical_range 는 예를 들어 worst만 optimize하는 게 아니라 worst에서 2.0 ns안으로 violated된 net까지 같이 optimize하고 싶을 때 쓸 수 있다고 합니다.(써보진 않았습니다만..)
또는 compile한 후 constraint를 약간 더 심하게 주고 다시 compile해도 됩니다.
Hierarchical design의 경우
dc_shell> current_design = top
dc_shell> characterize u1
dc_shell> current_design = s1
dc_shell> compile
dc_shell> current_design = top
dc_shell> characterize u2
dc_shell> current_design = s2
dc_shell> compile
와 같은 방법이 가장 많이 사용되는 방법이라고 합니다. (실제로 제가 담당한 ASIC 개발의 경우도 이런 compile-characterize-compile 방식도 쓰였고 bottom-up 방식, 즉 compile-set_dont_touch-read 방식도 쓰였었습니다. 경우에 따라 다른 것 같습니다.)
합성을 한 후 CTS(Clock Tree Synthesis) 을 포함한 P&R을 하기 전에는 pre-simulation을 하기 위해서는 clock driver나 global reset driver에 set_ideal_network이라고 해 주어야 SDF를 뽑을 때 driving strength를 무시하고 사용할 수 있는 SDF가 나옵니다.