Creating Forms in SKILL Code: A Practical Guide

Contents:

  1. Layout Forms
  2. GUI Template
  3. Map Callback
  4. Modify Callback
  5. Button Callback - Browse
  6. Button Callback - Run
  7. Conclusion
  8. Full code

Why should we create graphical user interfaces (GUIs) for our tools?

GUIs offer a user-friendly and intuitive way to interact with software, making complex tasks more accessible and efficient. They provide visual feedback and structured workflows, reducing errors and enhancing productivity, allowing users to focus on their work rather than memorizing commands. GUIs also simplify training, making it easier for new users to explore features without extensive documentation.

SKILL code offers powerful ways to develop GUIs (or forms) within the Virtuoso environment. Cadence provides comprehensive documentation for all their hi* functions, which handle form development, along with examples of various features:

In this guide, we'll walk through the GUI development process using a practical example. We'll use layout forms and define several callbacks, with the full code attached at the end.

1. Layout Forms

There are two main approaches for field placement in a GUI: using pixels (hiCreateAppForm()) and using layout forms (hiCreateLayoutForm()). Placing fields using pixels gives you freedom in choosing exact x, y coordinates and a size for your fields. Despite the fact that it’s intuitive, the major problem with this approach is it’s not scalable. The more complex the GUI is, the harder it’s to add a new field to it. Imagine you have to add a new field to a complex GUI such this:


Complex GUI example

Complex GUI example

In the by-pixel approach, in addition to a new field, you’ll have to change the location of other fields manually. Which will end up in long and tedious work. A more modern and scalable approach is using layout forms. At first, it’s not as intuitive as defining locations by pixels, but after you get hold of it, it turns into a powerful skill in GUI development. Layout forms allow you to build GUIs with container layouts (like Lego). When you have to add a new field, you just add it to the required layout and other fields will move automatically.

So for scalable and robust GUIs it is recommended to use layout forms approach.

Now, let’s get to our practical example.

2. GUI Template

Start by building a GUI template. Our GUI will receive the top cell's library, cell, and view names as input and display library and cell names used in the top cell's hierarchy.

1procedure( createLibrariesCellsInUseForm() 2 let( (libraryLabel libraryStringField cellLabel cellStringField viewLabel 3 viewStringField topGridLayout browseButton horizontalLayout 4 infoReportField getInfoButton verticalLayout) 5 6 libraryLabel = hiCreateLabel( 7 ?name 'libraryLabel 8 ?labelText "Library" 9 ?justification 'right 10 ) 11 12 libraryStringField = hiCreateStringField( 13 ?name 'libraryStringField 14 ) 15 16 cellLabel = hiCreateLabel( 17 ?name 'cellLabel 18 ?labelText "Cell" 19 ?justification 'right 20 ) 21 22 cellStringField = hiCreateStringField( 23 ?name 'cellStringField 24 ) 25 26 viewLabel = hiCreateLabel( 27 ?name 'viewLabel 28 ?labelText "View" 29 ?justification 'right 30 ) 31 32 viewStringField = hiCreateStringField( 33 ?name 'viewStringField 34 ) 35 36 topGridLayout = hiCreateGridLayout( 37 'topGridLayout 38 ?spacing 10 39 ?items list( 40 list(libraryLabel 'row 0 'col 0) 41 list(libraryStringField 'row 0 'col 1) 42 list(cellLabel 'row 1 'col 0) 43 list(cellStringField 'row 1 'col 1) 44 list(viewLabel 'row 2 'col 0) 45 list(viewStringField 'row 2 'col 1) 46 list('col_stretch 0 0) 47 list('col_stretch 1 1) 48 ) 49 ) 50 51 browseButton = hiCreateButton( 52 ?name 'browseButton 53 ?buttonText "Browse" 54 ?callback "" 55 ) 56 57 horizontalLayout = hiCreateHorizontalBoxLayout( 58 'horizontalLayout 59 ?frame "Top Cell Info" 60 ?spacing 10 61 ?items list( 62 list(topGridLayout 'stretch 1) 63 list(browseButton 'stretch 0) 64 ) 65 ) 66 67 infoReportField = hiCreateReportField( 68 ?name 'infoReportField 69 ?title "Hierarchy Info" 70 ?headers list( 71 list("Library" 100 'left 'string t) 72 list("Cell" 100 'left 'string t) 73 ) 74 ) 75 76 getInfoButton = hiCreateButton( 77 ?name 'getInfoButton 78 ?buttonText "Get Hierarchy Info" 79 ?callback "" 80 ) 81 82 verticalLayout = hiCreateVerticalBoxLayout( 83 'verticalLayout 84 ?items list(horizontalLayout infoReportField getInfoButton) 85 ) 86 87 hiCreateLayoutForm( 88 'librariesCellsInUseForm 89 "Libraries Cells in Use" 90 verticalLayout 91 ?buttonLayout 'Empty 92 ) 93 );let 94);procedure

Here, we define labels, string fields, buttons and a report field, and use form layouts to organize the fields into sections. To display the GUI in SKILL, create the form and display it with these two commands:

1form = createLibrariesCellsInUseForm() 2hiDisplayForm(form)


Display GUI in SKILL

Display GUI in SKILL

Tip: If you change anything in the form’s creation function, you have to re-create the form to see the changes. Re-run the two functions above.

3. Map Callback

Map callbacks are used to perform additional setup or customization when a form is instantiated and displayed. They are particularly useful for initializing form fields, setting default values, adjusting field sizes, or performing any other setup tasks that need to occur when the form is first displayed.

Define a map callback for our GUI to change the size of "Browse" and "Get Hierarchy Info" buttons and initialize top cell info fields if a cell view is currently open.

1procedure( librariesCellsInUseFormMapCB(form) 2 ;Map callback function for initializing the Libraries Cells In Use 3 ;form fields. 4 5 ;@param form formObject 6 ;The form object being instantiated. 7 8 let( (cv) 9 10 hiInstantiateForm(form) 11 hiSetFieldMinSize(form 'browseButton ?widgetHeight 25) 12 hiSetFieldMinSize(form 'getInfoButton ?widgetHeight 35) 13 14 cv = geGetEditCellView() 15 when( cv 16 form~>libraryStringField~>value = cv~>libName 17 form~>cellStringField~>value = cv~>cellName 18 form~>viewStringField~>value = cv~>viewName 19 );when 20 );let 21);procedure

Here, we must instantiate the form before modifying its fields’ sizes. Additionally, we assign values to our string fields. Set the function as a map callback by adding the ?mapCB keyword to the hiCreateLayoutForm() function.

1hiCreateLayoutForm( 2 'librariesCellsInUseForm 3 "Libraries Cells in Use" 4 verticalLayout 5 ?buttonLayout 'Empty 6 ?mapCB 'librariesCellsInUseFormMapCB 7 )

Note: You can define field callbacks either as a symbol, like 'myFunc, or as a string, such as "myFunc()". Using a symbol automatically passes default arguments to the function based on the field, with each field's callback having its own set of default arguments. In case of the ?mapCB, it sends the current form object as an argument. Setting callbacks as strings is useful when you need to pass different arguments to a function.


Callback example

Callback example

4. Modify Callback

A modify callback in SKILL is a function that is triggered whenever the user changes the value of a string field, such as by typing a new value. This callback is executed immediately when a change is detected in the field, but before the change is displayed.

The modify callback function can return one of three values:

  • t: If the function returns t, the changes made to the field are allowed and displayed as they are entered.
  • nil: If the function returns nil, the changes are not allowed, and the original value of the field is retained.
  • value: If the function returns a string, this value replaces the current value of the field.

This mechanism provides a powerful way to validate input, update other fields, or trigger additional logic based on the new value, enabling dynamic and responsive interactions within SKILL forms.

Add a modify callback to the library string field to validate the entered name and highlight the field if incorrect.

1procedure( libraryNameModifyCB(field scope latestTextValue sourceOfChange) 2 ;Modify callback function for validating and highlighting the library 3 ;name field. 4 ; 5 ;@param field formField 6 ; The form field being modified. 7 ;@param scope formObject 8 ; The form object contains the field. 9 ;@param latestTextValue string 10 ; The latest text value entered in the field. 11 ;@param sourceOfChange any 12 ; The source of the change, indicating whether the modification 13 ; was user-initiated. 14 ;@return t boolean 15 ; Returns t to allow the changes made to the field. 16 17 let( (libraryObject) 18 when( sourceOfChange 19 libraryObject = ddGetObj(latestTextValue) 20 if( !libraryObject 21 then 22 hiHighlightField(scope field~>hiFieldSym 'error) 23 else 24 hiHighlightField(scope field~>hiFieldSym 'background) 25 );if 26 );when 27 28 t 29 );let 30);procedure

Here, we attempt to retrieve a library object using its name and adjust the string field's highlight based on the result. Set the function as a modify callback by adding the ?modifyCallback keyword to the library's hiCreateStringField() function.

1libraryStringField = hiCreateStringField( 2 ?name 'libraryStringField 3 ?modifyCallback 'libraryNameModifyCB 4 )


Note: To practice, you can add modify callbacks to the cell and view string fields.

5. Button Callback - Browse

Instead of manually entering library/cell/view names, we can define a callback to invoke the Library Manager for selection.

1procedure( browseLibraryCellViewCB(form) 2 ;Callback function for synchronizing library, cell, and view fields with 3 ;the form. 4 5 ;@param form formObject 6 ;The form object containing the fields to be synchronized. 7 ddsSyncWithForm(form 'browse 'libraryStringField 'cellStringField 8 'viewStringField) 9);procedure

Here, we use the built-in ddsSyncWithForm() function, which requires a form, an action to perform, and fields’ symbols to update when selection is done.

Add this function to the browse button as a callback. Use the string approach this time, as we want to send only the form as an argument.

1browseButton = hiCreateButton( 2 ?name 'browseButton 3 ?buttonText "Browse" 4 ?callback "browseLibraryCellViewCB(hiGetCurrentForm())" 5 )


6. Button Callback - Run

The run button executes the main algorithm. Here, we’ll use the getLibrariesCellsUsedIn() function, described in the “Extracting Library and Cell Names from a Top Cell Hierarchy Using SKILL Code” guide.

Define a function to run the algorithm, get results, and display them in a table.

1procedure( getUsedLibrariesCellsCB(form) 2 ;Callback function to extract and display used libraries and cells from a 3 ;specified cell view. 4 ; 5 ;@param form formObject 6 ;The form object containing the input fields and report field. 7 prog( (libName cellName viewName cellView usedLibrariesCellsTable choices) 8 9 ; Get input 10 libName = form~>libraryStringField~>value 11 cellName = form~>cellStringField~>value 12 viewName = form~>viewStringField~>value 13 14 ; Check input 15 unless( checkInput(libName cellName viewName) 16 return() 17 );unless 18 19 ; Pre-process input 20 cellView = dbOpenCellViewByType(libName cellName viewName) 21 22 ; Run the libraries and cells extraction function 23 usedLibrariesCellsTable = getLibrariesCellsUsedIn(cellView) 24 25 ; Post-process output 26 foreach( library usedLibrariesCellsTable 27 foreach( cell usedLibrariesCellsTable[library] 28 choices = cons(list(library cell) choices) 29 );foreach 30 );foreach 31 32 ; Show output in a table 33 form~>infoReportField~>choices = choices 34 35 return(t) 36 );prog 37);procedure

Here, first of all, we check the input, and if it’s incorrect we exit the function in an early stage by return(). To use the return() function to exit your function at a desired stage, you’ll have to wrap your code in the prog() scope, instead of the let(). Next, we get the cell view object, using the provided library/cell/view names.

After this, we use the cell view object to get libraries and cells names that are in use in this cell view’s hierarchy. When we get the result, we need to process it to be able to add to the report field, our results table. And finally, we add the processed results to the report field.

Add this function to the run button as a callback.

1getInfoButton = hiCreateButton( 2 ?name 'getInfoButton 3 ?buttonText "Get Hierarchy Info" 4 ?callback "getUsedLibrariesCellsCB(hiGetCurrentForm())" 5 )

Now, when you provide correct library/cell/view names and click the run button, you'll see the extracted results in the report field.


7. Conclusion

In this guide, we've explored how to use layout forms, define various callbacks, highlight string fields, display error messages, invoke the Library Manager, and utilize let() and prog() scopes. To further explore, check out Cadence guides on creating forms and experiment with different fields and parameters.

8. Full code

1procedure( createLibrariesCellsInUseForm() 2 3 ;Creates a form for displaying and interacting with library, cell, and 4 ;view information. 5 6 ;@return formObject 7 ; The created form object for libraries and cells in use. 8 9 let( (libraryLabel libraryStringField cellLabel cellStringField viewLabel 10 viewStringField topGridLayout browseButton horizontalLayout 11 infoReportField getInfoButton verticalLayout) 12 13 libraryLabel = hiCreateLabel( 14 ?name 'libraryLabel 15 ?labelText "Library" 16 ?justification 'right 17 ) 18 19 libraryStringField = hiCreateStringField( 20 ?name 'libraryStringField 21 ?modifyCallback 'libraryNameModifyCB 22 ) 23 24 cellLabel = hiCreateLabel( 25 ?name 'cellLabel 26 ?labelText "Cell" 27 ?justification 'right 28 ) 29 30 cellStringField = hiCreateStringField( 31 ?name 'cellStringField 32 ) 33 34 viewLabel = hiCreateLabel( 35 ?name 'viewLabel 36 ?labelText "View" 37 ?justification 'right 38 ) 39 40 viewStringField = hiCreateStringField( 41 ?name 'viewStringField 42 ) 43 44 topGridLayout = hiCreateGridLayout( 45 'topGridLayout 46 ?spacing 10 47 ?items list( 48 list(libraryLabel 'row 0 'col 0) 49 list(libraryStringField 'row 0 'col 1) 50 list(cellLabel 'row 1 'col 0) 51 list(cellStringField 'row 1 'col 1) 52 list(viewLabel 'row 2 'col 0) 53 list(viewStringField 'row 2 'col 1) 54 list('col_stretch 0 0) 55 list('col_stretch 1 1) 56 ) 57 ) 58 59 browseButton = hiCreateButton( 60 ?name 'browseButton 61 ?buttonText "Browse" 62 ?callback "browseLibraryCellViewCB(hiGetCurrentForm())" 63 ) 64 65 horizontalLayout = hiCreateHorizontalBoxLayout( 66 'horizontalLayout 67 ?frame "Top Cell Info" 68 ?spacing 10 69 ?items list( 70 list(topGridLayout 'stretch 1) 71 list(browseButton 'stretch 0) 72 ) 73 ) 74 75 infoReportField = hiCreateReportField( 76 ?name 'infoReportField 77 ?title "Hierarchy Info" 78 ?headers list( 79 list("Library" 100 'left 'string t) 80 list("Cell" 100 'left 'string t) 81 ) 82 ) 83 84 getInfoButton = hiCreateButton( 85 ?name 'getInfoButton 86 ?buttonText "Get Hierarchy Info" 87 ?callback "getUsedLibrariesCellsCB(hiGetCurrentForm())" 88 ) 89 90 verticalLayout = hiCreateVerticalBoxLayout( 91 'verticalLayout 92 ?items list(horizontalLayout infoReportField getInfoButton) 93 ) 94 95 hiCreateLayoutForm( 96 'librariesCellsInUseForm 97 "Libraries Cells in Use" 98 verticalLayout 99 ?buttonLayout 'Empty 100 ?mapCB 'librariesCellsInUseFormMapCB 101 ) 102 );let 103);procedure 104 105 106procedure( librariesCellsInUseFormMapCB(form) 107 108 ;Map callback function for initializing the Libraries Cells In Use 109 ;form fields. 110 111 ;@param form formObject 112 ; The form object being instantiated. 113 114 let( (cellView) 115 116 hiInstantiateForm(form) 117 hiSetFieldMinSize(form 'browseButton ?widgetHeight 25) 118 hiSetFieldMinSize(form 'getInfoButton ?widgetHeight 35) 119 120 cellView = geGetEditCellView() 121 when( cellView 122 form~>libraryStringField~>value = cellView~>libName 123 form~>cellStringField~>value = cellView~>cellName 124 form~>viewStringField~>value = cellView~>viewName 125 );when 126 );let 127);procedure 128 129 130procedure( libraryNameModifyCB(field scope latestTextValue sourceOfChange) 131 132 ;Modify callback function for validating and highlighting the library 133 ;name field. 134 135 ;@param field formField 136 ; The form field being modified. 137 ;@param scope formObject 138 ; The form object containing the field. 139 ;@param latestTextValue string 140 ; The latest text value entered in the field. 141 ;@param sourceOfChange any 142 ; The source of the change, indicating whether the modification 143 ; was user-initiated. 144 ;@return t boolean 145 ; Returns t to allow the changes made to the field. 146 147 let( (libraryObject) 148 when( sourceOfChange 149 libraryObject = ddGetObj(latestTextValue) 150 if( !libraryObject 151 then 152 hiHighlightField(scope field~>hiFieldSym 'error) 153 else 154 hiHighlightField(scope field~>hiFieldSym 'background) 155 );if 156 );when 157 158 t 159 );let 160);procedure 161 162 163procedure( browseLibraryCellViewCB(form) 164 165 ;Callback function for synchronizing library, cell, and view fields with 166 ;the form. 167 168 ;@param form formObject 169 ; The form object containing the fields to be synchronized. 170 171 ddsSyncWithForm(form 'browse 'libraryStringField 'cellStringField 172 'viewStringField) 173);procedure 174 175 176procedure( getUsedLibrariesCellsCB(form) 177 178 ;Callback function to extract and display used libraries and cells from a 179 ;specified cell view. 180 181 ;@param form formObject 182 ; The form object containing the input fields and report field. 183 184 prog( (libName cellName viewName cellView usedLibrariesCellsTable choices) 185 186 ; Get input 187 libName = form~>libraryStringField~>value 188 cellName = form~>cellStringField~>value 189 viewName = form~>viewStringField~>value 190 191 ; Check input 192 unless( checkInput(libName cellName viewName) 193 return() 194 );unless 195 196 ; Pre-process input 197 cellView = dbOpenCellViewByType(libName cellName viewName) 198 199 ; Run the libraries and cells extraction function 200 usedLibrariesCellsTable = getLibrariesCellsUsedIn(cellView) 201 202 ; Post-process output 203 foreach( library usedLibrariesCellsTable 204 foreach( cell usedLibrariesCellsTable[library] 205 choices = cons(list(library cell) choices) 206 );foreach 207 );foreach 208 209 ; Show output in a table 210 form~>infoReportField~>choices = choices 211 212 return(t) 213 );prog 214);procedure 215 216 217procedure( checkInput(libName cellName viewName) 218 219 ;Validates the existence of a specified library, cell, and view 220 ;combination. 221 222 ;@param libName string 223 ; The name of the library to check. 224 ;@param cellName string 225 ; The name of the cell to check. 226 ;@param viewName string 227 ; The name of the view to check. 228 ;@return boolean 229 ; Returns t if the library, cell, and view exist; otherwise, 230 ; displays an error dialog and returns nil. 231 232 prog( (viewObject) 233 234 viewObject = ddGetObj(libName cellName viewName) 235 unless( viewObject 236 hiDisplayAppDBox( 237 ?name 'errorAppDBox 238 ?dboxBanner "*ERROR* Libraries Cells in Use" 239 ?dboxText "Selected library, cell or view don't exist!" 240 ?dialogType hicErrorDialog 241 ?buttonLayout 'Close 242 ) 243 244 return() 245 );unless 246 247 return(t) 248 );prog 249);procedure 250 251 252procedure( getLibrariesCellsUsedIn(cellView 253 @optional (usedLibrariesCellsTable nil)) 254 255 ;Retrieves all libraries and cells used in the hierarchy of a given 256 ;cell view. 257 258 ;@param cellView dbObject 259 ;The cell view object from which to retrieve the hierarchy. 260 261 ;@param usedLibrariesCellsTable table 262 ;Optional. A table to keep track of used libraries and cells. 263 ;If not provided, a new table is created. 264 265 ;@return table 266 ;A table containing libraries as keys and tables of cell names as 267 ;values, representing the hierarchy. 268 269 let( (cellTable libName cellName cellObject message viewName nextCellView) 270 271 ; First initialization 272 unless( usedLibrariesCellsTable 273 usedLibrariesCellsTable = makeTable('usedLibrariesCellsTable nil) 274 cellTable = makeTable('cellTable nil) 275 cellTable[cellView~>cellName] = t 276 usedLibrariesCellsTable[cellView~>libName] = cellTable 277 );unless 278 279 foreach( instance cellView~>instHeaders 280 libName = instance~>libName 281 cellName = instance~>cellName 282 cellObject = ddGetObj(libName cellName) 283 284 if( !cellObject 285 then 286 message = strcat("[getLibrariesCellsUsedIn] " libName "/" 287 cellName " cell doesn't exist in your Library Manager") 288 warn(message) 289 else 290 ; Creates cells' table for a library 291 unless( usedLibrariesCellsTable[libName] 292 cellTable = makeTable('cellTable nil) 293 usedLibrariesCellsTable[libName] = cellTable 294 );unless 295 296 unless( usedLibrariesCellsTable[libName][cellName] 297 ; This cell is not in table yet 298 usedLibrariesCellsTable[libName][cellName] = t 299 300 ; Gets instance's cell view 301 viewName = mapViewName(instance~>viewName) 302 nextCellView = dbOpenCellViewByType(libName cellName viewName) 303 when( nextCellView 304 usedLibrariesCellsTable = getLibrariesCellsUsedIn(nextCellView 305 usedLibrariesCellsTable) 306 );when 307 );unless 308 );if 309 );foreach 310 311 usedLibrariesCellsTable 312 );let 313);procedure 314 315 316procedure( mapViewName(viewName) 317 ; Maps a view name to a common view name that includes 'instances' 318 ;for hierarchy traversal. 319 320 ;@param viewName string 321 ;The name of the view to be mapped. 322 323 ;@return string 324 ;The mapped view name. 325 326 327 if( viewName == "symbol" ; Symbol's cell view doesn't have ~>instances 328 then 329 "schematic" 330 else 331 viewName 332 );if 333);procedure

Author: Eugeny Khanchin