Home > Community > Blogs > Custom IC Design > under construction skill for the skilled introduction to classes part 3
 
Login with a Cadence account.
Not a member yet?
Create a permanent login account to make interactions with Cadence more conveniennt.

Register | Membership benefits
Get email delivery of the Custom IC Design blog (individual posts).
 

Email

* Required Fields

Recipients email * (separate multiple addresses with commas)

Your name *

Your email *

Message *

Contact Us

* Required Fields
First Name *

Last Name *

Email *

Company / Institution *

Comments: *

SKILL for the Skilled: Introduction to Classes -- Part 3

Comments(0)Filed under: Custom IC Design, Virtuoso, SKILL, Team SKILL, programming, LISP, SKILL++, IC 6.1.5, object orientation, classes, Sudoku In the previous posting Introduction to Classes -- Part 2 we saw the high level function for initializing, solving, and displaying the sudoku puzzle.
(defun SkuSolve (partial_solution)
(let ((sudoku (SkuInitialize (SkuNew) partial_solution)))
(printf "starting with: \n%s\n"
(SkuPrint sudoku))
(printf "\nfound solution:\n%s\n"
(SkuPrint (SkuFindSolution sudoku)))))
In this posting we'll look at the definition of the SkuSudoku class as well as the definitions of the functions SkuNew, SkuInitialize, and SkuPrint. We leave the function SkuFindSolution until next posting.

In this posting, you'll see some examples of non-trivial class and instance manipulation which completely avoid the topic of methods. Although the SKILL++ object system provides a powerful method manipulation capability, you are not required to understand anything about methods to implement applications that operate on classes.

Non-trivial slot initialization

In the previous posting we saw how to use @initform to initialize instance slots to constant/default values. But the @initform can do more than provide constant defaults. The expression provided by @initform is actually SKILL++ code which runs every time an instance is created with makeInstance. The code in this SKILL++ expression is allowed to reference any global or local function or variable, such as functions defined within an labels as shown in the example below.

The SkuSudoku class defined below represents the sudoku board itself. It has a list of 81 cells, a list of 9 columns, a list of 9 rows, and a list of 9 3x3 blocks. Notice that the class SkuSudoku is defined inside a (labels ...) which defines a local function named repeat, and that local function is referenced inside the @initform expression.

In particular, in the class definition, the three slots (rows, columns, and b3x3s) each have a different @initform expression which each reference the local function repeat.

(labels ((repeat (n unary "xU")
;; call the given function N number of times,
;; collecting the return values.
(when (plusp n)
(cons (unary (sub1 n))
(repeat (sub1 n) unary)))))
(defclass SkuSudoku ()
((cells @initform nil)
(rows @initform (repeat 9 (lambda (n)
(makeInstance 'SkuRow ?index n))))
(columns @initform (repeat 9 (lambda (n)
(makeInstance 'SkuColumn ?index n))))
(b3x3s @initform (repeat 9 (lambda (n)
(makeInstance 'SkuB3x3 ?index n)))))))

A call to the SKILL primitive makeInstance such as an evaluation of the expression (makeInstance 'SkuSudoku) will create an instance of the class and will initialize the 4 slots by evaluating the 4 respective @initform expressions. Furthermore, the expressions will be evaluated such that the local function repeat is defined.

Encapsulation through lexical scoping

Note that the feature of encapsulation (the ability to limit the visibility of functions like repeat) is provided by SKILL++ lexical scoping. This is different from the C++/Java paradigm which forces you to use the object system to implement encapsulation. In SKILL++, encapsulation works with or without the object system, so all SKILL++ programs can take advantage of it, not just object-oriented programs.

Initialization using a factory function

There is a limit to how much initialization is possible from the @initform expressions. In particular the @initform expressions are not allowed to reference each other. Furthermore, you have no guarantee in which order the initialization expressions will evaluate. This lack of guarantee is especially important when define classes using single inheritance, and even more important in the case of multiple inheritance. All @initform expressions need to be mutually independent expressions which only depend on the environment of the defclass and not on each other.

Skill++ programs typically create instances of classes in one of two ways: either by a direct call to makeInstance or by a call to an intermediate function which calls makeInstance. Such an intermediate function is called a factory function. A benefit of a factory function is that the function may also preform any additional initialization as necessary for the correct behavior of the program.

There is still another advantage to using a factory function rather than a direct call to makeInstance. The function can initialize slots so that their initial values depend on each other. In the case of a properly initialized sudoku board, the cells, rows, columns, and 3x3 blocks all depend on each other in a particular way. There is a list of 81 cells cells slot, and each of these cell objects is in the correct row, column, and 3x3 block. The sudoku board cannot be initialized simply by the @initform expressions. The job of SkuNew is to assure that the cells, rows, columns, and 3x3 blocks reference each other properly.

A factory function typically does the following:

  1. Allocates an instance
  2. Initializes the slots of the instance, potentially according to the arguments of the factory function itself
  3. Returns the initialized instance.

Creating a structurally correct sudoku board

We want to define such a factory function named SkuNew which allocates, initializes, and returns an instance of the SkuSudoku class.

The function, SkuNew, does the actual allocation via a call to makeInstance as well as setting up the structure of the board independent of the actual content of the particular sudoku solution. In particular SkuNew assures that each cell can easily access its row, column, and 3x3 block and that each row, column, and 3x3 block can easily access its cells.

 

(defun SkuNew ()
;; allocate a blank-slate instance of SkuSudoku representing an empty
;; sudoku board
(let ((sudoku (makeInstance 'SkuSudoku)))
(let ((index 0))
(foreach row sudoku->rows
(foreach col sudoku->columns
(let ((cell (makeInstance 'SkuCell ?index index++))
(b3x3 (nth (xplus (xtimes 3
(xquotient row->index 3))
(xquotient col->index 3))
sudoku->b3x3s)))
;; add the cell to the list of sudoku cells.
sudoku->cells = (cons cell sudoku->cells)

;; tell the cell which row, column
;; and 3x3 block it belongs to.
cell->row = row
cell->column = col
cell->b3x3 = b3x3

;; tell the row, col, and 3x3 block
;; that this cell belongs to it.
row->cells = (cons cell row->cells)
col->cells = (cons cell col->cells)
b3x3->cells = (cons cell b3x3->cells)))
;; return the new instance.
sudoku))

Feeding in the partial solution

Finally, given an initialized and consistent object which represents the sudoku board, it is necessary to feed in a given partial solution in preparation for running the solution algorithm, SkuFindSolution. The function SkuInitialize iterates through the rows and columns of a given SkuSudoku instance, filling some of the cells with a number from 0 to 9 as per the given partial solution.

(defun SkuInitialize (sudoku partial_solution)
;; feed in the partial solution
(foreach (sudoku_row solution_row) sudoku->rows partial_solution
(foreach (cell solution) sudoku_row->cells solution_row
(when (numberp solution)
cell->value = solution)))
sudoku)

The sudoku instance initialization protocol

The two functions together form the sudoku initialization protocol:

SkuNew
Create a newly allocated, structurally correct sudoku board, complete with cross-references which allow for effeciently search.
SkuInitialize
Feed a given partial solution into a given structurally correct sudoku board.
Now you may experiment with the functions, SkuInitialize, and SkuNew by evaluating an expression such as the following in the CIWindow.
(SkuInitialize (SkuNew)
'((5 3 ? ? 7 ? ? ? ?)
(6 ? ? 1 9 5 ? ? ?)
(? 9 8 ? ? ? ? 6 ?)

(8 ? ? ? 6 ? ? ? 3)
(4 ? ? 8 ? 3 ? ? 1)
(7 ? ? ? 2 ? ? ? 6)

(? 6 ? ? ? ? 2 8 ?)
(? ? ? 4 1 9 ? ? 5)
(? ? ? ? 8 ? ? 7 9)))

==>
stdobj@0x1d454234

The printed object such as stdobj@0x1d454234 is how an instance of a SKILL++ class is printed.

Displaying an instance

The default way an instance of a SKILL++ class prints is not very informative. We need to write a special purpose function, SkuPrint to display the partial (or full) state of the sudoku board.

The following function SkuPrint generates a string representing the ASCII representation of the sudoku board. The string contains \n characters so you'll have to use (printf "%s" ...) or a similar function to print it so it is human readable.

 

(defun SkuPrint (sudoku)
(let ((divider "+-----------------+\n"))
(strcat divider
(buildString (foreach mapcar row sudoku->rows
(strcat "|"
(buildString (foreach mapcar cell row->cells
(if cell->value
(sprintf nil "%d" cell->value)
" "))
;; separate the cells with |
"|")
"|"))
;; separate the lines with \n
"\n")
"\n"
divider)))
You may now print the object with a call to printf as follows.
(printf "%s\n"
(SkuPrint (SkuInitialize (SkuNew)
'((5 3 ? ? 7 ? ? ? ?)
(6 ? ? 1 9 5 ? ? ?)
(? 9 8 ? ? ? ? 6 ?)

(8 ? ? ? 6 ? ? ? 3)
(4 ? ? 8 ? 3 ? ? 1)
(7 ? ? ? 2 ? ? ? 6)

(? 6 ? ? ? ? 2 8 ?)
(? ? ? 4 1 9 ? ? 5)
(? ? ? ? 8 ? ? 7 9)))))
==>
+-----+-----+-----+
|5|3| | |7| | | | |
|6| | |1|9|5| | | |
| |9|8| | | | |6| |
|8| | | |6| | | |3|
|4| | |8| |3| | |1|
|7| | | |2| | | |6|
| |6| | | | |2|8| |
| | | |4|1|9| | |5|
| | | | |8| | |7|9|
+-----+-----+-----+

 

Enhancements in IC615

The approach and the actual code shown here works in versions of SKILL++ using IC5033, IC5141, and IC61 up to and including IC615. However, there are some new features in IC615 which make initialization and presentation of SKILL++ objects easier and more flexible. To completely understand these and other new features, you'll need to understand something about generic functions and methods, which SKILL for the Skilled has not addressed yet, but here is a little taste.

  • There is a new generic function defined called printself. Virtuoso calls printself on SKILL++ instances when printing them into the CIWindow, stacktraces, or as a result of (printf ... "%L"). You may provide a method of printself specializing on your class which provides a string for the SKILL++ implementation to use as the printed representation of your object.
  • In IC615 makeInstance calls the generic function initializeInstance on a SKILL++ instance before returning it. You are allowed to provide what is called an @after method on initializeInstance specializing on your SKILL++ class to initialize it. This means that any application which makes an instance of your class will automatically call your initialization code without having to know the name of your factory function.
  • In IC615 defclass allows you to define classes which inherit from multiple potentially independent or interrelated superclasses called mix-in classes. In this case the various methods on initializeInstance are even more important as each such method is able to initialize the slots of an instance it is responsible for.

Review

In this posting you have seen some examples of non-trivial SKILL++ class allocation and initialization using both @initform and by a factory function. You've also seen a way to present SKILL++ objects in human readable form depending on the semantics of your particular class.

Preview

In the next posting of SKILL for the Skilled we'll finally look at how to implement the function SkuFindSolution which is the last function needed to complete the implementation of SkuSolve function.

Jim Newton

Comments(0)

Leave a Comment


Name
E-mail (will not be published)
Comment
 I have read and agree to the Terms of use and Community Guidelines.
Community Guidelines
The Cadence Design Communities support Cadence users and technologists interacting to exchange ideas, news, technical information, and best practices to solve problems and get the most from Cadence technology. The community is open to everyone, and to provide the most value, we require participants to follow our Community Guidelines that facilitate a quality exchange of ideas and information. By accessing, contributing, using or downloading any materials from the site, you agree to be bound by the full Community Guidelines.