diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c957a89 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build +*~ +GPATH +GRTAGS +GTAGS +TAGS diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c5c5150 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +SOURCES = $(wildcard source/*.sv) +TOP = sugar_lissajous +PCF = icesugar.pcf + +FAMILY = up5k +PACKAGE = sg48 +FREQ = 30 + +#DOSVG = --placed-svg place.svg --routed-svg route.svg +# nextpnr --randomize-seed write_verilog $(TOP).v + +BUILD_DIR ?= build + +TOP_BIN = $(BUILD_DIR)/$(TOP).bin +TOP_ASC = $(BUILD_DIR)/$(TOP).asc +TOP_JSON = $(BUILD_DIR)/$(TOP).json + +all: $(TOP_BIN) + +# Make bitstream +$(TOP_BIN): $(TOP_ASC) + icepack $(TOP_ASC) $(TOP_BIN) + +# Place and rouite +$(TOP_ASC): $(TOP_JSON) $(PCF) + nextpnr-ice40 -q -l $(BUILD_DIR)/nextpnr.log --$(FAMILY) --package $(PACKAGE) \ + --freq $(FREQ) --top $(TOP) --pcf $(PCF) --asc $(TOP_ASC) --json $(TOP_JSON) \ + $(DOSVG) + +# Synthesys +$(TOP_JSON): $(SOURCES) + mkdir -p $(BUILD_DIR) + yosys -q -l $(BUILD_DIR)/yosys.log -p \ + "proc; alumacc; share -fast; opt -full; synth_ice40 -top $(TOP) -json $(TOP_JSON) -abc2" \ + $(SOURCES) + +# Timing analysis +timing: $(TOP_ASC) + icetime -d $(FAMILY) -t -c $(FREQ) -r $(BUILD_DIR)/timing.log $(TOP_ASC) + +# Program +prog: $(TOP_BIN) + icesprog -w $(TOP_BIN) + +# Clean +clean: + rm -rf $(BUILD_DIR) + +# Convert SVG to PNG +png: route.png place.png + +route.png: route.svg + inkscape --export-type=png -o route.png -D -d 100 route.svg + +place.png: place.svg + inkscape --export-type=png -o place.png -D -d 150 place.svg diff --git a/README.md b/README.md new file mode 100644 index 0000000..8295da5 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +Light-organ based on [iCESugar 1.5](https://github.com/wuxx/icesugar) +board (Lattice iCE40UP5k), nameless SPI TFT LCD display from AliExpress, +and sound capture board [Dual MCP3102](https://github.com/punzik/dual-mcp3201-pmod). + +Project is synthesized by [Yosys](https://github.com/YosysHQ/yosys), routed and +placed by [nextpnr](https://github.com/YosysHQ/nextpnr), verefied by +[iverilog](https://github.com/steveicarus/iverilog) and +[verilator](https://github.com/verilator/verilator). For scripting, prototyping and +GUI use [Racket](https://racket-lang.org/) and [GNU +Octave](https://www.gnu.org/software/octave/index). + +Video 1: https://youtu.be/R9meEMrbPAM + +Video 2: https://youtu.be/H2083E0BFIM + +![Photo](photo.jpg) diff --git a/doc/ILI9341 Datasheet.pdf b/doc/ILI9341 Datasheet.pdf new file mode 100644 index 0000000..a18a880 Binary files /dev/null and b/doc/ILI9341 Datasheet.pdf differ diff --git a/icesugar.pcf b/icesugar.pcf new file mode 100644 index 0000000..e7e8ac7 --- /dev/null +++ b/icesugar.pcf @@ -0,0 +1,61 @@ +# iCESugar Board (iCE40UP5K-QFN48) + +set_io -nowarn LED_R_N 39 +set_io -nowarn LED_G_N 40 +set_io -nowarn LED_B_N 41 + +set_io -nowarn SW[0] 18 +set_io -nowarn SW[1] 19 +set_io -nowarn SW[2] 20 +set_io -nowarn SW[3] 21 + +set_io -nowarn CLK12 35 + +set_io -nowarn UART_RX 4 +set_io -nowarn UART_TX 6 + +set_io -nowarn USB_DP 10 +set_io -nowarn USB_DN 9 +set_io -nowarn USB_PUP 11 + +# PMOD 1 +set_io -nowarn P1_1 10 +set_io -nowarn P1_2 6 +set_io -nowarn P1_3 3 +set_io -nowarn P1_4 48 +set_io -nowarn P1_9 47 +set_io -nowarn P1_10 2 +set_io -nowarn P1_11 4 +set_io -nowarn P1_12 9 + +# PMOD 2 +set_io -nowarn P2_1 46 +set_io -nowarn P2_2 44 +set_io -nowarn P2_3 42 +set_io -nowarn P2_4 37 +set_io -nowarn P2_9 36 +set_io -nowarn P2_10 38 +set_io -nowarn P2_11 43 +set_io -nowarn P2_12 45 + +# PMOD 3 +set_io -nowarn P3_1 34 +set_io -nowarn P3_2 31 +set_io -nowarn P3_3 27 +set_io -nowarn P3_4 25 +set_io -nowarn P3_9 23 +set_io -nowarn P3_10 26 +set_io -nowarn P3_11 28 +set_io -nowarn P3_12 32 + +# PMOD 4 +set_io -nowarn P4_1 21 +set_io -nowarn P4_2 20 +set_io -nowarn P4_3 19 +set_io -nowarn P4_4 18 + +#spi +set_io -nowarn SPI_SS 16 +set_io -nowarn SPI_SCK 15 +set_io -nowarn SPI_MOSI 17 +set_io -nowarn SPI_MISO 14 diff --git a/photo.jpg b/photo.jpg new file mode 100644 index 0000000..9f3a377 Binary files /dev/null and b/photo.jpg differ diff --git a/source/.dir-locals.el b/source/.dir-locals.el new file mode 100644 index 0000000..5937326 --- /dev/null +++ b/source/.dir-locals.el @@ -0,0 +1,5 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((verilog-mode . ((flycheck-verilator-include-path . ("." "../../local/share/yosys/ice40")) + (verilog-library-directories . ("." "../../local/share/yosys/ice40"))))) diff --git a/source/assert.vh b/source/assert.vh new file mode 100644 index 0000000..735918a --- /dev/null +++ b/source/assert.vh @@ -0,0 +1,9 @@ +`ifndef _ASSERT_VH_ +`define _ASSERT_VH_ + +`define assert(assertion) \ + if (!(assertion)) begin \ + $error("ERROR: Assertion failed in %m: assertion");\ + end + +`endif diff --git a/source/circle-table.rkt b/source/circle-table.rkt new file mode 100644 index 0000000..7ce8167 --- /dev/null +++ b/source/circle-table.rkt @@ -0,0 +1,29 @@ +#lang racket + +(define (gen-circle-quadrant len) + (map (lambda (n) + (let ((angle (/ (* 2 pi n) len))) + (cons (sin angle) + (cos angle)))) + (range (/ len 4)))) + +(define (print-xy-table table) + (define (->hex x) + (~a (number->string (inexact->exact (round x)) 16) + #:min-width 2 #:align 'right #:pad-string "0")) + (for-each + (lambda (c) + (printf "~a~a\n" + (->hex (* 255 (car c))) + (->hex (* 255 (cdr c))))) + table)) + +;;; MAIN +(let ((x0 0) + (y0 0) + (len 1024)) + + (with-output-to-file (format "quadrant_~a.rom" (/ len 4)) + (lambda () + (print-xy-table + (gen-circle-quadrant len))))) diff --git a/source/circle_1024.sv b/source/circle_1024.sv new file mode 100644 index 0000000..b2d1433 --- /dev/null +++ b/source/circle_1024.sv @@ -0,0 +1,82 @@ +`timescale 1ns/100ps +`default_nettype none + +module circle_1024 + (input wire clock, + input wire reset, + + input wire [9:0] angle, + input wire [7:0] r, + input wire [7:0] x0, + input wire [7:0] y0, + + output wire [7:0] x, + output wire [7:0] y, + + input wire req_i, + output reg ack_o); + + localparam QUADR_LEN = 256; + localparam QUADR_ROM_FILE = "quadrant_256.rom"; + localparam QUADR_CW = $clog2(QUADR_LEN); + logic [15:0] q_rom[QUADR_LEN]; + initial $readmemh(QUADR_ROM_FILE, q_rom, 0, QUADR_LEN-1); + + logic [QUADR_CW-1:0] q_addr; + logic [7:0] kx, ky; + + always_ff @ (posedge clock) + {kx, ky} <= q_rom[q_addr]; + + logic [15:0] macx_o, macy_o; + logic xsub, ysub; + + ice40_2mac8x8 circle_mac + (.clock, .reset, + .a0(r), + .b0(kx), + .s0({x0, 8'b0}), + .sub0(xsub), + .y0(macx_o), + + .a1(r), + .b1(ky), + .s1({y0, 8'b0}), + .sub1(ysub), + .y1(macy_o)); + + assign q_addr = angle[8] ? 8'd255 - angle[7:0] : angle[7:0]; + assign xsub = angle[9]; + assign ysub = angle[8] == angle[9] ? 1'b0 : 1'b1; + assign x = macx_o[15:8]; + assign y = macy_o[15:8]; + + enum int unsigned { + ST_IDLE = 0, + ST_GET_K, + ST_MAC + } state; + + always_ff @(posedge clock, posedge reset) + if (reset) begin + state <= ST_IDLE; + ack_o <= 1'b0; + end + else + case (state) + ST_IDLE: + if (req_i) + state <= ST_GET_K; + + ST_GET_K: begin + ack_o <= 1'b1; + state <= ST_MAC; + end + + ST_MAC: begin + ack_o <= 1'b0; + state <= ST_IDLE; + end + endcase + +endmodule // circle diff --git a/source/fig_circle_8x8.rom b/source/fig_circle_8x8.rom new file mode 100644 index 0000000..b32931e --- /dev/null +++ b/source/fig_circle_8x8.rom @@ -0,0 +1,8 @@ +00 00 3f 3f 3f 3f 00 00 +00 3f 7f 7f 7f 7f 3f 00 +3f 7f bf bf bf bf 7f 3f +3f 7f bf ff ff bf 7f 3f +3f 7f bf ff ff bf 7f 3f +3f 7f bf bf bf bf 7f 3f +00 3f 7f 7f 7f 7f 3f 00 +00 00 3f 3f 3f 3f 00 00 diff --git a/source/fig_drawer.sv b/source/fig_drawer.sv new file mode 100644 index 0000000..68a9a4b --- /dev/null +++ b/source/fig_drawer.sv @@ -0,0 +1,223 @@ +`timescale 1ns/100ps +`default_nettype none +`include "assert.vh" + +module fig_drawer #(parameter FIG_W = 8, + parameter FIG_H = 8, + parameter FIG_ROM_FILE = "fig_circle_8x8.rom") + (input wire clock, + input wire reset, + + input wire [7:0] x_i, + input wire [8:0] y_i, + input wire [7:0] h_i, + input wire [7:0] s_i, + input wire [7:0] v_i, + input wire req_i, + output reg ack_o, + + output reg [7:0] fb_x_o, + output reg [8:0] fb_y_o, + output reg [15:0] fb_color_o, + output reg fb_req_o, + input wire fb_ack_i); + + initial begin + `assert(FIG_W > 0); + `assert(FIG_H > 0); + + /* Check power of 2 */ + `assert(FIG_W == 1 << $clog2(FIG_W)); + `assert(FIG_H == 1 << $clog2(FIG_H)); + end + + /* Figure bitmap */ + localparam FIG_SIZE = FIG_W * FIG_H; + localparam FIG_W_CW = $clog2(FIG_W); + localparam FIG_H_CW = $clog2(FIG_H); + localparam FIG_CW = $clog2(FIG_SIZE); + + logic [7:0] fig[FIG_SIZE]; + initial $readmemh(FIG_ROM_FILE, fig, 0, FIG_SIZE-1); + + logic [FIG_CW-1:0] fig_addr; + logic [7:0] fig_data; + + always_ff @ (posedge clock) + fig_data <= fig[fig_addr]; + + /* Scale brightness */ + logic [31:0] mac_v_o; + logic [7:0] fig_v; + + assign fig_v = mac_v_o[15:8]; + + ice40_mac16x16 mac_v + (.clock, .reset, + .a({8'b0, fig_data}), + .b({8'b0, v_i}), + .s(32'b0), + .sub(1'b0), + .y(mac_v_o)); + + /* Convert HSV ro RGB */ + logic hsv_ready; + logic rgb_valid; + logic [7:0] r, g, b; + + hsv2rgb hsv2rgb_i + (.clock, .reset, + .h(h_i), .s(s_i), .v(fig_v), + .ready_i(hsv_ready), + .r(r), .g(g), .b(b), + .valid_o(rgb_valid)); + + /* FSM states */ + enum int unsigned { + ST_IDLE = 0, // 0 + ST_READ_PIX, // 1 + ST_MULT_V, // 2 + ST_HSV_READY, // 3 + ST_RGB_WAIT, // 4 + ST_WAIT_FB, // 5 + ST_NEXT_PIXEL, // 6 + ST_DONE // 7 + } state, next; + + /* FSM sync part */ + always_ff @(posedge clock, posedge reset) + if (reset) state <= ST_IDLE; + else state <= next; + + /* Pixel coordinate */ + logic [FIG_W_CW-1:0] pix_x; + logic [FIG_H_CW-1:0] pix_y; + logic pix_reset, pix_next; + + assign fig_addr = {pix_y, pix_x}; + + always_ff @(posedge clock) + if (pix_reset) begin + pix_x <= '0; + pix_y <= '0; + end + else + if (pix_next) + if (pix_x == FIG_H_CW'(FIG_H-1)) begin + pix_x <= '0; + + if (pix_y == FIG_W_CW'(FIG_W-1)) + pix_y <= '0; + else + pix_y <= pix_y + 1'b1; + end + else + pix_x <= pix_x + 1'b1; + + /* Frame buffer control */ + logic [7:0] fb_x; + logic [8:0] fb_y; + logic [15:0] fb_color; + logic fb_hold, fb_restore; + logic fb_req; + + always_ff @ (posedge clock, posedge reset) + if (reset) + fb_req_o <= 1'b0; + else + if (fb_req) + fb_req_o <= 1'b1; + else + if (fb_ack_i) + fb_req_o <= 1'b0; + + assign fb_x = x_i + 8'(pix_x); + assign fb_y = y_i + 9'(pix_y); + + always_ff @ (posedge clock) begin + if (rgb_valid) begin + fb_color <= {r[7:3], g[7:2], b[7:3]}; + + if (~fb_hold) + fb_color_o <= {r[7:3], g[7:2], b[7:3]}; + end + + if (fb_restore) + fb_color_o <= fb_color; + end + + always_ff @ (posedge clock) + if (fb_req) begin + fb_x_o <= fb_x; + fb_y_o <= fb_y; + end + + /* FSM comb part */ + always_comb begin + next = state; + pix_reset = 1'b0; + pix_next = 1'b0; + fb_req = 1'b0; + fb_hold = 1'b0; + fb_restore = 1'b0; + ack_o = 1'b0; + hsv_ready = 1'b0; + + case (state) + ST_IDLE: + if (req_i) begin + pix_reset = 1'b1; + next = ST_READ_PIX; + end + + ST_READ_PIX: + next = ST_MULT_V; + + ST_MULT_V: + if (fig_data == '0) begin + pix_next = 1'b1; + next = ST_NEXT_PIXEL; + end + else + next = ST_HSV_READY; + + ST_HSV_READY: begin + hsv_ready = 1'b1; + next = ST_RGB_WAIT; + end + + ST_RGB_WAIT: + if (rgb_valid) begin + if (fb_req_o && !fb_ack_i) begin + fb_hold = 1'b1; + next = ST_WAIT_FB; + end + else begin + fb_req = 1'b1; + pix_next = 1'b1; + next = ST_NEXT_PIXEL; + end + end + + ST_WAIT_FB: + if (fb_ack_i) begin + fb_restore = 1'b1; + fb_req = 1'b1; + pix_next = 1'b1; + next = ST_NEXT_PIXEL; + end + + ST_NEXT_PIXEL: + if (pix_x == '0 && pix_y == '0) + next = ST_DONE; + else + next = ST_MULT_V; + + ST_DONE: begin + ack_o = 1'b1; + next = ST_IDLE; + end + endcase + end + +endmodule // fig_drawer diff --git a/source/fig_ring.sv b/source/fig_ring.sv new file mode 100644 index 0000000..dc4ee0b --- /dev/null +++ b/source/fig_ring.sv @@ -0,0 +1,168 @@ +`timescale 1ns/100ps +`default_nettype none +`include "assert.vh" + +module fig_ring #(parameter POINT_COUNT = 32, + parameter FADING = 1) + (input wire clock, + input wire reset, + + input wire [7:0] pt_x, + input wire [7:0] pt_y, + input wire [7:0] pt_h, + input wire pt_req_i, + output reg pt_ack_o, + + output reg [7:0] fig_x_o, + output reg [8:0] fig_y_o, + output reg [7:0] fig_h_o, + output reg [7:0] fig_s_o, + output reg [7:0] fig_v_o, + output reg fig_req_o, + input wire fig_ack_i); + + initial begin + `assert(POINT_COUNT > 0); + `assert(POINT_COUNT == (1 << $clog2(POINT_COUNT))); + end + + /* Points coordinate RAM */ + localparam POINT_CW = $clog2(POINT_COUNT); + localparam [7:0] V_INC = FADING == 0 ? 8'd255 : 8'('h100 / POINT_COUNT); + + logic [23:0] pt_ram[POINT_COUNT]; + logic [POINT_CW-1:0] pt_raddr; + logic [POINT_CW-1:0] pt_waddr; + logic pt_wr; + + logic [7:0] pt_xw, pt_xr; + logic [7:0] pt_yw, pt_yr; + logic [7:0] pt_hw, pt_hr; + + always_ff @ (posedge clock) begin + if (pt_wr) + pt_ram[pt_waddr] <= {pt_hw, pt_xw, pt_yw}; + + {pt_hr, pt_xr, pt_yr} <= pt_ram[pt_raddr]; + end + +`ifdef TESTBENCH + integer i; + initial + for (i = 0; i < POINT_COUNT; i ++) + pt_ram[i] = {8'(i*5), 8'(i*5 + 20), 8'(i*5 + 30)}; +`endif + + /* FSM */ + enum int unsigned { + ST_IDLE = 0, // 0 + ST_READ_LAST_PT, // 1 + ST_STORE_LAST_PT, // 2 + ST_WRITE_NEW_PT, // 3 + ST_DRAW_LAST, // 4 + ST_FIG_DRAW, // 5 + ST_NEXT_PT, // 6 + ST_READ_PT, // 7 + ST_STORE_PT, // 8 + ST_ACK // 9 + } state; + + logic [POINT_CW-1:0] pt_last; + + assign pt_xw = pt_x; + assign pt_yw = pt_y; + assign pt_hw = pt_h; + + always_ff @ (posedge clock, posedge reset) + if (reset) begin + state <= ST_IDLE; + pt_wr <= 1'b0; + pt_last <= '0; + pt_ack_o <= 1'b0; + fig_req_o <= 1'b0; + end + else + case (state) + ST_IDLE: begin + if (pt_req_i) begin + pt_raddr <= pt_last; + pt_waddr <= pt_last; + pt_wr <= 1'b0; + fig_v_o <= '0; + state <= ST_READ_LAST_PT; + end + end + + ST_READ_LAST_PT: + state <= ST_STORE_LAST_PT; + + ST_STORE_LAST_PT: begin + fig_x_o <= pt_xr; + fig_y_o <= {1'b0, pt_yr}; + fig_h_o <= pt_hr; + fig_s_o <= 8'hff; // TODO: add saturation to ring mem + pt_wr <= 1'b1; + state <= ST_WRITE_NEW_PT; + end + + ST_WRITE_NEW_PT: begin + pt_wr <= 1'b0; + fig_req_o <= 1'b1; + pt_raddr <= pt_last + 1'b1; + state <= ST_DRAW_LAST; + end + + ST_DRAW_LAST: + if (fig_ack_i) begin + fig_req_o <= 1'b0; + state <= ST_READ_PT; + end + + ST_FIG_DRAW: begin + fig_req_o <= 1'b1; + state <= ST_NEXT_PT; + end + + ST_NEXT_PT: + if (fig_ack_i) begin + fig_req_o <= 1'b0; + + if (pt_raddr == pt_last) begin + pt_last <= pt_last + 1'b1; + pt_ack_o <= 1'b1; + state <= ST_ACK; + end + else begin + if (pt_raddr == POINT_CW'(POINT_COUNT-1)) + pt_raddr <= '0; + else + pt_raddr <= pt_raddr + 1'b1; + + state <= ST_READ_PT; + end + end + + ST_READ_PT: + state <= ST_STORE_PT; + + ST_STORE_PT: begin + fig_x_o <= pt_xr; + fig_y_o <= {1'b0, pt_yr}; + fig_h_o <= pt_hr; + fig_s_o <= 8'hff; // TODO: add saturation to ring mem + + if (fig_v_o > (255-V_INC)) + fig_v_o <= 8'hff; + else + fig_v_o <= fig_v_o + V_INC; + + state <= ST_FIG_DRAW; + end + + ST_ACK: begin + pt_ack_o <= 1'b0; + state <= ST_IDLE; + end + endcase + +endmodule // fig_ring diff --git a/source/fir-filter.m b/source/fir-filter.m new file mode 100644 index 0000000..3d26a5f --- /dev/null +++ b/source/fir-filter.m @@ -0,0 +1,64 @@ +pkg load signal + +%% filter length +flen = 511; + +%% window +#win = ones(flen,1); +#win = chebwin(flen); +#win = blackmanharris(flen); +win = flattopwin(flen); + +%% data width +bits = 16; + +%% sampling frequency +fs = 20000 + +%% cutoff frequency +fc = 50; + +%% stopband frequency +fb = 100; + +%% amplification (dB) +amp_db = 10; + +%% stopband attenuation (dB) +stop_db = -40; + +%% filter +amp_k = 10^(amp_db/20); +stop_k = 10^((stop_db+amp_k)/20); +f = [0 fc/fs fb/fs 1]; +m = [amp_k amp_k stop_k stop_k]; +fcoeff = firls(flen-1, f, m) .* win; + +%% scale and round to 16 bit +fcoeff_i16 = round(fcoeff .* 65536); + +%% remove leading and trailing zeroes +fcoeff_i16_ms = fcoeff_i16(find(fcoeff_i16,1,'first'):find(fcoeff_i16,1,'last')); + +function ret = to_hex(x) + if (x < 0) + ret = 65536+x; + else + ret = x; + endif +endfunction + +%% convert negative coeffs to 16-bit two's complement +fcoeff_i16_hex = arrayfun(@to_hex, fcoeff_i16_ms); + +%% printf coeffs to file +filename = sprintf("fir_%d_%dhz_%dhz_%ddb_%ddb.rom", length(fcoeff_i16_ms), fc, fb, amp_db, -stop_db); +printf("Write %d coefficients to file %s\n", length(fcoeff_i16_ms), filename); + +file = fopen(filename, "w"); +fprintf(file, "%04x\n", fcoeff_i16_hex); +fclose(file); + +%% make real coeffs for freqz +fcoeff_i = fcoeff_i16_ms ./ 65536; +freqz(fcoeff_i); diff --git a/source/fir_363_250hz_400hz_0db_40db.rom b/source/fir_363_250hz_400hz_0db_40db.rom new file mode 100644 index 0000000..2eedb11 --- /dev/null +++ b/source/fir_363_250hz_400hz_0db_40db.rom @@ -0,0 +1,363 @@ +ffff +ffff +ffff +ffff +ffff +ffff +ffff +fffe +fffe +fffe +fffe +fffe +fffd +fffd +fffd +fffd +fffd +fffc +fffc +fffc +fffc +fffc +fffb +fffb +fffb +fffb +fffb +fffb +fffb +fffb +fffa +fffa +fffa +fffa +fffa +fffa +fffb +fffb +fffb +fffb +fffb +fffb +fffb +fffc +fffc +fffc +fffc +fffd +fffd +fffd +fffe +fffe +fffe +ffff +ffff +ffff +0000 +0000 +0000 +0001 +0001 +0001 +0001 +0001 +0001 +0001 +0001 +0001 +0001 +0000 +0000 +ffff +ffff +fffe +fffd +fffd +fffc +fffb +fff9 +fff8 +fff7 +fff5 +fff4 +fff2 +fff1 +ffef +ffed +ffeb +ffe9 +ffe7 +ffe6 +ffe4 +ffe2 +ffe0 +ffde +ffdc +ffda +ffd9 +ffd7 +ffd6 +ffd5 +ffd4 +ffd3 +ffd2 +ffd2 +ffd2 +ffd2 +ffd3 +ffd4 +ffd6 +ffd8 +ffda +ffdd +ffe0 +ffe4 +ffe9 +ffee +fff3 +fff9 +0000 +0008 +0010 +0019 +0023 +002d +0038 +0044 +0051 +005e +006c +007b +008b +009b +00ac +00be +00d0 +00e3 +00f7 +010b +0120 +0136 +014c +0162 +0179 +0190 +01a8 +01c0 +01d8 +01f1 +0209 +0222 +023b +0253 +026c +0284 +029c +02b4 +02cc +02e3 +02fa +0311 +0326 +033b +0350 +0364 +0377 +0389 +039a +03aa +03b9 +03c7 +03d4 +03e0 +03eb +03f5 +03fd +0404 +040a +040f +0412 +0414 +06f4 +0414 +0412 +040f +040a +0404 +03fd +03f5 +03eb +03e0 +03d4 +03c7 +03b9 +03aa +039a +0389 +0377 +0364 +0350 +033b +0326 +0311 +02fa +02e3 +02cc +02b4 +029c +0284 +026c +0253 +023b +0222 +0209 +01f1 +01d8 +01c0 +01a8 +0190 +0179 +0162 +014c +0136 +0120 +010b +00f7 +00e3 +00d0 +00be +00ac +009b +008b +007b +006c +005e +0051 +0044 +0038 +002d +0023 +0019 +0010 +0008 +0000 +fff9 +fff3 +ffee +ffe9 +ffe4 +ffe0 +ffdd +ffda +ffd8 +ffd6 +ffd4 +ffd3 +ffd2 +ffd2 +ffd2 +ffd2 +ffd3 +ffd4 +ffd5 +ffd6 +ffd7 +ffd9 +ffda +ffdc +ffde +ffe0 +ffe2 +ffe4 +ffe6 +ffe7 +ffe9 +ffeb +ffed +ffef +fff1 +fff2 +fff4 +fff5 +fff7 +fff8 +fff9 +fffb +fffc +fffd +fffd +fffe +ffff +ffff +0000 +0000 +0001 +0001 +0001 +0001 +0001 +0001 +0001 +0001 +0001 +0001 +0000 +0000 +0000 +ffff +ffff +ffff +fffe +fffe +fffe +fffd +fffd +fffd +fffc +fffc +fffc +fffc +fffb +fffb +fffb +fffb +fffb +fffb +fffb +fffa +fffa +fffa +fffa +fffa +fffa +fffb +fffb +fffb +fffb +fffb +fffb +fffb +fffb +fffc +fffc +fffc +fffc +fffc +fffd +fffd +fffd +fffd +fffd +fffe +fffe +fffe +fffe +fffe +ffff +ffff +ffff +ffff +ffff +ffff +ffff diff --git a/source/fir_425_50hz_100hz_0db_40db.rom b/source/fir_425_50hz_100hz_0db_40db.rom new file mode 100644 index 0000000..5cc5a15 --- /dev/null +++ b/source/fir_425_50hz_100hz_0db_40db.rom @@ -0,0 +1,425 @@ +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +fffe +fffe +fffe +fffe +fffe +fffe +fffe +fffe +fffe +fffe +fffd +fffd +fffd +fffd +fffd +fffd +fffd +fffc +fffc +fffc +fffc +fffc +fffc +fffb +fffb +fffb +fffb +fffb +fffb +fffa +fffa +fffa +fffa +fffa +fff9 +fff9 +fff9 +fff9 +fff9 +fff9 +fff8 +fff8 +fff8 +fff8 +fff8 +fff8 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff8 +fff8 +fff8 +fff8 +fff9 +fff9 +fff9 +fffa +fffa +fffb +fffb +fffc +fffd +fffd +fffe +ffff +ffff +0000 +0001 +0002 +0003 +0004 +0005 +0006 +0007 +0008 +0009 +000b +000c +000d +000f +0010 +0012 +0013 +0015 +0017 +0018 +001a +001c +001e +0020 +0022 +0024 +0026 +0028 +002a +002d +002f +0031 +0034 +0036 +0039 +003b +003e +0040 +0043 +0046 +0048 +004b +004e +0051 +0054 +0057 +0059 +005c +005f +0062 +0065 +0068 +006c +006f +0072 +0075 +0078 +007b +007e +0081 +0084 +0088 +008b +008e +0091 +0094 +0097 +009a +009d +00a0 +00a3 +00a6 +00a9 +00ac +00af +00b2 +00b4 +00b7 +00ba +00bc +00bf +00c1 +00c4 +00c6 +00c9 +00cb +00cd +00cf +00d1 +00d3 +00d5 +00d7 +00d9 +00db +00dc +00de +00df +00e1 +00e2 +00e3 +00e4 +00e5 +00e6 +00e7 +00e8 +00e8 +00e9 +00e9 +00e9 +00ea +00ea +03c9 +00ea +00ea +00e9 +00e9 +00e9 +00e8 +00e8 +00e7 +00e6 +00e5 +00e4 +00e3 +00e2 +00e1 +00df +00de +00dc +00db +00d9 +00d7 +00d5 +00d3 +00d1 +00cf +00cd +00cb +00c9 +00c6 +00c4 +00c1 +00bf +00bc +00ba +00b7 +00b4 +00b2 +00af +00ac +00a9 +00a6 +00a3 +00a0 +009d +009a +0097 +0094 +0091 +008e +008b +0088 +0084 +0081 +007e +007b +0078 +0075 +0072 +006f +006c +0068 +0065 +0062 +005f +005c +0059 +0057 +0054 +0051 +004e +004b +0048 +0046 +0043 +0040 +003e +003b +0039 +0036 +0034 +0031 +002f +002d +002a +0028 +0026 +0024 +0022 +0020 +001e +001c +001a +0018 +0017 +0015 +0013 +0012 +0010 +000f +000d +000c +000b +0009 +0008 +0007 +0006 +0005 +0004 +0003 +0002 +0001 +0000 +ffff +ffff +fffe +fffd +fffd +fffc +fffb +fffb +fffa +fffa +fff9 +fff9 +fff9 +fff8 +fff8 +fff8 +fff8 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff7 +fff8 +fff8 +fff8 +fff8 +fff8 +fff8 +fff9 +fff9 +fff9 +fff9 +fff9 +fff9 +fffa +fffa +fffa +fffa +fffa +fffb +fffb +fffb +fffb +fffb +fffb +fffc +fffc +fffc +fffc +fffc +fffc +fffd +fffd +fffd +fffd +fffd +fffd +fffd +fffe +fffe +fffe +fffe +fffe +fffe +fffe +fffe +fffe +fffe +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff diff --git a/source/fir_449_50hz_100hz_10db_40db.rom b/source/fir_449_50hz_100hz_10db_40db.rom new file mode 100644 index 0000000..24a0d43 --- /dev/null +++ b/source/fir_449_50hz_100hz_10db_40db.rom @@ -0,0 +1,449 @@ +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +fffe +fffe +fffe +fffe +fffe +fffe +fffd +fffd +fffd +fffd +fffd +fffc +fffc +fffc +fffc +fffb +fffb +fffb +fffb +fffa +fffa +fffa +fff9 +fff9 +fff8 +fff8 +fff8 +fff7 +fff7 +fff6 +fff6 +fff6 +fff5 +fff5 +fff4 +fff4 +fff3 +fff3 +fff2 +fff1 +fff1 +fff0 +fff0 +ffef +ffef +ffee +ffed +ffed +ffec +ffec +ffeb +ffeb +ffea +ffe9 +ffe9 +ffe8 +ffe8 +ffe7 +ffe7 +ffe6 +ffe6 +ffe5 +ffe5 +ffe4 +ffe4 +ffe4 +ffe3 +ffe3 +ffe3 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe3 +ffe3 +ffe4 +ffe4 +ffe5 +ffe5 +ffe6 +ffe7 +ffe8 +ffe9 +ffea +ffeb +ffed +ffee +fff0 +fff1 +fff3 +fff5 +fff7 +fff9 +fffc +fffe +0001 +0003 +0006 +0009 +000c +000f +0013 +0016 +001a +001e +0022 +0026 +002b +002f +0034 +0039 +003e +0043 +0048 +004e +0054 +0059 +005f +0066 +006c +0072 +0079 +0080 +0087 +008e +0095 +009d +00a4 +00ac +00b4 +00bc +00c4 +00cd +00d5 +00de +00e6 +00ef +00f8 +0101 +010a +0113 +011d +0126 +0130 +0139 +0143 +014d +0156 +0160 +016a +0174 +017e +0188 +0192 +019c +01a6 +01b0 +01ba +01c3 +01cd +01d7 +01e1 +01eb +01f4 +01fe +0207 +0211 +021a +0223 +022c +0235 +023e +0247 +024f +0258 +0260 +0268 +0270 +0277 +027f +0286 +028d +0294 +029a +02a1 +02a7 +02ad +02b3 +02b8 +02bd +02c2 +02c7 +02cb +02cf +02d3 +02d6 +02d9 +02dc +02df +02e1 +02e3 +02e5 +02e6 +02e7 +02e8 +02e8 +0698 +02e8 +02e8 +02e7 +02e6 +02e5 +02e3 +02e1 +02df +02dc +02d9 +02d6 +02d3 +02cf +02cb +02c7 +02c2 +02bd +02b8 +02b3 +02ad +02a7 +02a1 +029a +0294 +028d +0286 +027f +0277 +0270 +0268 +0260 +0258 +024f +0247 +023e +0235 +022c +0223 +021a +0211 +0207 +01fe +01f4 +01eb +01e1 +01d7 +01cd +01c3 +01ba +01b0 +01a6 +019c +0192 +0188 +017e +0174 +016a +0160 +0156 +014d +0143 +0139 +0130 +0126 +011d +0113 +010a +0101 +00f8 +00ef +00e6 +00de +00d5 +00cd +00c4 +00bc +00b4 +00ac +00a4 +009d +0095 +008e +0087 +0080 +0079 +0072 +006c +0066 +005f +0059 +0054 +004e +0048 +0043 +003e +0039 +0034 +002f +002b +0026 +0022 +001e +001a +0016 +0013 +000f +000c +0009 +0006 +0003 +0001 +fffe +fffc +fff9 +fff7 +fff5 +fff3 +fff1 +fff0 +ffee +ffed +ffeb +ffea +ffe9 +ffe8 +ffe7 +ffe6 +ffe5 +ffe5 +ffe4 +ffe4 +ffe3 +ffe3 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe2 +ffe3 +ffe3 +ffe3 +ffe4 +ffe4 +ffe4 +ffe5 +ffe5 +ffe6 +ffe6 +ffe7 +ffe7 +ffe8 +ffe8 +ffe9 +ffe9 +ffea +ffeb +ffeb +ffec +ffec +ffed +ffed +ffee +ffef +ffef +fff0 +fff0 +fff1 +fff1 +fff2 +fff3 +fff3 +fff4 +fff4 +fff5 +fff5 +fff6 +fff6 +fff6 +fff7 +fff7 +fff8 +fff8 +fff8 +fff9 +fff9 +fffa +fffa +fffa +fffb +fffb +fffb +fffb +fffc +fffc +fffc +fffc +fffd +fffd +fffd +fffd +fffd +fffe +fffe +fffe +fffe +fffe +fffe +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff diff --git a/source/fir_465_50hz_100hz_20db_40db.rom b/source/fir_465_50hz_100hz_20db_40db.rom new file mode 100644 index 0000000..f5ad8a9 --- /dev/null +++ b/source/fir_465_50hz_100hz_20db_40db.rom @@ -0,0 +1,465 @@ +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff +fffe +fffe +fffe +fffe +fffe +fffd +fffd +fffd +fffc +fffc +fffc +fffb +fffb +fffa +fffa +fffa +fff9 +fff9 +fff8 +fff7 +fff7 +fff6 +fff5 +fff5 +fff4 +fff3 +fff2 +fff2 +fff1 +fff0 +ffef +ffee +ffed +ffec +ffeb +ffe9 +ffe8 +ffe7 +ffe6 +ffe4 +ffe3 +ffe2 +ffe0 +ffdf +ffdd +ffdc +ffda +ffd9 +ffd7 +ffd5 +ffd4 +ffd2 +ffd0 +ffce +ffcd +ffcb +ffc9 +ffc7 +ffc5 +ffc3 +ffc2 +ffc0 +ffbe +ffbc +ffba +ffb8 +ffb6 +ffb5 +ffb3 +ffb1 +ffb0 +ffae +ffac +ffab +ffaa +ffa8 +ffa7 +ffa6 +ffa5 +ffa4 +ffa3 +ffa2 +ffa2 +ffa1 +ffa1 +ffa1 +ffa1 +ffa1 +ffa1 +ffa2 +ffa2 +ffa3 +ffa4 +ffa6 +ffa7 +ffa9 +ffab +ffae +ffb1 +ffb4 +ffb7 +ffba +ffbe +ffc3 +ffc7 +ffcc +ffd2 +ffd7 +ffdd +ffe4 +ffeb +fff2 +fffa +0002 +000a +0013 +001d +0027 +0031 +003c +0047 +0053 +005f +006c +007a +0087 +0096 +00a5 +00b4 +00c4 +00d4 +00e5 +00f7 +0109 +011b +012e +0142 +0156 +016a +0180 +0195 +01ab +01c2 +01d9 +01f1 +0209 +0221 +023a +0254 +026e +0288 +02a3 +02be +02d9 +02f5 +0312 +032e +034b +0368 +0386 +03a4 +03c2 +03e0 +03ff +041d +043c +045b +047b +049a +04b9 +04d9 +04f8 +0518 +0537 +0557 +0576 +0596 +05b5 +05d4 +05f3 +0612 +0630 +064f +066d +068a +06a8 +06c5 +06e2 +06fe +071a +0735 +0750 +076b +0785 +079e +07b7 +07cf +07e7 +07fe +0814 +082a +083f +0853 +0866 +0879 +088b +089c +08ac +08bb +08ca +08d8 +08e4 +08f0 +08fb +0905 +090f +0917 +091e +0924 +092a +092e +0932 +0934 +0935 +114e +0935 +0934 +0932 +092e +092a +0924 +091e +0917 +090f +0905 +08fb +08f0 +08e4 +08d8 +08ca +08bb +08ac +089c +088b +0879 +0866 +0853 +083f +082a +0814 +07fe +07e7 +07cf +07b7 +079e +0785 +076b +0750 +0735 +071a +06fe +06e2 +06c5 +06a8 +068a +066d +064f +0630 +0612 +05f3 +05d4 +05b5 +0596 +0576 +0557 +0537 +0518 +04f8 +04d9 +04b9 +049a +047b +045b +043c +041d +03ff +03e0 +03c2 +03a4 +0386 +0368 +034b +032e +0312 +02f5 +02d9 +02be +02a3 +0288 +026e +0254 +023a +0221 +0209 +01f1 +01d9 +01c2 +01ab +0195 +0180 +016a +0156 +0142 +012e +011b +0109 +00f7 +00e5 +00d4 +00c4 +00b4 +00a5 +0096 +0087 +007a +006c +005f +0053 +0047 +003c +0031 +0027 +001d +0013 +000a +0002 +fffa +fff2 +ffeb +ffe4 +ffdd +ffd7 +ffd2 +ffcc +ffc7 +ffc3 +ffbe +ffba +ffb7 +ffb4 +ffb1 +ffae +ffab +ffa9 +ffa7 +ffa6 +ffa4 +ffa3 +ffa2 +ffa2 +ffa1 +ffa1 +ffa1 +ffa1 +ffa1 +ffa1 +ffa2 +ffa2 +ffa3 +ffa4 +ffa5 +ffa6 +ffa7 +ffa8 +ffaa +ffab +ffac +ffae +ffb0 +ffb1 +ffb3 +ffb5 +ffb6 +ffb8 +ffba +ffbc +ffbe +ffc0 +ffc2 +ffc3 +ffc5 +ffc7 +ffc9 +ffcb +ffcd +ffce +ffd0 +ffd2 +ffd4 +ffd5 +ffd7 +ffd9 +ffda +ffdc +ffdd +ffdf +ffe0 +ffe2 +ffe3 +ffe4 +ffe6 +ffe7 +ffe8 +ffe9 +ffeb +ffec +ffed +ffee +ffef +fff0 +fff1 +fff2 +fff2 +fff3 +fff4 +fff5 +fff5 +fff6 +fff7 +fff7 +fff8 +fff9 +fff9 +fffa +fffa +fffa +fffb +fffb +fffc +fffc +fffc +fffd +fffd +fffd +fffe +fffe +fffe +fffe +fffe +ffff +ffff +ffff +ffff +ffff +ffff +ffff +ffff diff --git a/source/fir_filter.sv b/source/fir_filter.sv new file mode 100644 index 0000000..c06508b --- /dev/null +++ b/source/fir_filter.sv @@ -0,0 +1,123 @@ +`timescale 1ns/100ps +`default_nettype none + +module fir_filter #(parameter LEN = 449, + parameter COEFFS_ROM_FILE = "fir_449_50hz_100hz_10db_40db.rom") + (input wire clock, + input wire reset, + + input wire signed [15:0] data_i, + input wire ready_i, + + output wire signed [15:0] data_o, + output reg valid_o); + + localparam LEN_CW = $clog2(LEN); + localparam MEM_LEN = 1 << LEN_CW; + + /* Coeffs */ + logic signed [15:0] coeffs[LEN]; + initial $readmemh(COEFFS_ROM_FILE, coeffs, 0, LEN-1); + + logic signed [15:0] coeff; + logic [LEN_CW-1:0] coeff_addr; + + always_ff @ (posedge clock) + coeff <= coeffs[coeff_addr]; + + /* Z-1 BlockRAM */ + logic signed [15:0] mem[MEM_LEN]; + logic signed [15:0] mem_wdata; + logic signed [15:0] mem_rdata; + logic [LEN_CW-1:0] mem_addr; + logic mem_wr; + + always_ff @ (posedge clock) begin + if (mem_wr) + mem[mem_addr] <= mem_wdata; + mem_rdata <= mem[mem_addr]; + end + + initial begin + integer i; + for (i = 0; i < MEM_LEN; i++) + mem[i] = '0; + end + + /* MAC */ + logic [31:0] mac_o; + logic signed [15:0] a; + logic signed [15:0] b; + logic signed [15:0] s; + + ice40_mac16x16 #(.SIGNED(1)) mac + (.clock, .reset, + .a(a), + .b(b), + .s({s, 16'b0}), + .sub(1'b0), + .y(mac_o)); + + /* FSM */ + enum int unsigned { + ST_IDLE = 0, + ST_WRITE, + ST_CONV, + ST_DONE + } state; + + logic [LEN_CW-1:0] new_addr; + + assign mem_wdata = data_i; + assign data_o = s; + assign s = coeff_addr == '0 ? '0 : mac_o[31:16]; + + always_ff @ (posedge clock, posedge reset) + if (reset) begin + state <= ST_IDLE; + new_addr <= '0; + mem_wr <= 1'b0; + valid_o <= 1'b0; + end + else + case (state) + ST_IDLE: begin + a <= '0; + b <= '0; + mem_addr <= new_addr; + coeff_addr <= '0; + + if (ready_i) begin + mem_wr <= 1'b1; + state <= ST_WRITE; + end + end + + ST_WRITE: begin + mem_wr <= 1'b0; + state <= ST_CONV; + end + + ST_CONV: begin + a <= mem_rdata; + b <= coeff; + + if (coeff_addr == LEN_CW'(LEN-1)) begin + valid_o <= 1'b1; + state <= ST_DONE; + end + else begin + coeff_addr <= coeff_addr + 1'b1; + mem_addr <= mem_addr - 1'b1; + state <= ST_CONV; + end + end + + ST_DONE: begin + new_addr <= new_addr + 1'b1; + valid_o <= 1'b0; + state <= ST_IDLE; + end + endcase + +endmodule // fir_filter diff --git a/source/hsl2rgb.sv b/source/hsl2rgb.sv new file mode 100644 index 0000000..6a0488d --- /dev/null +++ b/source/hsl2rgb.sv @@ -0,0 +1,239 @@ +`timescale 1ns/100ps +`default_nettype none + +/** + * HSL to RGB translation. + * + * H, S, L = [0..1) + * + * Q = | L < 0.5 ? L + L*S + * | L >= 0.5 ? L + S - L*S + * + * P = 2 * L - Q + * + * TR = H < 2/3 ? H + 1/3 : 1/3 - (1 - H) + * TG = H + * TB = H >= 1/3 ? H - 1/3 : 1 - H + * + * COLORX = | TX < 1/6 ? P + ((Q - P) * 6 * TX) + * | 1/6 <= TX < 1/2 ? Q + * | 1/2 <= TX < 2/3 ? P + ((Q - P) * (2/3 - TX) * 6) + * | else : P + */ + +/* + * Datapath: + * + * if l < [1/2] + * then: lls = l + (l * s) + * else: lls = l - (l * s) + * + * m1h = ~(h - 1) + * + * tr = + * if h < [2/3] + * then: h + [1/3] + * else: [1/3] - m1h + * + * tg = h + * + * tb = + * if h >= [1/3] + * then: h - [1/3] + * else: m1h + * + * q = + * if l < [1/2] + * then: lls + * else: lls + s + * + * p = l * 2 - q + * qp = (q - l) * 2 + * + * r = + * p + 6 * qp * tr + * p + 6 * qp * ([2/3] - tr) + * + * g = + * p + 6 * qp * tg + * p + 6 * qp * ([2/3] - tg) + * + * b = + * p + 6 * qp * tb + * p + 6 * qp * ([2/3] - tb) + */ + +module hsl2rgb + (input wire clock, + input wire reset, + + input wire [7:0] h, + input wire [7:0] s, + input wire [7:0] l, + input wire ready_i, + + output reg [7:0] r, + output reg [7:0] g, + output reg [7:0] b, + output wire valid_o); + +`define C1_6 43 +`define C1_3 85 +`define C1_2 128 +`define C2_3 171 +`define C1_1 256 + + localparam STAGES = 5; + logic [STAGES-1:0] valid; + + assign valid_o = valid[STAGES-1]; + + always_ff @ (posedge clock, posedge reset) + if (reset) + valid <= '0; + else + valid <= { valid[STAGES-2:0], ready_i }; + + /* ---------------- Stage 1 ---------------- */ + logic [8:0] lls; // lls = l0 ± (l0 * s0) + logic ls_sub; + + /* verilator lint_off UNUSED */ + logic [31:0] mac_lls_o; + /* verilator lint_on UNUSED */ + + assign lls = {mac_lls_o[16:8]}; + assign ls_sub = l < `C1_2 ? 1'b0 : 1'b1; + + ice40_mac16x16 mac_lls + (.clock, .reset, + .a({8'b0, l}), + .b({8'b0, s}), + .s({16'b0, l, 8'b0}), + .sub(ls_sub), + .y(mac_lls_o)); + + /* propagate to next stage */ + logic [7:0] h1, s1, l1; + always_ff @ (posedge clock) begin + h1 <= h; + s1 <= s; + l1 <= l; + end + + /* ---------------- stage 2 ---------------- */ + logic [8:0] q_pre; + logic [7:0] q; + logic [7:0] minus1h; // minus1h = 256 - h = ~(h - 1) + + always_ff @ (posedge clock) begin + q_pre <= l1 < `C1_2 ? lls : lls + s1; + minus1h <= ~(h1 - 1); + end + + assign q = q_pre[8] ? 8'hff : q_pre[7:0]; + + /* propagate to next stage */ + logic [7:0] h2, l2; + always_ff @ (posedge clock) begin + h2 <= h1; + l2 <= l1; + end + + /* ---------------- stage 3 ---------------- */ + logic [7:0] tr, tg, tb; + logic [8:0] p_pre; + logic [7:0] p; // p = l * 2 - q + logic [7:0] qp; // qp = q - p + + always_ff @ (posedge clock) begin + tr <= h2 < 8'(`C2_3) ? h2 + 8'(`C1_3) : 8'(`C1_3) - minus1h; + tg <= h2; + tb <= h2 >= 8'(`C1_3) ? h2 - 8'(`C1_3) : minus1h; + + p_pre <= (9'(l2) << 1) - q; + qp <= 8'((q - l2) << 1); + end + + assign p = p_pre[8] ? 8'hff : p_pre[7:0]; + + /* propagate to next stage */ + logic [7:0] q3; + always_ff @ (posedge clock) + q3 <= q; + + /* ---------------- stage 4 ---------------- */ + logic [7:0] trx, tgx, tbx; + logic [10:0] qp6; + + /* verilator lint_off UNUSED */ + logic [31:0] mac_r_o; + logic [31:0] mac_g_o; + logic [31:0] mac_b_o; + /* verilator lint_on UNUSED */ + + assign qp6 = (11'(qp) << 1) + (11'(qp) << 2); + assign trx = (tr < `C1_6) ? tr : `C2_3 - tr; + assign tgx = (tg < `C1_6) ? tg : `C2_3 - tg; + assign tbx = (tb < `C1_6) ? tb : `C2_3 - tb; + + ice40_mac16x16 mac_r + (.clock, .reset, + .a({5'b0, qp6}), + .b({8'b0, trx}), + .s({16'b0, p, 8'b0}), + .sub(1'b0), + .y(mac_r_o)); + + ice40_mac16x16 mac_g + (.clock, .reset, + .a({5'b0, qp6}), + .b({8'b0, tgx}), + .s({16'b0, p, 8'b0}), + .sub(1'b0), + .y(mac_g_o)); + + ice40_mac16x16 mac_b + (.clock, .reset, + .a({5'b0, qp6}), + .b({8'b0, tbx}), + .s({16'b0, p, 8'b0}), + .sub(1'b0), + .y(mac_b_o)); + + /* propagate to next stage */ + logic [7:0] tr4, tg4, tb4; + logic [7:0] p4; + logic [7:0] q4; + + always_ff @ (posedge clock) begin + tr4 <= tr; + tg4 <= tg; + tb4 <= tb; + p4 <= p; + q4 <= q3; + end + + /* ---------------- stage 5 ---------------- */ + always_ff @ (posedge clock) begin + if (tr4 < `C1_6) r <= mac_r_o[16] ? 8'hff : mac_r_o[15:8]; + else if (tr4 < `C1_2) r <= q4; + else if (tr4 < `C2_3) r <= mac_r_o[16] ? 8'hff : mac_r_o[15:8]; + else r <= p4; + end + + always_ff @ (posedge clock) begin + if (tg4 < `C1_6) g <= mac_g_o[16] ? 8'hff : mac_g_o[15:8]; + else if (tg4 < `C1_2) g <= q4; + else if (tg4 < `C2_3) g <= mac_g_o[16] ? 8'hff : mac_g_o[15:8]; + else g <= p4; + end + + always_ff @ (posedge clock) begin + if (tb4 < `C1_6) b <= mac_b_o[16] ? 8'hff : mac_b_o[15:8]; + else if (tb4 < `C1_2) b <= q4; + else if (tb4 < `C2_3) b <= mac_b_o[16] ? 8'hff : mac_b_o[15:8]; + else b <= p4; + end + +endmodule // hsl2rgb diff --git a/source/hsv2rgb.sv b/source/hsv2rgb.sv new file mode 100644 index 0000000..f5f91be --- /dev/null +++ b/source/hsv2rgb.sv @@ -0,0 +1,98 @@ +`timescale 1ns/100ps +`default_nettype none + +module hsv2rgb + (input wire clock, + input wire reset, + + input wire [7:0] h, + input wire [7:0] s, + input wire [7:0] v, + input wire ready_i, + + output reg [7:0] r, + output reg [7:0] g, + output reg [7:0] b, + output wire valid_o); + + localparam STAGES = 2; + logic [STAGES-1:0] valid; + + assign valid_o = valid[STAGES-1]; + always_ff @ (posedge clock, posedge reset) + if (reset) valid <= '0; + else valid <= { valid[STAGES-2:0], ready_i }; + + /* ---------------- Stage 1 ---------------- */ + logic [7:0] flip_s; + logic [7:0] vmin; + logic [31:0] mac_vmin_o; + logic [5:0] h_mod_43; + + assign flip_s = 8'd255 - s; + assign vmin = mac_vmin_o[15:8]; + + always_ff @ (posedge clock) + h_mod_43 + <= (h < 43) ? 6'(h) : + (h < 86) ? 6'(h - 8'd43) : + (h < 128) ? 6'(h - 8'd86) : + (h < 171) ? 6'(h - 8'd128) : + (h < 214) ? 6'(h - 8'd171) : + 6'(h - 8'd214); + + ice40_mac16x16 mac_lls + (.clock, .reset, + .a({8'b0, flip_s}), + .b({8'b0, v}), + .s(32'b0), + .sub(1'b0), + .y(mac_vmin_o)); + + logic [7:0] h1, v1; + always_ff @ (posedge clock) begin + h1 <= h; + v1 <= v; + end + + /* ---------------- Stage 2 ---------------- */ + logic [31:0] mac_a_o; + logic [7:0] h_mod_43_6; + logic [7:0] v_vmin; + logic [7:0] a; + + assign a = mac_a_o[15:8]; + + assign h_mod_43_6 = (8'(h_mod_43) << 1) + (8'(h_mod_43) << 2); + assign v_vmin = v1 - vmin; + + ice40_mac16x16 mac_a + (.clock, .reset, + .a({8'b0, v_vmin}), + .b({8'b0, h_mod_43_6}), + .s(32'b0), + .sub(1'b0), + .y(mac_a_o)); + + logic [7:0] h2, v2, vmin2; + always_ff @ (posedge clock) begin + h2 <= h1; + v2 <= v1; + vmin2 <= vmin; + end + + /* ---------------- Output ---------------- */ + logic [7:0] vinc, vdec; + + assign vinc = vmin2 + a; + assign vdec = v2 - a; + + always_comb + if (h2 < 43) {r, g, b} = {v2, vinc, vmin2}; + else if (h2 < 86) {r, g, b} = {vdec, v2, vmin2}; + else if (h2 < 128) {r, g, b} = {vmin2, v2, vinc}; + else if (h2 < 171) {r, g, b} = {vmin2, vdec, v2}; + else if (h2 < 214) {r, g, b} = {vinc, vmin2, v2}; + else {r, g, b} = {v2, vmin2, vdec}; + +endmodule // hsv2rgb diff --git a/source/hsx2rgb.rkt b/source/hsx2rgb.rkt new file mode 100644 index 0000000..6414f5a --- /dev/null +++ b/source/hsx2rgb.rkt @@ -0,0 +1,135 @@ +#lang racket + +(define (hsl2rgb h s l) + (define (color q p h) + (let ((tc (cond + ((> h 1) (- h 1)) + ((< h 0) (+ h 1)) + (else h)))) + (cond + ((< tc 1/6) (+ p (* (- q p) tc 6))) + ((< tc 1/2) q) + ((< tc 2/3) (+ p (* (- q p) (- 2/3 tc) 6))) + (else p)))) + (let* ((q (if (< l 0.5) + (* l (+ 1 s)) + (+ l s (- (* l s))))) + (p (- (* 2 l) q))) + (values + (inexact->exact (round (* 256 (color q p (+ h 1/3))))) + (inexact->exact (round (* 256 (color q p h)))) + (inexact->exact (round (* 256 (color q p (- h 1/3)))))))) + +(define (hsl2rgb-ref h s l) + (hsl2rgb (/ h 256) (/ s 256) (/ l 256))) + +(define (hsv2rgb h s v) + (let* ((hi (modulo (floor (/ h 256/6)) 6)) + (vmin (/ (* v (- 256 s)) 256)) + (a (* (- v vmin) (/ (modulo h 43) 256/6))) + (vinc (+ vmin a)) + (vdec (- v a)) + + (vmin (inexact->exact (round vmin))) + (vinc (inexact->exact (round vinc))) + (vdec (inexact->exact (round vdec)))) + (cond + ((= hi 0) (values v vinc vmin)) + ((= hi 1) (values vdec v vmin)) + ((= hi 2) (values vmin v vinc)) + ((= hi 3) (values vmin vdec v)) + ((= hi 4) (values vinc vmin v)) + ((= hi 5) (values v vmin vdec))))) + +(define (hsv2rgb-int h s v) + (define (byte-div-43-mod-6 x) + (cond + ((< x 43) 0) + ((< x 86) 1) + ((< x 128) 2) + ((< x 171) 3) + ((< x 214) 4) + (else 5))) + + (define (byte-mod-43 x) + (cond + ((< x 43) x) + ((< x 86) (- x 43)) + ((< x 128) (- x 86)) + ((< x 171) (- x 128)) + ((< x 214) (- x 171)) + (else (- x 214)))) + + (define (*6 a) (+ (arithmetic-shift a 1) + (arithmetic-shift a 2))) + + (let* ((hi (byte-div-43-mod-6 h)) + (vmin (arithmetic-shift + (* (- 255 s) v) -8)) + (a (arithmetic-shift + (* (- v vmin) + (*6 (byte-mod-43 h))) -8)) + (vinc (+ vmin a)) + (vdec (- v a))) + (printf "hi=~a, vmin=~a, a=~a, vinc=~a, vdec=~a\n" hi vmin a vinc vdec) + (cond + ((= hi 0) (values v vinc vmin)) + ((= hi 1) (values vdec v vmin)) + ((= hi 2) (values vmin v vinc)) + ((= hi 3) (values vmin vdec v)) + ((= hi 4) (values vinc vmin v)) + ((= hi 5) (values v vmin vdec))))) + +(define (hsl2rgb-int h s l) + (define (*fp8 a b) (arithmetic-shift (* a b) -8)) + (define (*2 a) (arithmetic-shift a 1)) + (define (*6 a) (+ (arithmetic-shift a 1) + (arithmetic-shift a 2))) + + (define c1/6 43) + (define c1/3 85) + (define c1/2 128) + (define c2/3 171) + (define c1 256) + + (let* ((l*s (*fp8 l s)) + (q (if (< l c1/2) + (+ l l*s) + (+ l s (- l*s)))) + (p (- (*2 l) q)) + (q-p ;;(- q p) + (*2 (- q l))) + (tr (if (< h c2/3) [+ h c1/3] [- c1/3 (- c1 h)])) + (tg h) + (tb (if (>= h c1/3) [- h c1/3] [- c1 h])) + (q-p*6 (*6 q-p)) + + (r (cond + ((< tr c1/6) [+ p (*fp8 q-p*6 tr)]) + ((< tr c1/2) q) + ((< tr c2/3) [+ p (*fp8 q-p*6 (- c2/3 tr))]) + (else p))) + + (g (cond + ((< tg c1/6) [+ p (*fp8 q-p*6 tg)]) + ((< tg c1/2) q) + ((< tg c2/3) [+ p (*fp8 q-p*6 (- c2/3 tg))]) + (else p))) + + (b (cond + ((< tb c1/6) [+ p (*fp8 q-p*6 tb)]) + ((< tb c1/2) q) + ((< tb c2/3) [+ p (*fp8 q-p*6 (- c2/3 tb))]) + (else p)))) + ;; (printf "q=~a p=~a q-p=~a tr=~a tg=~a tb=~a\n" q p q-p tr tg tb) + (values r g b))) + +(define (p f h s x) + (let-values (((r g b) (f h s x))) + (printf "HSX ~a ~a ~a -> " h s x) + (printf "RGB ~a ~a ~a\n\n" r g b))) + +;;(p 20 255 100) +(p hsv2rgb-int 50 100 150) +(p hsv2rgb-int 111 222 33) +(p hsv2rgb-int 200 150 50) diff --git a/source/ice40_2mac8x8.sv b/source/ice40_2mac8x8.sv new file mode 100644 index 0000000..d555646 --- /dev/null +++ b/source/ice40_2mac8x8.sv @@ -0,0 +1,81 @@ +`timescale 1ns/100ps +`default_nettype none + +/* verilator lint_off PINCONNECTEMPTY */ + +module ice40_2mac8x8 #(parameter SIGNED = 0) + (input wire clock, + input wire reset, + input wire [7:0] a0, + input wire [7:0] b0, + input wire [15:0] s0, + input wire sub0, + output wire [15:0] y0, + + input wire [7:0] a1, + input wire [7:0] b1, + input wire [15:0] s1, + input wire sub1, + output wire [15:0] y1); + + /* register 'sub' input */ + logic sub0_r, sub1_r; + always_ff @ (posedge clock) begin + sub0_r <= sub0; + sub1_r <= sub1; + end + + logic [31:0] mac_o; + assign {y0, y1} = mac_o; + + SB_MAC16 + #(.NEG_TRIGGER(1'b0), + .C_REG(1'b1), // Registered C + .A_REG(1'b1), // Registered A + .B_REG(1'b1), // Registered B + .D_REG(1'b1), // Registered D + .TOP_8x8_MULT_REG(1'b0), + .BOT_8x8_MULT_REG(1'b0), + .PIPELINE_16x16_MULT_REG1(1'b0), + .PIPELINE_16x16_MULT_REG2(1'b0), + .TOPOUTPUT_SELECT(2'b00), // TOP output - ADD/SUB unregistered + .TOPADDSUB_LOWERINPUT(2'b01), // TOP adder input 1 - 8x8 top multiplier output + .TOPADDSUB_UPPERINPUT(1'b1), // TOP adder input 2 - input C + .TOPADDSUB_CARRYSELECT(2'b00), // TOP adder carry input - constant 0 + .BOTOUTPUT_SELECT(2'b00), // BOT output - ADD/SUB unregistered + .BOTADDSUB_LOWERINPUT(2'b01), // BOT adder input 1 - 8x8 bot multiplier output + .BOTADDSUB_UPPERINPUT(1'b1), // BOT adder input 2 - input D + .BOTADDSUB_CARRYSELECT(2'b00), // BOT adder carry input - constant 0 + .MODE_8x8(1'b1), + .A_SIGNED(SIGNED), + .B_SIGNED(SIGNED)) + mac_r + (.CLK(clock), + .CE(1'b1), + .C(s0), + .A({a0, a1}), + .B({b0, b1}), + .D(s1), + .AHOLD(1'b0), + .BHOLD(1'b0), + .CHOLD(1'b0), + .DHOLD(1'b0), + .IRSTTOP(reset), + .IRSTBOT(reset), + .ORSTTOP(reset), + .ORSTBOT(reset), + .OLOADTOP(1'b0), + .OLOADBOT(1'b0), + .ADDSUBTOP(sub0_r), + .ADDSUBBOT(sub1_r), + .OHOLDTOP(1'b0), + .OHOLDBOT(1'b0), + .CI(1'b0), + .ACCUMCI(1'b0), + .SIGNEXTIN(1'b0), + .O(mac_o), + .CO(), + .ACCUMCO(), + .SIGNEXTOUT()); + +endmodule // ice40_macadd16x16 diff --git a/source/ice40_mac16x16.sv b/source/ice40_mac16x16.sv new file mode 100644 index 0000000..a407f12 --- /dev/null +++ b/source/ice40_mac16x16.sv @@ -0,0 +1,70 @@ +`timescale 1ns/100ps +`default_nettype none + +/* verilator lint_off PINCONNECTEMPTY */ + +module ice40_mac16x16 #(parameter SIGNED = 0) + (input wire clock, + input wire reset, + input wire [15:0] a, + input wire [15:0] b, + input wire [31:0] s, + input wire sub, + output wire [31:0] y); + + /* register 'sub' input */ + logic sub_r; + always_ff @ (posedge clock) + sub_r <= sub; + + SB_MAC16 + #(.NEG_TRIGGER(1'b0), + .C_REG(1'b1), // Registered C + .A_REG(1'b1), // Registered A + .B_REG(1'b1), // Registered B + .D_REG(1'b1), // Registered D + .TOP_8x8_MULT_REG(1'b0), + .BOT_8x8_MULT_REG(1'b0), + .PIPELINE_16x16_MULT_REG1(1'b0), + .PIPELINE_16x16_MULT_REG2(1'b0), + .TOPOUTPUT_SELECT(2'b00), // TOP output - ADD/SUB unregistered + .TOPADDSUB_LOWERINPUT(2'b10), // TOP adder input 1 - 16x16 multiplier upper word + .TOPADDSUB_UPPERINPUT(1'b1), // TOP adder input 2 - input C + .TOPADDSUB_CARRYSELECT(2'b00), // TOP adder carry input - constant 0 + .BOTOUTPUT_SELECT(2'b00), // BOT output - ADD/SUB unregistered + .BOTADDSUB_LOWERINPUT(2'b10), // BOT adder input 1 - 16x16 multiplier lower word + .BOTADDSUB_UPPERINPUT(1'b1), // BOT adder input 2 - input D + .BOTADDSUB_CARRYSELECT(2'b00), // BOT adder carry input - constant 0 + .MODE_8x8(1'b0), + .A_SIGNED(SIGNED), + .B_SIGNED(SIGNED)) + mac_r + (.CLK(clock), + .CE(1'b1), + .C(s[31:16]), + .A(a), + .B(b), + .D(s[15:0]), + .AHOLD(1'b0), + .BHOLD(1'b0), + .CHOLD(1'b0), + .DHOLD(1'b0), + .IRSTTOP(reset), + .IRSTBOT(reset), + .ORSTTOP(reset), + .ORSTBOT(reset), + .OLOADTOP(1'b0), + .OLOADBOT(1'b0), + .ADDSUBTOP(sub_r), + .ADDSUBBOT(sub_r), + .OHOLDTOP(1'b0), + .OHOLDBOT(1'b0), + .CI(1'b0), + .ACCUMCI(1'b0), + .SIGNEXTIN(1'b0), + .O(y), + .CO(), + .ACCUMCO(), + .SIGNEXTOUT()); + +endmodule // ice40_macadd16x16 diff --git a/source/ice40_spram.sv b/source/ice40_spram.sv new file mode 100644 index 0000000..2b3d055 --- /dev/null +++ b/source/ice40_spram.sv @@ -0,0 +1,94 @@ +`timescale 1ns/100ps +`default_nettype none + +module ice40_spram + (input wire clock, + input wire [15:0] addr, + input wire [15:0] data_i, + output wire [15:0] data_o, + input wire wr); + + logic [15:0] data0_o; + logic [15:0] data1_o; + logic [15:0] data2_o; + logic [15:0] data3_o; + logic w0, w1, w2, w3; + + logic [15:0] datax_o; + assign data_o = datax_o; + + always @(*) begin + {w0, w1, w2, w3} = '0; + + case (addr[15:14]) + 2'd0: begin + datax_o = data0_o; + w0 = wr; + end + + 2'd1: begin + datax_o = data1_o; + w1 = wr; + end + + 2'd2: begin + datax_o = data2_o; + w2 = wr; + end + + 2'd3: begin + datax_o = data3_o; + w3 = wr; + end + endcase + end + + SB_SPRAM256KA spram0 + (.CLOCK(clock), + .ADDRESS(addr[13:0]), + .DATAIN(data_i), + .DATAOUT(data0_o), + .WREN(w0), + .MASKWREN({w0, w0, w0, w0}), + .CHIPSELECT(1'b1), + .STANDBY(1'b0), + .SLEEP(1'b0), + .POWEROFF(1'b1)); + + SB_SPRAM256KA spram1 + (.CLOCK(clock), + .ADDRESS(addr[13:0]), + .DATAIN(data_i), + .DATAOUT(data1_o), + .WREN(w1), + .MASKWREN({w1, w1, w1, w1}), + .CHIPSELECT(1'b1), + .STANDBY(1'b0), + .SLEEP(1'b0), + .POWEROFF(1'b1)); + + SB_SPRAM256KA spram2 + (.CLOCK(clock), + .ADDRESS(addr[13:0]), + .DATAIN(data_i), + .DATAOUT(data2_o), + .WREN(w2), + .MASKWREN({w2, w2, w2, w2}), + .CHIPSELECT(1'b1), + .STANDBY(1'b0), + .SLEEP(1'b0), + .POWEROFF(1'b1)); + + SB_SPRAM256KA spram3 + (.CLOCK(clock), + .ADDRESS(addr[13:0]), + .DATAIN(data_i), + .DATAOUT(data3_o), + .WREN(w3), + .MASKWREN({w3, w3, w3, w3}), + .CHIPSELECT(1'b1), + .STANDBY(1'b0), + .SLEEP(1'b0), + .POWEROFF(1'b1)); + +endmodule // ice40_spram diff --git a/source/lcd_init.rom b/source/lcd_init.rom new file mode 100644 index 0000000..3da98c0 --- /dev/null +++ b/source/lcd_init.rom @@ -0,0 +1,80 @@ +0CF // ---- Power control B +100 // default +1C9 // (or C1) непонятно что это. По документу вообще нужно 89 или 81 +130 // default + +0ED // ---- Power on sequence +164 // ? +103 // ? +112 // ? +181 // DDVDH enhance mode + +0E8 // ---- Driver timing control A +185 // Gate driver overlap timing +1unit (?) +110 // ? +17A // Precharge (default) + +0CB // ---- Power control A +139 // default +12C // default +100 // default +134 // default (Vcore = 1.6V) +102 // default (DDVDH = 5.6V) + +0F7 // ---- Pump ratio control +120 // DDVDH = 2xVCI + +0EA // ---- Driver timing control B +100 // ? +100 // default + +0C0 // ---- Power control 1 +11B // VRH = 4.2V + +0C1 // ---- Power control 2 +100 // ? + +0C5 // ---- VCOM control 1 +130 // (or 3F) VCOMH = 3.9V +130 // (or 3C) VCOML = -1.3V + +0C7 // ---- VCOM control 2 +1B7 // VCOMH = VMH-9, VCOML = VML-9 + +036 // ---- Memory Access Control +108 // RGB reverse + +03A // ---- Pixel format set +155 // 66 - 18 bit per pixel, 55 - 16 bit per pixel + +0F6 // ---- Interface control +101 // default +110 // EPF=01 (RGB mapping to 16-bit word) + +0B1 // ---- Frame rate control +100 // fosc/1 +11A // 26 clock per line (default - 1B) + +0B6 // ---- Display Function Control (отсутствуют два параметра) +10A // default +1A2 // Reverse shift direction + +0F2 // ---- 3Gamma Function Disable +100 + +026 // ---- Gamma curve +101 // default + +02B // ---- Page address set 0..319 +100 +100 +101 +13f + +02A // ---- Column address set 0..239 +100 +100 +100 +1ef + +011 // ---- Exit Sleep diff --git a/source/lcd_spi.sv b/source/lcd_spi.sv new file mode 100644 index 0000000..9dcc2c0 --- /dev/null +++ b/source/lcd_spi.sv @@ -0,0 +1,98 @@ +`timescale 1ns/100ps +`default_nettype none + +`include "assert.vh" + +module lcd_spi #(parameter DATA_WIDTH = 8, + parameter SPI_CLK_PERIOD = 10, + parameter PUSH_ON_DONE = 0) + (input wire clock, + input wire reset, + + input wire [DATA_WIDTH-1:0] data_i, + input wire push_i, + output reg done_o, + + output reg spi_clk_o, + output wire spi_dat_o); + + initial begin + `assert(DATA_WIDTH > 0); + `assert(SPI_CLK_PERIOD >= 2); + end + + localparam SCLK_LOW = SPI_CLK_PERIOD / 2; + + logic [$clog2(SPI_CLK_PERIOD)-1:0] sclk_cntr; + logic sclk_one; + logic sclk_nededge; + logic do_push; + + assign sclk_one = (sclk_cntr < SCLK_LOW) ? 1'b0 : 1'b1; + + always_ff @ (posedge clock, posedge reset) + if (reset) begin + sclk_cntr <= '0; + sclk_nededge <= 1'b0; + spi_clk_o <= 1'b0; + end + else begin + sclk_nededge <= 1'b0; + spi_clk_o <= do_push ? sclk_one : 1'b0; + + if (do_push || sclk_one) begin + if (sclk_cntr == (SPI_CLK_PERIOD-1)) begin + sclk_cntr <= '0; + sclk_nededge <= 1'b1; + end + else + sclk_cntr <= sclk_cntr + 1'b1; + end + else + sclk_cntr <= '0; + end + + localparam BIT_CW = $clog2(DATA_WIDTH); + logic [BIT_CW-1:0] sbit_cntr; + logic [DATA_WIDTH-1:0] data_sr; + + assign spi_dat_o = data_sr[DATA_WIDTH-1]; + + logic push; + + generate + if (PUSH_ON_DONE == 0) + assign push = push_i & done_o; + else + assign push = push_i; + endgenerate + + always_ff @(posedge clock, posedge reset) + if (reset) begin + do_push <= 1'b0; + done_o <= 1'b0; + end + else begin + if (do_push) begin + if (sclk_nededge) begin + data_sr <= data_sr << 1; + sbit_cntr <= sbit_cntr + 1'b1; + + if (sbit_cntr == BIT_CW'(DATA_WIDTH-1)) begin + do_push <= 1'b0; + done_o <= 1'b1; + end + end + end + else begin + done_o <= 1'b0; + + if (push) begin + data_sr <= data_i; + sbit_cntr <= '0; + do_push <= 1'b1; + end + end + end + +endmodule // lcd_spi diff --git a/source/lcd_top.sv b/source/lcd_top.sv new file mode 100644 index 0000000..95ca379 --- /dev/null +++ b/source/lcd_top.sv @@ -0,0 +1,737 @@ +`timescale 1ns/100ps +`default_nettype none + +/* Yosys do not support SPRAM and MAC inferring */ +`define USE_SPRAM_PRIMITIVE +`define USE_MAC_PRIMITIVE + +module lcd_top #(parameter SPI_CLK_PERIOD = 6) + (input logic clock, + input logic reset, + + output logic lcd_spi_csn_o, + output logic lcd_spi_clk_o, + output logic lcd_spi_dat_o, + output logic lcd_spi_dcn_o, + + input logic redraw_i, + output logic done_o, + + input logic [7:0] x_i, + input logic [8:0] y_i, + input logic [15:0] color_i, + output logic [15:0] color_o, + input logic req_i, + output logic ack_o, + input logic wr_i); + + /* Display size */ + localparam DISPLAY_MEM_SIZE = 320 * 240 * 2; // 2 byte per pixel + localparam DISPLAY_MEM_CW = $clog2(DISPLAY_MEM_SIZE); + + /* Display x/y address width */ + localparam DISPLAY_XCW = 8, + DISPLAY_YCW = 9; + + /* Screen size and origin */ + localparam XSIZE = 240, + YSIZE = 272, + XORIG = 0, + YORIG = 24; + + localparam FBSIZE = XSIZE * YSIZE; + localparam FBCW = $clog2(FBSIZE); + + /* Drawing block size */ + localparam BLOCK_XS = 16, // must be power of 2 + BLOCK_YS = 16; // must be power of 2 + + localparam GRID_XS = XSIZE / BLOCK_XS, + GRID_YS = YSIZE / BLOCK_YS; + + localparam BLOCK_XS_CW = $clog2(BLOCK_XS); + localparam BLOCK_YS_CW = $clog2(BLOCK_YS); + + localparam GRID_XS_CW = $clog2(GRID_XS); + localparam GRID_YS_CW = $clog2(GRID_YS); + + localparam DFLAG_SIZE = GRID_XS * GRID_YS; + localparam DFLAG_CW = $clog2(DFLAG_SIZE); + + localparam BLOCK_SIZE = BLOCK_XS * BLOCK_YS; + localparam BLOCK_CW = $clog2(BLOCK_SIZE); + + /* --------- Drawing flags memory --------- */ + logic dflag[DFLAG_SIZE]; + logic dflag_wdata; + logic dflag_rdata; + logic [DFLAG_CW-1:0] dflag_waddr; + logic [DFLAG_CW-1:0] dflag_raddr; + logic [DFLAG_CW-1:0] dflag_int_addr; // for screen refresher + logic [DFLAG_CW-1:0] dflag_ext_addr; // for external master + logic dflag_wr; + logic dflag_set; + logic dflag_clr; + + assign dflag_wr = dflag_set | dflag_clr; + assign dflag_waddr = dflag_clr ? dflag_int_addr : dflag_ext_addr; + assign dflag_raddr = dflag_int_addr; + assign dflag_wdata = dflag_set; + + /* Infer as sysMEM Block RAM */ + always_ff @ (posedge clock) begin + if (dflag_wr) + dflag[dflag_waddr] <= dflag_wdata; + + dflag_rdata <= dflag[dflag_raddr]; + end + + /* --------- Frame buffer RAM (one-port block RAM)--------- */ + logic [FBCW-1:0] fbaddr; + logic [15:0] fb_rdata; + logic [15:0] fb_wdata; + logic fbwrite; + +`ifdef USE_SPRAM_PRIMITIVE + ice40_spram spram_i + (.clock(clock), + .addr(fbaddr), + .data_i(fb_wdata), + .data_o(fb_rdata), + .wr(fbwrite)); +`else + logic [15:0] fbram[FBSIZE]; + + always_ff @ (posedge clock) + if (fbwrite) begin + fbram[fbaddr] <= fb_wdata; + fb_rdata <= 'x; + end + else + fb_rdata <= fbram[fbaddr]; +`endif + + /* --------- Framebuffer arbiter --------- */ + logic [FBCW-1:0] fba_int; // Frame buffer address from refresher + logic [FBCW-1:0] fba_ext; // Frame buffer address from client + + logic fb_busy_int; + logic fb_busy_ext; + logic fb_clear; + + assign fbaddr = fb_busy_int ? fba_int : fba_ext; + assign color_o = fb_rdata; + assign fb_wdata = fb_clear ? '0 : color_i; + + enum int unsigned { + FBST_CLEAR = 0, + FBST_CLEAR_NEXT, + FBST_WAIT, + FBST_ADDR, + FBST_READ, + FBST_DONE + } fbst; + + logic [FBCW-1:0] ymult; + logic [DFLAG_CW-1:0] fmult; + +`ifdef USE_MAC_PRIMITIVE + wire [31:0] ymac_o; + wire [31:0] fmac_o; + + ice40_mac16x16 ymac_i + (.clock, .reset, + .a(16'(y_i)), + .b(16'(XSIZE)), + .s(32'b0), + .sub(1'b0), + .y(ymac_o)); + + ice40_mac16x16 fmac_i + (.clock, .reset, + .a(16'(y_i) >> BLOCK_YS_CW), + .b(16'(GRID_XS)), + .s(32'b0), + .sub(1'b0), + .y(fmac_o)); + + assign ymult = ymac_o[FBCW-1:0]; + assign fmult = fmac_o[DFLAG_CW-1:0]; +`endif + + always_ff @ (posedge clock, posedge reset) + if (reset) begin + fbst <= FBST_CLEAR; + ack_o <= 1'b0; + fbwrite <= 1'b0; + fb_busy_ext <= 1'b0; + dflag_set <= 1'b0; + end + else + case (fbst) + /* Clear frame buffer RAM */ + FBST_CLEAR: begin + fba_ext <= '0; + fbwrite <= 1'b1; + fb_busy_ext <= 1'b1; + fb_clear <= 1'b1; + fbst <= FBST_CLEAR_NEXT; + end + + FBST_CLEAR_NEXT: + if (fba_ext == (FBSIZE-1)) begin + fbwrite <= 1'b0; + fb_busy_ext <= 1'b0; + fb_clear <= 1'b0; + fbst <= FBST_WAIT; + end + else + fba_ext <= fba_ext + 1'b1; + + /* Main loop */ + FBST_WAIT: + if (req_i && !fb_busy_int) + if (x_i >= XSIZE || y_i >= YSIZE) begin + ack_o <= 1'b1; + fbst <= FBST_READ; + end + else begin + +`ifndef USE_MAC_PRIMITIVE + ymult <= y_i * XSIZE; + fmult <= GRID_YS_CW'(y_i >> BLOCK_YS_CW) * GRID_XS; +`endif + fb_busy_ext <= 1'b1; + fbst <= FBST_ADDR; + end + + FBST_ADDR: + if (fb_busy_int) begin + fbst <= FBST_WAIT; + fb_busy_ext <= 1'b0; + end + else begin + fba_ext <= ymult + FBCW'(x_i); + dflag_ext_addr <= fmult + DFLAG_CW'(x_i >> BLOCK_XS_CW); + + if (wr_i) begin + ack_o <= 1'b1; + fbwrite <= 1'b1; + dflag_set <= 1'b1; + fbst <= FBST_DONE; + end + else + fbst <= FBST_READ; + end + + FBST_READ: begin + ack_o <= 1'b1; + fbst <= FBST_DONE; + end + + FBST_DONE: begin + fbwrite <= 1'b0; + dflag_set <= 1'b0; + ack_o <= 1'b0; + fb_busy_ext <= 1'b0; + fbst <= FBST_WAIT; + end + endcase + + /* --------- Read initialization commands from file --------- */ + localparam INIT_FILE = "lcd_init.rom"; + localparam INIT_ROM_SIZE = 64; + localparam INIT_DATA_SIZE = 61; + localparam INIT_ROM_CW = $clog2(INIT_ROM_SIZE); + + logic [8:0] init_rom [INIT_ROM_SIZE]; + logic [INIT_ROM_CW-1:0] init_addr; + logic [8:0] init_data; + + initial $readmemh(INIT_FILE, init_rom, 0, INIT_DATA_SIZE-1); + + /* Block RAM as ROM */ + always_ff @ (posedge clock) + init_data <= init_rom[init_addr]; + + /* --------- SPI master --------- */ + logic [7:0] spi_data; + logic spi_push; + logic spi_done; + + lcd_spi #(.DATA_WIDTH(8), + .SPI_CLK_PERIOD(SPI_CLK_PERIOD), + .PUSH_ON_DONE(1)) spim_i + (.clock, .reset, + .data_i(spi_data), + .push_i(spi_push), + .done_o(spi_done), + .spi_clk_o(lcd_spi_clk_o), + .spi_dat_o(lcd_spi_dat_o)); + + /* --------- Main FSM --------- */ +`ifdef TESTBENCH + localparam INIT_DELAY = 250; +`else + // localparam INIT_DELAY = 7500000; + localparam INIT_DELAY = 250; +`endif + + enum int unsigned { + ST_PREINIT_DELAY = 0, // 0 + ST_INIT_PUSH_SPI, + ST_INIT_WAIT_SPI, + ST_INIT_WAIT_LAST, + ST_POSTINIT_DELAY, + ST_DISPLAY_ON, + ST_DISPLAY_ONW, + ST_SCRCLR_CMD, + ST_SCRCLR, + ST_SCRCLR_LAST, + ST_START_REDRAW, // 10 + ST_READ_FLAG, + ST_CHECK_FLAG, + ST_CLEAR_FLAG, + ST_CADDR_CMD, + ST_CADDR_0, + ST_CADDR_1, + ST_CADDR_2, + ST_CADDR_3, + ST_PADDR_CMD, + ST_PADDR_0, // 20 + ST_PADDR_1, + ST_PADDR_2, + ST_PADDR_3, + ST_WRITE_CMD, + ST_WRITE_CMDW, + ST_FB_READ, + ST_STORE_PIXEL, + ST_WRITE_PIXEL_H, + ST_WRITE_PIXEL_L, + ST_NEXT_PIXEL, // 30 + ST_NEXT_BLOCK + } state, next; + + always_ff @(posedge clock, posedge reset) + if (reset) state <= ST_PREINIT_DELAY; + else state <= next; + + /* Pre/post init delay counter */ + localparam INIT_DELAY_CW = $clog2(INIT_DELAY); + logic [INIT_DELAY_CW-1:0] delay_cntr; + logic delay_cntr_incr; + + always_ff @(posedge clock) + delay_cntr <= (~reset && delay_cntr_incr) ? + delay_cntr + 1'b1 : '0; + + /* Initialization ROM address */ + logic init_addr_rst; + logic init_addr_incr; + + always_ff @ (posedge clock) + if (reset || init_addr_rst) + init_addr <= '0; + else + if (init_addr_incr) + init_addr <= init_addr + 1'b1; + + /* LCD chipselect */ + logic spi_disable; + + always_ff @ (posedge clock) + if (reset || spi_disable) + lcd_spi_csn_o <= 1'b1; + else if (spi_push) + lcd_spi_csn_o <= 1'b0; + + /* LCD data/command */ + logic spi_do_cmd; + logic spi_do_data; + + always_ff @ (posedge clock) + // if (spi_do_cmd) + // lcd_spi_dcn_o <= 1'b0; + // else if (spi_do_data) + // lcd_spi_dcn_o <= 1'b1; + if (spi_do_cmd != spi_do_data) + lcd_spi_dcn_o <= spi_do_data; + + /* Redraw block coordinates */ + logic [GRID_XS_CW-1:0] bx; + logic [DISPLAY_XCW-1:0] borigx; + logic [DISPLAY_YCW-1:0] borigy; + logic [DISPLAY_XCW-1:0] borigx_e; + logic [DISPLAY_YCW-1:0] borigy_e; + + assign borigx_e = borigx + BLOCK_XS - 1; + assign borigy_e = borigy + BLOCK_YS - 1; + + logic [FBCW-1:0] fbr_orig; + + logic reset_redraw; + logic next_block; + logic is_last_blk; + + always_ff @ (posedge clock) + if (reset_redraw) begin + dflag_int_addr <= '0; + bx <= '0; + borigx <= XORIG; + borigy <= YORIG; + fbr_orig <= '0; + is_last_blk <= 1'b0; + end + else + if (next_block) begin + dflag_int_addr <= dflag_int_addr + 1'b1; + is_last_blk <= (dflag_int_addr == DFLAG_CW'(DFLAG_SIZE-1)) ? 1'b1 : 1'b0; + + if (bx == GRID_XS_CW'(GRID_XS - 1)) + begin + bx <= '0; + borigx <= XORIG; + borigy <= borigy + BLOCK_YS; + fbr_orig <= fbr_orig + BLOCK_XS + ((BLOCK_YS - 1) * XSIZE); + end + else + begin + bx <= bx + 1'b1; + borigx <= borigx + BLOCK_XS; + fbr_orig <= fbr_orig + BLOCK_XS; + end + end + + /* Framebuffer handling */ + logic [BLOCK_XS_CW-1:0] px; + logic [BLOCK_CW-1:0] pi; + logic [15:0] pixel; + + logic start_write; + logic read_pixel; + logic next_pixel; + + always_ff @ (posedge clock) + if (read_pixel) + pixel <= fb_rdata; + + always_ff @ (posedge clock) + if (start_write) begin + fba_int <= fbr_orig; + px <= '0; + pi <= '0; + end + else + if (next_pixel) begin + pi <= pi + 1'b1; + + if (px == BLOCK_XS_CW'(BLOCK_XS-1)) + begin + fba_int <= fba_int + XSIZE - BLOCK_XS + 1'b1; + px <= '0; + end + else + begin + fba_int <= fba_int + 1'b1; + px <= px + 1'b1; + end + end + + /* Column/page addresses map */ + logic [15:0] caddr_b, caddr_e; + logic [15:0] paddr_b, paddr_e; + + assign caddr_b = { (16-DISPLAY_XCW)'('0), borigx }; + assign caddr_e = { (16-DISPLAY_XCW)'('0), borigx_e }; + assign paddr_b = { (16-DISPLAY_YCW)'('0), borigy }; + assign paddr_e = { (16-DISPLAY_YCW)'('0), borigy_e }; + + /* Clear screen pixel counter */ + logic [DISPLAY_MEM_CW-1:0] scrclr_pcntr; + logic scrclr_incr; + + always_ff @ (posedge clock) + if (reset || init_addr_rst) + scrclr_pcntr <= '0; + else + if (scrclr_incr) + scrclr_pcntr <= scrclr_pcntr + 1'b1; + + /* FSM combinational block */ + always @(*) begin + next = state; + + /* FSM outputs default value */ + delay_cntr_incr = 1'b0; + init_addr_rst = 1'b0; + init_addr_incr = 1'b0; + scrclr_incr = 1'b0; + spi_push = 1'b0; + spi_data = '0; + spi_disable = 1'b0; + spi_do_cmd = 1'b0; + spi_do_data = 1'b0; + reset_redraw = 1'b0; + dflag_clr = 1'b0; + next_block = 1'b0; + start_write = 1'b0; + next_pixel = 1'b0; + read_pixel = 1'b0; + fb_busy_int = 1'b0; + done_o = 1'b0; + + case (state) + ST_PREINIT_DELAY: begin + delay_cntr_incr = 1'b1; + init_addr_rst = 1'b1; + + if (delay_cntr == INIT_DELAY) + next = ST_INIT_PUSH_SPI; + end + + ST_INIT_PUSH_SPI: begin + init_addr_incr = 1'b1; + spi_data = init_data[7:0]; + spi_push = 1'b1; + + {spi_do_cmd, spi_do_data} + = init_data[8] ? 2'b01 : 2'b10; + + if (init_addr == (INIT_DATA_SIZE-1)) + next = ST_INIT_WAIT_LAST; + else + next = ST_INIT_WAIT_SPI; + end + + ST_INIT_WAIT_SPI: + if (spi_done) + next = ST_INIT_PUSH_SPI; + + ST_INIT_WAIT_LAST: + if (spi_done) + next = ST_POSTINIT_DELAY; + + ST_POSTINIT_DELAY: begin + spi_disable = 1'b1; + delay_cntr_incr = 1'b1; + + if (delay_cntr == INIT_DELAY) + next = ST_DISPLAY_ON; + end + + ST_DISPLAY_ON: begin + spi_data = 8'h29; + spi_do_cmd = 1'b1; + spi_push = 1'b1; + + next = ST_DISPLAY_ONW; + end + + ST_DISPLAY_ONW: + if (spi_done) begin + spi_disable = 1'b1; + next = ST_SCRCLR_CMD; + end + + ST_SCRCLR_CMD: begin + spi_data = 8'h2c; + spi_do_cmd = 1'b1; + spi_push = 1'b1; + next = ST_SCRCLR; + end + + ST_SCRCLR: + if (spi_done) begin + spi_data = 8'h00; //8'h32; + spi_do_data = 1'b1; + spi_push = 1'b1; + scrclr_incr = 1'b1; + + if (scrclr_pcntr == +`ifdef TESTBENCH + `ifdef VERILATOR + (DISPLAY_MEM_SIZE - 1) + `else + 200 + `endif +`else + (DISPLAY_MEM_SIZE - 1) +`endif + ) + next = ST_SCRCLR_LAST; + end + + ST_SCRCLR_LAST: + if (spi_done) + next = ST_START_REDRAW; + + ST_START_REDRAW: + if (redraw_i) begin + spi_disable = 1'b1; + reset_redraw = 1'b1; + next = ST_READ_FLAG; + end + + ST_READ_FLAG: + if (is_last_blk) begin + done_o = 1'b1; + next = ST_START_REDRAW; + end + else + next = ST_CHECK_FLAG; + + ST_CHECK_FLAG: + if (dflag_rdata == 1'b1) + next = ST_CLEAR_FLAG; + else + begin + next_block = 1'b1; + next = ST_READ_FLAG; + end + + ST_CLEAR_FLAG: begin + dflag_clr = 1'b1; + next = ST_CADDR_CMD; + end + + ST_CADDR_CMD: begin + spi_data = 8'h2a; + spi_do_cmd = 1'b1; + spi_push = 1'b1; + + next = ST_CADDR_0; + end + + ST_CADDR_0: + if (spi_done) begin + spi_data = caddr_b[15:8]; + spi_do_data = 1'b1; + spi_push = 1'b1; + + next = ST_CADDR_1; + end + + ST_CADDR_1: + if (spi_done) begin + spi_data = caddr_b[7:0]; + spi_push = 1'b1; + + next = ST_CADDR_2; + end + + ST_CADDR_2: + if (spi_done) begin + spi_data = caddr_e[15:8]; + spi_push = 1'b1; + + next = ST_CADDR_3; + end + + ST_CADDR_3: + if (spi_done) begin + spi_data = caddr_e[7:0]; + spi_push = 1'b1; + + next = ST_PADDR_CMD; + end + + ST_PADDR_CMD: + if (spi_done) begin + spi_data = 8'h2b; + spi_do_cmd = 1'b1; + spi_push = 1'b1; + + next = ST_PADDR_0; + end + + ST_PADDR_0: + if (spi_done) begin + spi_data = paddr_b[15:8]; + spi_do_data = 1'b1; + spi_push = 1'b1; + + next = ST_PADDR_1; + end + + ST_PADDR_1: + if (spi_done) begin + spi_data = paddr_b[7:0]; + spi_push = 1'b1; + + next = ST_PADDR_2; + end + + ST_PADDR_2: + if (spi_done) begin + spi_data = paddr_e[15:8]; + spi_push = 1'b1; + + next = ST_PADDR_3; + end + + ST_PADDR_3: + if (spi_done) begin + spi_data = paddr_e[7:0]; + spi_push = 1'b1; + + next = ST_WRITE_CMD; + end + + ST_WRITE_CMD: + if (spi_done) begin + spi_data = 8'h2c; + spi_do_cmd = 1'b1; + spi_push = 1'b1; + start_write = 1'b1; + next = ST_WRITE_CMDW; + end + + ST_WRITE_CMDW: + if (spi_done) + next = ST_FB_READ; + + ST_FB_READ: + if (!fb_busy_ext) begin + fb_busy_int = 1'b1; + next = ST_STORE_PIXEL; + end + + ST_STORE_PIXEL: begin + fb_busy_int = 1'b1; + read_pixel = 1'b1; + next = ST_WRITE_PIXEL_H; + end + + ST_WRITE_PIXEL_H: begin + spi_data = pixel[15:8]; + spi_do_data = 1'b1; + spi_push = 1'b1; + next = ST_WRITE_PIXEL_L; + end + + ST_WRITE_PIXEL_L: + if (spi_done) begin + spi_data = pixel[7:0]; + spi_push = 1'b1; + next = ST_NEXT_PIXEL; + end + + ST_NEXT_PIXEL: + if (spi_done) begin + next_pixel = 1'b1; + + if (pi == (BLOCK_SIZE - 1)) + next = ST_NEXT_BLOCK; + else + next = ST_FB_READ; + end + + ST_NEXT_BLOCK: begin + spi_disable = 1'b1; + next_block = 1'b1; + next = ST_READ_FLAG; + end + endcase + end + +endmodule // lcd_top diff --git a/source/lfsr.sv b/source/lfsr.sv new file mode 100644 index 0000000..4ced9c9 --- /dev/null +++ b/source/lfsr.sv @@ -0,0 +1,37 @@ +`timescale 1ns/100ps +`default_nettype none + +module lfsr #(parameter POLY = 32'hA3000000) + (clock, + preset, + data_i, + prnd_o); + + localparam WIDTH = $size(POLY); + + input wire clock; + input wire preset; + input wire [WIDTH-1:0] data_i; + output wire prnd_o; + + logic [WIDTH-1:0] sreg; + logic feedback; + + initial sreg = '1; + + assign feedback = sreg[0]; + assign prnd_o = feedback; + + integer i; + + always_ff @ (posedge clock) + if (preset) + sreg <= (data_i == '0) ? '1 : data_i; + else begin + sreg[WIDTH-1] <= feedback; + + for (i = 0; i < (WIDTH-1); i ++) + sreg[i] <= POLY[i] ? (sreg[i+1] ^ feedback) : sreg[i+1]; + end + +endmodule // lfsr diff --git a/source/mcp3201_ma.sv b/source/mcp3201_ma.sv new file mode 100644 index 0000000..a26941f --- /dev/null +++ b/source/mcp3201_ma.sv @@ -0,0 +1,133 @@ +`timescale 1ns/100ps +`default_nettype none +`include "assert.vh" + +/* + * MCP3201 controller + * Multichannel and sample rate accurate version. + */ +module mcp3201_ma #(parameter CHANNELS = 1, + parameter CLOCK_FREQ = 12000000, + parameter SCLK_FREQ = 1000000, + parameter SAMPLE_RATE = 44100) + (input wire clock, + input wire reset, + + output reg spi_clk_o, + output reg spi_ssn_o, + input wire [CHANNELS-1:0] spi_miso_i, + + output reg [CHANNELS*12-1:0] data_o, + output reg strb_o); + + initial begin + `assert(CHANNELS > 0); + end + + /* SCLK frequency not need accuracy */ + localparam SCLK_PERIOD = CLOCK_FREQ/SCLK_FREQ; + localparam SCLK_CW = $clog2(SCLK_PERIOD); + + logic [SCLK_CW-1:0] sclk_cnt; + logic sclk_posedge; + + /* Make SPI SCLK */ + always_ff @(posedge clock) + if (reset | spi_ssn_o) begin + spi_clk_o <= 1'b1; + sclk_cnt <= '0; + sclk_posedge <= 1'b0; + end + else begin + sclk_posedge <= 1'b0; + sclk_cnt <= sclk_cnt + 1'b1; + + if (sclk_cnt == SCLK_CW'(SCLK_PERIOD/2)) + spi_clk_o <= 1'b0; + else + if (sclk_cnt == SCLK_CW'(SCLK_PERIOD-1)) begin + spi_clk_o <= 1'b1; + sclk_cnt <= '0; + sclk_posedge <= 1'b1; + end + end + + /* Sample rate need more accuracy */ + localparam SRATE_PERIOD = $rtoi($floor($itor(CLOCK_FREQ)/$itor(SAMPLE_RATE) + 0.5)); + localparam SRATE_CW = $clog2(SRATE_PERIOD); + + logic [SRATE_CW-1:0] srate_cnt; + logic sample; + + always_ff @(posedge clock, posedge reset) + if (reset) begin + sample <= 1'b0; + srate_cnt <= '0; + end + else + if (srate_cnt == SRATE_CW'(SRATE_PERIOD-1)) begin + sample <= 1'b1; + srate_cnt <= '0; + end + else begin + sample <= 1'b0; + srate_cnt <= srate_cnt + 1'b1; + end + + /* Receive data FSM */ + enum int unsigned { + ST_RELAX = 0, + ST_SHIFT, + ST_STROBE + } state; + + logic [3:0] bit_cnt; + logic [11:0] data_sr[CHANNELS]; + integer i; + + always_ff @(posedge clock, posedge reset) + if (reset) begin + state <= ST_RELAX; + bit_cnt <= '0; + spi_ssn_o <= 1'b1; + strb_o <= 1'b0; + data_o <= '0; + + for (i = 0; i < CHANNELS; i ++) + data_sr[i] <= '0; + end + else begin + strb_o <= 1'b0; + + case (state) + ST_RELAX: + if (sample) begin + bit_cnt <= '0; + spi_ssn_o <= 1'b0; + state <= ST_SHIFT; + end + + ST_SHIFT: + if (sclk_posedge) begin + for (i = 0; i < CHANNELS; i ++) + data_sr[i] <= { data_sr[i][10:0], spi_miso_i[i] }; + + bit_cnt <= bit_cnt + 1'b1; + + if (bit_cnt == 4'd14) begin + spi_ssn_o <= 1'b1; + state <= ST_STROBE; + end + end + + ST_STROBE: begin + for (i = 0; i < CHANNELS; i ++) + data_o[i*12 +: 12] <= data_sr[i]; + + strb_o <= 1'b1; + state <= ST_RELAX; + end + endcase + end + +endmodule // mcp3201_ma diff --git a/source/pll.sv b/source/pll.sv new file mode 100644 index 0000000..2dc60ef --- /dev/null +++ b/source/pll.sv @@ -0,0 +1,77 @@ +`timescale 1ns/100ps + +/** + * PLL configuration 12MHz->30MHz + * + * F_PLLOUT: 30.000 MHz (requested) + * F_PLLOUT: 30.000 MHz (achieved) + * + * FEEDBACK: SIMPLE + * F_PFD: 12.000 MHz + * F_VCO: 960.000 MHz + * + * DIVR: 0 (4'b0000) + * DIVF: 79 (7'b1001111) + * DIVQ: 5 (3'b101) + * + * FILTER_RANGE: 1 (3'b001) + */ + +`ifdef VERILATOR + `define TESTBENCH +`endif + +module pll + (input clock_in, + output clock_out, + output locked); + + wire unused_0, unused_1; + +`ifdef TESTBENCH + `ifdef VERILATOR + /* In Verilator just forward clock_in to clock_out */ + assign clock_out = clock_in; + assign locked = 1'b1; + + `else // !VERILATOR + /* In Icarus Verilog generate new clock and 'locked' signal */ + logic clock_tb; + logic lock_tb; + + assign clock_out = clock_tb; + assign locked = lock_tb; + + initial begin + clock_tb = 1'b0; + lock_tb = 1'b0; + repeat (100) @(posedge clock_tb); + lock_tb = 1'b1; + end + + always #(33ns/2) clock_tb <= ~clock_tb; + `endif +`else + /* In HW use PLL primitive */ + SB_PLL40_PAD #(.FEEDBACK_PATH("SIMPLE"), + .DIVR(4'd0), + /* For 30 MHz: DIVF=79, DIVQ=5 + * For 50 MHz: DIVF=66, DIVQ=4 */ + .DIVF(7'd79), + .DIVQ(3'd5), + .FILTER_RANGE(3'd1)) + uut (.PACKAGEPIN (clock_in), + .PLLOUTGLOBAL (clock_out), + .EXTFEEDBACK (1'b0), + .DYNAMICDELAY (8'b0), + .LOCK (locked), + .BYPASS (1'b0), + .RESETB (1'b1), + .LATCHINPUTVALUE(1'b0), + .PLLOUTCORE (unused_0), + .SDO (unused_1), + .SDI (1'b0), + .SCLK (1'b0)); +`endif + +endmodule diff --git a/source/pll_lock_reset.sv b/source/pll_lock_reset.sv new file mode 100644 index 0000000..73eb0ac --- /dev/null +++ b/source/pll_lock_reset.sv @@ -0,0 +1,24 @@ +`timescale 1ns/100ps +`default_nettype none +`include "assert.vh" + +module pll_lock_reset #(parameter RESET_LEN = 8) + (input wire pll_clock, + input wire pll_lock, + output wire reset); + + initial begin + `assert(RESET_LEN > 1); + end + + logic [RESET_LEN:0] rst_sr; + + initial rst_sr = '0; + + always_ff @(posedge pll_clock, negedge pll_lock) + if (~pll_lock) rst_sr <= '0; + else rst_sr <= { 1'b1, rst_sr[RESET_LEN:1] }; + + assign reset = ~rst_sr[0]; + +endmodule // pll_lock_reset diff --git a/source/quadrant_256.rom b/source/quadrant_256.rom new file mode 100644 index 0000000..5071d15 --- /dev/null +++ b/source/quadrant_256.rom @@ -0,0 +1,256 @@ +00ff +02ff +03ff +05ff +06ff +08ff +09ff +0bff +0dff +0eff +10ff +11fe +13fe +14fe +16fe +17fe +19fe +1bfe +1cfd +1efd +1ffd +21fd +22fd +24fc +25fc +27fc +29fc +2afc +2cfb +2dfb +2ffb +30fa +32fa +33fa +35f9 +36f9 +38f9 +39f8 +3bf8 +3cf8 +3ef7 +3ff7 +41f7 +43f6 +44f6 +46f5 +47f5 +49f4 +4af4 +4cf4 +4df3 +4ff3 +50f2 +51f2 +53f1 +54f1 +56f0 +57f0 +59ef +5aee +5cee +5ded +5fed +60ec +62ec +63eb +64ea +66ea +67e9 +69e8 +6ae8 +6ce7 +6de7 +6ee6 +70e5 +71e4 +73e4 +74e3 +75e2 +77e2 +78e1 +7ae0 +7bdf +7cdf +7ede +7fdd +80dc +82dc +83db +84da +86d9 +87d8 +88d7 +8ad7 +8bd6 +8cd5 +8ed4 +8fd3 +90d2 +92d1 +93d0 +94d0 +95cf +97ce +98cd +99cc +9acb +9cca +9dc9 +9ec8 +9fc7 +a1c6 +a2c5 +a3c4 +a4c3 +a5c2 +a7c1 +a8c0 +a9bf +aabe +abbd +acbc +aebb +afba +b0b9 +b1b8 +b2b7 +b3b5 +b4b4 +b5b3 +b7b2 +b8b1 +b9b0 +baaf +bbae +bcac +bdab +beaa +bfa9 +c0a8 +c1a7 +c2a5 +c3a4 +c4a3 +c5a2 +c6a1 +c79f +c89e +c99d +ca9c +cb9a +cc99 +cd98 +ce97 +cf95 +d094 +d093 +d192 +d290 +d38f +d48e +d58c +d68b +d78a +d788 +d887 +d986 +da84 +db83 +dc82 +dc80 +dd7f +de7e +df7c +df7b +e07a +e178 +e277 +e275 +e374 +e473 +e471 +e570 +e66e +e76d +e76c +e86a +e869 +e967 +ea66 +ea64 +eb63 +ec62 +ec60 +ed5f +ed5d +ee5c +ee5a +ef59 +f057 +f056 +f154 +f153 +f251 +f250 +f34f +f34d +f44c +f44a +f449 +f547 +f546 +f644 +f643 +f741 +f73f +f73e +f83c +f83b +f839 +f938 +f936 +f935 +fa33 +fa32 +fa30 +fb2f +fb2d +fb2c +fc2a +fc29 +fc27 +fc25 +fc24 +fd22 +fd21 +fd1f +fd1e +fd1c +fe1b +fe19 +fe17 +fe16 +fe14 +fe13 +fe11 +ff10 +ff0e +ff0d +ff0b +ff09 +ff08 +ff06 +ff05 +ff03 +ff02 diff --git a/source/sugar_lissajous.sv b/source/sugar_lissajous.sv new file mode 100644 index 0000000..2c6ee53 --- /dev/null +++ b/source/sugar_lissajous.sv @@ -0,0 +1,323 @@ +`timescale 1ns/100ps +`default_nettype none + +/* verilator lint_off UNDRIVEN */ +/* verilator lint_off UNUSED */ + +`define VARIANT_1 +//`define VARIANT_2 + +module sugar_lissajous + (input wire CLK12, + + output wire LED_R_N, + output wire LED_G_N, + output wire LED_B_N, + + // PMOD 1: two ADC MCP3201 + output wire P1_1, // R SSN + input wire P1_2, // -- + input wire P1_3, // R DAT + output wire P1_4, // R CLK + output wire P1_9, // L CLK + input wire P1_10, // L DAT + input wire P1_11, // -- + output wire P1_12, // L SSN + + // PMOD 2: LCD + input wire P2_1, // + input wire P2_2, // + output wire P2_3, // DC + input wire P2_4, // + output wire P2_9, // CLK + input wire P2_10, // + output wire P2_11, // MOSI + output wire P2_12 // CSN + ); + +`ifdef VARIANT_1 + localparam POINTS_COUNT = 128; + localparam POINTS_FADING = 1; + localparam DECIMATION = 200; + localparam ANGLE_INCREMENT = 8; +`elsif VARIANT_2 + localparam POINTS_COUNT = 32; + localparam POINTS_FADING = 0; + localparam DECIMATION = 50; + localparam ANGLE_INCREMENT = 30; +`endif + + assign LED_R_N = 1'b1; + assign LED_G_N = 1'b1; + assign LED_B_N = 1'b1; + + logic clock; + logic reset; + logic pll_lock; + + pll pll_i + (.clock_in(CLK12), + .clock_out(clock), + .locked(pll_lock)); + + pll_lock_reset #(.RESET_LEN(8)) reset_i + (.pll_clock(clock), + .pll_lock(pll_lock), + .reset(reset)); + + localparam SPI_SCLK_FREQ = 1000000; + localparam SAMPLE_RATE = 20000; + + logic [11:0] rdata; + logic [11:0] ldata; + logic strb; + + logic adc_ssn, adc_clk; + logic radc_dat, ladc_dat; + + assign P1_1 = adc_ssn; + assign P1_4 = adc_clk; + assign P1_12 = adc_ssn; + assign P1_9 = adc_clk; + + assign radc_dat = P1_3; + assign ladc_dat = P1_10; + + /* Grab audio */ + mcp3201_ma #(.CHANNELS(2), + .CLOCK_FREQ(30000000), + .SCLK_FREQ(SPI_SCLK_FREQ), + .SAMPLE_RATE(SAMPLE_RATE)) adcs_i + (.clock, .reset, + .spi_clk_o(adc_clk), + .spi_ssn_o(adc_ssn), + .spi_miso_i({ radc_dat, ladc_dat }), + .data_o({ rdata, ldata }), + .strb_o(strb)); + + /* Filter audio stream */ + logic [15:0] fir_i; + logic [15:0] fir_o; + logic fir_i_ready; + logic fir_o_valid; + + logic [15:0] fir_abs; + logic fir_strb; + +`ifdef TESTBENCH + always_ff @ (posedge clock) + if (strb) begin + fir_abs <= 1024/2; + fir_strb <= 1'b1; + end + else + fir_strb <= 1'b0; + +`else // !TESTBENCH + + fir_filter fir_impl + (.clock, .reset, + .data_i(fir_i), + .data_o(fir_o), + .ready_i(fir_i_ready), + .valid_o(fir_o_valid)); + + always_ff @ (posedge clock, posedge reset) + if (reset) + fir_i_ready <= 1'b0; + else + if (~fir_i_ready) begin + fir_strb <= 1'b0; + + if (strb) begin + fir_i <= (ldata >= 1024) ? 16'(ldata) - 16'd1024 : 16'd64512 - 16'(ldata); + fir_i_ready <= 1'b1; + end + end + else + if (fir_o_valid) begin + fir_abs <= fir_o[15] ? 16'hffff - fir_o + 1'b1 : fir_o; + fir_i_ready <= 1'b0; + fir_strb <= 1'b1; + end +`endif + + /* Make frame redraw tick */ + logic tick_redraw; + tick_generator #(.PERIOD(600000)) + tick_refresh_gen (.clock, .reset, .tick_o(tick_redraw)); + + logic redraw; + logic redraw_done; + + always_ff @ (posedge clock) + if (redraw_done) + redraw <= 1'b0; + else + if (tick_redraw) + redraw <= 1'b1; + + /* LCD connection */ + logic spi_csn, spi_clk, spi_sdo, dcn; + assign P2_12 = spi_csn; + assign P2_9 = spi_clk; + assign P2_11 = spi_sdo; + assign P2_3 = dcn; + + logic [7:0] lcd_x; + logic [8:0] lcd_y; + logic lcd_req; + logic lcd_ack; + logic lcd_wr; + + logic [15:0] color_w; + logic [15:0] color_r; + + lcd_top #(.SPI_CLK_PERIOD(4)) lcd_i + (.clock, .reset, + .lcd_spi_csn_o(spi_csn), + .lcd_spi_clk_o(spi_clk), + .lcd_spi_dat_o(spi_sdo), + .lcd_spi_dcn_o(dcn), + .redraw_i(redraw), + .done_o(redraw_done), + .x_i(lcd_x), + .y_i(lcd_y), + .color_i(color_w), + .color_o(color_r), + .req_i(lcd_req), + .ack_o(lcd_ack), + .wr_i(lcd_wr)); + + /* Figure drawer */ + logic [7:0] fig_x; + logic [8:0] fig_y; + logic [7:0] fig_h; + logic [7:0] fig_s; + logic [7:0] fig_v; + logic fig_req; + logic fig_ack; + + assign lcd_wr = 1'b1; + + fig_drawer fig_i + (.clock, .reset, + .x_i(fig_x), + .y_i(fig_y), + .h_i(fig_h), + .s_i(fig_s), + .v_i(fig_v), + .req_i(fig_req), + .ack_o(fig_ack), + + .fb_x_o(lcd_x), + .fb_y_o(lcd_y), + .fb_color_o(color_w), + .fb_req_o(lcd_req), + .fb_ack_i(lcd_ack)); + + /* Awesome circle */ + logic [9:0] cir_angle; + logic [7:0] cir_x; + logic [7:0] cir_y; + logic cir_req; + logic cir_ack; + + circle_1024 DUT + (.clock, .reset, + .angle(cir_angle), + .r(fir_abs[11:4]), + .x0(8'd120), + .y0(8'd128), + .x(cir_x), + .y(cir_y), + .req_i(cir_req), + .ack_o(cir_ack)); + + /* Points ring buffer */ + logic [7:0] pt_x; + logic [7:0] pt_y; + logic [7:0] pt_h; + logic pt_req; + logic pt_ack; + + fig_ring #(.POINT_COUNT(POINTS_COUNT), + .FADING(POINTS_FADING)) fig_ring_i + (.clock, .reset, + .pt_x(pt_x), + .pt_y(pt_y), + .pt_h(pt_h), + .pt_req_i(pt_req), + .pt_ack_o(pt_ack), + + .fig_x_o(fig_x), + .fig_y_o(fig_y), + .fig_h_o(fig_h), + .fig_s_o(fig_s), + .fig_v_o(fig_v), + .fig_req_o(fig_req), + .fig_ack_i(fig_ack)); + + /* Decimate audio stream */ + localparam DECIMATION_CW = $clog2(DECIMATION); + + logic [DECIMATION_CW-1:0] decim_cntr; + logic decim_strobe; + + always_ff @ (posedge clock, posedge reset) + if (reset) + decim_cntr <= '0; + else + if (decim_cntr == (DECIMATION-1)) begin + decim_cntr <= '0; + decim_strobe <= 1'b1; + end + else begin + decim_strobe <= 1'b0; + + if (fir_strb) + decim_cntr <= decim_cntr + 1'b1; + end + + /* Draw autio sample on circle */ + enum int unsigned { + ST_FIG_IDLE = 0, + ST_FIG_CIRCLE, + ST_FIG_DRAW + } state_fig; + + always_ff @ (posedge clock, posedge reset) + if (reset) begin + state_fig <= ST_FIG_IDLE; + cir_req <= 1'b0; + pt_req <= 1'b0; + cir_angle <= '0; + pt_h <= '0; + end + else + case (state_fig) + ST_FIG_IDLE: + if (decim_strobe) begin + cir_req <= 1'b1; + state_fig <= ST_FIG_CIRCLE; + end + + ST_FIG_CIRCLE: + if (cir_ack) begin + cir_req <= 1'b0; + pt_x <= cir_x; + pt_y <= cir_y; + pt_req <= 1'b1; + state_fig <= ST_FIG_DRAW; + end + + ST_FIG_DRAW: + if (pt_ack) begin + pt_req <= 1'b0; + cir_angle <= cir_angle + ANGLE_INCREMENT; + pt_h <= pt_h + 1'b1; + state_fig <= ST_FIG_IDLE; + end + endcase + +endmodule // sugar_lissajous diff --git a/source/tick_generator.sv b/source/tick_generator.sv new file mode 100644 index 0000000..3996fbb --- /dev/null +++ b/source/tick_generator.sv @@ -0,0 +1,33 @@ +`timescale 1ns/100ps +`default_nettype none +`include "assert.vh" + +module tick_generator #(parameter PERIOD = 1000) + (input wire clock, + input wire reset, + output reg tick_o); + + initial begin + `assert(PERIOD > 1); + end + + localparam TICK_CW = $clog2(PERIOD); + logic [TICK_CW-1:0] cntr; + + always_ff @(posedge clock, posedge reset) + if (reset) begin + cntr <= '0; + tick_o <= 1'b0; + end + else begin + if (cntr == (PERIOD-1)) begin + cntr <= '0; + tick_o <= 1'b1; + end + else begin + cntr <= cntr + 1'b1; + tick_o <= 1'b0; + end + end + +endmodule // tick_generator diff --git a/testbench/.dir-locals.el b/testbench/.dir-locals.el new file mode 100644 index 0000000..61e855c --- /dev/null +++ b/testbench/.dir-locals.el @@ -0,0 +1,5 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((verilog-mode . ((flycheck-verilator-include-path . ("." "../source")) + (verilog-library-directories . ("." "../source"))))) diff --git a/testbench/.gitignore b/testbench/.gitignore new file mode 100644 index 0000000..27b7923 --- /dev/null +++ b/testbench/.gitignore @@ -0,0 +1,5 @@ +*.out +*.bin +*.vcd +*.gtkw +filtered.txt diff --git a/testbench/Makefile b/testbench/Makefile new file mode 100644 index 0000000..c0eee19 --- /dev/null +++ b/testbench/Makefile @@ -0,0 +1,31 @@ +VC = iverilog +VI = vvp + +#SOURCES = ../source/lcd_320x240_spi.sv +SOURCES = $(wildcard ../source/*.sv) +SOURCES += ../../local/share/yosys/ice40/cells_sim.v + +VFLAGS = -g2012 -I../source +TBS = $(wildcard tb_*.sv) +DEFINES = -D TESTBENCH +VCDDEPS = $(TBS:.sv=.vcd) +BINDEPS = $(TBS:.sv=.bin) + +all: $(VCDDEPS) + +.SECONDARY: +#.SILENT: $(VCDDEPS) $(BINDEPS) clean + +%.vcd: %.bin + @echo "Simulate :" $(<:.bin=.sv) + $(VI) $< #> $(<:.bin=.out) + +%.bin: %.sv $(SOURCES) + @echo "Compile :" $(@:.bin=.sv) + $(VC) $(VFLAGS) $(DEFINES) -D DUMPFILE=\"$(@:.bin=.vcd)\" -o $@ $< $(SOURCES) + +clean: + @echo "Remove *.bin, *.vcd, *.out" + rm -rf *.bin + rm -rf *.out + rm -rf *.vcd diff --git a/testbench/fig_circle_8x8.rom b/testbench/fig_circle_8x8.rom new file mode 120000 index 0000000..4a46ed1 --- /dev/null +++ b/testbench/fig_circle_8x8.rom @@ -0,0 +1 @@ +../source/fig_circle_8x8.rom \ No newline at end of file diff --git a/testbench/fir-magn.m b/testbench/fir-magn.m new file mode 100644 index 0000000..54893fe --- /dev/null +++ b/testbench/fir-magn.m @@ -0,0 +1,9 @@ +pkg load signal + +data = csvread("filtered.txt"); +data = data(8:length(data)); +spec = abs(fft(data ./ 32768)); +len = length(data); +x = 1:len/2; + +plot(x ./ len, mag2db(spec(1:len/2))); diff --git a/testbench/fir_425_50hz_100hz_0db_40db.rom b/testbench/fir_425_50hz_100hz_0db_40db.rom new file mode 120000 index 0000000..bc0a823 --- /dev/null +++ b/testbench/fir_425_50hz_100hz_0db_40db.rom @@ -0,0 +1 @@ +../source/fir_425_50hz_100hz_0db_40db.rom \ No newline at end of file diff --git a/testbench/lcd-model/.clang_complete b/testbench/lcd-model/.clang_complete new file mode 100644 index 0000000..6f8d2b4 --- /dev/null +++ b/testbench/lcd-model/.clang_complete @@ -0,0 +1,2 @@ +-I./obj_dir +-I/usr/share/verilator/include diff --git a/testbench/lcd-model/.dir-locals.el b/testbench/lcd-model/.dir-locals.el new file mode 100644 index 0000000..db5dd52 --- /dev/null +++ b/testbench/lcd-model/.dir-locals.el @@ -0,0 +1,5 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((verilog-mode . ((flycheck-verilator-include-path . ("." "../../source")) + (verilog-library-directories . ("." "../../source"))))) diff --git a/testbench/lcd-model/.gitignore b/testbench/lcd-model/.gitignore new file mode 100644 index 0000000..93d5350 --- /dev/null +++ b/testbench/lcd-model/.gitignore @@ -0,0 +1,2 @@ +obj_dir +*.vcd diff --git a/testbench/lcd-model/Makefile b/testbench/lcd-model/Makefile new file mode 100644 index 0000000..f1869e1 --- /dev/null +++ b/testbench/lcd-model/Makefile @@ -0,0 +1,31 @@ +SOURCE_DIR = ../../source + +SOURCES = testbench_top.cpp \ + testbench_top.sv \ + lcd_ili9341_4spi.sv \ + $(SOURCE_DIR)/sugar_lissajous.sv \ + $(SOURCE_DIR)/pll_lock_reset.sv \ + $(SOURCE_DIR)/pll.sv \ + $(SOURCE_DIR)/mcp3201_ma.sv \ + $(SOURCE_DIR)/lfsr.sv \ + $(SOURCE_DIR)/lcd_top.sv \ + $(SOURCE_DIR)/lcd_spi.sv \ + $(SOURCE_DIR)/ice40_spram.sv \ + $(SOURCE_DIR)/ice40_mac16x16.sv + +SOURCES += ../../../local/share/yosys/ice40/cells_sim.v + +TOP_MODULE = testbench_top + +FLAGS = -DTESTBENCH -Wno-WIDTH -cc -I$(SOURCE_DIR) --top-module $(TOP_MODULE) +1800-2017ext+sv -I$(SOURCE_DIR) +#FLAGS += --threads 8 +FLAGS += --trace + +all: $(SOURCES) + verilator $(FLAGS) --exe --build -o $(TOP_MODULE) $(SOURCES) + +pre: + verilator $(FLAGS) -o $(TOP_MODULE) $(SOURCES) + +clean: + rm -rf obj_dir diff --git a/testbench/lcd-model/fig_circle_8x8.rom b/testbench/lcd-model/fig_circle_8x8.rom new file mode 120000 index 0000000..777664a --- /dev/null +++ b/testbench/lcd-model/fig_circle_8x8.rom @@ -0,0 +1 @@ +../../source/fig_circle_8x8.rom \ No newline at end of file diff --git a/testbench/lcd-model/frontend.rkt b/testbench/lcd-model/frontend.rkt new file mode 100755 index 0000000..f17816f --- /dev/null +++ b/testbench/lcd-model/frontend.rkt @@ -0,0 +1,80 @@ +#! /usr/bin/env racket +#lang racket/gui + +(require racket/gui/base) + +(define display-width 240) +(define display-height 320) +(define fb-size (* display-width display-height)) + +(define (set-pixel! fb x y r g b) + (let ((i (* 4 (+ x (* y display-width))))) + (bytes-set! fb (+ i 0) 255) + (bytes-set! fb (+ i 1) r) + (bytes-set! fb (+ i 2) g) + (bytes-set! fb (+ i 3) b))) + +(define (clear-screen fb r g b) + (for-each + (lambda (n) + (let ((n (* n 4))) + (bytes-set! fb (+ n 0) 255) + (bytes-set! fb (+ n 1) r) + (bytes-set! fb (+ n 2) g) + (bytes-set! fb (+ n 3) b))) + (range fb-size))) + +;;; MAIN +(let* ((frame-buffer (make-bytes (* fb-size 4))) + (frame-bitmap (make-bitmap display-width display-height)) + + (frame (new frame% + (label "LCD") + (min-width display-width) + (min-height display-height) + (stretchable-width #f) + (stretchable-height #f))) + + (canvas (new canvas% (parent frame) + (paint-callback + (lambda (canvas dc) + (send frame-bitmap + set-argb-pixels 0 0 + display-width display-height + frame-buffer) + (send dc draw-bitmap frame-bitmap 0 0))))) + + (cmdl (current-command-line-arguments)) + (pipe (if (zero? (vector-length cmdl)) #f (vector-ref cmdl 0)))) + + + (clear-screen frame-buffer 50 100 150) + (send frame show #t) + + ;; Read pixels data + (thread (λ () + (let ((thunk + (lambda () + (let loop () + (let ((s (read-line))) + (if (eof-object? s) + (loop) ;; (send frame show #f) + (let ((l (string-split s))) + (when (= 5 (length l)) + (let* ((x (string->number (list-ref l 0))) + (y (string->number (list-ref l 1))) + (r (string->number (list-ref l 2))) + (g (string->number (list-ref l 3))) + (b (string->number (list-ref l 4)))) + (set-pixel! frame-buffer x y r g b))) + (loop)))))))) + (if pipe + (with-input-from-file "lcd_pipe" thunk) + (thunk))))) + + ;; Refresh screen + (thread (lambda () + (let loop () + (send canvas refresh) + (sleep 0.02) + (loop))))) diff --git a/testbench/lcd-model/lcd_ili9341_4spi.sv b/testbench/lcd-model/lcd_ili9341_4spi.sv new file mode 100644 index 0000000..46c3778 --- /dev/null +++ b/testbench/lcd-model/lcd_ili9341_4spi.sv @@ -0,0 +1,156 @@ +`timescale 1ns/100ps +`default_nettype none + +module lcd_ili9341_4spi + (input wire clock, + input wire reset, + + input wire csn_i, + input wire clk_i, + input wire sdi_i, + input wire dcn_i, + + output int x_o, + output int y_o, + output logic [7:0] r_o, + output logic [7:0] g_o, + output logic [7:0] b_o, + output logic strobe_o); + + logic [7:0] readed; + logic [7:0] spi_sr; + int bit_cntr; + logic clk_prev; + logic rstrobe; + + always_ff @ (posedge clock) clk_prev <= clk_i; + + always_ff @(posedge clock, posedge csn_i) + if (csn_i || reset) begin + bit_cntr <= 0; + end + else begin + if (clk_prev == 1'b0 && + clk_i == 1'b1) + begin + spi_sr <= { spi_sr[6:0], sdi_i }; + bit_cntr <= bit_cntr + 1; + end + + if (bit_cntr == 8) begin + readed <= spi_sr; + rstrobe <= 1'b1; + bit_cntr <= 0; + end + else + rstrobe <= 1'b0; + end + + enum int unsigned { + ST_IDLE = 0, + ST_CADDR, + ST_PADDR, + ST_MEM_WRITE + } state; + + logic [15:0] x_beg; + logic [15:0] x_end; + logic [15:0] y_beg; + logic [15:0] y_end; + + initial begin + x_beg = 0; + x_end = 239; + y_beg = 0; + y_end = 319; + end + + int n; + int x, y; + + logic [7:0] tmp; + + always_ff @ (posedge clock, posedge csn_i) + if (csn_i || reset) begin + state <= ST_IDLE; + n <= 0; + strobe_o <= 1'b0; + end + else begin + strobe_o <= 1'b0; + + if (rstrobe) begin + if (~dcn_i) begin + case (readed) + 8'h2a: state <= ST_CADDR; + 8'h2b: state <= ST_PADDR; + 8'h2c: begin + x <= int'(x_beg); + y <= int'(y_beg); + state <= ST_MEM_WRITE; + end + default: begin end + endcase + + n <= 0; + end + else + case (state) + ST_CADDR: begin + n <= n + 1; + + case (n) + 0: x_beg[15:8] <= readed; + 1: x_beg[7:0] <= readed; + 2: x_end[15:8] <= readed; + 3: x_end[7:0] <= readed; + endcase + end + + ST_PADDR: begin + n <= n + 1; + + case (n) + 0: y_beg[15:8] <= readed; + 1: y_beg[7:0] <= readed; + 2: y_end[15:8] <= readed; + 3: y_end[7:0] <= readed; + endcase + end + + ST_MEM_WRITE: begin + if (n == 0) begin + n <= 1; + tmp <= readed; + end + else begin + n <= 0; + + // $display("%d %d %d %d %d", x, y, + // { tmp[7:3], 1'b0 }, + // { tmp[2:0], readed[7:5] }, + // { readed[4:0], 1'b0 }); + + x_o <= x; + y_o <= y; + r_o <= { 2'b00, tmp[7:3], 1'b0 }; + g_o <= { 2'b00, tmp[2:0], readed[7:5] }; + b_o <= { 2'b00, readed[4:0], 1'b0 }; + strobe_o <= 1'b1; + + x <= x + 1; + if (x == int'(x_end)) begin + x <= int'(x_beg); + + y <= y + 1; + if (y == int'(y_end)) + y <= int'(y_beg); + end + + end + end + endcase + end + end + +endmodule // lcd_ili9341_4spi diff --git a/testbench/lcd-model/lcd_init.rom b/testbench/lcd-model/lcd_init.rom new file mode 100644 index 0000000..1d6ca1c --- /dev/null +++ b/testbench/lcd-model/lcd_init.rom @@ -0,0 +1,61 @@ +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 +000 diff --git a/testbench/lcd-model/quadrant_256.rom b/testbench/lcd-model/quadrant_256.rom new file mode 120000 index 0000000..8accb24 --- /dev/null +++ b/testbench/lcd-model/quadrant_256.rom @@ -0,0 +1 @@ +../../source/quadrant_256.rom \ No newline at end of file diff --git a/testbench/lcd-model/testbench_top.cpp b/testbench/lcd-model/testbench_top.cpp new file mode 100644 index 0000000..5666d99 --- /dev/null +++ b/testbench/lcd-model/testbench_top.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include + +#include +#include "Vtestbench_top.h" + +#define DUMPFILE "testbench_top.vcd" +#define PIPE_FILE "lcd_pipe" + +/* Clock period in timescale units + * In datapath.sv uses 100ps time unit */ +#define CLOCK_PERIOD 2 +#define TIMESCALE 20000 + +/* Simulation time */ +uint64_t simtime = 0; + +/* Clock cycle counter */ +uint64_t cycle = 0; + +/* Called by $time in Verilog */ +double sc_time_stamp() { + return simtime; +} + + +int main(int argc, char **argv) +{ + Verilated::commandArgs(argc, argv); + + /* Create model instance */ + Vtestbench_top *dp = new Vtestbench_top; + + /* Enable trace if compiled with --trace flag */ +#if (VM_TRACE == 1) + VerilatedVcdC *vcd = NULL; + const char* trace_flag = Verilated::commandArgsPlusMatch("trace"); + + if (trace_flag && (strcmp(trace_flag, "+trace") == 0)) + { + Verilated::traceEverOn(true); + vcd = new VerilatedVcdC; + dp->trace(vcd, 99); + vcd->open(DUMPFILE); + } +#endif + + /* Open pipe */ + FILE *o_file = fopen(PIPE_FILE, "w"); + if (!o_file) { + printf("ERROR: Can't open file/pipe '%s'\n", PIPE_FILE); + delete dp; + return -1; + } + + int posedge_clock = 0; + + int data_loops = 6; + uint64_t check_cycle; + + /* Initial */ + dp->reset = 1; + dp->clock = 0; + + while (!Verilated::gotFinish()) + { + posedge_clock = 0; + if ((simtime % (CLOCK_PERIOD/2)) == 0) { + dp->clock = !dp->clock; + if (dp->clock) { + posedge_clock = 1; + cycle ++; + } + } + + /* release reset at 200 simulation cycle */ + if (simtime == 200) dp->reset = 0; + + dp->eval(); + + /* ouput data */ + if (posedge_clock && !dp->reset && dp->strobe) + fprintf(o_file, "%i %i %i %i %i\n", + dp->x, dp->y, dp->r << 2, dp->g << 2, dp->b << 2); + +#if (VM_TRACE == 1) + if (vcd) + vcd->dump(simtime * TIMESCALE); +#endif + + simtime ++; + } + + dp->final(); + printf("[%lu] Stop simulation\n", simtime); + +#if (VM_TRACE == 1) + if (vcd) vcd->close(); +#endif + + fclose(o_file); + delete dp; + + return 0; +} diff --git a/testbench/lcd-model/testbench_top.sv b/testbench/lcd-model/testbench_top.sv new file mode 100644 index 0000000..9b89bfb --- /dev/null +++ b/testbench/lcd-model/testbench_top.sv @@ -0,0 +1,41 @@ +`timescale 1ns/100ps +`default_nettype none + +/* verilator lint_off PINMISSING */ + +module testbench_top + (input wire clock, + input wire reset, + + output int x, + output int y, + output [7:0] r, + output [7:0] g, + output [7:0] b, + output strobe); + + logic csn, mosi, clk, dcn; + + sugar_lissajous DUT + (.CLK12(clock), + .P1_3(1'b0), + .P1_10(1'b1), + .P2_3(dcn), + .P2_9(clk), + .P2_11(mosi), + .P2_12(csn)); + + lcd_ili9341_4spi LCD + (.clock, .reset, + .csn_i(csn), + .clk_i(clk), + .sdi_i(mosi), + .dcn_i(dcn), + .x_o(x), + .y_o(y), + .r_o(r), + .g_o(g), + .b_o(b), + .strobe_o(strobe)); + +endmodule // testbench_top diff --git a/testbench/lcd_init.rom b/testbench/lcd_init.rom new file mode 120000 index 0000000..b94a8a5 --- /dev/null +++ b/testbench/lcd_init.rom @@ -0,0 +1 @@ +../source/lcd_init.rom \ No newline at end of file diff --git a/testbench/quadrant_256.rom b/testbench/quadrant_256.rom new file mode 120000 index 0000000..aadf6d6 --- /dev/null +++ b/testbench/quadrant_256.rom @@ -0,0 +1 @@ +../source/quadrant_256.rom \ No newline at end of file diff --git a/testbench/tb_circle.sv b/testbench/tb_circle.sv new file mode 100644 index 0000000..f8a6835 --- /dev/null +++ b/testbench/tb_circle.sv @@ -0,0 +1,60 @@ +`timescale 1ns/100ps + +module tb_circle; + logic clock = 1'b0; + logic reset = 1'b1; + + /* Master clock 100MHz (10ns period) */ + always #(10ns/2) clock <= ~clock; + + logic [9:0] angle; + logic [7:0] r; + logic [7:0] x0; + logic [7:0] y0; + logic [7:0] x; + logic [7:0] y; + logic req, ack; + + circle_1024 DUT + (.clock, .reset, + .angle, + .r, + .x0, + .y0, + .x, + .y, + .req_i(req), + .ack_o(ack)); + + initial begin + reset = 1'b1; + req = 1'b0; + repeat(10) @(posedge clock) #1; + reset = 1'b0; + + @(posedge clock) #1; + + angle = '0; + r = 120; + x0 = 120; + y0 = 128; + + for (int i = 0; i < 1024; i ++) begin + @(posedge clock) #1; + req = 1'b1; + + wait (ack); + angle = angle + 1'b1; + end + req = 1'b0; + + repeat(10) @(posedge clock) #1; + $finish; + end + + initial begin + $dumpfile("tb_circle.vcd"); + $dumpvars; + end + +endmodule // tb_circle diff --git a/testbench/tb_fig_drawer.sv b/testbench/tb_fig_drawer.sv new file mode 100644 index 0000000..baba3ca --- /dev/null +++ b/testbench/tb_fig_drawer.sv @@ -0,0 +1,70 @@ +`timescale 1ns/100ps + +module tb_fig_drawer; + logic clock = 1'b0; + logic reset = 1'b1; + + /* Master clock 100MHz (10ns period) */ + always #(10ns/2) clock <= ~clock; + + logic [7:0] x; + logic [8:0] y; + logic [7:0] h, s, v; + logic req, ack; + + logic [7:0] fb_x; + logic [8:0] fb_y; + logic [15:0] fb_color; + logic fb_req, fb_ack; + + fig_drawer DUT + (.clock, .reset, + .x_i(x), .y_i(y), + .h_i(h), .s_i(s), .v_i(v), + .req_i(req), .ack_o(ack), + + .fb_x_o(fb_x), + .fb_y_o(fb_y), + .fb_color_o(fb_color), + .fb_req_o(fb_req), + .fb_ack_i(fb_ack)); + + int fb_lat_n; + always_ff @ (posedge clock) begin + fb_ack <= 1'b0; + + if (fb_req) + if (fb_lat_n == 2) begin + fb_ack <= 1'b1; + fb_lat_n <= 0; + end + else fb_lat_n <= fb_lat_n + 1; + end + + always_ff @ (posedge clock) + if (ack) + req <= 1'b0; + + initial begin + reset <= 1'b1; + repeat(10) @(posedge clock); + reset <= 1'b0; + + @(posedge clock); + x <= 'd20; + y <= 'd50; + h <= 50; + s <= 100; + v <= 150; + req <= 1'b1; + + repeat(1000) @(posedge clock); + $finish; + end + + initial begin + $dumpfile("tb_fig_drawer.vcd"); + $dumpvars; + end + +endmodule // tb_fig_drawer diff --git a/testbench/tb_fig_ring.sv b/testbench/tb_fig_ring.sv new file mode 100644 index 0000000..0f8ee6d --- /dev/null +++ b/testbench/tb_fig_ring.sv @@ -0,0 +1,69 @@ +`timescale 1ns/100ps + +module tb_fig_ring; + logic clock = 1'b0; + logic reset = 1'b1; + + /* Master clock 100MHz (10ns period) */ + always #(10ns/2) clock <= ~clock; + + logic pt_ack_o; + logic [7:0] fig_x_o; + logic [8:0] fig_y_o; + logic [7:0] fig_h_o; + logic [7:0] fig_s_o; + logic [7:0] fig_v_o; + logic fig_req_o; + logic [7:0] pt_x; + logic [7:0] pt_y; + logic [7:0] pt_h; + logic pt_req_i; + logic fig_ack_i; + + fig_ring DUT (/*AUTOINST*/ + // Outputs + .pt_ack_o (pt_ack_o), + .fig_x_o (fig_x_o[7:0]), + .fig_y_o (fig_y_o[8:0]), + .fig_h_o (fig_h_o[7:0]), + .fig_s_o (fig_s_o[7:0]), + .fig_v_o (fig_v_o[7:0]), + .fig_req_o (fig_req_o), + // Inputs + .clock (clock), + .reset (reset), + .pt_x (pt_x[7:0]), + .pt_y (pt_y[7:0]), + .pt_h (pt_h[7:0]), + .pt_req_i (pt_req_i), + .fig_ack_i (fig_ack_i)); + + assign fig_ack_i = 1'b1; + + always_ff @ (posedge clock) + if (pt_ack_o) + pt_req_i <= 1'b0; + + initial begin + reset = 1'b1; + pt_req_i = 1'b0; + + repeat(10) @(posedge clock) #1; + reset = 1'b0; + + @(posedge clock) #1; + pt_x = 0; + pt_y = 0; + pt_h = 100; + pt_req_i = 1'b1; + + repeat(1000) @(posedge clock) #1; + $finish; + end + + initial begin + $dumpfile("tb_fig_ring.vcd"); + $dumpvars; + end + +endmodule // tb_fig_ring diff --git a/testbench/tb_fir_filter.sv b/testbench/tb_fir_filter.sv new file mode 100644 index 0000000..9d3e26d --- /dev/null +++ b/testbench/tb_fir_filter.sv @@ -0,0 +1,66 @@ +`timescale 1ns/100ps + +module tb_fir_filter; + logic clock = 1'b0; + logic reset = 1'b1; + + /* Master clock 100MHz (10ns period) */ + always #(10ns/2) clock <= ~clock; + + logic signed [15:0] data_i; + logic signed [15:0] data_o; + logic input_ready, output_valid; + + localparam FILTER_LEN = 425; + + fir_filter #(.LEN(FILTER_LEN), + .COEFFS_ROM_FILE("fir_425_50hz_100hz_0db_40db.rom")) DUT + (.clock, .reset, + .data_i, .data_o, + .ready_i(input_ready), + .valid_o(output_valid)); + + event done; + integer file_o; + + initial begin + file_o = $fopen("filtered.txt", "w"); + @(done); + $fclose(file_o); + $finish; + end + + initial begin + reset = 1'b1; + input_ready = 1'b0; + + repeat(10) @(posedge clock) #1; + reset = 1'b0; + + @(posedge clock) #1; + data_i = 16'd32767; + input_ready = 1'b1; + + @(posedge clock) #1; + wait (output_valid) + $fdisplay(file_o, "%d", data_o); + data_i = '0; + + for (int i = 1; i < FILTER_LEN; i ++) begin + @(posedge clock) #1; + wait (output_valid) + $fdisplay(file_o, "%d", data_o); + end + + ->done; + + repeat(10) @(posedge clock) #1; + $finish; + end + + initial begin + $dumpfile("tb_fir_filter.vcd"); + $dumpvars; + end + +endmodule // tb_fir_filter diff --git a/testbench/tb_hsl2rgb.sv b/testbench/tb_hsl2rgb.sv new file mode 100644 index 0000000..c10eb05 --- /dev/null +++ b/testbench/tb_hsl2rgb.sv @@ -0,0 +1,49 @@ +`timescale 1ns/100ps + +module tb_hsl2rgb; + logic clock = 1'b0; + logic reset = 1'b1; + + /* Master clock 100MHz (10ns period) */ + always #(10ns/2) clock <= ~clock; + + logic [7:0] h, s, l; + logic [7:0] r, g, b; + logic valid, ready; + + hsl2rgb DUT + (.clock, .reset, + .h, .s, .l, + .ready_i(ready), + .r, .g, .b, + .valid_o(valid)); + + always_ff @ (posedge clock) + if (valid) + $display("%d %d %d", r, g, b); + + initial begin + reset = 1'b1; + ready = 1'b0; + repeat(10) @(posedge clock) #1; + reset = 1'b0; + + @(posedge clock) #1; + h = 8'd128; + s = 8'd255; + l = 8'd130; + ready = 1'b1; + + @(posedge clock) #1; + ready = 1'b0; + + repeat(20) @(posedge clock); + $finish; + end + + initial begin + $dumpfile("tb_hsl2rgb.vcd"); + $dumpvars; + end + +endmodule // tb_hsl2rgb diff --git a/testbench/tb_hsv2rgb.sv b/testbench/tb_hsv2rgb.sv new file mode 100644 index 0000000..034d614 --- /dev/null +++ b/testbench/tb_hsv2rgb.sv @@ -0,0 +1,78 @@ +`timescale 1ns/100ps + +module tb_hsv2rgb; + logic clock = 1'b0; + logic reset = 1'b1; + + /* Master clock 100MHz (10ns period) */ + always #(10ns/2) clock <= ~clock; + + logic [7:0] h, s, v; + logic [7:0] r, g, b; + logic valid, ready; + + hsv2rgb DUT + (.clock, .reset, + .h, .s, .v, + .ready_i(ready), + .r, .g, .b, + .valid_o(valid)); + + always_ff @ (posedge clock) + if (valid) + $display("%d %d %d", r, g, b); + + initial begin + reset = 1'b1; + ready = 1'b0; + repeat(10) @(posedge clock) #1; + reset = 1'b0; + + @(posedge clock) #1; + h = 8'd50; + s = 8'd100; + v = 8'd150; + ready = 1'b1; + + @(posedge clock) #1; + h = 8'dx; + s = 8'dx; + v = 8'dx; + ready = 1'b0; + + @(posedge clock) #1; + @(posedge clock) #1; + @(posedge clock) #1; + + @(posedge clock) #1; + h = 8'd50; + s = 8'd100; + v = 8'd150; + ready = 1'b1; + + @(posedge clock) #1; + h = 8'd111; + s = 8'd222; + v = 8'd33; + ready = 1'b1; + + @(posedge clock) #1; + h = 8'd200; + s = 8'd150; + v = 8'd50; + ready = 1'b1; + + @(posedge clock) #1; + ready = 1'b0; + + + repeat(10) @(posedge clock); + $finish; + end + + initial begin + $dumpfile("tb_hsv2rgb.vcd"); + $dumpvars; + end + +endmodule // tb_hsv2rgb diff --git a/testbench/tb_lcd_spi.sv b/testbench/tb_lcd_spi.sv new file mode 100644 index 0000000..bb3391f --- /dev/null +++ b/testbench/tb_lcd_spi.sv @@ -0,0 +1,63 @@ +`timescale 1ns/100ps + +module tb_lcd_spi; + logic clock = 1'b0; + logic reset = 1'b1; + + /* Master clock 50MHz (20ns period) */ + always #(20ns/2) clock <= ~clock; + + logic [7:0] data; + logic push, done; + logic sclk, sdo; + + lcd_spi #(.DATA_WIDTH(8), + .SPI_CLK_PERIOD(16)) DUT + (.clock, .reset, + .data_i(data), + .push_i(push), + .done_o(done), + .spi_clk_o(sclk), + .spi_dat_o(sdo)); + + int state; + + always_ff @(posedge clock) + if (reset) begin + push <= 1'b0; + state <= 0; + end + else begin + case (state) + 0: begin + data <= $random; + push <= 1'b1; + state <= 1; + end + + 1: begin + if (done) begin + //data <= $random; + push <= 1'b0; + state <= 0; + end + end + endcase + end + + + initial begin + reset = 1'b1; + repeat(10) @(posedge clock) #1; + reset = 1'b0; + + repeat(1000) @(posedge clock); + $finish; + end + + initial begin + $dumpfile("tb_lcd_spi.vcd"); + $dumpvars; + end + +endmodule // tb_lcd_spi