-
Notifications
You must be signed in to change notification settings - Fork 0
/
SearchCore.bs
376 lines (329 loc) · 15.8 KB
/
SearchCore.bs
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
package SearchCore where
import ChessState
import MoveEval
import MoveUpdate
import FIFO
import FIFOF
import GetPut
import ClientServer
import Connectable
import qualified List
import Vector
import CShow
type Score = Int 11
type Heuristic c = c -> State -> Score
type RequestId = UInt 8
type Depth = UInt 8
type MoveCount = UInt 8
minScore :: Score
minScore = minBound + 1 -- We want -minScore = maxScore
maxScore :: Score
maxScore = maxBound
moveQueueSize :: Integer
moveQueueSize = 60
data Outcome = NoOutcome | Check | CheckMate | Draw
deriving (Bits)
struct SearchQuery config =
rid :: RequestId
state :: State
depth :: Depth
getMoves :: Bool
alpha :: Maybe Score
beta :: Maybe Score
config :: config
deriving (Bits)
instance (DefaultValue config) => DefaultValue (SearchQuery config) where
defaultValue =
interface SearchQuery
rid = 0
state = initialState
depth = 5
getMoves = False
alpha = Nothing
beta = Nothing
config = defaultValue
struct SearchResult =
rid :: RequestId
outcome :: Outcome
bestMove :: Maybe Move
forcedOutcome :: Bool -- Can either player force a win
score :: Score
depth :: Depth
deriving (Bits)
interface Frame =
putState :: State -> Bool -> Score -> Score -> Action
putScore :: Score -> Bool -> ActionValue Bool
state :: State
hasMove :: Bool
forcedOutcome :: Bool
score :: Score
alpha :: Score
beta :: Score
moves :: FIFOF Move
mkFrame :: Module Frame
mkFrame = module
state <- mkReg _
inCheck <- mkReg False
hasMove <- mkReg False
forcedOutcome <- mkReg True
bestScore <- mkReg 0
alpha <- mkReg minScore
beta <- mkReg maxScore
moves <- mkSizedFIFOF moveQueueSize
interface
putState s c a b =
do state := s
inCheck := c
hasMove := False
forcedOutcome := True
alpha := a
beta := b
putScore s f =
do hasMove := True
let newScore = if f then (if s > 0 then s - 1 else s + 1) else s
isBestScore = newScore > bestScore || not hasMove
if isBestScore
then do bestScore := newScore
forcedOutcome := f
else noAction
if newScore > alpha then alpha := newScore else noAction
return isBestScore
state = state
hasMove = hasMove
forcedOutcome = forcedOutcome
score =
if hasMove then bestScore
else if inCheck then minScore -- Checkmate
else 0 -- Draw
alpha = alpha
beta = beta
moves = moves
interface (SearchCore :: * -> # -> *) config stackSize =
server :: Server (SearchQuery config) SearchResult
moves :: GetS MoveResponse
clear :: Action
status :: Bit 16 {-# always_ready, always_enabled #-}
mkSearchCore ::
(Add dispatchDepth 1 maxDepth, Bits config cb, CShow config) =>
Heuristic config -> Vector numDispatch (SearchCore config dispatchDepth) ->
Module (SearchCore config maxDepth)
mkSearchCore heuristic dispatchCores = module
queries :: FIFO (SearchQuery config) <- mkSizedFIFO 1
results :: FIFO SearchResult <- mkFIFO
cfg :: Reg config <- mkReg _
let rid = queries.first.rid
depth :: Reg Depth <- mkReg 0 -- This is a register in order to be always ready for the status
stack :: Vector maxDepth Frame <- replicateM mkFrame
stackSize :: Reg Depth <- mkReg 0
let bottomFrame = stack !! 0
state :: Wire State <- mkWire
currentPlayerInCheck :: Wire Bool <- mkWire
otherPlayerInCheck :: Wire Bool <- mkWire
stateHeuristicScore :: Wire Score <- mkWire
isDraw :: Wire Bool <- mkWire
moveUpdate :: MoveUpdate <- mkMoveUpdate
eval :: MoveEval <- mkMoveEval
movesComplete :: Reg Bool <- mkReg True
let pushState :: Frame -> Score -> Score -> Action
pushState frame alpha beta = do
frame.putState state otherPlayerInCheck alpha beta
eval.state.put state
movesComplete := False
bestMove :: Reg Move <- mkReg _
currentMove :: Reg Move <- mkReg _
queryStateInCheck :: Reg Bool <- mkReg _
initialMoves :: FIFO MoveResponse <- mkFIFO
let enqInitialMove m = if queries.first.getMoves then initialMoves.enq $ NextMove m else noAction
rules
"get_query_state": when stackSize == 0 ==> state := queries.first.state
"get_move_state": when stackSize > 0 ==> state := moveUpdate.nextState
"eval_state": when True ==> do
-- $display "eval_state " (cshow $ inCheck state.board (otherColor state.turn)) " " (cshow $ inCheck state.board state.turn) " " (cshow $ heuristic cfg state)
-- This seems backwards but isn't: state is the state being considered next,
-- while the "current" state is the top of the stack.
currentPlayerInCheck := inCheck state.board (otherColor state.turn)
otherPlayerInCheck := inCheck state.board state.turn
stateHeuristicScore := heuristic cfg state
isDraw := state.lastProgressMove >= 50
"update_query_state_in_check": when stackSize == 0 ==> queryStateInCheck := otherPlayerInCheck
let frameRules frameIndex =
let nextFrame = if frameIndex < valueOf maxDepth - 1 then stack !! (frameIndex + 1) else _
currentFrame = stack !! frameIndex
prevFrame = if frameIndex > 0 then stack !! (frameIndex - 1) else _
isTerminal = fromInteger frameIndex >= depth - 1 || isDraw
depthStr = (List.foldr (+++) "" $ List.replicate (frameIndex + 1) " ") +++ integerToString (frameIndex + 1)
requestMoveUpdate = not moveUpdate.hasRequest || fromInteger frameIndex >= depth - 1
in
rules
when stackSize > 0, stackSize - 1 == fromInteger frameIndex
rules
("cutoff_" +++ integerToString frameIndex): when frameIndex > 0, currentFrame.alpha >= currentFrame.beta ==> do
$display depthStr " cutoff " currentFrame.score
prevFrame.putScore (negate currentFrame.score) currentFrame.forcedOutcome
stackSize := fromInteger frameIndex
movesComplete := True
currentFrame.moves.clear
moveUpdate.clear
eval.clear
when currentFrame.alpha < currentFrame.beta
rules
("request_move_update_" +++ integerToString frameIndex): when requestMoveUpdate ==> do
$display depthStr " request_move_update " (cshow currentFrame.moves.first)
moveUpdate.enq currentFrame.state currentFrame.moves.first
currentFrame.moves.deq
("put_NextMove_" +++ integerToString frameIndex): when not movesComplete, NextMove m <- eval.move.first ==> do
$display depthStr " put_NextMove " (cshow m)
currentFrame.moves.enq m
eval.move.deq
("put_NoMove_" +++ integerToString frameIndex): when not movesComplete, NoMove <- eval.move.first ==> do
$display depthStr " put_NoMove"
movesComplete := True
eval.move.deq
-- This exists to avoid deadlocking if a state somehow has > 60 valid moves
("discard_overflow_state_" +++ integerToString frameIndex): when not movesComplete, not requestMoveUpdate, not currentFrame.moves.notFull ==> do
$display depthStr " discard_overflow_state " (cshow currentFrame.moves.first)
currentFrame.moves.deq
("ignore_check_state_" +++ integerToString frameIndex): when currentPlayerInCheck, moveUpdate.ready ==> do
$display depthStr " ignore_check_state " (cshow moveUpdate.nextMove)
moveUpdate.deq
("heuristic_state_" +++ integerToString frameIndex): when not currentPlayerInCheck, isTerminal, moveUpdate.ready ==> do
$display depthStr " heuristic_state " (cshow moveUpdate.nextMove) " " stateHeuristicScore
isBestScore <- currentFrame.putScore (if isDraw then 0 else negate stateHeuristicScore) False
if frameIndex == 0 && isBestScore then bestMove := moveUpdate.nextMove else noAction
if frameIndex == 0 then enqInitialMove moveUpdate.nextMove else noAction
moveUpdate.deq
when movesComplete
rules
("push_state_" +++ integerToString frameIndex): when not currentPlayerInCheck, not isTerminal, moveUpdate.ready ==> do
$display depthStr " push_state " (cshow moveUpdate.nextMove) " " stateHeuristicScore
pushState nextFrame (negate currentFrame.beta) (negate currentFrame.alpha)
if frameIndex == 0
then do currentMove := moveUpdate.nextMove
enqInitialMove moveUpdate.nextMove
else noAction
stackSize := fromInteger frameIndex + 2
moveUpdate.deq
("pop_state_" +++ integerToString frameIndex): when frameIndex > 0, not currentFrame.moves.notEmpty, not moveUpdate.hasRequest, not moveUpdate.ready ==> do
$display depthStr " pop_state " currentFrame.score
isBestScore <- prevFrame.putScore (negate currentFrame.score) currentFrame.forcedOutcome
if frameIndex == 1 && isBestScore then bestMove := currentMove else noAction
stackSize := fromInteger frameIndex
dispatchMoves :: Vector numDispatch (FIFOF Move) <- replicateM mkFIFOF
dispatchInWaiting :: Reg (UInt 4) <- mkReg $ fromInteger $ valueOf numDispatch
(dispatchRules, clearDispatchMoveUpdate) <-
if valueOf numDispatch > 0
then do
dispatchMoveUpdate :: MoveUpdate <- mkMoveUpdate
dispatchStateInCheck :: Wire Bool <- mkWire
addRules $
rules
"eval_dispatch_state": when True ==>
dispatchStateInCheck := inCheck dispatchMoveUpdate.nextState.board bottomFrame.state.turn
let dispatchStateRules =
rules
"request_dispatch_move_update": when depth > 1, dispatchInWaiting > 0, bottomFrame.alpha < bottomFrame.beta ==> do
$display "request_dispatch_move_update " (cshow dispatchInWaiting) " " (cshow bottomFrame.moves.first)
dispatchMoveUpdate.enq bottomFrame.state bottomFrame.moves.first
bottomFrame.moves.deq
dispatchInWaiting := dispatchInWaiting - 1
`rJoinDescendingUrgency`
rules
"ignore_dispatch_check_state": when dispatchStateInCheck, bottomFrame.alpha < bottomFrame.beta ==> do
$display "ignore_dispatch_check_state " (cshow dispatchInWaiting) " " (cshow dispatchMoveUpdate.nextMove)
dispatchInWaiting := dispatchInWaiting + 1
dispatchMoveUpdate.deq
dispatchRules i =
rules
("dispatch_query_" +++ integerToString i): when not dispatchStateInCheck, bottomFrame.alpha < bottomFrame.beta ==> do
let query = SearchQuery {
rid = rid;
state = dispatchMoveUpdate.nextState;
depth = depth - 1;
getMoves = False;
alpha = Just $ negate bottomFrame.beta;
beta = Just $ negate bottomFrame.alpha;
config = cfg;
}
$display ("dispatch_query_" +++ integerToString i +++ " ") (cshow dispatchMoveUpdate.nextMove) " " (cshow query)
(dispatchMoves !! i).enq dispatchMoveUpdate.nextMove
(dispatchCores !! i).server.request.put query
enqInitialMove dispatchMoveUpdate.nextMove
dispatchMoveUpdate.deq
("handle_dispatch_result_" +++ integerToString i): when stackSize > 0 ==> do
response <- (dispatchCores !! i).server.response.get
$display ("handle_dispatch_result_" +++ integerToString i +++ " ") (cshow response)
bestScore <- bottomFrame.putScore (negate response.score) response.forcedOutcome
if bestScore then bestMove := (dispatchMoves !! i).first else noAction
(dispatchMoves !! i).deq
dispatchInWaiting := dispatchInWaiting + 1
return
(foldr rJoinDescendingUrgency dispatchStateRules ((genWith dispatchRules) :: Vector numDispatch Rules),
dispatchMoveUpdate.clear)
else return (emptyRules, noAction)
let reset = do
joinActions $ map (\ frame -> (frame :: Frame).moves.clear) stack
joinActions $ map (\ core -> (core :: SearchCore config dispatchDepth).clear) dispatchCores
joinActions $ map (\ moves -> (moves :: (FIFOF Move)).clear) dispatchMoves
stackSize := 0
eval.clear
moveUpdate.clear
clearDispatchMoveUpdate
dispatchInWaiting := fromInteger (valueOf numDispatch)
movesComplete := True
result =
interface SearchResult
rid = rid
outcome =
if queries.first.state.lastProgressMove >= 50 then Draw else
case (queryStateInCheck, bottomFrame.hasMove) of
(False, False) -> Draw
(False, True) -> NoOutcome
(True, False) -> CheckMate
(True, True) -> Check
bestMove = if bottomFrame.hasMove then Just bestMove else Nothing
forcedOutcome = bottomFrame.forcedOutcome
score = bottomFrame.score
depth = depth
searchRules =
foldr1 rJoin ((genWith frameRules) :: Vector maxDepth Rules) <+>
rules
when movesComplete
rules
"invalid_query_depth": when stackSize == 0, queries.first.depth == 0 || queries.first.depth > fromInteger (valueOf maxDepth) ==> do
$display "0 invalid_query_depth " (cshow queries.first)
results.enq $ SearchResult {rid=rid; outcome=NoOutcome; bestMove=Nothing; forcedOutcome=False; score=0; depth=depth;}
queries.deq
"push_query_state": when stackSize == 0, queries.first.depth > 0, queries.first.depth <= fromInteger (valueOf maxDepth) ==> do
$display "0 push_query_state " (cshow queries.first)
depth := queries.first.depth
cfg := queries.first.config
pushState bottomFrame (fromMaybe minScore queries.first.alpha) (fromMaybe maxScore queries.first.beta)
stackSize := 1
"pop_result_state": when stackSize == 1, bottomFrame.alpha < bottomFrame.beta, not bottomFrame.moves.notEmpty, not moveUpdate.hasRequest, dispatchInWaiting == fromInteger (valueOf numDispatch), not moveUpdate.ready ==> do
$display " 1 pop_result_state " (cshow result)
if queries.first.getMoves then initialMoves.enq NoMove else noAction
results.enq result
queries.deq
stackSize := 0
"cutoff_result_state": when stackSize == 1, bottomFrame.alpha >= bottomFrame.beta, not queries.first.getMoves ==> do
$display " 1 cutoff_result_state " (cshow result)
results.enq result
queries.deq
reset
clear :: PulseWire <- mkPulseWire
let clearRule =
rules
"clear": when clear ==> do
queries.clear
results.clear
initialMoves.clear
reset
addRules $ clearRule <+ (dispatchRules `rJoinDescendingUrgency` searchRules)
interface
server =
interface Server
request = toPut queries
response = toGet results
moves = fifoToGetS initialMoves
clear = clear.send
status = ((truncate $ pack depth) :: Bit 4) ++ ((truncate $ pack stackSize) :: Bit 4) ++ 0