sugar-lissajous/source/lcd_top.sv
2021-02-28 18:59:56 +03:00

738 lines
19 KiB
Systemverilog

`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