1 |
2 |
sinclairrf |
; propagate.s
|
2 |
|
|
; Copyright 2012-2013, Sinclair R.F., Inc.
|
3 |
|
|
;
|
4 |
|
|
; Propagate state for Conway's Game of Life, SSBCC.9x8 implementation
|
5 |
|
|
;
|
6 |
|
|
; Method: As each successive line of the current state is processed, maintain
|
7 |
|
|
; copies of the preceding, current, and next lines. This provides a straight
|
8 |
|
|
; forward way to accommodate the "wrap" status.
|
9 |
|
|
|
10 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
11 |
|
|
;
|
12 |
|
|
; Propagate the state.
|
13 |
|
|
; ( - )
|
14 |
|
|
;
|
15 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
16 |
|
|
|
17 |
|
|
.function propagate
|
18 |
|
|
|
19 |
|
|
; Fill the middle line buffer with zeros or the last line of the image (if in
|
20 |
|
|
; wrap mode).
|
21 |
|
|
${C_N_MEM_LINES-1} .outport(O_ADDR_LINE)
|
22 |
|
|
line_curr
|
23 |
|
|
.fetchvalue(cmd_wrap) .callc(propagate__read_line)
|
24 |
|
|
.fetchvalue(cmd_wrap) 0= .callc(propagate__zero_buffer)
|
25 |
|
|
|
26 |
|
|
|
27 |
|
|
; Fill the last line buffer with the first line of the image
|
28 |
|
|
0x00 .outport(O_ADDR_LINE)
|
29 |
|
|
.call(propagate__read_line,line_next)
|
30 |
|
|
|
31 |
|
|
;
|
32 |
|
|
; Compute the new image.
|
33 |
|
|
;
|
34 |
|
|
|
35 |
|
|
; ( - ix_line )
|
36 |
|
|
0x00 :loop_outer
|
37 |
|
|
|
38 |
|
|
; Copy the middle and last line buffers to the first and middle line
|
39 |
|
|
; buffers respectively.
|
40 |
|
|
line_prev .call(propagate__copy_line,line_curr)
|
41 |
|
|
line_curr .call(propagate__copy_line,line_next)
|
42 |
|
|
|
43 |
|
|
; If this is is not the last line or if wrapping is commanded, then read the
|
44 |
|
|
; next line, otherwise fill the buffer with zeros.
|
45 |
|
|
; ( ix_line - ix_line (ix_line+1)&(mask) )
|
46 |
|
|
dup 1+ O_ADDR_LINE ${C_N_MEM_LINES-1} & .outport
|
47 |
|
|
; ( ix_line (ix_line+1)&(mask) - ix_line )
|
48 |
|
|
.fetchvalue(cmd_wrap) or .jumpc(do_read)
|
49 |
|
|
.call(propagate__zero_buffer,line_next) .jump(do_read_done)
|
50 |
|
|
:do_read
|
51 |
|
|
.call(propagate__read_line,line_next)
|
52 |
|
|
:do_read_done
|
53 |
|
|
|
54 |
|
|
; write the line number as the upper portion of the write address
|
55 |
|
|
; ( ix_line - ix_line )
|
56 |
|
|
O_ADDR_LINE .outport
|
57 |
|
|
|
58 |
|
|
; Compute and store the new state of the current line.
|
59 |
|
|
.call(propagate__line)
|
60 |
|
|
|
61 |
|
|
; ( ix_line - [(ix_line+1)&(mask)] )
|
62 |
|
|
1+ ${C_N_MEM_LINES-1} & .jumpc(loop_outer,nop) drop
|
63 |
|
|
|
64 |
|
|
.return
|
65 |
|
|
|
66 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
67 |
|
|
;
|
68 |
|
|
; Copy the current line buffer to the previous line buffer.
|
69 |
|
|
; ( u_prev u_curr - )
|
70 |
|
|
;
|
71 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
72 |
|
|
|
73 |
|
|
.function propagate__copy_line
|
74 |
|
|
; ( - n_remaining )
|
75 |
|
|
${(C_N_MEM_WORDS+2)-1} :loop_copy
|
76 |
|
|
; ( n_remaining - ) r: ( - n_remaining )
|
77 |
|
|
>r
|
78 |
|
|
; ( u_prev+n u_curr+n - u_prev+n+1 u_curr+n+1 )
|
79 |
|
|
.fetch+(ram) >r swap .store+(ram) r>
|
80 |
|
|
; ( - [n_remaining-1] ) r: ( n_remaining - )
|
81 |
|
|
r> .jumpc(loop_copy,1-) drop
|
82 |
|
|
; ( u_prev+N+2 u_curr+N+2 - )
|
83 |
|
|
drop .return(drop)
|
84 |
|
|
|
85 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
86 |
|
|
;
|
87 |
|
|
; Compute and store the new state of the current line.
|
88 |
|
|
; ( - )
|
89 |
|
|
;
|
90 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
91 |
|
|
|
92 |
|
|
.function propagate__line
|
93 |
|
|
|
94 |
|
|
; Initialize the processing state.
|
95 |
|
|
; ( - 0 u_left u_here ) r: ( - u_index u_mask )
|
96 |
|
|
|
97 |
|
|
0x80 >r
|
98 |
|
|
|
99 |
|
|
.call(propagate__fetch_triple)
|
100 |
|
|
.call(propagate__fetch_triple)
|
101 |
|
|
r> drop r>
|
102 |
|
|
|
103 |
|
|
:loop_outer >r
|
104 |
|
|
r@ 1- .outport(O_ADDR_WORD)
|
105 |
|
|
; Push the next 8-bit candidate value onto the return stack.
|
106 |
|
|
; r: ( - u_8bit )
|
107 |
|
|
|
108 |
|
|
0x01 :loop_inner
|
109 |
|
|
; Drop the oldest triple and bring in the triple from the right.
|
110 |
|
|
; ( u_left_old u_curr_old u_next_old - u_left_new=u_curr_old u_curr_new=u_next_old u_next_new )
|
111 |
|
|
>r swap drop r> .call(propagate__fetch_triple)
|
112 |
|
|
; Compute the new bit based on these surrounding bits
|
113 |
|
|
; ( u_left u_curr u_next - u_left u_curr u_next u_new_bit )
|
114 |
|
|
.call(propagate__new_bit)
|
115 |
|
|
; Shift the bitmask left 1 bit and finish the loop if the ones bit has rolled off the left.
|
116 |
|
|
r> <<0 .jumpc(loop_inner,nop) drop
|
117 |
|
|
r> .outport(O_BUFFER)
|
118 |
|
|
r> 1+ dup ${(C_N_MEM_WORDS+2)-1} - 0<> .jumpc(loop_outer) drop
|
119 |
|
|
|
120 |
|
|
.return
|
121 |
|
|
|
122 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
123 |
|
|
;
|
124 |
|
|
; Compute the new state of a pixel based on the 3x3 grid of pixels centered at
|
125 |
|
|
; the candidate pixel.
|
126 |
|
|
; ( u_prev u_curr u_next - u_prev u_curr u_next u_new )
|
127 |
|
|
;
|
128 |
|
|
; Rules:
|
129 |
|
|
; 1. Live pixels with fewer than 2 live neighbors die.
|
130 |
|
|
; 2. Live pixels with 2 or 3 live neighbors stay alive.
|
131 |
|
|
; 3. Live pixels with more than 3 live neighbors die.
|
132 |
|
|
; 4. Dead pixels with exactly 3 live neighbors come to life.
|
133 |
|
|
;
|
134 |
|
|
; Method used here:
|
135 |
|
|
; 1. Count all live pixels in the 3x3 grid, including the candidate pixel.
|
136 |
|
|
; 2. Get the alive/dead status of the candidate pixel.
|
137 |
|
|
; 3. If 3 pixels are alive or if 4 pixels are alive and the candidate pixel
|
138 |
|
|
; is alive, then propagate a live pixel.
|
139 |
|
|
;
|
140 |
|
|
; Note:
|
141 |
|
|
; 1. If exactly 3 pixels are alive, then either (1) the cell is alive and
|
142 |
|
|
; exactly two neighbors are alive or (2) the cell is dead and exactly 3
|
143 |
|
|
; neighbors are alive. In either case, the cell will either stay alive
|
144 |
|
|
; or become alive.
|
145 |
|
|
;
|
146 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
147 |
|
|
|
148 |
|
|
.function propagate__new_bit
|
149 |
|
|
|
150 |
|
|
; Put the candidate pixels status on the return stack
|
151 |
|
|
; ( u_curr u_next - u_curr u_next ) r: ( - f_this_bit_set )
|
152 |
|
|
over 0x02 & 0<> >r
|
153 |
|
|
|
154 |
|
|
; ( u_next - ) r: ( - u_next )
|
155 |
|
|
>r
|
156 |
|
|
|
157 |
|
|
; ( u_prev u_curr - u_prev u_curr n_prev )
|
158 |
|
|
over .fetch(nBitsSet)
|
159 |
|
|
|
160 |
|
|
; ( u_curr n_prev - u_curr n_prev+n_curr )
|
161 |
|
|
over .fetch(nBitsSet) +
|
162 |
|
|
|
163 |
|
|
; ( n_prev+n_curr - u_next n_total=n_prev+n_curr+n_next ) r: ( u_next - )
|
164 |
|
|
r> swap over .fetch(nBitsSet) +
|
165 |
|
|
|
166 |
|
|
; ( n_total - u_bit ) r: ( f_this_bit_set - )
|
167 |
|
|
dup 3 - 0= swap 4 - 0= r> & or 0x01
|
168 |
|
|
|
169 |
|
|
.return(&)
|
170 |
|
|
|
171 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
172 |
|
|
;
|
173 |
|
|
; Read a line into the specified buffer.
|
174 |
|
|
; ( u_line_buffer - )
|
175 |
|
|
;
|
176 |
|
|
; Method: Copy the contents of the current line to the specified buffer. Then,
|
177 |
|
|
; if wrapping is turned on, copy the first and last elements of the line to the
|
178 |
|
|
; end and start of the buffer respectively.
|
179 |
|
|
;
|
180 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
181 |
|
|
|
182 |
|
|
.function propagate__read_line
|
183 |
|
|
|
184 |
|
|
; Set the last element of the buffer to zero.
|
185 |
|
|
; ( u_line_buffer - u_line_buffer+1+C_N_MEM_WORDS-1 )
|
186 |
|
|
${C_N_MEM_WORDS+2-1) +
|
187 |
|
|
|
188 |
|
|
|
189 |
|
|
;
|
190 |
|
|
; Copy the current line to the buffer.
|
191 |
|
|
; ( u_line_buffer+1+C_N_MEM_WORDS-1 - u_line_buffer )
|
192 |
|
|
;
|
193 |
|
|
|
194 |
|
|
; Initialize the index/count for the transfer
|
195 |
|
|
; ( - u_ix_word )
|
196 |
|
|
${C_N_MEM_WORDS-1} :loop
|
197 |
|
|
|
198 |
|
|
; read the next word
|
199 |
|
|
; ( u_ix_word - u_word ) r: ( - u_ix_word )
|
200 |
|
|
.outport(O_ADDR_WORD,>r) .inport(I_BUFFER)
|
201 |
|
|
|
202 |
|
|
; store it in the buffer
|
203 |
|
|
; ( u_line_buffer+? u_word - u_line_buffer+?-1 )
|
204 |
|
|
swap .store-(ram)
|
205 |
|
|
|
206 |
|
|
; Do the loop iteration
|
207 |
|
|
; ( - [u_ix_word-1] ) r:( u_ix_word - )
|
208 |
|
|
r> .jumpc(loop,1-) drop
|
209 |
|
|
|
210 |
|
|
; If wrap mode is turned on, then copy the first and last entries in the line
|
211 |
|
|
; to the last and first entries in the buffer respectively.
|
212 |
|
|
; Effect is either
|
213 |
|
|
; ( u_line_buffer - u_line_buffer )
|
214 |
|
|
; or
|
215 |
|
|
; ( u_line_buffer - u_word )
|
216 |
|
|
.fetchvalue(cmd_wrap) 0= .jumpc(no_wrap)
|
217 |
|
|
; Copy the first entry in the line to the last entry in the buffer.
|
218 |
|
|
; ( u_line_buffer - u_line_buffer u_line_buffer+(C_N_MEM_WORDS+1)-1 )
|
219 |
|
|
dup 1+ .fetch(ram) over ${(C_N_MEM_WORDS+2)-1} + .store-(ram)
|
220 |
|
|
; Copy the last entry in the line to the first entry in the buffer.
|
221 |
|
|
; ( u_line_buffer u_line_buffer+(C_N_MEM_WORDS+1)-1 - u_word )
|
222 |
|
|
.fetch(ram) swap .store(ram)
|
223 |
|
|
:no_wrap
|
224 |
|
|
|
225 |
|
|
; Return and clean up the data stack.
|
226 |
|
|
; ( u_XXX - )
|
227 |
|
|
.return(drop)
|
228 |
|
|
|
229 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
230 |
|
|
;
|
231 |
|
|
; Fill the specified line buffer with all zeros.
|
232 |
|
|
; ( u_line - )
|
233 |
|
|
;
|
234 |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
235 |
|
|
|
236 |
|
|
.function propagate__zero_buffer
|
237 |
|
|
${(C_N_MEM_WORDS+2)-1} :loop_curr_fill_zero >r 0 swap .store+(ram) r> .jumpc(loop_curr_fill_zero,1-) drop
|
238 |
|
|
.return(drop)
|