#!/usr/bin/awk -f
# input should be C-preprocessed
# defines wrappers that automatically connect verilog to hacprsim

# optional arguments:
# -v options
#	wrapper_prefix= string to prefix wrapper definitions (default = "HAC_")
#	max_strlen= maximum size of string for identifiers before mangling
#	wrapper_ports= 0,1 whether make wrappers' ports public (default = 0)
#	reverse= 0,1 if 1, then reverse direction of to_prsim/from_prsim
#		useful when wrapper is intended for HAC/prsim to implement.
#		if 1, then also suppress forwarding of port to a dummy instance

BEGIN {
	error = 0;
	FS = "[ \t][ \t]*";	# to tokenize desugarized lines
	# LR(0) parsing...
	# parse stack to track state
	parse_stack_ptr = -1;
	parse_push("top");
	if (!length(wrapper_prefix)) {
		wrapper_prefix = "HAC";
	}
	if (!length(max_strlen)) {
		max_strlen = 64;
	} else {
		max_strlen = strtonum(max_strlen);
	}
	if (!length(wrapper_ports)) {
		wrapper_ports = 0;
	} else {
		wrapper_ports = strtonum(wrapper_ports);
	}
	if (!length(reverse)) {
		reverse = 0;
	} else {
		reverse = strtonum(reverse);
	}
	if (!length(debug)) {
		debug = 0;
	} else {
		debug = strtonum(debug);
	}
	print "// vi: syntax=verilog";	# :)
	print "// This file is automatically generated by wrap_verilog_modules_to_hacprsim.awk, maintained by David Fang.";
	print "";
}

function parse_error(str) {
	print "parse error on line " NR ": " str;
	error = 1;
	parse_debug();
	exit(1)
}

# shift
function parse_push(st) {
	++parse_stack_ptr;
	parse_stack[parse_stack_ptr] = st;
}

# reduce
function parse_pop() {
	--parse_stack_ptr;
}

function parse_replace(st) {
	parse_stack[parse_stack_ptr] = st;
}

# checks state of stack
# tok is optional current token
function parse_expect(st, tok) {
	if (parse_stack[parse_stack_ptr] != st) {
		parse_error("expected to be in state \"" st \
			"\" but got \"" parse_stack[parse_stack_ptr] "\".\n" \
			"current token [:" NR "]: " tok);
	}
}

function assert_token(tok, str) {
	if (tok != str) {
		parse_error("expected token \"" tok "\" but got \"" str "\"" \
			"at [:" NR "].");
	}
}

# print the parse stack
function parse_debug(i) {
	printf("stack: ");
	for (i=0; i<=parse_stack_ptr; ++i) {
		printf(parse_stack[i] " ")
	}
	print "";
}

# pad syntactic sugar (tokens) with spaces for easy splitting
function tokenize(str) {
	gsub("[][(,:;'#)]", " & ", str);	# pad with spaces
	return str;
}

function reset_globals() {
	type_name = "";
	param_index = 0;
	port_index = 0;
	# clear the associative arrays
	# delete ports;		# not POSIX
	for (p in ports) {
		delete ports[p];
	}
	for (p in ordered_ports) {
		delete ordered_ports[p];
	}
	for (p in ordered_params) {
		delete ordered_params[p];
	}
	for (p in param_default_values) {
		delete param_default_values[p];
	}
	for (p in dimensions) {
		delete upper[p];
		delete lower[p];
		delete dimensions[p];
	}
}

# param in_port declaration is local to ports-list (true) or module-body (false)
function set_port_dir(tok, dir, 
	# local vars
	in_port) {
if (tok == "[") {
	parse_push("expect-range-first");
} else {
	in_port = parse_stack[parse_stack_ptr -1] == "ports-list";
	if (tok != ";") {
	# permit declarations like "output reg ..."
	if (tok != "," && tok != "reg" && tok != ")") {
		# this may be parsed directly in the module-ports
		if (in_port) {
			ordered_ports[port_index] = tok;
			port_index++;
		}
		if (length(range_first) && length(range_second)) {
			upper[tok] = range_first;
			lower[tok] = range_second;
			dimensions[tok] = "[" range_first ":" range_second "]";
		}
		ports[tok] = dir;
	} else if (tok == ")" || (tok == "," && in_port)) {
		# yuck: context dependent handling of comma
		parse_pop();
		parse_expect("ports-list", tok);
	} # else ignore
	} else {
		parse_pop();
	}
}
}

# parse: parameter foo = ..., bar = ..., ... ;
function keep_parameter(tok) {
if (tok == "[") {
	parse_push("expect-range-first");
} else {
	if (tok != ";") {
	if (tok != ",") {
	if (tok != "=") {
		# this may be parsed directly in the module-ports
		ordered_params[param_index] = tok;
		param_index++;
		if (length(range_first) && length(range_second)) {
			upper[tok] = range_first;
			lower[tok] = range_second;
			dimensions[tok] = "[" range_first ":" range_second "]";
		}
	} else {		# with default value
		rvalue = "";	# global variable:
		parse_replace("param-default-value");
	}
	} else if (tok == ",") {
		# yuck: context dependent handling of comma
		parse_pop();
		parse_expect("param-list", tok);
	} # else ignore:  = ...
	} else {
		parse_pop();
	}
}
}

# where the parsing happens...
function parse_it(tok, 
	# local vars
	state, tmp) {
state = parse_stack[parse_stack_ptr];
if (state == "top") {
	# expect module
	if (tok == "module") {
		reset_globals();
		parse_push("module");
		parse_push("expect-type-name");
	}
	# else nothing
} else if (state == "expect-type-name") {
	type_name = tok;
	parse_replace("expect-ports");
} else if (state == "expect-ports") {
	if (tok == ";") {
		parse_replace("module-body");
	} else if (tok == "(") {
		parse_replace("ports-list");
	} else {
		parse_error("expected '(' or ';'");
	}
} else if (state == "ports-list") {
	# reset globals, yuck!
	range_first = "";
	range_second = "";
	if (tok != ")" && tok != ";") {
	if (tok != ",") {
		# ports can just be identifier or directional identifier
		if (tok == "input") {
			parse_push("input-list");
		} else if (tok == "output") {
			parse_push("output-list");
		} else if (tok == "inout") {
			parse_push("inout-list");
		} else {
		# is just untyped identifier
		ordered_ports[port_index] = tok;
		port_index++;
		ports[tok] = "";	# initialize to unknown direction
		# technically this isn't really needed
		}
	} # else ignore commas
	} else {
		parse_replace("module-body");
	}
} else if (state == "module-body") {
	# now look for input and output direction specs
	# reset globals, yuck!
	range_first = "";
	range_second = "";
	if (tok == "input") {
		parse_push("input-list");
	} else if (tok == "output") {
		parse_push("output-list");
	} else if (tok == "inout") {
		parse_push("inout-list");
	} else if (tok == "parameter") {
		parse_push("param-list");
	} else if (tok == "reg") {
		parse_push("ignore-to-semicolon");
	} else if (tok == "initial" || tok == "#") {
		parse_push("ignore-block");
	} else if (tok == "specify") {
		parse_push("ignore-specify");
	} else if (tok == "function") {
		parse_push("ignore-function");
	} else if (tok == "endmodule") {
		parse_pop();
		parse_expect("module", tok);
		parse_pop();
		write_out_wrapper();
		parse_expect("top", tok);
	}
} else if (state == "expect-range-first") {
	range_first = tok;
	parse_replace("expect-range-colon");
} else if (state == "expect-range-second") {
	range_second = tok;
	parse_replace("expect-range-end");
} else if (state == "expect-range-colon") {
	assert_token(tok, ":");
	parse_replace("expect-range-second");
} else if (state == "expect-range-end") {
	assert_token(tok, "]");
	parse_pop();
} else if (state == "input-list") {
	set_port_dir(tok, "in");
} else if (state == "output-list") {
	set_port_dir(tok, "out");
} else if (state == "inout-list") {
	set_port_dir(tok, "inout");
} else if (state == "param-list") {
	keep_parameter(tok);
} else if (state == "param-default-value") {
	tmp = ordered_params[param_index-1];	# current param
	# rvalue is global variable
	if (tok == ",") {
		parse_replace("param-list");
		param_default_values[tmp] = rvalue;
		rvalue = "";
	} else if (tok == ";") {
		param_default_values[tmp] = rvalue;
		rvalue = "";
		parse_pop();
	} else {
		rvalue = rvalue tok;	# accumulate global var
	}
} else if (state == "ignore-block") {
	if (tok == "begin") {
		parse_replace("ignore-to-end");
	} else {
		parse_replace("ignore-to-semicolon");
	}
} else if (state == "ignore-specify") {
	if (tok == "endspecify") {
		parse_pop();
	}
} else if (state == "ignore-function") {
	if (tok == "endfunction") {
		parse_pop();
	}
} else if (state == "ignore-to-comma") {
	if (tok == ",") {
		parse_pop();
	} # else ignore
} else if (state == "ignore-to-semicolon") {
	if (tok == ";") {
		parse_pop();
	} # else ignore
} else if (state == "ignore-to-comma-or-semicolon") {
	if (tok == ";" || tok == ",") {
		parse_pop();
	} # else ignore
} else if (state == "ignore-to-end") {
	if (tok == "end") {
		parse_pop();
	} # else ignore
} else if (state == "module-finished") {
	
}
}

# just wrap name in quotes
function enquote_mem_ref(name) {
	return "\"." name "\"";
}

function verilog_strcat2(s1, s2) {
	return "{" s1 ", " s2 "}";
}

function emit_from_prsim(name) {
	return "$from_prsim(" verilog_strcat2("prsim_name", name) \
		", " verilog_strcat2("verilog_name", name) ");";
}

function emit_to_prsim(name) {
	return "$to_prsim(" verilog_strcat2("verilog_name", name) \
		", " verilog_strcat2("prsim_name", name) ");";
}

# emit wrapper function
# no parameters, variables are only local
function write_out_wrapper(tmp, u, l, n, type, dim, endl) {
	print "module " wrapper_prefix "_" type_name (wrapper_ports ? " (" : ";");
	print "// need not be reg with acc: wn:*";
	# might as well preserve original port order
	for (i=0; i<param_index; ++i) {
		p = ordered_params[i];
		tmp = param_default_values[p];
		print "\tparameter " p (length(tmp) ? " = " tmp : "") ";";
		# preserve RHS values and expressions
	}
	for (i=0; i<port_index; ++i) {
		p = ordered_ports[i];
		type = (wrapper_ports ? ports[p] "put" : "wire");
		tmp = dimensions[p];
		dim = (length(tmp) ? tmp " " : "");
		endl = (wrapper_ports ? (i+1 < port_index ? "," : "") : ";")
		print "\t" type " " dim p endl;
	}
	if (wrapper_ports) {
		print ");";	# end of ports list
	}
	print "\tparameter prsim_name=\"\";";
	print "\tinteger i;";
	print "\treg [" max_strlen "*8:1] verilog_name, tmp;";
	# tmp is just local var for string manip
if (!reverse) {
	printf("\t" type_name);
	if (param_index) {
		printf(" #(");
	for (i=0; i<param_index; ++i) {
		tmp = ordered_params[i];
		printf(tmp (i+1 < param_index ? ", " : ""));
	}
		print ")";
		printf("\t");
	} else {
		printf(" ");
	}
	print "dummy (";
	# order matters for port fowarding
	for (i=0; i<port_index; ++i) {
		print "\t\t" ordered_ports[i] (i+1 < port_index ? "," : "");
	}
	print "\t);";
}
	print "initial begin";
	print "#0\t// happens *after* initial";
	print "\tif (prsim_name != \"\") begin";
	print "\t$sformat(verilog_name, \"%m\");"
# this order does not matter
for (p in ports) {
	type = ports[p];
	u = upper[p];
	l = lower[p];
	good = 0;
	# ports may be "inout" bidirectional, so I'm guessing both to and from
	if (match(type, "in")) {
		good = 1;
	if (length(u) && length(l)) {
#	for ( ; l<=u; ++l) {
#		n = p "[" l "]";
#		print "\t" (reverse ? emit_to_prsim(n) : emit_from_prsim(n));
#	}
	print "\tfor (i=" l "; i<=" u "; i=i+1) begin";
		print "\t\t$sformat(tmp, \"." p "[%d]\", i);"
		print "\t\t" (reverse ? emit_to_prsim("tmp") : emit_from_prsim("tmp"));
	print "\tend // end for";
	} else {
		n = enquote_mem_ref(p);
		print "\t" (reverse ? emit_to_prsim(n) : emit_from_prsim(n));
	}
	}
	if (match(type, "out")) {
		good = 1;
	if (length(u) && length(l)) {
#	for ( ; l<=u; ++l) {
#		n = enquote_mem_ref(p "[" l "]");
#		print "\t" (reverse ? emit_from_prsim(n) : emit_to_prsim(n));
#	}
	print "\tfor (i=" l "; i<=" u "; i=i+1) begin";
		print "\t\t$sformat(tmp, \"." p "[%d]\", i);"
		print "\t\t" (reverse ? emit_from_prsim("tmp") : emit_to_prsim("tmp"));
	print "\tend // end for";
	} else {
		n = enquote_mem_ref(p);
		print "\t" (reverse ? emit_from_prsim(n) : emit_to_prsim(n));
	}
	}
	if (!good) {
		print "Error: unknown port direction for " p;
		exit(1);
	}
}
	print "\tend // end if";
	print "end // end initial";
	print "endmodule";
	print "";
}

# to look like the following:
# module ACT_AND2;
#	wire A1, A2; // need not be reg with acc: wn:*
#	wire Z;
#	parameter prsim_name="";
#	reg [64*8:1] verilog_name;
#	AND2 dummy(A1, A2, Z);
# initial
# begin
# #0	// #0 comes *after* initial!
#	$sformat(verilog_name, "%m");
#	$from_prsim({prsim_name, ".A1"}, {verilog_name, ".A1"});
#	$from_prsim({prsim_name, ".A2"}, {verilog_name, ".A2"});
#	$to_prsim({verilog_name, ".Z"}, {prsim_name, ".Z"});
# end
# endmodule


# for every line
{
	# lex_it
	ntoks = split(tokenize($0), toks);
	for (i=1; i<= ntoks; ++i) {
	if (length(toks[i])) {
		if (debug) {
			parse_debug();
			print "token[:" NR "]: " toks[i];
		}
		parse_it(toks[i]);
	}
	}
}

END {
# if (!error) { }
}
