mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-11-03 21:09:12 +00:00 
			
		
		
		
	cxxrtl: refactor the formatter and use a closure.
This commit achieves three roughly equally important goals: 1. To bring the rendering code in kernel/fmt.cc and in cxxrtl.h as close together as possible, with an ideal of only having the bigint library as the difference between the render functions. 2. To make the treatment of `$time` and `$realtime` in CXXRTL closer to the Verilog semantics, at least in the formatting code. 3. To change the code generator so that all of the `$print`-to-`string` conversion code is contained inside of a closure. There are two reasons to aim for goal (3): a. Because output redirection through definition of a global ostream object is neither convenient nor useful for environments where the output is consumed by other code rather than being printed on a terminal. b. Because it may be desirable to, in some cases, ignore the `$print` cells that are present in the netlist based on a runtime decision. This is doubly true for an upcoming `$check` cell implementing assertions, since failing a `$check` would by default cause a crash.
This commit is contained in:
		
							parent
							
								
									bf1a99da09
								
							
						
					
					
						commit
						a33acb7cd9
					
				
					 5 changed files with 192 additions and 173 deletions
				
			
		| 
						 | 
				
			
			@ -1072,9 +1072,12 @@ struct CxxrtlWorker {
 | 
			
		|||
		dump_sigspec_rhs(cell->getPort(ID::EN));
 | 
			
		||||
		f << " == value<1>{1u}) {\n";
 | 
			
		||||
		inc_indent();
 | 
			
		||||
			f << indent << print_output;
 | 
			
		||||
			fmt.emit_cxxrtl(f, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); });
 | 
			
		||||
			f << ";\n";
 | 
			
		||||
			f << indent << "auto formatter = [&](int64_t itime, double ftime) {\n";
 | 
			
		||||
			inc_indent();
 | 
			
		||||
				fmt.emit_cxxrtl(f, indent, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); });
 | 
			
		||||
			dec_indent();
 | 
			
		||||
			f << indent << "};\n";
 | 
			
		||||
			f << indent << print_output << " << formatter(steps, steps);\n";
 | 
			
		||||
		dec_indent();
 | 
			
		||||
		f << indent << "}\n";
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -565,7 +565,7 @@ struct value : public expr_base<value<Bits>> {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	value<Bits> neg() const {
 | 
			
		||||
		return value<Bits> { 0u }.sub(*this);
 | 
			
		||||
		return value<Bits>().sub(*this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool ucmp(const value<Bits> &other) const {
 | 
			
		||||
| 
						 | 
				
			
			@ -763,102 +763,134 @@ std::ostream &operator<<(std::ostream &os, const value<Bits> &val) {
 | 
			
		|||
	return os;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<size_t Bits>
 | 
			
		||||
struct value_formatted {
 | 
			
		||||
	const value<Bits> &val;
 | 
			
		||||
	bool character;
 | 
			
		||||
	bool justify_left;
 | 
			
		||||
	char padding;
 | 
			
		||||
	int width;
 | 
			
		||||
	int base;
 | 
			
		||||
	bool signed_;
 | 
			
		||||
	bool plus;
 | 
			
		||||
// Must be kept in sync with `struct FmtPart` in kernel/fmt.h!
 | 
			
		||||
// Default member initializers would make this a non-aggregate-type in C++11, so they are commented out.
 | 
			
		||||
struct fmt_part {
 | 
			
		||||
	enum {
 | 
			
		||||
		STRING  	= 0,
 | 
			
		||||
		INTEGER 	= 1,
 | 
			
		||||
		CHARACTER = 2,
 | 
			
		||||
		TIME    	= 3,
 | 
			
		||||
	} type;
 | 
			
		||||
 | 
			
		||||
	value_formatted(const value<Bits> &val, bool character, bool justify_left, char padding, int width, int base, bool signed_, bool plus) :
 | 
			
		||||
		val(val), character(character), justify_left(justify_left), padding(padding), width(width), base(base), signed_(signed_), plus(plus) {}
 | 
			
		||||
	value_formatted(const value_formatted<Bits> &) = delete;
 | 
			
		||||
	value_formatted<Bits> &operator=(const value_formatted<Bits> &rhs) = delete;
 | 
			
		||||
	// STRING type
 | 
			
		||||
	std::string str;
 | 
			
		||||
 | 
			
		||||
	// INTEGER/CHARACTER types
 | 
			
		||||
	// + value<Bits> val;
 | 
			
		||||
 | 
			
		||||
	// INTEGER/CHARACTER/TIME types
 | 
			
		||||
	enum {
 | 
			
		||||
		RIGHT	= 0,
 | 
			
		||||
		LEFT	= 1,
 | 
			
		||||
	} justify; // = RIGHT;
 | 
			
		||||
	char padding; // = '\0';
 | 
			
		||||
	size_t width; // = 0;
 | 
			
		||||
 | 
			
		||||
	// INTEGER type
 | 
			
		||||
	unsigned base; // = 10;
 | 
			
		||||
	bool signed_; // = false;
 | 
			
		||||
	bool plus; // = false;
 | 
			
		||||
 | 
			
		||||
	// TIME type
 | 
			
		||||
	bool realtime; // = false;
 | 
			
		||||
	// + int64_t itime;
 | 
			
		||||
	// + double ftime;
 | 
			
		||||
 | 
			
		||||
	// Format the part as a string.
 | 
			
		||||
	//
 | 
			
		||||
	// The values of `itime` and `ftime` are used for `$time` and `$realtime`, correspondingly.
 | 
			
		||||
	template<size_t Bits>
 | 
			
		||||
	std::string render(value<Bits> val, int64_t itime, double ftime)
 | 
			
		||||
	{
 | 
			
		||||
		// We might want to replace some of these bit() calls with direct
 | 
			
		||||
		// chunk access if it turns out to be slow enough to matter.
 | 
			
		||||
		std::string buf;
 | 
			
		||||
		switch (type) {
 | 
			
		||||
			case STRING:
 | 
			
		||||
				return str;
 | 
			
		||||
 | 
			
		||||
			case CHARACTER: {
 | 
			
		||||
				buf.reserve(Bits/8);
 | 
			
		||||
				for (int i = 0; i < Bits; i += 8) {
 | 
			
		||||
					char ch = 0;
 | 
			
		||||
					for (int j = 0; j < 8 && i + j < int(Bits); j++)
 | 
			
		||||
						if (val.bit(i + j))
 | 
			
		||||
							ch |= 1 << j;
 | 
			
		||||
					if (ch != 0)
 | 
			
		||||
						buf.append({ch});
 | 
			
		||||
				}
 | 
			
		||||
				std::reverse(buf.begin(), buf.end());
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			case INTEGER: {
 | 
			
		||||
				size_t width = Bits;
 | 
			
		||||
				if (base != 10) {
 | 
			
		||||
					width = 0;
 | 
			
		||||
					for (size_t index = 0; index < Bits; index++)
 | 
			
		||||
						if (val.bit(index))
 | 
			
		||||
							width = index + 1;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (base == 2) {
 | 
			
		||||
					for (size_t i = width; i > 0; i--)
 | 
			
		||||
						buf += (val.bit(i - 1) ? '1' : '0');
 | 
			
		||||
				} else if (base == 8 || base == 16) {
 | 
			
		||||
					size_t step = (base == 16) ? 4 : 3;
 | 
			
		||||
					for (size_t index = 0; index < width; index += step) {
 | 
			
		||||
						uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2);
 | 
			
		||||
						if (step == 4)
 | 
			
		||||
							value |= val.bit(index + 3) << 3;
 | 
			
		||||
						buf += "0123456789abcdef"[value];
 | 
			
		||||
					}
 | 
			
		||||
					std::reverse(buf.begin(), buf.end());
 | 
			
		||||
				} else if (base == 10) {
 | 
			
		||||
					bool negative = signed_ && val.is_neg();
 | 
			
		||||
					if (negative)
 | 
			
		||||
						val = val.neg();
 | 
			
		||||
					if (val.is_zero())
 | 
			
		||||
						buf += '0';
 | 
			
		||||
					value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>();
 | 
			
		||||
					while (!xval.is_zero()) {
 | 
			
		||||
						value<(Bits > 4 ? Bits : 4)> quotient, remainder;
 | 
			
		||||
						if (Bits >= 4)
 | 
			
		||||
							std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u});
 | 
			
		||||
						else
 | 
			
		||||
							std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval);
 | 
			
		||||
						buf += '0' + remainder.template trunc<4>().template get<uint8_t>();
 | 
			
		||||
						xval = quotient;
 | 
			
		||||
					}
 | 
			
		||||
					if (negative || plus)
 | 
			
		||||
						buf += negative ? '-' : '+';
 | 
			
		||||
					std::reverse(buf.begin(), buf.end());
 | 
			
		||||
				} else assert(false && "Unsupported base for fmt_part");
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			case TIME: {
 | 
			
		||||
				buf = realtime ? std::to_string(ftime) : std::to_string(itime);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::string str;
 | 
			
		||||
		assert(width == 0 || padding != '\0');
 | 
			
		||||
		if (justify == RIGHT && buf.size() < width) {
 | 
			
		||||
			size_t pad_width = width - buf.size();
 | 
			
		||||
			if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) {
 | 
			
		||||
				str += buf.front();
 | 
			
		||||
				buf.erase(0, 1);
 | 
			
		||||
			}
 | 
			
		||||
			str += std::string(pad_width, padding);
 | 
			
		||||
		}
 | 
			
		||||
		str += buf;
 | 
			
		||||
		if (justify == LEFT && buf.size() < width)
 | 
			
		||||
			str += std::string(width - buf.size(), padding);
 | 
			
		||||
		return str;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<size_t Bits>
 | 
			
		||||
std::ostream &operator<<(std::ostream &os, const value_formatted<Bits> &vf)
 | 
			
		||||
{
 | 
			
		||||
	value<Bits> val = vf.val;
 | 
			
		||||
 | 
			
		||||
	std::string buf;
 | 
			
		||||
 | 
			
		||||
	// We might want to replace some of these bit() calls with direct
 | 
			
		||||
	// chunk access if it turns out to be slow enough to matter.
 | 
			
		||||
 | 
			
		||||
	if (!vf.character) {
 | 
			
		||||
		size_t width = Bits;
 | 
			
		||||
		if (vf.base != 10) {
 | 
			
		||||
			width = 0;
 | 
			
		||||
			for (size_t index = 0; index < Bits; index++)
 | 
			
		||||
				if (val.bit(index))
 | 
			
		||||
					width = index + 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (vf.base == 2) {
 | 
			
		||||
			for (size_t i = width; i > 0; i--)
 | 
			
		||||
				buf += (val.bit(i - 1) ? '1' : '0');
 | 
			
		||||
		} else if (vf.base == 8 || vf.base == 16) {
 | 
			
		||||
			size_t step = (vf.base == 16) ? 4 : 3;
 | 
			
		||||
			for (size_t index = 0; index < width; index += step) {
 | 
			
		||||
				uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2);
 | 
			
		||||
				if (step == 4)
 | 
			
		||||
					value |= val.bit(index + 3) << 3;
 | 
			
		||||
				buf += "0123456789abcdef"[value];
 | 
			
		||||
			}
 | 
			
		||||
			std::reverse(buf.begin(), buf.end());
 | 
			
		||||
		} else if (vf.base == 10) {
 | 
			
		||||
			bool negative = vf.signed_ && val.is_neg();
 | 
			
		||||
			if (negative)
 | 
			
		||||
				val = val.neg();
 | 
			
		||||
			if (val.is_zero())
 | 
			
		||||
				buf += '0';
 | 
			
		||||
			while (!val.is_zero()) {
 | 
			
		||||
				value<Bits> quotient, remainder;
 | 
			
		||||
				if (Bits >= 4)
 | 
			
		||||
					std::tie(quotient, remainder) = val.udivmod(value<Bits>{10u});
 | 
			
		||||
				else
 | 
			
		||||
					std::tie(quotient, remainder) = std::make_pair(value<Bits>{0u}, val);
 | 
			
		||||
				buf += '0' + remainder.template trunc<(Bits > 4 ? 4 : Bits)>().val().template get<uint8_t>();
 | 
			
		||||
				val = quotient;
 | 
			
		||||
			}
 | 
			
		||||
			if (negative || vf.plus)
 | 
			
		||||
				buf += negative ? '-' : '+';
 | 
			
		||||
			std::reverse(buf.begin(), buf.end());
 | 
			
		||||
		} else assert(false);
 | 
			
		||||
	} else {
 | 
			
		||||
		buf.reserve(Bits/8);
 | 
			
		||||
		for (int i = 0; i < Bits; i += 8) {
 | 
			
		||||
			char ch = 0;
 | 
			
		||||
			for (int j = 0; j < 8 && i + j < int(Bits); j++)
 | 
			
		||||
				if (val.bit(i + j))
 | 
			
		||||
					ch |= 1 << j;
 | 
			
		||||
			if (ch != 0)
 | 
			
		||||
				buf.append({ch});
 | 
			
		||||
		}
 | 
			
		||||
		std::reverse(buf.begin(), buf.end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert(vf.width == 0 || vf.padding != '\0');
 | 
			
		||||
	if (!vf.justify_left && buf.size() < vf.width) {
 | 
			
		||||
		size_t pad_width = vf.width - buf.size();
 | 
			
		||||
		if (vf.padding == '0' && (buf.front() == '+' || buf.front() == '-')) {
 | 
			
		||||
			os << buf.front();
 | 
			
		||||
			buf.erase(0, 1);
 | 
			
		||||
		}
 | 
			
		||||
		os << std::string(pad_width, vf.padding);
 | 
			
		||||
	}
 | 
			
		||||
	os << buf;
 | 
			
		||||
	if (vf.justify_left && buf.size() < vf.width)
 | 
			
		||||
		os << std::string(vf.width - buf.size(), vf.padding);
 | 
			
		||||
 | 
			
		||||
	return os;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// An object that can be passed to a `commit()` method in order to produce a replay log of every state change in
 | 
			
		||||
// the simulation.
 | 
			
		||||
struct observer {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue