From 25ad7a24b96b021c6ab25e275059e20187b4f882 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 14 Nov 2017 14:06:38 +0000 Subject: 5k RGB driver reverse engineered --- docs/ultraplus.html | 28 ++++ icefuzz/tests/rgba_drv_cbit/.gitignore | 1 + icefuzz/tests/rgba_drv_cbit/fuzz_rgba_drv_cbit.py | 174 +++++++++++++++++++++ icefuzz/tests/rgba_drv_cbit/rgba_drv_data_up5k.txt | 20 +++ 4 files changed, 223 insertions(+) create mode 100644 icefuzz/tests/rgba_drv_cbit/.gitignore create mode 100755 icefuzz/tests/rgba_drv_cbit/fuzz_rgba_drv_cbit.py create mode 100644 icefuzz/tests/rgba_drv_cbit/rgba_drv_data_up5k.txt diff --git a/docs/ultraplus.html b/docs/ultraplus.html index 1074862..1b9e950 100644 --- a/docs/ultraplus.html +++ b/docs/ultraplus.html @@ -217,4 +217,32 @@ The CLKLF output of SB_LFOSC is conne SPRAM_ENABLE(0, 1, CBIT_0)(0, 1, CBIT_1)(25, 1, CBIT_0)(25, 1, CBIT_1) +

RGB LED Driver

+

The UltraPlus devices contain an internal 3-channel 2-24mA constant-current driver intended for RGB led driving (SB_RGBA_DRV). It is broken out onto 3 pins: 39, 40 and 41 on the QFN48 package. +The LED driver is implemented using the IPConnect tiles and is entirely seperate to the IO cells, if the LED driver is ignored or disabled on a pin then the pin +can be used as an open-drain IO using the standard IO cell.

+

Note that the UltraPlus devices also have a seperate PWM generator IP core, which would often be connected to this one to create LED effects such as "breathing" without + involving FPGA resources.

+

The LED driver connections are shown in the label below.

+ + + + + + + +
SignalNet
CURREN(25, 29, lutff_6/in_3)
RGBLEDEN(0, 30, lutff_1/in_1)
RGB0PWM(0, 30, lutff_2/in_1)
RGB1PWM(0, 30, lutff_3/in_1)
RGB2PWM(0, 30, lutff_4/in_1)
+

The configuration bits are as follows. As well as the documented bits, another bit "RGBA_DRV_EN" is set if any of the channels are enabled.

+ + + + + + + + + + + +
ParameterBit
RGBA_DRV_EN(0, 28, CBIT_5)
RGB0_CURRENT[1:0](0, 28, CBIT_[7:6])
RGB0_CURRENT[5:2](0, 29, CBIT_[3:0])
RGB1_CURRENT[3:0](0, 29, CBIT_[7:4])
RGB1_CURRENT[5:4](0, 30, CBIT_[1:0])
RGB2_CURRENT[5:0](0, 30, CBIT_[7:2])
CURRENT_MODE(0, 28, CBIT_4)
diff --git a/icefuzz/tests/rgba_drv_cbit/.gitignore b/icefuzz/tests/rgba_drv_cbit/.gitignore new file mode 100644 index 0000000..68b6394 --- /dev/null +++ b/icefuzz/tests/rgba_drv_cbit/.gitignore @@ -0,0 +1 @@ +work_rgba_drv/ \ No newline at end of file diff --git a/icefuzz/tests/rgba_drv_cbit/fuzz_rgba_drv_cbit.py b/icefuzz/tests/rgba_drv_cbit/fuzz_rgba_drv_cbit.py new file mode 100755 index 0000000..a7e2006 --- /dev/null +++ b/icefuzz/tests/rgba_drv_cbit/fuzz_rgba_drv_cbit.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 + +import os, sys +# SB_RGBA_DRV automatic fuzzing script + +device = "up5k" + +# SB_RGBA_DRV config bits to be fuzzed +# These must be in an order such that a config with bit i set doesn't set any other undiscovered bits yet + + +fuzz_bits = [ + "RGB0_CURRENT_0", + "RGB0_CURRENT_1", + "RGB0_CURRENT_2", + "RGB0_CURRENT_3", + "RGB0_CURRENT_4", + "RGB0_CURRENT_5", + + "RGB1_CURRENT_0", + "RGB1_CURRENT_1", + "RGB1_CURRENT_2", + "RGB1_CURRENT_3", + "RGB1_CURRENT_4", + "RGB1_CURRENT_5", + + "RGB2_CURRENT_0", + "RGB2_CURRENT_1", + "RGB2_CURRENT_2", + "RGB2_CURRENT_3", + "RGB2_CURRENT_4", + "RGB2_CURRENT_5", + + "CURRENT_MODE" +] + + +# Boilerplate code based on the icefuzz script +code_prefix = """ +module top( + input curren, + input rgbleden, + input r_in, + input g_in, + input b_in, + output r_led, + output g_led, + output b_led); +""" + +def get_param_value(param_name, param_size, fuzz_bit): + param = "\"0b"; + #In the RGB driver, once bit i of a current parameter is set i-1..0 must also be set + is_high = False + for i in range(param_size - 1, -1, -1): + if fuzz_bit == param_name + "_" + str(i) or (i == 0 and fuzz_bit == "CURRENT_MODE") or is_high: + param += '1' + is_high = True + else: + param += '0' + param += "\"" + return param +def inst_rgba(fuzz_bit): + v = "" + v += """SB_RGBA_DRV rgba_inst ( + .CURREN(curren), + .RGBLEDEN(rgbleden), + .RGB0PWM(r_in), + .RGB1PWM(g_in), + .RGB2PWM(b_in), + .RGB0(r_led), + .RGB1(g_led), + .RGB2(b_led) + );""" + + v += "defparam rgba_inst.CURRENT_MODE = " + get_param_value("CURRENT_MODE", 1, fuzz_bit) + ";\n" + v += "defparam rgba_inst.RGB0_CURRENT = " + get_param_value("RGB0_CURRENT", 6, fuzz_bit) + ";\n" + v += "defparam rgba_inst.RGB1_CURRENT = " + get_param_value("RGB1_CURRENT", 6, fuzz_bit) + ";\n" + v += "defparam rgba_inst.RGB2_CURRENT = " + get_param_value("RGB2_CURRENT", 6, fuzz_bit) + ";\n" + + return v; + +def make_vlog(fuzz_bit): + vlog = code_prefix + vlog += inst_rgba(fuzz_bit) + vlog += "endmodule" + return vlog + +known_bits = [] + +# Set to true to continue even if multiple bits are changed (needed because +# of the issue discusssed below) +show_all_bits = False #TODO: make this an argument + +device = "up5k" #TODO: environment variable? + +#HACK: icecube doesn't let you set all of the config bits to 0, +#which makes fuzzing early on annoying as there is never a case +#with just 1 bit set. So a tiny bit of semi-manual work is needed +#first to discover this (basically run this script with show_all_bits=True +#and look for the stuck bit) +#TODO: clever code could get rid of this +rgba_drv_en_bit = { + "up5k" : (0, 28, 5) +} + +#Return a list of RGBA_DRIVER config bits in the format (x, y, bit) +def parse_exp(expfile): + current_x = 0 + current_y = 0 + bits = [] + with open(expfile, 'r') as f: + for line in f: + splitline = line.split(' ') + if splitline[0].endswith("_tile"): + current_x = int(splitline[1]) + current_y = int(splitline[2]) + elif splitline[0] == "IpConfig": + if splitline[1][:5] == "CBIT_": + bitidx = int(splitline[1][5:]) + bits.append((current_x, current_y, bitidx)) + return bits + +#Convert a bit tuple as returned from the above to a nice string +def bit_to_str(bit): + return "(%d, %d, \"CBIT_%d\")" % bit + +#The main fuzzing function +def do_fuzz(): + if not os.path.exists("./work_rgba_drv"): + os.mkdir("./work_rgba_drv") + known_bits.append(rgba_drv_en_bit[device]) + with open("rgba_drv_data_" + device + ".txt", 'w') as dat: + for fuzz_bit in fuzz_bits: + vlog = make_vlog(fuzz_bit) + with open("./work_rgba_drv/rgba_drv.v", 'w') as f: + f.write(vlog) + with open("./work_rgba_drv/rgba_drv.pcf", 'w') as f: + f.write(""" +set_io r_led 39 +set_io g_led 40 +set_io b_led 41 + """) + retval = os.system("bash ../../icecube.sh -" + device + " ./work_rgba_drv/rgba_drv.v > ./work_rgba_drv/icecube.log 2>&1") + if retval != 0: + sys.stderr.write('ERROR: icecube returned non-zero error code\n') + sys.exit(1) + retval = os.system("../../../icebox/icebox_explain.py ./work_rgba_drv/rgba_drv.asc > ./work_rgba_drv/rgba_drv.exp") + if retval != 0: + sys.stderr.write('ERROR: icebox_explain returned non-zero error code\n') + sys.exit(1) + rgba_bits = parse_exp("./work_rgba_drv/rgba_drv.exp") + new_bits = [] + for set_bit in rgba_bits: + if not (set_bit in known_bits): + new_bits.append(set_bit) + if len(new_bits) == 0: + sys.stderr.write('ERROR: no new bits set when setting config bit ' + fuzz_bit + '\n') + sys.exit(1) + if len(new_bits) > 1: + sys.stderr.write('ERROR: multiple new bits set when setting config bit ' + fuzz_bit + '\n') + for bit in new_bits: + sys.stderr.write('\t' + bit_to_str(bit) + '\n') + if not show_all_bits: + sys.exit(1) + if len(new_bits) == 1: + known_bits.append(new_bits[0]) + #print DIVQ_0 at the right moment, as it's not fuzzed normally + if fuzz_bit == "RGB0_CURRENT_0": + print(("\"RGBA_DRV_EN\":").ljust(24) + bit_to_str(rgba_drv_en_bit[device]) + ",") + dat.write(("\"RGBA_DRV_EN\":").ljust(24) + bit_to_str(rgba_drv_en_bit[device]) + ",\n") + print(("\"" + fuzz_bit + "\":").ljust(24) + bit_to_str(new_bits[0]) + ",") + dat.write(("\"" + fuzz_bit + "\":").ljust(24) + bit_to_str(new_bits[0]) + ",\n") +do_fuzz() \ No newline at end of file diff --git a/icefuzz/tests/rgba_drv_cbit/rgba_drv_data_up5k.txt b/icefuzz/tests/rgba_drv_cbit/rgba_drv_data_up5k.txt new file mode 100644 index 0000000..73bfb90 --- /dev/null +++ b/icefuzz/tests/rgba_drv_cbit/rgba_drv_data_up5k.txt @@ -0,0 +1,20 @@ +"RGBA_DRV_EN": (0, 28, "CBIT_5"), +"RGB0_CURRENT_0": (0, 28, "CBIT_6"), +"RGB0_CURRENT_1": (0, 28, "CBIT_7"), +"RGB0_CURRENT_2": (0, 29, "CBIT_0"), +"RGB0_CURRENT_3": (0, 29, "CBIT_1"), +"RGB0_CURRENT_4": (0, 29, "CBIT_2"), +"RGB0_CURRENT_5": (0, 29, "CBIT_3"), +"RGB1_CURRENT_0": (0, 29, "CBIT_4"), +"RGB1_CURRENT_1": (0, 29, "CBIT_5"), +"RGB1_CURRENT_2": (0, 29, "CBIT_6"), +"RGB1_CURRENT_3": (0, 29, "CBIT_7"), +"RGB1_CURRENT_4": (0, 30, "CBIT_0"), +"RGB1_CURRENT_5": (0, 30, "CBIT_1"), +"RGB2_CURRENT_0": (0, 30, "CBIT_2"), +"RGB2_CURRENT_1": (0, 30, "CBIT_3"), +"RGB2_CURRENT_2": (0, 30, "CBIT_4"), +"RGB2_CURRENT_3": (0, 30, "CBIT_5"), +"RGB2_CURRENT_4": (0, 30, "CBIT_6"), +"RGB2_CURRENT_5": (0, 30, "CBIT_7"), +"CURRENT_MODE": (0, 28, "CBIT_4"), -- cgit v1.2.3