~shiny/gbcap

140b2a3d0e7b39f0c473838a5bd27d80ddaa61f7 — Thomas Spurden 3 years ago 32ec80f
Simplify verilator testbenches

gbcap testbench is pretty much the same as the compress testbench just
with FIFO+UART output, so drop the compress tb - removes a lot of
copy-paste of code.

Testbench now reads and writes binary rather than hex encoded strings,
simplified reading of lines.
7 files changed, 54 insertions(+), 181 deletions(-)

M compress.py
D compress_tb_wrap.cpp
M dump_pgm.py
M gbcap.py
M gbcap_tb_wrap.cpp
D tb_wrap_utils.h
D utils.py
M compress.py => compress.py +0 -9
@@ 433,14 433,5 @@ if __name__ == '__main__':
        for i, l in enumerate(test_lines):
            print('{:02d} {:080x}'.format(i, l))
        run_simulation(dut, [lines_in(dut, test_lines), lines_out(dut, test_lines)], vcd_name='out.vcd')
    elif sys.argv[1] == 'verilator':
        import utils
        from migen.fhdl.verilog import convert
        tb = CompressFrames(160, 144, 1024)
        c = convert(tb, ios={
            tb.input.data, tb.input.ready, tb.input.valid,
            tb.output.data, tb.output.ready, tb.output.valid})
        utils.update_verilog_if_changed('compress_tb.v', c)
        utils.verilate(['compress_tb.v', 'compress_tb_wrap.cpp'], output='compress_tb')
    else:
        raise ValueError(sys.argv[1])

D compress_tb_wrap.cpp => compress_tb_wrap.cpp +0 -92
@@ 1,92 0,0 @@
#include "Vcompress_tb.h"
#include "verilated.h"
#include "verilated_vcd_c.h"
#include "tb_wrap_utils.h"

#include <stdio.h>

int main(int argc, char **argv)
{
	Verilated::commandArgs(argc, argv);
	Vcompress_tb* top = new Vcompress_tb;

	Verilated::traceEverOn(true);
	VerilatedVcdC* trace = new VerilatedVcdC;
	top->trace(trace, 99);

	trace->open("compress_tb.vcd");

	top->sys_rst = 1;
	for(unsigned i = 0; i != 10; i += 1) {
		top->sys_clk = i & 1;
		top->eval();
	}
	top->sys_rst = 0;

	top->output_ready = 1;
	uint32_t last_line = UINT32_MAX;
	uint32_t last_out = 0;
	uint32_t sys_clk = 0;
	bool no_more_lines = false;
	while(!Verilated::gotFinish()) {
		/* GB LCD runs at ~4.19MHz, each scanline takes 456 cycles. FPGA clock
		 * is 25MHz, so divide by 6 to get 4MHz.
		 */
		uint32_t line_idx = sys_clk / (6 * 456);
		/* GB LCD frame is 144 lines high, with 10 lines worth of vblank at the
		 * end of each frame.
		 */
		uint32_t line_in_frame = line_idx % 154;

		if(!no_more_lines && line_idx != last_line) {
			last_line = line_idx;
			fprintf(stderr, "input: line %d %d (%u)\n", line_idx, line_in_frame, sys_clk);
			if(line_in_frame < 144) {
				if(!read_line(top->input_data)) {
					no_more_lines = true;
					fprintf(stderr, "**EOF\n");
				}
				else {
					if(!top->input_ready) {
						fprintf(stderr, "Overflow\n");
						std::exit(1);
					}
					top->input_valid = 1;
				}
			}
		}

		/* output UART is running at 2MBaud, 9 bits per output byte
		 * = sys_clk / 12.5 / 9 */
		uint32_t uart_byte = sys_clk / 113;

		if(last_out != uart_byte) {
			top->output_ready = 1;
		}

		/* Falling edge */
		top->sys_clk = 0;
		top->eval();
		trace->dump(sys_clk << 1);
		/* Rising edge */
		top->sys_clk = 1;
		top->eval();
		trace->dump((sys_clk << 1) + 1);

		if(top->input_valid && top->input_ready) {
			top->input_valid = 0;
		}

		if(top->output_valid && top->output_ready) {
			printf("%02x\n", top->output_data);
			fflush(stdout);
			last_out = uart_byte;
			top->output_ready = 0;
		}
		sys_clk += 1;
	}

	trace->close();

	delete top;
}

M dump_pgm.py => dump_pgm.py +7 -1
@@ 1,10 1,12 @@
#!/usr/bin/env python3
import sys
import compress_ref

if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(description='Dump image(s) as a newline separated stream of packed lines (hex encoded)')
    parser.add_argument('--text', '-t', help='Dump as newline separated hex-encoded lines rather than raw binary')
    parser.add_argument('pgms', nargs='+', help='Images to dump')

    args = parser.parse_args()


@@ 12,4 14,8 @@ if __name__ == '__main__':
    for filename in args.pgms:
        with open(filename) as f:
            for lv in compress_ref.pixels_to_lines(compress_ref.read_pgm(f), 160):
                print('{:080X}'.format(lv))
                if args.text:
                    print('{:080X}'.format(lv))
                else:
                    sys.stdout.buffer.write(lv.to_bytes(40, byteorder='little'))
                    sys.stdout.buffer.flush()

M gbcap.py => gbcap.py +24 -3
@@ 286,8 286,29 @@ if __name__ == '__main__':
        run_simulation(dut, [tx(dut), rx(dut), check_comp_out(dut)], vcd_name='out.vcd')

    elif sys.argv[1] == 'verilator':
        import utils
        import subprocess
        import shutil
        import os
        from migen.fhdl.verilog import convert

        def update_verilog_if_changed(path, cvt):
            assert not cvt.data_files
            current = None
            if os.path.exists(path):
                with open(path, 'r') as f:
                    current = f.read()
            if current != cvt.main_source:
                with open(path, 'w') as f:
                    f.write(cvt.main_source)

        def verilate(files, output=None):
            cmd = ['verilator', '-Wno-COMBDLY', '-Wno-fatal', '-cc', '-CFLAGS', '-ggdb', '--trace', '--exe', '--build']
            if output is not None:
                cmd += ['-o', output]
            cmd += files
            res = subprocess.run(cmd)
            res.check_returncode()

        class TB(Module):
            def __init__(self):
                self.serial_pads = Record([('tx', 1), ('rx', 1)])


@@ 311,5 332,5 @@ if __name__ == '__main__':
            tb.compress.error,
        }
        c = convert(tb, ios=ios)
        utils.update_verilog_if_changed('gbcap_tb.v', c)
        utils.verilate(['gbcap_tb.v', 'gbcap_tb_wrap.cpp'], output='gbcap_tb')
        update_verilog_if_changed('gbcap_tb.v', c)
        verilate(['gbcap_tb.v', 'gbcap_tb_wrap.cpp'], output='gbcap_tb')

M gbcap_tb_wrap.cpp => gbcap_tb_wrap.cpp +23 -3
@@ 1,10 1,25 @@
#include "Vgbcap_tb.h"
#include "verilated.h"
#include "verilated_vcd_c.h"
#include "tb_wrap_utils.h"

#include <stdio.h>

static bool read_line(FILE* f, uint32_t* buf)
{
	uint8_t bytes[40];
	size_t n = fread(bytes, 1, 40, f);
	if(n != 40) {
		return false;
	}
	for(unsigned w = 0; w != 10; w += 1) {
		buf[w] = 0;
		for(unsigned b = 0; b != 4; b += 1) {
			buf[w] |= bytes[(4*w) + b] << (8 * b);
		}
	}
	return true;
}

int main(int argc, char **argv)
{
	Verilated::commandArgs(argc, argv);


@@ 43,7 58,7 @@ int main(int argc, char **argv)
			last_line = line_idx;
			fprintf(stderr, "input: line %d %d (%u)\n", line_idx, line_in_frame, sys_clk);
			if(line_in_frame < 144) {
				if(!read_line(top->line_pads_data)) {
				if(!read_line(stdin, top->line_pads_data)) {
					no_more_lines = true;
					fprintf(stderr, "**EOF\n");
				}


@@ 67,8 82,13 @@ int main(int argc, char **argv)
		}

		if(top->valid_1) {
			printf("%02x\n", top->data_1);
			fwrite(&top->data_1, 1, 1, stdout);
			fflush(stdout);
			if(no_more_lines) {
				/* Make it clear something is still happening */
				fprintf(stderr, ".");
				fflush(stderr);
			}
		}

		if(top->valid) {

D tb_wrap_utils.h => tb_wrap_utils.h +0 -52
@@ 1,52 0,0 @@
#include <stdio.h>
#include <assert.h>

static inline unsigned fromhex(char const s[2])
{
	unsigned accum = 0;
	for(unsigned i = 0; i != 2; i += 1) {
		unsigned n = 0;
		if(s[i] >= '0' && s[i] <= '9') {
			n = s[i] - '0';
		}
		else if(s[i] >= 'a' && s[i] <= 'z') {
			n = s[i] - 'a' + 10;
		}
		else if(s[i] >= 'A' && s[i] <= 'Z') {
			n = s[i] - 'A' + 10;
		}
		else {
			assert(false);
		}
		accum += n << ((1 - i) * 4);
	}
	return accum;
}

static inline bool read_line(uint32_t* line)
{
	char hex[2];
	for(unsigned i = 0; i != 10; i += 1) {
		uint32_t word = 0;
		for(unsigned w = 0; w != 4; w += 1) {
			if(fread(hex, 1, 2, stdin) != 2) {
				return false;
			}
			unsigned v = fromhex(hex);
			word |= v << ((3 - w) * 8);
		}
		line[9 - i] = word;
	}
	/* Allow last line to omit \n */
	if(fread(hex, 1, 1, stdin) == 1 && hex[0] != '\n') {
		return false;
	}

	return true;
}

static unsigned get_pixel(uint32_t const* line, unsigned idx)
{
	assert(idx < 160);
	return (line[idx / 16] >> ((idx % 16) * 2)) & 3u;
}

D utils.py => utils.py +0 -21
@@ 1,21 0,0 @@
import subprocess
import shutil
import os

def update_verilog_if_changed(path, cvt):
    assert not cvt.data_files
    current = None
    if os.path.exists(path):
        with open(path, 'r') as f:
            current = f.read()
    if current != cvt.main_source:
        with open(path, 'w') as f:
            f.write(cvt.main_source)

def verilate(files, output=None):
    cmd = ['verilator', '-Wno-COMBDLY', '-Wno-fatal', '-cc', '-CFLAGS', '-ggdb', '--trace', '--exe', '--build']
    if output is not None:
        cmd += ['-o', output]
    cmd += files
    res = subprocess.run(cmd)
    res.check_returncode()