diff --git a/.gitignore b/.gitignore index afc7feb..aa6460b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ *.o *.bin build/* +*.vcd +*.out diff --git a/Makefile b/Makefile index 625c48b..61e143d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,9 @@ -NEXTPNR_FLAGS= --45k --package CABGA381 --speed 6 --freq 65 -YOSYS_FLAGS= + + + +.PHONY all clean test + + + diff --git a/groovylight/hub75.py b/groovylight/hub75.py index c06ceff..5dffe07 100644 --- a/groovylight/hub75.py +++ b/groovylight/hub75.py @@ -28,16 +28,12 @@ class Hub75Driver(Module): if base_freq // 2 > 30e6: raise RuntimeError("hi") - self.phase = Signal() # divider/counter self.addr = Signal(5) self.latch = Signal() self.output_en = Signal() - self.rgb = Signal(6, reset=0b111010) - - - # clk-en acts as a gate. - clock_en = Signal() - + color = Signal(24, reset=0xA0FF00) + self.rgb = Signal(6) + self.clock_out = Signal() self.fsm = fsm = FSM() @@ -46,21 +42,34 @@ class Hub75Driver(Module): counter = Signal(32) + should_expose = (counter < (16 << bcm_value)) & (bcm_value != 0) + + + # this state both sets the OE low to drive the display with the previous frame + # while also loading the next row + # FIXME: there's a bug on the starting conditions right now, we are losing the lowest bit. fsm.act("WRITEROW", - self.output_en.eq(1), - If(counter < 256, + self.output_en.eq(~should_expose), + self.rgb[0].eq((color >> bcm_value) & 1), + self.rgb[3].eq((color >> bcm_value) & 1), + self.rgb[1].eq((color >> (bcm_value + 8)) & 1), + self.rgb[4].eq((color >> (bcm_value + 8)) & 1), + self.rgb[2].eq((color >> (bcm_value + 16)) & 1), + self.rgb[5].eq((color >> (bcm_value + 16)) & 1), + + NextValue(counter, counter + 1), + If(counter < linedepth * 2, self.clock_out.eq(counter[0]), - NextValue(counter, counter + 1), - If(counter[0], NextValue(self.rgb, self.rgb + 3)), - ).Else( + ), + If(~(counter < linedepth * 2) & ~should_expose, NextValue(counter, 0), - NextState("EXPOSE"), + NextState("LATCH"), ), ) fsm.act("EXPOSE", self.output_en.eq(0), - If(counter < (1000 << bcm_value), + If(counter < (16 << bcm_value), NextValue(counter, counter + 1), ).Else( NextValue(counter, 0), @@ -70,52 +79,14 @@ class Hub75Driver(Module): fsm.act("LATCH", self.latch.eq(1), + self.output_en.eq(1), NextValue(counter, 0), If(bcm_value == 7, NextValue(bcm_value, 0), NextValue(self.addr, self.addr + 1), - ).Else(NextValue(bcm_value, 1)), + ).Else( + NextValue(bcm_value, bcm_value + 1), + ), NextState("WRITEROW"), ) - - - # fsm.act("ready", - # NextValue(self.output_en, 1), - # NextValue(self.pixnum, linedepth - 1), - # NextValue(self.latch, 0), - # If(self.phase == 1, - # NextValue(self.addr, self.addr + 1), - # NextValue(clock_en, 1), - # NextState("transmit"), - # ), - # # If((self.state_count == 7), NextValue(clock_en, ~clock_en)), - # ) - # fsm.act("transmit", - # If(self.phase == 1, - # NextValue(self.pixnum, self.pixnum - 1), - # If(self.pixnum == 0, - # NextState("latch_delay"), - # ) - # ) - # ) - # fsm.act("latch_delay", - # NextValue(clock_en, 0), - # If(self.phase == 1, - # NextState("latchout") - # ) - # ) - # fsm.act("latchout", - # If(self.phase == 1, - # NextValue(self.latch, 1), - # NextValue(counter, 0), - # NextState("done") - # ) - # ) - # fsm.act("done", - # NextValue(self.output_en, 0), - # NextValue(self.latch, 0), - # NextValue(counter, counter + 1), - # If(counter == 255, NextState("ready")) - # ) - # diff --git a/verilog/hub75e.sv b/verilog/hub75e.sv new file mode 100644 index 0000000..49b865c --- /dev/null +++ b/verilog/hub75e.sv @@ -0,0 +1,125 @@ +module hub75e ( + input clk, + input write_trig, + input [1:0][BIT_DEPTH - 1:0] rgb_row[ROW_DEPTH], + output reg [4:0] addr, + output reg [2:0] panel_rgb0, + output reg [2:0] panel_rgb1, + output reg display_clk = 0, + output reg out_enable = 1, + output reg latch = 0, + output reg done = 0 +); + + parameter integer ROW_DEPTH = 128, BIT_DEPTH = 8; + + reg [31:0] counter = 0; + reg [ 3:0] bcm_shift = 7; // which bit of the colors are we currently exposing. + + localparam integer StateInit = 0; + localparam integer StateWriteRow = 1; + localparam integer StateLatchout = 2; + // the last data that we clock out for the row won't be exposed + // in the next writerow state because we'll change addresses. + localparam integer StateFinishExpose = 3; + + reg [7:0] state = StateInit; // our state + + + assign addr = 5'b10101; + assign panel_rgb0 = 3'b101; + assign panel_rgb1 = 3'b010; + + // initial begin + // state <= StateInit; + // counter <= 0; + // bcm_shift <= 7; + // end + + // The FSM is a bit confusing since it's optimized for *speed* + // We can basically display the previous line of data while we write the + // next one. So instead of having WRITEROW -> LATCH -> EXPOSE + // like most modules do, we can instead do (WRITEROW + EXPOSE_PREV) -> LATCH + // There is an edge case for the first/last bits of the color depth. + // When we start writing a line, we can't flash anything since there's no + // previous. Likewise, when we end a line, we have to have an extra expose + // period since there is no next writerow for this address. As a result, + // we want to go MSB to LSB for our BCM so that the trailing expose time is + // short! + + wire should_clock, should_expose; + assign should_clock = counter < ROW_DEPTH * 2; + assign should_expose = (counter < (16 << bcm_shift + 1)) && (bcm_shift != 7); + + always_ff @(posedge clk) begin + counter <= counter + 1; + case (state) + StateInit: begin + // wait for the signal to write out our lines. + if (write_trig) begin + bcm_shift <= 7; + counter <= 0; + done <= 0; + state <= StateWriteRow; + end + end + + StateWriteRow: begin + if (should_clock) begin + // we have data to clock + display_clk <= counter[0]; + if (counter[0]) begin + // load the next pixel data. this is the falling edge since the + // previous value is 1. + end + end + if (should_expose) begin + // we are still in our expose state, and our bcm shift is not the + // first one, so we should expose. + out_enable <= 0; + end else begin + out_enable <= 1; + end + // if we're done with our data clock out and also done with exposing + // the previous frame, go next. + if (~should_clock && ~should_expose) begin + counter <= 0; + state <= StateLatchout; + end + end + + StateLatchout: begin + // raise latch high; compute next bcm. + latch <= 1; + out_enable <= 1; + counter <= counter + 1; + if (counter > 3) begin + if (bcm_shift == 0) begin + // done with the line. do the last (short) expose + state <= StateFinishExpose; + latch <= 0; + end else begin + bcm_shift <= bcm_shift - 1; + state <= StateWriteRow; + latch <= 0; + end + end + end + + StateFinishExpose: begin + assert (bcm_shift == 0); + if (counter < (16 << bcm_shift)) begin + out_enable <= 0; + end else begin + out_enable <= 1; + state <= StateInit; + done <= 1; + // we are done! + end + end + default: begin + end + endcase + end + +endmodule diff --git a/verilog/tb/hub75e_tb.sv b/verilog/tb/hub75e_tb.sv new file mode 100644 index 0000000..146f6d5 --- /dev/null +++ b/verilog/tb/hub75e_tb.sv @@ -0,0 +1,48 @@ +`timescale 1ns / 100ps // 1 ns time unit, 100 ps resolution +module hub75e_tb; + + reg clk; + reg write_trig; + + reg [1:0][7:0] rgb_row[128]; + + wire [4:0] addr_out; + wire [2:0] rgb0; + wire [2:0] rgb1; + wire display_clk; + wire out_enable; + wire latch; + wire done; + + + hub75e dut ( + .clk(clk), + .write_trig(write_trig), + .rgb_row(rgb_row), + .addr(addr_out), + .panel_rgb0(rgb0), + .panel_rgb1(rgb1), + .display_clk(display_clk), + .out_enable(out_enable), + .latch(latch), + .done(done) + ); + + always #5 clk = !clk; + initial begin + $dumpfile("wave.vcd"); + $dumpvars(0, hub75e_tb); + clk = 0; + write_trig = 1; + repeat (2) @(posedge clk); + write_trig = 0; + + @(done); + repeat (20) @(posedge clk); + $finish(); + end + initial begin + repeat (100000) @(posedge clk); + $finish(); + end +endmodule