cgol python
This commit is contained in:
parent
2ce72cee33
commit
29bd4b878d
|
@ -1 +1,12 @@
|
|||
__pycache__
|
||||
out
|
||||
*/build/*
|
||||
*/verdiLog/*
|
||||
*/out/*
|
||||
*/__pycache__/*
|
||||
hammer.log
|
||||
output.json
|
||||
novas.conf
|
||||
*.swp
|
||||
novas.rc
|
||||
*/python/v/*
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
|
||||
# Not installed by default
|
||||
# pip3 install --user --upgrade pip
|
||||
# pip3 install --user Pillow
|
||||
from PIL import Image
|
||||
|
||||
# Not installed by default
|
||||
# pip3 install --user scipy
|
||||
from scipy.signal import convolve2d
|
||||
|
||||
from tqdm import tqdm # Not installed by default
|
||||
import numpy as np # Not installed by default
|
||||
|
||||
# # Test image generation
|
||||
# images=[]
|
||||
# for i in range(0,255):
|
||||
# arr = np.ones((500, 500, 3), dtype=np.uint8)
|
||||
# arr[:, :, 1] = i
|
||||
# images.append(Image.fromarray(arr))
|
||||
|
||||
# images_itr = iter(images)
|
||||
# img = next(images_itr)
|
||||
# img.save('output_video.gif', format='GIF', append_images=images_itr,
|
||||
# save_all=True, duration=2, loop=0)
|
||||
|
||||
|
||||
''' CGOL truth table
|
||||
Neighbors: 2 3 other
|
||||
Alive A A D
|
||||
Dead D A D
|
||||
'''
|
||||
|
||||
def cgol_iter(arr: np.ndarray) -> np.ndarray:
|
||||
# print('next game step...')
|
||||
X = arr.shape[0]
|
||||
Y = arr.shape[1]
|
||||
arr_next = np.zeros((X, Y), dtype=np.uint8)
|
||||
for x in range(X):
|
||||
for y in range(Y):
|
||||
ngh = arr[max(0, x-1):min(X, x+2), max(0, y-1):min(Y, y+2)].sum()
|
||||
if arr[x,y]:
|
||||
ngh -= 1
|
||||
if ngh in (2, 3): arr_next[x, y] = 1
|
||||
else:
|
||||
if ngh == 3: arr_next[x, y] = 1
|
||||
# print('done')
|
||||
return arr_next
|
||||
|
||||
def cgol_iter2(arr: np.ndarray) -> np.ndarray:
|
||||
X = arr.shape[0]
|
||||
Y = arr.shape[1]
|
||||
arr_next = np.zeros((X, Y), dtype=np.uint8)
|
||||
x_min = max(np.argwhere(arr)[:,0].min()-2, 0)
|
||||
x_max = min(np.argwhere(arr)[:,0].max()+2, X)
|
||||
y_min = max(np.argwhere(arr)[:,1].min()-2, 0)
|
||||
y_max = min(np.argwhere(arr)[:,1].max()+2, Y)
|
||||
|
||||
for x in range(x_min, x_max):
|
||||
for y in range(y_min, y_max):
|
||||
# 8 single-element reads instead
|
||||
ngh = 0
|
||||
if x > 0 and y > 0 : ngh += arr[x-1, y-1] # TL
|
||||
if y > 0 : ngh += arr[x , y-1] # T
|
||||
if x < X-1 and y > 0 : ngh += arr[x+1, y-1] # TR
|
||||
if x > 0 : ngh += arr[x-1, y ] # L
|
||||
if x < X-1 : ngh += arr[x+1, y ] # R
|
||||
if x > 0 and y < Y-1: ngh += arr[x-1, y+1] # BL
|
||||
if y < Y-1: ngh += arr[x , y+1] # B
|
||||
if x < X-1 and y < Y-1: ngh += arr[x+1, y+1] # BR
|
||||
|
||||
if arr[x,y]:
|
||||
if ngh in (2, 3): arr_next[x, y] = 1 # Stay alive
|
||||
else:
|
||||
if ngh == 3: arr_next[x, y] = 1 # Become alive
|
||||
return arr_next
|
||||
|
||||
def cgol_iter3(arr: np.ndarray) -> np.ndarray:
|
||||
# Perform 'num-neighbors' convolution
|
||||
ngh_window = np.array([[1,1,1],[1,0,1],[1,1,1]])
|
||||
ngh_arr = convolve2d(arr, ngh_window, mode='same', boundary='fill', fillvalue=0)
|
||||
# Generate next CGOL generation with the formula: "arr_next = (3nghs OR (arr AND 2nghs))"
|
||||
return np.logical_or((ngh_arr==3), np.logical_and(arr, (ngh_arr==2))).astype(np.uint8)
|
||||
|
||||
# def board_to_img(arr: np.ndarray, px_size: int) -> Image:
|
||||
# print('converting to image...')
|
||||
# X = arr.shape[0]
|
||||
# Y = arr.shape[1]
|
||||
# arr_img = np.empty((X*px_size, Y*px_size, 3), dtype=np.uint8)
|
||||
# for c in (0,1,2): arr_img[:, :, c] = dead_color[c]
|
||||
# for x in range(X):
|
||||
# for y in range(Y):
|
||||
# if arr[x,y]:
|
||||
# x_start = px_size*x
|
||||
# y_start = px_size*y
|
||||
# arr_img[x_start:x_start+px_size,
|
||||
# y_start:y_start+px_size, :] = alive_color
|
||||
# print('done')
|
||||
# return Image.fromarray(arr_img)
|
||||
|
||||
def board_to_img2(arr: np.ndarray, px_size: int, a:tuple=(255,255,255), d:tuple=(0,0,0)) -> Image:
|
||||
# print('converting to image...')
|
||||
X = arr.shape[0]
|
||||
Y = arr.shape[1]
|
||||
arr_img = np.empty((X*px_size, Y*px_size, 3), dtype=np.uint8)
|
||||
# Repeat matrix to size of board
|
||||
arr_rep = arr.repeat(px_size, 0).repeat(px_size, 1)
|
||||
# For each channel, set alive or dead value based on arr_rep
|
||||
for c in (0,1,2): arr_img[:, :, c] = arr_rep*(a[c]-d[c]) + d[c]
|
||||
im = Image.fromarray(arr_img)
|
||||
# print('done')
|
||||
return im
|
||||
|
||||
def save_gif(images: list, fname: str, frame_dur:int=50) -> None:
|
||||
print('Saving GIF... ', end='', flush=True)
|
||||
# images_itr = iter(images)
|
||||
# img = next(images_itr)
|
||||
# Note: set loop=1 to generate GIFs that play once
|
||||
images[0].save(fname, format='GIF', append_images=images[1:],
|
||||
save_all=True, duration=frame_dur, loop=0, optimize=False)
|
||||
print('done.')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
vid_out_fname = 'cgol.gif'
|
||||
board_x = 128 #512
|
||||
board_y = 128 #512
|
||||
game_length = 800 # 2300
|
||||
|
||||
dead_color = (128, 128, 128)
|
||||
alive_color = ( 0, 255, 0)
|
||||
grid_px_size = 4
|
||||
|
||||
# 'Glider'
|
||||
# board_init = np.zeros((board_y, board_x), dtype=np.uint8)
|
||||
# board_init[5,6] = 1
|
||||
# board_init[6,7] = 1
|
||||
# board_init[7,5:8] = 1
|
||||
|
||||
# # 'Spaceship'
|
||||
# board_x = 16
|
||||
# board_y = 256
|
||||
# game_length = 550
|
||||
# board_init = np.zeros((board_y, board_x), dtype=np.uint8)
|
||||
# board_init[2, 7] = 1
|
||||
# board_init[2, 9] = 1
|
||||
# board_init[5, 9] = 1
|
||||
# board_init[3:7, 6] = 1
|
||||
# board_init[6, 6:9] = 1
|
||||
# board_init = board_init.transpose()
|
||||
|
||||
# 'Acorn' (Goes about 5300)
|
||||
board_init = np.zeros((board_y, board_x), dtype=np.uint8)
|
||||
mid = (board_x//2, board_y//2)
|
||||
board_init[mid[0]-2, mid[1]-1]=1
|
||||
board_init[mid[0], mid[1]]=1
|
||||
board_init[mid[0]-3:mid[0]-1, mid[1]+1]=1
|
||||
board_init[mid[0]+1:mid[0]+4, mid[1]+1]=1
|
||||
board_init = board_init.transpose()
|
||||
|
||||
images = []
|
||||
board = board_init
|
||||
print('Generating game...')
|
||||
for i in tqdm(range(game_length), disable=False):
|
||||
images.append(board_to_img2(board, grid_px_size, a=alive_color, d=dead_color))
|
||||
board = cgol_iter3(board)
|
||||
|
||||
# Save animated GIF
|
||||
save_gif(images, vid_out_fname, frame_dur=35)
|
||||
# Save initial setup of the board
|
||||
# init_img = board_to_img2(board_init, grid_px_size, a=alive_color, d=dead_color)
|
||||
images[0].save('cgol_init.gif', format='GIF', save_all=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
# Configure the Final Project Test Suite
|
||||
|
||||
# This needs to match the parameters of the DUT instantiated in the testbench
|
||||
board:
|
||||
width: 32
|
||||
max_length: 1000
|
||||
|
||||
display:
|
||||
# RGB color of 'dead' spaces in the display
|
||||
dead_color: [128, 128, 128] # grey
|
||||
# RGB color of 'alive' spaces in the display
|
||||
alive_color: [ 0, 255, 0] # bright green
|
||||
# size of the grid in the display
|
||||
pixel_size: 10
|
||||
# Durration (ms) of each frame in the display
|
||||
frame_dur_ms: 50
|
||||
|
||||
games: [
|
||||
# Small and simple oscillator
|
||||
{
|
||||
length: 10,
|
||||
checks: "last", # Only check last frame
|
||||
init_alive: [
|
||||
[-1,0], [0,0], [1,0]
|
||||
],
|
||||
origin: 'center' # alive coorodinates relative to middle of the board
|
||||
},
|
||||
|
||||
# Common glider
|
||||
{
|
||||
length: 50,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[2,1], [3,2], [1,3], [2,3], [3,3]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
# Common glider (detailed test checking every frame)
|
||||
{
|
||||
length: 8,
|
||||
checks: "all", # Check every frame
|
||||
init_alive: [
|
||||
[2,1], [3,2], [1,3], [2,3], [3,3]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
# Very large 'Acorn' seed
|
||||
{
|
||||
length: 100,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[-2,-1], [0,0], [-3,1], [-2,1], [1,1], [2,1], [3,1]
|
||||
],
|
||||
origin: "center"
|
||||
},
|
||||
|
||||
# Very large Custom seed
|
||||
{
|
||||
length: 300,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[2,1], [3,1], [4,1], [-2,1], [-3,1], [-4,1], [2,-1], [3,-1], [4,-1], [-2,-1], [-3,-1], [-4,-1], [6,2], [6,3], [6,4], [6,-2], [6,-3], [6,-4], [-6,2], [-6,3], [-6,4], [-6,-2], [-6,-3], [-6,-4], [2,6], [3,6], [4,6], [-2,6], [-3,6], [-4,6], [2,-6], [3,-6], [4,-6], [-2,-6], [-3,-6], [-4,-6], [1,2], [1,3], [1,4], [-1,-2], [-1,-3], [-1,-4], [-1,2], [-1,3], [-1,4], [1,-2], [1,-3], [1,-4]
|
||||
],
|
||||
origin: "center"
|
||||
},
|
||||
]
|
|
@ -0,0 +1,167 @@
|
|||
|
||||
import yaml # Not installed by default
|
||||
import cgol
|
||||
import os
|
||||
import shutil
|
||||
import numpy as np # Not installed by default
|
||||
from argparse import ArgumentParser
|
||||
from tqdm import tqdm # Not installed by default
|
||||
from math import ceil, log2
|
||||
|
||||
# Default values:
|
||||
trace_data_width = 64 # Data traces should be this many bits wide
|
||||
output_dir = 'out' # GIFs are written here
|
||||
default_trace_fout = 'v/bsg_trace_master_0.tr' # Default output file
|
||||
default_cfg = 'default_sim_cfg.yml' # Default config file
|
||||
|
||||
def send_game_req(fout, board:np.ndarray, length, width, max_len):
|
||||
'''
|
||||
Given a board and a game length, write this to the trace file so the
|
||||
data is sent to the DUT.
|
||||
'''
|
||||
game_len_formatter = f'{{:0{ ceil(log2(max_len+1)) }b}}'
|
||||
game_len_bin = game_len_formatter.format(length)
|
||||
fout.write(f'### sending board config (game length {length}) ###\n')
|
||||
# Write traces to send data
|
||||
board_in = board.copy()
|
||||
board_in = board_in.reshape((width**2, 1))
|
||||
board_in = board_in.squeeze()
|
||||
board_in = np.pad(board_in, (0, trace_data_width), 'constant', constant_values=0) # Pad with 0s
|
||||
wr_idx = 0
|
||||
while wr_idx < width**2:
|
||||
if wr_idx==0:
|
||||
data = np.flip(board_in[0:(trace_data_width-len(game_len_bin))])
|
||||
wr_idx+=(trace_data_width-len(game_len_bin))
|
||||
fout.write(f'0001____0_00000000000_{"".join([str(x) for x in data])}_{game_len_bin}\n')
|
||||
else:
|
||||
data = np.flip(board_in[wr_idx:wr_idx+trace_data_width])
|
||||
wr_idx+=trace_data_width
|
||||
fout.write(f'0001____0_00000000000_{"".join([str(x) for x in data])}\n')
|
||||
fout.write('\n')
|
||||
return board
|
||||
|
||||
def send_game_check(fout, board:np.ndarray, width):
|
||||
'''
|
||||
Given a board, write the output expected trace to the trace file.
|
||||
'''
|
||||
fout.write(f'### Checking board output ###\n')
|
||||
board = board.reshape((width**2, 1))
|
||||
board = board.squeeze()
|
||||
board = np.pad(board, (0, trace_data_width), 'constant', constant_values=0) # Pad with 0s
|
||||
wr_idx = 0
|
||||
while wr_idx < width**2:
|
||||
data = np.flip(board[wr_idx:wr_idx+trace_data_width])
|
||||
wr_idx+=trace_data_width
|
||||
fout.write(f'0010____0_00000000000_{"".join([str(x) for x in data])}\n')
|
||||
fout.write('\n')
|
||||
|
||||
def add_image_to_list(list, board, disp):
|
||||
# Add a the next image to a list of images
|
||||
list.append(cgol.board_to_img2(board, disp['pixel_size'],
|
||||
a=disp['alive_color'],
|
||||
d=disp['dead_color']))
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('-cfg', required=False, default=default_cfg,
|
||||
help=f'The input config file (in yaml format), defaults to "{default_cfg}"')
|
||||
parser.add_argument('-out', required=False, default=default_trace_fout,
|
||||
help=f'Output trace file, defaults to "{default_trace_fout}"')
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f'Clearing any previous output in "{output_dir}"...')
|
||||
# Create output directory for testbench .data files
|
||||
try: shutil.rmtree(output_dir)
|
||||
except(FileNotFoundError): pass
|
||||
os.mkdir(output_dir)
|
||||
|
||||
cfg = yaml.safe_load(open(args.cfg, 'r'))
|
||||
width = cfg["board"]["width"]
|
||||
max_len = cfg["board"]["max_length"]
|
||||
cgol_iterations = 0
|
||||
cgol_elapsed_time = 0
|
||||
# split args.out into directory and file name
|
||||
f = os.path.split(args.out)
|
||||
# check if the directory exists, if not, create it
|
||||
if f[0] != '' and not os.path.exists(f[0]):
|
||||
os.makedirs(f[0])
|
||||
with open(args.out, 'w') as fout:
|
||||
|
||||
# Write trace header
|
||||
fout.write('')
|
||||
fout.write(f'# This trace file was generated by "final_project_pre_sim.py" with the config file "{args.cfg}", do not directly modify!\n')
|
||||
fout.write('\n')
|
||||
fout.write(f'# Board size: {width}x{width}, max game length: {cfg["board"]["max_length"]}\n')
|
||||
fout.write(f'# Beginning trace ROM with {len(cfg["games"])} games:\n')
|
||||
fout.write('\n')
|
||||
|
||||
# Write the config for every game...
|
||||
for (idx, game) in enumerate(cfg['games']):
|
||||
assert game['length'] <= max_len, 'Max game lenght exceded!'
|
||||
fout.write(f'########## Game {idx+1} ##########\n')
|
||||
# Generate the initial board
|
||||
board = np.zeros((width, width), dtype=np.uint8)
|
||||
alive_list = game['init_alive']
|
||||
if game['origin'] == 'center':
|
||||
for ii, p in enumerate(alive_list): alive_list[ii] = [p[0]+(width//2), p[1]+(width//2)]
|
||||
for pt in alive_list: board[pt[0]][pt[1]] = 1
|
||||
board = board.transpose()
|
||||
|
||||
expected_fout = f'game_{idx+1}_{width}x{width}_{game["length"]}.gif'
|
||||
|
||||
# Generate the trace for this game: only check the last frame
|
||||
if game["checks"] == "last":
|
||||
send_game_req(fout, board, game['length'], width, max_len) # Send initial data
|
||||
print(f'Generating expected result "{expected_fout}" ...')
|
||||
images = []
|
||||
# Simulate Game of Life
|
||||
with tqdm(range(game['length']), disable=False) as tq:
|
||||
for i in tq:
|
||||
add_image_to_list(images, board, cfg["display"])
|
||||
board = cgol.cgol_iter3(board)
|
||||
# Record stats
|
||||
cgol_iterations += tq.format_dict['total']
|
||||
cgol_elapsed_time += tq.format_dict['elapsed']
|
||||
add_image_to_list(images, board, cfg["display"]) # Add last frame
|
||||
# Save game as a GIF
|
||||
cgol.save_gif(images, os.path.join(output_dir, expected_fout), frame_dur=cfg['display']['frame_dur_ms'])
|
||||
send_game_check(fout, board, width) # Send output check
|
||||
|
||||
# Generate the trace for this game: check every frame a long the way
|
||||
elif game["checks"] == "all":
|
||||
print(f'Generating expected result "{expected_fout}" ...')
|
||||
images = []
|
||||
# Simulate Game of Life
|
||||
with tqdm(range(game['length']), disable=False) as tq:
|
||||
for i in tq:
|
||||
fout.write(f'# Checking frame {i+1}:\n')
|
||||
send_game_req(fout, board, 1, width, max_len) # Send next frame data (length=1)
|
||||
add_image_to_list(images, board, cfg["display"])
|
||||
board = cgol.cgol_iter3(board)
|
||||
send_game_check(fout, board, width) # Send output check
|
||||
add_image_to_list(images, board, cfg["display"]) # Add last frame
|
||||
# Save output image
|
||||
cgol.save_gif(images, os.path.join(output_dir, expected_fout), frame_dur=cfg['display']['frame_dur_ms'])
|
||||
|
||||
# Invalid option
|
||||
else:
|
||||
assert False, f'ERROR: invalid "checks" option: {game["checks"]}'
|
||||
|
||||
# After all games finished...
|
||||
fout.write(f'########## SIMULATION FINISHED ##########\n')
|
||||
fout.write(f'0011____0_00000000000_{64*"0"}\n')
|
||||
|
||||
# After output file closed...
|
||||
print('Done.')
|
||||
print()
|
||||
|
||||
# Print performancs stats
|
||||
|
||||
print('CGoL script performance: (measured with "checks=last" games only)')
|
||||
print(f' Computed and saved a total of {cgol_iterations} CGoL frames of size {width}x{width} over {cgol_elapsed_time:.3f} seconds')
|
||||
if cgol_elapsed_time==0 : cgol_elapsed_time = float('nan') # Handle case where there were no 'last' games
|
||||
print(f' Average performance {cgol_iterations/cgol_elapsed_time:.3f} frames/second')
|
||||
print()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,110 @@
|
|||
|
||||
# Configure the Final Project Test Suite
|
||||
|
||||
# This needs to match the parameters of the DUT instantiated in the testbench
|
||||
board:
|
||||
width: 3
|
||||
max_length: 1
|
||||
|
||||
display:
|
||||
# RGB color of 'dead' spaces in the display
|
||||
dead_color: [128, 128, 128] # grey
|
||||
# RGB color of 'alive' spaces in the display
|
||||
alive_color: [ 0, 255, 0] # bright green
|
||||
# size of the grid in the display
|
||||
pixel_size: 20
|
||||
# Durration (ms) of each frame in the display
|
||||
frame_dur_ms: 1000
|
||||
|
||||
games: [
|
||||
# No neighbors
|
||||
{
|
||||
length: 1,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[1,1]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
# 1 neighbors
|
||||
{
|
||||
length: 1,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[1,1], [0,0]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
# 2 neighbors
|
||||
{
|
||||
length: 1,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[1,1], [0,0], [0, 1]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
# 3 neighbors
|
||||
{
|
||||
length: 1,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[1,1], [0,0], [0, 1], [0, 2]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
# 4 neighbors
|
||||
{
|
||||
length: 1,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[1,1], [0,0], [0, 1], [0, 2], [1, 2]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
# 5 neighbors
|
||||
{
|
||||
length: 1,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[1,1], [0,0], [0, 1], [0, 2], [1, 2], [2, 2]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
# 6 neighbors
|
||||
{
|
||||
length: 1,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[1,1], [0,0], [0, 1], [0, 2], [1, 2], [2, 2], [2, 1]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
# 7 neighbors
|
||||
{
|
||||
length: 1,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[1,1], [0,0], [0, 1], [0, 2], [1, 2], [2, 2], [2, 1], [2, 0]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
# 8 neighbors
|
||||
{
|
||||
length: 1,
|
||||
checks: "last",
|
||||
init_alive: [
|
||||
[1,1], [0,0], [0, 1], [0, 2], [1, 2], [2, 2], [2, 1], [2, 0], [1, 0]
|
||||
],
|
||||
origin: "corner" # alive coorodinates relative to upper-left corner of the board
|
||||
},
|
||||
|
||||
]
|
Loading…
Reference in New Issue