#!/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);
	}
	print "// vi: syntax=verilog";	# :)
	print "// This file is automatically generated by wrap_verilog_modules_to_hacprsim.awk.";
	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
function parse_expect(st) {
	if (parse_stack[parse_stack_ptr] != st) {
		parse_error("expected to be in state \"" st \
			"\" but got \"" parse_stack[parse_stack_ptr] "\".");
	}
}

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

# 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 = "";
	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];
	}
}

function set_port_dir(tok, dir) {
if (tok == "[") {
	parse_push("expect-range-first");
} else {
	if (tok != ";") {
	# permit declarations like "output reg ..."
	if (tok != "," && tok != "reg") {
		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 ignore
	} else {
		parse_pop();
	}
}
}

# where the parsing happens...
function parse_it(tok, 
	# local vars
	state) {
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") {
	if (tok != ")") {
	if (tok != ",") {
		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 == "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 == "endmodule") {
		parse_pop();
		parse_expect("module");
		parse_pop();
		write_out_wrapper();
		parse_expect("top");
	}
} 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 == "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-to-semicolon") {
	if (tok == ";") {
		parse_pop();
	} # else ignore
} else if (state == "ignore-to-end") {
	if (tok == "end") {
		parse_pop();
	} # else ignore
} else if (state == "module-finished") {
	
}
}

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

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

# emit wrapper function
# no parameters, variables are only local
function write_out_wrapper(tmp, u, l, n) {
	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<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 "\treg [" max_strlen "*8:1] verilog_name;";
if (!reverse) {
	print "\t" type_name " 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));
	}
	} else {
		print "\t" (reverse ? emit_to_prsim(p) : emit_from_prsim(p));
	}
	}
	if (match(type, "out")) {
		good = 1;
	if (length(u) && length(l)) {
	for ( ; l<=u; ++l) {
		n = p "[" l "]";
		print "\t" (reverse ? emit_from_prsim(n) : emit_to_prsim(n));
	}
	} else {
		print "\t" (reverse ? emit_from_prsim(p) : emit_to_prsim(p));
	}
	}
	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])) {
		parse_it(toks[i]);
	}
	}
}

END {
# if (!error) { }
}
