Last Updated on July 2, 2017
This is a sequel to my old article about solving Sudoku. Last time, someone asked a question about using the “unique
” constraint to solve Sudoku. The “unique
” constraint, which was added to IEEE Std 1800™-2012, constrains a group of variables such that no two members of the group have the same value after randomization. This sounded promising, so I gave it a try. This method requires more steps than the original “sudoku
” class. In this article, I’m going to replace the three constraints (row_con
, column_con
, and block_con
) in the previous article with the constraints below.
Row constraint
Constraining boxes in the same row is pretty straightforward using the “unique
” constraint. This diagram shows the indexes of a two-dimensional array (box[N][N]
), where N=9
.
Line 5 makes the column values in each row unique.
1 2 3 4 5 6 7 | rand int unsigned box[N][N]; constraint row_con { foreach ( box[row,] ) { // for each row, unique { box[row] }; // column values must be unique } } |
Column constraints
Constraining boxes in the same column requires a little more work. This is because we cannot do the following with unique
:
foreach ( box[,col] ) { unique { box[][col] }; } // this doesn't work |
Instead, we will use a helper array (we call it “column
“) that transposes the row into a column like this diagram.
After transposing the array, we use “unique
” like we did before (line 11).
1 2 3 4 5 6 7 8 9 10 11 12 13 | local rand int unsigned column[N][N]; // helper array constraint row_to_column { // set the values of helper array foreach ( column[col,row] ) { column[col][row] == box[row][col]; // transpose } } constraint column_con { foreach ( column[col,] ) { // for each column, unique { column[col] }; // row values must be unique } } |
Block constraints
Similar to the column constraints, we will use another helper array (called “block
“) to map the row into a block like this diagram.
The mapping constraint is a little complicated (line 5). After mapping the array, we use “unique
” to make the values in the block unique. Note that the “loc
” loop variable indicates the location of the box within the block.
1 2 3 4 5 6 7 8 9 10 11 12 13 | local rand int unsigned block[N][N]; // helper array constraint row_to_block { // set the values of helper array foreach ( block[blk,loc] ) { block[blk][loc] == box[(blk/M)*M+loc/M][(blk%M)*M+loc%M]; // mapping } } constraint block_con { foreach ( block[blk,] ) { // for each block, unique { block[blk] }; // values in the block must be unique } } |
Sudoku class
Here is the full list of the new Sudoku class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | class sudoku#( int M = 3 ); // M >= 1 localparam N = M * M; local int unsigned puzzle[N][N]; rand int unsigned box [N][N]; // helper arrays local rand int unsigned column[N][N]; local rand int unsigned block [N][N]; // The value of each box must be between 1 and N. constraint box_con { foreach ( box[row, col] ) { box[row][col] inside { [ 1 : N ] }; } } // The boxes on the same row must have unique values. constraint row_con { foreach ( box[row,] ) { // for each row, unique { box[row] }; // column values must be unique } } // The boxes on the same column must have unique values. constraint row_to_column { // set the values of helper array foreach ( column[col,row] ) { column[col][row] == box[row][col]; // transpose } } constraint column_con { foreach ( column[col,] ) { // for each column, unique { column[col] }; // row values must be unique } } // The boxes in the same MxM block must have unique values. constraint row_to_block { // set the values of helper array foreach ( block[blk,loc] ) { block[blk][loc] == box[(blk/M)*M+loc/M][(blk%M)*M+loc%M]; // mapping } } constraint block_con { foreach ( block[blk,] ) { // for each block, unique { block[blk] }; // values in the block must be unique } } // The box must have the same value as the puzzle's if specified (!=0). constraint puzzle_con { foreach ( puzzle[row, col] ) { if ( puzzle[row][col] != 0 ) { box[row][col] == puzzle[row][col]; } } } // Sudoku solver function int solve_this( int unsigned puzzle[N][N] ); this.puzzle = puzzle; return this.randomize(); endfunction: solve_this // Print the solution. function void print(); for ( int i = 0; i < N; i++ ) begin if ( i % M == 0 ) $write( "\n" ); for ( int j = 0; j < N; j++ ) begin if ( j % M == 0 ) $write( " " ); $write( "%3d", box[i][j] ); end $write( "\n" ); end endfunction: print endclass: sudoku |
Compared to the original “sudoku
” class in the previous article, the new constraints are not as simple as expected. As stated in the introduction, I still prefer the original constraints because it does not need the helper arrays, but either method should work.
You can view and run the code on EDA Playground as usual.
Beautifully written! Diagrams were very helpful.
Question on the sudoku diagrams: Did you do them on excel or used some py + matplotlib code to generate them?
I draw them by hand using draw.io 🙂