From 44d224c8a82ede4b4cb20c2b0c131c4e4142fd91 Mon Sep 17 00:00:00 2001 From: Scott Alfter Date: Fri, 8 Aug 2025 14:39:10 -0700 Subject: [PATCH] upduino blinky (testbench not working) --- blinky/Makefile | 20 ++++ blinky/blinky.v | 34 +++++++ blinky/blinky_tb.v | 35 +++++++ blinky/sim.v | 23 +++++ blinky/tb_blinky.v | 30 ++++++ blinky/tbgen.py | 235 ++++++++++++++++++++++++++++++++++++++++++++ blinky/upduino.pcf | 63 ++++++++++++ upduino/apio.ini | 4 + upduino/leds.v | 53 ++++++++++ upduino/main.pcf | 8 ++ upduino/main.v | 30 ++++++ upduino/oscilator.v | 14 +++ 12 files changed, 549 insertions(+) create mode 100644 blinky/Makefile create mode 100644 blinky/blinky.v create mode 100644 blinky/blinky_tb.v create mode 100644 blinky/sim.v create mode 100644 blinky/tb_blinky.v create mode 100644 blinky/tbgen.py create mode 100644 blinky/upduino.pcf create mode 100644 upduino/apio.ini create mode 100644 upduino/leds.v create mode 100644 upduino/main.pcf create mode 100644 upduino/main.v create mode 100644 upduino/oscilator.v diff --git a/blinky/Makefile b/blinky/Makefile new file mode 100644 index 0000000..9196a8b --- /dev/null +++ b/blinky/Makefile @@ -0,0 +1,20 @@ +all: sim synth + +sim: blinky_tb.vcd + +synth: blinky.bin + +blinky_tb.vcd: blinky.v sim.v blinky_tb.v + iverilog -o blinky_tb.out blinky.v blinky_tb.v sim.v + ./blinky_tb.out + gtkwave blinky_tb.vcd & # blinky_tb.gtkw & + +blinky.bin: blinky.v upduino.pcf + yosys -p "synth_ice40 -top blinky -json blinky.json" blinky.v + nextpnr-ice40 --up5k --json blinky.json --pcf upduino.pcf --asc blinky.txt + icepack blinky.txt blinky.bin + +clean: + rm -f *.bin *.txt *.blif *.out *.vcd *~ *.json + +.PHONY: all clean diff --git a/blinky/blinky.v b/blinky/blinky.v new file mode 100644 index 0000000..af5d477 --- /dev/null +++ b/blinky/blinky.v @@ -0,0 +1,34 @@ +// cribbed from https://blog.idorobots.org/entries/upduino-fpga-tutorial.html + +module blinky (output wire led_blue, + output wire led_green, + output wire led_red); + + wire clk; + SB_HFOSC inthosc( + .CLKHFPU(1'b1), // power up + .CLKHFEN(1'b1), // enable + .CLKHF(clk)); // drive the clk signal + + localparam N=23; + reg [N:0] counter; + + always @(posedge clk) + counter <= counter+1; + + SB_RGBA_DRV rgb ( + .RGBLEDEN (1'b1), + .RGB0PWM (counter[N]), + .RGB1PWM (counter[N-1]), + .RGB2PWM (counter[N-2]), + .CURREN (1'b1), + .RGB0 (led_blue), + .RGB1 (led_green), + .RGB2 (led_red) + ); + defparam rgb.CURRENT_MODE = "0b1"; + defparam rgb.RGB0_CURRENT = "0b000001"; + defparam rgb.RGB1_CURRENT = "0b000001"; + defparam rgb.RGB2_CURRENT = "0b000001"; + +endmodule diff --git a/blinky/blinky_tb.v b/blinky/blinky_tb.v new file mode 100644 index 0000000..46250ba --- /dev/null +++ b/blinky/blinky_tb.v @@ -0,0 +1,35 @@ +`timescale 10ns/1ns + +module blinky_tb; + +reg clk; +wire led_blue; +wire led_green; +wire led_red; + +blinky dut ( + .led_blue (led_blue), + .led_green (led_green), + .led_red (led_red) +); + +initial clk=0; +always #2 clk=~clk; + +//-- Begin test +initial begin + + //-- Set the dumpfile + $dumpfile("blinky_tb.vcd"); + + //-- Dump everything into the dumpfile + $dumpvars(0, blinky_tb); + + //-- End after 10 time units + # 100000 $finish; +end + + +endmodule + + diff --git a/blinky/sim.v b/blinky/sim.v new file mode 100644 index 0000000..119d61e --- /dev/null +++ b/blinky/sim.v @@ -0,0 +1,23 @@ +module SB_HFOSC( + input CLKHFPU, + input CLKHFEN, + output CLKHF +); +parameter CLKHF_DIV = "0b00"; +endmodule + +module SB_RGBA_DRV ( + input RGBLEDEN, + input RGB0PWM, + input RGB1PWM, + input RGB2PWM, + input CURREN, + output RGB0, + output RGB1, + output RGB2 +); +parameter CURRENT_MODE="0b1"; +parameter RGB0_CURRENT="0b000001"; +parameter RGB1_CURRENT="0b000001"; +parameter RGB2_CURRENT="0b000001"; +endmodule diff --git a/blinky/tb_blinky.v b/blinky/tb_blinky.v new file mode 100644 index 0000000..1b3bcd5 --- /dev/null +++ b/blinky/tb_blinky.v @@ -0,0 +1,30 @@ +`timescale 1ns/10ps //Adjust to suit + +module tb_blinky; + +wire wire led_blue ; +wire wire led_green ; + +blinky uut ( + .wire led_blue ( wire led_blue ), + .wire led_green ( wire led_green ) +); + +parameter PERIOD = 10; //adjust for your timescale + +initial begin + $dumpfile("tb_output.vcd"); + $dumpvars(2, tb_blinky); + clk = 1'b0; + #(PERIOD/2); + forever + #(PERIOD/2) clk = ~clk; +end + +initial begin + rst=1'b0; + #(PERIOD*2) rst=~rst; + #PERIOD rst=~rst; + end +`include "user.tb_blinky.v" +endmodule diff --git a/blinky/tbgen.py b/blinky/tbgen.py new file mode 100644 index 0000000..77f4920 --- /dev/null +++ b/blinky/tbgen.py @@ -0,0 +1,235 @@ +#! /usr/bin/python + +# THE BEER-WARE LICENSE" (Revision 42): +# wrote this file. As long as you retain this notice you +# can do whatever you want with this stuff. If we meet some day, and you think +# this stuff is worth it, you can buy me a beer in return Xiongfei(Alex) Guo. + +# some minor additions by Al Williams 2018-9-11 + +''' +Created on 2010-4-23 + +@author: Alex Guo +''' + +import re +import sys +import argparse + +class TestbenchGenerator(object): + ''' + verilog test bench auto generation + ''' + + def __init__(self, vfile_name = None, ofile_name = None): + self.vfile_name = vfile_name + self.vfile = None + self.ofile_name = ofile_name + if(ofile_name == None): + self.ofile = sys.stdout + self.vcont = "" + self.mod_name = "" + self.pin_list = [] + self.clock_name = 'clk' + self.reset_name = 'rst' + + if vfile_name == None: + sys.stderr.write("ERROR: You did not provide an input file name.\n") + sys.exit(1) + else: + self.open() + self.parser() + self.open_outputfile() + + def open(self, vfile_name = None): + if vfile_name != None: + self.vfile_name = vfile_name + + try: + self.vfile = open(self.vfile_name, 'r') + self.vcont = self.vfile.read() + except Exception as e: + print("ERROR: Input file error.\n ERROR: %s" % e) + sys.exit(1) + + def open_outputfile(self, ofile_name = None): + try: + if(ofile_name == None): + if(self.ofile_name == None): + ofname = "tb_%s.v" % self.mod_name + self.ofile = open(ofname, 'w') + print("You did not specify an output file name, using '%s'." % ofname) + else: + self.ofile = open(self.ofile_name, 'w') + print("Output file is '%s'." % self.ofile_name) + else: + self.ofile = open(ofile_name, 'w') + print("Output file is '%s'." % ofile_name) + except Exception as e: + print("ERROR: Output file error. \n ERROR: %s" % e) + sys.exit(1) + + def clean_other(self, cont): + ## clean '// ...' + cont = re.sub(r"//[^\n^\r]*", '\n', cont) + ## clean '/* ... */' + cont = re.sub(r"/\*.*\*/", '', cont) + ## clean '`define ..., etc.' + #cont = re.sub(r"[^\n^\r]+`[^\n^\r]*", '\n', cont) + ## clean tables + cont = re.sub(r' +', ' ', cont) + ## clean '\n' * '\r' + #cont = re.sub(r'[\n\r]+', '', cont) + return cont + + def parser(self): + print("Parsing...") + # print vf_cont + mod_pattern = r"module[\s]+(\S*)[\s]*\([^\)]*\)[\s\S]*" + + module_result = re.findall(mod_pattern, self.clean_other(self.vcont)) + #print module_result + self.mod_name = module_result[0] + + self.parser_inoutput() + self.find_clk_rst() + + + def parser_inoutput(self): + pin_list = self.clean_other(self.vcont) + + comp_pin_list_pre = [] + for i in re.findall(r'(input|output|inout)[\s]+([^;,\)]+)[\s]*[;,]', pin_list): + comp_pin_list_pre.append((i[0], re.sub(r"^reg[\s]*", "", i[1]))) + + comp_pin_list = [] + type_name = ['reg', 'wire', 'wire', "ERROR"] + for i in comp_pin_list_pre: + x = re.split(r']', i[1]) + type = 0; + if i[0] == 'input': + type = 0 + elif i[0] == 'output': + type = 1 + elif i[0] == 'inout': + type = 2 + else: + type = 3 + + if len(x) == 2: + x[1] = re.sub('[\s]*', '', x[1]) + comp_pin_list.append((i[0], x[1], x[0] + ']', type_name[type])) + else: + comp_pin_list.append((i[0], x[0], '', type_name[type])) + + self.pin_list = comp_pin_list + # for i in self.pin_list: print i + + def print_dut(self): + max_len = 0 + for cpin_name in self.pin_list: + pin_name = cpin_name[1] + if len(pin_name) > max_len: + max_len = len(pin_name) + + + self.printo( "%s uut (\n" % self.mod_name ) + + align_cont = self.align_print(list(map(lambda x:("", "." + x[1], "(", x[1], '),'), self.pin_list)), 4) + align_cont = align_cont[:-2] + "\n" + self.printo( align_cont ) + + self.printo( ");\n" ) + + def print_wires(self): + self.printo(self.align_print(list(map(lambda x:(x[3], x[2], x[1], ';'), self.pin_list)), 4)) + self.printo("\n") + + def print_clock_gen(self,period,dfile,depth,resetpol): + fsdb = " $dumpfile(\"%s\");\n $dumpvars(%d, tb_%s);\n" % (dfile, depth, self.mod_name) + + clock_gen_text = "\nparameter PERIOD = %d; //adjust for your timescale\n\ninitial begin\n%s CLK = 1'b0;\n #(PERIOD/2);\n forever\n #(PERIOD/2) CLK = ~CLK;\nend\n" % (period, fsdb) + self.printo(re.sub('CLK', self.clock_name, clock_gen_text)) + if self.reset_name!="": + clock_gen_text = "\ninitial begin\n RST=1'b%d;\n #(PERIOD*2) RST=~RST;\n #PERIOD RST=~RST;\n end\n" % resetpol + self.printo(re.sub('RST',self.reset_name, clock_gen_text)) + + def find_clk_rst(self): + for pin in self.pin_list: + if re.match(r'[\S]*(clk|clock)[\S]*', pin[1]): + self.clock_name = pin[1] + print("Clock signal detected: '%s'." % pin[1]) + break + + for pin in self.pin_list: + if re.match(r'rst|reset', pin[1]): + self.reset_name = pin[1] + print("Reset signal detected: '%s'." % pin[1]) + break + +# Original code -- no longer used + def print_module_head_orig(self): + self.printo("`include \"timescale.v\"\nmodule tb_%s;\n\n" % self.mod_name) + + def print_module_head(self,timescale): + self.printo("`timescale %s //Adjust to suit\n\nmodule tb_%s;\n\n" % (timescale,self.mod_name)) + + def print_module_end(self,iname): + if iname is None: + iname='user.tb_%s.v' % self.mod_name + self.printo("`include \"%s\"\nendmodule\n" % iname) + + def printo(self, cont): + self.ofile.write(cont) + + def close(self): + if self.vfile != None: + self.vfile.close() + print("Output complete.\n\n") + + def align_print(self, content, indent): + """ Align pretty print.""" + + row_len = len(content) + col_len = len(content[0]) + align_cont = [""] * row_len + for i in range(col_len): + col = list(map(lambda x:x[i], content)) + max_len = max(list(map(len, col))) + for i in range(row_len): + l = len(col[i]) + align_cont[i] += "%s%s" % (col[i], (indent + max_len - l) * ' ') + + # remove space in line end + align_cont = list(map(lambda s:re.sub('[ ]*$', '', s), align_cont)) + return "\n".join(align_cont) + "\n" + + +if __name__ == "__main__": + print('''***************** tbgen - Auto generate a testbench. ***************** +Author: Xiongfei(Alex) Guo +License: Beerware +''') + ofile_name = None + + aparse = argparse.ArgumentParser(description='Automatically generate Verilog testbench') + aparse.add_argument('input_file', help='input Verilog file') + aparse.add_argument('output_file', help='output Verilog testbench', nargs='?', default=None) + aparse.add_argument('-p','--period', type=int, help='set period in clock ticks (default=10)', default=10) + aparse.add_argument('-t','--timescale',help='set timescale (default=1ns/10ps)', default='1ns/10ps') + aparse.add_argument('-d','--dumpfile',help='set dumpfile (default=tb_output.vcd)', default='tb_output.vcd') + aparse.add_argument('-l','--level', type=int, help='set dump depth level (usually 0,1, or 2; default=2)', default=2) + aparse.add_argument('-r','--resetneg', help='set reset to negative (default positive)', action='store_const', const=1, default=0) + aparse.add_argument('-i','--include', help='sets user include file name (default=user.tb_.v)', default=None) + args = aparse.parse_args() + + tbg = TestbenchGenerator(args.input_file, args.output_file) + + + tbg.print_module_head(args.timescale) + tbg.print_wires() + tbg.print_dut() + tbg.print_clock_gen(args.period,args.dumpfile,args.level,args.resetneg) + tbg.print_module_end(args.include) + tbg.close() diff --git a/blinky/upduino.pcf b/blinky/upduino.pcf new file mode 100644 index 0000000..15ad4e2 --- /dev/null +++ b/blinky/upduino.pcf @@ -0,0 +1,63 @@ +# The LED pins are on dedicated pins and cannot be modified! +# Note that they are negative logic (write a 0 to turn on). +# These are also brought out to the left side of the board. +# Cut the board trace on jumper R28 to disable the onboard 3 color LED. +set_io -nowarn led_green 39 +set_io -nowarn led_red 41 +set_io -nowarn led_blue 40 + +# FTDI chip interface +set_io -nowarn serial_txd 14 # FPGA transmit to USB +set_io -nowarn serial_rxd 15 # FPGA receive from USB +set_io -nowarn spi_cs 16 # Connected to SPI flash, drive high unless using SPI flash! + +# If using the FTDI MPSSE engine, the following signals apply +set_io -nowarn spi_sck 15 # Shared with the flash +set_io -nowarn spi_ssn 16 # Connected to SPI flash, drive high unless using SPI flash! +set_io -nowarn spi_mosi 17 # Shared with the flash +set_io -nowarn spi_miso 14 # Shared with the flash + +# Normal GPIO pins, left side +set_io -nowarn gpio_23 23 +set_io -nowarn gpio_25 25 +set_io -nowarn gpio_26 26 +set_io -nowarn gpio_27 27 +set_io -nowarn gpio_32 32 +set_io -nowarn gpio_35 35 +set_io -nowarn gpio_31 31 +set_io -nowarn gpio_37 37 +set_io -nowarn gpio_34 34 +set_io -nowarn gpio_43 43 +set_io -nowarn gpio_36 36 +set_io -nowarn gpio_42 42 +set_io -nowarn gpio_38 38 +set_io -nowarn gpio_28 28 + +# Normal GPIO pins, right side + +# Following pins are added on the v3.0 of the board. +# SPI pins are brought out to the right side of the board + +# Note: On board 12MHz clock can be brought to IOB_25B_G3 (pin 20) which is a global +# clock input. Short across R16 (labelled OSC on the board) to enable 12MHz clock to +# pin 20. +set_io -nowarn gpio_20 20 +set_io -nowarn gpio_10 10 + +# Following are also found on v2.x of the UPduino +set_io -nowarn gpio_12 12 +set_io -nowarn gpio_21 21 +set_io -nowarn gpio_13 13 +set_io -nowarn gpio_19 19 +set_io -nowarn gpio_18 18 +set_io -nowarn gpio_11 11 +set_io -nowarn gpio_9 9 +set_io -nowarn gpio_6 6 +set_io -nowarn gpio_44 44 +set_io -nowarn gpio_4 4 +set_io -nowarn gpio_3 3 +set_io -nowarn gpio_48 48 +set_io -nowarn gpio_45 45 +set_io -nowarn gpio_47 47 +set_io -nowarn gpio_46 46 +set_io -nowarn gpio_2 2 diff --git a/upduino/apio.ini b/upduino/apio.ini new file mode 100644 index 0000000..139b53f --- /dev/null +++ b/upduino/apio.ini @@ -0,0 +1,4 @@ +[env] +board = upduino31 +top-module = main + diff --git a/upduino/leds.v b/upduino/leds.v new file mode 100644 index 0000000..0a061c4 --- /dev/null +++ b/upduino/leds.v @@ -0,0 +1,53 @@ +// More info about the primitve led control block SB_RGBA_DRV: +// https://github.com/tinyvision-ai-inc/UPduino-v3.0/blob/master/RTL/blink_led/rgb_blink.v +// https://blog.idorobots.org/entries/upduino-fpga-tutorial.html + +module leds ( + input clk, + input red_en, + input green_en, + input blue_en, + output wire [2:0] leds_out // [0]=red, [1]=green, [2]=blue +); + + // Intensity multiplier for all three channels. + localparam integer IntensityAll = 2; + + // Intensity controls in the range [0, 512]. + localparam integer IntensityRed = 4 * IntensityAll; + localparam integer IntensityGreen = 2 * IntensityAll; + localparam integer IntensityBlue = 24 * IntensityAll; + + // Frequency divider generate the PWM counter. + reg [15:0] divider; + + // 9 bits PWM counter. + wire [ 8:0] pwm_counter = divider[15:7]; + + wire pwm_red = pwm_counter < IntensityRed; + wire pwm_green = pwm_counter < IntensityGreen; + wire pwm_blue = pwm_counter < IntensityBlue; + + // Behavior. + always @(posedge clk) begin + divider <= divider + 1'b1; + end + + // Instantiate the RGB current source primitive. + SB_RGBA_DRV #( + .CURRENT_MODE("0b1"), // "0b0" -> full current, "0b1" -> half current. + .RGB0_CURRENT("0b000001"), + .RGB1_CURRENT("0b000001"), + .RGB2_CURRENT("0b000001") + ) RGB_DRIVER ( + .RGBLEDEN(1'b1), + .RGB0PWM (green_en && pwm_green), // Green led input. + .RGB1PWM (blue_en & pwm_blue), // Blue led input. + .RGB2PWM (red_en & pwm_red), // Red led input. + .CURREN (1'b1), + .RGB0 (leds_out[1]), // Current regulated output to green led. + .RGB1 (leds_out[2]), // Current regulated output to blue led. + .RGB2 (leds_out[0]) // Current regulated output to red led. + ); + +endmodule diff --git a/upduino/main.pcf b/upduino/main.pcf new file mode 100644 index 0000000..ce09108 --- /dev/null +++ b/upduino/main.pcf @@ -0,0 +1,8 @@ +# Mapping of the design signals to FPGA pins. + +# The full Upduino3 PCF file is availble at: +# https://github.com/tinyvision-ai-inc/UPduino-v3.0/tree/master/RTL/common + +set_io leds_out[0] 41 # Red. +set_io leds_out[1] 39 # Green. +set_io leds_out[2] 40 # Blue. \ No newline at end of file diff --git a/upduino/main.v b/upduino/main.v new file mode 100644 index 0000000..f9ac5bb --- /dev/null +++ b/upduino/main.v @@ -0,0 +1,30 @@ +// Generates a RGB blinking pattern. Uses the internal oscilator. +module main ( + // [0]=red, [1]=green, [2]=blue. See pin definitions in main.pcf + output [2:0] leds_out +); + + wire clk; + + oscilator oscilator (.clk(clk)); + + // Frequency divider to generate the RGB patterns. + reg [25:0] divider; + wire red_en = divider[25] & divider[24]; + wire green_en = divider[25] & ~divider[24]; + wire blue_en = ~divider[25] & divider[24]; + + leds leds ( + .clk(clk), + .red_en(red_en), + .green_en(green_en), + .blue_en(blue_en), + .leds_out(leds_out) + ); + + // Behavior. + always @(posedge clk) begin + divider <= divider + 1'b1; + end + +endmodule diff --git a/upduino/oscilator.v b/upduino/oscilator.v new file mode 100644 index 0000000..7b29513 --- /dev/null +++ b/upduino/oscilator.v @@ -0,0 +1,14 @@ +// Instanciates the internal high speed oscilator. This is needed +// only if the external Upduino oscilator is not connected to the FPGA. + +module oscilator ( + output clk +); + + SB_HFOSC interal_osc ( + .CLKHFPU(1'b1), + .CLKHFEN(1'b1), + .CLKHF (clk) + ); + +endmodule